-------------------------------------------------------------
-- MSS copyright 2012-2014
-- Filename:  VA_TBU2.VHD
-- Authors: 
--		Alain Zarembowitch / MSS
-- Version: Rev 4
-- Last modified: 10/7/14
-- See also: VA_TBU.VHD (GMSK demod)

-- Inheritance: 	N/A
--
-- description:  Viterbi algorithm, traceback unit
-- The ACS outputs are written continuously to an elastic buffer.
-- Traceback is done by blocks of 2*TB_DEPTH bits. The first TB_DEPTH bits are ignored to give time
-- for the algorithm to converge. The process is repeated every TB_DEPTH bits.
--
-- Rev2 11/30/12 AZ
-- Passed parameters through generic for more configuration flexibility instead of common com1509pkg
-- changed N_TB_RAMB16 computation
--
-- Rev3 12/8/12 AZ
-- Corrected bug in TB_COUNTDOWN size (1 bit too short for TB_DEPTH between 64 and 120 
--
-- Rev 4 10/7/14 AZ
-- rephrase to work with Xilinx Vivado + ISIM
---------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity VA_TBU2 is
	 Generic (
		K_MAX: integer := 9;
		N_TRELLIS_STATES_MAX: integer := 256;
		TB_DEPTH: integer := 54
	 );
    Port ( 
		-- GLOBAL CLOCKS, RESET
		CLK: in std_logic;	-- synchronous clock
		SYNC_RESET: in std_logic;		-- synchronous reset. active high.
		
		-- ACSs decision outputs (1 bit for each treillis state)
		SELECT_B_AN: in std_logic_vector((N_TRELLIS_STATES_MAX-1) downto 0);
		INPUT_VALID: in std_logic;
			-- 1-CLK wide pulse for each set of N_TRELLIS_STATES decisions
			-- above inputs are read at the rising edge of CLK when INPUT_VALID = '1'

		--// CONFIGURATION 
		-- The configuration parameters below can be changed dynamically at run-time.
		-- Use SYNC_RESET after such a configuration change.
		K: in integer range 3 to 9;
			-- constraint length.
			-- valid values: 3,5,7,9
			-- MUST be less than or equal to the hardware capabilities set by K_MAX defined in com1509pkg.vhd

		--// OUTPUTS
		DECODED_BITS: out std_logic_vector(TB_DEPTH-1 downto 0);
		OUTPUT_VALID: out std_logic
		
		);
end entity;

architecture Behavioral of VA_TBU2 is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
	COMPONENT BRAM_DP
	GENERIC(
		DATA_WIDTHA: integer;
		ADDR_WIDTHA: integer;
		DATA_WIDTHB: integer;
		ADDR_WIDTHB: integer
	);
	PORT(
	    CLKA   : in  std_logic;
	    WEA    : in  std_logic;
	    ADDRA  : in  std_logic_vector(ADDR_WIDTHA-1 downto 0);
	    DIA   : in  std_logic_vector(DATA_WIDTHA-1 downto 0);
	    DOA  : out std_logic_vector(DATA_WIDTHA-1 downto 0);
	    CLKB   : in  std_logic;
	    WEB    : in  std_logic;
	    ADDRB  : in  std_logic_vector(ADDR_WIDTHB-1 downto 0);
	    DIB   : in  std_logic_vector(DATA_WIDTHB-1 downto 0);
	    DOB  : out std_logic_vector(DATA_WIDTHB-1 downto 0)
		);
	END COMPONENT;
--------------------------------------------------------
--     SIGNALS
--------------------------------------------------------
-- Suffix _D indicates a one CLK delayed version of the net with the same name
-- Suffix _E indicates a one CLK early version of the net with the same name
-- Suffix _X indicates an extended precision version of the net with the same name
-- Suffix _N indicates an inverted version of the net with the same name
-- Suffix G indicates a global clock

constant N_TB_RAMB16: integer := (N_TRELLIS_STATES_MAX + 16)/32; -- changed AZ 11/30/12
--constant N_TB_RAMB16: integer := (N_TRELLIS_STATES_MAX + 1)/32;
	-- number of 16Kbit RAM blocks used for traceback. 

----//-- WRITE TO DP RAM ------------------------------------------------------
signal WPTR: std_logic_vector(8 downto 0) := (others => '0');
type SLV32xN_TB_RAMB16 is array (integer range 0 to (N_TB_RAMB16 - 1)) of std_logic_vector(31 downto 0);
signal DIA: SLV32xN_TB_RAMB16  := (others => (others => '0'));
signal DOB: SLV32xN_TB_RAMB16  := (others => (others => '0'));

----//-- READ FROM DP RAM ------------------------------------------------------
signal EVENT1: std_logic := '0';
signal EVENT1_D: std_logic := '0';
--signal EVENT2: std_logic := '0';
signal RPTR: std_logic_vector(8 downto 0) := (others => '0');
signal RPTR_START: std_logic_vector(8 downto 0) := (others => '0');
--signal BUF_SIZE: std_logic_vector(8 downto 0) := (others => '0');
signal RPTR_NEXT: std_logic_vector(8 downto 0) := (others => '0');
signal TB_STATE: integer range 0 to 2 := 0;

--//-- TRACEBACK -------------------------------------------
signal TB_SELECT_B_AN: std_logic_vector((N_TRELLIS_STATES_MAX-1) downto 0) := (others => '0');
signal TB_COUNTDOWN: std_logic_vector(7 downto 0) := (others => '0');
signal BEST_STATE: integer range 0 to (N_TRELLIS_STATES_MAX-1) := 0;
signal BEST_STATE_slv: std_logic_vector((K_MAX-2) downto 0) := (others => '0');
signal PREV_BEST_STATE_slv: std_logic_vector((K_MAX-2) downto 0) := (others => '0');
signal BEST_STATE_MASK: std_logic_vector((K_MAX-2) downto 0) := (others => '0');
signal DECODED_BITS_local: std_logic_vector(TB_DEPTH-1 downto 0) := (others => '0');
signal OUTPUT_VALID_local: std_logic := '0';

--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------
begin


----//-- WRITE TO DP RAM ------------------------------------------------------
-- write enable, A-side of the elastic buffer
N_BRAM_K35: if(K_MAX = 3) or (K_MAX = 5) generate
	DIA(0) <= ext(SELECT_B_AN,32);
end generate;
N_BRAM_K7: if(K_MAX = 7) generate
	DIA(0) <= SELECT_B_AN(31 downto 0);
	DIA(1) <= SELECT_B_AN(63 downto 32);
end generate;
N_BRAM_K9: if(K_MAX = 9) generate
	DIA(0) <= SELECT_B_AN(31 downto 0);
	DIA(1) <= SELECT_B_AN(63 downto 32);
	DIA(2) <= SELECT_B_AN(95 downto 64);
	DIA(3) <= SELECT_B_AN(127 downto 96);
	DIA(4) <= SELECT_B_AN(159 downto 128);
	DIA(5) <= SELECT_B_AN(191 downto 160);
	DIA(6) <= SELECT_B_AN(223 downto 192);
	DIA(7) <= SELECT_B_AN(255 downto 224);
end generate;


-- WPTR management
WPTR_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			WPTR <= (others => '0');
		elsif(INPUT_VALID = '1') then
			WPTR <= WPTR + 1;
		end if;
	end if;
end process;

-- 16Kbit buffer(s) 
BRAM_DP_00x: for I in 0 to (N_TB_RAMB16 -1) generate
	Inst_BRAM_DP: BRAM_DP 
	GENERIC MAP(
		DATA_WIDTHA => 32,		
		ADDR_WIDTHA => 9,
		DATA_WIDTHB => 32,		 
		ADDR_WIDTHB => 9

	)
	PORT MAP(
		CLKA => CLK,
		WEA => INPUT_VALID,
		ADDRA => WPTR,	-- 9-bit address
		DIA => DIA(I),	-- 32-bit wide
		DOA => open,
		CLKB => CLK,
		WEB => '0',
		ADDRB => RPTR,	-- 9-bit address
		DIB => x"00000000",
		DOB => DOB(I)
	);
end generate;

--//-- READ FROM DP RAM ------------------------------------------------------
--BUF_SIZE <= WPTR - RPTR_NEXT;	
	-- number unread entries

EVENT1 <= '1' when (INPUT_VALID = '1') and (WPTR = RPTR_NEXT - 1) else '0';
	-- writing the last entry in a 2*TB_DEPTH window
--EVENT2 <= '1' when (TB_STATE = 1) and (RPTR = RPTR_START) else '0';
	-- reading the last entry in a  2*TB_DEPTH window

TB_STATE_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			TB_STATE <= 0;
		elsif(EVENT1 = '1') then
			TB_STATE <= 1;
		elsif(TB_STATE = 1) and (RPTR = RPTR_START) then	
			TB_STATE <= 0;
		end if;
	end if;
end process;

RPTR_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			RPTR <= (others => '0');
			RPTR_START <= (others => '0');
			RPTR_NEXT <= conv_std_logic_vector(2*TB_DEPTH,RPTR_NEXT'length);
		elsif(EVENT1 = '1') then
			RPTR <= RPTR_NEXT - 1;
			RPTR_NEXT <=  RPTR_NEXT + TB_DEPTH;
			if(TB_STATE = 1) and (RPTR = RPTR_START) then
				-- end of window write coincides with end of window read. Start a new read without waiting.
				RPTR_START <= RPTR_START + TB_DEPTH;
			end if;
		elsif(TB_STATE = 1) then
			-- tracing back = countdown back in time
			if(RPTR /= RPTR_START) then
				RPTR <= RPTR - 1;
			else
				RPTR_START <= RPTR_START + TB_DEPTH;
			end if;
		end if;
	end if;
end process;

--//-- TRACEBACK -------------------------------------------
TB_K35: if(K_MAX = 3) or (K_MAX = 5) generate
	TB_SELECT_B_AN <= DOB(0)((N_TRELLIS_STATES_MAX-1) downto 0) ;
end generate;
TB_K7: if(K_MAX = 7) generate
	TB_SELECT_B_AN(31 downto 0) <= DOB(0) ;
	TB_SELECT_B_AN(63 downto 32) <= DOB(1) ;
end generate;
TB_K9: if(K_MAX = 9) generate
	TB_SELECT_B_AN(31 downto 0) <= DOB(0) ;
	TB_SELECT_B_AN(63 downto 32) <= DOB(1) ;
	TB_SELECT_B_AN(95 downto 64) <= DOB(2) ;
	TB_SELECT_B_AN(127 downto 96) <= DOB(3) ;
	TB_SELECT_B_AN(159 downto 128) <= DOB(4) ;
	TB_SELECT_B_AN(191 downto 160) <= DOB(5) ;
	TB_SELECT_B_AN(223 downto 192) <= DOB(6) ;
	TB_SELECT_B_AN(255 downto 224) <= DOB(7) ;
end generate;
 
-- start at state0 and trace back
TRACE_STATE_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		EVENT1_D <= EVENT1;	
		
		if (SYNC_RESET = '1') then
			BEST_STATE <= 0;		-- initial traceback state does not matter since algorithm converges after TB_DEPTH
			-- To test convergence, compare BEST_STATE during simulations with different initial states. Should be identical for 
			-- the last TB_DEPTH iterations.
			TB_COUNTDOWN <= (others => '0');
		elsif(EVENT1_D = '1') then
			BEST_STATE <= 0;		-- initial traceback state does not matter since algorithm converges after TB_DEPTH
			-- To test convergence, compare BEST_STATE during simulations with different initial states. Should be identical for 
			-- the last TB_DEPTH iterations.
			TB_COUNTDOWN <= conv_std_logic_vector(2*TB_DEPTH,TB_COUNTDOWN'length);
		elsif(TB_COUNTDOWN /= 0) then	
			TB_COUNTDOWN <= TB_COUNTDOWN -1;
			BEST_STATE <= conv_integer(PREV_BEST_STATE_slv);
		end if;
	end if;
end process;

BEST_STATE_slv <= conv_std_logic_vector(BEST_STATE, K_MAX-1);
--PREV_BEST_STATE_slv <= ext(BEST_STATE_slv((K-3) downto 0) & TB_SELECT_B_AN(BEST_STATE), K_MAX-1);
-- rephrase 10/7/14 for Xilinx Vivado, ISIM
PREV_BEST_STATE_slv <= (BEST_STATE_slv((BEST_STATE_slv'left-1) downto 0) & TB_SELECT_B_AN(BEST_STATE)) and BEST_STATE_MASK;

BEST_STATE_MASK_gen: process(CLK)
begin
	if rising_edge(CLK) then
		if(K_MAX >= 5) and (K = K_MAX-2) then
			BEST_STATE_MASK((K_MAX-4) downto 0) <= (others => '1');
			BEST_STATE_MASK((K_MAX-2) downto (K_MAX-3)) <= (others => '0');
		elsif(K_MAX >= 7) and (K = K_MAX-4) then
			BEST_STATE_MASK((K_MAX-6) downto 0) <= (others => '1');
			BEST_STATE_MASK((K_MAX-2) downto (K_MAX-5)) <= (others => '0');
		elsif(K_MAX = 9) and (K = K_MAX-6) then
			BEST_STATE_MASK((K_MAX-8) downto 0) <= (others => '1');
			BEST_STATE_MASK((K_MAX-2) downto (K_MAX-7)) <= (others => '0');
		else
			BEST_STATE_MASK((K_MAX-2) downto 0) <= (others => '1');
		end if;
	end if;
end process;

TRACE_STATE_GEN_002: process(CLK)
begin
	if rising_edge(CLK) then
		if(TB_COUNTDOWN /= 0) then	
				-- going back in time. drop newest entry (bit K-2 on the left), reconstruct oldest entry (bit 0 on the right)
			DECODED_BITS_local(TB_DEPTH-1 downto 1) <= DECODED_BITS_local(TB_DEPTH-2 downto 0);
			DECODED_BITS_local(0) <= BEST_STATE_slv(K-2);
				-- shift in the last TB_DEPTH decoded bits in this window
			if(TB_COUNTDOWN = 1) then
				OUTPUT_VALID_local <= '1';
			else
				OUTPUT_VALID_local <= '0';
			end if;
		else
			OUTPUT_VALID_local <= '0';
		end if;
	end if;
end process;


-- freeze output
OUTPUT_002: process(CLK)
begin
	if rising_edge(CLK) then
		OUTPUT_VALID <= OUTPUT_VALID_local;
		
		if(OUTPUT_VALID_local = '1') then
			DECODED_BITS <= DECODED_BITS_local;
		end if;
	end if;
end process;


end Behavioral;

