--
-- ITk Strips LCB
-- Frame Sync
--
-- Matt Warren Aug 2016
--


library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

library downlink;
use downlink.lcb_pkg_globals.all;

entity lcb_frame_sync is
--   generic( 
--      DDR_SER_IN : integer := 0
--   );
   port(
      -- 160 MHz domain
      -----------------------------------------
      clk160        : in     std_logic;
--      ser_i         : in     std_logic;                       -- serial input
      ser_ddr0_i    : in     std_logic;                       -- ddr0 serial input
      ser_ddr1_i    : in     std_logic;                       -- ddr1 serial input
      -- 40MHz domain
      --------------------------------------------------------
      par4_o        : out    std_logic_vector (3 downto 0);
      frame_sync_o  : out    std_logic_vector (1 downto 0);
      dbg_frame_o   : out    std_logic_vector (15 downto 0);
      dbg_symbol_o  : out    std_logic_vector (7 downto 0);
      nibble_err_o  : out    std_logic;
      parity_err_o  : out    std_logic;
      locked_o      : out    std_logic;
      decoder_err_i : in     std_logic;
      clk40         : in     std_logic;
      rst           : in     std_logic
   );

-- Declarations

end entity lcb_frame_sync ;

---------------------------------------------------------------------------
architecture rtl of lcb_frame_sync is


  constant COUNTS_BIT_MAX : integer := 3;

  constant COUNTS_MAX  : std_logic_vector(COUNTS_BIT_MAX downto 0) := (others => '1');
  constant COUNTS_ZERO : std_logic_vector(COUNTS_BIT_MAX downto 0) := (others => '0');


  signal counta : std_logic_vector(COUNTS_BIT_MAX downto 0);
  signal countb : std_logic_vector(COUNTS_BIT_MAX downto 0);
  signal count  : std_logic_vector(COUNTS_BIT_MAX downto 0);


  signal counta_inc : std_logic;
  signal countb_inc : std_logic;
  signal counta_dec : std_logic;
  signal countb_dec : std_logic;

  signal counts_clr : std_logic;

  signal ser_ina : std_logic;
  signal ser_inb : std_logic;

  signal sr160a : std_logic_vector(3 downto 0);
  signal sr160b : std_logic_vector(3 downto 0);

  signal sr440a : std_logic_vector(15 downto 0);
  signal sr440b : std_logic_vector(15 downto 0);

  signal framea  : std_logic_vector(15 downto 0);
  signal frameb  : std_logic_vector(15 downto 0);
  signal frame   : std_logic_vector(15 downto 0);
  signal nibblea : std_logic_vector(3 downto 0);
  signal nibbleb : std_logic_vector(3 downto 0);
  signal nibble  : std_logic_vector(3 downto 0);
  signal symbol  : std_logic_vector(7 downto 0);

  signal nibble_err  : std_logic;
  signal nibblea_err : std_logic;
  signal nibbleb_err : std_logic;

  signal parity_err : std_logic;

  signal sel_a     : std_logic;
  signal sel_a_set : std_logic;
  signal sel_b_set : std_logic;

  signal locked      : std_logic;
  signal frame_sync  : std_logic_vector(1 downto 0);
  signal symbol_sync : std_logic;


  type states is (
    WaitIdleFrame,
    LockedNib0, LockedSymb0, LockedNib2, LockedSymb1,
    Reset);

  signal state, nstate : states;

begin



-- 160Mb "DDR" Deserialiser
-- ===========================================================


  prc_deser160 : process (clk160)
  begin
    if rising_edge(clk160) then
      if (rst = '1') then  -- not sure -do ASICs care about reset here
        sr160a <= (others => '0');
        sr160b <= (others => '0');

      else
        sr160a <= sr160a(2 downto 0) & ser_ddr0_i;
        sr160b <= sr160b(2 downto 0) & ser_ddr1_i;

      end if;

    end if;
  end process;

  prc_sr40 : process (clk40)
  begin
    if rising_edge(clk40) then
      --if (rst = '1') then
      --sr440a <= (others => '0');
      --sr440b <= (others => '0');

      --else
      sr440a <= sr440a(11 downto 0) & sr160a(3 downto 0);  -- sr160a(2 downto 0) & ser_ina
                                        -- Could do it earlier?? 
      sr440b <= sr440b(11 downto 0) & sr160b(3 downto 0);

    end if;
  end process;


  framea <= sr440a(15 downto 0);
  frameb <= sr440b(15 downto 0);

  nibblea <= framea(3 downto 0);
  nibbleb <= frameb(3 downto 0);


  frame  <= framea when (sel_a = '1') else frameb;
  nibble <= frame(3 downto 0);
  symbol <= frame(7 downto 0);




