Sequential Logic in HDL
Clocked always blocks and rising_edge, blocking versus non-blocking assignment, reset styles, counters, shift registers and parameters/generics.
1. Adding memory: the clocked process
Chapter 8 described circuits whose outputs depend only on present inputs. Almost every useful design also needs to remember: a count, a state, the previous value of a signal. In an FPGA, memory means the D flip-flop of Chapter 3 β on each rising edge of the clock it copies its input D to its output Q, and holds that value until the next edge. Each logic slice of the Zynq has eight of them, 106Β 400 in all on the PYNQ-Z2, each with a clock-enable and a set/reset input.
You describe flip-flops with a block that reacts only to the clock edge. In Verilog the event list is @(posedge clk); in VHDL the process is sensitive to clk alone and everything inside is wrapped in if rising_edge(clk) then. Every signal assigned inside such a block becomes the output of a flip-flop.
// dff: the basic register - a D flip-flop with clock enable and synchronous reset.
module dff (
input wire clk,
input wire rst, // synchronous, active high
input wire en, // clock enable
input wire d,
output reg q = 1'b0 // power-up value
);
always @(posedge clk) begin // only at the rising edge of clk
if (rst)
q <= 1'b0;
else if (en)
q <= d;
// no else: when en = 0, q keeps its value - that is what a flip-flop does
end
endmoduleCompare this with the latch of Chapter 8. There, a missing else was a bug, because it asked for memory in a block that should have none. Here, the missing else is exactly what we want: when en = 0 nothing is assigned, so q keeps its value β and in a clocked block that is implemented by the flip-flop's clock-enable pin, at no extra cost. The if (rst)that comes first maps onto the flip-flop's synchronous reset pin.
In the timing diagram the inputs change between edges, as they would when driven by other flip-flops. At each rising edge, q takes the value d had just before the edge β if en was 1 and rst was 0. When en drops, the 0 on d is ignored; when rst is 1 at an edge, q clears even though d = 1.
2. Two kinds of assignment
Verilog has two assignment operators inside always blocks, and choosing the wrong one is the most common bug in sequential Verilog.
- Blocking
a = b;β like a software assignment:achanges immediately, and the next statement in the block already sees the new value. - Non-blocking
a <= b;β the right-hand side is evaluated now, but the update is scheduled for the end of the time step. Every statement in the block reads the values from before the edge. This is exactly what a bank of flip-flops does: all of them sample their inputs at the same instant and change together.
VHDL has the same distinction under different names. A signal assignment s <= x;behaves like Verilog's non-blocking assignment: the signal keeps its old value until the process suspends. A variable (declared inside the process, assigned with :=) behaves like a blocking assignment: it changes immediately.
| Verilog | VHDL | Updated | Use for | |
|---|---|---|---|---|
| "Register" semantics | a <= b; | signal: a <= b; | after all statements | clocked logic |
| "Software" semantics | a = b; | variable: a := b; | immediately | combinational logic, temporaries |
Here is the classic demonstration: a three-stage shift register, where each stage copies the one before it. Written with non-blocking assignments (signals in VHDL), every stage reads its neighbour's oldvalue and the data moves one stage per clock edge:
// shift3: a 3-stage shift register, written correctly with non-blocking <=.
module shift3 (
input wire clk,
input wire d,
output reg q1 = 1'b0,
output reg q2 = 1'b0,
output reg q3 = 1'b0
);
always @(posedge clk) begin
q1 <= d; // all right-hand sides are read BEFORE the edge ...
q2 <= q1; // ... so q2 gets the OLD q1
q3 <= q2; // ... and q3 the OLD q2
end
endmoduleNow the same three lines with blocking assignments (variables in VHDL). The first line updates q1 immediately, the second copies that new value into q2, and so on: all three become d at every edge.
// shift3_bad: the same code with blocking = . Each line sees the line above.
module shift3_bad (
input wire clk,
input wire d,
output reg q1 = 1'b0,
output reg q2 = 1'b0,
output reg q3 = 1'b0
);
always @(posedge clk) begin
q1 = d; // q1 updated immediately ...
q2 = q1; // ... so q2 gets the NEW q1, i.e. d
q3 = q2; // ... and q3 gets d as well: one stage, not three
end
endmodule`timescale 1ns/1ps
// Feed the same bit stream into shift3 and shift3_bad and print both.
module shift3_tb;
reg clk = 0, d = 0;
wire a1, a2, a3, b1, b2, b3;
reg [7:0] stream = 8'b0000_0101; // sent LSB first: 1,0,1,0,0,...
integer i;
shift3 good (.clk(clk), .d(d), .q1(a1), .q2(a2), .q3(a3));
shift3_bad bad (.clk(clk), .d(d), .q1(b1), .q2(b2), .q3(b3));
always #5 clk = ~clk;
initial begin
$display("edge d | <= q1 q2 q3 | = q1 q2 q3");
for (i = 0; i < 6; i = i + 1) begin
d = stream[i];
@(posedge clk); #1;
$display(" %0d %b | %b %b %b | %b %b %b", i + 1, d, a1, a2, a3, b1, b2, b3);
end
$finish;
end
endmoduleedge d | <= q1 q2 q3 | = q1 q2 q3 1 1 | 1 0 0 | 1 1 1 2 0 | 0 1 0 | 0 0 0 3 1 | 1 0 1 | 1 1 1 4 0 | 0 1 0 | 0 0 0 5 0 | 0 0 1 | 0 0 0 6 0 | 0 0 0 | 0 0 0
With <= the pattern 1,0,1 walks diagonally down the three columns, one stage per edge. With = all columns are identical. Both simulations agree, and synthesis agrees with them: the blocking version really does build a single register. The code compiles without error β the bug is in the meaning.
<= in every always @(posedge clk) block and = in every always @(*) block, and never mix them in one block. In VHDL, use signals for anything that must be a register or must be seen outside the process; use variables only as short-lived intermediate values inside one process, assigned before they are read (as the feedback variable in the LFSR below). Following these rules, the order of statements in a clocked block stops mattering.3. Reset: synchronous or asynchronous
A reset forces registers to a known state. There are two styles, and both languages express the difference by where the reset test sits relative to the clock edge:
- Synchronous (the
dffabove): the reset is just another input sampled at the clock edge. The process is sensitive toclkonly, andif (rst)sits inside the edge test. - Asynchronous: the reset acts immediately, without waiting for an edge. In Verilog
rstis added to the event list (@(posedge clk or posedge rst)); in VHDL it is added to the sensitivity list and tested beforerising_edge.
// dff_async: the same register with an ASYNCHRONOUS reset (for comparison).
module dff_async (
input wire clk,
input wire rst, // asynchronous: acts immediately, without a clock edge
input wire en,
input wire d,
output reg q
);
always @(posedge clk or posedge rst) begin // rst is in the event list
if (rst)
q <= 1'b0;
else if (en)
q <= d;
end
endmoduleThe testbench regs_tb (listed at the end of Section 6, once all the parts it tests have been introduced) raises rst at 22Β ns, between the clock edges at 15 and 25Β ns. One nanosecond later the asynchronous flip-flop has already cleared; the synchronous one clears at the 25Β ns edge:
t=23 ns rst=1 sync q=1 async q=0 t=26 ns after edge sync q=0 async q=0 counter after 20 cycles = 4, wraps = 1 PASS: reset timing, counter wrap, delay of 3 cycles
On Xilinx 7-series devices (the Zynq's programmable logic) the recommendation is: use a synchronous, active-high reset, and only where it is needed. The reasons are concrete:
- The registers inside DSP48 slices and block RAM outputs support only synchronous reset. A design with asynchronous resets cannot pack its pipeline registers into them and must use fabric flip-flops instead.
- Releasing an asynchronous reset is itself an asynchronous event. If it happens close to a clock edge, some flip-flops leave reset one cycle before others (a recovery/removal violation, the reset-pin equivalent of setup/hold). A synchronous reset is timed like any other data input.
- Every flip-flop already gets its initial value when the FPGA is configured (Chapter 14), so many registers β pipeline stages, shift registers, data paths β need no reset at all. Fewer reset nets means less routing and more freedom for the tools (an SRL shift register, for example, cannot have a reset).
Active-high matters because the flip-flop's reset pin is active-high; an active-low reset costs an inverter, which is usually absorbed into a LUT but can block packing. If a reset arrives from outside the FPGA (a button), pass it through a two-flip-flop synchroniser like any other asynchronous input.
4. Counters
A counter is a register plus an adder feeding back into it: on each enabled edge, \(q \leftarrow q + 1 \bmod 2^{W}\). Synthesis maps the adder onto the carry chain (Chapter 5), so a 32-bit counter costs 32 flip-flops and runs comfortably at hundreds of megahertz.
// counter: WIDTH-bit up-counter with synchronous reset and clock enable.
module counter #(
parameter WIDTH = 8
) (
input wire clk,
input wire rst, // synchronous, active high
input wire en,
output reg [WIDTH-1:0] q = 0,
output wire wrap // 1 when the next enabled edge wraps to 0
);
always @(posedge clk) begin
if (rst)
q <= 0;
else if (en)
q <= q + 1'b1; // WIDTH bits: rolls over from all-ones to 0
end
assign wrap = en && (q == {WIDTH{1'b1}});
endmoduleThings to notice. In VHDL the count is held in an unsigned signal so that + 1 is defined, and converted to std_logic_vector for the port; the + of numeric_std keeps the width, so rolling over from all ones to zero is automatic, as in Verilog. The wrap output is combinational (it is assigned outside the clocked block): it is high during the last cycle before rolling over, which makes it the natural clock enable for a following, slower counter. {WIDTH{1'b1}} and (others => '1') both build an all-ones constant of the right width, whatever WIDTH is.
always @(posedge q[3]) to get a slower process. Do not: a clock derived from logic travels on ordinary routing with unpredictable skew, and the timing tools cannot verify the interface between the two βclock domainsβ. Keep one clock and use wrap (or any one-cycle pulse) as an enable β Chapter 14 builds a whole board design this way.5. Shift registers and the LFSR
A shift register moves data one position per edge. In HDL it is a single concatenation: {q[W-2:0], d} in Verilog, q(W-2 downto 0) & d in VHDL β the old bits shift up, the new bit enters at the bottom. Shift registers serialise and deserialise data (Chapter 16's UART) and build delays. Xilinx LUTs can also act as 32-bit shift registers (SRL32), so a long delay without reset costs very few resources.
Feed the shift register's input with the XOR of some of its own bits and you get a linear-feedback shift register (LFSR). With the right choice of taps, an \(n\)-bit LFSR steps through all \(2^n - 1\) non-zero states before repeating, in an order that looks random. It costs only \(n\) flip-flops and one XOR, and is used for pseudo-random test patterns, scramblers, CRCs and very cheap counters. The taps are described by a feedback polynomial; for 8 bits, taps at stages 8, 6, 5 and 4 give a maximal sequence:
// lfsr: Fibonacci linear-feedback shift register.
// Default: 8 bits, taps 8,6,5,4 (x^8 + x^6 + x^5 + x^4 + 1), period 255.
module lfsr #(
parameter WIDTH = 8,
parameter [WIDTH-1:0] TAPS = 8'b1011_1000, // bit k-1 set = tap at stage k
parameter [WIDTH-1:0] SEED = 1 // any non-zero value
) (
input wire clk,
input wire rst, // reload SEED
input wire en,
output reg [WIDTH-1:0] state = SEED
);
wire feedback = ^(state & TAPS); // XOR of the tapped bits
always @(posedge clk) begin
if (rst)
state <= SEED;
else if (en)
state <= {state[WIDTH-2:0], feedback}; // shift left, feedback in
end
endmoduleThe Verilog version computes the feedback in one line with the reduction XOR ^(state & TAPS). VHDL-93 has no reduction operators, so the VHDL version XORs the tapped bits in a for loop over a variable β a good example of a variable used correctly: assigned first, read immediately, never needed after the process ends. The loop does not run in time; synthesis unrolls it into an XOR of the selected bits. The alias is needed because a generic of unconstrained type takes the index range of the literal you pass, which for a string such as "10111000" is 0 to 7, the opposite direction.
The testbench instantiates the LFSR twice: once with its defaults and once as a 16-bit LFSR with taps 16, 14, 13, 11 (mask 0xB400) passed as parameters/generics. It counts the edges until each returns to its seed and writes the full 8-bit sequence to a text file.
`timescale 1ns/1ps
// Runs two lfsr instances (8-bit default, 16-bit via parameters), measures
// each period, prints the first states and writes the 8-bit sequence to a file.
module lfsr_tb;
reg clk = 0, rst = 0, en = 1;
wire [7:0] s8;
wire [15:0] s16;
integer n, p8 = 0, p16 = 0, f;
lfsr dut8 (.clk(clk), .rst(rst), .en(en), .state(s8));
lfsr #(.WIDTH(16), .TAPS(16'hB400), .SEED(16'h0001))
dut16 (.clk(clk), .rst(rst), .en(en), .state(s16));
always #4 clk = ~clk;
initial begin
f = $fopen("lfsr8_verilog.txt", "w");
$write("first states:");
for (n = 1; n <= 70000; n = n + 1) begin
if (n <= 255) $fdisplay(f, "%0d", s8);
if (n <= 12) $write(" %h", s8);
@(posedge clk); #1;
if (p8 == 0 && s8 == 8'h01) p8 = n; // back to the seed
if (p16 == 0 && s16 == 16'h0001) p16 = n;
end
$fclose(f);
$display("\nperiod 8-bit : %0d (expected 255)", p8);
$display("period 16-bit: %0d (expected 65535)", p16);
if (p8 == 255 && p16 == 65535) $display("PASS: both LFSRs are maximal length");
else $display("FAIL");
$finish;
end
endmodulefirst states: 01 02 04 08 11 23 47 8E 1C 38 71 E2 period 8-bit : 255 (expected 255) period 16-bit: 65535 (expected 65535) PASS: both LFSRs are maximal length
The two sequence files written by Icarus and GHDL are byte-for-byte identical.
6. Parameters, generics and generate
A parameter (Verilog) or generic (VHDL) is a constant that can be set differently for each instance. It is resolved when the design is elaborated, before synthesis, so it costs nothing in hardware: counter #(.WIDTH(4)) and counter #(.WIDTH(32)) are two different circuits generated from one description.
| Verilog | VHDL | |
|---|---|---|
| Declare | module m #(parameter N = 8) (...) | entity m is generic (N : positive := 8); port (...) |
| Override | m #(.N(16)) u (...); | u : entity work.m generic map (N => 16) port map (...); |
| Local constant | localparam W = $clog2(N); | constant W : natural := ...; |
| Vector width from it | reg [N-1:0] r; | signal r : unsigned(N - 1 downto 0); |
| Replicate hardware | generate for (...) begin : name | name : for k in ... generate |
| Optional hardware | generate if (COND) ... | name : if COND generate |
A generate loop replicates hardware: concurrent statements or instances, one copy per iteration. It is not a loop in time. The delay line below chains N copies of dff by connecting stage k's output to stage k+1's input through the vector tap. The labels (stage) are required in VHDL and strongly recommended in Verilog; the instances get hierarchical names such as stage[2].u (Verilog) or stage(2).u (VHDL), which you will see in Vivado's netlist and in the simulator.
// delay_line: N copies of dff in a chain, built with a generate loop.
module delay_line #(
parameter N = 4
) (
input wire clk,
input wire d,
output wire q // d delayed by N clock cycles
);
wire [N:0] tap; // tap[0] = input, tap[k] = output of stage k
assign tap[0] = d;
genvar k;
generate
for (k = 0; k < N; k = k + 1) begin : stage
dff u (.clk(clk), .rst(1'b0), .en(1'b1), .d(tap[k]), .q(tap[k + 1]));
end
endgenerate
assign q = tap[N];
endmoduleFor a simple shift register you would normally write q <= {q[N-2:0], d}; directly; the generate form shows how to replicate instances, which you need when the repeated block is more than a single flip-flop (a bank of filters, one UART per channel, β¦). The testbench below checks, among other things, that a single-cycle pulse comes out of delay_line #(.N(3)) exactly three edges later.
`timescale 1ns/1ps
// Checks dff vs dff_async (reset timing), counter #(4) and delay_line #(3).
module regs_tb;
reg clk = 0, rst = 0, en = 1, d = 1;
wire qs, qa, wrap, dl;
wire [3:0] cnt;
integer errors = 0, wraps = 0, i;
dff u_sync (.clk(clk), .rst(rst), .en(en), .d(d), .q(qs));
dff_async u_async (.clk(clk), .rst(rst), .en(en), .d(d), .q(qa));
counter #(.WIDTH(4)) u_cnt (.clk(clk), .rst(rst), .en(en), .q(cnt), .wrap(wrap));
delay_line #(.N(3)) u_dl (.clk(clk), .d(d), .q(dl));
always #5 clk = ~clk; // edges at 5, 15, 25, ... ns
always @(posedge clk) if (wrap) wraps = wraps + 1;
initial begin
// Reset styles: raise rst at 22 ns, between the edges at 15 and 25 ns
#22 rst = 1;
#1 $display("t=%0d ns rst=1 sync q=%b async q=%b", $time, qs, qa);
if (qs !== 1'b1 || qa !== 1'b0) errors = errors + 1; // async already cleared
@(posedge clk); #1;
$display("t=%0d ns after edge sync q=%b async q=%b", $time, qs, qa);
if (qs !== 1'b0) errors = errors + 1; // sync clears at the edge
rst = 0;
// Counter: 20 enabled cycles from 0 -> 20 mod 16 = 4, one wrap
repeat (20) @(posedge clk);
#1 $display("counter after 20 cycles = %0d, wraps = %0d", cnt, wraps);
if (cnt !== 4'd4 || wraps != 1) errors = errors + 1;
// Delay line: a single 1-cycle pulse must come out exactly 3 cycles later
d = 0; repeat (4) @(posedge clk);
@(negedge clk) d = 1; // sampled at the next edge (edge 1)
for (i = 1; i <= 4; i = i + 1) begin
@(posedge clk); #1;
d = 0;
if (dl !== (i == 3)) errors = errors + 1; // high after edge 3 only
end
if (errors == 0) $display("PASS: reset timing, counter wrap, delay of 3 cycles");
else $display("FAIL: %0d errors", errors);
$finish;
end
endmodule7. Python: the LFSR model and its period
The program below is a cycle-accurate model of the lfsr module: one call to lfsr_step is one clock edge, written with the same mask-and-XOR as the Verilog. It reproduces the first twelve states printed by both testbenches, measures the 8-bit and 16-bit periods, and then tries all 128 tap masks that include stage 8 to show that most choices give short cycles. (Run locally next to the testbench output files, it also compares all 255 states with the HDL simulations β they match.)
Click Run to execute the Python code
Code will be executed with Python 3 on the server
Only 16 of the 128 tap sets are maximal. That number is not an accident: maximal-length LFSRs correspond to primitive polynomials over GF(2), and there are \(\varphi(2^8-1)/8 = 128/8 = 16\) of them in degree 8. The left plot shows the register's defining property β each bit is the bit below it, one edge later β while the bottom row, the feedback, looks random. Tables of maximal taps for every width up to 168 bits are published (for example in Xilinx application note XAPP052), so in practice you look them up.
8. Summary: a template for every clocked block
| Rule | Verilog | VHDL (VHDL-93 compatible) |
|---|---|---|
| Clocked block | always @(posedge clk) begin ... end | process (clk) begin if rising_edge(clk) then ... end if; end process; |
| Assignment | non-blocking <= | signal <= (variables only as temporaries) |
| Reset | if (rst) ... else ... inside the edge | if rst = '1' then ... else ... inside the edge |
| Hold value | just do not assign (becomes CE) | just do not assign (becomes CE) |
| Initial value | reg [7:0] r = 8'd0; | signal r : ... := (others => '0'); |
| Sizes | parameter, localparam, $clog2 | generic, constant, range |
| Replication | generate for ... begin : label | label : for ... generate |
With Chapters 7β9 you can describe any synchronous digital circuit: combinational logic between registers, all clocked by one clock. Part IV turns to verification β writing testbenches systematically (Chapter 10), reading waveforms (Chapter 11) β and to the most important sequential structure of all, the finite state machine (Chapter 12).
Practice Problems
counter #(.WIDTH(12)); (b) delay_line #(.N(5)); (c) the default lfsr; (d) shift3_bad, once synthesised?counter is enabled on every cycle of the 125 MHz PYNQ-Z2 clock. How often is wrap high, and how long after reset is it high for the first time?always @(posedge clk) begin tmp = a + b; y = tmp >> 1; end.regs_tb, rst rises at 22 ns and the clock edges are at 5, 15, 25 ns. Explain the printed values of q at 23 ns and 26 ns for dff and dff_async. Then describe what could go wrong on the board if an asynchronous reset is released 50 ps before a clock edge.