-------------------------------------------------------------
-- Filename:  CPM_MF.VHD
-- Authors: 
--		Alain Zarembowitch / MSS
-- Version: Rev 3
-- Last modified: 4/12/19

-- Inheritance: 	N/A
--
-- Algorithm: clearest explanation see "Comparison of Noncoherent detectors
--	for SOQPSK and GMSK in Phase Noise Channels, Afzal Syed, MS thesis, KU), 
-- Chapter 3 Coherent Detectors for SOQPSK
--
-- Description:
-- For each estimated phase (p cases), and for each estimated symbol sequence over L (M^L cases), 
-- we compute a real value as follows
-- Step 1: for each L-symbols hypothesis (there are M^L), look-up the 4 phase shift samples over a symbol T
-- Step 2: for each L-symbols hypothesis (there are M^L), derotate the 4 received complex samples and sum.
-- thus producing M^L complex matched filter outputs.
-- Step 3: rotate each by 2*pi/4 and take the real value. Thus generating 4M^L real branch metrics
-- for use by follow-on Viterbi algorithm
-- The matched filters are computed twice per symbol.
-- The branch metrics are computed once per symbol.
--
-- LIMITATIONS in the code as currently written:
-- Fixed 4 samples/symbol.
-- Final phase rotation limited to multiple of 90 deg (h=0.5)
-- Maximum symbol rate <= fclkp/4 (4 CLKs min between successive symbols SYMBOL_CLK_IN)
--
-- Design note: even though it would be clearer to use 2D arrays, encountered simulation problems.
-- Thus the 2D arrays were declared as longer 1D array.
--
-- Rev 3 4/12/19 
-- Corrected configuration management bug regarding SOQPSK
-- 
-- Size (MPOWERLMAX = 4, PMAX = 4) 
-- FF: 838
-- LUT: 930
-- 18Kb BRAM: 1
-- DSP48: 33
-- 6.660ns (Maximum Frequency: 150.150MHz) (Artix7-100t-1)
---------------------------------------------------------------
-- VIVADO NOTE: WHEN COMPILING, MAKE SURE THIS FILE IS COMPILED BEFORE FSK_DEMOD which refers to the same package. 
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
Package CPM_MF_PKG is
	constant	CPM_MF_NBITS: integer := 10;	
			-- input samples precision
	constant	NSAMPLESPERSYMBOL: integer := 4;		
			-- valid: 4 (ONLY)
	constant MPOWERLMAX: integer := 4;	
	constant	LOG2_MPOWERLMAX: integer := 2;
		-- M^L   
		-- maximum number of hypothesis regarding the possible symbols over the matched filter integration window
		-- Examples: 
		--  MSK  M=2, L=1, M^L=2
		--  PCM/FM M=2, L=2, M^L=4
		--  SOQPSK-MIL  M=3, L(truncated) = 1, M^L = 3
		--  SOQPSK-TG  M=3, L(truncated) = 1, M^L = 3
		--  GMSK BT=0.3  M=2, L(truncated) = 1, M^L = 2
	constant PMAX: integer := 4;
		-- number of initial phase assumptions when computing the branch metrics
		-- valid only for 4 (phase modulation index h=0.5)
	constant	N_BRANCHES_MAX: integer := 16;
		-- 8 for MSK,PCM/FM
		-- 16 for SOQPSK
	constant	N_TRELLIS_STATES_MAX: integer := 8;
		-- maximum number of trellis states = P*M^(L-1) for coherent demod
	constant	LOG2_N_TRELLIS_STATES_MAX: integer := 3;
	constant	SOVA: std_logic := '1';	-- soft output enable (1) / disable (0)
	constant SOVA_NBITS: integer := 4;	-- soft output number of bits (including 1 info bit)
	type BM_TYPE is array (integer range 0 to N_BRANCHES_MAX-1) of std_logic_vector((CPM_MF_NBITS-1) downto 0);
	type MF_TYPE is array (integer range 0 to MPOWERLMAX-1) of std_logic_vector((CPM_MF_NBITS-1) downto 0);
	type DELTA_TYPE is array (integer range 0 to N_TRELLIS_STATES_MAX-1) of std_logic_vector((SOVA_NBITS-2) downto 0);
End Package;

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;
use work.CPM_MF_PKG.all;	-- defines types for this interface

