----------------------------------------------------------------------------------
--
-- Copyright CERN 2011.
-- 
-- This documentation describes Open Hardware and is licensed under the
-- CERN OHL v. 1.1.
--
-- You may redistribute and modify this documentation under the terms of the CERN
-- OHL v.1.1. (http://ohwr.org/cernohl).
--
-- This documentation is distributed WITHOUT
-- ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY
-- QUALITY AND FITNESS FOR A PARTICULAR PURPOSE. Please see the CERN OHL v.1.1 for
-- applicable conditions.
--
----------------------------------------------------------------------------------
--
-- Company:        CERN, BE-CO-HT 
-- Engineer:       Carlos Gil Soriano
-- 
-- Create Date:    23:57:51 06/10/2012 
-- Design Name:    m25p32 wishbone access
-- Module Name:    m25p32_core - Behavioral 
-- Project Name:   Level Conversion Circuits
-- Target Devices: Spartan 6
-- Tool versions: 
-- Description:    This is the logic that governs the access to the m25p32 Non
--                 volatile Flash memory
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
library work;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use work.m25p32_pkg.ALL;
use work.spi_master_pkg.ALL;


entity m25p32_core is
   generic(g_INST_LENGTH   : NATURAL := c_INST_LENGTH;
           g_ADDR_LENGTH   : NATURAL := c_ADDR_LENGTH;
           g_PAGE_SIZE     : NATURAL := c_PAGE_SIZE;
           g_CLK_I_PERIOD  : NATURAL := c_CLK_I_PERIOD);
   port (
           wb_rst_i    : in  STD_LOGIC;
           wb_clk      : in  STD_LOGIC;

           mosi_o      : out STD_LOGIC; 
           miso_i      : in  STD_LOGIC;  
           sclk_o      : out STD_LOGIC;  
           ss_n_o      : out STD_LOGIC; 

           wr_data_i   : in  STD_LOGIC_VECTOR(g_PAGE_SIZE*8   - 1  downto 0);
           rd_data_o   : out STD_LOGIC_VECTOR(r_SPI3'a_length - 1  downto 0);
           SR_m25p32_i : in  STD_LOGIC_VECTOR(r_SR_m25p32'a_length - 1 downto 0);

           rd_SPI3_o   : out STD_LOGIC;

           OP_o        : out STD_LOGIC_VECTOR (r_OP'a_length  - 1 downto 0);
           FMI_i       : in  STD_LOGIC_VECTOR (r_FMI'a_length - 1 downto 0)
        );
end m25p32_core;

architecture Behavioral of m25p32_core is

   type t_m25p32_fsm  is (R0_RESET,
                          S0_IDLE,
                          S1B_WREN,
                          S1_WREN,
                          S2B_SPI_INST,
                          S2_SPI_INST,
                          S3B_WRDI,
                          S3_WRDI,
                          Q0_END,
                          Q1_ERROR);


signal s_MEM_fsm           : t_m25p32_fsm;
signal s_MEM_fsm_d0        : t_m25p32_fsm;

signal s_OP                : r_OP;
signal s_FMI               : r_FMI;

--! Registers from spi_master_pkg
signal s_SPI0              : r_SPI0;
signal s_SPI0_d0           : r_SPI0;
signal s_SPI1              : r_SPI1;
signal s_SPI1_d0           : r_SPI1;
signal s_SPI2              : r_SPI2;
signal s_SPI0_slv          : STD_LOGIC_VECTOR (r_SPI0'a_length - 1 downto 0);
signal s_SPI1_slv          : STD_LOGIC_VECTOR (r_SPI1'a_length - 1 downto 0);
signal s_SPI2_slv          : STD_LOGIC_VECTOR (r_SPI2'a_length - 1 downto 0);
signal s_SPI3_slv          : STD_LOGIC_VECTOR (r_SPI3'a_length - 1 downto 0);

signal s_rd_SPI3           : STD_LOGIC;

signal s_inst_spi          : STD_LOGIC_VECTOR (8*g_INST_LENGTH - 1 downto 0);
signal s_addr_spi          : STD_LOGIC_VECTOR (8*g_ADDR_LENGTH - 1 downto 0);
signal s_data_i_spi        : STD_LOGIC_VECTOR (8*g_PAGE_SIZE   - 1 downto 0);

signal s_inst_spi_d0       : STD_LOGIC_VECTOR (8*g_INST_LENGTH - 1 downto 0);
signal s_addr_spi_d0       : STD_LOGIC_VECTOR (8*g_ADDR_LENGTH - 1 downto 0);
signal s_data_i_spi_d0     : STD_LOGIC_VECTOR (8*g_PAGE_SIZE   - 1 downto 0);


begin

   OP_o          <= f_STD_LOGIC_VECTOR(s_OP);
   s_FMI         <= f_FMI(FMI_i);

   s_SPI0_slv    <= f_STD_LOGIC_VECTOR(s_SPI0);
   s_SPI1_slv    <= f_STD_LOGIC_VECTOR(s_SPI1);
   s_SPI2        <= f_SPI2(s_SPI2_slv);
   rd_data_o     <= s_SPI3_slv;
   rd_SPI3_o     <= s_rd_SPI3;

   inst_spi_master_core: spi_master_core
   generic map(
       g_inst_length => c_INST_LENGTH,
       g_addr_length => c_ADDR_LENGTH,
       g_data_length => c_PAGE_SIZE,
       g_clk_i_period => c_CLK_I_PERIOD
   ) port map(
       rst_i       => wb_rst_i, 
       clk_i       => wb_clk, 

       inst_i      => s_inst_spi, 
       addr_i      => s_addr_spi, 
       data_i      => s_data_i_spi, 

       SPI0_i      => s_SPI0_slv,
       SPI1_i      => s_SPI1_slv,
       SPI2_o      => s_SPI2_slv,
       SPI3_o      => s_SPI3_slv,

       rd_SPI3_o   => s_rd_SPI3,

       spi_mosi_o  => mosi_o,
       spi_miso_i  => miso_i,
       spi_clk_o   => sclk_o,
       spi_cs_n_o  => ss_n_o 
   );

   p_delays : process (wb_clk)
   begin
      if rising_edge(wb_clk) then
         s_SPI0_d0       <= s_SPI0;
         s_SPI1_d0       <= s_SPI1;
         s_inst_spi_d0   <= s_inst_spi;
         s_addr_spi_d0   <= s_addr_spi;
         s_data_i_spi_d0 <= s_data_i_spi;
      end if;
   end process;

   --! @brief Combinational process that sets a SPI operation thanks
   --!        to SPI0 and SPI1.
   --! @param s_MEM_fsm    Current state of the m25p32 SPI handler
   --!        s_MEM_fsm_d0 One clock delay of s_MEM_fsm 
   p_comb_SPIx : process (s_MEM_fsm, s_MEM_fsm_d0)

      procedure setup_SPI0 (operation : t_operations) is
      begin
         case operation is
            when RDID   =>
               s_SPI0 <= c_SPI0_RDID;
            when RDSR   =>
               s_SPI0 <= c_SPI0_RDSR;
            when WRSR   =>
               s_SPI0 <= c_SPI0_WRSR;
            when READ   =>
               s_SPI0 <= c_SPI0_READ;
            when PP     =>
               s_SPI0 <= c_SPI0_PP;
            when SE     =>
               s_SPI0 <= c_SPI0_SE;
            when BE     =>
               s_SPI0 <= c_SPI0_BE;
            when others =>
               s_SPI0 <= c_SPI0_IDLE;
         end case;
      end procedure;

      procedure setup_SPI1 (operation : t_operations) is
      begin
         case operation is
            when RDID   =>
               s_SPI1 <= c_SPI1_RDID;
            when RDSR   =>
               s_SPI1 <= c_SPI1_RDSR;
            when WRSR   =>
               s_SPI1 <= c_SPI1_WRSR;
            when READ   =>
               s_SPI1 <= c_SPI1_READ;
            when PP     =>
               s_SPI1 <= c_SPI1_PP;
            when SE     =>
               s_SPI1 <= c_SPI1_SE;
            when BE     =>
               s_SPI1 <= c_SPI1_BE;
            when others =>
               s_SPI1 <= c_SPI1_IDLE;
         end case;
      end procedure;

      procedure place_inst (operation : t_operations) is
         variable v_inst_spi  : STD_LOGIC_VECTOR(8*g_INST_LENGTH - 1 downto 0);
      begin
         v_inst_spi := (others => '0');
         case operation is
            when RDID   =>
               v_inst_spi := c_RDID_INST;
            when RDSR   =>
               v_inst_spi := c_RDSR_INST;
            when WRSR   =>
               v_inst_spi := c_WRSR_INST;
            when READ   =>
               v_inst_spi := c_READ_INST;
            when PP     =>
               v_inst_spi := c_PP_INST;
            when SE     =>
               v_inst_spi := c_SE_INST;
            when BE     =>
               v_inst_spi := c_BE_INST;
            when others =>
               null;
         end case;
         s_inst_spi(s_inst_spi'length - 1 downto
                    s_inst_spi'length - 1 - (v_inst_spi'length- 1))
                                                                 <= v_inst_spi;
      end procedure;

      procedure place_addr (operation : t_operations) is
         variable v_addr_spi : STD_LOGIC_VECTOR(c_M25P32_ADDR_SIZE*8 - 1
                                                                     downto 0);
      begin
         if    operation = READ
            or operation = PP
            or operation = SE then
            v_addr_spi :=   c_ADDR_HEADER_PAD
                          & STD_LOGIC_VECTOR(s_FMI.SCT)
                          & STD_LOGIC_VECTOR(s_FMI.PG)
                          & c_ADDR_FOOTER_PAD;
         else
            v_addr_spi := (others => '0');
         end if;

         s_addr_spi(s_addr_spi'length - 1 downto
                    s_addr_spi'length - 1 - (c_M25P32_ADDR_SIZE*8 - 1))
                                                                 <= v_addr_spi;
      end procedure;

      procedure place_data (operation : t_operations) is
         variable v_data_i_spi  : STD_LOGIC_VECTOR(s_data_i_spi'length - 1 downto 0);
      begin
         if operation = WRSR then
            v_data_i_spi(s_data_i_spi'length - 1 downto
                         s_data_i_spi'length - 1 - (r_SR_m25p32'a_length - 1))
                                                                := SR_m25P32_i;
         else
            v_data_i_spi := wr_data_i;
         end if;
         s_data_i_spi <= v_data_i_spi;
      end procedure;

   begin
      s_SPI0       <= s_SPI0_d0;
      s_SPI1       <= c_SPI1_IDLE;

      s_inst_spi   <= s_inst_spi_d0;
      s_addr_spi   <= s_addr_spi_d0;
      s_data_i_spi <= s_data_i_spi_d0;

      if s_MEM_fsm_d0 /= s_MEM_fsm then
         case s_MEM_fsm is
            when S1B_WREN  =>
               s_inst_spi <= c_WREN_inst;
               s_SPI0     <= c_SPI0_WREN;
            when S1_WREN   =>
               s_SPI1    <= c_SPI1_WREN;
            when S2B_SPI_INST =>
               place_inst(s_FMI.OP);
               place_addr(s_FMI.OP);
               place_data(s_FMI.OP);
               setup_SPI0(s_FMI.OP);
            when S2_SPI_INST  =>
               setup_SPI1(s_FMI.OP);
            when S3B_WRDI  =>
               s_inst_spi <= c_WRDI_inst;
               s_SPI0    <= c_SPI0_WRDI;
            when S3_WRDI   =>
               s_SPI1    <= c_SPI1_WRDI;
            when others =>
               null;
         end case;
      end if;
   end process p_comb_SPIx;

   --! @brief Combinational process that sets OP[X] fields
   --! @param s_MEM_fsm    Current state of the m25p32 SPI handler
   p_comb_OP : process(s_MEM_fsm)
   begin
      case s_MEM_fsm is
         when R0_RESET     =>
            s_OP     <= c_OP_default;
         when S0_IDLE      =>
            s_OP.OPA <= '0';
            s_OP.OPF <= '0';
         when Q0_END       =>
            s_OP.OPA <= '1';
            s_OP.OPF <= '1';
         when Q1_ERROR     =>
            s_OP.OPA <= '1';
            s_OP.OPF <= '1';
         when others =>
            s_OP.OPA <= '1';
            s_OP.OPF <= '0';
      end case;
   end process p_comb_OP;


   --! @brief   Main clocked process that rules the m25p32 fsm.
   --! @details In this process the SPI device is HW controlled via the values
   --!          of the registers in m25p32_regs.
   --!          From the wishbone side, when we have already loaded all the data
   --!          we want to send (in case we want to transfer some data), a write
   --!          in FMI register is requiered to kick out the m25p32 operation.
   --! @param   wb_clk System clock
   p_m25p32_fsm: process (wb_clk)
   begin
      if rising_edge(wb_clk) then
         if wb_rst_i = '1' then
            s_MEM_fsm <= R0_RESET;
         else
            s_MEM_fsm_d0     <= s_MEM_fsm;
            case s_MEM_fsm is
               when R0_RESET     =>
                  s_MEM_fsm <= S0_IDLE;
               when S0_IDLE      =>
                  if s_FMI.OPR = '1' then 
                     s_MEM_fsm <= S1B_WREN;
                  end if;
               when S1B_WREN     => 
                  s_MEM_fsm <= S1_WREN;
               when S1_WREN      =>
                  if s_SPI2.SENT_OP = '1' then
                     s_MEM_fsm <= S2B_SPI_INST;
                  end if;
               when S2B_SPI_INST => 
                  s_MEM_fsm <= S2_SPI_INST;
                  --! We add this assignment here for being less verbose
                  --! Easiest way to add concurrency while performing an SPI
                  --! operation.
               when S2_SPI_INST  => 
                  if s_SPI2.SENT_OP = '1' then
                     s_MEM_fsm <= S3B_WRDI;
                  end if;
               when S3B_WRDI     =>
                  s_MEM_fsm    <= S3_WRDI;
               when S3_WRDI      =>
                  if s_SPI2.SENT_OP = '1' then
                     s_MEM_fsm <= Q0_END;
                  end if;
               when Q0_END    =>
                  s_MEM_fsm <= R0_RESET;
               when Q1_ERROR  =>
                  s_MEM_fsm <= R0_RESET;
               when others =>
                  s_MEM_fsm <= Q1_ERROR;
            end case;
         end if;
      else
      end if;
   end process p_m25p32_fsm;

end Behavioral;


