library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_textio.all;

library std; -- for Printing
use std.textio.all;

use work.mem_pkg.all;
use work.op_pkg.all;
use work.core_pkg.all;
use work.tb_util_pkg.all;

entity tb is
end entity;

architecture bench of tb is
	constant CLK_PERIOD : time := 10 ns;

	signal clk : std_logic;
	signal res_n : std_logic := '0';
	signal stop : boolean := false;
	signal start : std_logic := '0';
	
	file input_file : text;
	file output_ref_file : text;

	subtype addr is std_logic_vector(REG_BITS-1 downto 0);
	subtype data is std_logic_vector(DATA_WIDTH-1 downto 0);
	
	type data_arr is array(0 to 400) of MEM_DATA_TYPE;

	type input_t is
	record
		stall     	: std_logic;
		flush		: std_logic;

		pc_in		: PC_TYPE;
		instr		: instr_type;

	end record;

	type output_t is
	record
		pc_out 	: PC_TYPE;
		exec_op	: exec_op_type;
		mem_op	: mem_op_type;
		wb_op	: wb_op_type;
		exc_dec	: std_logic;
	end record;

	signal inp  : input_t := (
		'0',
		'0',
		(others => '0'),
		(others => '0')
	);
	signal outp : output_t;
	signal reg_write : reg_write_type := (write => '0', reg => (others => '0'), data => (others => '0'));

	impure function read_next_input(file f : text) return input_t is
		variable l : line;
		variable result : input_t;
	begin
		l := get_next_valid_line(f);
		l := get_next_valid_line(f);
		result.stall := str_to_sl(l(1));

		l := get_next_valid_line(f);
		result.flush := str_to_sl(l(1));

		l := get_next_valid_line(f);
		result.pc_in := bin_to_slv(l.all, PC_TYPE'LENGTH);

		l := get_next_valid_line(f);
		result.instr := bin_to_slv(trim(rm_comment(l.all)), instr_type'LENGTH);

		return result;
	end function;

	impure function read_next_output(file f : text) return output_t is
		variable l : line;
		variable result : output_t;
	begin
		l := get_next_valid_line(f);
		l := get_next_valid_line(f);
		result.pc_out := bin_to_slv(l.all, PC_TYPE'LENGTH);

		--exec_op
		l := get_next_valid_line(f);
		result.exec_op.aluop := str_to_alu_op(l.all);

		l := get_next_valid_line(f);
		result.exec_op.alusrc1 := str_to_sl(l(1));

		l := get_next_valid_line(f);
		result.exec_op.alusrc2 := str_to_sl(l(1));

		l := get_next_valid_line(f);
		result.exec_op.alusrc3 := str_to_sl(l(1));

		l := get_next_valid_line(f);
		result.exec_op.rs1 := bin_to_slv(l.all, REG_ADR_TYPE'LENGTH);

		l := get_next_valid_line(f);
		result.exec_op.rs2 := bin_to_slv(l.all, REG_ADR_TYPE'LENGTH);

		l := get_next_valid_line(f);
		result.exec_op.readdata1 := bin_to_slv(l.all, data_type'LENGTH);

		l := get_next_valid_line(f);
		result.exec_op.readdata2 := bin_to_slv(l.all, data_type'LENGTH);

		l := get_next_valid_line(f);
		result.exec_op.imm := bin_to_slv(l.all, data_type'LENGTH);

		--mem_op
		l := get_next_valid_line(f);
		result.mem_op.branch := str_to_branch(l.all);

		l := get_next_valid_line(f);
		result.mem_op.mem.memread := str_to_sl(l(1));
		result.mem_op.mem.memwrite := str_to_sl(l(2));

		l := get_next_valid_line(f);
		result.mem_op.mem.memtype := str_to_mem_op(l.all);

		--wb_op
		l := get_next_valid_line(f);
		result.wb_op.rd := bin_to_slv(l.all, REG_ADR_TYPE'LENGTH);

		l := get_next_valid_line(f);
		result.wb_op.write := str_to_sl(l(1));

		l := get_next_valid_line(f);
		result.wb_op.src := str_to_wbs_op(l.all);



		l := get_next_valid_line(f);
		result.exc_dec := str_to_sl(l(1));

		return result;
	end function;

	procedure check_output(output_ref : output_t) is
		variable passed : boolean;
	begin
		passed := (outp = output_ref);

		if passed then
			report " PASSED: "
			& "stall="     & to_string(inp.stall)
			& " flush="  & to_string(inp.flush)
			& " pc_in="   & to_string(inp.pc_in)
			& " instr="   & to_string(inp.instr)
			severity note;
		else
			report "FAILED: "
			& "stall="     & to_string(inp.stall)
			& " flush="  & to_string(inp.flush)
			& " pc_in="   & to_string(inp.pc_in)
			& " instr="   & to_string(inp.instr) & lf
			& "**     expected: pc_out=" & to_string(output_ref.pc_out) &  lf
			& "**        aluop=" & to_string(output_ref.exec_op.aluop) & lf
			& "**        alusrc1=" & to_string(output_ref.exec_op.alusrc1) & " alusrc2=0" & to_string(output_ref.exec_op.alusrc2) & " alusrc3= " & to_string(output_ref.exec_op.alusrc3) & lf
			& "**        rs1= " & to_string(output_ref.exec_op.rs1) & " rs2= " & to_string(output_ref.exec_op.rs2) & lf 
			& "**        readdata1=" & to_string(output_ref.exec_op.readdata1) & " readdata2= " & to_string(output_ref.exec_op.readdata2) & " imm= " & to_string(output_ref.exec_op.imm) & lf
			& lf 
			& "**        branch= " & to_string(output_ref.mem_op.branch) & lf
			& "**        memread= " & to_string(output_ref.mem_op.mem.memread) & " memwrite= " & to_string(output_ref.mem_op.mem.memwrite) & " memtype= " & to_string(output_ref.mem_op.mem.memtype) & lf
			& lf
			& "**        rd=" & to_string(output_ref.wb_op.rd) & " write= " & to_string(output_ref.wb_op.write) & " src= " & to_string(output_ref.wb_op.src) & lf
			& lf
			& "**        exc_dec= " & to_string(output_ref.exc_dec) & lf 
			& lf

			& "**    actual:   pc_out= " & to_string(outp.pc_out) &  lf
			& "**       aluop= " & to_string(outp.exec_op.aluop) & lf
			& "**       alusrc1= " & to_string(outp.exec_op.alusrc1) & " alusrc2=0" & to_string(outp.exec_op.alusrc2) & " alusrc3= " & to_string(outp.exec_op.alusrc3) & lf
			& "**       rs1=  " & to_string(outp.exec_op.rs1) & " rs2= " & to_string(outp.exec_op.rs2) & lf 
			& "**       readdata1= " & to_string(outp.exec_op.readdata1) & " readdata 2=" & to_string(outp.exec_op.readdata2) & " imm= " & to_string(outp.exec_op.imm) & lf
			& lf 
			& "**       branch= " & to_string(outp.mem_op.branch) & lf
			& "**       memread= " & to_string(outp.mem_op.mem.memread) & " memwrite= " & to_string(outp.mem_op.mem.memwrite) & " memtype= " & to_string(outp.mem_op.mem.memtype) & lf
			& lf
			& "**       rd=" & to_string(outp.wb_op.rd) & " write= " & to_string(outp.wb_op.write) & " src= " & to_string(outp.wb_op.src) & lf
			& lf
			& "**       exc_dec= " & to_string(outp.exc_dec) & lf & lf
			severity error;
		end if;
	end procedure;

begin

	decode_inst : entity work.decode
	port map (
		clk     	=> clk,
		res_n   	=> res_n,
		stall   	=> inp.stall,
		flush		=> inp.flush,
		
		pc_in		=> inp.pc_in,	
		instr		=> inp.instr,

		reg_write 	=> reg_write,
		
		pc_out		=> outp.pc_out,
		exec_op		=> outp.exec_op,
		mem_op		=> outp.mem_op,
		wb_op		=> outp.wb_op,

		exc_dec		=> outp.exc_dec
	);



			
	stimulus : process
		variable fstatus: file_open_status;
	begin
		
		res_n <= '0';
		wait until rising_edge(clk);
		res_n <= '1';
		
		for i in 1 to 2**5-1 loop
			reg_write <= (write => '1', reg => std_logic_vector(to_unsigned(i, reg_adr_type'LENGTH)), data => std_logic_vector(to_unsigned(i, data_type'LENGTH)));
			wait until rising_edge(clk);
		end loop;
		reg_write <= (write => '0', reg => (others => '0'), data => (others => '0'));
		file_open(fstatus, input_file, "testdata/input.txt", READ_MODE);
		
		wait until falling_edge(clk);
		wait for CLK_PERIOD/10;

		while not endfile(input_file) loop
			inp <= read_next_input(input_file);
			start <= '1';
			timeout(1, CLK_PERIOD);
		end loop;
		
		wait;
	end process;

	output_checker : process
		variable fstatus: file_open_status;
		variable output_ref : output_t;
	begin
		file_open(fstatus, output_ref_file, "testdata/output.txt", READ_MODE);

		wait until res_n = '1';
		wait until start = '1';
		while not endfile(output_ref_file) loop
			output_ref := read_next_output(output_ref_file);

			wait until falling_edge(clk);
			check_output(output_ref);
			wait until rising_edge(clk);
		end loop;
		stop <= true;
		
		wait;
	end process;

	generate_clk : process
	begin
		clk_generate(clk, CLK_PERIOD, stop);
		wait;
	end process;

end architecture;
