----------------------------------------------
-- MSS copyright 2012
-- Filename: BER3.VHD
-- Author: Alain Zarembowitch / MSS
-- Inheritance: N/A
-- Edit date: 7/25/12
-- Revision: 1
-- See also: 
--
-- Description: comparison of two streams to compute the bit error rate (BER).
-- The streams can be offset in time but have to start with the same bit after a SYNC_RESET (there is no 
-- self-synchronization circuit within)
----------------------------------------------      
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity BER3 is
	generic(
		DATA_WIDTH: integer range 1 to 16 := 3;
			-- data width in bits, must be the same for both input streams DATAIN_A, DATAIN_B and MASK_A.
			-- Valid range 1 to 16-bit width
		BER_WINDOW: integer := 1000
			-- BER window size. Maximum 2^32 -1
	);
   port (
		-- GLOBAL CLOCKS, RESETN
		CLK: in std_logic;	-- synchronous clock
		SYNC_RESET: in std_logic;		-- synchronous reset. active high.


		--// INPUTS
		-- Coding group
		DATAIN_A: in std_logic_vector(DATA_WIDTH-1 downto 0);	
		MASK_A: in std_logic_vector(DATA_WIDTH-1 downto 0);	
			-- '1' to ignore a specific bit in the BER computation
			-- For example: "101" to use only bit 1 in the stream comparison.
		INPUT_VALID_A: in std_logic;
			-- USE NOTE1: A-STREAM IS AT LEAST 2 CLKS AHEAD OF THE MATCHING B-STREAM
			-- USE NOTE2: A-STREAM IS AT MOST 500 SAMPLES AHEAD OF THE MATCHING B-STREAM
			
		DATAIN_B: in std_logic_vector(DATA_WIDTH-1 downto 0);	
		INPUT_VALID_B: in std_logic;
			-- above inputs are read at the rising edge of CLK when INPUT_VALID = '1'

		--// OUTPUTS
		BER: out std_logic_vector(31 downto 0) := (others => '0');
		OUTPUT_VALID: out std_logic;
			-- read BER at rising edge of CLK when OUTPUT_VALID = '1'
		
		--// MONITORING
		BIT_ERROR: out std_logic
			-- 1 CLK-wide pulse for each detected bit error
			-- Helpful in understanding the bit error statistics (with an oscilloscope): bursty? or fairly uniformly distributed?
    );
end entity;

architecture behavioral of BER3 is
-----------------------------------------------------------------
-- COMPONENTS
-----------------------------------------------------------------
	COMPONENT BRAM_DP
	GENERIC(
		DATA_WIDTHA: integer;
		ADDR_WIDTHA: integer;
		DATA_WIDTHB: integer;
		ADDR_WIDTHB: integer
	);
	PORT(
	    CLKA   : in  std_logic;
	    WEA    : in  std_logic;
	    ADDRA  : in  std_logic_vector(ADDR_WIDTHA-1 downto 0);
	    DIA   : in  std_logic_vector(DATA_WIDTHA-1 downto 0);
	    DOA  : out std_logic_vector(DATA_WIDTHA-1 downto 0);
	    CLKB   : in  std_logic;
	    WEB    : in  std_logic;
	    ADDRB  : in  std_logic_vector(ADDR_WIDTHB-1 downto 0);
	    DIB   : in  std_logic_vector(DATA_WIDTHB-1 downto 0);
	    DOB  : out std_logic_vector(DATA_WIDTHB-1 downto 0)
		);
	END COMPONENT;

	COMPONENT PC_16
	PORT(
		A : IN std_logic_vector(15 downto 0);          
		O : OUT std_logic_vector(4 downto 0)
		);
	END COMPONENT;
