// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Testbench module for prim_fifo_sram_async

module prim_fifo_async_sram_adapter_tb #(
  parameter int unsigned Width = 32,
  parameter int unsigned Depth = 16,

  parameter int unsigned FpgaSram = 0 // Use FF based
) (
  input clk_wr_i,
  input clk_rd_i,

  input rst_ni,

  input                    wvalid_i,
  output logic             wready_o,
  input        [Width-1:0] wdata_i,

  output logic             rvalid_o,
  input                    rready_i,
  output logic [Width-1:0] rdata_o
);

  localparam int unsigned       SramAw = 7;
  localparam int unsigned       SramDw = 32;
  localparam logic [SramAw-1:0] SramBaseAddr = 'h 30;

  logic              w_sram_req;
  logic              w_sram_gnt;
  logic              w_sram_write;
  logic [SramAw-1:0] w_sram_addr;
  logic [SramDw-1:0] w_sram_wdata;
  logic [SramDw-1:0] w_sram_wmask;
  logic              w_sram_rvalid; // not used
  logic [SramDw-1:0] w_sram_rdata;  // not used
  logic [1:0]        w_sram_rerror; // not used

  logic              r_sram_req;
  logic              r_sram_gnt;
  logic              r_sram_write;
  logic [SramAw-1:0] r_sram_addr;
  logic [SramDw-1:0] r_sram_wdata; // not used
  logic [SramDw-1:0] r_sram_wmask; // not used
  logic              r_sram_rvalid;
  logic [SramDw-1:0] r_sram_rdata;
  logic [1:0]        r_sram_rerror;

  prim_fifo_async_sram_adapter #(
    .Width (Width),
    .Depth (Depth),

    .SramAw (SramAw), // TODO: random with constraint
    .SramDw (SramDw), // TODO: random with constraint

    .SramBaseAddr(SramBaseAddr) // TODO: random?
  ) dut (
    .clk_wr_i,
    .rst_wr_ni (rst_ni),
    .clk_rd_i,
    .rst_rd_ni (rst_ni),

    .wvalid_i,
    .wready_o,
    .wdata_i,

    .wdepth_o (),

    .rvalid_o,
    .rready_i,
    .rdata_o,

    .rdepth_o (),

    // Sram Interface
    .w_sram_req_o   (w_sram_req   ),
    .w_sram_gnt_i   (w_sram_gnt   ),
    .w_sram_write_o (w_sram_write ),
    .w_sram_addr_o  (w_sram_addr  ),
    .w_sram_wdata_o (w_sram_wdata ),
    .w_sram_wmask_o (w_sram_wmask ),
    .w_sram_rvalid_i(w_sram_rvalid), // not used
    .w_sram_rdata_i (w_sram_rdata ),  // not used
    .w_sram_rerror_i(w_sram_rerror), // not used

     // Read SRAM port
    .r_sram_req_o   (r_sram_req   ),
    .r_sram_gnt_i   (r_sram_gnt   ),
    .r_sram_write_o (r_sram_write ),
    .r_sram_addr_o  (r_sram_addr  ),
    .r_sram_wdata_o (r_sram_wdata ), // not used
    .r_sram_wmask_o (r_sram_wmask ), // not used
    .r_sram_rvalid_i(r_sram_rvalid),
    .r_sram_rdata_i (r_sram_rdata ),
    .r_sram_rerror_i(r_sram_rerror),

    .r_full_o(),

    .r_notempty_o(),

    .w_full_o()
  );

if (FpgaSram == 1) begin : g_sram_fpga
  // FPV looks like does not like the style of ram_2p.
  prim_ram_2p #(
    .Depth           (2**SramAw),
    .Width           (SramDw),
    .DataBitsPerMask (1)
  ) u_sram (
    .clk_a_i  (clk_wr_i),
    .clk_b_i  (clk_rd_i),

    .a_req_i   (w_sram_req   ),
    .a_write_i (w_sram_write ),
    .a_addr_i  (w_sram_addr  ),
    .a_wdata_i (w_sram_wdata ),
    .a_wmask_i (w_sram_wmask ),
    .a_rdata_o (w_sram_rdata ),

    .b_req_i   (r_sram_req   ),
    .b_write_i (r_sram_write ),
    .b_addr_i  (r_sram_addr  ),
    .b_wdata_i (r_sram_wdata ),
    .b_wmask_i (r_sram_wmask ),
    .b_rdata_o (r_sram_rdata ),

    .cfg_i     ('0),
    .cfg_o     ()
  );
end else begin : g_sram_ff
  logic [SramDw-1:0] mem [2**SramAw];

  always_ff @(posedge clk_wr_i) begin
    if (w_sram_req && w_sram_write) begin
      for (int unsigned i = 0; i < SramDw ; i++) begin
        if (w_sram_wmask[i]) mem[w_sram_addr][i] <= w_sram_wdata[i];
      end // for
    end // if
  end

  always_ff @(posedge clk_rd_i) begin
    if (r_sram_req && !r_sram_write) begin
      r_sram_rdata <= mem[r_sram_addr];
    end
  end
end // !FpgaSram

  assign w_sram_gnt = w_sram_req;

  always_ff @(posedge clk_wr_i) begin
    w_sram_rvalid <= w_sram_req && !w_sram_write;
  end

  assign w_sram_rerror = '0;

  assign r_sram_gnt = r_sram_req;

  always_ff @(posedge clk_rd_i) begin
    r_sram_rvalid <= r_sram_req && !r_sram_write;
  end

  assign r_sram_rerror = '0;

  `ASSUME_FPV(WdataBins_M,
              wvalid_i |-> wdata_i inside {
                32'h DEAD_BEEF, 32'h5A5A_A5A5, 32'h 1234_5678
                },
              clk_wr_i, !rst_ni)

`ifdef FPV_ON
  logic [Width-1:0] storage [Depth];
  logic [Width-1:0] rdata;
  logic [$clog2(Depth)-1:0] wptr, rptr;
  logic wack, rack;

  assign wack = wvalid_i && wready_o;
  assign rack = rvalid_o && rready_i;

  always_ff @(posedge clk_wr_i or negedge rst_ni) begin
    if (!rst_ni) wptr <= '0;
    else if (wack) wptr <= wptr+1;
  end
  always_ff @(posedge clk_rd_i or negedge rst_ni) begin
    if (!rst_ni) rptr <= '0;
    else if (rack) rptr <= rptr+1;
  end

  always_ff @(posedge clk_wr_i or negedge rst_ni) begin
    if (!rst_ni) storage <= '{default:'0};
    else if (wack) begin
      storage[wptr] <= wdata_i;
    end
  end

  assign rdata = storage[rptr];

  `ASSERT(DataIntegrityCheck_A,
          rack |-> rdata_o == rdata,
          clk_rd_i, !rst_ni)
`endif // FPV_ON

endmodule : prim_fifo_async_sram_adapter_tb
