I spent quite a long time debugging some Verilog code only to realize that the design was correct the entire time and for whatever reason Verilog's display
function was having unexpected behavior.
I have the following testbench file:
`timescale 1ns / 1ps
module tb ();
logic signed [7:0] Y;
logic signed [15:0] X;
logic signed [23:0] Z;
logic clk;
reg [8:0] counter;
// instantiate device under test
CSAM dut (Z,X,Y);
// 2 ns clock
initial
begin
clk = 1'b1;
assign counter = 8'b0;
forever #10 clk = ~clk;
end
initial
begin
//while (1)
//begin
#10 X = 16'b0000000001111011;
#0 Y = 8'b11110011;
#20 X = 16'b0000000001111011;
#0 Y = 8'b11110011;
$display ("Test data:\ninput A is 0b'%16b or 0h'%4h or %d\ninput B is 0b'%8b or 0h'%2h or %d\noutput is 0b'%24b or 0h'%6h or %d\n", X,X,X,Y,Y,Y,Z,Z,Z);
#20 X = 16'b0000000000001111;
#0 Y = 8'b00000000;
$display ("Test data:\ninput A is 0b'%16b or 0h'%4h or %d\ninput B is 0b'%8b or 0h'%2h or %d\noutput is 0b'%24b or 0h'%6h or %d\n", X,X,X,Y,Y,Y,Z,Z,Z);
#20 X = 16'b0000000000001111;
#0 Y = 8'b11111111;
$display ("Test data:\ninput A is 0b'%16b or 0h'%4h or %d\ninput B is 0b'%8b or 0h'%2h or %d\noutput is 0b'%24b or 0h'%6h or %d\n", X,X,X,Y,Y,Y,Z,Z,Z);
#20 X = 16'b11111111111111;
#0 Y = 8'b11111111;
$display ("Test data:\ninput A is 0b'%16b or 0h'%4h or %d\ninput B is 0b'%8b or 0h'%2h or %d\noutput is 0b'%24b or 0h'%6h or %d\n", X,X,X,Y,Y,Y,Z,Z,Z);
#20 X = 16'b1010101010101010;
#0 Y = 8'b01010101;
$display ("Test data:\ninput A is 0b'%16b or 0h'%4h or %d\ninput B is 0b'%8b or 0h'%2h or %d\noutput is 0b'%24b or 0h'%6h or %d\n", X,X,X,Y,Y,Y,Z,Z,Z);
#20 X = 16'b0101010101010101;
#0 Y = 8'b00101010;
$display ("Test data:\ninput A is 0b'%16b or 0h'%4h or %d\ninput B is 0b'%8b or 0h'%2h or %d\noutput is 0b'%24b or 0h'%6h or %d\n", X,X,X,Y,Y,Y,Z,Z,Z);
end
endmodule
You can see that I am running some tests of inputs X,Y and Z is the output.
My display function prints X,Y,Z in decimal, hex, and binary for readability.
$display ("Test data:\ninput A is 0b'%16b or 0h'%4h or %d\ninput B is 0b'%8b or 0h'%2h or %d\noutput is 0b'%24b or 0h'%6h or %d\n", X,X,X,Y,Y,Y,Z,Z,Z);
The output looks like this in Modelsim's terminal:
# Test data:
# input A is 0b'0000000001111011 or 0h'007b or 123
# input B is 0b'11110011 or 0h'f3 or -13
# output is 0b'111111111111100111000001 or 0h'fff9c1 or -1599
#
# Test data:
# input A is 0b'0000000000001111 or 0h'000f or 15
# input B is 0b'00000000 or 0h'00 or 0
# output is 0b'111111111111111100111101 or 0h'ffff3d or -195
#
# Test data:
# input A is 0b'0000000000001111 or 0h'000f or 15
# input B is 0b'11111111 or 0h'ff or -1
# output is 0b'000000000000000000000000 or 0h'000000 or 0
You can see that for 15x0, and 15x-1 the results are actually incorrect. However, if I inspect the Z waveform manually in Modelsim's waveform viewer I can see the answer is actually correct. So somehow, the display function is not behaving as intended and I would like to know what I can do to fix that. It is particularly weird to me that the inputs X,Y display properly for all tests but Z does not.
Since you only have combinatorial logic in your CSAM
module, your issue can still be reproduced by changing that line to assign Z = X * Y;
, which is equivalent.
TL;DR: Because the execution of expression evaluation and net update events may be intermingled, race conditions are possible. You can avoid races by changing your code like this:
#20;
X = 16'b0000000001111011;
Y = 8'b11110011;
#0;
$display("time: %t, X: %d, Y: %d, X*Y=Z: %d", $time, X, Y, Z);
For a detailed description of this see SystemVerilog IEEE Std 1800-2017 4.8 Race conditions, for example:
assign p = q; initial begin q = 1; #1 q = 0; $display(p); end
The simulator is correct in displaying either a 1 or a 0. The assignment of 0 to q enables an update event for p. The simulator may either continue and execute the
$display
task or execute the update for p, followed by the$display
task.
The SystemVerilog language is defined in terms of a discrete event execution model, that is an event-driven simulation model based on Stratified event scheduler. For details to see SystemVerilog IEEE Std 1800-2017 chapter 4 Scheduling semantics and this paper: SystemVerilog Event Regions, Race Avoidance & Guidelines.
To simplify things, let's look at event regions in Verilog as follows (Image sourced from the above paper):
We can see that both blocking assignments
and continuous assignments
and $display task
are in Active
region, so races may occur. We can specify #0
to holds the events to be evaluated after all the Active events are processed to avoid races, see SystemVerilog IEEE Std 1800-2017 4.4.2.3 Inactive events region.
If events are being executed in the active region set, an explicit #0 delay control requires the process to be suspended and an event to be scheduled into the Inactive region of the current time slot so that the process can be resumed in the next Inactive to Active iteration.
Your other confusion is about the waveform:
However, if I inspect the Z waveform manually in Modelsim's waveform viewer I can see the answer is actually correct.
You can use Expanded Time Deltas Mode or Expanded Time Events Mode to see waveform details in Modelsim from tab. For Example, In your actual case, let's look at 50ns:
In Expanded Time Deltas Mode, you can roughly see that those signals may have race; In Expanded Time Events Mode, you can see how the simulator makes decisions and schedules at each step.
Finally, recommend it again, if you want to know more, please see the paper: SystemVerilog Event Regions, Race Avoidance & Guidelines.