16.5 Sequential RTL: always_ff and Clocked Storage

Write a synthesizable clocked flip-flop in SystemVerilog using always_ff, add an asynchronous active-low reset, and explain why nonblocking assignment is the correct choice inside this block.

Every circuit described so far in this module has been purely combinational — its output depends only on the present inputs, with no memory of the past. Real digital systems need memory: registers that hold a value across clock cycles, counters, state machines, pipelines. Sequential RTL is the Verilog/SystemVerilog style used to describe that memory, and it centers on one construct: the always_ff block, triggered by a clock edge, using nonblocking assignment to update state. This lesson introduces always_ff, walks through the module's own two worked examples (a plain D flip-flop and one with asynchronous reset), and sets up the blocking-vs-nonblocking distinction that 16.6 covers in full depth.

always_ff: Describing an Edge-Triggered Flip-Flop

always_ff @(posedge clk) begin ... end describes a block of logic that only executes at the rising (positive) edge of the clk signal — everywhere else in time, whatever is inside the block simply does not run, and the signals it drives hold their last assigned value. This is the RTL description of a D flip-flop's defining behavior: capture the input value at the clock edge, hold it steady until the next edge. posedge names the rising-edge trigger explicitly; negedge (used later for the reset signal in this same lesson) names a falling-edge trigger the same way.

The keyword always_ff is a SystemVerilog addition specifically intended for describing flip-flops: like always_comb, it lets synthesis and simulation tools apply extra checks confirming the block truly describes edge-triggered sequential hardware (for example, flagging a block that looks like it might synthesize to a latch or combinational logic instead). Using always_ff for sequential logic and always_comb for combinational logic, rather than the older generic always block for everything, is now considered standard RTL practice because it makes design intent explicit and catches a wider range of mistakes at compile time.

Step by Step: The Plain D Flip-Flop

The module's simplest sequential example is exactly three lines: always_ff @(posedge clk) begin q <= d; end. Read this as: 'every time clk rises, capture whatever value d currently holds and store it into q.' Between clock edges, nothing in this block executes at all — q simply continues holding whatever was last captured, which is precisely the defining behavior of a D flip-flop and the fundamental storage primitive underneath registers, counters, and state machines throughout this entire course module.

Notice the assignment operator used inside this block is <=, not = — this is nonblocking assignment, and using it here (rather than blocking assignment) is not a style preference but a functional requirement for correctly modeling clocked hardware, as the next section and lesson 16.6 explain.

Adding Reset: Asynchronous, Active-Low

Real flip-flops almost always need a defined starting state, provided by a reset signal. The module's reset example extends the sensitivity list to @(posedge clk or negedge rst_n): this block now triggers on either a rising clock edge or a falling edge of rst_n, and the if (!rst_n) check inside decides which event caused it to run. Because negedge rst_n is explicitly listed in the sensitivity list alongside posedge clk, this reset is asynchronous — it takes effect immediately when rst_n falls, without waiting for a clock edge, which is exactly the behavior needed to force a chip into a known state as soon as power-good/reset logic asserts it, independent of whether the clock is even running yet.

The signal name rst_n follows a very common RTL naming convention: the trailing _n (or sometimes _b, for 'bar') signals that the reset is active-low, meaning the reset condition is rst_n == 0, not rst_n == 1 — which is exactly why the code checks if (!rst_n) rather than if (rst_n). Active-low resets are extremely common in real chips because many reset-generation and power-sequencing circuits naturally produce an active-low signal, and consistent _n naming across a codebase prevents an entire class of polarity-confusion bugs.

Why Nonblocking Assignment Belongs Here

The module's own tricky-problem note for this lesson states the rule plainly: nonblocking assignment <= is normally used for clocked sequential logic. The intuitive reason is that real flip-flops in a clocked design all sample their inputs at the same instant (the clock edge) and all update their outputs at that same instant — there is no real ordering among them, they are all triggered simultaneously by the same physical clock signal. Nonblocking assignment models this correctly: every right-hand side in an always_ff block is evaluated using the values signals held just before the clock edge, and only after all of those evaluations complete are the left-hand sides actually updated, matching real hardware's simultaneous-update behavior exactly.