-----------------------------------------------------------------
-- SIGNALS
-----------------------------------------------------------------
-- suffix _D denotes a one-CLK delayed version of the net with the same name
signal DIA: std_logic_vector(31 downto 0) := (others => '0');
signal WPTR: std_logic_vector(8 downto 0) := (others => '0');
signal RPTR: std_logic_vector(8 downto 0) := (others => '1');
signal BUF_SIZE: std_logic_vector(8 downto 0) := (others => '0');
signal DOB: std_logic_vector(31 downto 0) := (others => '0');
signal INPUT_VALID_B_D: std_logic := '0';
signal INPUT_VALID_B_D2: std_logic := '0';
signal INPUT_VALID_B_D3: std_logic := '0';
signal DATAIN_B_D: std_logic_vector(DATA_WIDTH -1 downto 0) := (others => '0');
signal DATAIN_B_D2: std_logic_vector(DATA_WIDTH -1 downto 0) := (others => '0');
signal COMPARE_AB_WORDS: std_logic_vector(DATA_WIDTH -1 downto 0) := (others => '0');
signal N_BIT_ERRORS: std_logic_vector(4 downto 0) := (others => '0');
signal N_BIT_ERRORS_D: std_logic_vector(4 downto 0) := (others => '0');
signal BER_PARTIAL: std_logic_vector(BER'left downto 0) := (others => '0');
signal BER_WINDOW_CNTR: std_logic_vector(BER'left downto 0) := (others => '0');

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

-- WPTR management
WPTR_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			WPTR <= (others => '0');
		elsif(INPUT_VALID_A = '1') then
			WPTR <= WPTR + 1;
		end if;
	end if;
end process;

DIA(15 downto 0) <= ext(DATAIN_A,16);	-- max 16-bit wide input data stream A
DIA(31 downto 16) <= ext(MASK_A,16);	-- max 16-bit wide MASK_A

-- 16Kbit buffer(s) 
Inst_BRAM_DP: BRAM_DP 
GENERIC MAP(
	DATA_WIDTHA => 32,		
	ADDR_WIDTHA => 9,
	DATA_WIDTHB => 32,		 
	ADDR_WIDTHB => 9

)
PORT MAP(
	CLKA => CLK,
	WEA => INPUT_VALID_A,
	ADDRA => WPTR,	-- 9-bit address
	DIA => DIA,
	DOA => open,
	CLKB => CLK,
	WEB => '0',
	ADDRB => RPTR,	-- 9-bit address
	DIB => x"00000000",
	DOB => DOB
);

BUF_SIZE <= WPTR + (not RPTR);	
	-- number unread entries

RPTR_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			RPTR <= (others => '1');
		elsif(BUF_SIZE /= 0) and (INPUT_VALID_B = '1') then
			RPTR <= RPTR + 1;
		end if;
	end if;
end process;

-- delay B-stream  by 2 CLKs to realign with RAM block output
RECLOCK_001: process(CLK)
begin
	if rising_edge(CLK) then
		INPUT_VALID_B_D <= INPUT_VALID_B;
		INPUT_VALID_B_D2 <= INPUT_VALID_B_D;
		DATAIN_B_D <= DATAIN_B;
		DATAIN_B_D2 <= DATAIN_B_D;
	end if;
end process;

-- compare -A and -B streams, then mask result (upper half of DOB)
COMPARE_AB_WORDS <= (DOB(DATA_WIDTH -1 downto 0) xor DATAIN_B_D2) and (not DOB(DATA_WIDTH -1 + 16 downto 16));

-- count the number of 1's in COMPARE.
PC_16_001: PC_16 PORT MAP(
	A => ext(COMPARE_AB_WORDS,16),
	O => N_BIT_ERRORS
);

-- Monitoring
MONITORING_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(INPUT_VALID_B_D3 = '1') then
			if(N_BIT_ERRORS_D /= 0) then
				BIT_ERROR <= '1';
			else
				BIT_ERROR <= '0';
			end if;
		else
			BIT_ERROR <= '0';
		end if;
	end if;
end process;

-- BER count
BER_COUNT_001: process(CLK)
begin
	if rising_edge(CLK) then
		-- reclock for better timing
		N_BIT_ERRORS_D <= N_BIT_ERRORS;
		INPUT_VALID_B_D3 <= INPUT_VALID_B_D2;
		
		if(SYNC_RESET = '1') then
			BER_PARTIAL <= (others => '0');
			BER_WINDOW_CNTR <= (others => '0');
			OUTPUT_VALID <= '0';
		elsif(INPUT_VALID_B_D3 = '1') then
			if(BER_WINDOW_CNTR = BER_WINDOW) then
				-- reached the end of the BER window. Output BER result and start a new window.
				BER <= BER_PARTIAL;
				OUTPUT_VALID <= '1';
				BER_PARTIAL <= ext(N_BIT_ERRORS_D, BER_PARTIAL'length);
				BER_WINDOW_CNTR <= (0 => '1', others => '0');
			else
				BER_PARTIAL <= BER_PARTIAL + ext(N_BIT_ERRORS_D, BER_PARTIAL'length);
				BER_WINDOW_CNTR <= BER_WINDOW_CNTR + 1;
				OUTPUT_VALID <= '0';
			end if;
		else
			OUTPUT_VALID <= '0';
		end if;
		
	end if;
end process;


end behavioral;					
