library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library work;
use work.mbist_pkg.all;

-- ----------------------------------------------------------------------------
-- Entity
-- ----------------------------------------------------------------------------
entity MBIST_CNT_CFSR is
  generic (
    WIDTH      : positive;        -- Width of counter
    POLYNOM    : std_logic_vector
  );
  port (
    CLK        : in  std_logic;  -- System clock
    RST_B      : in  std_logic;  -- System wide reset

--    POLYNOM    : in  std_logic_vector(WIDTH downto 0);
                                 -- Generating polynomial

    CNTE       : in  std_logic;  -- Counter enable
    UP_DOWN    : in  t_DIRECTION;  -- Counter direction
    CLR        : in  std_logic;  -- Counter clear

    CNT_OUT    : out std_logic_vector(WIDTH - 1 downto 0);
                                 -- Current value of counter (reg)
    CNT_END    : out std_logic;  -- This value is last in cycle

    -- Serial interface
    SCK_EDGE   : in  std_logic;  -- Serial clock
    SDI        : in  std_logic;  -- Serial data in
    SEN        : in  std_logic;  -- Enable serial communication
    SDO        : out std_logic;  -- Serial data out
    SME        : in  std_logic   -- Serial memory enable
  );
end entity MBIST_CNT_CFSR;

-- ----------------------------------------------------------------------------
-- Architecture
-- Use WIDTH: 9 POLYNOM: 10'b10_0010_0001
--
-- CFSR counter is LFSR counter which has additional logic to ensure pass
--   trough (others => '0') state
--
-- SCH:
--    ----    ----    ----
-- -->|FF|-*->|FF|-*->|FF|--
-- |  ---- |  ---- |  ---- |
-- |  ___ / \--|   |       |
-- --<XOR|-----------------|
--    ---\     /   |
--        -<OR|----/
--
--
-- Standard LFSR: - Upcounter
-- X(n+1) <= X(n)*A where A is generating matrix of LFSR counter in form
--  |1 0 1|
--  |1 0 0|
--  |0 1 0| (this means polynomial: 0xD (MSB equals lowest index in this formula))
--
-- Standard LFSR: - Downcounter
-- From previous expression we can calculate:
-- X(n+1)*A^-1 <= X(n)*A*A^-1
-- X(n+1)*A^-1 <= X(n)
-- And change indexes:
-- X(n-1) <= X(n)*A^-1
--
-- So the generation matrix for back LFSR from previous example will be:
-- |0 1 0|
-- |0 0 1|
-- |1 1 0| - polynomial 0xB (which is 0xD in reverse bit order (coincidence i think not)
--
-- Up: from 1000...00 to 0000...00
-- Down: from 0000...00 to 1000...00
--
-- Size estimation:
--  (WIDTH)* FF
--  ($counthot(POLYNOM)+1)*XOR
--  (WIDTH+1)* NOR
--  (WIDTH)*   4-input MUX
-- ----------------------------------------------------------------------------
architecture rtl of MBIST_CNT_CFSR is

  -- Internal shift register
  signal shift_in, shift_en : std_logic;
  signal shift_dir : t_DIRECTION;

  -- Feedback functions
  signal lfsr_fb, cfsr_fb : std_logic;
  signal cfsr_fb_up, cfsr_fb_down : std_logic;

  subtype t_CNT is unsigned(WIDTH-1 downto 0);

  -- Counter value
  signal cnt_c, cnt_s : t_CNT;

begin

  -- Combinational logic of bi-dir shift register
  p_shift_c : process (cnt_s, shift_in, shift_dir, shift_en, CLR) is
  begin
    cnt_c <= cnt_s;
    if CLR = '1' and SEN = '0' then
      cnt_c <= (others => '0');
      if shift_dir = c_UP then
        cnt_c(cnt_c'high) <= '1';
      else
        cnt_c(0) <= '0';
      end if;
    elsif shift_en = '1' then
      if shift_dir = c_UP then
        cnt_c <= shift_in & cnt_s(WIDTH - 1 downto 1);
      else
        cnt_c <= cnt_s(WIDTH - 2 downto 0) & shift_in;
      end if;
    end if;
  end process;

  -- Connection of shift register
  shift_en <= SCK_EDGE when (SEN = '1' and SME = '0') else
              '0'      when (SEN = '1')               else
              CNTE;

  -- CFRS feedback added non-linear function
  cfsr_fb_up <= '1' when (cnt_s(t_CNT'high downto 1) = 0) else
                '0';
  cfsr_fb_down <= '1' when (cnt_s(t_CNT'high - 1 downto 0) = 0) else
                '0';

  cfsr_fb <= cfsr_fb_up when (UP_DOWN = c_UP) else
             cfsr_fb_down;

  -- Configuration of feedback
  shift_in <= SDI when (SEN = '1') else
              (lfsr_fb xor cfsr_fb);

  shift_dir <= c_UP when (SEN = '1') else
               UP_DOWN;

  -- LFSR feedback function
  p_lfsr_c : process (cnt_s) is
    variable next_bit : std_logic;
  begin
    next_bit := '0';
    -- Go trough each bit in generating polynom
    for i in t_CNT'range loop
      if ( UP_DOWN = c_UP) then
        next_bit := next_bit xor (POLYNOM(i) and cnt_s(i));
      elsif( UP_DOWN = c_DOWN) then
        next_bit := next_bit xor (POLYNOM(i+1) and cnt_s(i));
      else
        next_bit := next_bit;
      end if;
    end loop;
    lfsr_fb <= next_bit;
  end process p_lfsr_c;

  CNT_END <= (cfsr_fb_up and cfsr_fb_down) when (UP_DOWN = c_UP) else -- Current state is be 000...0
             (cfsr_fb_down and lfsr_fb);        -- Next state will be 100...0

  -- Register for counter
  p_adr_s : process (CLK, RST_B) is
  begin
    if RST_B = '0' then
      cnt_s <= ( others => '0');
    elsif rising_edge(CLK) then
      cnt_s <= cnt_c;
    end if;
  end process p_adr_s;

  -- Output assign
  CNT_OUT <= std_logic_vector(cnt_s);

  -- Serial bus output
  SDO     <= cnt_s(0);

end architecture rtl;
