//---------------------------------------------------
// Macro definition
//---------------------------------------------------
`define NUM_MASTERS  1
`define NUM_SLAVES   1

`define TB        $root.xsimtop.xsimtop.top.lEcho_dut
`define MASTER0   $root.xsimtop.xsimtop.top.lEcho_dut.master_0
`define SLAVE0    $root.xsimtop.xsimtop.top.lEcho_dut.slave_0

module test_program ();

   import verbosity_pkg::*;
   import avalon_mm_pkg::*;

//---------------------------------------------------
// Constants
//---------------------------------------------------
   localparam ADDR_W                   = 12;
            
   localparam SYMBOL_W                 = 8;
   localparam NUM_SYMBOLS              = 4;
   localparam DATA_W                   = NUM_SYMBOLS * SYMBOL_W;
            
   localparam BURST_W                  = 4;
   localparam MAX_BURST                = 8;
   
   localparam SLAVE_SPAN               = 32'h1000;
   
   localparam MAX_COMMAND_IDLE         = 5;
   localparam MAX_COMMAND_BACKPRESSURE = 2;
   localparam MAX_DATA_IDLE            = 3;

//---------------------------------------------------
// Data structures
//---------------------------------------------------
   typedef logic [BURST_W-1:0]      Burstcount;

   typedef enum bit
   {
       WRITE = 0,
       READ  = 1
   } Transaction;
   
   typedef enum bit
   {
       NOBURST = 0,
       BURST   = 1
   } Burstmode;

   typedef struct 
   {
       Transaction                  trans;
       Burstcount                   burstcount;
       logic [ADDR_W-1: 0]          addr;
       logic [DATA_W-1:0]           data       [MAX_BURST-1:0];
       logic [NUM_SYMBOLS-1:0]      byteenable [MAX_BURST-1:0];
       bit [31:0]                   cmd_delay;
       bit [31:0]                   data_idles [MAX_BURST-1:0];
   } Command;

   typedef struct
   {
      Burstcount                    burstcount;
      logic [DATA_W-1:0]            data     [MAX_BURST-1:0];
      bit [31:0]                    latency  [MAX_BURST-1:0];
   } Response;

//---------------------------------------------------
// Command and Response Queues
//---------------------------------------------------
// master command queue
Command  write_command_queue_master[`NUM_MASTERS][$];
Command  read_command_queue_master[`NUM_MASTERS][$];

// slave command queue
Command  write_command_queue_slave[`NUM_SLAVES][$];
Command  read_command_queue_slave[`NUM_SLAVES][$];

// slave response queue
Response read_response_queue_slave[`NUM_SLAVES][$];

//---------------------------------------------------
// Macro Definitions
//---------------------------------------------------
`define MACRO_CONFIGURE_AND_PUSH_COMMAND_TO_MASTER(MASTER_ID) \
   task automatic configure_and_push_command_to_master_``MASTER_ID ( \
      Command  cmd \
   ); \
      `MASTER``MASTER_ID.set_command_address(cmd.addr); \
      `MASTER``MASTER_ID.set_command_burst_count(cmd.burstcount); \
      `MASTER``MASTER_ID.set_command_burst_size(cmd.burstcount); \
      `MASTER``MASTER_ID.set_command_init_latency(cmd.cmd_delay); \
\
      if (cmd.trans == WRITE) begin \
         `MASTER``MASTER_ID.set_command_request(REQ_WRITE); \
         for (int i = 0; i < cmd.burstcount; i++) begin \
            `MASTER``MASTER_ID.set_command_data(cmd.data[i], i); \
            `MASTER``MASTER_ID.set_command_byte_enable(cmd.byteenable[i], i); \
            `MASTER``MASTER_ID.set_command_idle(cmd.data_idles[i], i); \
         end \
      end else begin \
         `MASTER``MASTER_ID.set_command_request(REQ_READ); \
         `MASTER``MASTER_ID.set_command_idle(cmd.data_idles[0], 0); \
      end \
\
      `MASTER``MASTER_ID.push_command(); \
   endtask

// Get command received by slave, verify command.
// If command is read command, send out response
`define MACRO_SLAVE_THREAD(SLAVE_ID) \
   always @(`SLAVE``SLAVE_ID.signal_command_received) begin \
\
      Command     actual_cmd, exp_cmd; \
      Response    rsp; \
      string      msg; \
