3
\$\begingroup\$

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:

gtkwave

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.

\$\endgroup\$

2 Answers 2

3
+100
\$\begingroup\$

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; 
\$\endgroup\$
2
  • \$\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\$ Commented Nov 4, 2024 at 17:03
  • \$\begingroup\$ @JohnRennie: You're welcome, and thank you for the huge award! \$\endgroup\$ Commented Nov 7, 2024 at 12:35
0
\$\begingroup\$

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.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ My answer explains why. \$\endgroup\$ Commented Nov 4, 2024 at 16:10

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.