-------------------------------------------------------------------------------
--
-- Project name: JESD204b Receiver core 
--
-- (C) Copyright 2017 DFC Design, s.r.o., Brno, Czech Republic
-- Author: Marek Kvas (m.kvas@dfcdesign.cz)
--
-------------------------------------------------------------------------------
--
-- 
--
-------------------------------------------------------------------------------


library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
--use work.tb_print_pkg.all;

Library UNISIM;
use UNISIM.vcomponents.all;

entity jesd204b_lmfc is
   generic (
      g_lane_count         : integer := 1;
      g_stat_cnt_length    : integer := 16;
      g_sysref_en          : boolean := false
           );
   port (
      CLK                  : in  std_logic;
      RST                  : in  std_logic;
   
      -- Lane control signals
      CTL_REINIT           : out std_logic;
      CTL_CGS_DONE         : in  std_logic_vector(g_lane_count - 1 downto 0);
      CTL_REINIT_REQ       : in  std_logic_vector(g_lane_count - 1 downto 0);
      CTL_ERR_REPORT_REQ   : in  std_logic_vector(g_lane_count - 1 downto 0);
      CTL_IFA_DONE         : in  std_logic_vector(g_lane_count - 1 downto 0);
      CTL_RELEASE_DATA     : out std_logic;
      CTL_DATA_READY       : in  std_logic_vector(g_lane_count - 1 downto 0);

      -- External device control
      SYNC                 : out std_logic;
      SYSREF               : in  std_logic;

      -- Protocol parameters
      CFG_FxK              : in  std_logic_vector(10 downto 0);
      CFG_F                : in  std_logic_vector(7 downto 0);
      CFG_SUBCLASS         : in  std_logic_vector(1 downto 0);
      CFG_SYSREF_DELAY     : in  std_logic_vector(3 downto 0);
      CFG_SYSREF_ALLRESYNC : in  std_logic;
      CFG_SYSREF_POLARITY  : in  std_logic;
      CFG_REINIT_EN        : in  std_logic;
      CFG_ERR_REPORT_EN    : in  std_logic;

      -- Status signals
      STAT_SYSREF_SEEN     : out std_logic;
      STAT_SYNC_OK         : out std_logic;

      STAT_ERR_LMFC_SYNC   : out std_logic;
      STAT_ERR_LMFC_DIFF   : out std_logic_vector(10 downto 0);

      -- Debugging signals
      LMFC_OUT             : out std_logic;
      LMFC_PREC_OUT        : out std_logic_vector(3 downto 0)
      
        );
end entity;



