• Top
    • Documentation
    • Books
    • Boolean-reasoning
    • Projects
    • Debugging
    • Std
    • Proof-automation
    • Macro-libraries
    • ACL2
    • Interfacing-tools
    • Hardware-verification
      • Gl
      • Esim
      • Vl2014
        • Warnings
        • Primitives
        • Use-set
        • Syntax
        • Getting-started
        • Utilities
        • Loader
        • Transforms
          • Expression-sizing
          • Occform
          • Oprewrite
          • Expand-functions
          • Delayredux
          • Unparameterization
          • Caseelim
          • Split
          • Selresolve
          • Weirdint-elim
          • Vl-delta
          • Replicate-insts
          • Rangeresolve
          • Propagate
          • Clean-selects
          • Clean-params
          • Blankargs
          • Inline-mods
          • Expr-simp
          • Trunc
          • Always-top
            • Edgesynth
              • Vl-edgesynth-stmt-p
              • Vl-edgetable-p
              • Vl-always-edgesynth
              • Vl-edgesynth-merge-data-ifs
              • Vl-assignstmtlist->controls
              • Vl-assignstmtlist->lhses
              • Vl-assignstmtlist->rhses
              • Vl-edgesynth-flatten-data-ifs
              • Vl-edgesynth-pattern-match
              • Nedgeflop
              • Vl-edgesynth-make-data-inputs
              • Vl-edgesynth-make-clock-inputs
              • Vl-edgesynth-stmt-clklift
              • Vl-edgesynth-blockelim
              • Vl-alwayslist-edgesynth
              • Vl-edgesynth-create
              • Vl-edgesynth-classify-iftest
              • Vl-module-edgesynth
              • Vl-edgesynth-normalize-ifs
              • Vl-edgesynth-delays-okp
              • Vl-edgesynth-stmt-assigns
              • Vl-make-edgetable
              • Vl-edgesynth-sort-edges
              • Vl-modulelist-edgesynth
              • Vl-modulelist-edgesynth-aux
              • Vl-assignstmtlist-p
              • Vl-edgesynth-edgelist-p
              • Vl-assigncontrols-p
              • Vl-edgesynth-stmt-conditions
              • Vl-edgesynth-edge-p
              • Vl-design-edgesynth
              • Vl-edgesynth-get-delay
              • Vl-edgesynth-iftype-p
              • Edge-tables
            • Stmtrewrite
            • Cblock
            • Vl-always-convert-regports
            • Vl-always-convert-regs
            • Stmttemps
            • Edgesplit
            • Vl-always-check-reg
            • Vl-convert-regs
            • Latchsynth
            • Vl-always-check-regs
            • Vl-match-always-at-some-edges
            • Unelse
            • Vl-always-convert-reg
            • Vl-design-always-backend
            • Vl-stmt-guts
            • Vl-always-convert-regport
            • Vl-always-scary-regs
            • Eliminitial
            • Ifmerge
            • Vl-edge-control-p
            • Elimalways
          • Gatesplit
          • Gate-elim
          • Expression-optimization
          • Elim-supplies
          • Wildelim
          • Drop-blankports
          • Clean-warnings
          • Addinstnames
          • Custom-transform-hooks
          • Annotate
          • Latchcode
          • Elim-unused-vars
          • Problem-modules
        • Lint
        • Mlib
        • Server
        • Kit
        • Printer
        • Esim-vl
        • Well-formedness
      • Sv
      • Fgl
      • Vwsim
      • Vl
      • X86isa
      • Svl
      • Rtl
    • Software-verification
    • Math
    • Testing-utilities
  • Always-top

Edgesynth

Synthesize simple edge-triggered always blocks into primitives.

This is our "final," transformation for synthesizing edge-triggered always blocks into primitives. This transform supports only a limited subset of Verilog statements. Generally speaking, VL can handle a much richer subset of Verilog statements than we are going to deal with here. Other transforms—e.g., stmtrewrite, caseelim, and edgesplit—are typically first used to reduce these much richer statements into the simple form that we now target.

Despite the many limits we place on the Verilog statements that we do try to support here, synthesizing edge-triggered always blocks is still difficult to do in a way that is completely faithful to the Verilog semantics. We describe some of the challenges and our general approach below.

Preliminaries: Edge-Triggered Blocks, Clocks

Definition. We say that an always block is edge-triggered when it contains a vl-timingstmt whose control is a list of posedge or negedge events. As examples:

always @(posedge clk) ... ;                  // edge-triggered
always @(negedge clk) ... ;                  // edge-triggered
always @(posedge clk or negedge reset) ... ; // edge-triggered

