Forums » Development »
Behaviour when ignoring COMBDLY
Added by Jeremy Bennett 11 months ago
I have the following module in my code:
module sim_top (clk , ctrl);
input wire clk;
output wire ctrl;
reg y1;
reg y2;
assign ctrl = y2;
always @(clk) begin
if (clk) begin
y1 <= 1'b0;
end
else begin
y1 <= 1'b1;
end
end
always @(posedge clk) begin
y2 <= y1;
end
endmodule
Verilator warns (COMBDLY) that using non-blocking assignment inside a combinatorial always block is bad practice, referring to the Clifford Cummings paper at SNUG 2000 on the subject (http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA_rev1_2.pdf). If COMBDLY is overridden, Verilator treats the non-blocking assignments as though they were blocking, i.e. as though the code were:
module sim_top (clk , ctrl);
input wire clk;
output wire ctrl;
reg y1;
reg y2;
assign ctrl = y2;
always @(clk) begin
if (clk) begin
y1 = 1'b0;
end
else begin
y1 = 1'b1;
end
end
always @(posedge clk) begin
y2 <= y1;
end
endmodule
On the posedge of clk, this code contains a race between the assignment of y1 as the inverse of the clock (the first, combinatorial, always block) and the evaluation of y1 as the RHS of a non-blocking assignment (the second, sequential, always block). The Verilog 2001 standard is explicit (section 5.4.2) that the active events of different always blocks may be evaluated in any order. If the combinatorial always is evaluated first, then y2 will be latched high. If the sequential always is evaluated first, then y2 will be latched low.
Testing shows that event driven simulators, other cycle-accurate modeling tools and gates synthesized by Design Compiler all choose to evaluate the combinatorial block first and y2 is latched high. Verilator evaluates the sequential block first, and y2 is latched low.
At one level this doesn't matter. This is clearly bad code, and the fact that Verilator behaves differently from other tools is a consequence. If this were new code, then clearly it would have to be fixed. However in this case, it is long-standing "golden" production code. It has this bug in it (and probably more like it), but it causes no problems because all other tools work in one particular way. Now we are building models of this legacy code, it would be helpful if Verilator resolved races in the same way as other tools, particularly synthesis tools. This would allow us to avoid modifying the golden reference RTL.
It seems that all other tools have rules used when modeling races, beyond what is specified in the Verilog standard. Does anyone have an understanding of what those rules are. Should there be an option to Verilator to allow those rules to be used, so Verilator models more closely match other simulation tools? Should the COMBDLY warning give more information to help in identifying these races, and if so what should that warning be?
I would welcome community suggestions on this.
Replies (7)
RE: Behaviour when ignoring COMBDLY - Added by Jeremy Bennett 11 months ago
One observation is that, although using non-blocking assignments in combinatorial code is bad practice, it is legal Verilog. Should we really be treating the first block as though it were clocked sequential logic:
always @(posedge clk or negedge clk) begin
if (clk) begin
y1 <= 1'b0;
end
else begin
y1 <= 1'b1;
end
end
RE: Behaviour when ignoring COMBDLY - Added by Wilson Snyder 11 months ago
What exactly does design compiler do with this in terms of the gates it generates? I'm skeptical there's an easy fix here, but perhaps there's some pattern that can be easily recognized like design compiler does.
RE: Behaviour when ignoring COMBDLY - Added by Jeremy Bennett 11 months ago
The synthesized module is an inverter feeding into a D-type flip-flop:
module sim_top ( clk, ctrl ); input clk; output ctrl; wire y1; DFFQX1 y2_reg ( .D(y1), .CK(clk), .Q(ctrl) ); CLKINVX1 U4 ( .A(clk), .Y(y1) ); endmodule
I've attached a PDF of the schematic.
print.pdf (11.2 kB)
RE: Behaviour when ignoring COMBDLY - Added by Jeremy Bennett 11 months ago
I understand the PDF may not display/print properly using evince on Linux. I attach a PostScript version to this message.
print.ps (12.8 kB)
RE: Behaviour when ignoring COMBDLY - Added by Jeremy Bennett 11 months ago
module sim_top (clk , ctrl);
input wire clk;
output wire ctrl;
reg y1;
reg y2;
assign ctrl = y2;
always @(clk) begin
if (clk) begin
y1 <= 1'b0;
end
else begin
y1 <= 1'b1;
end
end
always @(posedge clk) begin
y2 <= y1;
end
endmodule
is bad design, but it is not invalid Verilog, and unlike the version using blocking assignments, under Verilog evaluation rules it has no race.
Assuming we have clk==1, then when clk goes low we eventually get
clk==0 && y1==1'b1
then when clk goes high we get
clk==1 && y1==1'b1 && y1'==1'b0 && y2'==(y1)
where y1' is the uncommitted non-blocking assignment update, which is
clk==1 && y1==1'b1 && y1'==1'b0 && y2'==(1'b1)
so eventually we have
clk==1 && y1==1'b0 && y2==1'b1
so y2 latches hi (as we describe the other tools doing?). If we observe correct non-blocking assignment timing. ie on a posedge, y1 is not yet low when y2 samples.
So it seems that Verilator's treatment of -Wno-COMBDLY by replacing all non-blocking assignments in combinatorial blocks by blocking assignments causes some code to simulate incorrectly. The solution is for -Wno-COMBDLY to correctly model NBA. I am not sure if a quick(er) fix to this is for all sensitivities in blocks with non-blocking assigments to be treated as edges (positive and negative) rather than combinatorial activity.
RE: Behaviour when ignoring COMBDLY - Added by Jeremy Bennett 10 months ago
I believe we can achieve the required result by a rewrite. The original code, which triggers COMBDLY, is:
always @(clk)
begin
if (clk) begin
y1 <= 1'b0;
end
else begin
y1 <= 1'b1;
end
end
always @(posedge clk) begin
y2 <= y1;
end
We can replace this by
initial begin
y1_tmp = 1'bx;
end
always @(clk)
begin
if (clk) begin
y1_tmp = 1'b0;
end
else begin
y1_tmp = 1'b1;
end
end
always @(posedge y1_tmp or negedge y1_tmp) begin
y1 <= y1_tmp;
end
always @(posedge clk) begin
y2 <= y1;
end
This is compilable by Verilator without complaint and yields the expected behavior (the initial setting to 1'bx is required to ensure the first edge is picked up). Although presented as a source level rewrite, it can be implemented as an AST edit in V3Active.cpp, conditional on the use of -Wno-COMBDLY. I shall work on an implementation.
RE: Behaviour when ignoring COMBDLY - Added by Jeremy Bennett 10 months ago
This rewrite doesn't work if y1 is more than 1 bit wide. For example:
reg [1:0] y1;
reg [1:0] y2;
always @(clk)
begin
y1 <= {clk, ~clk};
end
always @(posedge clk)
begin
y2 <= y1;
end
In this case, the rewrite should use a register to latch any changes in the combinatorial assignment:
reg [1:0] y1;
reg [1:0] y1_tmp;
reg y1_latch;
reg [1:0] y2;
always @(clk) begin
y1_tmp = {clk, ~clk};
y1_latch = 1'b0;
end
always @(y1_tmp) begin
y1_latch = 1'b1;
end
always @(posedge y1_latch) begin
y1 <= y1_tmp;
end
always @(posedge clk) begin
y2 <= y1;
end
(1-7/7)
![[logo]](/img/veripool_small.png)