----------------------------------------------
-- MSS copyright 2012
-- Filename: VA_ACS2.VHD
-- Author: Alain Zarembowitch / MSS
-- Inheritance: N/A
-- Edit date: 12/1/12
-- Revision: 2
-- See also VA_ACS.vhd 
--
-- VHDL code for Viterbi algorithm, Add-Compare-Select (ACS) unit. Uses modulo normalization technique.
-- Algorithm:
-- An important property of path metrics in an ACS unit is that the magnitude of the path metrics 
-- is unlimited, yet a fixed number limits the difference between them:
-- Viterbi algorithm inherently bounds the maximum dynamic range Lmax of state metrics 
-- Dmax =Lmax*log2N (N-number of states, Lmax maximum branch metric of the radix-2 trellis)
-- Compute new state metric with precision > 2Dmax
--
-- Rev2 11/29/12 AZ
-- Passed parameters through generic for more configuration flexibility instead of common com1509pkg
-- Time-sharing the adder to minimize the LUTs resources used.
----------------------------------------------      
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;	

entity VA_ACS2 is
	 Generic (
		NBITS_SM: integer := 11;
		NBITS_BM: integer := 6
	 );
   port (
		-- GLOBAL CLOCKS, RESET
		CLK: in std_logic;	-- synchronous clock
		SYNC_RESET: in std_logic;		-- synchronous reset. active high.

		--// INPUTS
		INPUT_VALID: in std_logic;
			-- read input sample at rising_edge of CLK and INPUT_VALID  = '1'.
			-- One CLK wide pulse.
		SM_A_IN: in std_logic_vector((NBITS_SM-1) downto 0);
		SM_B_IN: in std_logic_vector((NBITS_SM-1) downto 0);
			-- accumulated metric for starting states A and B respectively
		BM_A_IN: in std_logic_vector((NBITS_BM-1) downto 0);
		BM_B_IN: in std_logic_vector((NBITS_BM-1) downto 0);
			-- branch metrics for the two paths from starting states A and B and ending in state C 

		--// OUTPUTS
		-- Latency: 2 CLKs after INPUT_VALID
		SELECT_B_AN: out std_logic;
			-- selection 0 = A best, 0 = B best
		SM_C_OUT: out std_logic_vector((NBITS_SM-1) downto 0);	
			-- accumulated metric for end state C
		NORMALIZATION: out std_logic;
			-- '1' when an overflow condition was detected. This information can be helpful in assessing
			-- Viterbi decoder lock.
		OUTPUT_VALID: out std_logic
			-- read output at the rising edge of CLK when OUTPUT_VALID = '1'
			-- Latency: 2 CLKs after INPUT_VALID
    );
end entity;

architecture behavioral of VA_ACS2 is
-----------------------------------------------------------------
-- COMPONENTS
-----------------------------------------------------------------
-----------------------------------------------------------------
-- SIGNALS
-----------------------------------------------------------------
-- suffix _D denotes a one-CLK delayed version of the net with the same name
signal BM_A: std_logic_vector((NBITS_BM-1) downto 0) := (others => '0');
signal BM_B: std_logic_vector((NBITS_BM-1) downto 0) := (others => '0');
signal BM_B_D: std_logic_vector((NBITS_BM-1) downto 0) := (others => '0');
signal BM: std_logic_vector((NBITS_BM-1) downto 0) := (others => '0');
signal BM_D: std_logic_vector((NBITS_BM-1) downto 0) := (others => '0');
signal INPUT_VALID_D: std_logic := '0';
signal SM_B_IN_D: std_logic_vector((NBITS_SM-1) downto 0) := (others => '0');
signal SM_IN: std_logic_vector((NBITS_SM-1) downto 0) := (others => '0');
signal NEW_SM: std_logic_vector((NBITS_SM-1) downto 0) := (others => '0');
signal NEW_SM_A: std_logic_vector((NBITS_SM-1) downto 0) := (others => '0');
signal NEW_SM_B: std_logic_vector((NBITS_SM-1) downto 0) := (others => '0');

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

-- simulation. Prevents 'X' propagation in the accumulator during simulation.
BM_A <= BM_A_IN when (SIMULATION = '0') or (BM_A_IN(0) /= 'X') else (others => '0');
BM_B <= BM_B_IN when (SIMULATION = '0') or (BM_B_IN(0) /= 'X') else (others => '0');

-- accumulate. Modulo normalization: let overflow occur. Will be detected at the time of comparison below.
-- Number of bits NBITS_SM is sized to be greater than twice the state metric dynamic range.
-- The code is a little bit convoluted because we are trying to minimize the number of LUTs (since the ACS component 
-- is instantiated many times, 256 times when K=9). A single adder is shared between two successive additions at INPUT_VALID and INPUT_VALID_D.
-- This is possible because INPUT_VALID has a 50% duty cycle.
ACC_001: process(CLK)
begin
	if rising_edge(CLK) then
		INPUT_VALID_D <= INPUT_VALID;
		
		if(INPUT_VALID = '1') then
			-- delay SM_B and BM_B adder inputs for addition at the next clock
			SM_B_IN_D <= SM_B_IN;	
			BM_B_D <= BM_B;
			-- save NEW_SM_A adder output to realign with NEW_SM_B after time-shared adder
			NEW_SM_A <= NEW_SM;
		end if;
	end if;
end process;

-- adder inputs
SM_IN <= SM_A_IN when (INPUT_VALID = '1') else SM_B_IN_D;
BM <= BM_A when (INPUT_VALID = '1') else BM_B_D;

-- time-shared adder
NEW_SM <= SM_IN + ext(BM, NBITS_SM);
NEW_SM_B <= NEW_SM;  -- when (INPUT_VALID_D = '1')

-- Select best of two: A or B?
-- The smallest the cumulative state metric, the better.
-- Implements the modulo normalization technique.
SELECT_BEST_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			NORMALIZATION <= '0';
			SELECT_B_AN <= '0';
			SM_C_OUT <= (others => '0');
			OUTPUT_VALID <= '0';
		elsif(INPUT_VALID_D = '1') then
			OUTPUT_VALID <= '1';
			-- detect overflow (NEW_SM_A and NEW_SM_B) in different quarters
			if(NEW_SM_A((NBITS_SM-1) downto (NBITS_SM-2)) = "11") and
				(NEW_SM_B((NBITS_SM-1) downto (NBITS_SM-2)) = "00") then
				-- NEW_SM_B overflow: NEW_SM_A in top quarter, NEW_SM_B in lower quarter)
				NORMALIZATION <= '1';
				SELECT_B_AN <= '0';	-- A is best
				SM_C_OUT <= NEW_SM_A;
			elsif(NEW_SM_A((NBITS_SM-1) downto (NBITS_SM-2)) = "00") and
				(NEW_SM_B((NBITS_SM-1) downto (NBITS_SM-2)) = "11") then
				-- NEW_SM_A overflow: NEW_SM_B in top quarter, NEW_SM_A in lower quarter)
				NORMALIZATION <= '1';
				SELECT_B_AN <= '1';	-- B is best
				SM_C_OUT <= NEW_SM_B;
			elsif(NEW_SM_A < NEW_SM_B) then
				SELECT_B_AN <= '0';	-- A is best
				SM_C_OUT <= NEW_SM_A;
				NORMALIZATION <= '0';
			else
				SELECT_B_AN <= '1';	-- B is best
				SM_C_OUT <= NEW_SM_B;
				NORMALIZATION <= '0';
			end if;
		else
			OUTPUT_VALID <= '0';
		end if;
	end if;
end process;

end behavioral;					
