// Copyright 2020 ETH Zurich and University of Bologna.
// Solderpad Hardware License, Version 0.51, see LICENSE for details.
// SPDX-License-Identifier: SHL-0.51

// Author: Florian Zaruba <zarubaf@iis.ee.ethz.ch>

`include "reqrsp_interface/assign.svh"

/// Testbench for `reqrsp_demux`. Random drivers on the slave and master ports
/// drive a random pattern. The monitors track all packets and the scoreboard
/// checks the integrity of the schedule and data. We test the demux on a fixed,
/// address-based routing scheme. That makes it deterministic which port should
/// have received the corresponding datum and we can use the scoreboard to track
/// this.
module reqrsp_demux_tb import reqrsp_pkg::*; #(
  parameter int unsigned AW = 32,
  parameter int unsigned DW = 32,
  parameter int unsigned NrPorts = 4,
  parameter int unsigned RespDepth = 2,
  parameter int unsigned NrRandomTransactions = 1000
);
  localparam time ClkPeriod = 10ns;
  localparam time ApplTime =  2ns;
  localparam time TestTime =  8ns;
  localparam int unsigned SelectWidth = cf_math_pkg::idx_width(NrPorts);
  typedef logic [SelectWidth-1:0] select_t;
  logic  clk, rst_n;
  select_t slv_select;

  REQRSP_BUS #(
    .ADDR_WIDTH ( AW ),
    .DATA_WIDTH ( DW )
  ) master ();

  REQRSP_BUS_DV #(
    .ADDR_WIDTH ( AW ),
    .DATA_WIDTH ( DW )
  ) master_dv (clk);

  REQRSP_BUS #(
    .ADDR_WIDTH ( AW ),
    .DATA_WIDTH ( DW )
  ) slave [NrPorts]();

  REQRSP_BUS_DV #(
    .ADDR_WIDTH ( AW ),
    .DATA_WIDTH ( DW )
  ) slave_dv [NrPorts] (clk);

  reqrsp_demux_intf #(
    .NrPorts (NrPorts),
    .AddrWidth (AW),
    .DataWidth (DW),
    .RespDepth (RespDepth)
  ) dut (
    .clk_i (clk),
    .rst_ni (rst_n),
    .slv_select_i (slv_select),
    .slv (master),
    .mst (slave)
  );

  assign slv_select = master.q_addr % NrPorts;

  `REQRSP_ASSIGN(master, master_dv)
  for (genvar i = 0; i < NrPorts; i++) begin : gen_if_assignment
    `REQRSP_ASSIGN(slave_dv[i], slave[i])
  end

  // ----------------
  // Clock generation
  // ----------------
  initial begin
    rst_n = 0;
    repeat (3) begin
      #(ClkPeriod/2) clk = 0;
      #(ClkPeriod/2) clk = 1;
    end
    rst_n = 1;
    forever begin
      #(ClkPeriod/2) clk = 0;
      #(ClkPeriod/2) clk = 1;
    end
  end

  // -------
  // Monitor
  // -------
  typedef reqrsp_test::reqrsp_monitor #(
    // Reqrsp bus interface paramaters;
    .AW ( AW ),
    .DW ( DW ),
    // Stimuli application and test time
    .TA ( ApplTime ),
    .TT ( TestTime )
  ) reqrsp_monitor_t;

  reqrsp_monitor_t reqrsp_mst_monitor = new (master_dv);
  // Reqrsp Monitor.
  initial begin
    @(posedge rst_n);
    reqrsp_mst_monitor.monitor();
  end

  reqrsp_monitor_t reqrsp_slv_monitor [NrPorts];
  for (genvar i = 0; i < NrPorts; i++) begin : gen_mst_mon
    initial begin
      reqrsp_slv_monitor[i] = new (slave_dv[i]);
      @(posedge rst_n);
      reqrsp_slv_monitor[i].monitor();
    end
  end

  // ------
  // Driver
  // ------
  typedef reqrsp_test::rand_reqrsp_slave #(
    // Reqrsp bus interface paramaters;
    .AW ( AW ),
    .DW ( DW ),
    // Stimuli application and test time
    .TA ( ApplTime ),
    .TT ( TestTime )
  ) reqrsp_rand_slave_t;

  reqrsp_rand_slave_t rand_reqrsp_slave [NrPorts];
  for (genvar i = 0; i < NrPorts; i++) begin : gen_slv_driver
    initial begin
      rand_reqrsp_slave[i] = new (slave_dv[i]);
      rand_reqrsp_slave[i].reset();
      @(posedge rst_n);
      rand_reqrsp_slave[i].run();
    end
  end

  typedef reqrsp_test::rand_reqrsp_master #(
    // Reqrsp bus interface paramaters;
    .AW ( AW ),
    .DW ( DW ),
    // Stimuli application and test time
    .TA ( ApplTime ),
    .TT ( TestTime )
  ) reqrsp_rand_master_t;

  reqrsp_rand_master_t rand_reqrsp_master = new (master_dv);

  // Reqrsp master.
  initial begin
    rand_reqrsp_master.reset();
    @(posedge rst_n);
    rand_reqrsp_master.run(NrRandomTransactions);
    // Wait until all transactions have ceased.
    repeat(1000) @(posedge clk);
    $finish;
  end

  // ----------
  // Scoreboard
  // ----------
  initial begin
    forever begin
        automatic reqrsp_test::req_t req;
        automatic reqrsp_test::req_t req_slv;
        automatic reqrsp_test::rsp_t rsp;
        automatic reqrsp_test::rsp_t rsp_slv;
        automatic select_t select;
        reqrsp_mst_monitor.req_mbx.get(req);
        reqrsp_mst_monitor.rsp_mbx.get(rsp);
        // Check that for each master transaction we see a slave transaction on
        // the right port.
        // The slave select in the testbench is steered based on the
        // (randomized) address.
        select = req.addr % NrPorts;
        reqrsp_slv_monitor[select].req_mbx.get(req_slv);
        reqrsp_slv_monitor[select].rsp_mbx.get(rsp_slv);
        assert(req_slv.do_compare(req));
        assert(rsp_slv.do_compare(rsp));
    end
  end

  // Check that we have associated all transactions.
  final begin
    assert(reqrsp_mst_monitor.req_mbx.num() == 0);
    assert(reqrsp_mst_monitor.rsp_mbx.num() == 0);
    for (int i = 0; i < NrPorts; i++) begin
      assert(reqrsp_slv_monitor[i].req_mbx.num() == 0);
      assert(reqrsp_slv_monitor[i].rsp_mbx.num() == 0);
    end
    $display("Checked for non-empty mailboxes.");
  end

endmodule