Verilog · lesson 7

Sequential Logic and the Clock

The standard flip-flop template, synchronous versus asynchronous reset proven in simulation, an enabled counter, and why reg doesn't always mean a real flip-flop.

11 min read · Free, no login

The template every flip-flop in this course follows

Lesson 5 of the Digital Electronics path built a D flip-flop from two gated latches and explained setup and hold time; this lesson is about describing that same device in Verilog, correctly, every time. Every clocked register in this course follows one template: an always block whose sensitivity list names only the clock edge (and, for an asynchronous reset, the reset edge too), assigning every output with the non-blocking operator <=.

A 4-bit counter with enable and asynchronous reset
module counter4 (
    input  wire       clk,
    input  wire       rst_n,   // active-low asynchronous reset
    input  wire       en,
    output reg  [3:0] count
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            count <= 4'd0;
        else if (en)
            count <= count + 1'b1;
    end
endmodule

Three things about this template are not stylistic preferences; they are the difference between code that synthesizes to the intended hardware and code that does not. First, <= (non-blocking assignment), not = โ€” lesson 8 is built entirely around why, with a worked example of what goes wrong when this is reversed. Second, the sensitivity list names clock and reset edges only, in sharp contrast to lesson 5's always @*, which infers its list from every signal read inside the block โ€” a clocked block's list is deliberately short, because the whole point is that count only changes at a clock edge (or an async reset), not whenever any input happens to change. Third, the reset check (if (!rst_n)) comes first, before the functional logic โ€” this is what makes reset take priority over the counting logic, matching how reset is almost always meant to behave.

Synchronous versus asynchronous reset

The sensitivity list is what decides whether a reset is synchronous or asynchronous, and the difference is a real, observable one in both simulation and silicon. counter4's sensitivity list, @(posedge clk or negedge rst_n), names the reset's falling edge explicitly โ€” the block reacts to rst_n going low immediately, at any point in the clock cycle, without waiting for a clock edge. A synchronous reset instead omits the reset edge from the sensitivity list entirely (@(posedge clk) only) and checks rst_n as an ordinary condition inside the block โ€” it only takes effect at the next clock edge, exactly like any other input to the logic.

time=83000  mid-cycle (last negedge was at 80, next edge is 85), count= 6
time=84000  1ns after rst_n went low, count= 0  (reset took effect with no clock edge in between)

Here rst_n is pulled low at simulation time 83000 โ€” deliberately mid-cycle, with the previous clock edge at 80 and the next one not due until 85. One nanosecond later, with no clock edge having occurred at all, count has already dropped to 0. This is asynchronous reset behaving exactly as its name says: it does not wait for the clock. A synchronous version of the same counter, checked with the identical mid-cycle stimulus, would still read count = 6 at this point and would only reset once the next posedge clk actually arrived.

Neither style is simply "more correct" โ€” real designs use both, for different reasons that are outside this lesson's scope (asynchronous reset reaches every flop immediately regardless of clock activity, which matters at power-up; synchronous reset avoids reset-removal timing hazards and keeps the reset path itself timing-analyzable through ordinary setup/hold checks). What matters for this course is that the sensitivity list is the only thing in the Verilog that decides which one a given block describes, and that the choice has to be deliberate, not accidental.

What synthesis actually builds

Running counter4 through Yosys's synthesis flow confirms the template produces exactly the hardware it reads as: four single-bit flip-flop cells, each with a clock input, an asynchronous active-low reset input, and an enable input ($_DFFE_PN0P_ in Yosys's cell-naming convention โ€” Posedge clock, async reset on a Negedge, reset value 0, enable active-Positive), plus a handful of combinational gates computing the next value to load. There is no ambiguity here: a reg declared this way, driven by a clocked, edge-sensitive always block, becomes real storage โ€” in sharp contrast to lesson 6's always @* blocks, where reg was only ever a procedural-assignment requirement and the result was pure combinational logic with no storage at all. The keyword reg never tells you which one you are looking at; the sensitivity list and the kind of block it belongs to do.

Shift registers: the other standard sequential template

A shift register follows the identical clocked template, with the body doing one thing instead of counting: moving every bit one position and loading a new bit in at the end.

An 8-bit shift register, serial in, serial out
module shift_reg8 (
    input  wire clk,
    input  wire rst_n,
    input  wire serial_in,
    output wire serial_out
);
    reg [7:0] sr;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            sr <= 8'd0;
        else
            sr <= {sr[6:0], serial_in};
    end
    assign serial_out = sr[7];
endmodule

{sr[6:0], serial_in} reuses the concatenation operator from lesson 4: it builds a new 8-bit value from the top seven bits of the current register and the new incoming bit, which is exactly a one-position shift with a new bit loading in at the bottom. A parallel-load version follows the same shape, with an extra load input selecting between shifting and loading a full new value in one step โ€” a direct extension of this same template, not a different one.

Common mistakes

Mixing a reset edge into the sensitivity list of a block that was meant to have a synchronous reset โ€” writing @(posedge clk or negedge rst_n) and then checking rst_n as an ordinary if condition โ€” produces an asynchronous reset whether that was intended or not; the sensitivity list decides this, not how the check reads inside the block. The second is reversing the priority inside the block, checking the enable or data condition before the reset condition โ€” since an if/else if chain only evaluates branches in order, putting reset second means an active enable can mask a simultaneous reset for one cycle. The third, covered in full in the next lesson, is using = instead of <= inside a clocked block โ€” it frequently still simulates correctly for simple logic like a single counter, which is exactly what makes it dangerous once two or more clocked blocks need to exchange values in the same cycle.

Interview questions

What decides whether a reset is synchronous or asynchronous in Verilog? The sensitivity list of the clocked always block. Including the reset's edge in the list (@(posedge clk or negedge rst_n)) makes it asynchronous โ€” it takes effect immediately, independent of the clock. Leaving the reset out of the list and checking it only inside the block as an ordinary condition makes it synchronous โ€” it only takes effect at the next active clock edge.

Does declaring a signal reg guarantee it synthesizes to a flip-flop? No. A reg assigned inside a combinational always @* block (lesson 6) synthesizes to plain combinational logic with no storage. A reg only becomes a real flip-flop when it is assigned inside a clocked, edge-sensitive always block, with the non-blocking operator, as this lesson's template does.

In an if/else if chain inside a clocked block, why does reset usually come first? Branches are checked in order, and only the first matching one executes. Putting the reset check first guarantees it always takes priority over the functional logic, even if the enable or data condition is also true in the same cycle; putting it later would let an active enable mask a simultaneous reset.

Key takeaways

  • A clocked register follows one template: a sensitivity list naming only the clock (and reset) edge, non-blocking assignment (<=) for every output, reset checked first in the branch order.
  • Whether a reset is synchronous or asynchronous is decided entirely by whether its edge appears in the sensitivity list โ€” confirmed here by a mid-cycle reset taking effect with no clock edge having occurred, which only an asynchronous reset does.
  • Synthesis of this template produces a real flip-flop cell per bit ($_DFFE_..._ in Yosys), unlike the pure combinational logic a reg produces in an always @* block โ€” the keyword never tells you which; the block's sensitivity list and clocking do.
  • A shift register uses the identical clocked template, with concatenation ({sr[6:0], bit_in}) doing the actual shifting.