On Day 6 you built combinational logic — outputs that change instantly with the inputs. But real designs need memory: registers, counters, state machines, pipelines. That requires sequential logic — the other half of digital design. This lesson explains the D flip-flop, the clock, synchronous and asynchronous resets, and setup/hold time. You'll build a complete 4-bit up-counter with a self-checking testbench.
Output = f(inputs right now). No clock. No memory. Change an input → output changes immediately. Gates, muxes, adders.
Output = f(inputs + stored state). Changes only at the clock edge. Has memory. Flip-flops, registers, counters, FSMs.
Every real digital system uses both: sequential registers to hold state, combinational logic to compute what state comes next.
The most basic sequential element. On every rising clock edge, the output Q captures the value of input D. Between edges, Q holds its value no matter what D does.
In Verilog, a D flip-flop is written with always @(posedge clk). The synthesis tool maps this directly to a DFF cell on the FPGA fabric.
module dff (
input wire clk,
input wire d,
output reg q
);
always @(posedge clk)
q <= d; // non-blocking: captures d at rising edge
endmodule
Use <= (non-blocking) inside clocked always blocks. It schedules updates to happen after all right-hand sides are evaluated — correctly modelling that all flip-flops capture their input simultaneously on the clock edge. Using blocking = in clocked blocks causes simulation races that don't match hardware behaviour.
| Assignment | Where to use | Behaviour |
|---|---|---|
| <= (non-blocking) | always @(posedge clk) | All RHS evaluated first, then all LHS updated — flip-flop model |
| = (blocking) | always @(*) combinational | Each statement executes sequentially — like software |
Every flip-flop needs a way to start in a known state. Two styles:
Reset only applies on the clock edge. Checked inside the always block as an ordinary if. Easier to meet timing. Preferred on FPGAs.
Reset applies immediately — listed in the sensitivity list. Faster response but needs a reset synchronizer to avoid metastability on release. Common in ASIC design.
// Synchronous reset — rst checked at clock edge only
module dff_sync_rst (input clk, input rst, input d, output reg q);
always @(posedge clk) begin
if (rst) q <= 1'b0;
else q <= d;
end
endmodule
// Asynchronous reset — rst acts immediately (in sensitivity list)
module dff_async_rst (input clk, input rst, input d, output reg q);
always @(posedge clk or posedge rst) begin
if (rst) q <= 1'b0;
else q <= d;
end
endmodule
On FPGAs (Xilinx, Intel/Altera) the built-in flip-flop cells have a dedicated synchronous set/reset input — a synchronous reset maps cleanly to that cell and meets timing more easily.
A flip-flop only captures data reliably if the data signal is stable for a small window around the clock edge:
Violate either → the flip-flop may go metastable and output a random value. Your synthesis + place-and-route tools run Static Timing Analysis (STA) after every build to guarantee all paths meet setup and hold across the entire design. That's why running "implementation" on Vivado or Quartus takes time — it's placing and routing wires to satisfy these constraints.
The maximum clock frequency your design can run at is set by the longest combinational path between two flip-flops — the critical path. A shorter critical path = higher clock frequency.
The classic first sequential module. It increments by 1 every clock cycle, wraps from 15 back to 0, and can be reset synchronously. Ports:
| Port | Dir | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | clock — count increments on rising edge |
| rst | input | 1 | synchronous reset — drives count to 0 on next clock edge |
| en | input | 1 | enable — only counts when en=1; holds when en=0 |
| count | output | 4 | current counter value, 0–15 |
// 4-bit synchronous up-counter with enable and synchronous reset
module counter (
input wire clk, // clock
input wire rst, // synchronous reset (active high)
input wire en, // count enable
output reg [3:0] count // 4-bit counter output (0..15)
);
always @(posedge clk) begin
if (rst) count <= 4'b0000; // reset to 0
else if (en) count <= count + 1; // wrap 15->0 automatically (4-bit overflow)
end
endmodule
Note the 4-bit overflow: when count is 4'b1111 (15) and count + 1 is computed, it naturally wraps to 4'b0000 — no extra logic needed. That's the beauty of fixed-width arithmetic in Verilog.
`timescale 1ns/1ps
module tb_counter;
reg clk, rst, en;
wire [3:0] count;
counter dut (.clk(clk), .rst(rst), .en(en), .count(count));
initial clk = 0;
always #5 clk = ~clk; // 10 ns period = 100 MHz
integer errors = 0;
task check(input [3:0] exp);
if (count !== exp) begin
$display("FAIL: count=%0d expected=%0d", count, exp);
errors = errors + 1;
end else
$display("ok: count=%0d", count);
endtask
integer i;
initial begin
// --- reset ---
rst=1; en=0; @(posedge clk); #1;
check(4'd0); // after reset = 0
// --- count 1..8 with en=1 ---
rst=0; en=1;
for (i=1; i<=8; i=i+1) begin
@(posedge clk); #1;
check(i[3:0]);
end
// --- hold at 8 (en=0) ---
en=0; @(posedge clk); #1;
check(4'd8); // should not advance
// --- count 9..15 then wrap to 0 ---
en=1;
for (i=9; i<=15; i=i+1) begin
@(posedge clk); #1;
check(i[3:0]);
end
@(posedge clk); #1;
check(4'd0); // wrapped back to 0
// --- mid-count reset ---
@(posedge clk); #1; // count is now 1
rst=1; @(posedge clk); #1;
check(4'd0); // synchronous reset applied
if (errors==0) $display("ALL TESTS PASSED");
else $display("%0d TEST(S) FAILED", errors);
$finish;
end
endmodule
iverilog -o counter_tb counter.v tb_counter.v vvp counter_tb
Expected output:
ok: count=0 ok: count=1 ok: count=2 ok: count=3 ok: count=4 ok: count=5 ok: count=6 ok: count=7 ok: count=8 ok: count=8 ok: count=9 ok: count=10 ok: count=11 ok: count=12 ok: count=13 ok: count=14 ok: count=15 ok: count=0 ok: count=1 ok: count=0 ALL TESTS PASSED
A mirror (combinational logic) reflects whatever is in front of it right now — change the scene, the reflection changes instantly. A photograph (flip-flop) captures the scene at one moment (the clock edge) and holds that image until the next shot. No matter how the inputs change between clock edges, the flip-flop's output is frozen from the last snapshot.
D at each rising clock edge: always @(posedge clk).<= in clocked blocks; use blocking = in combinational blocks.ALL TESTS PASSED.<= instead of = inside always @(posedge clk)?Logic whose output depends on current inputs AND stored state from previous clock cycles. Changes only at clock edges. Flip-flops are the building block.
Synchronous: reset takes effect on the next clock edge — simpler timing. Asynchronous: reset takes effect immediately, regardless of clock — needs a reset synchronizer.
Data must be stable for setup time before and hold time after the clock edge. Violation causes metastability. STA tools verify all paths meet these constraints.
It triggers only on the rising clock edge, matching real flip-flop hardware behaviour. The synthesizer maps it directly to DFF cells.
<= schedules all updates to happen after all RHS are evaluated — correctly models simultaneous flip-flop capture on a clock edge. Use it in all clocked always blocks.