entity CPM_MF is
    Port ( 
		--GLOBAL CLOCK, RESET
		CLK   : in  std_logic;
		SYNC_RESET: in std_logic;	
			-- synchronous reset
			-- must use SYNC_RESET after changing the modulation scheme
		
		-- INPUT SAMPLES
		DATA_I_IN  : in  std_logic_vector((CPM_MF_NBITS-1) downto 0);
		DATA_Q_IN  : in  std_logic_vector((CPM_MF_NBITS-1) downto 0);
			-- received complex baseband signal. Sampled at 4x symbols. 
			-- read DATA_X_IN when SAMPLE_CLK_IN = '1'.
		SAMPLE_CLK_IN: in std_logic;	
		SAMPLE_CNTR_IN: in std_logic_vector(1 downto 0);
			-- modulo 4 counter to delineate a symbol
			-- 0 is generally the symbol center, 2 the symbol edge

		-- CONTROLS
		-- must use SYNC_RESET upon changing the modulation scheme below
		M_SEL: in std_logic_vector(2 downto 0);
 			-- modulation order M selection 
			-- 0: M=2, 1: M=4, 2: M=8, 3: M=3 SOQPSK precoder
		FILT_SEL: in std_logic_vector(3 downto 0);
			-- filter selection
			-- 0 = FSK
			-- 1 = PCM/FM
			-- 2 = GFSK BT=0.7
			-- 3 = GFSK BT=0.5
			-- 4 = GFSK BT=0.3
			-- 5 = SOQPSK-MIL
			-- 6 = SOQPSK-TG
			-- 7 = multi-h ARTM CPM (Tier 2) 
			-- 8 = GFSK BT=0.25
		MODULATION_INDEX: in std_logic_vector(15 downto 0);
			-- user-specified modulation index. signed fixed-point format 4.12
			-- read any change at MODULATION_INDEX_VALID = '1'
		MODULATION_INDEX_VALID: in std_logic;
			-- 1 CLK-wide pulse to indicate that the modulation index changes
		
		-- Branch metrics outputs, once per symbol (only for coherent demodulation)
		BM_OUT: out BM_TYPE;
			-- Branch metrics: real signed value. 
			-- One for each L-symbols hypothesis, and for each initial phase hypothesis.
			-- Example for MSK: BM(6) [6 = 110] is the likelihood of receiving a '1' (MSb) while at state 10 (LSbs)
			-- Valid only for h=0.5 cases. Therefore not valid for PCM/FM h=0.7
			-- Computed once per symbol.
		SYMBOL_CLK_OUT: out std_logic;
			-- latency: 7 CLK after SYMBOL_CLK_IN
		
		-- matched filters complex outputs (before rotation)
		-- MPOWERLMAX complex numbers. Aligned with SYMBOL_CLKx2_IN_D7. CPM_MF_NBITS precision on both I and Q
		-- Computed twice per symbol
		MF_I_OUT: out MF_TYPE;
		MF_Q_OUT: out MF_TYPE;
		MF_SYMBOL_CLKx2_OUT: out std_logic;
		MF_SYMBOL_CLK_OUT: out std_logic;
		
		COS_PIH: out std_logic_vector(7 downto 0);
		SIN_PIH: out std_logic_vector(7 downto 0);
			-- cos(pi*h) and sin(pi*h) to be used by multi-symbol detector to rotate the matched filter outputs
			
		-- MONITORING
		MF_INITIALIZED: out std_logic
			-- all internal coefficients have been properly initialized and are now ready to use. 
			-- After reset or at power up, better wait for this flag before processing input samples
		);
end entity;


architecture Behavioral of CPM_MF is
-----------------------------------------------------------------
-- COMPONENTS
-----------------------------------------------------------------
	-- sin/cos look-up table, 4 quadrants, 16K steps x 18 bits precision
	component SIGNED_SIN_COS_TBL3 is
		generic (
			NADDR: integer
		);
		port (
			CLK: in std_logic;
			PHASE: in std_logic_vector(NADDR+1 downto 0);
			SIN_OUT: out std_logic_vector(17 downto 0);
			COS_OUT: out std_logic_vector(17 downto 0)
		);
	end component;
	
	COMPONENT CPM_PHASEx4
	PORT(
		CLKA : IN std_logic;
		CSA : IN std_logic;
		OEA : IN std_logic;
		ADDRA  : in  std_logic_vector(9 downto 0);
		DOA  : out std_logic_vector(11 downto 0);
		CLKB : IN std_logic;
		CSB : IN std_logic;
		OEB : IN std_logic;
		ADDRB  : in  std_logic_vector(9 downto 0);
		DOB  : out std_logic_vector(11 downto 0)
		);
	END COMPONENT;

	component SIM2OUTFILE is
		 Generic (
			FILENAME: string
		 );
		 Port ( 
			CLK: in std_logic;
			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
	);
	END COMPONENT;

--------------------------------------------------------
--     SIGNALS
--------------------------------------------------------
constant NBITS_COEFF: integer := 8;
	-- matched filter coefficients precision, 
	-- also precision of sin/cos phase rotation

