-- TestBench Template 

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.numeric_std.ALL;
USE std.textio.ALL;


ENTITY multiboot_core_tb IS
   END multiboot_core_tb;

ARCHITECTURE behavior OF multiboot_core_tb IS 

   component multiboot_core
      port(
             clk             : in   STD_LOGIC;
             rst             : in   STD_LOGIC;
             CTRL0_i         : in   STD_LOGIC_VECTOR (15 downto 0);
             CTRL1_o         : out  STD_LOGIC_VECTOR (15 downto 0);
             STAT_ICAP_o     : out  STD_LOGIC_VECTOR (15 downto 0);
             GENERAL1_i      : in   STD_LOGIC_VECTOR (15 downto 0);
             GENERAL2_i      : in   STD_LOGIC_VECTOR (15 downto 0);
             GENERAL3_i      : in   STD_LOGIC_VECTOR (15 downto 0);
             GENERAL4_i      : in   STD_LOGIC_VECTOR (15 downto 0);
             GENERAL1_ICAP_o : out  STD_LOGIC_VECTOR (15 downto 0);
             GENERAL2_ICAP_o : out  STD_LOGIC_VECTOR (15 downto 0);
             GENERAL3_ICAP_o : out  STD_LOGIC_VECTOR (15 downto 0);
             GENERAL4_ICAP_o : out  STD_LOGIC_VECTOR (15 downto 0)
          );
   end component;

   signal  rst               : STD_LOGIC; 
   signal  clk               : STD_LOGIC; 
   signal  CTRL0_i           : STD_LOGIC_VECTOR (15 downto 0);
   signal  CTRL1_o           : STD_LOGIC_VECTOR (15 downto 0);
   signal  STAT_ICAP_o       : STD_LOGIC_VECTOR (15 downto 0);
   signal  GENERAL1_i        : STD_LOGIC_VECTOR (15 downto 0);
   signal  GENERAL2_i        : STD_LOGIC_VECTOR (15 downto 0);
   signal  GENERAL3_i        : STD_LOGIC_VECTOR (15 downto 0);
   signal  GENERAL4_i        : STD_LOGIC_VECTOR (15 downto 0);
   signal  GENERAL1_ICAP_o   : STD_LOGIC_VECTOR (15 downto 0);
   signal  GENERAL2_ICAP_o   : STD_LOGIC_VECTOR (15 downto 0);
   signal  GENERAL3_ICAP_o   : STD_LOGIC_VECTOR (15 downto 0);
   signal  GENERAL4_ICAP_o   : STD_LOGIC_VECTOR (15 downto 0); 


   constant clk_period       : TIME := 200 ns; 

   constant OP_FULLMULTIBOOT : STD_LOGIC_VECTOR(3 downto 0) := X"0";
   constant OP_WR_GENERAL1   : STD_LOGIC_VECTOR(3 downto 0) := X"1";
   constant OP_WR_GENERAL2   : STD_LOGIC_VECTOR(3 downto 0) := X"2";
   constant OP_WR_GENERAL3   : STD_LOGIC_VECTOR(3 downto 0) := X"3";
   constant OP_WR_GENERAL4   : STD_LOGIC_VECTOR(3 downto 0) := X"4";
   constant OP_RD_GENERAL1   : STD_LOGIC_VECTOR(3 downto 0) := X"5";
   constant OP_RD_GENERAL2   : STD_LOGIC_VECTOR(3 downto 0) := X"6";
   constant OP_RD_GENERAL3   : STD_LOGIC_VECTOR(3 downto 0) := X"7";
   constant OP_RD_GENERAL4   : STD_LOGIC_VECTOR(3 downto 0) := X"8";
   constant OP_IPROG         : STD_LOGIC_VECTOR(3 downto 0) := X"C";
   constant OP_STAT          : STD_LOGIC_VECTOR(3 downto 0) := X"D";

   file file_handler         : TEXT;
   constant log_path         : STRING := "../test/log/multiboot_core_tb.log";
   constant sep              : CHARACTER := ht;

