----------------------------------------------
-- MSS copyright 2017
-- Filename: ROM_FIL1.VHD
-- Author: Alain Zarembowitch / MSS
-- Inheritance: n/a
-- Edit date: 6/3/17
-- Revision: 0
--
-- VHDL code for a single FIR filter with coefficients stored in ROM
-- 4 or 8 samples/symbol because that's how the filter impulse response is stored in CPM_FILTERS.
-- 
-- Latency is depends on filter selection (4-7 CLKs) 
--
----------------------------------------------      
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.ALL;

entity ROM_FIL1 is
	generic(
		NBITS: integer;
			-- input/output samples precision
		NSAMPLESPERSYMBOL: integer := 8;
			-- 4 or 8 samples/symbol
		LMAX: integer range 1 to 8 := 1;
			-- maximal length of the frequency pulse in symbols
			-- Objective: minimize the FPGA occupancy depending on the highest order modulation needed in the application
		SIMULATION: std_logic := '0'
			-- enable optional simulation features
    );
    port (
		CLK: in std_logic;
			-- reference clock for synchronous design
		SYNC_RESET: in std_logic;
			-- global reset, active high.
			
		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
		L_OUT: out std_logic_vector(3 downto 0);
			-- filter impulse response length

		DATA_IN: in std_logic_vector((NBITS-1) downto 0);
			-- input signal
			-- read at rising edge of CLK when SAMPLE_CLK_IN = '1'
			-- DATA_IN is a non-zero impulse when SYMBOL_CLK_IN = '1', 0 otherwise
		SYMBOL_CLK_IN: in std_logic;
			-- MUST be 8 samples/symbol
		SAMPLE_CLK_IN: in std_logic;
			-- read input sample at rising_edge of CLK and SAMPLE_CLK_IN  = '1'.
			-- One CLK wide pulse.

		SAMPLE_CLK_OUT: out std_logic;
		DATA_OUT: out std_logic_vector((NBITS-1) downto 0)
			-- Read at rising edge of CLK and SAMPLE_CLK_OUT = '1'.
        	-- Input to Output Processing latency depends on the filter selection (4-7 CLKs)
    );
end entity;

architecture behavioral of ROM_FIL1 is
-----------------------------------------------------------------
-- COMPONENTS
-----------------------------------------------------------------
	COMPONENT CPM_FILTERSx8
	GENERIC (
		DATA_WIDTH: integer;	
		ADDR_WIDTH: integer
	);
	PORT(
		CLKA : IN std_logic;
		CSA : IN std_logic;
		OEA : IN std_logic;
		ADDRA  : in  std_logic_vector(ADDR_WIDTH-1 downto 0);
		DOA  : out std_logic_vector(DATA_WIDTH-1 downto 0);
		CLKB : IN std_logic;
		CSB : IN std_logic;
		OEB : IN std_logic;
		ADDRB  : in  std_logic_vector(ADDR_WIDTH-1 downto 0);
		DOB  : out std_logic_vector(DATA_WIDTH-1 downto 0)
		);
	END COMPONENT;
	
	COMPONENT CPM_FILTERSx4
	GENERIC (
		DATA_WIDTH: integer;	
		ADDR_WIDTH: integer
	);
	PORT(
		CLKA : IN std_logic;
		CSA : IN std_logic;
		OEA : IN std_logic;
		ADDRA  : in  std_logic_vector(ADDR_WIDTH-1 downto 0);
		DOA  : out std_logic_vector(DATA_WIDTH-1 downto 0);
		CLKB : IN std_logic;
		CSB : IN std_logic;
		OEB : IN std_logic;
		ADDRB  : in  std_logic_vector(ADDR_WIDTH-1 downto 0);
		DOB  : out std_logic_vector(DATA_WIDTH-1 downto 0)
		);
	END COMPONENT;
	
-----------------------------------------------------------------
-- SIGNALS
-----------------------------------------------------------------
-- suffix _D denotes a one-CLK delayed version of the net with the same name
constant ADDR_WIDTH: integer := 8;
	-- defines ROM maximum size