--//-- SELECTED MODULATION PARAMETERS ----------------------------------
signal L: unsigned(1 downto 0):= (others => '0');
	-- truncated frequency impulse length (in number of symbols)
	-- valid range [1 - LMAX]
signal MPOWERL: unsigned(4 downto 0):= (others => '0');	-- limitation: M^L <= 16
signal SYMBOL_CLKx4_IN: std_logic := '0';
signal SYMBOL_CLKx2_IN: std_logic := '0';
signal SYMBOL_CLK_IN: std_logic := '0';
signal SYMBOL_CLK_IN_SHIFT: std_logic_vector(12 downto 1) := (others => '0');
signal SYMBOL_CLKx2_IN_SHIFT: std_logic_vector(12 downto 1) := (others => '0');
type NSAMPLES is array (integer range 0 to NSAMPLESPERSYMBOL-1) of signed((CPM_MF_NBITS-1) downto 0);
signal DATA0_I: NSAMPLES := (others => (others => '0'));
signal DATA0_Q: NSAMPLES := (others => (others => '0'));
signal DATA1_I: NSAMPLES := (others => (others => '0'));
signal DATA1_Q: NSAMPLES := (others => (others => '0'));
type MULT1A is array (integer range 0 to (MPOWERLMAX-1)) of signed((CPM_MF_NBITS-1) downto 0);
signal MULT21A: MULT1A := (others => (others => '0'));
signal MULT22A: MULT1A := (others => (others => '0'));
signal MULT23A: MULT1A := (others => (others => '0'));
signal MULT24A: MULT1A := (others => (others => '0'));
signal MULT25A: MULT1A := (others => (others => '0'));
signal MULT26A: MULT1A := (others => (others => '0'));
signal MULT27A: MULT1A := (others => (others => '0'));
signal MULT28A: MULT1A := (others => (others => '0'));
-- 2D arrays are declared as a longer 1D to alleviate simulator problems
type TCOEFFS is array (integer range 0 to 31) of signed((NBITS_COEFF-1) downto 0);
	-- adjust for additional filters. here sized for pcm/fm worst case L=2,M=2,4samples/symbol
signal COEFFS_I: TCOEFFS := (others => (others => '0'));
signal COEFFS_Q: TCOEFFS := (others => (others => '0'));
signal MULT21B: TCOEFFS := (others => (others => '0'));
signal MULT22B: TCOEFFS := (others => (others => '0'));
signal MULT23B: TCOEFFS := (others => (others => '0'));
signal MULT24B: TCOEFFS := (others => (others => '0'));
signal MULT25B: TCOEFFS := (others => (others => '0'));
signal MULT26B: TCOEFFS := (others => (others => '0'));
signal MULT27B: TCOEFFS := (others => (others => '0'));
signal MULT28B: TCOEFFS := (others => (others => '0'));
type MULT1 is array (0 to (MPOWERLMAX-1)) of signed((CPM_MF_NBITS+NBITS_COEFF-1) downto 0);
signal MULT21: MULT1 := (others => (others => '0'));
signal MULT22: MULT1 := (others => (others => '0'));
signal MULT23: MULT1 := (others => (others => '0'));
signal MULT24: MULT1 := (others => (others => '0'));
signal MULT25: MULT1 := (others => (others => '0'));
signal MULT26: MULT1 := (others => (others => '0'));
signal MULT27: MULT1 := (others => (others => '0'));
signal MULT28: MULT1 := (others => (others => '0'));
type MULT2 is array (0 to (MPOWERLMAX-1)) of signed((CPM_MF_NBITS-1) downto 0);
signal DATA21_I: MULT2 := (others => (others => '0'));
signal DATA21_Q: MULT2 := (others => (others => '0'));
signal DATA22_I: MULT2 := (others => (others => '0'));
signal DATA22_Q: MULT2 := (others => (others => '0'));
signal DATA23_I: MULT2 := (others => (others => '0'));
signal DATA23_Q: MULT2 := (others => (others => '0'));
signal DATA24_I: MULT2 := (others => (others => '0'));
signal DATA24_Q: MULT2 := (others => (others => '0'));
type SUM1 is array (0 to (MPOWERLMAX-1)) of signed((CPM_MF_NBITS+1) downto 0);
signal DATA3_I: SUM1 := (others => (others => '0'));
signal DATA3_Q: SUM1 := (others => (others => '0'));
--type COSSIN is array (0 to (PMAX/2-1)) of signed((NBITS_COEFF-1) downto 0);
--signal COS: COSSIN := (others => (others => '0'));
--signal SIN: COSSIN := (others => (others => '0'));
signal BM_local: BM_TYPE;
signal BM_local2: BM_TYPE;

