Verifying single port RAM using Verilog - memory

I've written a Verilog testbench for a single PORT SRAM with write operation at address i, and reading it successively at i-1 address. something like this below
task write_read;
integer i;
begin
for (i=1,i<=20,i=i+1) begin
write_mode(i,$urandom); // i= address, $urandom=data
read_mode(i-1); //i-1 address
end
end
endtask
PS: write_mode, read_mode are tasks that set wen, cs, and some scan mode pins along with delays.
I'm seeing correct read and write operations in the Verdi waveform visually. But, I want to verify data at the address i being written is the same as the data at the address i being read from the log files. If they don't match, it should display an ERROR.
I'm not sure how to implement this in the code. When I've hundreds of compilers, I can't go inside all the compiler paths, open their waveform files and check read, write operations manually.
I tried to store $urandom data for a particular address location, but it gets overwritten with each iterative cycle. I can use a function to return the $urandom value, but my environment contains delays, so I can't use functions.
In a nutshell, I'm looking for Verilog code help on memory verification without dumping waveforms.
can someone please help? please, Let me know if more details are needed
Thanks

Using the Kristen framework you get some test bench code generated which is available for reuse. Below is one file from that test bench called test_support.vh. The file contains functions for displaying errors and counting errors. I would recommend that you use === or !== when comparing memory locations as undefined signals can match inadvertently. At the end of the test, you call the display_test_final_status and that will create an overal test report for you in the log file. After your full regression completes you may now run a grep on the log files looking for ERROR and it will show anything that failed in a consistent mannor.
I have a ovearching Perl regression script that runs all of my tests and does the grep automagically afterwords and sends a success or fail email.
On a per test basis, you will need to set some defines to indicate the test name, and whether you want errors to halt the test immediately.
Good luck.
// -----------------------------------------------------------
// test_support.vh
// Generated file specifies which numerical test cases to run.
// Kristen Software License - Version 1.0 - January 1st, 2019
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
// GENERATED FILE - DO NOT MODIFY THIS FILE MANUALLY.
// -----------------------------------------------------------
logic[63:0] error_count = 0;
logic[63:0] lcl_error_count = 0;
logic bool_quick_mode = 0;
logic[511:0] support_test_passes;
logic[15:0] support_test_fails [0:511];
logic[511:0] support_test_was_run;
task test_init;
error_count = 0;
lcl_error_count = 0;
endtask
task display_test_begin_status;
begin
`ifdef QUICK_MODE
bool_quick_mode = 1;
`endif
$display("=========================================================================");
$display("| Test: %s QUICK_MODE = %s", `TEST_NAME_STR, bool_quick_mode ? "ON" : "OFF");
$display("| VERBOSE = %0d", `VERBOSE);
$display("=========================================================================");
end
endtask
task display_test_start;
input[31:0] test_id;
input string test_description;
begin
lcl_error_count = 0;
$display("=========================================================================");
$display("%0t: Test %0d : %s.", $time, test_id, test_description);
$display("=========================================================================");
end
endtask
task display_test_end;
input[31:0] test_id;
begin
$display("=========================================================================");
$display("%0t: Test %0d Complete with %0d ERRORS.", $time, test_id, lcl_error_count);
$display("=========================================================================");
support_test_was_run[test_id] = 1'b1;
if (lcl_error_count == 0)
support_test_passes[test_id] = 1'b1;
else
support_test_fails[test_id] = lcl_error_count;
end
endtask
task display_error_inc;
input string error_description;
begin
error_count++;
lcl_error_count++;
//$display("=========================================================================");
$display("%0t: ERROR: %s : error_count: %0d",$time, error_description, error_count );
//$display("=========================================================================");
`ifdef TEST_STOP_ON_ERROR
if (error_count >= `TEST_STOP_ON_ERROR_LIMIT) begin
$display("%0t, Stopping on error count = %d, %m", $time, error_count);
$finish();
end
`endif
end
endtask
task display_test_final_status;
//input string testname;
begin
$display("=========================================================================");
$display("%0t: Test %s %s with %0d ERRORS",$time, `TEST_NAME_STR, error_count > 0 ? "FAILS" : "PASSES",error_count);
$display("=========================================================================");
if (error_count !== 'h0)
begin
$display("Test failures:");
for (int err_fail_cnt = 0;err_fail_cnt < 512; err_fail_cnt = err_fail_cnt + 1)
begin
if (support_test_was_run[err_fail_cnt] == 1'b1 && support_test_passes[err_fail_cnt] != 1'b1)
begin
$display("Test %d, fails with %d errors", err_fail_cnt, support_test_fails[err_fail_cnt]);
end
end
end
end
endtask
task display_no_test_found;
input[31:0] test_id;
input string test_description;
begin
lcl_error_count = 0;
$display("=========================================================================");
$display("%0t: Test %0d : %s NOT FOUND SKIPPING.", $time, test_id, test_description);
$display("=========================================================================");
end
endtask
From the Kristen generated test bench, when a RAM unit is detected, this is the test that is run. I would treat this as an algorithmic solution, as you would have to generate several vector strings and additional functions that convert those strings to addresses.
xreg_v1_0_0_write_mpi_test is a task that writes to the RAM and xreg_v1_0_0_read_mpi_test is a task that reads from the RAM. There is a compare task xreg_v1_0_0_register_compare_with_error which obviously generates an error. I'll show those below as well.
// This is a casex snippet from the RAM test
7'd2: begin
// xreg_v1_0_0_mem_ack test
$display("%0t, xreg_v1_0_0_mem_ack test", $time);
oo_limit = get_entries_by_index(tc_index) > 256 ? 256 : get_entries_by_index(tc_index); //Max number of entries for this test
mm_step = get_entry_offset(tc_index);
nn_limit = perfect_by_datawidth(get_size_by_index(get_address_by_index(tc_index)),DATAPATH_WIDTH)/8; // Number of bytes for this entry
nn_step = DATAPATH_WIDTH == 8 ? 1 : DATAPATH_WIDTH == 16 ? 2 : DATAPATH_WIDTH == 32 ? 4 : 8;
for(oo = 0; oo < oo_limit; oo = oo + 1) // Move through each entry we will be testing
begin
for (nn = 0; nn < nn_limit; nn = nn + nn_step) // Walk through each access of a given entry
begin
test_address = get_address_by_index(tc_index) + (oo*mm_step) + nn;
expected = oo[7:0] ^ nn[7:0] ^ mm_step[7:0] ^ nn_step[7:0];
expected = {expected[6:0],expected[7],expected[2:0],expected[7:3],expected[5],expected[3],expected[1],expected[7],expected[0],expected[2],expected[6],expected[4],expected[7:0]};
xreg_v1_0_0_write_mpi_test(aclk, test_address, expected);
end
end
for(oo = 0; oo < oo_limit; oo = oo + 1) // Move through each entry we will be testing
begin
for (nn = 0; nn < nn_limit; nn = nn + nn_step) // Walk through each access of a given entry
begin
test_address = get_address_by_index(tc_index) + (oo*mm_step) + nn;
mask = get_entry_mask_by_index(tc_index);
expected = oo[7:0] ^ nn[7:0] ^ mm_step[7:0] ^ nn_step[7:0];
expected = {expected[6:0],expected[7],expected[2:0],expected[7:3],expected[5],expected[3],expected[1],expected[7],expected[0],expected[2],expected[6],expected[4],expected[7:0]} & (get_entry_mask_by_index(tc_index) >> (nn*8));
xreg_v1_0_0_read_mpi_test(aclk, test_address, result);
xreg_v1_0_0_register_compare_with_error(test_address,expected,result);
end
end
end
And here are some of the referenced tasks that show writes, reads and compares as implemented. Some of this is System Verilog I think, so you will have to treat it as algorithmic.
task automatic xreg_v1_0_0_read_mpi_test;
ref logic clock;
input [`TEST_ADDR_WIDTH-1:0] address;
output [`TEST_DATA_WIDTH-1:0] result;
begin
$display("ENTER >>> xreg_v1_0_0_read_mpi_test");
repeat(1) #(posedge clock);
`TEST_MPI_ADDR = address;
`TEST_MPI_RD_REQ = 1'b1;
`TEST_MPI_ENABLE = 1'b1;
while (!`TEST_MPI_ACK) repeat(1) #(posedge clock);
`TEST_MPI_ADDR = 'h0;
`TEST_MPI_RD_REQ = 1'b0;
`TEST_MPI_ENABLE = 1'b0;
result = `TEST_MPI_RD_DATA;
$display("WAITING ACK <<< xreg_v1_0_0_read_mpi_test");
while (`TEST_MPI_ACK) repeat(1) #(posedge clock);
$display("EXIT <<< xreg_v1_0_0_read_mpi_test");
end
endtask
task automatic xreg_v1_0_0_write_mpi_test;
ref logic clock;
input [`TEST_ADDR_WIDTH-1:0] address;
input [`TEST_DATA_WIDTH-1:0] data;
begin
$display("ENTER >>> xreg_v1_0_0_write_mpi_test");
repeat(1) #(posedge clock);
`TEST_MPI_ADDR = address;
`TEST_MPI_WR_DATA = data;
`TEST_MPI_WR_REQ = 1'b1;
`TEST_MPI_ENABLE = 1'b1;
while (!`TEST_MPI_ACK) repeat(1) #(posedge clock);
`TEST_MPI_ADDR = 'h0;
`TEST_MPI_WR_DATA = 'h0;
`TEST_MPI_WR_REQ = 1'b0;
`TEST_MPI_ENABLE = 1'b0;
$display("WAITING ACK <<< xreg_v1_0_0_write_mpi_test");
while (`TEST_MPI_ACK) repeat(1) #(posedge clock);
$display("EXIT <<< xreg_v1_0_0_write_mpi_test");
end
endtask
task automatic xreg_v1_0_0_register_compare_with_error;
input [`TEST_ADDR_WIDTH-1:0] address;
input [`TEST_DATA_WIDTH-1:0] expected;
input [`TEST_DATA_WIDTH-1:0] result;
begin
$display("ENTER >>> xreg_v1_0_0_register_compare_with_error");
if (expected !== result)
begin
$display("%0t, Address = 0x%X", $time, address);
$display("%0t, Expected 0x%X", $time, expected);
$display("%0t, Read 0x%X", $time, result);
display_error_inc("xreg_v1_0_0_rdconst_test: Mismatch between read and expected data.");
end
$display("EXIT <<< xreg_v1_0_0_register_compare_with_error");
end
endtask