always @(*) ... ;                            // not edge-triggered
always @(a or b or c) ... ;                  // not edge-triggered
always @(posedge clk or a) ... ;             // not edge-triggered
always #3 ... ;                              // not edge-triggered
always begin ... end ;                       // not edge-triggered

Throughout this discussion, we will assume that we are attempting to synthesize an edge-triggered always block. Some other kinds of always blocks are supported by other VL transforms, e.g., the cblock transform can process certain kinds of combinational always blocks. But we aren't going to try to cover any of that, here.

For many years, VL only supported edge-triggered always blocks that had a single posedge or negedge trigger. We now support arbitrary lists of posedge and negedge edges. That is, we can now handle always blocks such as:

always @(posedge a or negedge b or posedge c or ...)
begin
   // restricted statements as explained below
end

Definition. We say that the clocks of such an always block are the expressions in the sensitivity list, regardless of whether they are used with posedge or negedge specifiers. For instance:

// example:                                  // clocks (expressions)
always @(posedge clk) ...;                   //   clk
always @(posedge mclk or negedge reset) ...; //   mclk, reset
always @(posedge a & b or posedge c[0]) ...; //   a & b, c[0]

We only support always blocks whose clocks are each one-bit wide, plain identifiers. Why?

  • The width restriction is meant to avoid mismatches between Verilog tools: we have found that various Verilog simulators do not agree about what constitutes an edge for a wide signal. At any rate, it would be weird to ask about the edge of a wide signal.
  • Requiring the clock be a plain identifier is a significant help in keeping our processing code more simple. We'll soon describe some of the timing nuances that make it important to be able to distinguishing clocks from non-clocks in if statements and right-hand sides of expressions. We think this restriction is probably not too severe, as most reasonable designs will not include, e.g., vectors of clocks.

Synthesis Challenge: X Optimism

One basic problem in synthesizing edge-triggered always blocks (or any other kind of always blocks, for that matter) is the unwarranted optimism of if statements.

Suppose for simplicity that en is one bit wide. In the Verilog semantics, a statement like if (en) q <= d causes an update to q only if en evaluates to 1. If en instead evaluates to X or Z, the if statement pretends it evaluated to 0 and does not update q. This is generally bad, as it fails to properly treat X as an unknown.

When we synthesize these always blocks for use with properly monotonic back-ends like esim, where X really does represent an unknown, we have no way to model this kind of optimism. Instead, roughly speaking, we are going to treat these statements like ?: operators, which have safer but different X behavior. That is, in our semantics, when en evaluates to X or Z, we may write an X value into q instead of failing to update it.

We cannot see any way to avoid this kind of mismatch. We generally regard the ?: behavior as more reasonable and safer. But it's easy to imagine that if q is later fed into other, non-conservative Verilog constructs (such as if statements, case/casex/casez statements, case equality operators, etc.), then a Verilog simulator could go produce completely different results than, say, an esim-based simulation.

Synthesis Challenge: Clock/Data Races

A much more subtle and tricky problem is the event-based Verilog timing model allows for a number of races between the clock signals and the data inputs. To illustrate this, consider the following examples:

// Version 1:

always @(posedge clk or posedge reset)
  q <= reset ? 0 : data;

// Version 2:

wire q_next;
assign q_next = reset ? 0 : data;
always @(posedge clk or posedge reset)
  q <= q_next;

You might hope these would behave the same. After all, it looks like the only difference is that we've named an intermediate expression. Unfortunately, Version 2 has a race condition in Verilog simulators. In Version 2, when reset transitions from low to high, two separate events need to be scheduled: the assign and the always block. These events may occur in any order, so we may find that either:

  • The assign statement happens first, so q_next "properly" is updated to 0 before the always block updates q, or
  • The always statement happens first, so q_next "improperly" has the old value of data when we assign it to q.

(If you run these on a Verilog simulator and find that they simulate in the same way, try reordering the assignment and the always block and you may get a different result.)

In contrast, at least in Verilog's simulation semantics, Version 1 does not suffer from this problem and will "properly" reset q. The reason this works is that there are no competing events are triggered by the transition of reset; only the single always block must be run.

Here is an attempt to visualize what we might be modeling when we write these two fragments of Verilog.

Version 1 might be sensible. If this handling of reset is to be an intrinsic part of the always block, a circuit designer might be able to design something that implements it correctly.

But Version 2 is clearly not okay. Here, with the muxing done independently of the flop, we can see that a change in reset is going to trigger an update in both the flop itself and in the mux that is feeding the flop. This is a clear race.