--//-- INITIALIZATION STATES -----------------------------------------
signal STATE: integer range 0 to 2 := 0;
signal STATE_D: integer range 0 to 2 := 0;
signal STATE_D2: integer range 0 to 2 := 0;
signal STATE_D3: integer range 0 to 2 := 0;
signal STATE_D4: integer range 0 to 2 := 0;

--//-- INITIALIZE SIN/COS COEFFICIENTS ----------------------------------
signal SEQ: unsigned(6 downto 0) := (others => '0');
signal SEQ_D: unsigned(6 downto 0) := (others => '0');
signal SEQ_D2: unsigned(6 downto 0) := (others => '0');
signal SEQ_D3: unsigned(6 downto 0) := (others => '0');
signal SEQ_VALID_D: std_logic := '0';
signal SEQ_VALID_D2: std_logic := '0';
signal SEQ_VALID_D3: std_logic := '0';
signal PHASE_IN: std_logic_vector(11 downto 0) := (others => '0');
signal SIN_OUT: std_logic_vector(17 downto 0) := (others => '0');
signal COS_OUT: std_logic_vector(17 downto 0) := (others => '0');
signal SIN_T: std_logic_vector((NBITS_COEFF-1) downto 0) := (others => '0');
signal COS_T: std_logic_vector((NBITS_COEFF-1) downto 0) := (others => '0');

--//-- INITIALIZE MATCHED FILTER COEFFICIENTS ----------------------------------
signal T: unsigned(1 downto 0) := (others => '0');	
signal T_INC: unsigned(2 downto 0) := (others => '0');
signal SYMBS_HYPO: unsigned(3 downto 0) := (others => '0');	-- limitation: M^L <= 16
signal SYMBS_HYPO_INC: unsigned(4 downto 0) := (others => '0');	-- limitation: M^L <= 16
signal COEFF_INDEX: unsigned(6 downto 0):= (others => '0');
signal COEFF_INDEX_D: unsigned(6 downto 0):= (others => '0');
signal COEFF_INDEX_D2: unsigned(6 downto 0):= (others => '0');
signal COEFF_INDEX_D3: unsigned(6 downto 0):= (others => '0');
signal COEFF_INDEX_D4: unsigned(6 downto 0):= (others => '0');
signal CPM_PHASE_ADDRA: std_logic_vector(9 downto 0) := (others => '0');
signal CPM_PHASE_DOA: std_logic_vector(11 downto 0) := (others => '0');
signal CPM_PHASExH: signed(27 downto 0) := (others => '0');

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

--//-- SELECTED MODULATION PARAMETERS ----------------------------------
-- Typical values for M,L,P:
-- 2-FSK  h=0.5,   M = 2, L = 1, P = 4 
-- PCM/FM h = 0.7  M = 2, L = 2, P = N/A(non-coherent demod)
-- GMSK M = 2, L = 1(truncated), P = 4
-- SOQPSK-MIL h = 0.5, M = 3, L = 1, P = 4
-- SOQPSK-TG h = 0.5, M = 3, L = 1(truncated), P = 4
-- ARTM CPM h=4/16 or 5/16, M = 4, L = 3, P = 32

MOD_SEL_001: process(CLK)
begin
	if rising_edge(CLK) then
		case FILT_SEL is 
			when "0001" => 	-- 1 = PCM/FM
				L <= "10";
			when others =>
				L <= "01";
		end case;
	end if;
end process;

-- compute M^L
MOD_SEL_002: process(CLK)
begin
	if rising_edge(CLK) then
		if(unsigned(M_SEL) = 0) and (L = 1) then
			-- M = 2, L = 1
			MPOWERL <= "00010";
		elsif(unsigned(M_SEL) = 3) and (L = 1) then
			-- M = 3, L = 1
			MPOWERL <= "00011";
		elsif(unsigned(M_SEL) = 0) and (L = 2) then
			-- M = 2, L = 2
			MPOWERL <= "00100";
		end if;
	end if;
end process;



--//-- INPUT CONDITIONING ----------------------------------

-- Align the symbol center (symbol tracking loop) and the cumulative phase response in COEFFS prior to integration
-- When symbol tracking loop is locked, SAMPLE_CNTR_IN = 0 represents the symbol center = maximum frequency deviation
SYMBOL_CLKx4_IN <= SAMPLE_CLK_IN;
SYMBOL_CLKx2_IN <= SAMPLE_CLK_IN when (SAMPLE_CNTR_IN(0) = '0') else '0';
SYMBOL_CLK_IN <= SAMPLE_CLK_IN when (L(0) = '1') and (unsigned(SAMPLE_CNTR_IN) = 2) else -- L = 1 or 3
					  SAMPLE_CLK_IN when (L = 2) and (unsigned(SAMPLE_CNTR_IN) = 0) else
						'0';

