----------------------------------------------------------------------------------
--
-- 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.m25p32_core_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_DATA_LENGTH   : NATURAL := c_DATA_LENGTH);
   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; 

           inst_db_i     : in  STD_LOGIC_VECTOR (8*g_INST_LENGTH - 1 downto 0);
           addr_db_i     : in  STD_LOGIC_VECTOR (8*g_ADDR_LENGTH - 1 downto 0);
           data_db_i     : in  STD_LOGIC_VECTOR (8*g_DATA_LENGTH - 1 downto 0);

           inst_core_o        : out STD_LOGIC_VECTOR (8*g_INST_LENGTH - 1 downto 0);

           push_i_core_o : out STD_LOGIC;
           push_a_core_o : out STD_LOGIC;
           push_d_core_o : out STD_LOGIC;
           bov_core_o    : out STD_LOGIC;

           CTR0_o        : out STD_LOGIC_VECTOR (r_CTR0'a_length - 1 downto 0);
           FMOH_i        : in  STD_LOGIC_VECTOR (r_FMOH'a_length - 1 downto 0)
        );
end m25p32_core;

architecture Behavioral of m25p32_core is


signal MEM_fsm             : t_m25p32_fsm;
signal MEM_fsm_d0          : t_m25p32_fsm;

signal s_CTR0              : r_CTR0;
signal s_CTR1              : r_CTR1;
signal s_FMOH              : r_FMOH;

--! Registers from spi_master_pkg
signal s_SPI0              : r_SPI0;
signal s_SPI0_slv          : STD_LOGIC_VECTOR (31 downto 0);
signal s_SPI1              : r_SPI1;
signal s_SPI1_slv          : STD_LOGIC_VECTOR (31 downto 0);
signal s_SPI2              : r_SPI2;
signal s_SPI2_slv          : STD_LOGIC_VECTOR (15 downto 0);

signal s_wr_rqt            : STD_LOGIC;

signal s_inst_m25p32_regs  : STD_LOGIC_VECTOR (8*g_INST_LENGTH - 1 downto 0);
signal s_addr_m25p32_regs  : STD_LOGIC_VECTOR (8*g_ADDR_LENGTH - 1 downto 0);
signal s_data_m25p32_regs  : STD_LOGIC_VECTOR (8*g_DATA_LENGTH - 1 downto 0);

signal s_inst_m25p32_core  : STD_LOGIC_VECTOR (8*g_INST_LENGTH - 1 downto 0);

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_spi          : STD_LOGIC_VECTOR (8*g_DATA_LENGTH - 1 downto 0);


begin

   CTR0_o        <= f_STD_LOGIC_VECTOR(s_CTR0);
   push_i_core_o <= s_CTR1.PUSH_I; 
   push_a_core_o <= s_CTR1.PUSH_A; 
   push_d_core_o <= s_CTR1.PUSH_D; 
   bov_core_o    <= s_CTR1.BOV; 

   s_FMOH        <= f_FMOH(FMOH_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);

   s_inst_m25p32_regs  <= inst_db_i;
   s_addr_m25p32_regs  <= addr_db_i;
   s_data_m25p32_regs  <= data_db_i;

   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_DATA_LENGTH
   ) 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_spi, 

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

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


   --! if s_FMOH.BOV, Buffer OVerride flag is set to one, the SPI buffer
   --! is loaded from the m25p32 instead than from the double buffer.
   s_inst_spi <= s_inst_m25p32_regs when s_CTR1.BOV = '0'
                   else s_inst_m25p32_core;

   s_addr_spi <= s_addr_m25p32_regs;

   --! TODO: Reorder bytes depending on the length of the instruction
   s_data_spi (8*(g_DATA_LENGTH - 1) - 1 downto 0)
                   <= (others => '0') when s_FMOH.OP  = WRSR 
                 else  s_data_m25p32_regs (8*(g_DATA_LENGTH - 1) - 1 downto 0);

   s_data_spi (8*g_DATA_LENGTH - 1 downto 8*(g_DATA_LENGTH - 1))
                   <= s_data_m25p32_regs (7 downto 0) when s_FMOH.OP = WRSR 
                 else s_data_m25p32_regs (8*g_DATA_LENGTH - 1 downto
                                          8*(g_DATA_LENGTH - 1));


   --! @brief This is the combinational process that sets up the correct
   --!        instruction field from m25p32_core.
   --!        This instruction fields are only set for immediate bypassing
   --!        in WREN and WRDI instructions (beginning and end of frame). 
   p_inst_m25p32_core: process(s_CTR1.BOV, MEM_fsm)
   begin
      if s_CTR1.BOV = '1' then
         case MEM_fsm is 
            when S1_WREN               =>
               s_inst_m25p32_core    <= c_WREN_inst;
            when S4_WRDI               =>
               s_inst_m25p32_core    <= c_WRDI_inst;
            when others                =>
               s_inst_m25p32_core    <= (others => '0');
         end case;
      end if;
   end process;

   --! @brief This is the combinational process that sets up the correct
   --!        instruction field from m25p32_core to be loaded into the
   --!        double buffers in m25p32_regs. 
   p_inst_m25p32_regs_i : process(s_FMOH.OP)
   begin
      case s_FMOH.OP is
         when ERS       =>
            inst_core_o    <= c_SE_inst; 
         when RDP       =>
            inst_core_o    <= c_READ_inst; 
         when WRP       =>
            inst_core_o    <= c_PP_inst;
         when RDSR      =>
            inst_core_o    <= c_RDSR_inst; 
         when WRSR      =>
            inst_core_o    <= c_WRSR_inst; 
         when others    =>
            inst_core_o    <= (others => '0');
      end case;
   end process;


