// Copyright (c) 2020 ETH Zurich and University of Bologna.
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 0.51 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
// or agreed to in writing, software, hardware and materials distributed under
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//
// Authors:
// - Wolfgang Roenninger <wroennin@iis.ee.ethz.ch>
// - Fabian Schuiki <fschuiki@iis.ee.ethz.ch>
// - Andreas Kurth <akurth@iis.ee.ethz.ch>

// Directed Random Verification Testbench for `axi_lite_xbar`:  The crossbar is instantiated with
// a number of random axi master and slave modules. Each random master executes a fixed number of
// writes and reads over the whole addess map. All masters simultaneously issue transactions
// through the crossbar, thereby fully saturating all its bandwidth.

`include "axi/typedef.svh"
`include "axi/assign.svh"

module tb_axi_lite_xbar;
  // Dut parameters
  localparam int unsigned NoMasters   = 32'd6;    // How many Axi Masters there are
  localparam int unsigned NoSlaves    = 32'd8;    // How many Axi Slaves  there are
  // Random master no Transactions
  localparam int unsigned NoWrites   = 32'd10000;  // How many writes per master
  localparam int unsigned NoReads    = 32'd10000;  // How many reads per master
  // timing parameters
  localparam time CyclTime = 10ns;
  localparam time ApplTime =  2ns;
  localparam time TestTime =  8ns;
  // axi configuration
  localparam int unsigned AxiAddrWidth      =  32'd32;    // Axi Address Width
  localparam int unsigned AxiDataWidth      =  32'd64;    // Axi Data Width
  localparam int unsigned AxiStrbWidth      =  AxiDataWidth / 32'd8;
  // in the bench can change this variables which are set here freely
  localparam axi_pkg::xbar_cfg_t xbar_cfg = '{
    NoSlvPorts:         NoMasters,
    NoMstPorts:         NoSlaves,
    MaxMstTrans:        32'd10,
    MaxSlvTrans:        32'd6,
    FallThrough:        1'b0,
    LatencyMode:        axi_pkg::CUT_ALL_AX,
    AxiAddrWidth:       AxiAddrWidth,
    AxiDataWidth:       AxiDataWidth,
    NoAddrRules:        32'd8,
    default:            '0
  };
  typedef logic [AxiAddrWidth-1:0]      addr_t;
  typedef axi_pkg::xbar_rule_32_t       rule_t; // Has to be the same width as axi addr
  typedef logic [AxiDataWidth-1:0]      data_t;
  typedef logic [AxiStrbWidth-1:0]      strb_t;

  `AXI_LITE_TYPEDEF_AW_CHAN_T(aw_chan_lite_t, addr_t)
  `AXI_LITE_TYPEDEF_W_CHAN_T(w_chan_lite_t, data_t, strb_t)
  `AXI_LITE_TYPEDEF_B_CHAN_T(b_chan_lite_t)

  `AXI_LITE_TYPEDEF_AR_CHAN_T(ar_chan_lite_t, addr_t)
  `AXI_LITE_TYPEDEF_R_CHAN_T(r_chan_lite_t, data_t)

  `AXI_LITE_TYPEDEF_REQ_T(req_lite_t, aw_chan_lite_t, w_chan_lite_t, ar_chan_lite_t)
  `AXI_LITE_TYPEDEF_RESP_T(resp_lite_t, b_chan_lite_t, r_chan_lite_t)

  localparam rule_t [xbar_cfg.NoAddrRules-1:0] AddrMap = '{
    '{idx: 32'd7, start_addr: 32'h0001_0000, end_addr: 32'h0001_1000},
    '{idx: 32'd6, start_addr: 32'h0000_9000, end_addr: 32'h0001_0000},
    '{idx: 32'd5, start_addr: 32'h0000_8000, end_addr: 32'h0000_9000},
    '{idx: 32'd4, start_addr: 32'h0000_7000, end_addr: 32'h0000_8000},
    '{idx: 32'd3, start_addr: 32'h0000_6300, end_addr: 32'h0000_7000},
    '{idx: 32'd2, start_addr: 32'h0000_4000, end_addr: 32'h0000_6300},
    '{idx: 32'd1, start_addr: 32'h0000_3000, end_addr: 32'h0000_4000},
    '{idx: 32'd0, start_addr: 32'h0000_0000, end_addr: 32'h0000_3000}
  };

  typedef axi_test::axi_lite_rand_master #(
    // AXI interface parameters
    .AW ( AxiAddrWidth       ),
    .DW ( AxiDataWidth       ),
    // Stimuli application and test time
    .TA ( ApplTime           ),
    .TT ( TestTime           ),
    .MIN_ADDR ( 32'h0000_0000 ),
    .MAX_ADDR ( 32'h0001_3000 ),
    .MAX_READ_TXNS  ( 10 ),
    .MAX_WRITE_TXNS ( 10 )
  ) rand_lite_master_t;
  typedef axi_test::axi_lite_rand_slave #(
    // AXI interface parameters
    .AW ( AxiAddrWidth       ),
    .DW ( AxiDataWidth       ),
    // Stimuli application and test time
    .TA ( ApplTime           ),
    .TT ( TestTime           )
  ) rand_lite_slave_t;

  // -------------
  // DUT signals
  // -------------
  logic clk;
  // DUT signals
  logic rst_n;
  logic [NoMasters-1:0] end_of_sim;

  // master structs
  req_lite_t  [NoMasters-1:0] masters_req;
  resp_lite_t [NoMasters-1:0] masters_resp;

  // slave structs
  req_lite_t  [NoSlaves-1:0] slaves_req;
  resp_lite_t [NoSlaves-1:0] slaves_resp;

  // -------------------------------
  // AXI Interfaces
  // -------------------------------
  AXI_LITE #(
    .AXI_ADDR_WIDTH ( AxiAddrWidth      ),
    .AXI_DATA_WIDTH ( AxiDataWidth      )
  ) master [NoMasters-1:0] ();
  AXI_LITE_DV #(
    .AXI_ADDR_WIDTH ( AxiAddrWidth      ),
    .AXI_DATA_WIDTH ( AxiDataWidth      )
  ) master_dv [NoMasters-1:0] (clk);
  for (genvar i = 0; i < NoMasters; i++) begin : gen_conn_dv_masters
    `AXI_LITE_ASSIGN(master[i], master_dv[i])
    `AXI_LITE_ASSIGN_TO_REQ(masters_req[i], master[i])
    `AXI_LITE_ASSIGN_FROM_RESP(master[i], masters_resp[i])
  end

  AXI_LITE #(
    .AXI_ADDR_WIDTH ( AxiAddrWidth     ),
    .AXI_DATA_WIDTH ( AxiDataWidth     )
  ) slave [NoSlaves-1:0] ();
  AXI_LITE_DV #(
    .AXI_ADDR_WIDTH ( AxiAddrWidth     ),
    .AXI_DATA_WIDTH ( AxiDataWidth     )
  ) slave_dv [NoSlaves-1:0](clk);
  for (genvar i = 0; i < NoSlaves; i++) begin : gen_conn_dv_slaves
    `AXI_LITE_ASSIGN(slave_dv[i], slave[i])
    `AXI_LITE_ASSIGN_FROM_REQ(slave[i], slaves_req[i])
    `AXI_LITE_ASSIGN_TO_RESP(slaves_resp[i], slave[i])
  end
  // -------------------------------
  // AXI Rand Masters and Slaves
  // -------------------------------
  // Masters control simulation run time
  for (genvar i = 0; i < NoMasters; i++) begin : gen_rand_master
    initial begin : proc_generate_traffic
      automatic rand_lite_master_t lite_axi_master = new ( master_dv[i], $sformatf("MST_%0d", i));
      automatic data_t          data = '0;
      automatic axi_pkg::resp_t resp = '0;
      end_of_sim[i] <= 1'b0;
      lite_axi_master.reset();
      @(posedge rst_n);
      lite_axi_master.write(32'h0000_1100, axi_pkg::prot_t'('0), 64'hDEADBEEFDEADBEEF, 8'hFF, resp);
      lite_axi_master.read(32'h0000_e100, axi_pkg::prot_t'('0), data, resp);
      lite_axi_master.run(NoReads, NoWrites);
      end_of_sim[i] <= 1'b1;
    end
  end

  for (genvar i = 0; i < NoSlaves; i++) begin : gen_rand_slave
    initial begin : proc_recieve_traffic
      automatic rand_lite_slave_t lite_axi_slave = new( slave_dv[i] , $sformatf("SLV_%0d", i));
      lite_axi_slave.reset();
      @(posedge rst_n);
      lite_axi_slave.run();
    end
  end

  initial begin : proc_stop_sim
    wait (&end_of_sim);
    repeat (1000) @(posedge clk);
    $display("Simulation stopped as all Masters transferred their data, Success.",);
    $stop();
  end

  //-----------------------------------
  // Clock generator
  //-----------------------------------
  clk_rst_gen #(
    .ClkPeriod    ( CyclTime ),
    .RstClkCycles ( 5        )
  ) i_clk_gen (
    .clk_o (clk),
    .rst_no(rst_n)
  );

  //-----------------------------------
  // DUT
  //-----------------------------------
  axi_lite_xbar #(
    .Cfg       ( xbar_cfg       ),
    .aw_chan_t ( aw_chan_lite_t ),
    .w_chan_t  (  w_chan_lite_t ),
    .b_chan_t  (  b_chan_lite_t ),
    .ar_chan_t ( ar_chan_lite_t ),
    .r_chan_t  (  r_chan_lite_t ),
    .req_t     (  req_lite_t    ),
    .resp_t    ( resp_lite_t    ),
    .rule_t    ( rule_t         )
  ) i_xbar_dut (
    .clk_i      ( clk      ),
    .rst_ni     ( rst_n    ),
    .test_i     ( 1'b0     ),
    .slv_ports_req_i  ( masters_req  ),
    .slv_ports_resp_o ( masters_resp ),
    .mst_ports_req_o  ( slaves_req   ),
    .mst_ports_resp_i ( slaves_resp  ),
    .addr_map_i       ( AddrMap      ),
    .en_default_mst_port_i ( '0      ),
    .default_mst_port_i    ( '0      )
  );
endmodule