\
      automatic int backpressure_cycles; \
\
      // set random backpressure cycles for next command \
      for (int i = 0; i < MAX_BURST; i++) begin \
         backpressure_cycles = $urandom_range(0, MAX_COMMAND_BACKPRESSURE); \
         `SLAVE``SLAVE_ID.set_interface_wait_time(backpressure_cycles, i); \
      end \
\
      actual_cmd = get_command_from_slave_``SLAVE_ID(); \
\
      // set read response \
      if (actual_cmd.trans == READ) begin \
      $sformat(msg, "read command %x %x", actual_cmd.burstcount, actual_cmd.addr); \
      print(VERBOSITY_INFO, msg); \
         rsp = create_response(actual_cmd.burstcount); \
         configure_and_push_response_to_slave_``SLAVE_ID(rsp); \
         read_response_queue_slave[``SLAVE_ID].push_back(rsp); \
      end \
   end
   
`define MACRO_GET_COMMAND_FROM_SLAVE(SLAVE_ID) \
   function automatic Command get_command_from_slave_``SLAVE_ID (); \
\
      Command cmd; \
\
      `SLAVE``SLAVE_ID.pop_command(); \
      cmd.burstcount          = `SLAVE``SLAVE_ID.get_command_burst_count(); \
      cmd.addr                = `SLAVE``SLAVE_ID.get_command_address(); \
\
      if (`SLAVE``SLAVE_ID.get_command_request() == REQ_WRITE) begin \
         cmd.trans = WRITE; \
         for(int i = 0; i < cmd.burstcount; i++) begin \
            cmd.data[i]       =`SLAVE``SLAVE_ID.get_command_data(i); \
            cmd.byteenable[i] =`SLAVE``SLAVE_ID.get_command_byte_enable(i); \
         end \
      end else begin \
         cmd.trans = READ; \
      end \
\
      return cmd; \
   endfunction
   
`define MACRO_PENDING_READ_CYCLES(SLAVE_ID) \
   int pending_read_cycles_slave_``SLAVE_ID = 0; \
   always @(posedge `TB.clk_clk) begin \
      if (pending_read_cycles_slave_``SLAVE_ID > 0) begin \
         pending_read_cycles_slave_``SLAVE_ID--; \
      end \
   end

`define MACRO_CONFIGURE_AND_PUSH_RESPONSE_TO_SLAVE(SLAVE_ID) \
task automatic configure_and_push_response_to_slave_``SLAVE_ID ( \
      Response rsp \
   ); \
\
      int read_response_latency; \
\
      `SLAVE``SLAVE_ID.set_response_request(REQ_READ); \
      `SLAVE``SLAVE_ID.set_response_burst_size(rsp.burstcount); \
      for (int i = 0; i < rsp.burstcount; i++) begin \
         `SLAVE``SLAVE_ID.set_response_data(rsp.data[i], i); \
\
         if (i == 0) begin \
            `SLAVE``SLAVE_ID.set_response_latency(rsp.latency[i] + pending_read_cycles_slave_``SLAVE_ID, i); \
            read_response_latency = rsp.latency[i]; \
         end else begin \
            `SLAVE``SLAVE_ID.set_response_latency(rsp.latency[i], i); \
            read_response_latency = rsp.latency[i] + read_response_latency; \
         end \
\
      end \
      `SLAVE``SLAVE_ID.push_response(); \
      pending_read_cycles_slave_``SLAVE_ID = pending_read_cycles_slave_``SLAVE_ID + read_response_latency + rsp.burstcount + 2; \
   endtask

`define MACRO_MASTER_RESPONSE_THREAD(MASTER_ID) \
   // Get read response received by master and verify read response \
   always @(`MASTER``MASTER_ID.signal_read_response_complete) begin \
\
      Command  cmd; \
      Response actual_rsp, exp_rsp; \
\
      cmd = read_command_queue_master[``MASTER_ID].pop_front(); \
      actual_rsp = get_read_response_from_master_``MASTER_ID(); \
      exp_rsp = get_expected_read_response(cmd); \
      verify_response(actual_rsp, exp_rsp); \
   end \
\
   // Flush out response for write command created by master bfm \
   always @(`MASTER``MASTER_ID.signal_write_response_complete) begin \
      `MASTER``MASTER_ID.pop_response(); \
   end

`define MACRO_GET_READ_RESPONSE_FROM_MASTER(MASTER_ID) \
   function automatic Response get_read_response_from_master_``MASTER_ID (); \
\
      Response rsp; \
\
      `MASTER``MASTER_ID.pop_response(); \
      rsp.burstcount    = `MASTER``MASTER_ID.get_response_burst_size(); \
      for (int i = 0; i < rsp.burstcount; i++) begin \
         rsp.data[i]    = `MASTER``MASTER_ID.get_response_data(i); \
      end \
\
      return rsp; \
   endfunction

//---------------------------------------------------
// Macro Instantiations
//---------------------------------------------------
// master 0
//`MACRO_CONFIGURE_AND_PUSH_COMMAND_TO_MASTER(0)
//`MACRO_MASTER_RESPONSE_THREAD(0)
//`MACRO_GET_READ_RESPONSE_FROM_MASTER(0)

// slave 0
`MACRO_SLAVE_THREAD(0)
`MACRO_GET_COMMAND_FROM_SLAVE(0)
`MACRO_PENDING_READ_CYCLES(0)
`MACRO_CONFIGURE_AND_PUSH_RESPONSE_TO_SLAVE(0)

//---------------------------------------------------
// Test status checking
//---------------------------------------------------
bit test_success = 1;

//---------------------------------------------------
// Events
//---------------------------------------------------
event assert_fail;

//---------------------------------------------------
// Test program
//---------------------------------------------------
   // master test program
   initial begin
      set_verbosity(VERBOSITY_INFO);
      $display("Starting master test program");

      wait (`TB.reset_reset_n == 1);
      //$display("Master sending out non bursting write commands");
      //master_send_commands(10, WRITE, NOBURST);
      //
      //$display("Master sending out non bursting read commands");
      //master_send_commands(10, READ, NOBURST);
      //
      //$display("Master sending out burst write commands");
      //master_send_commands(10, WRITE, BURST);
      //
      //$display("Master sending out burst read commands");
      //master_send_commands(10, READ, BURST);
      //
      //$display("Master has sent out all commands");
   end
   
   task automatic master_send_commands (
      int            num_command,
      Transaction    trans,
      Burstmode      burstmode
   );

      Command     cmd;
      Response    rsp, exp_rsp;
      int         master_id, slave_id;
      
      master_id   = 0;
      slave_id    = 0;
      
      for (int i = 0; i < num_command; i++) begin
         
         cmd = create_command (
            .trans(trans),
            .burstmode(burstmode),
            .slave_id(slave_id)
         );
         queue_command(cmd, master_id, slave_id);
      end

   endtask
   
   function automatic Command create_command (
      Transaction    trans,
      Burstmode      burstmode,
      int            slave_id
   );

      Command cmd;
      
      if (burstmode == BURST) begin
         cmd.burstcount             = randomize_burstcount();
      end else begin
         cmd.burstcount             = 1;
      end
      
      cmd.trans                  = trans;
      cmd.addr                   = generate_random_aligned_address(slave_id);
      cmd.cmd_delay              = $urandom_range(0, MAX_COMMAND_IDLE);
      
      if (trans == WRITE) begin
         for (int i = 0; i < cmd.burstcount; i++) begin
            cmd.data[i]          = $random;
            cmd.byteenable[i]    = {NUM_SYMBOLS{1'b1}};
            cmd.data_idles[i]    = $urandom_range(0, MAX_DATA_IDLE);
         end
      end else begin
         cmd.data_idles[0]       = $urandom_range(0, MAX_DATA_IDLE);
      end
      
      return cmd;
   endfunction
   
   function automatic Burstcount randomize_burstcount ();
      
      Burstcount burstcount;
      
      burstcount = $urandom_range(1, MAX_BURST);
      return burstcount;
   endfunction
   
   function automatic logic [ADDR_W-1: 0] generate_random_aligned_address (
      int slave_id
   );
      logic [ADDR_W-1:0] base_addr, addr;
      
      base_addr = slave_id * SLAVE_SPAN;
      addr = base_addr + ($random % SLAVE_SPAN);
      
      return (addr / NUM_SYMBOLS) * NUM_SYMBOLS;
   endfunction
   
   task automatic queue_command (
      Command  cmd,
      int      master_id,
      int      slave_id
   );
      
      //save_command_master(cmd, master_id);
      save_command_slave(cmd, slave_id);
      //configure_and_push_command_to_master(cmd, master_id);
   endtask
   
//   task automatic save_command_master( 
//      Command  cmd,
//      int      master_id
//   );
//
//         if (cmd.trans == WRITE) begin
//            write_command_queue_master[master_id].push_back(cmd);
//         end else begin
//            read_command_queue_master[master_id].push_back(cmd);
//         end
//
//   endtask
   
   task automatic save_command_slave(
      Command  cmd,
      int      slave_id
   );
   
      if (cmd.trans == WRITE) begin
         write_command_queue_slave[slave_id].push_back(cmd);
      end else begin
         read_command_queue_slave[slave_id].push_back(cmd);
      end
   
   endtask
   
//   task automatic configure_and_push_command_to_master (
//      Command  cmd,
//      int      master_id
//   );
//      if (master_id == 0) begin
//         configure_and_push_command_to_master_0(cmd);
//      end
//      
//   endtask
   
   function automatic Command get_expected_command_for_slave (
      Command cmd,
      int     slave_id
   );

      Command exp_cmd;
      int found = 0;

      if (cmd.trans == WRITE) begin
         foreach (write_command_queue_slave[slave_id, i]) begin
            exp_cmd = write_command_queue_slave[slave_id][i];
            if (exp_cmd.addr == cmd.addr) begin
               write_command_queue_slave[slave_id].delete(i);
               found = 1;
               break;
            end
         end
         if (found == 0) begin
            exp_cmd = write_command_queue_slave[slave_id].pop_front();
         end 
      end else begin
         foreach (read_command_queue_slave[slave_id, i]) begin
            exp_cmd = read_command_queue_slave[slave_id][i];
            if (exp_cmd.addr == cmd.addr) begin
               read_command_queue_slave[slave_id].delete(i);
               found = 1;
               break;
            end
         end
         if (found == 0) begin
            exp_cmd = read_command_queue_slave[slave_id].pop_front();
         end
      end

      return exp_cmd;
   endfunction
   
   task automatic verify_command (
      Command actual_cmd, exp_cmd
   );
   
      assert_equals("wrong address", exp_cmd.addr, actual_cmd.addr);
      assert_equals("wrong burstcount", exp_cmd.burstcount, actual_cmd.burstcount);
      
      if (actual_cmd.trans == WRITE) begin
         for (int i = 0; i < actual_cmd.burstcount; i++) begin
            assert_equals("wrong write data", exp_cmd.data[i], actual_cmd.data[i]);
            assert_equals("wrong byteenable", exp_cmd.byteenable[i], actual_cmd.byteenable[i]);
         end
      end
   
   endtask
   
   task automatic assert_equals(
      string message,
      logic [1023:0] expected_obj,
      logic [1023:0] actual_obj
   );
      string data_comparison_msg;

      begin
         if (actual_obj == expected_obj) begin
         // Success case.  Code it this way because in Verilog, 
         //   1) "!=" and "==" give 'x' if either operand contains 'x' or 'z'
         //   2) 'x' evaluated as a boolean is false
         end else begin
            $sformat(data_comparison_msg, "%s, expected %0x got %0x",
               message,
               expected_obj,
               actual_obj);
            print(VERBOSITY_ERROR, data_comparison_msg);
            test_success = 0;                
            -> assert_fail;
         end
      end
   endtask

   function automatic Response create_response (
      Burstcount burstcount
   );
   
      Response rsp;
      
      rsp.burstcount       = burstcount;
      for (int i = 0;i < burstcount; i++) begin
         rsp.data[i]       = $random;
         rsp.latency[i]    = $urandom_range(0, MAX_DATA_IDLE);
      end
      
      return rsp;
   endfunction
   
   function automatic Response get_expected_read_response (
      Command cmd
   );
      
      Response rsp;
      int      slave_id = cmd.addr / SLAVE_SPAN;
      
      rsp = read_response_queue_slave[slave_id].pop_front();
      
      return rsp;
   endfunction
   
   task automatic verify_response (
      Response actual_rsp, exp_rsp
   );
      
      assert_equals("wrong burstcount", exp_rsp.burstcount, actual_rsp.burstcount);
      for (int i = 0; i < actual_rsp.burstcount; i++) begin
         assert_equals("wrong read data", exp_rsp.data[i], actual_rsp.data[i]);
      end
      
   endtask
   
endmodule 
