Loading header...

Sequential Logic in Verilog

Sequential logic remembers state. In an FPGA, that usually means flip-flops that sample inputs on a clock edge and update outputs together. This is the main shift from ordinary programming: a Verilog clocked block describes many registers updating in parallel, not statements executing one after another in real time.

Learning Objectives

By the end of this lesson, you should be able to:

  • write clocked Verilog that infers flip-flops;
  • use nonblocking assignments correctly;
  • choose synchronous reset, asynchronous reset, or no reset intentionally;
  • build counters, enables, and edge detectors;
  • avoid derived-clock and clock-domain mistakes.

Flip-Flops from Clocked Blocks

The basic register pattern is:

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

This describes one D flip-flop. On each rising edge of clk, q captures the value of d. Between clock edges, q keeps its previous value.

For a bus, the same pattern creates one flip-flop per bit:

reg [7:0] data_q;

always @(posedge clk) begin
    data_q <= data_d;
end

Nonblocking Assignment

Inside clocked blocks, use nonblocking assignment <=.

always @(posedge clk) begin
    a <= b;
    b <= a;
end

Both registers sample first and update together. The result is a swap. If blocking assignment = were used here, simulation could imply statement-by-statement behavior that does not match the intended flip-flop network.

Counter with Enable and Reset

module counter8 (
    input  wire       clk,
    input  wire       rst,
    input  wire       en,
    output reg  [7:0] count
);
    always @(posedge clk) begin
        if (rst) begin
            count <= 8'd0;
        end else if (en) begin
            count <= count + 8'd1;
        end
    end
endmodule

When en is low, count is not assigned in that clock edge branch. That does not infer a latch because this is a clocked block. It means the flip-flops keep their current values.

Reset Choices

Reset style Verilog pattern Good use Risk
Synchronous reset always @(posedge clk) then if (rst) most FPGA control logic reset must be sampled by clock
Asynchronous reset always @(posedge clk or posedge rst) external reset pins or required primitives reset release can violate timing
No reset register assigned only by normal logic datapaths flushed by protocol startup value must not matter

Prefer synchronous resets for beginner FPGA designs unless the board, IP block, or primitive requires asynchronous reset. Reset control state and user-visible status. Avoid resetting every large datapath register by reflex; resets consume routing and can make timing harder.

Worked Example: Edge Detector

To detect a rising edge, store the previous sampled value and compare it with the current sampled-domain signal.

module edge_rise (
    input  wire clk,
    input  wire rst,
    input  wire signal_sync,
    output wire pulse
);
    reg signal_d;

    always @(posedge clk) begin
        if (rst)
            signal_d <= 1'b0;
        else
            signal_d <= signal_sync;
    end

    assign pulse = signal_sync & ~signal_d;
endmodule

pulse is high for one clock cycle when signal_sync changes from 0 to 1.

title "Illustrative one-clock edge detector"
time start=0 end=10 unit=cycles divisions=10

CLK: square label="clk" low=0 high=1 duty=50 cycles=5 unit=logic color=#2563eb
SIG: step label="signal_sync" low=0 high=1 at=3 unit=logic color=#dc2626
PULSE: pulse label="pulse" low=0 high=1 at=4 width=1 unit=logic color=#16a34a

marker SAMPLE at=4 label="sampled edge"

This waveform is explanatory. In a real simulation, the pulse alignment depends on how the testbench changes signal_sync relative to the clock edge.

Clock Enables, Not Derived Clocks

Do not create a new clock with ordinary logic:

assign slow_clk = counter[20];   // bad clocking habit for general RTL

Use a clock enable instead:

wire tick = (counter == 21'd0);

always @(posedge clk) begin
    if (rst) begin
        led <= 1'b0;
    end else if (tick) begin
        led <= ~led;
    end
end

FPGA clock networks are special resources. Data-path logic used as a clock can create skew, timing, and routing problems.

Worked Example: Terminal Count

A 4-bit counter counts from 0 to 15. A terminal-count flag can be combinational or registered.

module counter4_tc (
    input  wire clk,
    input  wire rst,
    input  wire en,
    output reg  [3:0] count,
    output wire       terminal_count
);
    always @(posedge clk) begin
        if (rst)
            count <= 4'd0;
        else if (en)
            count <= count + 4'd1;
    end

    assign terminal_count = (count == 4'd15);
endmodule

The flag is combinational here, so it reflects the current counter value after the register updates. If downstream logic needs a timing-friendly registered flag, put the comparison inside a clocked block and document whether the flag is early, current, or delayed by one cycle.

Timing View

Most synchronous FPGA paths follow this pattern:

flowchart LR R1["source flip-flop"] --> C["combinational logic"] C --> R2["destination flip-flop"] CLK["same clock"] --> R1 CLK --> R2

The clock period must be long enough for source clock-to-Q delay, combinational delay, routing delay, destination setup time, and clock uncertainty. Static timing analysis checks this after place and route.

Exercise

Write a 4-bit counter with:

  • synchronous reset;
  • enable input;
  • output terminal_count, high when count == 4'd15;
  • a self-checking testbench that verifies reset, hold-when-disabled, increment-when-enabled, wraparound, and terminal-count behavior.

Then implement a second version with a registered terminal_count and explain the one-cycle timing difference.

Common Mistakes

  • Using blocking assignment in clocked logic without a specific reason.
  • Feeding an asynchronous signal directly into edge detection logic.
  • Creating clocks from counters or gates instead of using enables.
  • Resetting huge datapaths that could be flushed by valid bits.
  • Forgetting that all registers update together at the clock edge.
  • Comparing a counter against an unsized constant and ignoring width warnings.

Practical Checks

  • Run simulation around reset release and enable changes.
  • Confirm every register is driven in exactly one clocked block.
  • Check synthesis warnings for inferred latches, multiple drivers, and clocking issues.
  • Review the timing report for the register-to-register path.
  • Name synchronized signals clearly, for example button_sync rather than button.

Summary

Sequential Verilog describes state stored in flip-flops. Use clean posedge clk blocks, nonblocking assignments, deliberate reset strategy, and clock enables. Keep asynchronous inputs out of state logic until they are synchronized. These habits support counters, edge detectors, state machines, FIFOs, and larger FPGA systems.

Next: Finite-State Machines in Verilog.

Further Reading

  • Vendor FPGA HDL coding guidelines for synchronous design.
  • Clifford Cummings papers on nonblocking assignments and reset design.
  • Project F FPGA tutorials on counters, clocks, and simple video timing.
  • Yosys and nextpnr documentation for synthesis and static timing reports.

Mind Map

mindmap root((Sequential Verilog)) Core concept Flip flops store state posedge clk samples data Registers update together State changes per cycle Applications Counters Edge detectors Pipelines Control registers Timers Calculations Four bit count zero to fifteen Terminal count equals count fifteen Clock period limits path delay Enable holds current value Design rules Nonblocking in clocked blocks Prefer clock enables Reset control state Avoid derived clocks Synchronize external inputs Practical checks Simulate reset release Test enable hold Check timing report Search multiple drivers Review width warnings Common mistakes Blocking in flops Async signal direct use Gated clock in LUT Resetting all datapaths Assuming line by line timing