----------------------------------------------------------------------------------
--
-- 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 := 1;
      g_ADDR_LENGTH     : NATURAL := 3;
      g_DATA_LENGTH     : NATURAL := 256;
      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 (31 downto 0);
      SPI1_i         : in  STD_LOGIC_VECTOR (31 downto 0);
      SPI2_o         : out STD_LOGIC_VECTOR (15 downto 0);

      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;

type SPI_clk_fsm is(S0_IDLE,
                    S1_SETUP,
                    S2_SPI_ACTIVITY,
                    S3_HOLD);

type SPI_spi_clk_fsm is (S0_IDLE,
                         S1_START_SPI_CLK,
                         S2_INST,
                         S3_ADDR,
                         S4_DATA,
                         S5_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_spi_clk_fsm_d1clk  : SPI_spi_clk_fsm;

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');

constant c_CS_SU_TICKS      : NATURAL := c_CS_SU_SPI/c_CLK_I_PERIOD + 1;
constant c_CS_HOLD_TICKS    : NATURAL := c_CS_HOLD_SPI/c_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_spi_counter_clk             : STD_LOGIC; 
signal s_spi_counter_en              : STD_LOGIC;
signal s_spi_counter_manual_rst_clk  : STD_LOGIC;
signal s_spi_counter_rst_i           : STD_LOGIC;
signal s_spi_counter_cnt             : STD_LOGIC_VECTOR(c_COUNTER_DATA_WIDTH - 1 downto 0);

signal s_spi_cs_n                    : 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;

signal s_spi_clk_tmp                 : 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_manual_rst_i        : STD_LOGIC;
signal s_spi_clk_rst_i               : STD_LOGIC;

signal s_STATUS_pull_byte_already           : STD_LOGIC;
signal s_spi_counter_manual_rst_clk_already : STD_LOGIC; 
 
--! Watch out here, 9 is the length of those fields
signal s_BINST : UNSIGNED (9 - 1  downto 0);
signal s_BADDR : UNSIGNED (9 - 1  downto 0);
signal s_BDATA : UNSIGNED (9 - 1  downto 0); 

signal s_spi_clk_fsm_d0clk           : SPI_spi_clk_fsm; 
signal s_clk_fsm_d0                  : SPI_clk_fsm; 
signal s_spi_start_stop_spi_tx       : STD_LOGIC;

type r_cycle_sent_status_tmp is
   record
     SENT_INST : STD_LOGIC;
     SENT_ADDR : STD_LOGIC;
     SENT_DATA : STD_LOGIC;
   end record;

signal   s_cycle_status_tmp         : r_cycle_sent_status_tmp;
constant c_cycle_status_tmp_default : r_cycle_sent_status_tmp := (SENT_INST => '0',
                                                                  SENT_ADDR => '0',
                                                                  SENT_DATA => '0'
                                                                );

begin

   spi_cs_n_o             <= s_spi_cs_n;

   spi_clk_o              <= s_spi_clk_d0;
   s_spi_clk_divider_i    <= std_logic_vector(s_SPI1.CLK_DIV);

   s_spi_counter_clk      <= s_spi_clk_d0   when     (s_SPI0.CPOL = '0')
                                                 and (s_SPI0.CPHA = '0') else

                             s_spi_clk_n_d0 when     (s_SPI0.CPOL = '0')
                                                 and (s_SPI0.CPHA = '1') else

                             s_spi_clk_n_d0 when     (s_SPI0.CPOL = '1')
                                                 and (s_SPI0.CPHA = '0') else

                             s_spi_clk_d0;

   s_spi_clk_rst_i        <=  s_spi_clk_manual_rst_i or rst_i;

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

   s_spi_counter_rst_i    <= s_spi_counter_manual_rst_clk   or rst_i;
   s_timing_counter_rst_i <= s_timing_counter_manual_rst_i  or rst_i;

   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.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.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.PUSH_DATA,
      flush   => rst_i,
      oen_i   => s_STATUS.PULL_DATA,
      reg_o   => s_data_reg_o
   );

   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 
           );
   --! Added the line to control SPI modes via CPOL
   s_spi_clk      <= s_spi_clk_tmp  when  (s_SPI0.CPOL = '0')
                                    else  not(s_spi_clk_tmp);

   s_spi_clk_n    <= not(s_spi_clk);

   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
        );


   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
              );

   spi_bit_counter: gc_counter
   generic map( g_data_width => c_COUNTER_DATA_WIDTH)
   port map(
              clk_i  => s_spi_counter_clk,
              rst_i  => s_spi_counter_rst_i,
              en_i   => s_spi_counter_en,
              cnt_o  => s_spi_counter_cnt
              );

   p_clk_output :process(clk_i)
   begin
      if rising_edge(clk_i) then
         if rst_i = '1' 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;

   -----------------------------------------------------------
   --! HW crop of bad BINST, BADDR and BDATA field configuration 
   -----------------------------------------------------------
   p_crop_send_bytes :process(clk_i)
   begin
      if rising_edge(clk_i) then
         if rst_i = '1' then
            s_BINST <= c_SPI0_default.BINST;
            s_BADDR <= c_SPI0_default.BADDR;
            s_BDATA <= c_SPI0_default.BDATA;
         else
            s_BINST <= c_SPI0_default.BINST;
            s_BADDR <= c_SPI0_default.BADDR;
            s_BDATA <= c_SPI0_default.BDATA;

            if (to_integer(s_SPI0.BINST) <= c_SPI0_default.BINST) then 
               s_BINST <= s_SPI0.BINST;
            end if;

            if (to_integer(s_SPI0.BADDR) <= c_SPI0_default.BADDR) then
               s_BADDR <= s_SPI0.BADDR; 
            end if;

            if (to_integer(s_SPI0.BDATA) <= c_SPI0_default.BDATA) then 
               s_BDATA <= s_SPI0.BDATA; 
            end if;
         end if;
      else
      end if;
   end process;

   -----------------------------------------------------------
   --! WORK HERE 
   -----------------------------------------------------------
   p_FIFO_pull_byte: process(clk_i)
   begin
      if rising_edge(clk_i) then
         if rst_i = '1' 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;
            if s_SPI1.SEND_OP = '1' then
               --! First prefetch for the first byte of the INST field.
               if    s_STATUS.spi_clk_fsm = S1_START_SPI_CLK
                  or ((    s_STATUS.spi_clk_fsm = S2_INST
                        or s_STATUS.spi_clk_fsm = S3_ADDR
                        or s_STATUS.spi_clk_fsm = S4_DATA) and
                      (s_spi_counter_cnt(2 downto 0) = "000"))     then
                    if s_STATUS_pull_byte_already = '0' then
                       s_STATUS_pull_byte_already    <= '1';
                       if     (s_SPI1.SEND_INST = '1') 
                          and (s_STATUS.spi_clk_fsm = S2_INST or
                               s_STATUS.spi_clk_fsm = S1_START_SPI_CLK) then
                            s_STATUS.PULL_INST         <= '1';
                       else
                       end if;
                       if     (s_SPI1.SEND_ADDR = '1')
                          and (s_STATUS.spi_clk_fsm = S3_ADDR or
                               ((s_SPI1.SEND_INST = '0') and
                                (s_STATUS.spi_clk_fsm = S1_START_SPI_CLK))) then
                            s_STATUS.PULL_ADDR         <= '1';
                       else
                       end if;
                       if     (s_SPI1.SEND_DATA = '1')
                          and (s_STATUS.spi_clk_fsm = S4_DATA or
                               ((s_SPI1.SEND_INST = '0') and
                                (s_SPI1.SEND_ADDR = '0') and
                                (s_STATUS.spi_clk_fsm = S1_START_SPI_CLK))) then
                            s_STATUS.PULL_DATA         <= '1';
                       else
                       end if;
                    else
                    end if;
               else
                  s_STATUS_pull_byte_already    <= '0'; 
               end if;
            else
            end if;
         end if;
      else
      end if;
   end process;

   -----------------------------------------------------------
   --! Process for pulling the FIFO contents right before
   --! sending
   -----------------------------------------------------------
   p_spi_counter_manual_rst :process(clk_i)
   begin
      if rising_edge(clk_i) then
         if rst_i = '1' then
            s_spi_counter_manual_rst_clk         <= '0';
         else
            s_spi_counter_manual_rst_clk         <= '0';
            if     s_spi_clk_fsm_d0clk   = S2_INST
               and s_STATUS.spi_clk_fsm /= S2_INST then
                  s_spi_counter_manual_rst_clk         <= '1';
               end if;

               if     s_spi_clk_fsm_d0clk   = S3_ADDR
               and s_STATUS.spi_clk_fsm /= S3_ADDR then
                  s_spi_counter_manual_rst_clk         <= '1';
               end if;

               if     s_spi_clk_fsm_d0clk   = S4_DATA
               and s_STATUS.spi_clk_fsm /= S4_DATA then
                  s_spi_counter_manual_rst_clk         <= '1';
               end if;
         end if;
      else
      end if;
   end process;

   -----------------------------------------------------------
   --! SPI mosi output
   -----------------------------------------------------------
   p_spi_clk_output :process(clk_i, rst_i, s_STATUS.PULL_INST,
                                           s_STATUS.PULL_DATA,
                                           s_STATUS.PULL_ADDR)
      procedure mosi_update is
      begin
         case s_STATUS.spi_clk_fsm  is
            when S2_INST    =>
               spi_mosi_o <= s_inst_reg_o(to_integer(unsigned(
                                    not(s_spi_counter_cnt(2 downto 0)))));
            when S3_ADDR    =>
               spi_mosi_o <= s_addr_reg_o(to_integer(unsigned(
                                    not(s_spi_counter_cnt(2 downto 0)))));
            when S4_DATA    =>
               spi_mosi_o <= s_data_reg_o(to_integer(unsigned(
                                    not(s_spi_counter_cnt(2 downto 0)))));
            when others   =>
               if    s_SPI1.SEND_INST = '1' then
                  spi_mosi_o <= s_inst_reg_o(to_integer(unsigned(
                                not(s_spi_counter_cnt(2 downto 0)))));
               elsif s_SPI1.SEND_ADDR = '1' then
                  spi_mosi_o <= s_addr_reg_o(to_integer(unsigned(
                                not(s_spi_counter_cnt(2 downto 0)))));
               elsif s_SPI1.SEND_DATA = '1' then
                  spi_mosi_o <= s_data_reg_o(to_integer(unsigned(
                                not(s_spi_counter_cnt(2 downto 0)))));
               end if;
         end case;
      end procedure;
   begin
      if rising_edge(clk_i) then

         if rst_i = '1' then
            spi_mosi_o  <= '0';
         else
            if     s_STATUS.spi_clk_fsm = S1_START_SPI_CLK
              and (     s_SPI1.SEND_INST = '1'
                   or   s_SPI1.SEND_ADDR = '1'
                   or   s_SPI1.SEND_DATA = '1')             then
               mosi_update;
            end if;
   
            case s_SPI0.CPOL is
               when '0'    =>
                  if s_SPI0.CPHA = '0' then
                  --! Sampling is done in rising edge, we must output in falling
                  --! As it will happen first a rising edge that a falling one in
                  --! the first clock, we have to place the correct value time
                  --! before in mosi line, for instance when CS is asserted
                     if ((s_spi_clk = '0') and (s_spi_clk_d0 = '1')) then
                        mosi_update;
                     else
                     end if;
                  else
                     --! Sampling is done in falling edge, we must output in rising
                     if ((s_spi_clk = '1') and (s_spi_clk_d0 = '0')) then
                           mosi_update;
                     end if;
                  end if;
               when others =>
                  if s_SPI0.CPHA = '0' then
                  --! Sampling is done in falling edge, we must output in rising
                  --! As it will happen first a falling edge that a rising one in
                  --! the first clock, we have to place the correct value time
                  --! before in mosi line, for instance when CS is asserted
                     if    (    ((s_STATUS.PULL_INST = '1') or (s_STATUS.PULL_ADDR = '1')
                                                            or (s_STATUS.PULL_DATA = '1'))
                            and (unsigned(s_spi_counter_cnt) = 0))
                        or ((s_spi_clk = '1') and (s_spi_clk_d0 = '0')) then
                           mosi_update;
                     else
                     end if;
                 else
                  --! Sampling is done in rising edge, we must output in falling
                     if (s_spi_clk = '0') and (s_spi_clk_d0 = '1') then
                        mosi_update;
                     end if;
                  end if;
            end case;
         end if;
      else
      end if;
   end process;


   p_timing_counter :process(clk_i)
   begin
      if rising_edge(clk_i) then
         if rst_i = '1' 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   =>
                  null;
            end case;
         end if;
      else
      end if;
   end process;


   p_spi_counter :process(clk_i)
   begin
      if rising_edge(clk_i) then
         if rst_i = '1' then
            s_spi_counter_en             <= '0';
            s_spi_clk_manual_rst_i       <= '0';
            s_spi_clk_divider_oe_n       <= '1';
            s_spi_start_stop_spi_tx      <= '0';
         else
            s_spi_clk_manual_rst_i       <= '0';
            s_spi_counter_en             <= '0';
            s_spi_start_stop_spi_tx      <= '0';
                  s_spi_clk_divider_oe_n <= '1';

            s_clk_fsm_d0           <= s_STATUS.clk_fsm;

            if    (    s_STATUS.clk_fsm = S2_SPI_ACTIVITY
                   and s_clk_fsm_d0     = S1_SETUP       )
               or s_STATUS.spi_clk_fsm = Q0_END             then
                  s_spi_start_stop_spi_tx     <= '1';
            else
            end if;

            case s_STATUS.spi_clk_fsm is
               when S1_START_SPI_CLK         =>
                  if s_spi_clk_fsm_d0clk = S0_IDLE then
                     s_spi_clk_manual_rst_i <= '1';
                  end if;
                  s_spi_counter_en       <= '1';
                  s_spi_clk_divider_oe_n <= '0';
               when S2_INST                  =>
                  s_spi_counter_en       <= '1';
                  s_spi_clk_divider_oe_n <= '0';
               when S3_ADDR                  =>
                  s_spi_counter_en       <= '1';
                  s_spi_clk_divider_oe_n <= '0';
               when S4_DATA                  =>
                  s_spi_counter_en       <= '1';
                  s_spi_clk_divider_oe_n <= '0';
                  if  (  to_integer(unsigned(s_spi_counter_cnt))
                       = to_integer(s_BDATA)*8 - 1)               then
                     case s_SPI0.CPHA is
                     --! We must anticipate to avoid a wrong rx of bit
                        when '1'      =>
                           if     (s_spi_clk_d0  = '1' and s_spi_clk = '0')
                              and (s_SPI0.CPOL = '0') then
                                 s_spi_clk_manual_rst_i   <= '1';
                                 s_spi_start_stop_spi_tx  <= '1';
                           end if;
                           if     (s_spi_clk_d0  = '0' and s_spi_clk = '1')
                              and (s_SPI0.CPOL = '1') then
                                 s_spi_clk_manual_rst_i   <= '1';
                                 s_spi_start_stop_spi_tx  <= '1';
                           end if;
                        when others   =>
                           if     (s_spi_clk_d0  = '0' and s_spi_clk = '1')
                              and (s_SPI0.CPOL = '1') then
                                 s_spi_clk_manual_rst_i   <= '1';
                                 s_spi_start_stop_spi_tx  <= '1';
                           end if;
                     end case;
                  end if;
               when S5_STOP_SPI_CLK         =>
                  s_spi_clk_manual_rst_i <= '1';
                  if s_spi_clk_fsm_d0clk /= S5_STOP_SPI_CLK then
                     s_spi_start_stop_spi_tx  <= '1';
                  end if;
              when others                   =>
            end case;
         end if;
      else
      end if;
   end process;


   p_clk_fsm: process(clk_i)
   begin
      if rising_edge(clk_i) then
         if rst_i = '1' then
            s_STATUS.clk_fsm         <= c_STATUS_default.clk_fsm;
            s_SPI2                   <= c_SPI2_default;
         else
            s_STATUS.clk_fsm         <= c_STATUS_default.clk_fsm;
            s_SPI2                   <= c_SPI2_default;
            s_SPI2.CLK_DIV           <= s_SPI1.CLK_DIV;
            s_spi_clk_fsm_d0clk      <= s_STATUS.spi_clk_fsm;
            if s_SPI1.SEND_OP = '1' then
               case s_STATUS.clk_fsm is
                  when  S0_IDLE        =>
                     if     (   (s_SPI1.SEND_INST = '1' and s_BINST /= 0)
                             or (s_SPI1.SEND_ADDR = '1' and s_BADDR /= 0)
                             or (s_SPI1.SEND_DATA = '1' and s_BDATA /= 0))
                        --! We add this condition to let one clock between
                        --! consecutive writes
                        and (s_SPI2.SENT_OP ='0') then
                           s_STATUS.clk_fsm                <= S1_SETUP;
                     else
                     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;
                     case s_STATUS.spi_clk_fsm  is
                        when S3_ADDR             =>
                           if s_spi_clk_fsm_d0clk = S2_INST then
                                 s_SPI2.SENT_INST    <= '1';
                           else
                           end if;
                        when S4_DATA  =>
                           if    s_spi_clk_fsm_d0clk = S2_INST then
                              s_SPI2.SENT_INST    <= '1';
                           elsif s_spi_clk_fsm_d0clk = S3_ADDR then
                              s_SPI2.SENT_ADDR    <= '1';
                           else
                           end if;
                        when S5_STOP_SPI_CLK     =>
                           if    s_spi_clk_fsm_d0clk = S2_INST then
                              s_SPI2.SENT_INST    <= '1';
                           elsif s_spi_clk_fsm_d0clk = S3_ADDR then
                              s_SPI2.SENT_ADDR    <= '1';
                           elsif s_spi_clk_fsm_d0clk = S4_DATA then
                              s_SPI2.SENT_DATA    <= '1';
                           else
                           end if;
                        when Q0_END   =>
                           s_STATUS.clk_fsm                <= S3_HOLD;
                        when others   =>
                     end case;
                  when S3_HOLD                =>
                     if to_integer(unsigned(s_timing_counter_cnt))
                                                         < c_CS_HOLD_TICKS then
                        s_STATUS.clk_fsm                <= S3_HOLD;
                     else
                        s_SPI2.SENT_OP                  <= '1';
                        s_STATUS.clk_fsm                <= S0_IDLE;
                     end if;
                  when others          =>
               end case;
            else
            end if;
         end if;
      else
      end if;
   end process;


   p_spi_clk_fsm :process(clk_i, rst_i)

      procedure check_SPI_fsm is
      begin
         s_STATUS.spi_clk_fsm          <= S0_IDLE; 
         if (s_SPI1.SEND_INST = '1' or s_SPI1.SEND_ADDR = '1' or 
             s_SPI1.SEND_DATA = '1') and s_SPI1.SEND_OP = '1' then
            case s_STATUS.spi_clk_fsm is
               when  S0_IDLE                 =>
                  if s_STATUS.clk_fsm = S2_SPI_ACTIVITY then
                     if    s_SPI1.SEND_INST = '1'  
                        or s_SPI1.SEND_ADDR = '1'  
                        or s_SPI1.SEND_DATA = '1'  then
                           s_STATUS.spi_clk_fsm        <= S1_START_SPI_CLK;
                     else
                     end if;
                  else
                  end if;
               when S1_START_SPI_CLK    =>
                  if    s_SPI1.SEND_INST = '1'  then
                     s_STATUS.spi_clk_fsm        <= S2_INST; 
                  elsif s_SPI1.SEND_ADDR = '1'  then
                     s_STATUS.spi_clk_fsm        <= S3_ADDR; 
                  elsif s_SPI1.SEND_DATA = '1'  then
                     s_STATUS.spi_clk_fsm        <= S4_DATA; 
                  else
                  end if;
               when  S2_INST       =>
                  if   to_integer(unsigned(s_spi_counter_cnt))
                     < to_integer(s_BINST)*8 - 1               then
                        s_STATUS.spi_clk_fsm        <= S2_INST;
                  else
                     if s_SPI1.SEND_ADDR = '1' then
                        s_STATUS.spi_clk_fsm        <= S3_ADDR; 
                     elsif s_SPI1.SEND_DATA = '1' then
                        s_STATUS.spi_clk_fsm        <= S4_DATA;
                     else
                        s_STATUS.spi_clk_fsm        <= S5_STOP_SPI_CLK;
                     end if;
                  end if;
               when  S3_ADDR       =>
                  if   to_integer(unsigned(s_spi_counter_cnt))
                     < to_integer(s_BADDR)*8 - 1               then
                        s_STATUS.spi_clk_fsm <= S3_ADDR; 
                  else
                     if s_SPI1.SEND_DATA = '1' then
                        s_STATUS.spi_clk_fsm        <= S4_DATA;
                     else
                        s_STATUS.spi_clk_fsm        <= S5_STOP_SPI_CLK; 
                     end if;
                  end if;
               when  S4_DATA       =>
                  if   to_integer(unsigned(s_spi_counter_cnt))
                     < to_integer(s_BDATA)*8 - 1               then
                        s_STATUS.spi_clk_fsm          <= S4_DATA;
                  else
                     s_STATUS.spi_clk_fsm          <= S5_STOP_SPI_CLK; 
                  end if;
               when S5_STOP_SPI_CLK       =>
                     s_STATUS.spi_clk_fsm          <= Q0_END; 
               when others                =>
                  s_STATUS.spi_clk_fsm  <= S0_IDLE; 
            end case;
         else
         end if;
      end procedure;

   begin
      if rst_i = '1' then
         s_STATUS.spi_clk_fsm          <= S0_IDLE;
      else
         if rising_edge(clk_i) then
            if s_spi_start_stop_spi_tx = '1' then
               check_SPI_fsm;
            end if;

            if (s_SPI0.CPOL xor s_SPI0.CPHA)  = '0' then
               if  (s_spi_clk = '1') and (s_spi_clk_d0 = '0') then
                  check_SPI_fsm;
               else
               end if;
            else
               if  (s_spi_clk = '0') and (s_spi_clk_d0 = '1') then 
                  check_SPI_fsm;
               else
               end if;
            end if;
         else
         end if;
      end if;
   end process;

end Behavioral;