begin

   uut: multiboot_core
   port map(
              clk         => clk,
              rst         => rst,
              CTRL0_i     => CTRL0_i,
              CTRL1_o     => CTRL1_o,
              STAT_ICAP_o       => STAT_ICAP_o,
              GENERAL1_i        => GENERAL1_i,
              GENERAL2_i        => GENERAL2_i,
              GENERAL3_i        => GENERAL3_i,
              GENERAL4_i        => GENERAL4_i,
              GENERAL1_ICAP_o   => GENERAL1_ICAP_o,
              GENERAL2_ICAP_o   => GENERAL2_ICAP_o,
              GENERAL3_ICAP_o   => GENERAL3_ICAP_o,
              GENERAL4_ICAP_o   => GENERAL4_ICAP_o
           );


   clk_process: process
   begin
      clk <= '1';
      wait for clk_period/2;
      clk <= '0';
      wait for clk_period/2;
   end process;


   tb : process

      procedure reset_proc is
      begin
         wait for clk_period;
         GENERAL1_i     <= (others => '0'); 
         GENERAL2_i     <= (others => '0'); 
         GENERAL3_i     <= (others => '0'); 
         GENERAL4_i     <= (others => '0'); 
         CTRL0_i        <= (others => '0');
         rst       <= '1';
         wait for clk_period;
         rst       <= '0';
         wait for clk_period*25;
      end reset_proc;


   procedure assert16_to_file(data:STD_LOGIC_VECTOR(15 downto 0);
   reference:STD_LOGIC_VECTOR(15 downto 0);
   test_id :string; msg :string) is

      variable line_buffer : LINE;
   begin
      if data = reference then
         write(line_buffer, string'(test_id&sep&"OK"&sep&msg)); 
      else
         write(line_buffer, string'(test_id&sep&"Fail"&sep&msg)); 
      end if;
      writeline(file_handler, line_buffer);
      assert data = reference  report "Bad"&msg; 
   end procedure;

   procedure assert32_to_file(data:STD_LOGIC_VECTOR(31 downto 0);
   reference:STD_LOGIC_VECTOR(31 downto 0);
   test_id :string; msg :string) is

      variable line_buffer : LINE;
   begin
      if data = reference then
         write(line_buffer, string'(test_id&sep&"OK"&sep&msg)); 
      else
         write(line_buffer, string'(test_id&sep&"Fail"&sep&msg)); 
      end if;
      writeline(file_handler, line_buffer);
      assert data = reference  report "Bad"&msg; 
   end procedure;


   variable header_line_buffer : LINE;

begin
   file_open(file_handler, log_path, WRITE_MODE);
     write(header_line_buffer, string'("-----------------------------------------"));
   writeline(file_handler, header_line_buffer);
     write(header_line_buffer, string'("-----     multiboot_core_tb.vhd     -----"));
   writeline(file_handler, header_line_buffer);
     write(header_line_buffer, string'("-----------------------------------------"));
   writeline(file_handler, header_line_buffer);
     write(header_line_buffer, string'(""));
   writeline(file_handler, header_line_buffer);
     write(header_line_buffer, string'("Test ID"&sep&"Status"&sep&"Description"));
   writeline(file_handler, header_line_buffer);


   -- Initialization assert
   wait for 2*clk_period;
   reset_proc;
      assert16_to_file(GENERAL1_ICAP_o, X"0000", "0x0", "GENERAL1_ICAP register initialization");
      assert16_to_file(GENERAL2_ICAP_o, X"0000", "0x1", "GENERAL2_ICAP register initialization");
      assert16_to_file(GENERAL3_ICAP_o, X"0000", "0x2", "GENERAL3_ICAP register initialization");
      assert16_to_file(GENERAL4_ICAP_o, X"0000", "0x3", "GENERAL4_ICAP register initialization");
      assert16_to_file(STAT_ICAP_o, X"0000", "0x4", "STAT_ICAP register initialization");
  
   GENERAL1_i     <= X"1234"; 
   GENERAL2_i     <= X"5678"; 
   GENERAL3_i     <= X"9ABC"; 
   GENERAL4_i     <= X"DEF0"; 

   -- We will test the full multiboot sequence
   CTRL0_i(3 downto 0)    <= OP_FULLMULTIBOOT;
   CTRL0_i(4)             <= '1';
   CTRL0_i(15 downto 5)   <= (others => '0');
   -- Every wb_clk cycle the fsm should move one state. The chain state will be:
   -- {S0_IDLE, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, Q0_OK, }
   wait for 13*clk_period;
   wait for clk_period/2;
   wait until rising_edge(clk);
   assert16_to_file(CTRL1_o, X"0001", "0x5", "Multiboot sequence is correct");
   -- We act as the multiboot_regs.vhd by deasserting CTRL0_i(4) bit
   CTRL0_i(4)             <= '0';
   wait for 100*clk_period;

   CTRL0_i(3 downto 0)    <= OP_WR_GENERAL1;
   CTRL0_i(4)             <= '1';
   CTRL0_i(15 downto 5)   <= (others => '0');
   -- Every wb_clk cycle the fsm should move one state. The chain state will be:
   -- {S0_IDLE, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, Q0_OK, }
   wait for 5*clk_period;
   wait for clk_period/2;
   wait until rising_edge(clk);
   assert16_to_file(CTRL1_o, X"0001", "0x6", "GENERAL1 write sequence is correct");
   -- We act as the multiboot_regs.vhd by deasserting CTRL0_i(4) bit
   CTRL0_i(4)             <= '0';
   wait for 100*clk_period;

   CTRL0_i(3 downto 0)    <= OP_WR_GENERAL2;
   CTRL0_i(4)             <= '1';
   CTRL0_i(15 downto 5)   <= (others => '0');
   -- Every wb_clk cycle the fsm should move one state. The chain state will be:
   -- {S0_IDLE, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, Q0_OK, }
   wait for 5*clk_period;
   wait for clk_period/2;
   wait until rising_edge(clk);
   assert16_to_file(CTRL1_o, X"0001", "0x7", "GENERAL2 write sequence is correct");
   -- We act as the multiboot_regs.vhd by deasserting CTRL0_i(4) bit
   CTRL0_i(4)             <= '0';
   wait for 100*clk_period;

   CTRL0_i(3 downto 0)    <= OP_WR_GENERAL3;
   CTRL0_i(4)             <= '1';
   CTRL0_i(15 downto 5)   <= (others => '0');
   -- Every wb_clk cycle the fsm should move one state. The chain state will be:
   -- {S0_IDLE, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, Q0_OK, }
   wait for 5*clk_period;
   wait for clk_period/2;
   wait until rising_edge(clk);
   assert16_to_file(CTRL1_o, X"0001", "0x8", "GENERAL3 write sequence is correct");
   -- We act as the multiboot_regs.vhd by deasserting CTRL0_i(4) bit
   CTRL0_i(4)             <= '0';
   wait for 100*clk_period;

   CTRL0_i(3 downto 0)    <= OP_WR_GENERAL4;
   CTRL0_i(4)             <= '1';
   CTRL0_i(15 downto 5)   <= (others => '0');
   -- Every wb_clk cycle the fsm should move one state. The chain state will be:
   -- {S0_IDLE, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, Q0_OK, }
   wait for 5*clk_period;
   wait for clk_period/2;
   wait until rising_edge(clk);
   assert16_to_file(CTRL1_o, X"0001", "0x9", "GENERAL4 write sequence is correct");
   -- We act as the multiboot_regs.vhd by deasserting CTRL0_i(4) bit
   CTRL0_i(4)             <= '0';
   wait for 100*clk_period;

   CTRL0_i(3 downto 0)    <= OP_RD_GENERAL1;
   CTRL0_i(4)             <= '1';
   CTRL0_i(15 downto 5)   <= (others => '0');
   -- Every wb_clk cycle the fsm should move one state. The chain state will be:
   -- {S0_IDLE, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, Q0_OK, }
   wait for 15*clk_period;
   wait for clk_period/2;
   wait until rising_edge(clk);
   assert16_to_file(CTRL1_o, X"0001", "0xA", "GENERAL1 fsm readback is correct");
   -- We act as the multiboot_regs.vhd by deasserting CTRL0_i(4) bit
   CTRL0_i(4)             <= '0';
   wait for 100*clk_period;

   CTRL0_i(3 downto 0)    <= OP_RD_GENERAL2;
   CTRL0_i(4)             <= '1';
   CTRL0_i(15 downto 5)   <= (others => '0');
   -- Every wb_clk cycle the fsm should move one state. The chain state will be:
   -- {S0_IDLE, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, Q0_OK, }
   wait for 15*clk_period;
   wait for clk_period/2;
   wait until rising_edge(clk);
   assert16_to_file(CTRL1_o, X"0001", "0xB", "GENERAL2 fsm readback is correct");
   -- We act as the multiboot_regs.vhd by deasserting CTRL0_i(4) bit
   CTRL0_i(4)             <= '0';
   wait for 100*clk_period;

   CTRL0_i(3 downto 0)    <= OP_RD_GENERAL3;
   CTRL0_i(4)             <= '1';
   CTRL0_i(15 downto 5)   <= (others => '0');
   -- Every wb_clk cycle the fsm should move one state. The chain state will be:
   -- {S0_IDLE, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, Q0_OK, }
   wait for 15*clk_period;
   wait for clk_period/2;
   wait until rising_edge(clk);
   assert16_to_file(CTRL1_o, X"0001", "0xC", "GENERAL3 fsm readback is correct");
   -- We act as the multiboot_regs.vhd by deasserting CTRL0_i(4) bit
   CTRL0_i(4)             <= '0';
   wait for 100*clk_period;

   CTRL0_i(3 downto 0)    <= OP_RD_GENERAL4;
   CTRL0_i(4)             <= '1';
   CTRL0_i(15 downto 5)   <= (others => '0');
   -- Every wb_clk cycle the fsm should move one state. The chain state will be:
   -- {S0_IDLE, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, Q0_OK, }
   wait for 15*clk_period;
   wait for clk_period/2;
   wait until rising_edge(clk);
   assert16_to_file(CTRL1_o, X"0001", "0xD", "GENERAL4 fsm readback is correct");
   -- We act as the multiboot_regs.vhd by deasserting CTRL0_i(4) bit
   CTRL0_i(4)             <= '0';
   wait for 100*clk_period;



   assert false report "No error. Simulation ends." severity failure;
end process tb;

  end;
