-------------------------------------------------------------
-- MSS copyright 2013-2015
--	Filename:  AGC18.VHD
-- Author: Alain Zarembowitch / MSS
--	Version: 3
--	Date last modified: 3/30/15
-- Inheritance: 	n/a
--
-- Description:  feed-forward gain control. 
-- Algorithm: average the complex input magnitude, then scale the input with 1/average magnitude.
-- Averaging over 512 time units (defined as symbol period SYMBOL_CLK_IN)
--
-- Rev 1 3/24/14 AZ
-- move SIMULATION to generic parameter
--
-- Rev 2 7/25/14 AZ
-- longer AVERAGING_LENGTH up to 16384
--
-- Rev 3 3/30/15 AZ increased range
---------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity AGC18 is
	generic (
		AVERAGING_LENGTH: integer := 512;
			-- currently supports only 512, 1024, 2048, 4096, 8192, 16384
		SIMULATION: std_logic := '0'
	);
    port ( 

	--// GLOBAL CLOCKS
   CLK : in std_logic;				-- System clock
	SYNC_RESET: in std_logic;				-- synchronous reset

	--// INPUT DATA STREAM
	DATA_I_IN: in std_logic_vector(13 downto 0);
	DATA_Q_IN: in std_logic_vector(13 downto 0);
	SAMPLE_CLK_IN: in std_logic;
		-- sample clock. 

	--// CONTROLS 
	MTARGET: in std_logic_vector(13 downto 0);
		-- magnitude target for the output samples. Unsigned.
	CONTROL: in std_logic_vector(0 downto 0);
		-- bit 0: enabled(1) or bypassed(0)
	SYMBOLx2_CLK_IN: in std_logic;
		-- defines the AGC response time: approximately 2*modulation symbol rate is best

 	--// NORMALIZED OUTPUTS
 	-- latency: 3 CLK
	DATA_I_OUT: out std_logic_vector(13 downto 0);
	DATA_Q_OUT: out std_logic_vector(13 downto 0);
	SAMPLE_CLK_OUT: out std_logic;
		-- read at rising edge of CLK when SAMPLE_CLK_OUT = '1'
	SATURATION: out std_logic;
	
	--// TEST POINTS
	DATA_M : OUT std_logic_vector(13 downto 0);
	SAMPLE_M_CLK : OUT std_logic;
		-- input magnitude
	TP: out std_logic_vector(10 downto 1);
	CS1: out std_logic_vector(7 downto 0);
	CS1_CLK: out std_logic
		-- internal gain


			);
end entity;

architecture behavioral of AGC18 is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
	COMPONENT POLAR3
	PORT(
		CLK : IN std_logic;
		X_IN : IN std_logic_vector(11 downto 0);
		Y_IN : IN std_logic_vector(11 downto 0);
		SAMPLE_CLK_IN : IN std_logic;          
		PHASE_OUT : OUT std_logic_vector(10 downto 0);
		MAGNITUDE_OUT : OUT std_logic_vector(13 downto 0);
		SAMPLE_CLK_OUT : OUT std_logic
		);
	END COMPONENT;

    COMPONENT DIVIDER
	 Generic(
		NBITS: integer
	 );
    PORT(
         CLK : IN  std_logic;
         NUM : IN  std_logic_vector((NBITS-1) downto 0);
         DENOM : IN  std_logic_vector((NBITS-1) downto 0);
         SAMPLE_CLK_IN : IN  std_logic;
         QUOTIENT : OUT  std_logic_vector((NBITS-1) downto 0);
         REMAINDER : OUT  std_logic_vector((NBITS-1) downto 0);
         SAMPLE_CLK_OUT : OUT  std_logic
        );
    END COMPONENT;

	COMPONENT MULT18X18SIGNED
	PORT(
		A : IN std_logic_vector(17 downto 0);
		B : IN std_logic_vector(17 downto 0);          
		P : OUT std_logic_vector(35 downto 0)
		);
	END COMPONENT;

	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 indicates a one CLK delayed version of the net with the same name
-- Suffix _E indicates an extended precision version of the net with the same name
-- Suffix _N indicates an inverted version of the net with the same name

--//-- INTERNAL AGC --------------------------------------------------------------
signal DATA1M: std_logic_vector(13 downto 0) := (others => '0');
signal SAMPLE1M_CLK: std_logic := '0';
signal DATA1M_ACC: std_logic_vector(27 downto 0) := (others => '0');
signal DATA1M_AVERAGE: std_logic_vector(13 downto 0) := (others => '0');
signal DATA0_I: std_logic_vector(17 downto 0) := (others => '0');
signal ONEOVERX_DENOM: std_logic_vector(23 downto 0) := (others => '0');
signal ONEOVERX_NUM: std_logic_vector(23 downto 0) := (others => '0');
signal ONEOVERMAG1: std_logic_vector(23 downto 0) := (others => '0');
signal ONEOVERMAG1_SAMPLE_CLK: std_logic := '0';
signal DATA0_Q: std_logic_vector(17 downto 0) := (others => '0');
signal SAMPLE0_CLK: std_logic := '0';
signal DATA2_I: std_logic_vector(35 downto 0) := (others => '0');
signal DATA2_Q: std_logic_vector(35 downto 0) := (others => '0');
signal DATA2_I_D: std_logic_vector(35 downto 0) := (others => '0');
signal DATA2_Q_D: std_logic_vector(35 downto 0) := (others => '0');
signal SAMPLE2_CLK: std_logic := '0';
signal INTERNAL_GAIN: std_logic_vector(17 downto 0) := (others => '0');
signal INTERNAL_GAIN_SHIFT: std_logic := '0';
signal INTERNAL_GAIN_SHIFT_D: std_logic := '0';
signal DATA3_I: std_logic_vector(13 downto 0) := (others => '0');
signal DATA3_Q: std_logic_vector(13 downto 0) := (others => '0');
signal SAMPLE3_CLK: std_logic := '0';
signal SATURATION_AGC_I: std_logic := '0';
signal SATURATION_AGC_Q: std_logic := '0';
--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------