--! @brief   Main clocked process that rules the 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 FMOH register is requiered to kick out the m25p32 operation.
p_out: process (wb_clk)

   --! @brief   Procedure for setting up correctly the push values of a CTR1
   --!          register depending upon the value of a certin default SPI1
   --!          register state.
   --! @details This procedure will be typically called for pushing the values
   --!          inside the double buffer in m25p32_regs.vhd
   --! @param   Register which PUSH_[x] bits must be updated
   procedure decode_SPI1_pushes (signal reg_register : out r_CTR1;
                                 signal operation    : in  t_operations) is
   begin
      case operation  is
         when WRSR                    => 
            reg_register.PUSH_I     <= c_SPI1_WRSR.PUSH_INST; 
            reg_register.PUSH_A     <= c_SPI1_WRSR.PUSH_ADDR; 
            reg_register.PUSH_D     <= c_SPI1_WRSR.PUSH_DATA; 
         when WRP                     =>  
            reg_register.PUSH_I     <= c_SPI1_PP.PUSH_INST; 
            reg_register.PUSH_A     <= c_SPI1_PP.PUSH_ADDR; 
            reg_register.PUSH_D     <= c_SPI1_PP.PUSH_DATA; 
         when ERS                     =>
            reg_register.PUSH_I     <= c_SPI1_SE.PUSH_INST; 
            reg_register.PUSH_A     <= c_SPI1_SE.PUSH_ADDR; 
            reg_register.PUSH_D     <= c_SPI1_SE.PUSH_DATA; 
         when others                  =>
            null;
      end case;
   end procedure;


   --! @brief   Procedure for setting up correctly the push values of a SPI0
   --!          register depending upon the value of a certin default SPI0
   --!          register state.
   --! @details This procedure will be typically called for pushing the values
   --!          for the output of each of the double buffers in m25p32_regs.vhd
   --!          to the internal memory of the spi_master_core.vhd, which will
   --!          be sent out in the SPI interface.
   --! @param   Register which PUSH_[x] bits must be updated
   procedure decode_SPI0_pushes (signal reg_register : out r_SPI0;
                                 signal operation    : in  t_operations) is
   begin
      case operation  is
         when WRSR             => 
            reg_register     <= c_SPI0_WRSR; 
         when WRP              =>  
            reg_register     <= c_SPI0_PP; 
         when ERS              =>
            reg_register     <= c_SPI0_SE; 
         when others           =>
            null;
      end case;
   end procedure;


   --! @brief   Procedure for setting up correctly the push values of a SPI1
   --!          register depending upon the value of a certin default SPI1
   --!          register state.
   --! @details This procedure will be typically called for pushing the values
   --!          for the output of each of the double buffers in m25p32_regs.vhd
   --!          to the internal memory of the spi_master_core.vhd, which will
   --!          be sent out in the SPI interface.
   --! @param   Register which PUSH_[x] bits must be updated
   procedure decode_SPI1_pushes (signal reg_register : out r_SPI1;
                                 signal operation    : in  t_operations) is
   begin
      case operation  is
         when WRSR             => 
            reg_register     <= c_SPI1_WRSR; 
         when WRP              =>  
            reg_register     <= c_SPI1_PP; 
         when ERS              =>
            reg_register     <= c_SPI1_SE; 
         when others           =>
            null;
      end case;
   end procedure;

   --! @brief A general fsm for the m25p32
   procedure general_fsm (signal operation    : in  t_operations) is
   begin
      --! Creating one clock signal for r_FMOH.BOV bit
      s_CTR1.BOV        <= '0';
      --! Creating one clock signal for CTR1 push bits
      s_CTR1.PUSH_I     <= '0';
      s_CTR1.PUSH_A     <= '0';
      s_CTR1.PUSH_D     <= '0';
      --! Creating one clock signal for SPI1 push bits
      s_SPI1.PUSH_INST  <= '0'; 
      s_SPI1.PUSH_ADDR  <= '0'; 
      s_SPI1.PUSH_DATA  <= '0'; 
      case MEM_fsm is
         when S0_IDLE              =>
            if s_CTR0.OPF = '0' then
               s_CTR0.OPA        <= '1';
               MEM_fsm           <= S1_WREN;
               s_SPI0            <= c_SPI0_WREN;
               s_SPI1            <= c_SPI1_WREN;
                --! Push directly the instruction from m25p32_core
               s_CTR1.BOV        <= '1';
           else
               s_CTR0            <= c_CTR0_default;
               s_SPI0            <= c_SPI0_default;
               s_SPI1            <= c_SPI1_default;
            end if;
         when S1_WREN              =>
            if s_SPI2.SENT_OP = '1' then
               MEM_fsm           <= S2A_SPI_OP_LD_DBUF;
               --! Load the double buffer in m25p32_regs.vhd
               decode_SPI1_pushes(s_CTR1, operation);
            end if;
         when S2A_SPI_OP_LD_DBUF   =>
               --! As it only takes only cycle to load the buffer, we just
               --! pass to the next state
               MEM_fsm           <= S2B_SPI_OP_LD_SPIBUF;
               --! Push the contents into the spi module.
               decode_SPI0_pushes(s_SPI0, operation);
               decode_SPI1_pushes(s_SPI1, operation);
         when S2B_SPI_OP_LD_SPIBUF =>
            if s_SPI2.SENT_OP = '1' then
               MEM_fsm           <= S4_WRDI;
               s_SPI0            <= c_SPI0_WRDI;
               s_SPI1            <= c_SPI1_WRDI;
               --! Push directly the instruction from m25p32_core
               s_CTR1.BOV        <= '1';
            end if;
         when S4_WRDI              =>
            if s_SPI2.SENT_OP = '1' then
               MEM_fsm           <= S0_IDLE;
               s_SPI1            <= c_SPI1_IDLE;
               s_CTR0.OPF        <= '1';
            end if;
         when Q0_ERROR             =>
            s_SPI1               <= c_SPI1_IDLE;
            MEM_fsm              <= S4_WRDI;
         when others               =>
            s_SPI1               <= c_SPI1_IDLE;
      end case;
   end procedure;     

begin
   if rising_edge(wb_clk) then
      if wb_rst_i = '1' then
         s_CTR0         <= c_CTR0_default;
         s_CTR1         <= c_CTR1_default;
         s_SPI0         <= c_SPI0_default;
         s_SPI1         <= c_SPI1_default;
      else
         MEM_fsm_d0     <= MEM_fsm;
         --! If there is an operation pending, then we go for it.
         if s_FMOH.OPP = '1' then
            general_fsm (s_FMOH.OP);
         else
            s_CTR0.OPA     <= '0';
            s_CTR0.OPF     <= '0';
         end if;
      end if;
   else
   end if;
end process;

end Behavioral;
