// Copyright 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:
// - Andreas Kurth <akurth@iis.ee.ethz.ch>

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

/// Testbench for `axi_modify_address`
module tb_axi_modify_address #(
  // DUT Parameters
  parameter int unsigned AXI_SLV_PORT_ADDR_WIDTH = 32,
  parameter int unsigned AXI_MST_PORT_ADDR_WIDTH = 48,
  parameter int unsigned AXI_DATA_WIDTH = 64,
  parameter int unsigned AXI_ID_WIDTH = 3,
  parameter int unsigned AXI_USER_WIDTH = 2,
  // TB Parameters
  parameter time TCLK = 10ns,
  parameter time TA = TCLK * 1/4,
  parameter time TT = TCLK * 3/4,
  parameter int unsigned REQ_MIN_WAIT_CYCLES = 0,
  parameter int unsigned REQ_MAX_WAIT_CYCLES = 10,
  parameter int unsigned RESP_MIN_WAIT_CYCLES = 0,
  parameter int unsigned RESP_MAX_WAIT_CYCLES = REQ_MAX_WAIT_CYCLES/2,
  parameter int unsigned N_TXNS = 1000
);

  localparam int unsigned N_RD_TXNS = N_TXNS / 2;
  localparam int unsigned N_WR_TXNS = N_TXNS / 2;

  // Clock and Reset
  logic clk,
        rst_n;
  clk_rst_gen #(
    .ClkPeriod     (TCLK),
    .RstClkCycles  (5)
  ) i_clk_rst_gen (
    .clk_o  (clk),
    .rst_no (rst_n)
  );

  // AXI Interfaces
  AXI_BUS_DV #(
    .AXI_ADDR_WIDTH (AXI_SLV_PORT_ADDR_WIDTH),
    .AXI_DATA_WIDTH (AXI_DATA_WIDTH),
    .AXI_ID_WIDTH   (AXI_ID_WIDTH),
    .AXI_USER_WIDTH (AXI_USER_WIDTH)
  ) upstream_dv (
    .clk_i  (clk)
  );
  AXI_BUS #(
    .AXI_ADDR_WIDTH (AXI_SLV_PORT_ADDR_WIDTH),
    .AXI_DATA_WIDTH (AXI_DATA_WIDTH),
    .AXI_ID_WIDTH   (AXI_ID_WIDTH),
    .AXI_USER_WIDTH (AXI_USER_WIDTH)
  ) upstream ();
  `AXI_ASSIGN(upstream, upstream_dv)
  AXI_BUS_DV #(
    .AXI_ADDR_WIDTH (AXI_MST_PORT_ADDR_WIDTH),
    .AXI_DATA_WIDTH (AXI_DATA_WIDTH),
    .AXI_ID_WIDTH   (AXI_ID_WIDTH),
    .AXI_USER_WIDTH (AXI_USER_WIDTH)
  ) downstream_dv (
    .clk_i  (clk)
  );
  AXI_BUS #(
    .AXI_ADDR_WIDTH (AXI_MST_PORT_ADDR_WIDTH),
    .AXI_DATA_WIDTH (AXI_DATA_WIDTH),
    .AXI_ID_WIDTH   (AXI_ID_WIDTH),
    .AXI_USER_WIDTH (AXI_USER_WIDTH)
  ) downstream ();
  `AXI_ASSIGN(downstream_dv, downstream)

  // Types
  typedef logic [AXI_MST_PORT_ADDR_WIDTH-1:0]   addr_t;
  typedef logic [AXI_DATA_WIDTH-1:0]            data_t;
  typedef logic [AXI_ID_WIDTH-1:0]              id_t;
  typedef logic [AXI_MST_PORT_ADDR_WIDTH-13:0]  page_t;
  typedef logic [AXI_DATA_WIDTH/8-1:0]          strb_t;
  typedef logic [AXI_USER_WIDTH-1:0]            user_t;
  `AXI_TYPEDEF_AW_CHAN_T(aw_t, addr_t, id_t, user_t)
  `AXI_TYPEDEF_W_CHAN_T(w_t, data_t, strb_t, user_t)
  `AXI_TYPEDEF_B_CHAN_T(b_t, id_t, user_t)
  `AXI_TYPEDEF_AR_CHAN_T(ar_t, addr_t, id_t, user_t)
  `AXI_TYPEDEF_R_CHAN_T(r_t, data_t, id_t, user_t)

  // DUT
  addr_t  mst_aw_addr,
          mst_ar_addr;
  axi_modify_address_intf #(
    .AXI_SLV_PORT_ADDR_WIDTH  (AXI_SLV_PORT_ADDR_WIDTH),
    .AXI_MST_PORT_ADDR_WIDTH  (AXI_MST_PORT_ADDR_WIDTH),
    .AXI_DATA_WIDTH           (AXI_DATA_WIDTH),
    .AXI_ID_WIDTH             (AXI_ID_WIDTH),
    .AXI_USER_WIDTH           (AXI_USER_WIDTH)
  ) i_dut (
    .slv            (upstream),
    .mst_aw_addr_i  (mst_aw_addr),
    .mst_ar_addr_i  (mst_ar_addr),
    .mst            (downstream)
  );

  // Test harness master
  typedef axi_test::axi_rand_master #(
    .AW                   (AXI_SLV_PORT_ADDR_WIDTH),
    .DW                   (AXI_DATA_WIDTH),
    .IW                   (AXI_ID_WIDTH),
    .UW                   (AXI_USER_WIDTH),
    .TA                   (TA),
    .TT                   (TT),
    .MAX_READ_TXNS        (N_TXNS),
    .MAX_WRITE_TXNS       (N_TXNS),
    .AX_MIN_WAIT_CYCLES   (REQ_MIN_WAIT_CYCLES),
    .AX_MAX_WAIT_CYCLES   (REQ_MAX_WAIT_CYCLES),
    .W_MIN_WAIT_CYCLES    (REQ_MIN_WAIT_CYCLES),
    .W_MAX_WAIT_CYCLES    (REQ_MAX_WAIT_CYCLES),
    .RESP_MIN_WAIT_CYCLES (RESP_MIN_WAIT_CYCLES),
    .RESP_MAX_WAIT_CYCLES (RESP_MAX_WAIT_CYCLES),
    .AXI_MAX_BURST_LEN    (16)
  ) axi_master_t;
  axi_master_t axi_master = new(upstream_dv);
  initial begin
    wait (rst_n);
    axi_master.run(N_RD_TXNS, N_WR_TXNS);
    #(10*TCLK);
    $finish();
  end

  // Test harness slave
  typedef axi_test::axi_rand_slave #(
    .AW                   (AXI_MST_PORT_ADDR_WIDTH),
    .DW                   (AXI_DATA_WIDTH),
    .IW                   (AXI_ID_WIDTH),
    .UW                   (AXI_USER_WIDTH),
    .TA                   (TA),
    .TT                   (TT),
    .AX_MIN_WAIT_CYCLES   (RESP_MIN_WAIT_CYCLES),
    .AX_MAX_WAIT_CYCLES   (RESP_MAX_WAIT_CYCLES),
    .R_MIN_WAIT_CYCLES    (RESP_MIN_WAIT_CYCLES),
    .R_MAX_WAIT_CYCLES    (RESP_MAX_WAIT_CYCLES),
    .RESP_MIN_WAIT_CYCLES (RESP_MIN_WAIT_CYCLES),
    .RESP_MAX_WAIT_CYCLES (RESP_MAX_WAIT_CYCLES)
  ) axi_slave_t;
  axi_slave_t axi_slave = new(downstream_dv);
  initial begin
    wait (rst_n);
    axi_slave.run();
  end

  // Assign offset within page from upstream.
  assign mst_aw_addr[11:0] = upstream.aw_addr[11:0];
  assign mst_ar_addr[11:0] = upstream.ar_addr[11:0];

  // Randomize page number.
  page_t  mst_aw_page,
          mst_ar_page;
  assign mst_aw_addr[AXI_MST_PORT_ADDR_WIDTH-1:12] = mst_aw_page;
  assign mst_ar_addr[AXI_MST_PORT_ADDR_WIDTH-1:12] = mst_ar_page;
  initial begin
    logic rand_success;
    mst_aw_page = '0;
    mst_ar_page = '0;
    wait (rst_n);
    forever begin
      @(posedge clk);
      #TA;
      if (!(upstream.aw_valid && !upstream.aw_ready)) begin
        rand_success = std::randomize(mst_aw_page);
        assert(rand_success);
      end
      if (!(upstream.ar_valid && !upstream.ar_ready)) begin
        rand_success = std::randomize(mst_ar_page);
        assert(rand_success);
      end
    end
  end

  // Signals for expected and actual responses
  aw_t  aw_exp, aw_act;
  w_t   w_exp,  w_act;
  b_t   b_exp,  b_act;
  ar_t  ar_exp, ar_act;
  r_t   r_exp,  r_act;

  // Compute expected responses.
  always_comb begin
    `AXI_SET_TO_AW(aw_exp, upstream)
    aw_exp.addr = mst_aw_addr;
    `AXI_SET_TO_AR(ar_exp, upstream)
    ar_exp.addr = mst_ar_addr;
  end
  `AXI_ASSIGN_TO_W(w_exp, upstream)
  `AXI_ASSIGN_TO_B(b_exp, downstream)
  `AXI_ASSIGN_TO_R(r_exp, downstream)

  // Determine actual responses.
  `AXI_ASSIGN_TO_AW(aw_act, downstream)
  `AXI_ASSIGN_TO_W(w_act, downstream)
  `AXI_ASSIGN_TO_B(b_act, upstream)
  `AXI_ASSIGN_TO_AR(ar_act, downstream)
  `AXI_ASSIGN_TO_R(r_act, upstream)

  // Assert that actual responses match expected responses.
  default disable iff (~rst_n);
  aw: assert property(@(posedge clk)
    downstream.aw_valid |-> aw_act == aw_exp
  ) else $error("AW %p != %p!", aw_act, aw_exp);
  w: assert property(@(posedge clk)
    downstream.w_valid |-> w_act == w_exp
  ) else $error("W %p != %p!", w_act, w_exp);
  b: assert property(@(posedge clk)
    upstream.b_valid |-> b_act == b_exp
  ) else $error("B %p != %p!", b_act, b_exp);
  ar: assert property(@(posedge clk)
    downstream.ar_valid |-> ar_act == ar_exp
  ) else $error("AR %p != %p!", ar_act, ar_exp);
  r: assert property(@(posedge clk)
    upstream.r_valid |-> r_act == r_exp
  ) else $error("R %p != %p!", r_act, r_exp);

endmodule