The fanciful "horrible circuit" is similar to Version 2, but made worse just to drive the point home. Imagine here that the adder is some large circuit with some large delay. When reset goes high, the flip-flop is triggered and, meanwhile, the adder's inputs have changed, so it will be busily transitioning to compute the new sum. The value that gets latched in, then, is anyone's guess. Clearly you would never want to design something like this.

At any rate, if we regard the right-hand sides of assignments in an edge-triggered always block as the data inputs to flip-flops, then for our design to make any sense, we really need these data inputs to be stable whenever a clock changes. For that to hold, these assignments should, at the very least, not depend on the clocks of the always block.

This is difficult to reliably identify syntactically. Even in a simple case like Version 2, we would need to analyze the assignments that are occurring in the module to discover that q_next depends on reset. If q_next were instead driven by some submodule, then noticing this would require an analysis of that submodule. Aliasing makes this much worse, e.g., what if we have something like:

wire my_clk = real_clk;
always @(posedge my_clk) q <= real_clk;

At any rate, detecting this situation seems very difficult, so we have not seriously considered trying to identify these races. We do, however, at least forbid the clocks from occurring in the right hand sides of expressions.

This may seem quite unsatisfying—it rules out Version 1 and doesn't rule out Version 2! But this restriction is practically very useful. It means that for the blocks we do support, it's reasonable to move the right-hand sides out of the always block. That is, it makes it safe to do something like:

always @(posedge clk or posedge reset)
  if (reset)
     q <= 0;
  else
     q <= q+1;
-->

assign q_next = q+1;

always @(posedge clk or posedge reset)
  if (reset)
     q <= 0;
  else
     q <= q_next;

This sort of reassignment wouldn't be valid if the right-hand side expression mentioned any of the clocks, as we have just beaten to death, above. (It's the whole difference between Version 1 and Version 2).

Historic Approach to Edge-Triggered Blocks

Before describing our new approach, it's useful to describe the old way that VL handled edge-triggered blocks. Previously, VL supported only basic, single-edge registers, and reduced all supported always blocks into instances of a single, 1-bit flip-flop primitive.

module VL_1_BIT_FLOP (q, d, clk);
  output reg q;
  input d;
  input clk;
  always @(posedge clk)
    q <= d;
endmodule

Given this primitive, it was straightforward to implement a simple, posedge-triggered, N-bit flip-flop: just instance N of these primitive flops, one for each bit. We named the resulting modules, e.g., VL_4_BIT_FLOP, VL_128_BIT_FLOP, and so forth, for any N.

We then had a transformation that could support basic always blocks by converting them into instances of an appropriately sized flop module. For instance:

always @(posedge clk)
  q <= a + b + cin;
 --->
VL_12_BIT_FLOP foo123 (q, a + b + cin, clk);

Our transform could also support always blocks with limited if/else expressions, by merging the if/else structure into the data input. For instance:

always @(posedge clk)
begin
  if (cycle)
     q <= 12'b0;
  else
     q <= a + b + cin;
