module tb_axi_sim_mem #(
  // TB Parameters
  parameter time TbTclk = 10ns,
  // Module Parameters
  parameter int unsigned TbAddrWidth = 32'd64,
  parameter int unsigned TbDataWidth = 32'd128,
  parameter int unsigned TbIdWidth = 32'd6,
  parameter int unsigned TbUserWidth = 32'd2,
  parameter bit TbWarnUninitialized = 1'b0,
  parameter time TbApplDelay = 2ns,
  parameter time TbAcqDelay = 8ns
);

  logic clk,
        rst_n;
  clk_rst_gen #(
    .ClkPeriod    (TbTclk),
    .RstClkCycles (5)
  ) i_clk_rst_gen (
    .clk_o  (clk),
    .rst_no (rst_n)
  );

  localparam int unsigned StrbWidth = TbDataWidth / 8;
  typedef logic [TbAddrWidth-1:0] addr_t;
  typedef logic [TbDataWidth-1:0] data_t;
  typedef logic [TbIdWidth-1:0]   id_t;
  typedef logic [StrbWidth-1:0] strb_t;
  typedef logic [TbUserWidth-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)
  `AXI_TYPEDEF_REQ_T(req_t, aw_t, w_t, ar_t)
  `AXI_TYPEDEF_RESP_T(rsp_t, b_t, r_t)

  req_t req;
  rsp_t rsp;
  axi_sim_mem #(
    .AddrWidth          (TbAddrWidth),
    .DataWidth          (TbDataWidth),
    .IdWidth            (TbIdWidth),
    .UserWidth          (TbUserWidth),
    .req_t              (req_t),
    .rsp_t              (rsp_t),
    .WarnUninitialized  (TbWarnUninitialized),
    .ApplDelay          (TbApplDelay),
    .AcqDelay           (TbAcqDelay)
  ) i_sim_mem (
    .clk_i      (clk),
    .rst_ni     (rst_n),
    .axi_req_i  (req),
    .axi_rsp_o  (rsp)
  );

  AXI_BUS_DV #(
    .AXI_ADDR_WIDTH (TbAddrWidth),
    .AXI_DATA_WIDTH (TbDataWidth),
    .AXI_ID_WIDTH   (TbIdWidth),
    .AXI_USER_WIDTH (TbUserWidth)
  ) axi_dv (clk);
  `AXI_ASSIGN_TO_REQ(req, axi_dv)
  `AXI_ASSIGN_FROM_RESP(axi_dv, rsp)
  typedef axi_test::axi_driver #(
    .AW(TbAddrWidth), .DW(TbDataWidth), .IW(TbIdWidth), .UW(TbUserWidth),
    .TA(1ns), .TT(6ns)
  ) drv_t;
  drv_t drv = new(axi_dv);

  // Simply read and write a random memory region.
  initial begin
    automatic logic rand_success;
    automatic data_t exp_data[$];
    automatic drv_t::ax_beat_t aw_beat = new, ar_beat = new;
    automatic drv_t::w_beat_t w_beat = new;
    automatic drv_t::b_beat_t b_beat;
    automatic drv_t::r_beat_t r_beat;
    drv.reset_master();
    wait (rst_n);
    // AW
    rand_success = aw_beat.randomize(); assert(rand_success);
    aw_beat.ax_addr >>= $clog2(StrbWidth); // align address with data width
    aw_beat.ax_addr <<= $clog2(StrbWidth);
    aw_beat.ax_len = $urandom();
    aw_beat.ax_size = $clog2(StrbWidth);
    aw_beat.ax_burst = axi_pkg::BURST_INCR;
    drv.send_aw(aw_beat);
    // W beats
    for (int unsigned i = 0; i <= aw_beat.ax_len; i++) begin
      rand_success = w_beat.randomize(); assert(rand_success);
      w_beat.w_strb = '1;
      if (i == aw_beat.ax_len) begin
        w_beat.w_last = 1'b1;
      end
      drv.send_w(w_beat);
      exp_data.push_back(w_beat.w_data);
    end
    // B
    drv.recv_b(b_beat);
    assert(b_beat.b_resp == axi_pkg::RESP_OKAY);
    // AR
    ar_beat.ax_addr = aw_beat.ax_addr;
    ar_beat.ax_len = aw_beat.ax_len;
    ar_beat.ax_size = aw_beat.ax_size;
    ar_beat.ax_burst = aw_beat.ax_burst;
    drv.send_ar(ar_beat);
    // R beats
    for (int unsigned i = 0; i <= ar_beat.ax_len; i++) begin
      automatic data_t exp = exp_data.pop_front();
      drv.recv_r(r_beat);
      assert(r_beat.r_data == exp) else
        $error("Received 0x%h != expected 0x%h!", r_beat.r_data, exp);
    end
    // Done.
    #(TbTclk);
    $finish();
  end

endmodule