Sequential logic, registers, counters, and reset

FPGA design · Building blocks · Jul 2026

Combinational logic computes, but it cannot wait or count or hold a result, because it has no memory. The moment a design needs to remember something on purpose it reaches for the flip-flop, and it needs a clock to say when to remember. Circuits built this way are sequential, and nearly every useful FPGA design is a lot of combinational logic threaded between flip-flops that capture results on a shared clock edge. This page writes the common clocked blocks, settles the one syntax rule that trips up newcomers, and lays out the reset discipline that keeps a design predictable.

always_ff and the nonblocking assignment

Clocked logic is written in an always_ff block that triggers on a clock edge, and inside it you use the nonblocking assignment, written <=. Nonblocking means every assignment in the block reads its right-hand side using the old values, then all the left-hand sides update together at the edge. That is exactly how real flip-flops behave, all sampling their inputs at the same instant, so the rule to remember is simple. Sequential logic in always_ff uses nonblocking <=, and combinational logic in always_comb uses blocking =. Mixing them is the classic source of a design that simulates one way and synthesizes another.

register.sv// a WIDTH-bit register with synchronous reset and a clock enable.
// rst is checked on the clock edge, so the reset is synchronous.
// en gates whether the register updates, without touching the clock.
module register #(
    parameter int WIDTH = 8
)(
    input  logic             clk,
    input  logic             rst,
    input  logic             en,
    input  logic [WIDTH-1:0] d,
    output logic [WIDTH-1:0] q
);
    always_ff @(posedge clk) begin
        if (rst)      q <= '0;      // clear to zero on reset
        else if (en)  q <= d;       // otherwise load d only when enabled
        // when neither holds, q keeps its value, which is what a register does
    end
endmodule

Two details in that block matter for the rest of the series. The reset is tested inside the clocked block, so it takes effect on a clock edge, which makes it a synchronous reset. The enable is an ordinary input that decides whether to load, so the register pauses without anyone stopping its clock. Pausing logic with an enable rather than by gating the clock is a rule with very few exceptions, because a clock that passes through logic picks up delay and glitches that the timing tools then struggle to reason about.

Counters

A counter is a register that adds one to itself each time it is enabled, so it is the combinational adder from the previous page wired back into a flip-flop. Counters are everywhere. They generate addresses, measure time, and sequence through steps. This one counts up, wraps to zero on its own when it reaches the top, and can be loaded to a specific value, which covers most of what a counter is asked to do.

counter.svmodule counter #(
    parameter int WIDTH = 8
)(
    input  logic             clk,
    input  logic             rst,
    input  logic             en,     // advance only when high
    input  logic             load,   // load din instead of counting
    input  logic [WIDTH-1:0] din,
    output logic [WIDTH-1:0] count,
    output logic             tc      // terminal count, high on the last value
);
    assign tc = en && (count == {WIDTH{1'b1}});

    always_ff @(posedge clk) begin
        if (rst)       count <= '0;
        else if (load) count <= din;
        else if (en)   count <= count + 1'b1;   // wraps naturally at the top
    end
endmodule

The terminal count output tc is combinational, a plain comparison of the current value against all ones, and it is high on the cycle the counter holds its last value. A wider counter used as a timer often exposes this so that a later stage knows an interval has elapsed. Notice the counter wraps for free, because adding one to the all-ones value overflows back to zero in fixed-width arithmetic, so no special case is needed.

Shift registers

A shift register is a chain of flip-flops where each one passes its value to the next on every clock, so data marches through one step at a time. It is how a design serializes a word onto a single wire, deserializes a stream back into a word, or delays a signal by a known number of cycles to line it up with another path. That last use, delaying to align, comes back constantly once the series reaches pipelining.

shift_register.sv// serial in, parallel out. each cycle a new bit enters at the top and the
// whole word shifts down by one, so after WIDTH cycles the word is full.
module shift_register #(
    parameter int WIDTH = 8
)(
    input  logic             clk,
    input  logic             rst,
    input  logic             en,
    input  logic             sin,       // serial input bit
    output logic [WIDTH-1:0] parallel,
    output logic             sout        // serial output, the bit falling off
);
    assign sout = parallel[0];

    always_ff @(posedge clk) begin
        if (rst)     parallel <= '0;
        else if (en) parallel <= {sin, parallel[WIDTH-1:1]};   // shift down, insert sin at top
    end
endmodule

The concatenation {sin, parallel[WIDTH-1:1]} is the whole idea. It builds the next value from the new input bit followed by all but the lowest bit of the current value, which is a shift by one with an insertion at the top. On the fabric this maps to nothing but the flip-flops already paired with the logic, so a shift register is one of the cheapest sequential structures there is, and short ones can even be packed into a single lookup table configured as a small shift element.

Reset discipline and the single clock

Two design habits keep everything above predictable, and they are worth stating plainly because ignoring them causes failures that are hard to reproduce. The first is to prefer a synchronous reset, tested on the clock edge as the register above does, so that reset is just another synchronous input and the timing tools analyze it like any other. Asynchronous reset has its uses, mostly for bringing a chip up from power-on, but as the everyday reset it invites trouble at the moment it is released, because that release has to be lined up with the clock or a flip-flop can be caught halfway.

The second habit is to build a block around a single clock. When two clocks are genuinely unavoidable, the crossing between them is a real hazard with its own established solution, which is important enough to get its own page later on clock domain crossing. Inside one clock domain, though, every flip-flop samples on the same edge, the timing question is well posed, and the tools can give a clear answer about the fastest the design can run. Almost everything in this series lives inside a single clock domain for exactly that reason.

With combinational logic to compute and sequential logic to remember, the next natural block is the one that decides what to do next based on what it is doing now. That is the finite state machine, and it is the next page.

Next, finite state machines.