-- generate all delayed versions of the symbol clock
SYMBOL_CLK_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		SYMBOL_CLK_IN_SHIFT <= SYMBOL_CLK_IN_SHIFT(11 downto 1) & SYMBOL_CLK_IN;
		SYMBOL_CLKx2_IN_SHIFT <= SYMBOL_CLKx2_IN_SHIFT(11 downto 1) & SYMBOL_CLKx2_IN;
	end if;
end process;

-- collect all complex input samples over ]0,T] 
DATA_IN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYMBOL_CLKx4_IN = '1') then
			DATA0_I(2) <= signed(DATA_I_IN);
			DATA0_Q(2) <= signed(DATA_Q_IN);
			for I in 1 to 2 loop
				DATA0_I(I-1) <= DATA0_I(I);
				DATA0_Q(I-1) <= DATA0_Q(I);
			end loop;
		end if;
		
		-- freeze the 4 samples during MF computation 
		-- Index in increasing time 0 = earlier. 
		-- Symbol markers at 3
		if(SYMBOL_CLKx2_IN = '1') then
			DATA1_I(3) <= signed(DATA_I_IN);
			DATA1_Q(3) <= signed(DATA_Q_IN);
			for I in 0 to 2 loop
				DATA1_I(I) <= DATA0_I(I);
				DATA1_Q(I) <= DATA0_Q(I);
			end loop;
		end if;
		
	end if;
end process;

--//-- MATCHED FILTERS TAPS ----------------------------------
-- complex multiplication input samples * mf coefficients
-- Note: coefficients are on the complex circle. No change in amplitude after complex multiplication, just phase rotation.
CMULT_001: for I in 0 to (MPOWERLMAX-1) generate
	-- number of symbols hypotheses
	-- computed over several successive clocks to time-share hardware multipliers
	-- We have to do 4 vector rotations = 16 multiplications (over 2 CLKs between successive symbols) * M^L symbol hypothesis cases
	CMULT_001: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SYMBOL_CLKx2_IN_SHIFT(1) = '1') then
				MULT21A(I) <= DATA1_I(0);
				MULT22A(I) <= DATA1_Q(0);
				MULT23A(I) <= DATA1_I(0);
				MULT24A(I) <= DATA1_Q(0);
				MULT21B(I) <= COEFFS_I((I*NSAMPLESPERSYMBOL) + 0);
				MULT22B(I) <= COEFFS_Q((I*NSAMPLESPERSYMBOL) + 0);
				MULT23B(I) <= COEFFS_Q((I*NSAMPLESPERSYMBOL) + 0);
				MULT24B(I) <= COEFFS_I((I*NSAMPLESPERSYMBOL) + 0);
				MULT25A(I) <= DATA1_I(1);
				MULT26A(I) <= DATA1_Q(1);
				MULT27A(I) <= DATA1_I(1);
				MULT28A(I) <= DATA1_Q(1);
				MULT25B(I) <= COEFFS_I((I*NSAMPLESPERSYMBOL) + 1);
				MULT26B(I) <= COEFFS_Q((I*NSAMPLESPERSYMBOL) + 1);
				MULT27B(I) <= COEFFS_Q((I*NSAMPLESPERSYMBOL) + 1);
				MULT28B(I) <= COEFFS_I((I*NSAMPLESPERSYMBOL) + 1);
			elsif(SYMBOL_CLKx2_IN_SHIFT(2) = '1') then
				MULT21A(I) <= DATA1_I(2);
				MULT22A(I) <= DATA1_Q(2);
				MULT23A(I) <= DATA1_I(2);
				MULT24A(I) <= DATA1_Q(2);
				MULT21B(I) <= COEFFS_I((I*NSAMPLESPERSYMBOL) + 2);
				MULT22B(I) <= COEFFS_Q((I*NSAMPLESPERSYMBOL) + 2);
				MULT23B(I) <= COEFFS_Q((I*NSAMPLESPERSYMBOL) + 2);
				MULT24B(I) <= COEFFS_I((I*NSAMPLESPERSYMBOL) + 2);
				MULT25A(I) <= DATA1_I(3);
				MULT26A(I) <= DATA1_Q(3);
				MULT27A(I) <= DATA1_I(3);
				MULT28A(I) <= DATA1_Q(3);
				MULT25B(I) <= COEFFS_I((I*NSAMPLESPERSYMBOL) + 3);
				MULT26B(I) <= COEFFS_Q((I*NSAMPLESPERSYMBOL) + 3);
				MULT27B(I) <= COEFFS_Q((I*NSAMPLESPERSYMBOL) + 3);
				MULT28B(I) <= COEFFS_I((I*NSAMPLESPERSYMBOL) + 3);
			end if;
		end if;
	end process;

	-- multiplier output at SYMBOL_CLKx2_IN_D3/4
	CMULT_002: process(CLK)
	begin
		if rising_edge(CLK) then
			MULT21(I) <= MULT21A(I) * MULT21B(I);
			MULT22(I) <= MULT22A(I) * MULT22B(I);
			MULT23(I) <= MULT23A(I) * MULT23B(I);
			MULT24(I) <= MULT24A(I) * MULT24B(I);
			MULT25(I) <= MULT25A(I) * MULT25B(I);
			MULT26(I) <= MULT26A(I) * MULT26B(I);
			MULT27(I) <= MULT27A(I) * MULT27B(I);
			MULT28(I) <= MULT28A(I) * MULT28B(I);
		end if;
	end process;
	
	-- complete the complex multiplication. Truncate the result back to the same precision as the input samples
	-- (reason: this is just a vector rotation, no change in magnitude)
	-- why? this removes the phase change generated by the modulator (for the various symbol hypothesis)
	-- thus going back in time one symbol.
	CMULT_003: process(CLK)
	begin
		if rising_edge(CLK) then
			-- real part
			if(SYMBOL_CLKx2_IN_SHIFT(3) = '1') then
				DATA21_I(I) <= signed(MULT21(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1))
									- signed(MULT22(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1));
				DATA21_Q(I) <= signed(MULT23(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1))
									+ signed(MULT24(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1));
				DATA22_I(I) <= signed(MULT25(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1))
									- signed(MULT26(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1));
				DATA22_Q(I) <= signed(MULT27(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1))
									+ signed(MULT28(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1));
			elsif(SYMBOL_CLKx2_IN_SHIFT(4) = '1') then
				DATA23_I(I) <= signed(MULT21(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1))
									- signed(MULT22(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1));
				DATA23_Q(I) <= signed(MULT23(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1))
									+ signed(MULT24(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1));
				DATA24_I(I) <= signed(MULT25(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1))
									- signed(MULT26(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1));
				DATA24_Q(I) <= signed(MULT27(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1))
									+ signed(MULT28(I)((CPM_MF_NBITS+NBITS_COEFF-2) downto NBITS_COEFF-1));
			end if;
		end if;
	end process;
