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 β€” a flip-flop with synchronous reset and clock enable
// 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
endmodule

Compare 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.

FDREdDenCErstRclkQqclkdenrstq
What the dff code becomes: a single FDRE primitive (flip-flop with clock enable CE and synchronous reset R) β€” no LUT needed. The timing diagram shows q changing only at rising edges: d is ignored while en = 0, and rst clears q at the next edge.

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: a changes 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.

VerilogVHDLUpdatedUse for
"Register" semanticsa <= b;signal: a <= b;after all statementsclocked logic
"Software" semanticsa = b;variable: a := b;immediatelycombinational 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 β€” correct: non-blocking assignment / signals
// 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
endmodule

Now 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 β€” blocking assignment / variables: one stage, not three
// 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
shift3_tb β€” the same bit stream into both versions
`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
endmodule
Output (identical from Icarus and GHDL)
edge 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.

q1 <= d; q2 <= q1; q3 <= q2;dFFq1FFq2FFq3three flip-flops: q3 is d delayed by 3 cyclesq1 = d; q2 = q1; q3 = q2;dFFq1q2q3one flip-flop: q1 = q2 = q3 = d delayed by 1 cycle
What the two versions synthesise to. Non-blocking: three flip-flops in a chain. Blocking: each line sees the value just computed by the line above, so every output is driven by d through one flip-flop.
Key idea: the two assignment rules
In Verilog, use <= 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 dff above): the reset is just another input sampled at the clock edge. The process is sensitive to clk only, and if (rst) sits inside the edge test.
  • Asynchronous: the reset acts immediately, without waiting for an edge. In Verilog rst is added to the event list (@(posedge clk or posedge rst)); in VHDL it is added to the sensitivity list and tested before rising_edge.
dff_async β€” the same register with an asynchronous reset
// 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
endmodule

The 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:

Output of regs_tb (both languages)
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 β€” parameterised up-counter with wrap output
// 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}});
endmodule

Things 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.

Common mistake: using a counter bit as a clock
It is tempting to write 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:

\[ p(x) = x^8 + x^6 + x^5 + x^4 + 1, \qquad s_{n+1} = \big(s_n \ll 1\big) \;\big|\; \big(s_7 \oplus s_5 \oplus s_4 \oplus s_3\big), \qquad \text{period} = 2^8 - 1 = 255 \]
lfsr β€” Fibonacci LFSR with parameterised width, taps and seed
// 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
endmodule

The 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.

lfsr_tb β€” period measurement for an 8-bit and a 16-bit LFSR
`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
endmodule
Output (both languages; Icarus prints hex in lower case)
first 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.

VerilogVHDL
Declaremodule m #(parameter N = 8) (...)entity m is generic (N : positive := 8); port (...)
Overridem #(.N(16)) u (...);u : entity work.m generic map (N => 16) port map (...);
Local constantlocalparam W = $clog2(N);constant W : natural := ...;
Vector width from itreg [N-1:0] r;signal r : unsigned(N - 1 downto 0);
Replicate hardwaregenerate for (...) begin : namename : for k in ... generate
Optional hardwaregenerate 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 flip-flops chained with a generate loop
// 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];
endmodule

For 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.

regs_tb β€” reset timing, counter wrap-around and delay line in one testbench
`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
endmodule

7. 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.)

Python
script.py67 lines

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

RuleVerilogVHDL (VHDL-93 compatible)
Clocked blockalways @(posedge clk) begin ... endprocess (clk) begin if rising_edge(clk) then ... end if; end process;
Assignmentnon-blocking <=signal <= (variables only as temporaries)
Resetif (rst) ... else ... inside the edgeif rst = '1' then ... else ... inside the edge
Hold valuejust do not assign (becomes CE)just do not assign (becomes CE)
Initial valuereg [7:0] r = 8'd0;signal r : ... := (others => '0');
Sizesparameter, localparam, $clog2generic, constant, range
Replicationgenerate for ... begin : labellabel : 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

Progress: 0/5 (0%)
1
⭐ Easy
How many flip-flops does each of these describe: (a) counter #(.WIDTH(12)); (b) delay_line #(.N(5)); (c) the default lfsr; (d) shift3_bad, once synthesised?
2
⭐ Easy
A 16-bit 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?
3
⭐⭐ Medium
Rewrite the following Verilog so that it behaves the same in simulation but follows the coding rules of this chapter, and explain what hardware it describes: always @(posedge clk) begin tmp = a + b; y = tmp >> 1; end.
4
⭐⭐ Medium
In 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.
5
⭐⭐⭐ Hard
The 8-bit LFSR with taps 8,6,5,4 visits all 255 non-zero states. (a) Why can it never visit 0? (b) Show that its next state is a linear function of the current state over GF(2) β€” i.e. \(s_{n+1} = M s_n\) for an \(8\times8\) binary matrix \(M\) β€” and say what property of \(M\) makes the period exactly \(2^8 - 1\). (c) The Python program finds 16 maximal tap sets among the 128 that include stage 8. Where does 16 come from?

Problem Set Statistics

Total Problems
5
Attempted
0
Completion
0%
Share:XRedditLinkedIn
Rate this chapter: