----------------------------------------------------------------------------------
--
-- 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:    17:34:27 06/14/2012 
-- Design Name:     
-- Module Name:    spi_master_core - Behavioral 
-- Project Name:   CTDAH/SVEC
-- Target Devices: Spartan 6
-- Tool versions:  Xilinx ISE 13.4
-- Description:    This is a spi_master with programmable field length and progra
--                 mmable setup and hold chip select times.
--
-- Dependencies: 
--
-- Revision: 0.1
--   0.01          + File Created
--   0.1           - Alpha version. Able to write and output data correctly at
--                   different configurations.
-- Additional Comments: 
--
----------------------------------------------------------------------------------

library IEEE;
library work;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use work.spi_master_pkg.ALL;
use work.ctdah_pkg.ALL;

entity spi_master_core is
   generic(
      g_INST_LENGTH     : NATURAL := c_INST_LENGTH;
      g_ADDR_LENGTH     : NATURAL := c_ADDR_LENGTH;
      g_DATA_LENGTH     : NATURAL := c_DATA_LENGTH;
      g_CLK_I_PERIOD    : NATURAL := c_CLK_I_PERIOD);
   port(
      rst_i          : in  STD_LOGIC;
      clk_i          : in  STD_LOGIC;
   
      inst_i         : in  STD_LOGIC_VECTOR (8*g_INST_LENGTH - 1 downto 0);
      addr_i         : in  STD_LOGIC_VECTOR (8*g_ADDR_LENGTH - 1 downto 0);
      data_i         : in  STD_LOGIC_VECTOR (8*g_DATA_LENGTH - 1 downto 0);

      SPI0_i         : in  STD_LOGIC_VECTOR (r_SPI0'a_length - 1 downto 0);
      SPI1_i         : in  STD_LOGIC_VECTOR (r_SPI1'a_length - 1 downto 0);
      SPI2_o         : out STD_LOGIC_VECTOR (r_SPI2'a_length - 1 downto 0);
      SPI3_o         : out STD_LOGIC_VECTOR (r_SPI3'a_length - 1 downto 0);

      rd_SPI3_o      : out STD_LOGIC;

      spi_mosi_o     : out STD_LOGIC;
      spi_miso_i     : in  STD_LOGIC;
      spi_clk_o      : out STD_LOGIC;
      spi_cs_n_o     : out STD_LOGIC);
   end spi_master_core;

architecture Behavioral of spi_master_core is

signal s_inst_reg_o    : STD_LOGIC_VECTOR (7 downto 0);
signal s_addr_reg_o    : STD_LOGIC_VECTOR (7 downto 0);
signal s_data_reg_o    : STD_LOGIC_VECTOR (7 downto 0);

signal s_SPI0          : r_SPI0;
signal s_SPI1          : r_SPI1;
signal s_SPI2          : r_SPI2;
signal s_SPI2_d0       : r_SPI2;

signal s_PUSH_INST_tmp : STD_LOGIC;
signal s_PUSH_ADDR_tmp : STD_LOGIC;
signal s_PUSH_DATA_tmp : STD_LOGIC;

--! This copy of registers is done to protect for bad use
signal s_SPI0_core     : r_SPI0;
signal s_SPI1_core     : r_SPI1;
signal s_SPI1_core_d0  : r_SPI1;


--type SPI_clk_fsm is(R0_RESET,
--                    S0_IDLE,
--                    S1_SETUP,
--                    S2_SPI_ACTIVITY,
--                    S3_HOLD);
--
--type SPI_spi_clk_fsm is (S0_IDLE,
--                         S1_START_SPI_CLK,
--                         S2P_INST,
--                         S2_INST,
--                         S3P_ADDR,
--                         S3_ADDR,
--                         S4P_DATA,
--                         S4_DATA,
--                         S5_READ,
--                         S5U_READ,
--                         S6_STOP_SPI_CLK,
--                         Q0_END);
--
--type r_STATUS is
--   record
--      clk_fsm      : SPI_clk_fsm;
--      spi_clk_fsm  : SPI_spi_clk_fsm;
--      PULL_INST    : STD_LOGIC;
--      PULL_ADDR    : STD_LOGIC;
--      PULL_DATA    : STD_LOGIC;
--   end record;

signal   s_STATUS               : r_STATUS;

constant c_STATUS_DEFAULT       : r_STATUS := (clk_fsm     => S0_IDLE,
                                               spi_clk_fsm => S0_IDLE,
                                               PULL_INST   => '0', 
                                               PULL_ADDR   => '0', 
                                               PULL_DATA   => '0');

signal s_spi_clk_fsm_d0         : SPI_spi_clk_fsm; 
signal s_clk_fsm_d0             : SPI_clk_fsm; 

constant c_CS_SU_TICKS          : NATURAL := c_CS_SU_SPI/g_CLK_I_PERIOD + 1;
constant c_CS_HOLD_TICKS        : NATURAL := c_CS_HOLD_SPI/g_CLK_I_PERIOD + 1;

signal s_cs_su_count                 : NATURAL;
signal s_cs_hold_count               : NATURAL;

signal s_timing_counter_en           : STD_LOGIC;
signal s_timing_counter_manual_rst_i : STD_LOGIC;
signal s_timing_counter_rst_i        : STD_LOGIC;
signal s_timing_counter_cnt          : STD_LOGIC_VECTOR(c_COUNTER_DATA_WIDTH
                                                                  - 1 downto 0);
signal s_read_edge_counter_clk            : STD_LOGIC; 
signal s_read_edge_counter_en             : STD_LOGIC;
signal s_read_edge_counter_manual_rst_clk : STD_LOGIC;
signal s_read_edge_counter_rst_i     : STD_LOGIC;
signal s_read_edge_counter_cnt       : STD_LOGIC_VECTOR(c_COUNTER_DATA_WIDTH
                                                                 - 1 downto 0);
signal s_read_edge_counter_cnt_d0    : STD_LOGIC_VECTOR(c_COUNTER_DATA_WIDTH
                                                                 - 1 downto 0);

signal s_spi_cs_n                    : STD_LOGIC;

signal s_spi_clk_manual_rst_i        : STD_LOGIC;
signal s_spi_clk_rst_i               : STD_LOGIC;
signal s_spi_clk_divider_i           : STD_LOGIC_VECTOR(15  downto 12);
signal s_spi_clk_divider_oe_n        : STD_LOGIC;
signal s_spi_clk_tmp                 : STD_LOGIC;

signal s_spi_clk                     : STD_LOGIC;
signal s_spi_clk_n                   : STD_LOGIC;
signal s_spi_clk_d0                  : STD_LOGIC;
signal s_spi_clk_n_d0                : STD_LOGIC;

--! Watch out here, 9 is the length of those fields

signal s_spi_miso_data               : STD_LOGIC_VECTOR(31 downto 0);
signal s_rd_SPI3                     : STD_LOGIC;

begin

   s_SPI0        <= f_SPI0(SPI0_i);
   s_SPI1        <= f_SPI1(SPI1_i);
   SPI2_o        <= f_STD_LOGIC_VECTOR(s_SPI2);
   SPI3_o        <= s_spi_miso_data; 

   spi_clk_o     <= s_spi_clk_d0 when s_SPI0.CPOL = '0'
                                 else s_spi_clk_n_d0;
   spi_cs_n_o    <= s_spi_cs_n;
--   s_spi_mosi    <= s_mosi

   rd_SPI3_o     <= s_rd_SPI3;

   inst_fifo_i2c: FIFO_dispatcher
   generic map(
      g_data_width => 8, g_dispatcher_depth => c_INST_LENGTH)
   port map(
      reg_i   => inst_i,
      clk     => clk_i,
      load    => s_SPI1_core.PUSH_INST,
      flush   => rst_i,
      oen_i   => s_STATUS.PULL_INST,
      reg_o   => s_inst_reg_o 
   );

   addr_fifo_i2c: FIFO_dispatcher
   generic map(
      g_data_width => 8, g_dispatcher_depth => c_ADDR_LENGTH)
   port map(
      reg_i   => addr_i,
      clk     => clk_i,
      load    => s_SPI1_core.PUSH_ADDR,
      flush   => rst_i,
      oen_i   => s_STATUS.PULL_ADDR,
      reg_o   => s_addr_reg_o
   );

   data_fifo_i2c: FIFO_dispatcher
   generic map(
      g_data_width => 8, g_dispatcher_depth => c_DATA_LENGTH)
   port map(
      reg_i   => data_i,
      clk     => clk_i,
      load    => s_SPI1_core.PUSH_DATA,
      flush   => rst_i,
      oen_i   => s_STATUS.PULL_DATA,
      reg_o   => s_data_reg_o
   );

   --! Please consider that gc_counter has an ASYNCHRONOUS RESET

   -----------------------------------------------------------
   --! Timing counter for the HOLD and SETUP configurable
   --! minimum times.
   -----------------------------------------------------------
      s_timing_counter_rst_i      <=    s_timing_counter_manual_rst_i
                                     or rst_i;

   timing_counter: gc_counter
   generic map( g_data_width => c_COUNTER_DATA_WIDTH)
   port map(
              clk_i  => clk_i,
              rst_i  => s_timing_counter_rst_i,
              en_i   => s_timing_counter_en,
              cnt_o  => s_timing_counter_cnt
              );



      s_spi_clk_rst_i       <=  s_spi_clk_manual_rst_i or rst_i;
      s_spi_clk_divider_i   <= std_logic_vector(s_SPI1_core.CLK_DIV);

   spi_cclk: gc_clk_divider
   generic map( g_clk_division_logSize => 4)
   port map(
              clk_i      => clk_i, 
              rst_i      => s_spi_clk_rst_i, 
              oe_n_i     => s_spi_clk_divider_oe_n, 
              clk_o      => s_spi_clk_tmp, 
              divider_i  => s_spi_clk_divider_i 
           );

         s_spi_clk          <= s_spi_clk_tmp;
         s_spi_clk_n        <= not(s_spi_clk_tmp);

      ff_spi_clk_d0: gc_ff
      port map(
              Q   => s_spi_clk_d0, 
              C   => clk_i, 
              CLR => rst_i, 
              D   => s_spi_clk
           );

      ff_spi_clk_n_d0: gc_ff
      port map(
              Q   => s_spi_clk_n_d0, 
              C   => clk_i, 
              CLR => rst_i, 
              D   => s_spi_clk_n
           );



   -----------------------------------------------------------
   --! Counter for spi read edges
   -----------------------------------------------------------
      s_read_edge_counter_clk     <= s_spi_clk_d0   when s_SPI0.CPHA = '0' 
                                                    else  s_spi_clk_n_d0;
      s_read_edge_counter_rst_i   <=    s_read_edge_counter_manual_rst_clk
                                     or rst_i;

   spi_read_edge_counter: gc_counter
   generic map( g_data_width => c_COUNTER_DATA_WIDTH)
   port map(
              clk_i  => s_read_edge_counter_clk,
              rst_i  => s_read_edge_counter_rst_i,
              en_i   => s_read_edge_counter_en,
              cnt_o  => s_read_edge_counter_cnt
              );

   -----------------------------------------------------------
   --! Control of the chip select line 
   -----------------------------------------------------------
   p_chip_select :process(clk_i)
   begin
      if rising_edge(clk_i) then
         if s_STATUS.clk_fsm = R0_RESET then
            s_spi_cs_n    <= '1';
         else
            if s_STATUS.clk_fsm = S0_IDLE  then
               s_spi_cs_n    <= '1';
            else
               s_spi_cs_n    <= '0';
            end if;
         end if;
      else
      end if;
   end process p_chip_select;

   -----------------------------------------------------------
   --! Delays of some signals (fsms and registers)
   -----------------------------------------------------------
   p_fsm_delays : process(clk_i)
   begin
      if rising_edge(clk_i) then
         if s_STATUS.clk_fsm = R0_RESET then
            s_clk_fsm_d0           <= R0_RESET;
            s_spi_clk_fsm_d0       <= S0_IDLE;
            s_SPI1_core_d0         <= c_SPI1_default;
            s_SPI2_d0              <= c_SPI2_default;
            s_read_edge_counter_cnt_d0 <= (others => '0');
         else
            s_clk_fsm_d0           <= s_STATUS.clk_fsm;
            s_spi_clk_fsm_d0       <= s_STATUS.spi_clk_fsm;
            s_SPI1_core_d0         <= s_SPI1_core;
            s_SPI2_d0              <= s_SPI2;
            s_read_edge_counter_cnt_d0 <= s_read_edge_counter_cnt;
         end if;
      end if;
   end process p_fsm_delays;

   -----------------------------------------------------------
   --! HW crop of bad length fields configuration 
   -----------------------------------------------------------
   p_comb_addr_length_bytes : process(s_SPI0)
   begin
      if s_STATUS.clk_fsm = R0_RESET then
         s_SPI0_core.BINST <= c_SPI0_default.BINST;
         s_SPI0_core.BADDR <= c_SPI0_default.BADDR;
         s_SPI0_core.BDATA <= c_SPI0_default.BDATA;
         s_SPI0_core.BREAD <= c_SPI0_default.BREAD;
      else
         s_SPI0_core       <= c_SPI0_default;
         s_SPI0_core.CPOL  <= s_SPI0.CPOL;
         s_SPI0_core.CPHA  <= s_SPI0.CPHA;

         if (to_integer(s_SPI0.BINST) <= to_unsigned(c_INST_LENGTH, 9)) then 
            s_SPI0_core.BINST <= s_SPI0.BINST;
         else
            s_SPI0_core.BINST <= to_unsigned(c_INST_LENGTH, 9);
         end if;

         if (to_integer(s_SPI0.BADDR) <= to_unsigned(c_ADDR_LENGTH, 9)) then
            s_SPI0_core.BADDR <= s_SPI0.BADDR; 
         else
            s_SPI0_core.BADDR <= to_unsigned(c_ADDR_LENGTH, 9);
         end if;

         if (to_integer(s_SPI0.BDATA) <= to_unsigned(c_DATA_LENGTH, 9)) then 
            s_SPI0_core.BDATA <= s_SPI0.BDATA;
         else
            s_SPI0_core.BDATA <= to_unsigned(c_DATA_LENGTH, 9);
         end if;

         if (to_integer(s_SPI0.BREAD) <= to_unsigned(c_READ_LENGTH, 3)) then 
            s_SPI0_core.BREAD <= s_SPI0.BREAD;
         else
            s_SPI0_core.BREAD <= to_unsigned(c_READ_LENGTH, 3);
         end if;

      end if;
   end process p_comb_addr_length_bytes;

   -----------------------------------------------------------
   --! HW crop of bad instructions 
   -----------------------------------------------------------
   p_comb_SPI1_ctrl: process(s_SPI0_core, s_SPI1, s_STATUS.clk_fsm)
   begin
      if    s_STATUS.clk_fsm = R0_RESET
         or (    s_SPI1.SEND_INST      = '1'
             and s_SPI0_core.BINST     =  0)  then
--         or (    s_STATUS.clk_fsm = S0_IDLE
--             and s_clk_fsm_d0     = R0_RESET) then
         s_SPI1_core.x         <= c_SPI1_default.x;
         s_SPI1_core.READ_MISO <= c_SPI1_default.READ_MISO;
         s_SPI1_core.SEND_DATA <= c_SPI1_default.SEND_DATA;
         s_SPI1_core.SEND_ADDR <= c_SPI1_default.SEND_ADDR;
         s_SPI1_core.SEND_INST <= c_SPI1_default.SEND_INST;
         s_SPI1_core.SEND_OP   <= c_SPI1_default.SEND_OP;
         s_SPI1_core.y         <= c_SPI1_default.y;
         s_SPI1_core.CLK_DIV   <= c_SPI1_default.CLK_DIV;
         s_SPI1_core.z         <= c_SPI1_default.z;

         s_PUSH_INST_tmp         <= c_SPI1_default.PUSH_INST;
         s_PUSH_ADDR_tmp         <= c_SPI1_default.PUSH_ADDR;
         s_PUSH_DATA_tmp         <= c_SPI1_default.PUSH_DATA;
      else
         s_SPI1_core.x           <= s_SPI1_core_d0.x;
         s_SPI1_core.READ_MISO   <= s_SPI1_core_d0.READ_MISO;
         s_SPI1_core.SEND_DATA   <= s_SPI1_core_d0.SEND_DATA;
         s_SPI1_core.SEND_ADDR   <= s_SPI1_core_d0.SEND_ADDR;
         s_SPI1_core.SEND_INST   <= s_SPI1_core_d0.SEND_INST;
         s_SPI1_core.SEND_OP     <= s_SPI1_core_d0.SEND_OP;
         s_SPI1_core.y           <= s_SPI1_core_d0.y;
         s_SPI1_core.CLK_DIV     <= s_SPI1_core_d0.CLK_DIV;
         s_SPI1_core.z           <= s_SPI1_core_d0.z;

         s_PUSH_INST_tmp         <= c_SPI1_default.PUSH_INST;
         s_PUSH_ADDR_tmp         <= c_SPI1_default.PUSH_ADDR;
         s_PUSH_DATA_tmp         <= c_SPI1_default.PUSH_DATA;
         --! SPI1 in case we don't have a running operation in the
         --! SPI bus, we can attend writes into SPI1
         --! As there is a running operation, we DON'T GENERATE an
         --! acknowledge of operation ended (SENT_OP) because it
         --! corresponds to the running one not the requested one.
         if s_SPI1_core.SEND_OP = '0' then
            --! We evaluate in clk_fsm the validity of SEND_OP and
            --! and SEND_INST.
            --! There, it will go to a R0_RESET state to produce a
            --! SENT_OP flag.
            s_SPI1_core.SEND_OP     <= s_SPI1.SEND_OP;
            s_SPI1_core.SEND_INST   <= s_SPI1.SEND_INST;

            --! In case, zero address bytes are to be written, we skip the
            --! address state in the spi_clk_fsm
            if     s_SPI1.SEND_ADDR      = '1'
               and s_SPI0_core.BADDR     =  0  then
               s_SPI1_core.SEND_ADDR <= c_SPI1_default.SEND_ADDR;
            else
               s_SPI1_core.SEND_ADDR   <= s_SPI1.SEND_ADDR;
            end if;

            --! In case, zero data bytes are to be written, we skip the
            --! data state in the spi_clk_fsm
            if     s_SPI1.SEND_DATA      = '1'
               and s_SPI0_core.BDATA     =  0  then
               s_SPI1_core.SEND_DATA <= c_SPI1_default.SEND_DATA;
            else
               s_SPI1_core.SEND_DATA   <= s_SPI1.SEND_DATA;
            end if;

            --! In case, zero read bytes are to be written, we skip the
            --! read state in the spi_clk_fsm
            if     s_SPI1.READ_MISO      = '1'
               and s_SPI0_core.BREAD     =  0  then
               s_SPI1_core.READ_MISO <= c_SPI1_default.READ_MISO;
            else
               s_SPI1_core.READ_MISO   <= s_SPI1.READ_MISO;
            end if;

            --! A minimum value of prescaling of 2 is requiered.
            if     s_SPI1.CLK_DIV < 2 then
               s_SPI1_core.CLK_DIV     <= c_SPI1_default.CLK_DIV;
            else
               s_SPI1_core.CLK_DIV     <= s_SPI1.CLK_DIV;
            end if;
            --! The push buffers are temporally loaded here to properly
            --! attend one-clock SPI1.SEND_OP = '1' when they are directly
            --! called to spi_master_core.vhd
            s_PUSH_INST_tmp         <= s_SPI1.PUSH_INST;
            s_PUSH_ADDR_tmp         <= s_SPI1.PUSH_ADDR;
            s_PUSH_DATA_tmp         <= s_SPI1.PUSH_DATA;

            s_SPI1_core.x           <= s_SPI1.x;
            s_SPI1_core.y           <= s_SPI1.y;
            s_SPI1_core.z           <= s_SPI1.z;
         end if;

      end if;
   end process p_comb_SPI1_ctrl;

   -----------------------------------------------------------
   --! Process to control the load of the FIFOs 
   -----------------------------------------------------------
   p_SPI1_PUSH_ctrl : process(s_STATUS.clk_fsm, s_clk_fsm_d0)
   begin
      s_SPI1_core.PUSH_INST <= c_SPI1_default.PUSH_INST; 
      s_SPI1_core.PUSH_ADDR <= c_SPI1_default.PUSH_ADDR; 
      s_SPI1_core.PUSH_DATA <= c_SPI1_default.PUSH_DATA;
         --! As the S1_SETUP will take one clock at least, we push
         --! from SPI1 into internal s_SPI1_core, depending upon the
         --! setup.
         --! After PUSH is asserted, the next clock will output the
         --! FIFO content and in the following clock the first bit of
         --! instruction will be placed in the mosi_o line.
         if    s_STATUS.clk_fsm = S1_SETUP
           and s_clk_fsm_d0    /= S1_SETUP then

            if     s_SPI1_core.SEND_INST = '1' then
               s_SPI1_core.PUSH_INST <= s_PUSH_INST_tmp;
            end if;

            if     s_SPI1_core.SEND_ADDR = '1'      then
               s_SPI1_core.PUSH_ADDR <= s_PUSH_ADDR_tmp;
            end if;

            if     s_SPI1_core.SEND_DATA = '1'      then
               s_SPI1_core.PUSH_DATA <= s_PUSH_DATA_tmp;
            end if;

         end if;
   end process p_SPI1_PUSH_ctrl;

   -----------------------------------------------------------
   --!  Combinational process to control setup and hold timing
   -----------------------------------------------------------
   p_comb_timing_counter : process (s_STATUS.clk_fsm)
   begin
      if s_STATUS.clk_fsm = R0_RESET then
         s_timing_counter_manual_rst_i  <= '0';
         s_timing_counter_en            <= '0';
      else
         s_timing_counter_en            <= '0';
         s_timing_counter_manual_rst_i  <= '0';
         case s_STATUS.clk_fsm is
            when S1_SETUP         =>
               s_timing_counter_en            <= '1';
            when S2_SPI_ACTIVITY  =>
               s_timing_counter_manual_rst_i  <= '1';
            when S3_HOLD          =>
               s_timing_counter_en            <= '1';
            when others           =>
               s_timing_counter_manual_rst_i  <= '1';
         end case;
      end if;
   end process p_comb_timing_counter;

   -----------------------------------------------------------
   --!  Combinational process to SPI clk generator
   -----------------------------------------------------------
   p_comb_spi_cclk : process(s_STATUS.clk_fsm, s_STATUS.spi_clk_fsm)
   begin
      if s_STATUS.clk_fsm = R0_RESET then
         s_spi_clk_divider_oe_n       <= '1';
         s_spi_clk_manual_rst_i       <= '0';
      else
         s_spi_clk_divider_oe_n       <= '1';
         s_spi_clk_manual_rst_i       <= '0';
         case s_STATUS.clk_fsm is
            when S2_SPI_ACTIVITY        =>
               s_spi_clk_divider_oe_n <= '0';
               case s_STATUS.spi_clk_fsm is
                  when S1_START_SPI_CLK           =>
                  --! We place a reset as long as S1_START_SPI_CLK takes.
                     s_spi_clk_divider_oe_n     <= '1';
                     s_spi_clk_manual_rst_i     <= '1';
                  when S6_STOP_SPI_CLK            =>
                  --! We place a reset as long as S6_STOP_SPI_CLK takes.
                     s_spi_clk_divider_oe_n     <= '1';
                     s_spi_clk_manual_rst_i     <= '1';
                  when Q0_END                     =>
                     s_spi_clk_divider_oe_n     <= '1';
                  when others =>
                     null;
               end case;
            when others =>
               null;
         end case;
      end if;
   end process p_comb_spi_cclk;

   -----------------------------------------------------------
   --! Combinational process to pull the FIFOs
   -----------------------------------------------------------
   p_comb_FIFO_pull_byte: process(s_STATUS.clk_fsm,
                                  s_STATUS.spi_clk_fsm)
   begin
      if s_STATUS.clk_fsm = R0_RESET then
         s_STATUS.PULL_INST         <= c_STATUS_default.PULL_INST;
         s_STATUS.PULL_ADDR         <= c_STATUS_default.PULL_ADDR;
         s_STATUS.PULL_DATA         <= c_STATUS_default.PULL_DATA;
      else
         s_STATUS.PULL_INST         <= c_STATUS_default.PULL_INST;
         s_STATUS.PULL_ADDR         <= c_STATUS_default.PULL_ADDR;
         s_STATUS.PULL_DATA         <= c_STATUS_default.PULL_DATA;
         case s_STATUS.spi_clk_fsm is
            when S2P_INST             =>
               s_STATUS.PULL_INST   <= '1'; 
            when S3P_ADDR             =>
               s_STATUS.PULL_ADDR   <= '1'; 
            when S4P_DATA             =>
               s_STATUS.PULL_DATA   <= '1'; 
            when others =>
               null;
         end case;
      end if;
   end process p_comb_FIFO_pull_byte;

   -----------------------------------------------------------
   --! Combinational process to update SPI2
   -----------------------------------------------------------
   p_comb_SPI2_reg: process (s_STATUS.spi_clk_fsm,
                             s_STATUS.clk_fsm)

      procedure check_spi_clk_fsm_d0 is
      begin
         case s_spi_clk_fsm_d0 is
            when S2_INST           =>
               s_SPI2.SENT_INST  <= '1';
            when S3_ADDR           =>
               s_SPI2.SENT_ADDR  <= '1';
            when S4_DATA           =>
               s_SPI2.SENT_DATA  <= '1';
            when S5_READ           =>
               s_SPI2.READ_DONE  <= '1';
            when others            =>
               null;
         end case;
      end procedure;

   begin
      s_SPI2.READ_DONE <= s_SPI2_d0.READ_DONE;
      s_SPI2.SENT_DATA <= s_SPI2_d0.SENT_DATA;
      s_SPI2.SENT_ADDR <= s_SPI2_d0.SENT_ADDR;
      s_SPI2.SENT_INST <= s_SPI2_d0.SENT_INST;
      s_SPI2.SENT_OP   <= c_SPI2_default.SENT_OP;
      s_SPI2.x         <= c_SPI2_default.x;
      s_SPI2.CLK_DIV   <= s_SPI1_core.CLK_DIV;

      case s_STATUS.clk_fsm is
         when R0_RESET =>
            if s_clk_fsm_d0 = S3_HOLD then
               s_SPI2.SENT_OP   <= '1';
            elsif s_SPI1_core.SEND_INST = '0' then
               s_SPI2.SENT_OP   <= '1';
            end if;
         when S0_IDLE =>
            if s_clk_fsm_d0 = R0_RESET then
               s_SPI2.READ_DONE <= c_SPI2_default.READ_DONE;
               s_SPI2.SENT_DATA <= c_SPI2_default.SENT_DATA;
               s_SPI2.SENT_ADDR <= c_SPI2_default.SENT_ADDR;
               s_SPI2.SENT_INST <= c_SPI2_default.SENT_INST;
               s_SPI2.SENT_OP   <= c_SPI2_default.SENT_OP;
            else
            end if;
         when S2_SPI_ACTIVITY =>
            case s_STATUS.spi_clk_fsm is
               when S3P_ADDR        =>
                  check_spi_clk_fsm_d0;
               when S4P_DATA        =>
                  check_spi_clk_fsm_d0;
               when S5_READ         =>
                  check_spi_clk_fsm_d0;
               when S6_STOP_SPI_CLK =>
                  check_spi_clk_fsm_d0;
               when others          =>
                  null;
            end case;
         when others =>
            null;
      end case;
   end process p_comb_SPI2_reg;

   -----------------------------------------------------------
   --! Combinational process for enable pin of the spi_read_edge_counter 
   -----------------------------------------------------------
   p_comb_spi_read_edge_counter_en : process(s_STATUS.clk_fsm) is
   begin
      if s_STATUS.clk_fsm = R0_RESET then
         s_read_edge_counter_en <= '0';
      else
         s_read_edge_counter_en <= '0';
         case s_STATUS.clk_fsm is
            when S2_SPI_ACTIVITY =>
               s_read_edge_counter_en   <= '1';
            when others =>
               null;
         end case;
      end if;
   end process p_comb_spi_read_edge_counter_en;

   -----------------------------------------------------------
   --! Process for controlling s_read_edge_counter_manual_rst_clk
   -----------------------------------------------------------
   p_spi_read_edge_counter_rst :process(clk_i)
   begin
      if rising_edge(clk_i) then
         if s_STATUS.clk_fsm = R0_RESET then
            s_read_edge_counter_manual_rst_clk         <= '1';
         else
            s_read_edge_counter_manual_rst_clk         <= '0';
            if     s_spi_clk_fsm_d0      = S1_START_SPI_CLK
               and s_STATUS.spi_clk_fsm  = S2P_INST         then
                  s_read_edge_counter_manual_rst_clk   <= '1';
            end if;
            if     s_spi_clk_fsm_d0     /= S3_ADDR
               and s_STATUS.spi_clk_fsm  = S3P_ADDR         then
                   s_read_edge_counter_manual_rst_clk  <= '1';
            end if;
            if     s_spi_clk_fsm_d0     /= S4_DATA
               and s_STATUS.spi_clk_fsm  = S4P_DATA         then
                   s_read_edge_counter_manual_rst_clk  <= '1';
            end if;
            if     s_spi_clk_fsm_d0     /= S5U_READ
               and s_spi_clk_fsm_d0     /= S5_READ
               and s_STATUS.spi_clk_fsm  = S5_READ         then
                   s_read_edge_counter_manual_rst_clk  <= '1';
            end if;
--            if     s_spi_clk_fsm_d0      = S5_READ
--               and s_STATUS.spi_clk_fsm  = S6_STOP_SPI_CLK then
--                   s_read_edge_counter_manual_rst_clk  <= '1';
--            end if;
         end if;
      else
      end if;
   end process p_spi_read_edge_counter_rst;

   -----------------------------------------------------------
   --! SPI mosi output
   -----------------------------------------------------------
   --! As FIFO dispatcher serves first the MSB, to keep
   --! the consistency of the data, we will send first MSb,
   --! which translates into sending first the bit 7 and last
   --! the bit 0
   -----------------------------------------------------------
   p_spi_mosi_output :process(clk_i)

      procedure mosi_update is
      begin
         case s_STATUS.spi_clk_fsm  is
            when S2_INST    =>

               if UNSIGNED(s_read_edge_counter_cnt) = (s_SPI0_core.BINST*8) then
                  if    (s_SPI1_core.SEND_ADDR xor s_SPI2.SENT_ADDR) = '1' then
                     spi_mosi_o <= s_addr_reg_o(to_integer(unsigned(
                                   not(s_read_edge_counter_cnt(2 downto 0)))));
                  elsif (s_SPI1_core.SEND_DATA xor s_SPI2.SENT_DATA) = '1' then
                     spi_mosi_o <= s_addr_reg_o(to_integer(unsigned(
                                   not(s_read_edge_counter_cnt(2 downto 0)))));
                  end if;
               else
                  spi_mosi_o <= s_inst_reg_o(to_integer(unsigned(
                                   not(s_read_edge_counter_cnt(2 downto 0)))));
               end if;

            when S3_ADDR    =>
               spi_mosi_o <= s_addr_reg_o(to_integer(unsigned(
                                    not(s_read_edge_counter_cnt(2 downto 0)))));
            when S4_DATA    =>
               spi_mosi_o <= s_data_reg_o(to_integer(unsigned(
                                    not(s_read_edge_counter_cnt(2 downto 0)))));
            when others     =>
               --! TODO: high impedance is just to see the proper switching in
               --!       blue in ModelSim. Change to 0
               --spi_mosi_o <= '0';
               spi_mosi_o <= 'Z';
         end case;
      end procedure;

   begin
      if rising_edge(clk_i) then

         if s_STATUS.clk_fsm = R0_RESET then
            --! TODO: high impedance is just to see the proper switching in
            --!       blue in ModelSim. Change to 0
            --spi_mosi_o <= '0';
            spi_mosi_o  <= 'Z';
         else

            if    (s_spi_clk_fsm_d0 = S2P_INST)
              and (UNSIGNED(s_read_edge_counter_cnt) = 0) then
              spi_mosi_o <= s_inst_reg_o(to_integer(unsigned(
                            not(s_read_edge_counter_cnt(2 downto 0)))));
            end if;

            if f_write_edge(s_SPI0, s_spi_clk, s_spi_clk_d0) = '1' then
               mosi_update;
            else
            end if;

         end if;
      else
      end if;
   end process p_spi_mosi_output;

   -----------------------------------------------------------
   --! SPI DUP field 
   -----------------------------------------------------------
   p_comb_miso : process (rst_i, s_STATUS.spi_clk_fsm,
                                 s_STATUS.clk_fsm) is
   begin
      s_SPI2.MISO_DUP         <= c_SPI2_default.MISO_DUP;
      case s_STATUS.clk_fsm is
         when R0_RESET        =>
            if s_clk_fsm_d0 = S3_HOLD then
               s_SPI2.MISO_DUP   <= '1';
            end if;
         when S2_SPI_ACTIVITY =>
            if s_STATUS.spi_clk_fsm = S5U_READ then
               s_SPI2.MISO_DUP   <= '1';
            end if;
         when S3_HOLD         =>
            s_SPI2.MISO_DUP   <= '1';
         when others =>
            null;
      end case;
   end process p_comb_miso;

   -----------------------------------------------------------
   --! SPI miso input reads
   -----------------------------------------------------------
   p_miso :process(s_STATUS.clk_fsm, s_read_edge_counter_clk)
   begin
      if s_STATUS.clk_fsm = R0_RESET then
         s_spi_miso_data           <= (others => '0');
      else
         if rising_edge(s_read_edge_counter_clk) then
         else
            s_spi_miso_data <= (others => '0');
            if      s_spi_clk_fsm_d0 = S5_READ then
               s_spi_miso_data           <=   s_spi_miso_data(30 downto 0)
                                            & spi_miso_i;
            end if;
         end if;
      end if;
   end process p_miso;

   -----------------------------------------------------------
   --! fsm to determine access to SPI lines 
   -----------------------------------------------------------
   p_comb_rd_miso : process(s_STATUS.clk_fsm, s_STATUS.spi_clk_fsm)
   begin
      if s_STATUS.clk_fsm = R0_RESET then
         s_rd_SPI3                 <= '0';
      else
         s_rd_SPI3                 <= '0';
         if s_STATUS.spi_clk_fsm = S5U_READ then
            s_rd_SPI3 <= '1';
         end if;
      end if;
   end process p_rd_miso;

   -----------------------------------------------------------
   --! fsm to determine access to SPI lines 
   -----------------------------------------------------------
   p_clk_fsm: process(clk_i)
   begin
      if rising_edge(clk_i) then
         if rst_i = '1' then
            s_STATUS.clk_fsm             <= R0_RESET;
         else
            if s_STATUS.clk_fsm =  R0_RESET then
               s_STATUS.clk_fsm <= S0_IDLE;
            else
               s_STATUS.clk_fsm             <= c_STATUS_default.clk_fsm;
               --! Here the part of the code for the MOSI
               if s_SPI1_core.SEND_OP = '1' then
                  case s_STATUS.clk_fsm is
                     when  S0_IDLE        =>
                        if (s_SPI1_core.SEND_INST = '1' and s_SPI0_core.BINST /= 0) then
                           s_STATUS.clk_fsm                <= S1_SETUP;
                        elsif s_SPI1_core.SEND_INST = '0' then
                           s_STATUS.clk_fsm          <= R0_RESET;
                        end if;
                     when S1_SETUP =>
                        if to_integer(unsigned(s_timing_counter_cnt))
                        < c_CS_SU_TICKS then
                           s_STATUS.clk_fsm                <= S1_SETUP;
                        else
                           s_STATUS.clk_fsm                <= S2_SPI_ACTIVITY;
                        end if;
                     when S2_SPI_ACTIVITY =>
                        s_STATUS.clk_fsm                <= S2_SPI_ACTIVITY;
                        if s_STATUS.spi_clk_fsm = Q0_END then
                           s_STATUS.clk_fsm                <= S3_HOLD;
                        end if;
                     when S3_HOLD                =>
                        if to_integer(unsigned(s_timing_counter_cnt))
                        < c_CS_HOLD_TICKS then
                           s_STATUS.clk_fsm       <= S3_HOLD;
                        else
                           s_STATUS.clk_fsm       <= R0_RESET;
                        end if;
                     when others          =>
                  end case;
               end if;
            end if;
         end if;
      else
      end if;
   end process p_clk_fsm;

   -----------------------------------------------------------
   --! fsm to determine writes and reads within S2_SPI_ACTIVITY
   --! in clk_fsm finite state machine
   -----------------------------------------------------------
   p_spi_clk_fsm :process(clk_i)

      procedure updater(max_value       : UNSIGNED(8 downto 0);
                        next_fsm_state0 : SPI_spi_clk_fsm;
                        next_fsm_state1 : SPI_spi_clk_fsm;
                        clk             : STD_LOGIC;  clk_d0 : STD_LOGIC) is
      begin
         if     (f_read_edge(s_SPI0_core, clk, clk_d0) = '1')
            and (s_read_edge_counter_cnt(2 downto 0) = "111") then
            if   to_integer(UNSIGNED(s_read_edge_counter_cnt))
               < to_integer(max_value) * 8 - 1                  then
               s_STATUS.spi_clk_fsm <= next_fsm_state0;
            else
               s_STATUS.spi_clk_fsm <= next_fsm_state1;
            end if;
         end if;
      end procedure;

      procedure update_fsm(max_value       : UNSIGNED(8 downto 0);
                           next_fsm_state0 : SPI_spi_clk_fsm;
                           next_fsm_state1 : SPI_spi_clk_fsm)     is
      begin
         if s_SPI0_core.CPOL = '0' then 
               updater(max_value, next_fsm_state0, next_fsm_state1,
                       s_spi_clk, s_spi_clk_d0);
         else
               updater(max_value, next_fsm_state0, next_fsm_state1,
                       s_spi_clk_n, s_spi_clk_n_d0);
         end if;
      end procedure;

      procedure analyze_fsm is
      begin
         case s_STATUS.spi_clk_fsm is
            when S2P_INST =>
               s_STATUS.spi_clk_fsm <= S2_INST;
            when S2_INST  =>
               if    (s_SPI1_core.SEND_ADDR = '1') then
                  update_fsm(s_SPI0_core.BINST, S2P_INST, S3P_ADDR);
               elsif (s_SPI1_core.SEND_DATA = '1') then
                  update_fsm(s_SPI0_core.BINST, S2P_INST, S4P_DATA);
               elsif (s_SPI1_core.READ_MISO = '1') then
                  update_fsm(s_SPI0_core.BINST, S2P_INST, S5_READ);
               else
                  update_fsm(s_SPI0_core.BINST, S2P_INST, S6_STOP_SPI_CLK);
               end if;
            when S3P_ADDR =>
               s_STATUS.spi_clk_fsm <= S3_ADDR;
            when S3_ADDR  =>
               if (s_SPI1_core.SEND_DATA = '1') then
                  update_fsm(s_SPI0_core.BADDR, S3P_ADDR, S4P_DATA);
               elsif (s_SPI1_core.READ_MISO = '1') then
                  update_fsm(s_SPI0_core.BADDR, S3P_ADDR, S5_READ);
               else
                  update_fsm(s_SPI0_core.BADDR, S3P_ADDR, S6_STOP_SPI_CLK);
               end if;
            when S4P_DATA =>
               s_STATUS.spi_clk_fsm <= S4_DATA;
            when S4_DATA  =>
               if (s_SPI1_core.READ_MISO = '1') then
                  update_fsm(s_SPI0_core.BDATA, S4P_DATA, S5_READ);
               else
                  update_fsm(s_SPI0_core.BDATA, S4P_DATA, S6_STOP_SPI_CLK);
               end if;
            when S5_READ  =>
               --! We force always to go into S5U_READ to produce a flag for
               --! fetching the received data
               update_fsm("000000" & s_SPI0_core.BREAD, S5U_READ, S5U_READ);
            when S5U_READ =>
               if   to_integer(UNSIGNED(s_read_edge_counter_cnt))
                  < to_integer(s_SPI0_core.BREAD) * 8 - 1                  then
                  s_STATUS.spi_clk_fsm <= S5_READ;
               else
                  s_STATUS.spi_clk_fsm <= S6_STOP_SPI_CLK;
               end if;
            when others =>
         end case;

      end procedure;

      procedure check_SPI_fsm is
      begin
         --! Before this we hae to check the validity of the instruction
         case s_STATUS.spi_clk_fsm is
            when S1_START_SPI_CLK   =>
               if    s_SPI1_core.SEND_INST = '1'  then
                  s_STATUS.spi_clk_fsm        <= S2P_INST; 
               else
                  s_STATUS.spi_clk_fsm        <= S6_STOP_SPI_CLK; 
               end if;
            when S6_STOP_SPI_CLK       =>
               if s_spi_clk_divider_oe_n = '1' then
                  s_STATUS.spi_clk_fsm        <= Q0_END; 
               else
               end if;
            when Q0_END =>
               s_STATUS.spi_clk_fsm    <= S0_IDLE;
            when others =>
               analyze_fsm;
         end case;
      end procedure;

   begin
      if rising_edge(clk_i) then
         case s_STATUS.clk_fsm is
            when R0_RESET =>
               s_STATUS.spi_clk_fsm          <= S0_IDLE;
            when S1_SETUP =>
               if   to_integer(unsigned(s_timing_counter_cnt))
                  < c_CS_SU_TICKS then
               else
                  s_STATUS.spi_clk_fsm        <= S1_START_SPI_CLK;
               end if;
            when S2_SPI_ACTIVITY =>
               check_SPI_fsm;
            when others =>
         end case;
      else
      end if;
   end process p_spi_clk_fsm;

end Behavioral;