end generate;


--//-- MATCHED FILTERS SUMMING NETWORK ----------------------------------
-- sum the scaled samples over ]0,T] to complete the match filter processing
CSUM_001: for I in 0 to (MPOWERLMAX-1) generate
	-- number of symbols hypotheses
	CSUM_001: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SYMBOL_CLKx2_IN_SHIFT(4) = '1') then
				-- add 2 extra bit or more to prevent overflow.
				DATA3_I(I) <= resize(DATA21_I(I),CPM_MF_NBITS+2) + resize(DATA22_I(I),CPM_MF_NBITS+2);
				DATA3_Q(I) <= resize(DATA21_Q(I),CPM_MF_NBITS+2) + resize(DATA22_Q(I),CPM_MF_NBITS+2);
			elsif(SYMBOL_CLKx2_IN_SHIFT(5) = '1') then
				DATA3_I(I) <= DATA3_I(I)  + resize(DATA23_I(I),CPM_MF_NBITS+2) + resize(DATA24_I(I),CPM_MF_NBITS+2);
				DATA3_Q(I) <= DATA3_Q(I)  + resize(DATA23_Q(I),CPM_MF_NBITS+2) + resize(DATA24_Q(I),CPM_MF_NBITS+2);
			end if;
		end if;
	end process;
	
	MF_OUT_001: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SYMBOL_CLKx2_IN_SHIFT(6) = '1') then
				MF_I_OUT(I) <= std_logic_vector(DATA3_I(I)(CPM_MF_NBITS+1 downto 2));
				MF_Q_OUT(I) <= std_logic_vector(DATA3_Q(I)(CPM_MF_NBITS+1 downto 2));
			end if;
		end if;
	end process;
end generate;
MF_SYMBOL_CLKx2_OUT <= SYMBOL_CLKx2_IN_SHIFT(7);
MF_SYMBOL_CLK_OUT <= SYMBOL_CLK_IN_SHIFT(7);

