Skip to content

Time-0 race condition for simulation #594

Open
@RobertBaruch

Description

@RobertBaruch

Minimal test case. simbug.zip

Summary:
The Verilog that nMigen outputs can contain time-0 race conditions which cause problems for simulation.

Details:

I'm running through the Zero to Asic course using nMigen. The digital design part assumes you're writing Verilog, runs the code through Icarus Verilog, and then simulates/tests using cocotb. Everything is fine, except I managed to stumble into a Verilog simulation subtlety. Best explained by the Icarus Verilog FAQ:

My "always" statement doesn't trigger at time 0. Is Icarus Verilog broken?

In this case, the bug is most likely in your program, and is probably the most common and unnoticed error in the entire history of Verilog use. Your program probably looks something like this:

reg [7:0] a, b, c;
always @(a or b) c = a + b;
initial begin a = 1; b = 2; end

This is in fact a race condition at time zero. James Lee has this to say:

[T]he bug is the race between the always at time zero getting to the @ and the initial setting the values. A #1 in the initial block before changing a or b should do the trick.
I think that most verilog simulators have some sort of trick to catch the changes at time zero.
The IEEE 1364-1995 standard is also clear on this issue: the given example leaves c with an unpredictable value at time 0. This is a frightfully common mistake, we've all done it. It often goes unnoticed because compilers often start threads from first (in the source file) to last. It just so happens that Icarus Verilog, by a detail of implementation, starts threads from last to first.

What people don't typically realize is that #0 <statement> has a well defined meaning. I suggest that you preceed statements in your initial processes with a #0 to eliminate the race, like so:

reg [7:0] a, b, c;
always @(a or b) c = a + b;
initial #0 begin a = 1; b = 2; end

This improved version doesn't have a time-0 race--if the initial block is scheduled ahead of the always block, the ``#0'' will tell the scheduler to first yield to all other threads, then continue.

The Verilog generated by nMigen is something like this:

reg [3:0] a = 4'h0;
...
always @* begin
  ... use a here
end

This results in the time-0 race described above. While not synthesizable, manually altering the Verilog resolves the problem:

reg [3:0] a;
initial #0 begin a = 4'h0; end
...
always @* begin
  ... use a here
end

You'd think that you could use a reset signal to reset registers to known states, but that doesn't fix the problem.

I emphasize that this is a simulation issue, not a synthesis issue. I'm honestly not sure what the solution here is. I don't really want to see initial #0 in Verilog for synthesis.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions