library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.prng_pkg.all;

entity prng_tb is
end entity;

architecture bench of prng_tb is

	component prng is
		port (
			clk       : in std_logic;
			res_n     : in std_logic;
			load_seed : in std_logic;
			seed      : in std_logic_vector(7 downto 0) := (others => '0');
			en        : in std_logic;
			prdata    : out std_logic
		);
	end component;

	-- prng signals
	signal clk       : std_logic;
	signal res_n     : std_logic := '1';
	signal load_seed : std_logic := '0';
	signal seed      : std_logic_vector(7 downto 0) := (others => '0');
	signal en        : std_logic := '1';
	signal prdata    : std_logic;

	-- tb signals
	signal seq        : std_logic_vector(15 downto 0) := (others => '0');
	signal initial    : std_logic_vector(seq'range) := (others => '0');
	signal stop_clock : boolean := false;

	-- constants
	-- 1/50MHz = 20ns
	constant CLK_PERIOD : time := 20 ns;
	-- (11907086 % 15) + 1)_10 = (12)_10 = (1100)_2
	-- (1100)_2 << (4)_10 = (11000000)_2 = (192)_10
	constant SEED_FROM : integer := 192;
	-- 192 + 15 = 207
	constant SEED_TO : integer := 207;

begin
	prng_inst : prng
	port map (
		clk       => clk,
		res_n     => res_n,
		load_seed => load_seed,
		seed      => seed,
		en        => en,
		prdata    => prdata
	);

	-- detects the period of the LFSR
	measure : process
		variable count : integer := 1;
		-- TODO: find good way to make this respect generics (2^seq'length) 
		variable min_count : integer := 65535;
		variable max_count : integer := 0;
	begin
			wait for 50 ns;
			-- apply reset
			res_n <= '0';
			-- wait some time
			wait for 50 ns;
			wait until rising_edge(clk);
			-- release reset
			res_n <= '1';
		for current_seed in SEED_FROM to SEED_TO loop
			wait until rising_edge(clk);
			-- reset count
			count := 0;

			-- interpret integer as std_logic_vector
			seed <= std_logic_vector(to_unsigned(current_seed, seed'length));
			wait until rising_edge(clk);
			load_seed <= '1';
			wait until rising_edge(clk);
			wait until rising_edge(clk);

			load_seed <= '0';

			-- it takes seq'length cycles to bubble up valid values of the LFSR
			-- magic number 9 just because
			wait for (seq'length + 9) * CLK_PERIOD;
			initial <= seq;
			wait until rising_edge(clk);

			while not(seq = initial) loop
				if (rising_edge(clk)) then
					count := count + 1;
				end if;
				wait on clk;
			end loop;

			if count < min_count then
				min_count := count;
			end if;

			if count > max_count then
				max_count := count;
			end if;

			report "seed: " & to_string(to_integer(unsigned(seed))) & ", period: " & to_string(count);
		end loop;

		-- report final results
		report "min period: " & to_string(min_count) & ", max period: " & to_string(max_count);
		-- stop testbench once all seeds have been tried
		stop_clock <= true;
		wait;
	end process;

	-- implements seq as a shift register of length seq'range
	shift : process(clk, res_n)
	begin
		if res_n = '0' then
			seq <= (others => '0');
		elsif rising_edge(clk) then
			-- take vector without first bit and append prdata
			seq <= seq(seq'high - 1 downto seq'low) & prdata;
			--for i in seq'high downto seq'low + 1 loop
				--seq(i) <= seq(i - 1);
			--end loop;

			--seq(seq'low) <= prdata;
		end if;
	end process;

	-- taken from top_tb
	generate_clk : process
	begin
		while not stop_clock loop
			clk <= '0', '1' after CLK_PERIOD / 2;
			wait for CLK_PERIOD;
		end loop;
		wait;
	end process;

end architecture;
