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:
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 whencount == 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_syncrather thanbutton.
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.