--==============================================================================
-- © Copyright CERN for the benefit of the HPTD interest group 2019. All rights not
--   expressly granted are reserved.
--
--   This file is part of TClink.
--
-- TClink is free VHDL code: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- 
-- TClink is distributed in the hope that it will be useful,
-- but WITHout ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with TClink.  If not, see .
--==============================================================================
--! @file prbs_chk.vhd
--==============================================================================
--! Standard library
library ieee;
--! Standard packages
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
--! Specific packages
-------------------------------------------------------------------------------
-- --
-- CERN, EP-ESE-BE, HPTD
-- --
-------------------------------------------------------------------------------
--
-- unit name: PRBS-checker (prbs_chk)
--
--! @brief parallel unfolded PRBS-checker with clock enable signal
--! Frame PRBS checker
--! Convention: LSB first received
--!
--! @author Eduardo Brandao de Souza Mendes - eduardo.brandao.de.souza.mendes@cern.ch
--! @date 22\10\2019
--! @version 1.0
--! @details
--!
--! Dependencies:\n
--! 
--!
--! References:\n
--!  \n
--! 
--!
--! Modified by:\n
--! Author: Eduardo Brandao de Souza Mendes
-------------------------------------------------------------------------------
--! \n\nLast changes:\n
--! 22\10\2019 - EBSM - Created\n
--! 14\07\2020 - EBSM - Reset error and locked flag, check if pattern is not constant before locking\n
--! 
-------------------------------------------------------------------------------
--! @todo - \n
--! Add error counting \n
--
-------------------------------------------------------------------------------
--==============================================================================
--! Entity declaration for prbs_chk
--==============================================================================
entity prbs_chk is
  generic (
    g_GOOD_FRAME_TO_LOCK  : integer          := 15         ;                      --! Number of correct frames predicted for PRBS to go locked (g_GOOD_FRAME_TO_LOCK+2)
    g_BAD_FRAME_TO_UNLOCK : integer          := 5          ;                      --! Number of wrong received frames for PRBS to go unlocked  (g_BAD_FRAME_TO_UNLOCK+2)
    g_PARAL_FACTOR        : integer          := 254        ;                      --! Size of parallel bus: it is assumed in this implementation that the size of the parallel bus is bigger than the length of the polynomial
    g_PRBS_POLYNOMIAL     : std_logic_vector :=	"11000001"                        --! Notation: x^7 + x^6 + 1 (PRBS-7)
  );                                                                              
  port (                                                                          
    clk_i            : in  std_logic;                                             --! clock input
    reset_i          : in  std_logic;                                             --! active high sync. reset     <--- N --->       ____       ____       ____       ____
    en_i             : in  std_logic;                                             --! enable input                ______/    \_____/    \_____/    \_____/    \_____/    \_____/ 
    data_i           : in  std_logic_vector(g_PARAL_FACTOR-1 downto 0);           --! Input data                  X     D1   X     D2   X     D3   X D4-error X     D5   X     
    data_o           : out std_logic_vector(g_PARAL_FACTOR-1 downto 0);           --! PRBS output expected data              X     D0   X     D1   X     D2   X     D3   X     D4____X____   - Latency is 2N cycles
    error_o          : out std_logic;                                             --! PRBS Frame error                   ______________________________________________________/          \  - Kept to one for the whole duration; Latency is 2N+1 cycles
    locked_o         : out std_logic                                              --! PRBS locked                 <-------- 2N --------->                                <-+1-> 
  );
