----------------------------------------------------------------------------------
--
-- 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:55:10 06/10/2012 
-- Design Name:    m25p32 wishbone access
-- Module Name:    m25p32_regs - Behavioral 
-- Project Name:   Level Conversion Circuits
-- Target Devices: Spartan 6
-- Tool versions: 
-- Description:    This is a wishbone slave able to access m25p32 Non volatile
--                 Flash devices and manage the write and read to the memory.
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
library work;
use IEEE.STD_LOGIC_1164.ALL;
--! Insert this line for conversion functions
use IEEE.NUMERIC_STD.ALL;
use work.m25p32_pkg.ALL;
use work.spi_master_pkg.ALL;

entity m25p32_regs 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; 

           wb_we_i       : in  STD_LOGIC;  
           wb_stb_i      : in  STD_LOGIC;
           wb_cyc_i      : in  STD_LOGIC;
           wb_sel_i      : in  STD_LOGIC_VECTOR (3  downto 0);
           wb_data_i     : in  STD_LOGIC_VECTOR (31 downto 0); 
           wb_data_o     : out STD_LOGIC_VECTOR (31 downto 0); 
           wb_addr_i     : in  STD_LOGIC_VECTOR (3  downto 0); 
           wb_ack_o      : out STD_LOGIC;
           wb_rty_o      : out STD_LOGIC;
           wb_err_o      : out STD_LOGIC;

           inst_core_i   : in  STD_LOGIC_VECTOR (8*g_INST_LENGTH - 1 downto 0);

           inst_db_o     : out STD_LOGIC_VECTOR (8*g_INST_LENGTH - 1 downto 0);
           addr_db_o     : out STD_LOGIC_VECTOR (8*g_ADDR_LENGTH - 1 downto 0);
           data_db_o     : out STD_LOGIC_VECTOR (8*g_DATA_LENGTH - 1 downto 0);

           push_i_core_i : in STD_LOGIC;
           push_a_core_i : in STD_LOGIC;
           push_d_core_i : in STD_LOGIC;
           bov_core_i    : in STD_LOGIC;

           CTR0_i        : in  STD_LOGIC_VECTOR (r_CTR0'a_length - 1 downto 0);
           FMOH_o        : out STD_LOGIC_VECTOR (r_FMOH'a_length - 1 downto 0)
        );
end m25p32_regs;

architecture Behavioral of m25p32_regs is

   signal s_CTR0           : r_CTR0;
   signal s_CTR1           : r_CTR1;
   signal s_CTR1_buff      : r_CTR1;
   signal s_CTR1_buff_slv  : STD_LOGIC_VECTOR (r_CTR1'a_length - 1 downto 0);
   signal s_FMOH           : r_FMOH;
   signal s_SR_m25p32      : r_SR_m25p32;
   signal s_DBUF           : r_DBUF;
   signal s_DBUF_slv       : STD_LOGIC_VECTOR (r_DBUF'a_length - 1 downto 0);

   signal s_addr_i         : STD_LOGIC_VECTOR (8*g_ADDR_LENGTH - 1 downto 0);
   signal s_data_i         : STD_LOGIC_VECTOR (31 downto 0);

   signal s_inst_db_o      : STD_LOGIC_VECTOR (c_NUMBER_OF_BUFFERS*8*c_INST_LENGTH - 1 
                                                                    downto 0);
   signal s_addr_db_o      : STD_LOGIC_VECTOR (c_NUMBER_OF_BUFFERS*8*c_ADDR_LENGTH - 1
                                                                    downto 0);
   signal s_data_db_o      : STD_LOGIC_VECTOR (c_NUMBER_OF_BUFFERS*8*c_DATA_LENGTH - 1
                                                                          downto 0);

   signal s_wb_ack_o       : STD_LOGIC;
   signal s_wb_rty_o       : STD_LOGIC;
   signal s_wb_err_o       : STD_LOGIC;

begin

   wb_ack_o         <= s_wb_ack_o;
   wb_rty_o         <= s_wb_rty_o;
   wb_err_o         <= s_wb_err_o;

   s_CTR0                 <= f_CTR0(CTR0_i);
      s_CTR1_buff.PUSH_I  <= s_CTR1.PUSH_I or push_i_core_i;
      s_CTR1_buff.PUSH_A  <= s_CTR1.PUSH_A or push_a_core_i;
      s_CTR1_buff.PUSH_D  <= s_CTR1.PUSH_D or push_d_core_i;
      s_CTR1_buff.BOV     <= s_CTR1.BOV    or bov_core_i;
   s_CTR1_buff_slv  <= f_STD_LOGIC_VECTOR(s_CTR1);

   FMOH_o           <= f_STD_LOGIC_VECTOR(s_FMOH);
   s_DBUF(0)        <= f_BUF(s_DBUF_slv(  r_BUF'a_length - 1 downto 0));
   s_DBUF(1)        <= f_BUF(s_DBUF_slv(2*r_BUF'a_length - 1 downto
                                     r_BUF'a_length ));

   --! It needs to be multiplexed
   --! TODO: place a generic multiplexer
   inst_db_o <= s_inst_db_o(8*c_INST_LENGTH - 1 downto 0)
                                              when (s_CTR1.ARBF = "01") else
                s_inst_db_o(2*8*c_INST_LENGTH - 1 downto 8*c_INST_LENGTH)
                                              when (s_CTR1.ARBF = "10") else
                (others => '0');

   addr_db_o <= s_addr_db_o(8*c_ADDR_LENGTH - 1 downto 0)
                                              when (s_CTR1.ARBF = "01") else
                s_addr_db_o(2*8*c_ADDR_LENGTH - 1 downto 8*c_ADDR_LENGTH)
                                              when (s_CTR1.ARBF = "10") else
                (others => '0');

   data_db_o <= s_data_db_o(8*c_DATA_LENGTH - 1 downto 0)
                                              when (s_CTR1.ARBF = "01") else
                s_data_db_o(2*8*c_DATA_LENGTH - 1 downto 8*c_DATA_LENGTH)
                                              when (s_CTR1.ARBF = "10") else
                (others => '0');


   inst_tbuff : m25p32_buff
   generic map (
       g_BUFFER_SIZE => c_NUMBER_OF_BUFFERS,
       g_inst_width  => c_INST_LENGTH,
       g_addr_width  => c_ADDR_LENGTH,
       g_data_width  => c_DATA_LENGTH
   ) port map(
       rst_i      => wb_rst_i,
       clk        => wb_clk,

       inst_i     => inst_core_i,
       addr_i     => s_addr_i,
       data_i     => s_data_i,

       inst_db_o  => s_inst_db_o,
       addr_db_o  => s_addr_db_o,
       data_db_o  => s_data_db_o,

       CTR1_i     => s_CTR1_buff_slv,
       DBUF_o     => s_DBUF_slv
    );


   p_wbslave:   process (wb_clk)
      variable v_FMOH : r_FMOH;
   begin
      if rising_edge(wb_clk) then
         if wb_rst_i = '1' then
            s_CTR1       <=  c_CTR1_default;
            s_FMOH       <=  c_FMOH_default;

            wb_data_o    <= (others => '0');
            s_wb_ack_o   <= '0'; 
            s_wb_rty_o   <= '0'; 
            s_wb_err_o   <= '0'; 

            s_addr_i     <= (others => '0'); 
            s_data_i     <= (others => '0'); 
         else
            --! These two fields are written bu accessing FMOH
            s_CTR1.PUSH_I <= '0';
            s_CTR1.PUSH_A <= '0';
            --! This field is automodified by writing into c_DATA_BUF_addr
            s_CTR1.PUSH_D <= '0';

            if s_CTR0.OPF = '1' then
               s_FMOH.OPP <= '0';
            else
            end if;

            --! We never retry
            s_wb_rty_o  <= '0';

            if (wb_stb_i = '1' and wb_cyc_i = '1') then
               if (s_wb_ack_o or s_wb_rty_o or s_wb_err_o) = '1' then
                  s_wb_ack_o  <= '0'; 
                  s_wb_err_o  <= '0'; 
               else
                  s_wb_ack_o  <= '1';
                  s_wb_err_o  <= '0';
                  case wb_we_i is
                     when '1'    =>
                        case wb_addr_i(3 downto 0) is
                           when c_FMOH_addr   =>
                              if s_FMOH.OPP = '0' then
                                 v_FMOH         := f_FMOH(wb_data_i); 
                                 s_FMOH         <= v_FMOH;
                                 s_CTR1.PUSH_I  <= '1';
                                 s_CTR1.PUSH_A  <= '1';
                                 case v_FMOH.OP is
                                    --! ERase Sector
                                    when ERS      =>
                                       null;
                                    --! ReaD Page
                                    when RDP      =>
                                    --! WRite Page
                                    when WRP      =>  
                                    --! ReaD Status Register
                                    when RDSR     =>
                                       s_CTR1.PUSH_A  <= '0';
                                    --! WRite Status Register
                                    when WRSR     =>
                                       s_CTR1.PUSH_A  <= '0';
                                    when others   =>
                                       s_CTR1.PUSH_I  <= '0';
                                       s_CTR1.PUSH_A  <= '0';
                                 end case;

                                 --! In case we want to clear the buffer
                                 --! counter
                                 if v_FMOH.CLBC = '1' then
                                    s_CTR1.CNTA      <= to_unsigned(0,
                                                              s_CTR1.CNTA'length);
                                 end if;

                                 --! We can manually shift buffer banking
                                 --! This can be useful when we sent an
                                 --! instruction without data payload and we
                                 --! want to start sending the a different one
                                 if v_FMOH.FS = '1' then
                                    s_CTR1.FS <= '1';
                                    s_CTR1.CNTA      <= to_unsigned(0,
                                                              s_CTR1.CNTA'length);
                                 end if;

                              else
                                 s_wb_ack_o  <= '0';
                                 s_wb_err_o  <= '1';
                              end if;
                           when c_DATA_BUF_addr    =>
                              s_CTR1.CNTA       <= to_unsigned(
                                                   to_integer(s_CTR1.CNTA) + 1,
                                                           s_CTR1.CNTA'length);
                              if s_CTR1.CNTA = c_PAGE_SIZE/4 - 1 then
                                 s_CTR1.CNTA    <= to_unsigned(0,
                                                          s_CTR1.CNTA'length);
                              end if;
                              s_CTR1            <= f_ACTIVE_BUFFERS(s_CTR1);
                              s_data_i          <= wb_data_i;
                              s_CTR1.PUSH_D     <= '1';
                           when c_SR_m25p32_addr  =>
                              s_SR_m25p32 <= f_SR_m25p32(wb_data_i(
                                               r_SR_m25p32'a_length-1 downto 0));
                              s_data_i (r_SR_m25p32'a_length-1 downto 0)
                                   <= wb_data_i(r_SR_m25p32'a_length-1 downto 0);
                              s_data_i (31 downto r_SR_m25p32'a_length)
                                   <= (others => '0');
                              s_CTR1.PUSH_D    <= '1';
                           when others      =>
                              s_wb_ack_o  <= '0';
                              s_wb_rty_o  <= '0';
                              s_wb_err_o  <= '1';
                        end case;
                     when others =>
                        case wb_addr_i is
                           when c_CTR0_addr        =>
                              wb_data_o(r_CTR0'a_length - 1 downto 0)
                                                 <= f_STD_LOGIC_VECTOR(s_CTR0);
                              wb_data_o(31 downto r_CTR0'a_length)
                                                            <= (others => '0');
                           when c_CTR1_addr           =>
                              wb_data_o(r_CTR1'a_length - 1 downto 0)
                                                            <= s_CTR1_buff_slv;
                              wb_data_o(31 downto r_CTR1'a_length) 
                                                            <= (others => '0');
                           when c_FMOH_addr        =>
                              wb_data_o   <= f_STD_LOGIC_VECTOR(s_FMOH);
                           when c_SR_m25p32_addr   =>
                              wb_data_o(r_SR_m25p32'a_length - 1 downto 0)
                                            <= f_STD_LOGIC_VECTOR(s_SR_m25p32);
                              wb_data_o(31 downto r_SR_m25p32'a_length)
                                                            <= (others => '0');
                           when others             =>
                              s_wb_ack_o  <= '0';
                              s_wb_rty_o  <= '0';
                              s_wb_err_o  <= '1';
                        end case;
                  end case;
               end if;
            else
            end if;
         end if;
      else
      end if;
   end process;

end Behavioral;