begin

--//-- INTERNAL AGC --------------------------------------------------------------
-- Feedforward control: input is scaled by 1/average magnitude

-- Complex/Real input to magnitude
-- Latency: 5 CLK
POLAR3_001: POLAR3 PORT MAP(
	CLK => CLK,
	X_IN => DATA_I_IN(13 downto 2),
	Y_IN => DATA_Q_IN(13 downto 2),
	SAMPLE_CLK_IN => SAMPLE_CLK_IN,
	PHASE_OUT => open,	-- for simulation, unused (optimized out) during build.
	MAGNITUDE_OUT => DATA1M,	-- sqrt(X_IN(13:0)^2 + Y_IN(13:0)^2)
	SAMPLE_CLK_OUT => SAMPLE1M_CLK 
);
-- monitoring
DATA_M <= DATA1M;
SAMPLE_M_CLK <= SAMPLE1M_CLK;

-- running average magnitude over 512-16384 symbols
DATA1M_ACC_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			DATA1M_ACC <= (others => '0');
		elsif(SYMBOLx2_CLK_IN = '1') then
			DATA1M_ACC <= DATA1M_ACC + ext(DATA1M, DATA1M_ACC'length) - ext(DATA1M_AVERAGE, DATA1M_ACC'length);
		end if;
	end if;
end process;

-- select averaging length
AVERAGING_LENGTH_512: if(AVERAGING_LENGTH = 512) generate
	DATA1M_AVERAGE <= DATA1M_ACC(22 downto 9);
end generate;
AVERAGING_LENGTH_1024: if(AVERAGING_LENGTH = 1024) generate
	DATA1M_AVERAGE <= DATA1M_ACC(23 downto 10);
end generate;
AVERAGING_LENGTH_2048: if(AVERAGING_LENGTH = 2048) generate
	DATA1M_AVERAGE <= DATA1M_ACC(24 downto 11);
end generate;
AVERAGING_LENGTH_4096: if(AVERAGING_LENGTH = 4096) generate
	DATA1M_AVERAGE <= DATA1M_ACC(25 downto 12);
end generate;
AVERAGING_LENGTH_8192: if(AVERAGING_LENGTH = 8192) generate
	DATA1M_AVERAGE <= DATA1M_ACC(26 downto 13);
end generate;
AVERAGING_LENGTH_16384: if(AVERAGING_LENGTH = 16384) generate
	DATA1M_AVERAGE <= DATA1M_ACC(27 downto 14);
end generate;

-- inverse magnitude
ONEOVERX_NUM <= MTARGET & "0000000000";	-- extend target magnitude with 10 LSbs
ONEOVERX_DENOM <= ext(DATA1M_AVERAGE, 24); -- extend average to 24 bits
ONEOVERX_001: DIVIDER 
GENERIC MAP(
	NBITS => 24
)
PORT MAP (
		 CLK => CLK,
		 NUM => ONEOVERX_NUM,
		 DENOM => ONEOVERX_DENOM,
		 SAMPLE_CLK_IN => SYMBOLx2_CLK_IN,	
		 QUOTIENT => ONEOVERMAG1,	-- format 14.10
		 REMAINDER => open,
		 SAMPLE_CLK_OUT => ONEOVERMAG1_SAMPLE_CLK
	  );


-- reclock at multiplier input for best timing
INTERNAL_AGC_001: process(CLK)
begin
	if rising_edge(CLK) then
		DATA0_I <= DATA_I_IN & "0000";	-- extend precision for 18x18 multiplier
		DATA0_Q <= DATA_Q_IN & "0000";
		SAMPLE0_CLK <= SAMPLE_CLK_IN;
		
		if(CONTROL(0) = '1') then
			-- internal gain control is enabled.
			if(ONEOVERMAG1(23 downto 17) /= 0) then
				INTERNAL_GAIN_SHIFT <= '1';	-- shift result by 7 bits
				INTERNAL_GAIN <= "0" & ONEOVERMAG1(23 downto 7);				
			else
				INTERNAL_GAIN_SHIFT <= '0';
				INTERNAL_GAIN <= "0" & ONEOVERMAG1(16 downto 0);
				-- gain. Always positive.
			end if;
		else
			-- bypass. Fixed unit gain here.
			INTERNAL_GAIN <= "000000010000000000";	-- verified by sim 12/23/12 az
		end if;
	end if;