--//-- ROTATE MF OUTPUTS ---------------------------------
-- rotate the matched filters outputs for p phase assumptions.
-- Keep only the real part.
-- number of symbols hypotheses. 
-- Applicability: restricted to coherent demodulation, M=2 or 3,L=1
ROTATE_001a: for I in 0 to (MPOWERLMAX-1) generate
	ROTATE_001a: process(CLK)
	begin
		if rising_edge(CLK) then
			-- NEW CODE. VALID FOR P = 4 (h=0.5) 
			if(SYMBOL_CLK_IN_SHIFT(6) = '1') then
				BM_local(4*I+0) <= std_logic_vector(DATA3_I(I)(CPM_MF_NBITS+1 downto 2));	-- p = 0 deg
				BM_local(4*I+1) <= std_logic_vector(DATA3_Q(I)(CPM_MF_NBITS+1 downto 2));	-- p = 90 deg
				BM_local(4*I+2) <= std_logic_vector(-DATA3_I(I)(CPM_MF_NBITS+1 downto 2));	-- p = 180 deg
				BM_local(4*I+3) <= std_logic_vector(-DATA3_Q(I)(CPM_MF_NBITS+1 downto 2));	-- p = 270 deg
			end if;
		end if;
	end process;
end generate;

BM_OUT <= BM_local;
SYMBOL_CLK_OUT <= SYMBOL_CLK_IN_SHIFT(7);


--//-- INITIALIZATION STATES -----------------------------------------
-- sequence the one-time generation of sin/cos coefficients and matched filter coefficients.
-- Both initializations share a common sin/cos look-up table.
-- Matched filter is ready when reaching STATE = 2
STATE_001: process(CLK)
begin
	if rising_edge(CLK) then
		STATE_D <= STATE;
		
		if(SYNC_RESET = '1') or (MODULATION_INDEX_VALID = '1') then
			STATE <= 0; 
		elsif(STATE = 0) then
			STATE <= 1;
		elsif(STATE = 1) and (T_INC = NSAMPLESPERSYMBOL) and (SYMBS_HYPO_INC = MPOWERL) then
			-- done initializing the matched filter coefficients
			STATE <= 2;
		end if;
	end if;
end process;
				
MF_INITIALIZED <= '1' when (STATE = 2) else '0';	
		

--//-- INITIALIZE MATCHED FILTER COEFFICIENTS ----------------------------------
-- For a given modulation, there are M^L possible data sequences times L*NSAMPLESPERSYMBOL complex 
-- matched filter coefficients over LT.
-- At power-up or reset we read all M^L * L*NSAMPLESPERSYMBOL phases from a ROM, then convert them into 
-- complex coefficients through  SIGNED_SIN_COS_TBL3.