This becomes essential the moment one flip-flop's output feeds another flip-flop's input in the same clock-edge block (as in a shift register or pipeline stage) — 16.6 works through exactly why blocking assignment produces the wrong result in that situation, while nonblocking assignment produces the physically correct one.

Sequential Block with Asynchronous Active-Low Reset

posedge/negedge name the triggering edge of a signal; listing a signal in the sensitivity list without one of these keywords means level-sensitive triggering (used for combinational logic, not here). Listing negedge rst_n alongside posedge clk in the same sensitivity list is what makes this reset asynchronous — it fires independent of the clock. Inside always_ff, use nonblocking assignment (<=) for every assignment, including the reset branch — never mix = into a sequential block.

always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        q <= 1'b0;   // asynchronous reset, active low
    else
        q <= d;      // normal clocked update
end

Code Examples

This is the smallest possible clocked storage element: on every rising edge of clk, q is loaded with whatever d currently holds; between edges, q holds steady regardless of what d does. Note q is declared logic (a variable type), required because it's the assignment target of a procedural block — exactly the same rule that applied to y_case in the previous lesson. Expected result: q always reflects the value d had at the most recent rising clock edge, and only changes at that instant.

The sensitivity list now includes both posedge clk and negedge rst_n, so the block runs whenever either occurs. if (!rst_n) checks whether rst_n is currently 0 (asserted); if so, q is forced to 1'b0 immediately, regardless of the clock. Otherwise (rst_n is 1, i.e. reset is deasserted), the block behaves exactly like the plain D flip-flop, capturing d into q on the rising clock edge. Because negedge rst_n is in the sensitivity list, this reset takes effect the instant rst_n falls — it does not wait for the next clock edge, which is what makes it asynchronous rather than synchronous.

always_ff @(posedge clk or negedge rst_n) begin

module dff (
    input  wire clk,
    input  wire d,
    output logic q
);

always_ff @(posedge clk) begin
    q <= d;
end

endmodule
module dff_reset (
    input  wire clk,
    input  wire rst_n,   // active-low asynchronous reset
    input  wire d,
    output logic q
);

always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        q <= 1'b0;
    else
        q <= d;
end

endmodule

D flip-flop timing waveform with asynchronous reset

q updates on each posedge clk to match d — except when rst_n falls, which forces q to 0 the instant it happens, independent of the clock.

clk    __|‾|__|‾|__|‾|__|‾|__|‾|__
d      ───────0───1───1───────────
rst_n  ‾‾‾‾‾‾‾‾‾‾‾‾‾‾\_____/‾‾‾‾‾‾
q      ───────0───1───\0/──1──────
                        ^
                        rst_n falls: q forced to 0
                        immediately, no clock edge needed

Important Rules

always_ff @(posedge clk) describes an edge-triggered flip-flop: the block runs only at the named clock edge, holding state at all other times.

negedge/posedge name an edge trigger; a signal listed in a sequential sensitivity list without one of these describes level-sensitive (latch-like) behavior instead.

Including a reset edge (e.g. negedge rst_n) alongside the clock edge in the sensitivity list makes that reset asynchronous — it fires independent of the clock.

A _n (or _b) suffix is the standard RTL naming convention for an active-low signal; check it with if (!signal_n), not if (signal_n).

Nonblocking assignment (<=) is the normal, correct choice for every assignment inside always_ff, modeling the simultaneous sample-and-update behavior of real clocked hardware.

Reset Strategy in Real Chips