end process;

INTERNAL_AGC_002: MULT18X18SIGNED PORT MAP(
	A => DATA0_I,
	B => INTERNAL_GAIN,	-- format 8.10
	P => DATA2_I
);

INTERNAL_AGC_003: MULT18X18SIGNED PORT MAP(
	A => DATA0_Q,
	B => INTERNAL_GAIN,
	P => DATA2_Q
);

-- reclock after multiplier for better timing
INTERNAL_AGC_004: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE2_CLK <= SAMPLE0_CLK;
		INTERNAL_GAIN_SHIFT_D <= INTERNAL_GAIN_SHIFT;	
		DATA2_I_D <= DATA2_I;
		DATA2_Q_D <= DATA2_Q;
	end if;
end process;

-- AGC range > 42 dB (7 bits) (TBC)
INTERNAL_AGC_005: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE3_CLK <= SAMPLE2_CLK;
		SATURATION <= SATURATION_AGC_I or SATURATION_AGC_Q;
		
		if(INTERNAL_GAIN_SHIFT_D = '0') then
			if(DATA2_I_D(35) = '1') and (DATA2_I_D(34 downto 27) /= "11111111") then
				-- negative saturation
				DATA3_I <= "10000000000001";
				SATURATION_AGC_I <= '1';
			elsif(DATA2_I_D(35) = '0') and (DATA2_I_D(34 downto 27) /= 0) then
				-- positive saturation
				DATA3_I <= "01111111111111";
				SATURATION_AGC_I <= '1';
			else
				DATA3_I <= DATA2_I_D(27 downto 14);	
				SATURATION_AGC_I <= '0';
			end if;
			if(DATA2_Q_D(35) = '1') and (DATA2_Q_D(34 downto 27) /= "11111111") then
				-- negative saturation
				DATA3_Q <= "10000000000001";
				SATURATION_AGC_Q <= '1';
			elsif(DATA2_Q_D(35) = '0') and (DATA2_Q_D(34 downto 27) /= 0) then
				-- positive saturation
				DATA3_Q <= "01111111111111";
				SATURATION_AGC_Q <= '1';
			else
				DATA3_Q <= DATA2_Q_D(27 downto 14);	
				SATURATION_AGC_Q <= '0';
			end if;
		else
			-- restore the 7-bit shift
			if(DATA2_I_D(35) = '1') and ((not DATA2_I_D(34 downto 20)) /= 0) then
				-- negative saturation
				DATA3_I <= "10000000000001";
				SATURATION_AGC_I <= '1';
			elsif(DATA2_I_D(35) = '0') and (DATA2_I_D(34 downto 20) /= 0) then
				-- positive saturation
				DATA3_I <= "01111111111111";
				SATURATION_AGC_I <= '1';
			else
				DATA3_I <= DATA2_I_D(20 downto 7);	
				SATURATION_AGC_I <= '0';
			end if;
			if(DATA2_Q_D(35) = '1') and ((not DATA2_Q_D(34 downto 20)) /= 0) then
				-- negative saturation
				DATA3_Q <= "10000000000001";
				SATURATION_AGC_Q <= '1';
			elsif(DATA2_Q_D(35) = '0') and (DATA2_Q_D(34 downto 20) /= 0) then
				-- positive saturation
				DATA3_Q <= "01111111111111";
				SATURATION_AGC_Q <= '1';
			else
				DATA3_Q <= DATA2_Q_D(20 downto 7);
				SATURATION_AGC_Q <= '0';
			end if;
		end if;
	end if;
end process;

--// output
DATA_I_OUT <= DATA3_I;
DATA_Q_OUT <= DATA3_Q;
SAMPLE_CLK_OUT <= SAMPLE3_CLK;

--// ComScope monitoring
CS1 <= INTERNAL_GAIN(13 downto 6);
CS1_CLK <= ONEOVERMAG1_SAMPLE_CLK;

--// simulation. send to a tab-delimited text file for subsequent (Matlab) plot.
--SIM2OUTFILE_X: if(SIMULATION = '1') generate
--	SIM2OUTFILE_001: SIM2OUTFILE 
--	generic map(
--		FILENAME => "agc18a.txt"
--	)
--	PORT MAP(
--		CLK => CLK,
--		DATA1 => DATA1M(12 downto 1),
--		DATA2 => DATA1M_AVERAGE(12 downto 1),
--		DATA3 => ONEOVERMAG1(15 downto 4),
--		SAMPLE_CLK_IN => '1'
--	);
--	SIM2OUTFILE_002: SIM2OUTFILE 
--	generic map(
--		FILENAME => "agc18b.txt"
--	)
--	PORT MAP(
--		CLK => CLK,
--		DATA1 => INTERNAL_GAIN(17 downto 6),
--		DATA2 => INTERNAL_GAIN(14 downto 3),
--		DATA3 => INTERNAL_GAIN(11 downto 0),
--		SAMPLE_CLK_IN => '1'
--	);
--end generate;

end behavioral;