You would want to first store the write data in a queue. Then when reading the SRAM pass the address to the queue and compare the queue_data_out and the sram_data_out. print ERROR if the comparison fails.
See pseudo code below.
task write_read;
integer i;
bit write_data_queue [$];
begin
for (i=1,i<=20,i=i+1) begin
write_mode(i,$urandom); // i= address, $urandom=data
write_data_queue.push_front($urandom);
read_mode(i-1); //i-1 address
queue_data = write_data_queue.pop_front();
if(queue_data != read_mode(i-1))
$display("ERROR'\n")
end
end
endtask

Related

Task does not update testbench sclk

I'm trying to understand why my signal is not updating when it is processed by the task.
As you could see below, the problem is related to the signal that internally on the task are changing correctly but even in a hierarchical call do not change the signal outside the task.
//-------------------------------
timeunit 1ps;
timeprecision 1ps;
`define CLK_HALF_PERIOD 10
`define SCK_HALF_PERIOD 30
module tbench ();
logic clk;
logic sclk;
logic RST;
hwpe_stream_intf_stream MOSI();
hwpe_stream_intf_stream MISO();
logic try;
initial begin
spi_send (.addr({1'b1,3'b111,12'd1,16'd0 }),
.data(1),
.MISO(try),
.MOSI(MOSI.data),
.SCK(sclk));
end
always
begin
# `CLK_HALF_PERIOD clk = 1;
# `CLK_HALF_PERIOD clk = 0;
end
task automatic spi_send (
input logic [31:0] addr,
input logic [31:0] data,
input logic MISO, // not used
ref logic MOSI,
ref logic SCK
);
integer i = 0;
$display ("add=%-32d",addr );
for (i=0; i<32; i=i+1) begin
//$display("add", 31-i , " MOSI ",MOSI);
// MOSI = ;
MOSI = addr[31-i];
tbench.try = MOSI;
#`SCK_HALF_PERIOD
tbench.sclk = 1'b1;
#`SCK_HALF_PERIOD;
tbench.sclk = 1'b0;
$display("add", addr[30-i] , " MOSI ",MOSI);
end
endtask
endmodule
tbench.sclk and MOSI are not changing globally, but only locally.
Here is the interface:
interface hwpe_stream_intf_stream() ;
logic valid;
logic ready;
logic data;
logic [8/8-1:0] strb;
modport source (
output valid, data, strb,
input ready
);
modport sink (
input valid, data, strb,
output ready
);
endinterface
You need to zoom in to the beginning of your waveforms to see sclk toggling. It toggles between 0 and 2000ps, then stops toggling.
You can add this to your testbench to stop the simulation much sooner to make it more obvious:
initial #3ns $finish;