signal L: integer range 1 to 8 := 8;	
signal FILT_ADDRA1: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal FILT_ADDRB2: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal FILT_ADDRA3: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal FILT_ADDRB4: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal FILT_ADDRA5: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal FILT_ADDRB6: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal FILT_ADDRA7: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal FILT_ADDRB8: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal BASE_ADDR: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal ADDR_OFFSET: unsigned(2 downto 0) := (others => '0');	-- because modulo 4 or 8samples/symbol
type DS_TYPE is array (1 to LMAX) of signed((NBITS-1) downto 0);
signal DATA_SYMBOL: DS_TYPE := (others => (others => '0'));
signal DATA_SYMBOL_D: DS_TYPE := (others => (others => '0'));
type COEFF_TYPE is array (1 to 8) of std_logic_vector(15 downto 0);
signal COEFF: COEFF_TYPE := (others => (others => '0'));
type FIL1_TYPE is array (1 to LMAX) of signed(NBITS+15 downto 0);
signal FIL1: FIL1_TYPE := (others => (others => '0'));
type FIL2_TYPE is array (1 to LMAX/2) of signed(NBITS+7 downto 0);
signal FIL2: FIL2_TYPE := (others => (others => '0'));
type FIL3_TYPE is array (1 to LMAX/4) of signed(NBITS+7 downto 0);
signal FIL3: FIL3_TYPE := (others => (others => '0'));
signal FIL4: signed(NBITS+7 downto 0) := (others => '0');
signal SAMPLE_CLK_IN_D: std_logic := '0';
signal SAMPLE_CLK_IN_D2: std_logic := '0';
signal SAMPLE_CLK_IN_D3: std_logic := '0';
signal SAMPLE_CLK_IN_D4: std_logic := '0';
signal SAMPLE_CLK_IN_D5: std_logic := '0';
signal SAMPLE_CLK_IN_D6: std_logic := '0';
-----------------------------------------------------------------
-- IMPLEMENTATION
-----------------------------------------------------------------
begin   

--//-- Filter selection ---------------------------

-- range 0 -7 or 0-3 depending on NSAMPLESPERSYMBOL
FILT_ADDR_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') or (SYMBOL_CLK_IN = '1') then
			ADDR_OFFSET <= (others => '0');
		elsif(SAMPLE_CLK_IN = '1') then
			if(NSAMPLESPERSYMBOL = 4) then
				-- modulo-4 sample counter
				ADDR_OFFSET(1 downto 0) <= unsigned(ADDR_OFFSET(1 downto 0)) + 1;
			elsif(NSAMPLESPERSYMBOL = 8) then
				-- modulo-8 sample counter
				ADDR_OFFSET(2 downto 0) <= unsigned(ADDR_OFFSET(2 downto 0)) + 1;
			end if;
		end if;
	end if;
end process;

SAMPLINGx4: if(NSAMPLESPERSYMBOL = 4) generate
	FILT_ADDRA1 <= BASE_ADDR + ADDR_OFFSET;
	FILT_ADDRB2 <= BASE_ADDR + ADDR_OFFSET + 4;
	FILT_ADDRA3 <= BASE_ADDR + ADDR_OFFSET + 8;
	FILT_ADDRB4 <= BASE_ADDR + ADDR_OFFSET + 12;
	FILT_ADDRA5 <= BASE_ADDR + ADDR_OFFSET + 16;
	FILT_ADDRB6 <= BASE_ADDR + ADDR_OFFSET + 20;
	FILT_ADDRA7 <= BASE_ADDR + ADDR_OFFSET + 24;
	FILT_ADDRB8 <= BASE_ADDR + ADDR_OFFSET + 28;

	-- Modulation   Start address  Number of samples filter length(L)
	-- FSK	x0	4 1 rectangular frequency impulse
	-- PCM/FM x4	7 2 raised cosine premod filter
	-- GFSK BT=0.7 x0C  7 2
	-- GFSK BT=0.5 x14  9 3(rounded up to 4 when implementing the modulation)
	-- GFSK BT=0.3 x24  15 4 
	-- SOQPSK-MIL x34  4 1
	-- SOQPSK-TG x38  31 8
	-- multi-h CPM (ARTM Tier 2) x58 11 4
	-- GFSK BT=0.25 x68  19 5 (rounded up to 8 when implementing the modulation)

	FILT_SEL_001: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SYNC_RESET = '1') then
				BASE_ADDR <= (others => '0');
				L <= 1;
			else
				case to_integer(unsigned(FILT_SEL)) is
					when 1 => 
						BASE_ADDR <= to_unsigned(4,ADDR_WIDTH);
						L <= 2;
					when 2 => 
						BASE_ADDR <= to_unsigned(12,ADDR_WIDTH);
						L <= 2;
					when 3 => 
						BASE_ADDR <= to_unsigned(20,ADDR_WIDTH);
						L <= 4;
					when 4 => 
						BASE_ADDR <= to_unsigned(36,ADDR_WIDTH);
						L <= 4;
					when 5 => 
						BASE_ADDR <= to_unsigned(52,ADDR_WIDTH);
						L <= 1;
					when 6 => 
						BASE_ADDR <= to_unsigned(56,ADDR_WIDTH);	
						L <= 8;
					when 7 => 
						BASE_ADDR <= to_unsigned(88,ADDR_WIDTH);
						L <= 4;
					when 8 => 
						BASE_ADDR <= to_unsigned(104,ADDR_WIDTH);
						L <= 8;													
					when others => 
						BASE_ADDR <= to_unsigned(0,ADDR_WIDTH);
						L <= 1;
				end case;
			end if;
		end if;
	end process;
	
	-- report L
	L_OUT <= std_logic_vector(to_unsigned(L,4));

	CPM_FILTERSx4_001: CPM_FILTERSx4 
	GENERIC MAP(
		DATA_WIDTH => 16,
		ADDR_WIDTH => ADDR_WIDTH
	)
	PORT MAP(
		CLKA => CLK,
		CSA => '1',
		OEA => '1',
		ADDRA => std_logic_vector(FILT_ADDRA1),
		DOA => COEFF(1),
		CLKB => CLK,
		CSB => '1',
		OEB => '1',
		ADDRB => std_logic_vector(FILT_ADDRB2),
		DOB => COEFF(2)
	);

	CPM_FILTERSx4_002: if(LMAX > 2) generate
		CPM_FILTERSx4_002: CPM_FILTERSx4 
		GENERIC MAP(
			DATA_WIDTH => 16,
			ADDR_WIDTH => ADDR_WIDTH
		)
		PORT MAP(
			CLKA => CLK,
			CSA => '1',
			OEA => '1',
			ADDRA => std_logic_vector(FILT_ADDRA3),
			DOA => COEFF(3),
			CLKB => CLK,
			CSB => '1',
			OEB => '1',
			ADDRB => std_logic_vector(FILT_ADDRB4),
			DOB => COEFF(4)
		);
	end generate;

	CPM_FILTERSx4_003: if(LMAX > 4) generate
		CPM_FILTERSx4_003: CPM_FILTERSx4 
		GENERIC MAP(
			DATA_WIDTH => 16,
			ADDR_WIDTH => ADDR_WIDTH
		)
		PORT MAP(
			CLKA => CLK,
			CSA => '1',
			OEA => '1',
			ADDRA => std_logic_vector(FILT_ADDRA5),
			DOA => COEFF(5),
			CLKB => CLK,
			CSB => '1',
			OEB => '1',
			ADDRB => std_logic_vector(FILT_ADDRB6),
			DOB => COEFF(6)
		);
	end generate;

	CPM_FILTERSx4_004: if(LMAX > 6) generate
		CPM_FILTERSx4_004: CPM_FILTERSx4 
		GENERIC MAP(
			DATA_WIDTH => 16,
			ADDR_WIDTH => ADDR_WIDTH
		)
		PORT MAP(
			CLKA => CLK,
			CSA => '1',
			OEA => '1',
			ADDRA => std_logic_vector(FILT_ADDRA7),
			DOA => COEFF(7),
			CLKB => CLK,
			CSB => '1',
			OEB => '1',
			ADDRB => std_logic_vector(FILT_ADDRB8),
			DOB => COEFF(8)
		);
	end generate;
end generate;