-- Very basic integrity checking
  -- Async for now - trying to keep things fast
  prc_integrity_check : process (symbol, nibblea, nibbleb)
  begin
    --  if rising_edge(clk40) then

    if (nibblea = "0000") or (nibblea = "1111") then
      nibblea_err <= '1';
    else
      nibblea_err <= '0';
    end if;

    if (nibbleb = "0000") or (nibbleb = "1111") then
      nibbleb_err <= '1';
    else
      nibbleb_err <= '0';
    end if;


    parity_err <= symbol(7) xor
                  symbol(6) xor
                  symbol(5) xor
                  symbol(4) xor
                  symbol(3) xor
                  symbol(2) xor
                  symbol(1) xor
                  symbol(0);


  --end if;
  end process;


  nibble_err <= nibblea_err when (sel_a = '1') else nibbleb_err;



-- state machine
--==========================================================

  prc_sm_sync_part : process (clk40)
  begin
    if rising_edge(clk40) then
      if (rst = '1') then
        state <= Reset;
      else
        state <= nstate;
      end if;
    end if;
  end process;



  prc_sm_decode : process (count, counta, countb, framea, frameb, state)
  begin

    -- defaults
    counts_clr <= '0';
    counta_inc <= '0';
    countb_inc <= '0';
    sel_a_set  <= '0';
    sel_b_set  <= '0';
    locked     <= '0';
    frame_sync <= "00";



    case state is

      ---------------------------------------------------
      when Reset =>
        counts_clr <= '1';
        nstate     <= WaitIdleFrame;

      ---------------------------------------------------
      when WaitIdleFrame =>
        nstate <= WaitIdleFrame;

        if (framea = IDLE_FRAME) then
          counta_inc <= '1';
        end if;

        if (frameb = IDLE_FRAME) then
          countb_inc <= '1';
        end if;


        if (counta = COUNTS_MAX) then
          sel_a_set <= '1';
          nstate    <= LockedSymb0;  --LockedNib0 -- Note offset - acounts for 1
          --clk40 delay to detect IDLE;

        elsif (countb = COUNTS_MAX) then
          sel_b_set <= '1';
          nstate    <= LockedSymb0;     --LockedNib0

        end if;

      -----------------------------------------------------------
      when LockedNib0 =>
        nstate     <= LockedSymb0;
        frame_sync <= "00";
        locked     <= '1';


      when LockedSymb0 =>
        nstate     <= LockedNib2;
        locked     <= '1';
        frame_sync <= "01";

      when LockedNib2 =>
        nstate     <= LockedSymb1;
        locked     <= '1';
        frame_sync <= "10";


      when LockedSymb1 =>
        nstate     <= LockedNib0;
        locked     <= '1';
        frame_sync <= "11";

        counta_inc <= '1';  -- inc both 'cause don't which one we're in
        countb_inc <= '1';

        if (count = COUNTS_ZERO) then
          nstate <= Reset;
        end if;

    ---------------------------------------------------
    end case;

  end process;


  symbol_sync <= '1' when (frame_sync = FSYNC_FRAME) or (frame_sync = FSYNC_SYMB0) else '0';


  ----------------------------------------------------------------------------------
  counta_dec <= nibblea_err or (parity_err and symbol_sync) or (decoder_err_i and locked);
  countb_dec <= nibbleb_err or (parity_err and symbol_sync) or (decoder_err_i and locked);


  prc_counts : process (clk40)

  begin
    if rising_edge(clk40) then
      if (counts_clr = '1') then
        counta <= (others => '0');
        countb <= (others => '0');

      else

        if(counta_dec = '1') and (counta /= COUNTS_ZERO) then
          counta <= counta - '1';

        elsif (counta_inc = '1') and (counta /= COUNTS_MAX) then
          counta <= counta + '1';

        end if;


        if (countb_dec = '1') and (countb /= COUNTS_ZERO) then
          countb <= countb - '1';

        elsif (countb_inc = '1') and (countb /= COUNTS_MAX) then
          countb <= countb + '1';

        end if;

      end if;
    end if;
  end process;

  count <= counta when (sel_a = '1') else countb;


---------------------------------------------------------------------------
  prc_select : process (clk40)
  begin
    if rising_edge(clk40) then

      if (rst = '1') then
        sel_a <= '1';

      else

        if (sel_a_set = '1') then
          sel_a <= '1';

        elsif (sel_b_set = '1') then
          sel_a <= '0';

        end if;
      end if;
    end if;
  end process;


-------------------------------------------------------------------------------
  prc_par4_out : process (clk40)
    -- duplicate transfer from 160 domain here to keep latency down

  begin
    if rising_edge(clk40) then

      if (sel_a = '1') then
        par4_o <= sr160a(3 downto 0);
        -- Could do it earlier?? --sr160a(2 downto 0) & ser_ina;

      else
        par4_o <= sr160b(3 downto 0);
        --sr160b(2 downto 0) & ser_inb;

      end if;
    end if;

  end process;



  nibble_err_o <= nibble_err;
  parity_err_o <= parity_err;
  locked_o     <= locked;
  frame_sync_o <= frame_sync;

  dbg_frame_o  <= frame  when (frame_sync = FSYNC_FRAME);  -- yes, yes, a latch, I know,
  dbg_symbol_o <= symbol when (symbol_sync = '1');  -- it's just for debug!



end rtl;