// 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. // // Author: Wolfgang Roenninger <wroennin@ethz.ch>, ETH Zurich // Description: Testbench for the functional `*_sram` modules module tb_tc_sram #( parameter int unsigned NumPorts = 32'd2, parameter int unsigned Latency = 32'd1, parameter int unsigned NumWords = 32'd1024, parameter int unsigned DataWidth = 32'd64, parameter int unsigned ByteWidth = 32'd8, parameter int unsigned NoReq = 32'd200000, parameter string SimInit = "zeros", parameter time CyclTime = 10ns, parameter time ApplTime = 2ns, parameter time TestTime = 8ns ); //----------------------------------- // Clock generator //----------------------------------- logic clk, rst_n; clk_rst_gen #( .ClkPeriod ( CyclTime ), .RstClkCycles( 5 ) ) i_clk_gen ( .clk_o ( clk ), .rst_no ( rst_n ) ); logic [NumPorts-1:0] done; localparam int unsigned AddrWidth = (NumWords > 32'd1) ? $clog2(NumWords) : 32'd1; localparam int unsigned BeWidth = (DataWidth + ByteWidth - 32'd1) / ByteWidth; typedef logic [AddrWidth-1:0] addr_t; typedef logic [DataWidth-1:0] data_t; typedef logic [BeWidth-1:0] be_t; // signal declarations for each sram logic [NumPorts-1:0] req, we; addr_t [NumPorts-1:0] addr; data_t [NumPorts-1:0] wdata, rdata; be_t [NumPorts-1:0] be; // golden model data_t memory [NumWords-1:0]; longint unsigned failed_test; // This process drives the requests on the port with random data. for (genvar i = 0; i < NumPorts; i++) begin : gen_stimuli initial begin : proc_drive_port automatic logic stim_write; automatic addr_t stim_addr; automatic data_t stim_data; automatic be_t stim_be; done[i] <= 1'b0; req[i] <= 1'b0; we[i] <= 1'b0; addr[i] <= addr_t'(0); wdata[i] <= data_t'(0); be[i] <= be_t'(0); @(posedge rst_n); repeat (10) @(posedge clk); for (int unsigned j = 0; j < NoReq; j++) begin stim_write = bit'($urandom()); for (int unsigned k = 0; k < AddrWidth; k++) begin stim_addr[k] = bit'($urandom()); end // this statement makes sure that only valid addresses are in a request while (stim_addr >= NumWords) begin for (int unsigned k = 0; k < AddrWidth; k++) begin stim_addr[k] = bit'($urandom()); end end for (int unsigned k = 0; k < DataWidth; k++) begin stim_data[k] = bit'($urandom()); end for (int unsigned k = 0; k < BeWidth; k++) begin stim_be[k] = bit'($urandom()); end req[i] <= #ApplTime 1'b1; we[i] <= #ApplTime stim_write; addr[i] <= #ApplTime stim_addr; wdata[i] <= #ApplTime stim_data; be[i] <= #ApplTime stim_be; @(posedge clk); req[i] <= #ApplTime 1'b0; we[i] <= #ApplTime 1'b0; addr[i] <= #ApplTime addr_t'(0); wdata[i] <= #ApplTime data_t'(0); be[i] <= #ApplTime be_t'(0); repeat ($urandom_range(0,5)) @(posedge clk); end done[i] <= 1'b1; end end // This process controls the golden model // - The memory array is initialized according to the parameter // - Data is written exactly at the clock edge, if there is a write request on a port. // - At `TestTime` a process is launched on read requests which lives for `Latency` cycles. // This process asserts the expected read output at `TestTime` in the respective cycle. initial begin: proc_golden_model failed_test = 0; for (int unsigned i = 0; i < NumWords; i++) begin for (int unsigned j = 0; j < DataWidth; j++) begin case (SimInit) "zeros": memory[i][j] = 1'b0; "ones": memory[i][j] = 1'b1; default: memory[i][j] = 1'bx; endcase end end @(posedge rst_n); forever begin @(posedge clk); // writes get latched at clock in golden model array for (int unsigned i = 0; i < NumPorts; i++) begin if (req[i] && we[i]) begin for (int unsigned j = 0; j < DataWidth; j++) begin if (be[i][j/ByteWidth]) begin memory[addr[i]][j] = wdata[i][j]; end end end end // read test process is launched at `TestTime` #TestTime; fork for (int unsigned i = 0; i < NumPorts; i++) begin check_read(i, addr[i]); end join_none end end // Read test process. This task lives for a number of cycles determined by `Latency`. task automatic check_read(input int unsigned port, input addr_t read_addr); // only continue if there is a read request at this port if (req[port] && !we[port]) begin data_t exp_data = memory[read_addr]; if (Latency > 0) begin repeat (Latency) @(posedge clk); #TestTime; end for (int unsigned i = 0; i < DataWidth; i++) begin if (!$isunknown(exp_data[i])) begin assert(exp_data[i] === rdata[port][i]) else begin $warning("Port: %0d unexpected bit[%0h], Addr: %0h expected: %0h, measured: %0h", port, i, read_addr, exp_data[i], rdata[port][i]); failed_test++; end end end end endtask : check_read // Stop the simulation at the end. initial begin : proc_stop @(posedge rst_n); wait (&done); repeat (10) @(posedge clk); $info("Simulation done, errors: %0d", failed_test); $stop(); end tc_sram #( .NumWords ( NumWords ), // Number of Words in data array .DataWidth ( DataWidth ), // Data signal width .ByteWidth ( ByteWidth ), // Width of a data byte .NumPorts ( NumPorts ), // Number of read and write ports .Latency ( Latency ), // Latency when the read data is available .SimInit ( SimInit ), // Simulation initialization .PrintSimCfg ( 1'b1 ) // Print configuration ) i_tc_sram_dut ( .clk_i ( clk ), // Clock .rst_ni ( rst_n ), // Asynchronous reset active low .req_i ( req ), // request .we_i ( we ), // write enable .addr_i ( addr ), // request address .wdata_i ( wdata ), // write data .be_i ( be ), // write byte enable .rdata_o ( rdata ) // read data ); endmodule