Synthesize simple edge-triggered
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.
Definition. We say that an
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
For many years, VL only supported edge-triggered always blocks that had a
single
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
// 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?
One basic problem in synthesizing edge-triggered
Suppose for simplicity that
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
We cannot see any way to avoid this kind of mismatch. We generally regard
the
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
(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
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
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
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).
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.,
We then had a transformation that could support basic
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
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
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
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