In hardware description languages (HDL), sensitivity lists are used to indicate which events may trigger a VHDL process or (System)Verilog always statement. These trigger events are usually transitions of signals that are inputs of the process or always statement.
Simulators and synthesis tools tend to treat sensitivity lists differently.
- A simulator will observe the sensitivity list. If an input of a procedure or always statement is not in the sensitivity list, a transition of that signal will not trigger the procedure, and no new output is produced.
- A synthesis tool will ignore the sensitivity list. A new output is calculated according to the code of the procedure or always statement. The synthesis tool may warn for an incomplete sensitivity list.
This different treatment may lead to differences between simulation and synthesis, which may hide design flaws until late in the design cycle. This is particularly true for combinatorial processes, which may have many inputs and hence long sensitivity lists.
Three different problems may be present in a sensitivity list: missing items, superfluous items and duplicate items. The former two could either cause or hide a design flaw and cause synthesis-simulation mismatch. The latter is just messy and makes the code harder to understand and maintain.
In recent versions of VHDL and (System)Verilog, new constructs have been introduced to remove the need of exhaustive sensitivity lists.
In VHDL'2008, the
all keyword was introduced for combinatorial processes. Using
process(all) is equivalent to listing all inputs to the process in the sensitivity list.
This ensures that the process is sensitive to all relevant signals. As a result, synthesis-simulation mismatch is avoided.
process(all) -- equivalent to process(a, b) begin foo <= a + b; end process;
But what about side effects, when signals are read by a procedure or impure function that are not parameters of the procedure or impure function?
Note that this is bad design practice, it is recommended that functions and procedures get all of their inputs through parameters.
all keyword however also covers those signals.
This makes the
all keyword safe to use even with procedures and impure functions.
entity sense is port( a, b : in integer; y1, y2 : out integer ); end entity sense; architecture RTL of sense is begin p1 : process(all) is impure function func_plus(t1 : integer) -- impure function: reads `b` directly (bad practice!) return integer is begin return t1 + b; end function func_plus; begin -- process(all) includes a and b y1 <= func_plus(a); -- even though b is not visible in the process body end process p1; p2 : process(all) is procedure proc_plus is -- procedure reads a and b directly (bad practice!) begin y2 <= a + b; end procedure proc_plus; begin -- process(all) includes a and b proc_plus; -- even though a and b are not visible in the end process p2; -- process body end architecture RTL;
Performance-wise, the use of
process(all) instead of a sensitivity
list with all inputs is not expected to make much difference.
Synthesis tools generally ignore the sensitivity list
already. Simulators may take a negligible amount of extra time during
compilation to determine the actual sensitivity list, but the
simulation speed is not expected to be affected.
Sigasi Studio warns for incomplete sensitivity lists and sensitivity lists with superfluous or duplicate entries. A quick fix is available to sanitize sensitivity lists.
A similar construct was introduced in Verilog'2001. In Verilog, an asterisk (
*) denotes that the
always block triggers when any input signal changes.
always @* // triggers on transitions of a and b y = a + b;
An important caveat is that
always @* does not cover inputs which are read directly by subprograms (i.e. functions and tasks).
In the example below, the
always @* block is triggered by transitions of
a but not by transitions of
module sense( input integer a, b, output integer y ); function integer fun_add(input integer t1); // function reads b directly, not as a parameter begin // (bad practice!) fun_add = t1 + b; end endfunction always @* begin // always block triggers on transitions of a only y = fun_add(a); // A transition of b won't trigger an update of y! end endmodule
As can be seen on the simulation waveforms,
y gets updated on transitions of
a but not on transitions of
This shows that Verilog’s
always @* construct should be used with great care.
This also illustrates why reading inputs other than through the parameter list is bad practice.
SystemVerilog has an improved solution over plain Verilog: the
always_comb block. An important advantage of
always @* is that it is sensitive to changes of variables read
directly inside a function (but not in a task). This removes a
potential source of confusion (and design flaws).
module sense( // SystemVerilog input integer a, b, output integer y ); function integer fun_add(input integer t1); // function reads b directly, not as a parameter begin // (bad practice!) fun_add = t1 + b; end endfunction always_comb begin // always_comb block triggers on transitions of y = fun_add(a); // a and b end endmodule
The use of
always_comb has more advantages (a.o. automatic execution
at time 0 and protection against writing variables from multiple
always_comb blocks). In SystemVerilog,
always_comb is always
VHDL, Verilog and SystemVerilog offer constructs that remove the need of writing exhaustive sensitivity lists for combinatorial processes. Apart from easier coding, the risk of synthesis-simulation mismatch due to an incorrect sensitivity list is reduced. These constructs are meant in particular for combinatorial processes.
- Case statements in VHDL and (System)Verilog (blog post)
- Actual? Formal? What do they mean? (blog post)
- Sigasi's Software Development Kit Part 2 (blog post)
- Sigasi's Software Development Kit Part 1 (blog post)
- Prefix all signals in an instantiation (blog post)