---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------
--  File:          sck_logic_16.vhd
--
--
--  Original file: sck_logic.vhd
--

--  Created: 8-23-00 ALS
--  This file generates an internal SCK by dividing the system clock as determined by CLKDIV.
--  This internal SCK has a CPHA=1 relationship to data and is used to clock the internal SPI
--  shift register. This internal SCK is used to generate the desired output SCK as determined
--  by CPHA and CPOL in the control register. The SCK output to the SPI bus is output only when
--  the slave select signals are asserted, but the internal SCK runs continuously.
--
--  Revised: 8-28-00 ALS
--  Revised: 9-11-00 ALS
--  Revised: 10-17-00 ALS

---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------
--  Modified from 8 to 16 bit word size by Patrick Vogler
--  18th November 2009
--
--	 Modifications are marked by: *Mod: <modification>
--
--  Cleaned up by Quirin Weitzel
--  21th January 2010
---------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_arith.all;

entity sck_logic is
  port(  
    -- internal uC interface signals
    clkdiv     : in    std_logic_vector(1 downto 0);   -- sets the clock divisor for sck clock
    cpha       : in    std_logic;  -- sets clock phase for output sck clock
    cpol       : in    std_logic;  -- sets clock polarity for output sck clock
         
    -- internal spi interface signals
    clk0_mask  : in    std_logic;  -- clock mask for sck when cpha=0
    clk1_mask  : in    std_logic;  -- clock mask for sck when cpha=1
    sck_1      : inout std_logic;  -- internal sck created from dividing system clock
    sck_int_re : out   std_logic;  -- rising edge of internal sck
    sck_int_fe : out   std_logic;  -- falling edge of internal sck
    sck_re     : out   std_logic;  -- rising edge of external sck
    sck_fe     : out   std_logic;  -- falling edge of external sck
    ss_in_int  : in    std_logic;  -- another master is on the bus
         
    -- external spi interface signals
    sck        : inout std_logic;      -- sck as determined by cpha, cpol, and clkdiv
         
    -- clock and reset
    reset      : in    std_logic;  -- active high reset    
    clk        : in    std_logic   -- clock
    
  );
        
end sck_logic;

architecture DEFINITION of sck_logic is

--**************************** Constants ***************************************

constant RESET_ACTIVE   : std_logic := '0';

--**************************** Signals ***************************************

signal clk_cnt      : STD_LOGIC_VECTOR(4 downto 0); -- clock divider output
signal clk_cnt_en   : STD_LOGIC;            -- count enable for clock divider
signal clk_cnt_rst  : STD_LOGIC;            -- clock count reset


signal sck_int_d1   : STD_LOGIC;    -- sck_int delayed one clock for edge detection
signal sck_int      : STD_LOGIC;    -- version of sck when CPHA=1
signal sck_0        : STD_LOGIC;    -- version of sck when CPHA=0
signal sck_out      : STD_LOGIC;    -- sck to be output if SS_IN_INT is not asserted
signal sck_d1       : std_logic;    -- output sck delayed one clock for edge detection


--**************************** Component Definitions  ********************************
-- 5-bit counter for clock divider
component upcnt5
  port(          
    cnt_en       : in STD_LOGIC;                        -- Count enable                      
    clr          : in STD_LOGIC;                        -- Active high clear
    clk          : in STD_LOGIC;                        -- Clock
    qout         : inout STD_LOGIC_VECTOR (4 downto 0)    
  );
        
end component;