Use of Fork-Join in Verilog

I would like to do a task that checks in parallel one of the two statements commented below and that doesn't break the execution of the program that follows it:
The Task checks if startTx has been generated. It must wait for startTx signal 5 clock cycles. If the startTx is asserted, it prints a successful message. If not (after the timeout), it prints an error message and increases the error counter.
What I've done so far is:
task checkStart;
begin
fork
// evento 1
begin
waitCycles(5); // timeOut
$display("[Error! %t] Time Out has been reached", $time);
errors = errors + 1;
end
// evento 2
begin
#(posedge(startTx))
$display("[Info] startTx has been generated successfully");
end
join
disable fork;
end
endtask
But doesn't seem to be working as it apparently checks for both statements to be true and I would like something as 'join_any' from SystemVerilog that breaks whenever one of both statements become true.
This works without using the SystemVerilog join_any. Comment startTx or uncomment it to see the two conditions that occur.
module tb ();
bit SIGNAL;
bit startTx;
int errors;
task checkStart;
fork
// evento 1
begin : timeout
#500; // timeOut
$display("[Error! %t] Time Out has been reached", $time);
errors = errors + 1;
$finish;
end
// evento 2
begin :wait_for_signal
#(posedge(startTx))
$display("[Info] startTx has been generated successfully");
disable timeout;
end
//
join
//
$display("+++++ Finished checkStart ++++++");
endtask
initial
begin
checkStart();
// Do something else
#20;
$display("**** normal finish ****");
$finish;
end
initial
begin
#1;
// coment one of these at a time
//startTx = 1;
startTx = 0;
end
endmodule