end prbs_chk;
--==============================================================================
-- architecture declaration
--==============================================================================
architecture rtl of prbs_chk is
  --! Function declaration
  function fcn_max(arg1 : integer; arg2 : integer) return integer is
  begin
    if(arg1 > arg2) then
      return arg1;
	else
      return arg2;	
	end if;
  end fcn_max;
  
  --! Constant declaration  
  --! Signal declaration
  signal seed               : std_logic_vector(g_PRBS_POLYNOMIAL'length-2 downto 0); --! Seed for polynomial
  signal load               : std_logic;                                             --! Load seed when not locked
  signal error              : std_logic;                                             --! PRBS error
  signal data_notzero       : std_logic;                                             --! Check if data is not always 0 before locking
  signal data_chk           : std_logic_vector(g_PARAL_FACTOR-1 downto 0);           --! PRBS output data
  --------------------------------------------------------------------------------------------------------------
  -- FSM PRBS-locking
  -- principle:
  -- HUNT            : received a correct frame                       -> GOING_LOCK
  --                   received a wrong frame                         -> HUNT
  -- GOING_LOCK      : received a consecutive number of correct frame -> LOCK
  --                   received a wrong frame                         -> HUNT   
  -- LOCK            : received a wrong frame                         -> GOING_HUNT
  -- GOING_HUNT      : received a consecutive number of wrong frame   -> HUNT
  --                   received a correct frame                       -> LOCK
  type   t_PRBS_LOCK_STATE is (HUNT, GOING_LOCK, LOCK, GOING_HUNT);
  signal prbs_lock_state : t_PRBS_LOCK_STATE;
  signal frame_cntr : integer range 0 to fcn_max(g_BAD_FRAME_TO_UNLOCK, g_GOOD_FRAME_TO_LOCK);  
  --------------------------------------------------------------------------------------------------------------
  signal data_r  : std_logic_vector(data_i'range);
  signal data_r2 : std_logic_vector(data_i'range);
  --! Component declaration
  component prbs_gen is
    generic (
      g_PARAL_FACTOR    : integer          := 254        ;                          --! Size of parallel bus
      g_PRBS_POLYNOMIAL : std_logic_vector :=	"11000001"                          --! Notation: x^7 + x^6 + 1 (PRBS-7)
    );                                                                              
    port (                                                                          
      clk_i            : in  std_logic;                                             --! clock input
      en_i             : in  std_logic;                                             --! enable input
      reset_i          : in  std_logic;                                             --! active high sync. reset
      seed_i           : in  std_logic_vector(g_PRBS_POLYNOMIAL'length-2 downto 0); --! Seed for polynomial
      load_i           : in  std_logic;                                             --! Load seed	
      data_o           : out std_logic_vector(g_PARAL_FACTOR-1 downto 0);           --! PRBS output data
      data_valid_o     : out std_logic                                              --! PRBS data valid output		  
    );
  end component prbs_gen;
begin
  --! Seed is retrieved from the input data
  seed <= data_i(seed'range);
  --! Component declaration of prbs generator
  cmp_prbs_gen : prbs_gen
    generic map(
      g_PARAL_FACTOR    => g_PARAL_FACTOR,
      g_PRBS_POLYNOMIAL => g_PRBS_POLYNOMIAL
    )                                                                              
    port map(                                                                          
      clk_i            => clk_i  ,
      en_i             => en_i   ,
      reset_i          => reset_i,
      seed_i           => seed   ,
      load_i           => load   ,
      data_o           => data_chk,
      data_valid_o     => open
    );
  
  --============================================================================
  -- Process p_prbs_lock_fsm
  --============================================================================  
  p_prbs_lock_fsm : process(clk_i)
  begin
    if(clk_i'event and clk_i = '1') then
      if(reset_i = '1') then
        prbs_lock_state <= HUNT;
      else
        if(en_i='1') then
            case prbs_lock_state is
              when HUNT =>
                  if(error='0') then
                      prbs_lock_state <= GOING_LOCK;
                  end if;
              when GOING_LOCK =>
                  if(error='1') then
                      prbs_lock_state <= HUNT;
                  elsif(frame_cntr >= g_GOOD_FRAME_TO_LOCK) then
                    if(data_notzero='1') then				  
                      prbs_lock_state <= LOCK;
                    else
                      prbs_lock_state <= HUNT;
                    end if;					
                  end if;
              when LOCK =>
                  if(error='1') then
                      prbs_lock_state <= GOING_HUNT;
                  end if;
              when GOING_HUNT =>
                  if(error='0') then
                      prbs_lock_state <= LOCK;
                  elsif(frame_cntr >= g_BAD_FRAME_TO_UNLOCK) then
                      prbs_lock_state <= HUNT;
                  end if;
              when others => prbs_lock_state <= HUNT;
            end case;
        end if;
      end if;
    end if;
  end process p_prbs_lock_fsm;
  --============================================================================
  -- Process p_frame_cntr
  --============================================================================  
  p_frame_cntr : process(clk_i)
  begin
    if(clk_i'event and clk_i = '1') then
      if(reset_i = '1') then
        frame_cntr <= 0;
      else
        if(en_i='1') then
            if(prbs_lock_state = GOING_LOCK or prbs_lock_state=GOING_HUNT) then
                if(frame_cntr < fcn_max(g_BAD_FRAME_TO_UNLOCK, g_GOOD_FRAME_TO_LOCK)) then
                  frame_cntr <= frame_cntr + 1;
                else
                  frame_cntr <= 0;
                end if;				  
            else
              frame_cntr <= 0;			 
            end if;
        end if;
      end if;
    end if;
  end process p_frame_cntr;
  --============================================================================
  -- Process p_data_r
  --============================================================================  
  p_data_r : process(clk_i)
  begin
    if(clk_i'event and clk_i = '1') then
      if(en_i='1') then
        data_r  <= data_i;
        data_r2 <= data_r;		
      end if;
    end if;
  end process p_data_r;
  --============================================================================
  -- Process p_status_out
  --============================================================================  
  load     <= '1' when prbs_lock_state = HUNT or prbs_lock_state = GOING_LOCK else '0';
  error    <= '1' when data_chk /= data_r2 else '0';
  p_status_out : process(clk_i)
  begin
    if(clk_i'event and clk_i = '1') then
      if(reset_i='1') then 
        locked_o <= '0';
        error_o  <= '0';	  
      else
        locked_o <= (not load);
        error_o  <= error;	  
      end if;
    end if;
  end process p_status_out;  
  data_o   <= data_chk;
  --============================================================================
  -- Process p_data_notzero - used to check if data it not always zero before locking
  --============================================================================  
  p_data_notzero : process(clk_i)
  begin
    if(clk_i'event and clk_i = '1') then
      if(prbs_lock_state = HUNT) then
        data_notzero  <= '0';
      elsif(prbs_lock_state = GOING_LOCK) then
        if(to_integer(unsigned(data_r2)) /= 0 ) then
          data_notzero  <= '1';
		end if;
      end if;
    end if;
  end process p_data_notzero;
end architecture rtl;
--==============================================================================
-- architecture end
--==============================================================================