begin

  --************************** Clock Divider Instantiation ********************************
  CLK_DIVDR : upcnt5
    port map(          
      cnt_en       => clk_cnt_en,
      clr          => clk_cnt_rst,
      clk          => clk,
      qout         => clk_cnt
    );
  -- This counter is always enabled, can't instantiate the counter with a literal
  clk_cnt_en <= '1';

  -- Clock counter is reset whenever reset is active and ss_in_int is asserted
  clk_cnt_rst <= RESET_ACTIVE when reset = RESET_ACTIVE or ss_in_int = '0'
                 else not(RESET_ACTIVE);

  --************************** Internal SCK Generation ***************************************
  -- SCK when CPHA=1 is simply the output of the clock divider determined by the CLK_DIV bits
  -- SCK_INT is based off the CPHA=1 waveforms and is used as the internal SCK
  -- SCK_INT is used to clock the SPI shift register, therefore, it runs before and after SS_N
  -- is asserted
  -- SCK_1 is SCK_INT when clk1_mask = 1. The clock mask blocks SCK_INT edges that are before
  -- and after SS_N
  sck_int_process: process (clk,reset)
  begin
    if reset = RESET_ACTIVE  then
      sck_int <= '0';
    elsif clk'event and clk = '1' then    
      -- determine clock divider output based on control register
      case clkdiv is
        when "00" =>
          sck_int <= clk_cnt(1);
        when "01" =>
          sck_int <= clk_cnt(2);
        when "10" =>
          sck_int <= clk_cnt(3);
        when "11" =>
          sck_int <= clk_cnt(4);
        when others =>  -- error in register
          sck_int <= '0';
      end case;
    end if;
  end process;

  sck_1 <= sck_int and clk1_mask;

  -- Detect rising and falling edges of SCK_1 to use as state transition
  sck_intedge_process: process(clk, reset)
  begin
    if reset = RESET_ACTIVE then
      sck_int_d1 <= '0';
    elsif clk'event and clk = '1' then
      sck_int_d1 <= sck_int;
    end if;
  end process;

  sck_int_re <= '1' when sck_int = '1' and sck_int_d1 = '0'
                else '0';
  sck_int_fe <= '1' when sck_int = '0' and sck_int_d1 = '1'
                else '0';

  -- SCK when CPHA=0 is out of phase with internal SCK - therefore its toggle needs to be delayed
  -- by the signal clk_mask and then its simply an inversion of the counter bit

  sck_0_process: process (clk, reset)
  begin
    if reset = RESET_ACTIVE then
      sck_0 <= '0';
    elsif clk'event and clk = '1' then        
      if clk0_mask = '0' then
        sck_0 <= '0';
      else
        case clkdiv is
          when "00" =>
            sck_0 <= not(clk_cnt(1));
          when "01" =>
            sck_0 <= not(clk_cnt(2));
          when "10" =>
            sck_0 <= not(clk_cnt(3));
          when "11" =>
            sck_0 <= not(clk_cnt(4));
          when others =>  -- error in register
            sck_0 <= '0';
        end case;
      end if;
    end if;
  end process;

  --************************** External SCK Generation **********************************
  -- This process outputs SCK based on the CPHA and CPOL parameters set in the control register
  sck_out_process: process(clk, reset, cpol)
    variable temp:  std_logic_vector(1 downto 0);
  begin
    if reset = RESET_ACTIVE then
      sck_out <= '0';
    elsif clk'event and clk = '1' then
      temp := cpol & cpha;
      case temp is
        when "00" =>    -- polarity = 0, phase = 0
          -- sck = sck_0
          sck_out <= sck_0;
        when "01" =>    -- polarity= 0, phase = 1
          -- sck = sck_1
          sck_out <= sck_1;
        when "10" =>    -- polarity = 1, phase = 0
          -- sck = not(sck_0)
          sck_out <= not(sck_0);
        when "11" =>    -- polarity = 1, phase = 1
          -- sck = not(sck_1)
          sck_out <= not(sck_1);
        when others =>  -- default to sck_0
          sck_out <= sck_0;
      end case;
    end if;
  end process;

  -- Detect rising and falling edges of SCK_OUT to use to end cycle correctly
  -- and to keep RCV_LOAD signal 1 system clock in width
  SCK_DELAY_PROCESS: process(clk, reset)
  begin
    if reset = RESET_ACTIVE then
      sck_d1 <= '0';
    elsif clk'event and clk = '1' then
      sck_d1 <= sck_out;
    end if;
  end process;

  sck_re <= '1' when sck_out = '1' and sck_d1 = '0'
            else '0';
  sck_fe <= '1' when sck_out = '0' and sck_d1 = '1'
            else '0';

  -- The SCK output is 3-stated if the SS_IN_INT signal is asserted indicating that another
  -- master is on the bus
  sck <= sck_out when ss_in_int = '1'
         else    'Z';

end DEFINITION;