Getting a SimpleHellaCacheIF exception when making two memory accesses from the same instruction

I am trying to modify the accumulator generator example to load two accesses at once, for example to load two indices from one array. All I changed is that when the first memory response comes in, I increment a counter and send out another. But I get a cache exception. My code for the accelerator is below. It only has a few changes from the accumulator.
What’s confusing too me is that the first access works and I get the data, and for now, I’m using the same address with the second access. So why wouldn’t this work?
val regfile = Mem(4, UInt(width = 16.W))
val busy = Reg(init = Vec.fill(4){Bool(false)})
val cmd = Queue(io.cmd)
val funct = cmd.bits.inst.funct
val addr = cmd.bits.rs2(log2Up(4)-1,0)
val doLoad = funct === UInt(0)
val doRead = funct === UInt(1)
val doWrite = funct === UInt(2)
val doAccum = funct === UInt(3)
val memRespTag = io.mem.resp.bits.tag
// datapath
val addend = cmd.bits.rs1
val accum = regfile(addr)
val wdata = Mux(doWrite, addend, accum + addend)
val counter = RegInit(0.U)
when (cmd.fire() && (doWrite || doAccum)) {
regfile(addr) := wdata
}
when (io.mem.resp.valid) {
regfile(memRespTag) := io.mem.resp.bits.data
busy(memRespTag) := Bool(false)
when(counter === 0.U) {
counter := 1.U
}
}
// control
when (io.mem.req.fire()) {
busy(counter) := Bool(true)
}
val doResp = cmd.bits.inst.xd
val stallReg = busy(counter)
val stallLoad = doLoad && !io.mem.req.ready
val stallResp = doResp && !io.resp.ready
cmd.ready := !stallReg && !stallLoad && !stallResp // command resolved if no stalls AND not issuing a load that will need a request
// PROC RESPONSE INTERFACE
io.resp.valid := cmd.valid && doResp && !stallReg && !stallLoad
// valid response if valid command, need a response, and no stalls
io.resp.bits.rd := cmd.bits.inst.rd
// Must respond with the appropriate tag or undefined behavior
io.resp.bits.data := accum
// Semantics is to always send out prior accumulator register value
io.busy := cmd.valid || busy.reduce(_||_)
// Be busy when have pending memory requests or committed possibility of pending requests
io.interrupt := Bool(false)
// Set this true to trigger an interrupt on the processor (please refer to supervisor documentation)
io.mem.req.valid := ((cmd.valid && doLoad) || counter === 1.U ) && !stallReg && !stallResp
io.mem.req.bits.addr := addend
io.mem.req.bits.tag := addr
io.mem.req.bits.cmd := M_XRD // perform a load (M_XWR for stores)
io.mem.req.bits.size := log2Ceil(8).U
io.mem.req.bits.signed := Bool(false)
io.mem.req.bits.data := Bits(0) // we're not performing any stores...
io.mem.req.bits.phys := Bool(false)
I managed to read multiple locations from io.mem interface but still struggling with multiple writes.
Initially, i also faced same exception because i provided invalid memory address (no memory was existed with that address) at the time of io.mem request is triggered.
Here, in your case after toggling counter to value 1 from 0. It remains high. So there is no control from cmd.valid. In this case cmd.valid is important as value (addend) assigned to io.mem.req.bits.addr signal is only valid when cmd.valid is high. You can make interface independent of cmd.valid.You have to modify interface accordingly.
So, conclusion is,Sample valid address for io.mem.req interface.From my experience you can read as many locations as you want with single custom command.
Thanks & Regards,
Sanket,