end
--->
VL_12_BIT_FLOP foo123 (q, cycle ? 12'b0 : a + b + cin, clk);

Along with other transforms, e.g., for converting negedge into posedge signals by inverting them, this allowed VL to support a fairly rich set of single-edge always blocks. Meanwhile, back-end tools like esim only needed to support a single VL_1_BIT_FLOP primitive. Historically this was done using a traditional master/slave latch style.

New Primitives

Unfortunately, we don't how we can construct a multi-edge flip flop out of a single-edge flip-flop. To allow VL to support always blocks with multiple edges, then, we need new primitives.

After studying the kinds of multi-edge flops that we wanted to support, we decided that what we really want is a flip-flop with a built-in priority mux that is governed by the clock edges. Our new, simplest flip-flop primitive is just like the previous VL_1_BIT_FLOP:

module VL_1_BIT_1_EDGE_FLOP (q, d, clk);
  output reg q;
  input d;
  input clk;
  always @(posedge clk)
    q <= d;
endmodule

The next simplest primitive has two clocks and two data signals. We assume that one clock has priority over the other. This module is just a slight generalization of, e.g., Version 1 of the resettable mux that we saw above, where instead of necessarily resetting to zero we can choose between two arbitrary data inputs.

module VL_1_BIT_2_EDGE_FLOP (q, d0, d1, clk0, clk1);
 output reg q;
 input d0, d1;
 input clk0, clk1;
 always @(posedge clk0 or posedge clk1)
   if (clk0)
      q <= d0
   else
      q <= d1;
endmodule

BOZO consider using ?: instead of IF here for the Verilog definition.

For three clocks we simply add another clock and data input, extending the priority mux. This module could be used to model, e.g., a flip-flop with asynchronous set and clears, e.g., by allowing d0 = 1 and d1 = 0 or vice versa depending on the desired priority of set versus clear.

module VL_1_BIT_3_EDGE_FLOP (q, d0, d1, d2, clk0, clk1, clk2);
 output reg q;
 input d0, d1;
 input clk0, clk1;
 always @(posedge clk0 or posedge clk1 or posedge clk2)
   if (clk0)
      q <= d0
   else if (clk1)
      q <= d1;
   else
      q <= d2;
endmodule

Although we don't know what use there would be for such a flip-flop with more than three clocks and data inputs, we can continue this way, adding more clocks and data inputs, up to any number desired. See nedgeflop for more details about how we generate these primitives.

Given these new primitives, we can still construct wide flip-flops by chaining together one-bit primitive flops.

The remaining challenge is to line up edge-triggered always with instances of these primitives.

Subtopics

Vl-edgesynth-stmt-p
Supported statements: if statements, block statements, null statements, non-blocking assignments to whole identifiers.
Vl-edgetable-p
Associates each edge name to the edge itself.
Vl-always-edgesynth
Try to synthesize a single always block.
Vl-edgesynth-merge-data-ifs
Safely merge if (condition) q <= d1 else q <= d2 into q <= condition ? d1 : d2.
Vl-assignstmtlist->controls
(vl-assignstmtlist->controls x) maps vl-assignstmt->ctrl across a list.
Vl-assignstmtlist->lhses
(vl-assignstmtlist->lhses x) maps vl-assignstmt->lvalue across a list.
Vl-assignstmtlist->rhses
(vl-assignstmtlist->rhses x) maps vl-assignstmt->expr across a list.
Vl-edgesynth-flatten-data-ifs
Flatten out bottom-level if tests about data signals, such as if (data-expr) q <= d1 else q <= d2 into assignments like q <= data-expr ? d1 : d2.
Vl-edgesynth-pattern-match
Recognize basic if statements for the core of a multi-edge block: we're basically looking for a proper priority ordering on the edges.
Nedgeflop
Generation of new primitives for edge-triggered always blocks.
Vl-edgesynth-make-data-inputs
Create (if necessary) temporary wires for the data inputs.
Vl-edgesynth-make-clock-inputs
Create (if necessary) temporary wires for the clock inputs.
Vl-edgesynth-stmt-clklift
Lightweight rewriting to support allow us to support right-hand, sides such as reset ? 0 : data.
Vl-edgesynth-blockelim
Eliminate begin/end blocks and leave us with just an IF structure.
Vl-alwayslist-edgesynth
Extends vl-always-edgesynth to a list of always blocks.
Vl-edgesynth-create
Vl-edgesynth-classify-iftest
Vl-module-edgesynth
Synthesize edge-triggered always blocks in a module.
Vl-edgesynth-normalize-ifs
Try to push data IFs down, pull clock IFs up, and align the polarity of clock-based IFs with the edge declarations.
Vl-edgesynth-delays-okp
Check that internal delays on assignments are simple enough to process and compatible with one another.
Vl-edgesynth-stmt-assigns
Collect all assignsment statements from an edgesynth statement.
Vl-make-edgetable
Vl-edgesynth-sort-edges
Vl-modulelist-edgesynth
Synthesize edge-triggered always blocks in a module list, perhaps adding some new, supporting modules.
Vl-modulelist-edgesynth-aux
Vl-assignstmtlist-p
(vl-assignstmtlist-p x) recognizes lists where every element satisfies vl-assignstmt-p.
Vl-edgesynth-edgelist-p
(vl-edgesynth-edgelist-p x) recognizes lists where every element satisfies vl-edgesynth-edge-p.
Vl-assigncontrols-p
(vl-assigncontrols-p x) recognizes lists where every element satisfies vl-maybe-delayoreventcontrol-p.
Vl-edgesynth-stmt-conditions
Collect all conditions from if statements in an edgesynth statement.
Vl-edgesynth-edge-p
Vl-design-edgesynth
Top-level edgesynth transform.
Vl-edgesynth-get-delay
Extract the shared, simple delay from the controls for the assignment statements.
Vl-edgesynth-iftype-p
Classifications of if tests into clock/data signals.
Edge-tables
Data structure that conveniently summarizes a sensitivity list.