----------------------------------------------
-- MSS copyright 2012-2017
-- Filename: CPM_VA_ACS.VHD
-- Author: Alain Zarembowitch / MSS
-- Inheritance: N/A
-- Edit date: 10/30/17
-- Revision: 1
--
-- VHDL code for Viterbi algorithm, Add-Compare-Select (ACS) unit
----------------------------------------------      
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity CPM_VA_ACS is
 	generic (
		NBITS: integer := 10;	-- input/output precision
		NBITSM: integer := 12;  -- metrics precision
		SOVA_NBITS: integer := 4;	-- soft decision precision
		SOVA: std_logic := '1';	-- soft output enable (1) / disable (0)
		SIMULATION: std_logic := '0'
	);
   port (
		--// CLOCKS, RESET
		CLK: in std_logic;
			-- reference clock for synchronous design
		SYNC_RESET: in std_logic;
			-- synchronous reset, active high.


		--// INPUTS
		SAMPLE_CLK_IN: in std_logic;
			-- read input sample at rising_edge of CLK and SAMPLE_CLK_IN  = '1'.
			-- One CLK wide pulse.
		METRIC_A_IN: in std_logic_vector((NBITSM-1) downto 0);
		METRIC_B_IN: in std_logic_vector((NBITSM-1) downto 0);
			-- accumulated metric for starting states A and B respectively
		BM_A: in std_logic_vector((NBITS-1) downto 0);
		BM_B: in std_logic_vector((NBITS-1) downto 0);
			-- branch metrics for the two paths from starting states A and B and ending in state C 
		SHIFT_DOWN: in std_logic;
			-- subtract a fixed value from all accumulated metrics during this cycle to prevent saturation 

		--// OUTPUTS
		SELECT_OUT: out std_logic;
			-- winner selection 0 = A best, 1 = B best
		METRIC_C_OUT: out std_logic_vector((NBITSM-1) downto 0);	
			-- accumulated metric for end state C
		DELTA_OUT: out std_logic_vector((SOVA_NBITS-2) downto 0);
			-- metric difference (best - other). 7 is best confidence, 0 is worse
		NEAR_SATURATION: out std_logic;
			-- '1' when near saturation. Subtract a fixed value from all accumulated metrics (all ACSs) during the next cycle.
		SAMPLE_CLK_OUT: out std_logic
			-- Read at rising edge of CLK and SAMPLE_CLK_OUT = '1'.
			-- Latency: 2 CLKs after SAMPLE_CLK_IN
    );
end entity;

architecture behavioral of CPM_VA_ACS is
-----------------------------------------------------------------
-- COMPONENTS
-----------------------------------------------------------------
	component SIM2OUTFILE is
		 Generic (
			FILENAME: string
		 );
		 Port ( 
			--// CLK, RESET
			CLK: in std_logic;
				-- Reference clock
			DATA1: in std_logic_vector(11 downto 0);
			DATA2: in std_logic_vector(11 downto 0);
			DATA3: in std_logic_vector(11 downto 0);
			SAMPLE_CLK_IN: in std_logic
				-- DATA_IN is read at the rising edge of CLK when SAMPLE_CLK_IN = '1'

	);
	END COMPONENT;
-----------------------------------------------------------------
-- SIGNALS
-----------------------------------------------------------------
-- suffix _D denotes a one-CLK delayed version of the net with the same name
signal SAMPLE_CLK_IN_D: std_logic := '0';
signal SHIFT_DOWN_VALUE0: std_logic_vector((NBITSM-1) downto 0) := (others => '0');
signal SHIFT_DOWN_VALUE: std_logic_vector((NBITSM-1) downto 0) := (others => '0');
signal METRIC_A: std_logic_vector((NBITSM-1) downto 0) := (others => '0');
signal METRIC_B: std_logic_vector((NBITSM-1) downto 0) := (others => '0');
signal NEW_METRIC_A: signed(NBITSM downto 0) := (others => '0');
signal NEW_METRIC_B: signed(NBITSM downto 0) := (others => '0');
signal DELTA0: signed(NBITSM downto 0) := (others => '0');

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

-- simulation. Prevents 'X' propagation in the accumulator during simulation.
METRIC_A <= METRIC_A_IN when (SIMULATION = '0') or (METRIC_A_IN(0) /= 'X') else (others => '0');
METRIC_B <= METRIC_B_IN when (SIMULATION = '0') or (METRIC_B_IN(0) /= 'X') else (others => '0');

-- if saturation, subtract the maximum positive value of a branch metric (after 1/8 scaling)
SHIFT_DOWN_VALUE0((NBITSM-1) downto (NBITSM-3)) <= (others => '0');
SHIFT_DOWN_VALUE0(NBITSM-4) <= '1';
SHIFT_DOWN_VALUE0((NBITSM-5) downto 0) <= (others => '0');

SHIFT_DOWN_VALUE <=  SHIFT_DOWN_VALUE0  when (SHIFT_DOWN = '1') else (others => '0');


-- accumulate. Extend precision by 1 bit to avoid any overflow during accumulation.
-- scale down branch metric to leave enough headroom in the accumulator.
ACC_001: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE_CLK_IN_D <= SAMPLE_CLK_IN;
		
		if(SYNC_RESET = '1') then
			NEW_METRIC_A <= (others => '0');
			NEW_METRIC_B <= (others => '0');
		elsif(SAMPLE_CLK_IN = '1') then
			NEW_METRIC_A <= resize(signed(METRIC_A), NBITSM + 1) + resize(signed(BM_A((NBITS-1) downto 1)), NBITSM + 1) - resize(signed(SHIFT_DOWN_VALUE), NBITSM + 1);	
			NEW_METRIC_B <= resize(signed(METRIC_B), NBITSM + 1) + resize(signed(BM_B((NBITS-1) downto 1)), NBITSM + 1) - resize(signed(SHIFT_DOWN_VALUE), NBITSM + 1);	
		end if;
	end if;