Resources overused

Can anybody help me to optimize this code.I guess the nested looping is taking a lot of resources. The census_transform is not taking much resources but this correlation block is taking 158% of resources.How can I avoid the use of loops.
module correlation(clk,disp_map
);
input clk;
output reg [5:0] disp_map;
reg [7:0] d = 0;
wire [119:0] census_left;
wire [119:0] census_right;
reg [119:0] temp [0:63];
reg [119:0] hamming;
reg en_l = 1;
reg en_r = 0;
reg [6:0]prevSAD = 7'h7f;
reg [6:0] SAD = 0;
reg BestPosition = 0;
// integer count =0;
integer j =0;
integer i =0;
integer k =0;
integer p =0;
//----------------------------------------------------------------------//
/*always # (posedge clk)
count = count+1;*/
//----------------------------------------------------------------------//
always # (posedge clk)
begin
if(d==63)
en_r = 1;
else
d = d+1;
//-----------------------------------------------------------------------//
if(j<=16380)
begin
temp[0] <= census_left;
for(i=0;i<63;i=i+1)
begin
temp[i+1] <= temp[i];
end
j=j+1;
end
//------------------------------------------------------------------------//
if(en_r)
begin
for(k=0;k<63;k=k+1)
begin
hamming = census_right^temp[k];
for(p=0;p<=119;p=p+1)
SAD = SAD + hamming[p];
if(prevSAD > SAD)
begin
prevSAD = SAD;
BestPosition = k;
end
end
disp_map = BestPosition;
end
end
census_transform transform(clk,en_l,en_r,census_left,census_right);
endmodule
While your resource usage is not clear, I've got a couple of things here.
Never mix blocking and nonblocking assignments.
Why are you declaring different loop variables? A single variable is enough.
Rather than assigning default values, use reset logic in the design.
Please clarify about which resources are you talking about, your simulator version and which nested looping are to be discussed.
There can many more points, I have listed just a few.

Extract document name from print job

Is there a reliable way to extract a document name or job name from a postscript print job if you have the raw postscript data?
I've seen print release station software that labels each job with a document name or url it was printed from, so it seems possible.
There is no reliable way to do this, as there is no such (metadata) information in the PostScript language. If your files are DSC (Document Structuring Convention) compliant then you can look for comments. These are documented in the DSC reference manual. Valid PostScript files need not be DSC compliant.
Other than that, there is no information there to extract, at least as far as PostScript is concerned.
To extract document name from print job using C++.
#include <WinSpool.h>
wstring GetDocumentName(wstring m_strFriendlyName)
{
wstring strDocName = L"";
HANDLE hPrinter ;
if ( OpenPrinter(const_cast<LPWSTR>(m_strFriendlyName.c_str()), &hPrinter, NULL) == 0 )
{
/*OpenPrinter call failed*/
return false;
}
DWORD dwBufsize = 0;
PRINTER_INFO_2* pinfo = 0;
GetPrinter(hPrinter, 2,(LPBYTE)pinfo, dwBufsize, &dwBufsize); //Get dwBufsize
PRINTER_INFO_2* pinfo2 = (PRINTER_INFO_2*)malloc(dwBufsize); //Allocate with dwBufsize
GetPrinter(hPrinter, 2,(LPBYTE)pinfo2, dwBufsize, &dwBufsize);
DWORD numJobs = pinfo2->cJobs;
free(pinfo2);
JOB_INFO_1 *pJobInfo = 0;
DWORD bytesNeeded = 0, jobsReturned = 0;
//Get info about jobs in queue.
EnumJobs(hPrinter, 0, numJobs, 1, (LPBYTE)pJobInfo, 0,&bytesNeeded,&jobsReturned);
pJobInfo = (JOB_INFO_1*) malloc(bytesNeeded);
EnumJobs(hPrinter, 0, numJobs, 1, (LPBYTE)pJobInfo, bytesNeeded, &bytesNeeded, &jobsReturned);
JOB_INFO_1 *pJobInfoInitial = pJobInfo;
for(unsigned short count = 0; count < jobsReturned; count++)
{
if (pJobInfo != NULL)
{
strDocName = pJobInfo->pDocument; // Document name
DWORD dw = pJobInfo->Status;
}
pJobInfo++;
}
free(pJobInfoInitial);
ClosePrinter( hPrinter );
return strDocName;
}
What you might be seeing is the document name that the application submitted to the print spooler. Also, it may not be reliable but most print drivers put the document name in PJL or XML at the top of the print job. With some flexible rules you might be able to pull this data with some confidence.
This, of course, assumes that the PS data was generated by a printer drivers.

Resources