I have a Verilog program for a memory module that looks really simple but is behaving oddly, and I cannot see why. If anyone can find my mistake, I would be eternally grateful. This is the memory program:
module mem_WidthxDepth (
clk_i,
wr_addr_i,
rd_addr_i,
wr_i,
data_in_i,
data_out_o
);
parameter Width = 8;
parameter Depth = 8;
//AW = Address Width
localparam AW = $clog2 (Depth);
//IO
input clk_i;
input [AW-1:0] wr_addr_i;
input [AW-1:0] rd_addr_i;
input wr_i;
input [Width-1:0] data_in_i;
output [Width-1:0] data_out_o;
//Memory declaration.
reg [Width-1:0] Mem [0:Depth-1];
//Write into the memory
always @ (posedge clk_i)
if (wr_i)
Mem[wr_addr_i] <= data_in_i;
//Read from the memory
assign data_out_o = Mem [rd_addr_i];
endmodule
and this is the testbench code:
module mem_tb;
reg clk_i;
reg [2:0] wr_addr_i;
reg [2:0] rd_addr_i;
reg wr_i;
reg [7:0] data_in_i;
wire [7:0] data_out_o;
// Instantiate the memory
mem_WidthxDepth mem
(
clk_i,
wr_addr_i,
rd_addr_i,
wr_i,
data_in_i,
data_out_o
);
// Clock generation
always #5 clk_i = ~clk_i;
initial begin
clk_i = 0;
wr_i = 0;
rd_addr_i = 1;
// Write data into FIFO
for (integer i = 0; i < 8; i = i + 1) begin
@(posedge clk_i);
wr_i = 1'b1;
wr_addr_i = i[2:0];
data_in_i = i[7:0];
$display("Write %d", data_in_i);
end
// Stop writing
@(negedge clk_i);
wr_i = 0;
// Read data back
for (integer i = 0; i < 8; i = i + 1) begin
@(posedge clk_i);
rd_addr_i = i[2:0];
$display("Read %d", data_out_o);
end
// Finish simulation
$finish;
end
// Waveform generation
initial begin
$dumpfile("mem_tb.vcd");
$dumpvars(0, mem_tb);
end
endmodule
so it should just write 0 to 7 into memory addresses 0 to 7 then read back the numbers. But, when I run it using iverilog
(on Ubuntu) I get:
renniej@gramrat:/mnt/d/rhs/Students/Tejas/VLSI/L6$ iverilog -o mem_tb.vvp mem_Wi
dthxDepth.v mem_tb.v
renniej@gramrat:/mnt/d/rhs/Students/Tejas/VLSI/L6$ vvp mem_tb.vvp
VCD info: dumpfile mem_tb.vcd opened for output.
Write 0
Write 1
Write 2
Write 3
Write 4
Write 5
Write 6
Write 7
Read 0
Read x
Read 2
Read x
Read 4
Read x
Read 6
Read x
mem_tb.v:49: $finish called at 155 (1s)
For some reason, every second write and/or read appears to fail. If I look at the signals for the memory module in gtkwave
I get:
which shows that data_out_o
is undefined every second read, i.e. apparently it was never written. But, I just cannot see what is going wrong. This is such simple code that I cannot see where it is failing.
2 Answers 2
I can not reproduce your result exactly. When I try to compile your code with iverilog
, I get syntax errors, and I can not run a simulation. You and I are probably using different versions of iverilog
, which would explain the discrepancy.
When I use a different simulator (Cadence), I get an unexpected result for the Read
values. This is due to Verilog simulation race conditions in your code. Other simulators are available to you free on the EDA Playground site.
As you correctly deduced in your answer, you need to use nonblocking assignments (<=
) when you drive all the synchronous inputs to the design from the testbench.
You should also be consistent and always drive at the posegde clk_i
(you drive at negedge
in one place).
By driving the inputs in the same way as you drive internal signals in the design, you are guaranteed to avoid these race conditions, namely:
@(posegde clk_i)
- Nonblocking (
<=
) assignments
There is also a second type of race condition in the code. You should never $display
a signal when it is changing because you will get indeterminate results. Since your signals change on posedge clk_i
, it is better to display their value when they are stable on the negedge clk_i
.
Here is a cleaner way to write the testbench code:
initial begin
clk_i = 0;
wr_i = 0;
rd_addr_i = 1;
// Write data into FIFO
for (integer i = 0; i < 8; i = i + 1) begin
@(posedge clk_i);
wr_i <= 1'b1;
wr_addr_i <= i[2:0];
data_in_i <= i[7:0];
@(negedge clk_i);
$display($time, , "Write %d", data_in_i);
end
// Stop writing
@(posedge clk_i);
wr_i <= 0;
// Read data back
for (integer i = 0; i < 8; i = i + 1) begin
@(posedge clk_i);
rd_addr_i <= i[2:0];
@(negedge clk_i);
$display($time, , "Read %d", data_out_o);
end
$finish;
end
I was able to finally get the code to compile with iverilog
. For some reason, the version I have does not support declaring a variable inside a for
loop. I just moved the integer
keyword outside the initial
block:
integer i;
-
\$\begingroup\$ It works! :-) Thanks for taking the time to explain the problem in detail. A bounty will be on its way as soon as I can award one! \$\endgroup\$John Rennie– John Rennie2024年11月04日 17:03:22 +00:00Commented Nov 4, 2024 at 17:03
-
\$\begingroup\$ @JohnRennie: You're welcome, and thank you for the huge award! \$\endgroup\$toolic– toolic2024年11月07日 12:35:28 +00:00Commented Nov 7, 2024 at 12:35
And of course it was a silly mistake on my part. I was using blocking assignments in the loops in the testbench code - a habit that is going to be hard to shake after 40 years of writing C. When I change the testbench code to use non-blocking assignments the code works as expected.
However I don't understand why using blocking assignments was causing the problem. If anyone can add an answer explaining this I'd accept that and award a bonus. I'm guessing it's some timing issue e.g. I was assuming that when I write:
@(posedge clk_i);
wr_i = 1'b1;
wr_addr_i = i[2:0];
data_in_i = i[7:0];
everything happens at the same time e.g. wr_i
has the value 1
at the moment of the rising edge for clk_i
. Is this the case? I have to confess I do not understand how the timings work in Verilog.
-
2\$\begingroup\$ My answer explains why. \$\endgroup\$toolic– toolic2024年11月04日 16:10:02 +00:00Commented Nov 4, 2024 at 16:10