-- 2-dimensional scan for samples (range 0 - L*NSAMPLESPERSYMBOL-1) and symbols hypothesis (range 0 - M^L-1) 
T_INC <= resize(T,T_INC'length) + 1;
SYMBS_HYPO_INC <= resize(SYMBS_HYPO,SYMBS_HYPO_INC'length) + 1;
MF_COEFFS_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		COEFF_INDEX_D <= COEFF_INDEX;
		
		if(SYNC_RESET = '1') or (MODULATION_INDEX_VALID = '1') or (STATE = 0) then
			T <= (others => '0');	-- time (sample number within the LT window)
				-- range 0 - NSAMPLESPERSYMBOL-1
			SYMBS_HYPO <= (others => '0');	
				-- hypothesis regarding the possible symbols over a LT window
				-- range 0 - M^L-1
			COEFF_INDEX <= (others => '0');
		elsif(STATE = 1)  then
			COEFF_INDEX <= COEFF_INDEX + 1;
			if(T_INC < NSAMPLESPERSYMBOL) then
				-- next sample
				-- range 0 - NSAMPLESPERSYMBOL-1
				T <= T_INC(T'left downto 0);
			else
				-- next symbols hypothesis
				-- range 0 - M^L-1
				T <= (others => '0');	-- time (sample number within the LT window)
				SYMBS_HYPO <= SYMBS_HYPO_INC(SYMBS_HYPO'left downto 0);
			end if;
		end if;
	end if;
end process;

-- lookup the 12-bit phase for each time sample and symbol hypothesis
-- The table is constructed for the most complex case of LMAX = 4. 
-- The address is constructed as
	-- bits (1:0)  sample number, range [0 - 3[
	-- bits (5:2)  symbols assumption for most complex case M^L = 16, range [0 - 16[
	-- bits (9:6) modulation scheme
			-- All assume h = 0.5 and M = 2 unless otherwise stated
			-- 0 = FSK  
			-- 1 = PCM/FM
			-- 2 = GFSK BT=0.7	
			-- 3 = GFSK BT=0.5	
			-- 4 = GFSK BT=0.3	
			-- 5 = SOQPSK-MIL
			-- 6 = SOQPSK-TG
			-- 7 = multi-h ARTM CPM (Tier 2) 
			-- 8 = GFSK BT=0.25
-- aligned with STATE = 1
CPM_PHASE_ADDRA(1 downto 0) <= std_logic_vector(T(1 downto 0));
CPM_PHASE_ADDRA(5 downto 2) <= std_logic_vector(SYMBS_HYPO(3 downto 0));
CPM_PHASE_ADDRA(9 downto 6) <= std_logic_vector(FILT_SEL);

-- 12-bit phase samples
-- cumulative phase output assumes a modulation index of 0.5 
-- 1 CLK latency from CPM_PHASE_ADDRA to CPM_PHASE_DOA
-- CPM_PHASE_DOA aligned with STATE_D = 1
-- direction: bit=0 -> decreasing phase, bit=1 -> increasing phase
MF_COEFFS_GEN_002: CPM_PHASEx4
PORT MAP(
	CLKA => CLK,
	CSA => '1',
	OEA => '1',
	ADDRA => CPM_PHASE_ADDRA,
	DOA => CPM_PHASE_DOA,	-- 12-bit cumulative phase, modulo 2pi, assuming modulation index h = 0.5
	CLKB => CLK,
	CSB => '0',
	OEB => '0',
	ADDRB => (others => '0'),
	DOB => open
);

-- scale modulation index for cases other than h = 0.5
MF_COEFFS_GEN_003: process(CLK)
begin
	if rising_edge(CLK) then
		-- realign states/indices with cos/sin look-up table output
		STATE_D2 <= STATE_D;
		STATE_D3 <= STATE_D2;
		STATE_D4 <= STATE_D3;
		COEFF_INDEX_D2 <= COEFF_INDEX_D;
		COEFF_INDEX_D3 <= COEFF_INDEX_D2;
		COEFF_INDEX_D4 <= COEFF_INDEX_D3;
		
		CPM_PHASExH <= signed(CPM_PHASE_DOA) * signed(MODULATION_INDEX);	
			-- 0.12  * 4.12  = 4.24
			-- keep CPM_PHASExH(22: 11). Dropping MSbs is equivalent to doing modulo(2pi)
	end if;
end process;
PHASE_IN <= std_logic_vector(CPM_PHASExH(22 downto 11));	-- to sin/cos look-up table. Aligned with STATE_D2 = 3

-- latency: 2 CLKs
SIN_COS_GEN_002: SIGNED_SIN_COS_TBL3 
GENERIC MAP (
	NADDR => PHASE_IN'length - 2
)
PORT MAP(
	CLK => CLK,
	PHASE => PHASE_IN,
	SIN_OUT => SIN_OUT,
	COS_OUT => COS_OUT
	);

-- truncate sin/cos down. No need for full precision when rotating vector.
COS_T <= COS_OUT(17 downto 18-NBITS_COEFF);
SIN_T <= SIN_OUT(17 downto 18-NBITS_COEFF);

-- latch in the matched filter coefficients
-- Truncate to NBITS_COEFF bits.
-- -imaginary part because coefficients are complex conjugate of expected waveform
-- After complex conjugate, direction is reverted to: bit 0 -> decreasing phase, bit 1 -> increasing phase
MF_COEFFS_GEN_004a: for I in 0 to 31 generate	-- TODO adjust when adding other filters
	MF_COEFFS_GEN_004c: process(CLK)
	begin
		if rising_edge(CLK) then
			if(STATE_D4 = 1) and (COEFF_INDEX_D4 = I) then
				COEFFS_I(I) <= signed(COS_T);
				COEFFS_Q(I) <= signed(not SIN_T);	-- do not use minus(-) unless increasing precision 1bit 
			end if;
		end if;
	end process;
end generate;

-- save sin/cos of pi*h (for use by the multi-symbol detector for such modulations as PCM/FM)
MF_COEFFS_GEN_004c: process(CLK)
begin
	if rising_edge(CLK) then
		if(STATE_D4 = 1) and (STATE_D3 /= 1) then
			COS_PIH <= COS_T;
			SIN_PIH <= SIN_T;	
		end if;
	end if;
end process;

-- Simulation capture to output file
--SIM2OUTFILE_001: SIM2OUTFILE 
--generic map(
--	FILENAME => "cpm_mf1.txt"
--)
--PORT MAP(
--	CLK => CLK,
--	DATA1 => DATA_I_IN & "00",		
--	DATA2 => DATA_Q_IN & "00",		
--	DATA3 => "0000000000" & SAMPLE_CNTR_IN,
--	SAMPLE_CLK_IN => SAMPLE_CLK_IN
--);

end Behavioral;