Every register on a real chip needs a defined power-on state, and the asynchronous-active-low pattern shown here is one of the most widely used reset architectures in industry, because it lets a single external reset pin or power-on-reset circuit force the entire chip into a known state instantly, without depending on a clock that might not even be stable yet at power-up. Many designs then internally synchronize that asynchronous reset's de-assertion to the clock domain to avoid metastability when reset is released — a refinement built directly on the async-reset flip-flop shown in this lesson.

always_ff @(posedge clk) begin if (!rst_n) q <= 1'b0; else q <= d; end // rst_n omitted from sensitivity list always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) q <= 1'b0; else q <= d; end

If rst_n is only tested inside the block but never listed in the sensitivity list with negedge, the reset becomes synchronous by accident — it only takes effect on the next rising clock edge after rst_n falls, not immediately. That is a real, valid design style (synchronous reset), but it is a different circuit than an asynchronous reset, and writing the if (!rst_n) check while forgetting to add negedge rst_n to the sensitivity list is a common way to end up with unintended synchronous behavior, or a simulation/synthesis mismatch on some tools.

[tricky-problem] Inside an always_ff block describing clocked sequential logic, which assignment operator should normally be used, and why? A. Nonblocking (<=), because it models every flip-flop in the block sampling its input and updating its output simultaneously at the clock edge, matching real hardware B. Blocking (=), because it executes faster in simulation C. Either operator works identically inside always_ff, so the choice is purely stylistic D. Nonblocking (<=) only for the reset branch, and blocking (=) for the normal data-capture branch

A. Nonblocking (<=), because it models every flip-flop in the block sampling its input and updating its output simultaneously at the clock edge, matching real hardware — This is the module's stated tricky problem for this lesson. Real clocked flip-flops all sample and update at the same clock edge, with no inherent ordering among them. Nonblocking assignment reproduces that: every right-hand side is read using pre-edge values, and all left-hand sides update together only after the whole block finishes evaluating — matching physical hardware. Blocking assignment, by contrast, updates immediately and sequentially, which can produce simulation results that don't match the intended hardware once multiple sequential assignments interact (the focus of lesson 16.6).

[interview] What makes the reset in always_ff @(posedge clk or negedge rst_n) asynchronous rather than synchronous? A. negedge rst_n is included directly in the sensitivity list, so the block runs the instant rst_n falls, independent of the clock B. The 1'b0 literal used in the reset branch C. The use of if/else instead of a case statement D. The fact that q is declared as logic rather than wire

A. negedge rst_n is included directly in the sensitivity list, so the block runs the instant rst_n falls, independent of the clock — A reset is synchronous when it is only checked inside the block's logic and takes effect on the next active clock edge; it becomes asynchronous specifically when its edge is also listed in the sensitivity list (here, negedge rst_n), which causes the block — and therefore the reset — to trigger immediately, without waiting for posedge clk.

Why is the reset signal in this lesson's examples named rst_n rather than rst? A. The _n suffix is the standard RTL naming convention indicating the signal is active-low — reset is asserted when it reads 0, not 1 B. Verilog requires all reset signals to end in _n C. _n marks the signal as belonging to the negative supply rail D. It has no meaning; rst_n and rst are always functionally identical

A. The _n suffix is the standard RTL naming convention indicating the signal is active-low — reset is asserted when it reads 0, not 1 — _n (sometimes _b for 'bar') is a widely used naming convention signaling active-low polarity, which is exactly why the code checks if (!rst_n) to detect reset being asserted rather than if (rst_n). Consistent naming like this across a design prevents a very common class of active-high/active-low polarity bugs.

Modify the dff_reset module so the reset is synchronous instead of asynchronous — q should only be forced to 0 on a rising clock edge while rst_n is low, not immediately when rst_n falls.

module dff_sync_reset ( input wire clk, input wire rst_n, input wire d, output logic q ); always_ff @(posedge clk) begin if (!rst_n) q <= 1'b0; else q <= d; end endmodule

A synchronous reset is checked only inside the block's logic, not added to the sensitivity list — remove negedge rst_n from the always_ff sensitivity list entirely, leaving only posedge clk.