---------------------------------------------------------------------------------------------------
-- Filename    : usbrx_halfband.vhd
-- Project     : OsmoSDR FPGA Firmware
-- Purpose     : Multichannel Halfband Decimation Filter
---------------------------------------------------------------------------------------------------

-----------------------------------------------------------------------------------
-- Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany --
-- written by Matthias Kleffel                                                   --
--                                                                               --
-- This program is free software; you can redistribute it and/or modify          --
-- it under the terms of the GNU General Public License as published by          --
-- the Free Software Foundation as version 3 of the License, or                  --
--                                                                               --
-- This program is distributed in the hope that it will be useful,               --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of                --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                  --
-- GNU General Public License V3 for more details.                               --
--                                                                               --
-- You should have received a copy of the GNU General Public License             --
-- along with this program. If not, see <http://www.gnu.org/licenses/>.          --
-----------------------------------------------------------------------------------

-------------------------------------------------------------------------------
-- halfband decimation filter - input cache/control ---------------------------
-------------------------------------------------------------------------------

library ieee;
	use ieee.std_logic_1164.all;
	use ieee.numeric_std.all;
library work;
	use work.all;
	use work.mt_toolbox.all;
	use work.mt_filter.all;

entity usbrx_halfband_ictrl is
	generic (
		N        : natural := 3
	);
	port (
		-- common
		clk      : in  std_logic;
		reset    : in  std_logic;
		
		-- input
		in_clk   : in  std_logic_vector(N-1 downto 0);
		in_i     : in  fir_databus18(N-1 downto 0);
		in_q     : in  fir_databus18(N-1 downto 0);
		
		-- output
		out_ready : in  std_logic;
		out_clk   : out std_logic;
		out_chan  : out unsigned(log2(N)-1 downto 0);
		out_i0    : out fir_dataword18;
		out_i1    : out fir_dataword18;
		out_q0    : out fir_dataword18;
		out_q1    : out fir_dataword18
	);
end usbrx_halfband_ictrl;

architecture rtl of usbrx_halfband_ictrl is	

	-- control
	signal phase : std_logic_vector(N-1 downto 0);
	signal tmp_i : fir_databus18(N-1 downto 0);
	signal tmp_q : fir_databus18(N-1 downto 0);
	signal oclk  : std_logic;
	
	-- output
	signal pending  : std_logic_vector(N-1 downto 0);
	signal cache_i0 : fir_databus18(N-1 downto 0);
	signal cache_i1 : fir_databus18(N-1 downto 0);
	signal cache_q0 : fir_databus18(N-1 downto 0);
	signal cache_q1 : fir_databus18(N-1 downto 0);
	
