Project

General

Profile

[logo] 
 
Home
News
Activity
About/Contact
Major Tools
  Dinotrace
  Verilator
  Verilog-mode
  Verilog-Perl
Other Tools
  IPC::Locker
  Parallel::Forker
  Voneline
General Info
  Papers

Behaviour when ignoring COMBDLY

Added by Jeremy Bennett over 7 years 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 over 7 years 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 over 7 years 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 over 7 years 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 over 7 years 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 over 7 years ago

I talked further with some of my former colleagues. The original 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
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 over 7 years 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 over 7 years 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)