SAMPLINGx8: if(NSAMPLESPERSYMBOL = 8) generate
	FILT_ADDRA1 <= BASE_ADDR + ADDR_OFFSET;
	FILT_ADDRB2 <= BASE_ADDR + ADDR_OFFSET + 8;
	FILT_ADDRA3 <= BASE_ADDR + ADDR_OFFSET + 16;
	FILT_ADDRB4 <= BASE_ADDR + ADDR_OFFSET + 24;
	FILT_ADDRA5 <= BASE_ADDR + ADDR_OFFSET + 32;
	FILT_ADDRB6 <= BASE_ADDR + ADDR_OFFSET + 40;
	FILT_ADDRA7 <= BASE_ADDR + ADDR_OFFSET + 48;
	FILT_ADDRB8 <= BASE_ADDR + ADDR_OFFSET + 56;

	-- Modulation   Start address  Number of samples filter length(L)
	-- FSK	x0	8 1 rectangular frequency impulse
	-- PCM/FM x8	16 2 raised cosine premod filter
	-- GFSK BT=0.7 x18  13 2
	-- GFSK BT=0.5 x28  19 4
	-- GFSK BT=0.3 x48  29 4 
	-- SOQPSK-MIL x68  15 2
	-- SOQPSK-TG x78  63 8
	-- multi-h ARTM CPM (Tier 2) xB8 4
	FILT_SEL_001: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SYNC_RESET = '1') then
				BASE_ADDR <= (others => '0');
				L <= 1;
			else
				case to_integer(unsigned(FILT_SEL)) is
					when 1 => 
						BASE_ADDR <= to_unsigned(8,ADDR_WIDTH);
						L <= 2;
					when 2 => 
						BASE_ADDR <= to_unsigned(24,ADDR_WIDTH);
						L <= 2;
					when 3 => 
						BASE_ADDR <= to_unsigned(40,ADDR_WIDTH);
						L <= 4;
					when 4 => 
						BASE_ADDR <= to_unsigned(72,ADDR_WIDTH);
						L <= 4;
					when 5 => 
						BASE_ADDR <= to_unsigned(104,ADDR_WIDTH);
						L <= 1;
					when 6 => 
						BASE_ADDR <= to_unsigned(112,ADDR_WIDTH);
						L <= 8;
					when 7 => 
						BASE_ADDR <= to_unsigned(176,ADDR_WIDTH);
						L <= 4;
					when others => 
						BASE_ADDR <= to_unsigned(0,ADDR_WIDTH);
						L <= 1;
				end case;
			end if;
		end if;
	end process;

	CPM_FILTERSx8_001: CPM_FILTERSx8 
	GENERIC MAP(
		DATA_WIDTH => 16,
		ADDR_WIDTH => ADDR_WIDTH
	)
	PORT MAP(
		CLKA => CLK,
		CSA => '1',
		OEA => '1',
		ADDRA => std_logic_vector(FILT_ADDRA1),
		DOA => COEFF(1),
		CLKB => CLK,
		CSB => '1',
		OEB => '1',
		ADDRB => std_logic_vector(FILT_ADDRB2),
		DOB => COEFF(2)
	);

	CPM_FILTERSx8_002: if(LMAX > 2) generate
		CPM_FILTERSx8_002: CPM_FILTERSx8 
		GENERIC MAP(
			DATA_WIDTH => 16,
			ADDR_WIDTH => ADDR_WIDTH
		)
		PORT MAP(
			CLKA => CLK,
			CSA => '1',
			OEA => '1',
			ADDRA => std_logic_vector(FILT_ADDRA3),
			DOA => COEFF(3),
			CLKB => CLK,
			CSB => '1',
			OEB => '1',
			ADDRB => std_logic_vector(FILT_ADDRB4),
			DOB => COEFF(4)
		);
	end generate;

	CPM_FILTERSx8_003: if(LMAX > 4) generate
		CPM_FILTERSx8_003: CPM_FILTERSx8 
		GENERIC MAP(
			DATA_WIDTH => 16,
			ADDR_WIDTH => ADDR_WIDTH
		)
		PORT MAP(
			CLKA => CLK,
			CSA => '1',
			OEA => '1',
			ADDRA => std_logic_vector(FILT_ADDRA5),
			DOA => COEFF(5),
			CLKB => CLK,
			CSB => '1',
			OEB => '1',
			ADDRB => std_logic_vector(FILT_ADDRB6),
			DOB => COEFF(6)
		);
	end generate;

	CPM_FILTERSx8_004: if(LMAX > 6) generate
		CPM_FILTERSx8_004: CPM_FILTERSx8 
		GENERIC MAP(
			DATA_WIDTH => 16,
			ADDR_WIDTH => ADDR_WIDTH
		)
		PORT MAP(
			CLKA => CLK,
			CSA => '1',
			OEA => '1',
			ADDRA => std_logic_vector(FILT_ADDRA7),
			DOA => COEFF(7),
			CLKB => CLK,
			CSB => '1',
			OEB => '1',
			ADDRB => std_logic_vector(FILT_ADDRB8),
			DOB => COEFF(8)
		);
	end generate;