begin
	
	-- control logic
	process(clk)
		variable found : std_logic;
	begin
		if rising_edge(clk) then
			-- set default values
			oclk <= '0';
			
			-- check if we are allowed to output the next entry
			if out_ready='1' and oclk='0' then
				-- search for pending cache-entry
				found := '0';
				for i in 0 to N-1 loop
					if found='0' and pending(i)='1' then
						--> output entry
						oclk <= '1';
						out_chan <= to_unsigned(i,out_chan'length);
						out_i0 <= cache_i0(i);
						out_i1 <= cache_i1(i);
						out_q0 <= cache_q0(i);
						out_q1 <= cache_q1(i);
						
						-- update status
						pending(i) <= '0';
						found := '1';
					end if;
				end loop;
			end if;
			
			-- handle incoming samples
			for i in in_clk'range loop
				if in_clk(i)='1' then
					-- write sample into cache
					if phase(i)='0' then 
						tmp_i(i) <= in_i(i);
						tmp_q(i) <= in_q(i);
					else 
						pending(i)  <= '1';
						cache_i0(i) <= tmp_i(i);
						cache_q0(i) <= tmp_q(i);
						cache_i1(i) <= in_i(i);
						cache_q1(i) <= in_q(i);	
					end if;
					phase(i) <= not phase(i);
				
					-- debug check
					assert phase(i)='0' or pending(i)='0'
						report "usbrx_halfband_ictrl: input too fast" 
						severity error;
				end if;
			end loop;
			
			-- handle reset
			if reset='1' then
				phase    <= (others=>'0');
				tmp_i    <= (others=>(others=>'0'));
				tmp_q    <= (others=>(others=>'0'));
				pending  <= (others=>'0');
				cache_i0 <= (others=>(others=>'0'));
				cache_i1 <= (others=>(others=>'0'));
				cache_q0 <= (others=>(others=>'0'));
				cache_q1 <= (others=>(others=>'0'));
				oclk     <= '0';
				out_chan <= (others=>'0');
				out_i0   <= (others=>'0');
				out_i1   <= (others=>'0');
				out_q0   <= (others=>'0');
				out_q1   <= (others=>'0');
			end if;
		end if;
	end process;
	
	-- connect output-clock
	out_clk <= oclk;
	
end rtl;


-------------------------------------------------------------------------------
-- halfband decimation filter - output control --------------------------------
-------------------------------------------------------------------------------

library ieee;
	use ieee.std_logic_1164.all;
	use ieee.numeric_std.all;
library work;
	use work.all;
	use work.mt_toolbox.all;
	use work.mt_filter.all;

entity usbrx_halfband_octrl is
	generic (
		N        : natural := 3
	);
	port (
		-- common
		clk      : in  std_logic;
		reset    : in  std_logic;
		
		-- input
		in_clk   : in  std_logic;
		in_chan  : in  unsigned(log2(2*N)-1 downto 0);
		in_data  : in  fir_dataword18;
		
		-- output
		out_clk  : out std_logic_vector(N-1 downto 0);
		out_i    : out fir_databus18(N-1 downto 0);
		out_q    : out fir_databus18(N-1 downto 0)
	);
end usbrx_halfband_octrl;

architecture rtl of usbrx_halfband_octrl is	

	-- cache
	signal tmp_i : fir_databus18(N-1 downto 0);

begin
	
	-- control logic
	process(clk)
		variable sel : natural range 0 to N-1;
	begin
		if rising_edge(clk) then
			-- set default values
			out_clk <= (others=>'0');
			
			-- handle input
			if in_clk='1' then
				sel := to_integer(in_chan)/2;
				for i in out_clk'range loop
					if sel=i then
						if in_chan(0)='0' then
							-- cache result
							tmp_i(i) <= in_data;
						else
							-- output result 
							out_clk(i) <= '1';
							out_i(i) <= tmp_i(i);
							out_q(i) <= in_data;
						end if;
					end if;
				end loop;
			end if;
			
			-- handle reset
			if reset='1' then
				tmp_i   <= (others=>(others=>'0'));
				out_clk <= (others=>'0');
				out_i   <= (others=>(others=>'0'));
				out_q   <= (others=>(others=>'0'));
			end if;
		end if;
	end process;
end rtl;


-------------------------------------------------------------------------------
-- halfband decimation filter -------------------------------------------------
-------------------------------------------------------------------------------

library ieee;
	use ieee.std_logic_1164.all;
	use ieee.numeric_std.all;
library work;
	use work.all;
	use work.mt_toolbox.all;
	use work.mt_filter.all;

entity usbrx_halfband is
	generic (
		N        : natural := 3
	);
	port (
		-- common
		clk      : in  std_logic;
		reset    : in  std_logic;
		
		-- input
		in_clk   : in  std_logic_vector(N-1 downto 0);
		in_i     : in  fir_databus18(N-1 downto 0);
		in_q     : in  fir_databus18(N-1 downto 0);
		
		-- output
		out_clk  : out std_logic_vector(N-1 downto 0);
		out_i    : out fir_databus18(N-1 downto 0);
		out_q    : out fir_databus18(N-1 downto 0)
	);
end usbrx_halfband;

architecture rtl of usbrx_halfband is	

	-- internal types
	type state_t is (S_IDLE, S_FILTER_I, S_FILTER_Q, S_COOLDOWN);
	
	-- status
	signal state : state_t;
	
	-- input control
	signal ic_ready : std_logic;
	signal ic_clk   : std_logic;
	signal ic_chan  : unsigned(log2(N)-1 downto 0);
	signal ic_i0    : fir_dataword18;
	signal ic_i1    : fir_dataword18;
	signal ic_q0    : fir_dataword18;
	signal ic_q1    : fir_dataword18;

	-- next output
	signal on_clk : std_logic;
	signal on_chan : unsigned(log2(2*N)-1 downto 0);
	signal on_dat1 : fir_dataword18;
	signal on_dat2 : fir_dataword18;
	
	-- output control
	signal oc_iclk  : std_logic;
	signal oc_ichan : unsigned(log2(2*N)-1 downto 0);
	signal oc_idata : fir_dataword18;
	
	-- FIR input
	signal fir_iclk  : std_logic;
	signal fir_iack  : std_logic;
	signal fir_idata : fir_dataword18;
	signal fir_ichan : unsigned(log2(2*N)-1 downto 0);
	
	-- FIR output
	signal fir_oclk  : std_logic;
	signal fir_ochan : unsigned(log2(2*N)-1 downto 0);
	signal fir_odata : fir_dataword18;
			
	-- center samples
	signal cent_i : fir_databus18(N-1 downto 0);
	signal cent_q : fir_databus18(N-1 downto 0);
	
	-- coefficients for 2x-FIR-interpolator
	-- (halfband, order 48, wpass 0.40)
	constant coeffs : fir_coefficients(0 to 11) := ( 
		  -52,    151,   -354,    715,
		-1307,   2227,  -3614,   5691,
		-8907,  14403, -26386,  82957
	);
	
begin

	-- control logic
	process(clk)
		variable sel : natural range 0 to N-1;
	begin
		if rising_edge(clk) then
			-- set default values
			fir_iclk <= '0';
			oc_iclk  <= '0';
			on_clk   <= '0';
		
			-- filter control
			case state is
				when S_IDLE =>
					-- wait for new sample-pair
					if ic_clk='1' then
						-- start filter (I)
						fir_iclk  <= '1';
						fir_idata <= ic_i1;
						fir_ichan <= resize(ic_chan & "0", fir_ichan'length);

						-- update status
						state <= S_FILTER_I;
					end if;
					
				when S_FILTER_I =>
					-- wait until filtering is done
					if fir_iack='1' then
						-- start filter (Q)
						fir_iclk  <= '1';
						fir_idata <= ic_q1;
						fir_ichan <= resize(ic_chan & "1", fir_ichan'length);
						
						-- update status
						state <= S_FILTER_Q;
					end if;
					
				when S_FILTER_Q =>
					-- wait until filtering is done
					if fir_iack='1' then
						-- update status
						state <= S_COOLDOWN;
					end if;
					
				when S_COOLDOWN =>
					-- TODO: get rid of state
					if fir_oclk='1' then
						state <= S_IDLE;
					end if;
			end case;
			
			-- fetch FIR result and corresonding center-sample
			if fir_oclk='1' then
				on_clk  <= '1';
				on_chan <= fir_ochan;
				on_dat1 <= fir_odata; 
				sel := to_integer(fir_ochan)/2;
				if fir_ochan(0)='0' 
					then on_dat2 <= cent_i(sel);
					else on_dat2 <= cent_q(sel);
				end if;
			end if;
			
			-- create final result
			if on_clk='1' then
				oc_iclk  <= '1';
				oc_ichan <= on_chan;
				oc_idata <= resize((resize(on_dat1,19) + resize(on_dat2,19)) / 2,18);
			end if;
			
			-- handle reset
			if reset='1' then
				state     <= S_IDLE;
				fir_iclk  <= '0';
				fir_idata <= (others=>'0');
				fir_ichan <= (others=>'0');
				oc_iclk   <= '0';
				oc_ichan  <= (others=>'0');
				oc_idata  <= (others=>'0');
				on_clk    <= '0';
				on_chan   <= (others=>'0');
				on_dat1   <= (others=>'0');
				on_dat2   <= (others=>'0');
			end if;
		end if;
	end process;
	
	-- create ready-flag
	ic_ready <= '1' when state=S_IDLE else '0';
	
	-- shift register for center-sample
	sreg: for i in 0 to N-1 generate
		subtype entry_t is signed(35 downto 0);
		type sreg_t is array(natural range<>) of entry_t;
		signal sreg : sreg_t(11 downto 0) := (others=>(others=>'0'));
	begin
		
		-- infer shift-register
		process(clk)
			variable temp : entry_t;
		begin
			if rising_edge(clk) then
				if ic_clk='1' and to_integer(ic_chan)=i then
					temp := ic_q0 & ic_i0;
					sreg <= sreg(sreg'left-1 downto 0) & temp;
				end if;
			end if;
		end process;
		
		-- output center-sample
		cent_i(i) <= sreg(sreg'left)(17 downto  0);
		cent_q(i) <= sreg(sreg'left)(35 downto 18);
	end generate;
	
	-- input control
	ic: entity usbrx_halfband_ictrl
		generic map (
			N        => N
		)
		port map (
			-- common
			clk      => clk,
			reset    => reset,
			
			-- input
			in_clk   => in_clk,
			in_i     => in_i,
			in_q     => in_q,
			
			-- output
			out_ready => ic_ready,
			out_clk   => ic_clk,
			out_chan  => ic_chan,
			out_i0    => ic_i0,
			out_i1    => ic_i1,
			out_q0    => ic_q0,
			out_q1    => ic_q1
		);

	-- FIR filter core
	fir: entity mt_fir_symmetric_slow
		generic map (
			CHANNELS  => 2*N,
			TAPS      => 12,
			COEFFS    => coeffs,
			RAMSTYLE  => "block_ram",
			ROMSTYLE  => "logic"
		)
		port map (
			-- common
			clk        => clk,
			reset      => reset,
			
			-- input port
			in_clk     => fir_iclk,
			in_ack     => fir_iack,
			in_data    => fir_idata,
			in_chan    => fir_ichan,
			
			-- output port
			out_clk    => fir_oclk,
			out_chan   => fir_ochan,
			out_data   => fir_odata
		);
	
	-- output control
	oc: entity usbrx_halfband_octrl
		generic map (
			N        => N
		)
		port map (
			-- common
			clk      => clk,
			reset    => reset,
			
			-- input
			in_clk   => oc_iclk,
			in_chan  => oc_ichan,
			in_data  => oc_idata,
			
			-- output
			out_clk  => out_clk,
			out_i    => out_i,
			out_q    => out_q
		);

end rtl;