architecture synthesis of jesd204b_lmfc is

   constant LC_SUBCLASS_0        : std_logic_vector(1 downto 0) := "00";
   constant LC_SUBCLASS_1        : std_logic_vector(1 downto 0) := "01";
   constant LC_SUBCLASS_2        : std_logic_vector(1 downto 0) := "10";

   function vec_and(vec : std_logic_vector) return std_logic is
   begin
      if unsigned(not vec) = 0 then
         return '1';
      else
         return '0';
      end if;
   end function;

   function vec_or(vec : std_logic_vector) return std_logic is
   begin
      if unsigned(vec) = 0 then
         return '0';
      else
         return '1';
      end if;
   end function;

   -- Generate vector corresponding to lmfc clock fractions
   function prec(pol : std_logic; vec : unsigned) return 
         std_logic_vector is
      variable res : std_logic_vector(3 downto 0);
   begin
      case to_integer(vec) is
         when 0 =>
            res := "0000";
         when 1 =>
            res := "0001";
         when 2 =>
            res := "0011";
         when 3 =>
            res := "0111";
      end case;
      
      if pol = '0' then
         res := not res;
      end if;
      return res;
   end function;

   signal sync_i                 : std_logic;
   signal ctl_reinit_i           : std_logic;
   signal ctl_release_data_i     : std_logic;

   signal stat_err_lmfc_sync_i   : std_logic;
   signal stat_sync_ok_i         : std_logic;
   

   signal lmfc_out_i             : std_logic;
   signal lmfc_prec_out_i        : std_logic_vector(LMFC_PREC_OUT'range);
   signal stat_err_lmfc_diff_i   : unsigned(CFG_FxK'range);

   signal sysref_clear_lock      : std_logic;
   signal sysref_capture         : std_logic;
   signal sysref_locked          : std_logic;
   signal sysref_edge_det        : std_logic;
   signal sysref_seen_i          : std_logic;


   type lmfc_fsm_type is (LMFC_REINIT, LMFC_CGS, LMFC_IFA, LMFC_MONITOR);
   signal lmfc_fsm : lmfc_fsm_type;

   signal lmfc_cnt               : unsigned(CFG_FxK'left - 2 downto 0);
   signal lmfc_cnt_frac          : unsigned(2 downto 0);
   signal lmfc_pre               : std_logic;

   signal sysref_d               : std_logic;
   signal sysref_dd              : std_logic;
   signal sysref_f               : std_logic;
   signal sysref_r               : std_logic;
   signal sysref_dly_cnt         : unsigned(CFG_SYSREF_DELAY'range);

   signal sync_cnt               : unsigned(9 downto 0);
   signal not_clk                : std_logic;
begin

   lmfc_fsm_proc : process (CLK)
   begin
      if rising_edge(CLK) then
         ctl_reinit_i <= '0';
         sync_i <= '0';
         sysref_clear_lock <= '0';

         if RST = '1' then
            lmfc_fsm <= LMFC_REINIT;
            --sysref_clear_lock <= '1';
            ctl_release_data_i <= '0';
            sysref_capture <= '1';
         else
            case lmfc_fsm is
               when LMFC_REINIT =>
                  -- Start reinit in all lanes
                  -- And instruct ADC to send /K/
                  ctl_reinit_i <= '1';
                  ctl_release_data_i <= '0';
                  sync_i <= '1';

                  -- Sync has certain minimal length, so ensure it is fulfilled
                  -- It should be 5*frame length +9 octets, but we should
                  -- be free to prolong it.
                  -- it is 8* frame length + 12; 2 of 5 are used to cover
                  -- for CFG_F being F-1
                  if sync_i = '0' then
                     sync_cnt <= unsigned('0'&unsigned(CFG_F)&'0') + 5;
                  elsif sync_cnt > 0 then
                     sync_cnt <= sync_cnt - 1;
                  else                  
                     -- Based on subclass wait for SYSREF event
                     if CFG_SUBCLASS = LC_SUBCLASS_1 then
                        if sysref_locked = '1' then
                           lmfc_fsm <= LMFC_CGS;
                           sysref_capture <= '0';
                        end if;
                     else
                        lmfc_fsm <= LMFC_CGS;
                     end if;
                  end if;
               when LMFC_CGS =>
                  sync_i <= '1';
                  -- Wait until all lanes get code group sync
                  -- Deassert sync on LMFC rising edge
                  if vec_and(CTL_CGS_DONE) = '1'
                     and lmfc_pre = '1' then
                        sync_i <= '0';
                     lmfc_fsm <= LMFC_IFA;
                  end if;
               when LMFC_IFA =>
                  if vec_and(CTL_DATA_READY) = '1' and
                     lmfc_pre = '1' then
                     ctl_release_data_i <= '1';
                     lmfc_fsm <= LMFC_MONITOR;
                  end if;
               when LMFC_MONITOR =>
                  -- If any lane requires reinit and it is allowed
                  -- trigger it.
                  if vec_or(CTL_REINIT_REQ) = '1' and 
                     CFG_REINIT_EN = '1' then
                     lmfc_fsm <= LMFC_REINIT;
                     if CFG_SYSREF_ALLRESYNC = '1' then
                        -- Sysref is required for all resyncs so,
                        -- clear current synchronization flag
                        sysref_capture <= '1';
                     end if;
                  end if;
                  -- If any Lane reported error that doesn't require reinit
                  -- And sync reporting is enabled, report error
                  if CFG_ERR_REPORT_EN = '1' then
                     -- error reporting not yet implemented
                  end if;
            end case;
         end if;
      end if;
   end process;

   stat_sync_ok_i <= '1' when lmfc_fsm = LMFC_MONITOR else '1';

   lmfc_counter : process (CLK)
   begin
      if rising_edge(CLK) then
         lmfc_pre <= '0';
         stat_err_lmfc_sync_i <= '0';
         sysref_locked <= '0';
         if RST = '1' then
            lmfc_out_i <= '0';
            lmfc_prec_out_i <= (others => '0');
            lmfc_cnt <= unsigned(CFG_FxK(CFG_FxK'left downto 2));
            lmfc_cnt_frac <= unsigned('0' & CFG_FxK(1 downto 0));
         else
            if lmfc_cnt > 1 then
               if lmfc_cnt_frac < 4 then
                  lmfc_cnt <= lmfc_cnt - 1;
               else
                  -- Fractions are more then one cycle, so use it
                  lmfc_cnt_frac <= lmfc_cnt_frac - 4;
               end if;
            else
               lmfc_cnt <=  unsigned(CFG_FxK(CFG_FxK'left downto 2));
               lmfc_cnt_frac <= lmfc_cnt_frac + unsigned(CFG_FxK(1 downto 0));
               lmfc_out_i <= not lmfc_out_i;
               lmfc_prec_out_i <= prec(lmfc_out_i, lmfc_cnt_frac(1 downto 0));
            end if;
            if lmfc_cnt = 2 then
               lmfc_pre <= '1';
            end if;

            if CFG_SUBCLASS = LC_SUBCLASS_1 then
               if sysref_edge_det = '1' then
                  if sysref_capture = '1' then
                     -- Realign lmfc
                     lmfc_cnt <=  unsigned(CFG_FxK(CFG_FxK'left downto 2));
                     lmfc_cnt_frac <= lmfc_cnt_frac + unsigned(CFG_FxK(1 downto 0));
                     sysref_locked <= '1';
                  else
                     -- Check alignment
                     if lmfc_pre = '0' then
                        -- SYSREF somewhere not expected
                        stat_err_lmfc_sync_i <= '1';
                        stat_err_lmfc_diff_i <= unsigned(lmfc_cnt & "00") +
                                                lmfc_cnt_frac;
                     end if;
                  end if;
               end if;
            end if;

         end if;
      end if;
   end process;

   sysref_det_proc : process (CLK)
   begin
      if rising_edge(CLK) then
         sysref_edge_det <= '0';

         if CFG_SYSREF_POLARITY = '0' then
            -- falling edge
            sysref_d <= sysref_f;
         else
            sysref_d <= sysref_r;
         end if;
         sysref_dd <= sysref_d;
      
         if sysref_d = '1' and sysref_dd = '0' then
            sysref_dly_cnt <= unsigned(CFG_SYSREF_DELAY);
            if unsigned(CFG_SYSREF_DELAY) = 0 then
               sysref_edge_det <= '1';
            end if;
         end if;
   
         if sysref_dly_cnt > 0 then
            sysref_dly_cnt <= sysref_dly_cnt - 1;
            if sysref_dly_cnt = 1 then
               sysref_edge_det <= '1';
            end if;
         end if;
      end if;
   end process;

   
--   sysref_IDDR_inst : IDDR
--   generic map (
--      DDR_CLK_EDGE => "SAME_EDGE",
--      INIT_Q1 => '0', -- Initial value of Q1: '0' or '1'
--      INIT_Q2 => '0', -- Initial value of Q2: '0' or '1'
--      SRTYPE => "SYNC") -- Set/Reset type: "SYNC" or "ASYNC"
--   port map (
--    Q1 => sysref_r, -- 1-bit output for positive edge of clock
--      Q2 => sysref_f, -- 1-bit output for negative edge of clock
--      C => clk, -- 1-bit clock input
--      CE => '1', -- 1-bit clock enable input
--      D => SYSREF, -- 1-bit DDR data input
--      R => '0', -- 1-bit reset
--      S => '0' -- 1-bit set
--   );


--   sysref_edge_capture_iddr: if g_sysref_en generate
--   not_clk <= not CLK;
--   
--   sysref_IDDRE1_inst : IDDRE1
--   generic map (
--      DDR_CLK_EDGE   => "SAME_EDGE",
--      IS_C_INVERTED  => '0') -- Set/Reset type: "SYNC" or "ASYNC"
--   port map (
--      Q1 => sysref_r, -- 1-bit output for positive edge of clock
--      Q2 => sysref_f, -- 1-bit output for negative edge of clock
--      C => CLK, -- 1-bit clock input
--      CB => not_clk,
--      D => SYSREF, -- 1-bit DDR data input
--      R => '0' -- 1-bit reset
--   );
--   end generate;
   
   -- Output assignments
   CTL_REINIT           <= ctl_reinit_i;
   CTL_RELEASE_DATA     <= ctl_release_data_i;
   SYNC                 <= not sync_i;
   STAT_SYSREF_SEEN     <= sysref_locked;
   STAT_SYNC_OK         <= stat_sync_ok_i;
   STAT_ERR_LMFC_SYNC   <= stat_err_lmfc_sync_i;
   STAT_ERR_LMFC_DIFF   <= std_logic_vector(stat_err_lmfc_diff_i);
   LMFC_OUT             <= lmfc_out_i;
   LMFC_PREC_OUT        <= lmfc_prec_out_i;

end architecture;