end generate;

--//-- delay input symbols and scale with filter coefficients
SPAN_001: for I in 1 to LMAX generate
	-- generate delayed LMAX delayed symbols
	DELAY_001: process(CLK)
	begin
		if rising_edge(CLK) then
			DATA_SYMBOL_D(I) <= DATA_SYMBOL(I);
			-- align symbols with impulse samples FILT_DOXX from CPM_FILTERS
			-- aligned with SYMBOL_CLK_IN_D2
			
			if(SYNC_RESET = '1') then
				DATA_SYMBOL(I)  <= (others => '0');
			elsif(SYMBOL_CLK_IN = '1') then
				if(I = 1) then
					DATA_SYMBOL(I) <= signed(DATA_IN);
				else
					DATA_SYMBOL(I) <= DATA_SYMBOL(I-1);
				end if;
			end if;
		end if;
	end process;
	
	-- aligned with SAMPLE_CLK_IN_D3
	MULT_001: process(CLK)
	begin
		if rising_edge(CLK) then
			FIL1(I) <= DATA_SYMBOL_D(I) * signed(COEFF(I));  -- Fixed point  7.13 (when N-BITS = 20 bit inputs) * 1.15 = 8.28
		end if;
	end process;
	
	
end generate;

-- When summing, no need to check for overflow as the coefficients sum <= 1
-- Drop 8 LSBs after coeff multiplication (negligible quantization error)
SUM2: for I in 1 to LMAX/2 generate
	-- aligned with SAMPLE_CLK_IN_D4
	SUM_002: process(CLK)
	begin
		if rising_edge(CLK) then
			FIL2(I) <= FIL1((2*I)-1)(NBITS+15 downto 8) + FIL1(2*I)(NBITS+15 downto 8);
		end if;
	end process;
end generate;

SUM3: for I in 1 to LMAX/4 generate
	-- aligned with SAMPLE_CLK_IN_D5
	SUM_003: process(CLK)
	begin
		if rising_edge(CLK) then
			FIL3(I) <= FIL2((2*I)-1) + FIL2(2*I);
		end if;
	end process;
end generate;

SUM4: if(LMAX = 8) generate
	-- aligned with SAMPLE_CLK_IN_D6
	SUM_004: process(CLK)
	begin
		if rising_edge(CLK) then
			FIL4 <= FIL3(1) + FIL3(2);
		end if;
	end process;
end generate;

--//-- OUTPUT ---------------------
OUT_MUX_001: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE_CLK_IN_D <= SAMPLE_CLK_IN;
		SAMPLE_CLK_IN_D2 <= SAMPLE_CLK_IN_D;
		SAMPLE_CLK_IN_D3 <= SAMPLE_CLK_IN_D2;
		SAMPLE_CLK_IN_D4 <= SAMPLE_CLK_IN_D3;
		SAMPLE_CLK_IN_D5 <= SAMPLE_CLK_IN_D4;
		SAMPLE_CLK_IN_D6 <= SAMPLE_CLK_IN_D5;

		if(L = 1) then
			-- Latency 4 CLKs
			DATA_OUT <= std_logic_vector(FIL1(1)(NBITS+14 downto 15)); 
			SAMPLE_CLK_OUT <= SAMPLE_CLK_IN_D3;
		elsif(L = 2) then
			-- Latency 5 CLKs
			DATA_OUT <= std_logic_vector(FIL2(1)(NBITS+6 downto 7));
			SAMPLE_CLK_OUT <= SAMPLE_CLK_IN_D4;
		elsif(L = 4) then
			-- Latency 6 CLKs
			DATA_OUT <= std_logic_vector(FIL3(1)(NBITS+6 downto 7));
			SAMPLE_CLK_OUT <= SAMPLE_CLK_IN_D5;
		--elsif(L = 8) then
		else
			-- Latency 7 CLKs
			DATA_OUT <= std_logic_vector(FIL4(NBITS+6 downto 7));
			SAMPLE_CLK_OUT <= SAMPLE_CLK_IN_D6;
		end if;
	end if;
end process;


end behavioral;					
