Module 5: Behavioral Verilog for Synthesis ยท 5.2 Sensitivity Lists and Combinational always Blocks

The Missing Signal

There is a bug that sits between two facts: the simulator is event-driven, and the synthesiser reads intent rather than running the simulation. Put an incomplete sensitivity list between them and you get a design that passes weeks of simulation, with no syntax error and often no warning, and then does something else in the lab.

The setup

`timescale 1ns / 1ps
`default_nettype none

module and_gate_broken (
    input  wire a,
    input  wire b,
    output reg  y
);

    always @(a) begin      // b is missing
        y = a & b;
    end

endmodule

The trace

Time Event a b Block runs y simulated y correct
0 start 0 0 no x 0
10 a rises 1 0 yes 0 0
20 b rises 1 1 no 0 1
30 a falls 0 1 yes 0 0
40 b falls 0 0 no 0 0
50 a rises 1 0 yes 0 0

At 20 ns b rises. It is not in the list, the block does not run, and y stays at 0 when it should be 1. At 30 ns a falls, the block runs, and y is 0, which is right for these inputs; the transition to 1 that should have happened at 20 ns never did. The simulation is modelling a circuit that remembers its output while only b changes: storage, gated by changes of a.

Only one row of the truth table is wrong, a = 1, b = 1, and only when that combination is reached by b changing last. Arrive at it by changing a last and the block runs and the answer is right. Whether your tests find it depends on the order they happen to toggle the inputs.

What synthesis does

The synthesiser does not run the block. It reads y = a & b, sees both inputs in the expression, and builds an AND gate: the right hardware, and not what you simulated (Figure 1). Some tools, reading the list as intent, build a latch enabled by a instead. Which you get depends on the vendor, and neither matches the simulation.

Simulation against synthesis
Figure 1: The simulator models a hold; the synthesiser builds a gate

There is a third party to the disagreement. Verilator treats any level-sensitive block as combinational and ignores the contents of the list, so the same file simulates one way in Icarus and another in Verilator. When two simulators disagree about your design, the design is the problem.

Seeing it

Lint finds this reliably. Verilator, SpyGlass and the lint modes of the commercial simulators all have a check for a signal that is read inside a block and absent from its sensitivity list, and the message names the signal and the line. Run lint on every file before simulating; it takes seconds.

A testbench that targets the case shows the disagreement directly:

`timescale 1ns / 1ps
`default_nettype none

module tb_and_gate_broken;

    reg  a, b;
    wire y_broken, y_correct;

    and_gate_broken  dut_broken  (.a(a), .b(b), .y(y_broken));
    and_gate_correct dut_correct (.a(a), .b(b), .y(y_correct));

    initial begin
        $dumpfile("tb_and_gate_broken.vcd");
        $dumpvars(0, tb_and_gate_broken);

        a = 0; b = 0;
        #10 a = 1;
        #10 b = 1;          // b changes last: the broken block does not run
        #10 $display("a=1,b=1: broken=%b correct=%b", y_broken, y_correct);
        #10 a = 0;
        #10 b = 0;
        #10 $finish;
    end

endmodule

module and_gate_correct (
    input  wire a,
    input  wire b,
    output reg  y
);
    always @(*) begin
        y = a & b;
    end
endmodule
a=1,b=1: broken=0 correct=1

The fix

There is one fix, and it is not adding b to the list:

always @(*) begin
    y = a & b;
end

Adding b leaves the next person to add c with the same trap. @(*) cannot be incomplete.

Why it keeps happening

Old code, textbooks and application notes from before 2001 use manual lists, and people copy them. Editor templates autocomplete always @( with the clock. Not every simulator warns. And keeping a list in sync is exactly the kind of clerical job that slips when the logic is hard. The rule that works is the one with no exceptions: combinational blocks use @(*), always, including the two-input ones.

The next article is the other road to an accidental latch, which a perfect sensitivity list does nothing to prevent: an if without an else.

โ† always @(*) Accidental Latches โ†’