end process;

-- Select best of two: A or B?
SELECT_BEST_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			SELECT_OUT <= '0';	
		elsif(SAMPLE_CLK_IN_D = '1') then
			if(NEW_METRIC_A > NEW_METRIC_B) then
				SELECT_OUT <= '0';	-- A is best
			else
				SELECT_OUT <= '1';	-- B is best
			end if;
		end if;
	end if;
end process;

-- prevent negative overflow, positive overflow
NEW_METRIC_001: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE_CLK_OUT <= SAMPLE_CLK_IN_D;
		
		if(SYNC_RESET = '1') then
			METRIC_C_OUT <= (others => '0');
		elsif(SAMPLE_CLK_IN_D = '1') then
			if(NEW_METRIC_A > NEW_METRIC_B) then
				-- A is best
				if(NEW_METRIC_A(NBITSM) = '0') and (NEW_METRIC_A(NBITSM-1) /= '0') then
					-- positive saturation
					METRIC_C_OUT(NBITSM-1) <= '0';
					METRIC_C_OUT((NBITSM-2) downto 0) <= (others => '1');
				elsif(NEW_METRIC_A(NBITSM) = '1') and (NEW_METRIC_A(NBITSM-1) /= '1') then
					-- negative saturation
					METRIC_C_OUT(NBITSM-1) <= '1';
					METRIC_C_OUT((NBITSM-2) downto 1) <= (others => '0');
					METRIC_C_OUT(0) <= '1';
				else
					METRIC_C_OUT <= std_logic_vector(NEW_METRIC_A((NBITSM-1) downto 0));
				end if;
			else
				-- B is best
				if(NEW_METRIC_B(NBITSM) = '0') and (NEW_METRIC_B(NBITSM-1) /= '0') then
					-- positive saturation
					METRIC_C_OUT(NBITSM-1) <= '0';
					METRIC_C_OUT((NBITSM-2) downto 0) <= (others => '1');
				elsif(NEW_METRIC_B(NBITSM) = '1') and (NEW_METRIC_B(NBITSM-1) /= '1') then
					-- negative saturation
					METRIC_C_OUT(NBITSM-1) <= '1';
					METRIC_C_OUT((NBITSM-2) downto 1) <= (others => '0');
					METRIC_C_OUT(0) <= '1';
				else
					METRIC_C_OUT <= std_logic_vector(NEW_METRIC_B((NBITSM-1) downto 0));
				end if;
			end if;
		end if;
	end if;
end process;

-- detect full or near saturation. A fixed value will be subtracted from the metrics in the next cycle.
NEAR_SATURATION_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			NEAR_SATURATION <= '0';
		elsif(SAMPLE_CLK_IN_D = '1') then
			if(NEW_METRIC_A(NBITSM downto (NBITSM-1)) = "01") then
				-- full saturation
				NEAR_SATURATION <= '1';
			elsif(NEW_METRIC_B(NBITSM downto (NBITSM-1)) = "01") then
				-- full saturation
				NEAR_SATURATION <= '1';
			elsif(NEW_METRIC_A(NBITSM downto (NBITSM-4)) = "0111") then
				-- near saturation
				NEAR_SATURATION <= '1';
			elsif(NEW_METRIC_B(NBITSM downto (NBITSM-4)) = "0111") then
				-- near saturation
				NEAR_SATURATION <= '1';
			else
				NEAR_SATURATION <= '0';
			end if;
		end if;
	end if;
end process;

-- metric difference (best - other). Use if soft output.
-- Quantize down to SOVA_NBITS-1 bits
DELTA_001: if(SOVA = '1') generate
	DELTA0 <= NEW_METRIC_A - NEW_METRIC_B;

	DELTA_001: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SYNC_RESET = '1') then
				DELTA_OUT <= (others => '0');
			elsif(SAMPLE_CLK_IN_D = '1') then
				-- truncate
				if(DELTA0(NBITSM) = '0') then
					--positive
					-- A is best
					if(DELTA0(NBITSM-1 downto NBITSM-9+SOVA_NBITS) /= 0) then
						-- positive saturation
						DELTA_OUT <= (others => '1');
					else
						DELTA_OUT <= std_logic_vector(DELTA0(NBITSM-10+SOVA_NBITS downto NBITSM-12+SOVA_NBITS));
					end if;
				else
					-- negative
					-- B is best
					if(not (DELTA0(NBITSM-1 downto NBITSM-9+SOVA_NBITS)) /= 0) then
						-- negative saturation
						DELTA_OUT <= (others => '1');
					else
						DELTA_OUT <= std_logic_vector(not DELTA0(NBITSM-10+SOVA_NBITS downto NBITSM-12+SOVA_NBITS));
					end if;
				end if;
			end if;
		end if;
	end process;
end generate;

--// simulation. send to a tab-delimited text file for subsequent (Matlab) plot.
--SIM2OUTFILE_X: if(SIMULATION = '1') generate
-- 	SIM2OUTFILE_001: SIM2OUTFILE 
--	generic map(
--		FILENAME => "C:\Users\Alain\Documents\1VHDL\com-1827 FSK modem 001\matlab\acs.txt"
--	)
--	PORT MAP(
--		CLK => CLK,
--		DATA1 => std_logic_vector(DELTA0(DELTA0'left downto DELTA0'left-11)),
--		DATA2 => x"000",		
--		DATA3 => x"000",
--		SAMPLE_CLK_IN => SAMPLE_CLK_IN_D
--	);
--end generate;

end behavioral;					
