Skip to content

4.8 Sequential logic in SystemVerilog

Why this matters

The pictures of the last three sections turn into code with one pattern: a block that runs at each rising clock edge and updates variables with <=. Get the pattern right and the code means exactly the flip-flops you drew. Get one character wrong, an = for a <=, and the code still compiles and simulates, but what it means now depends on the order of the statements, and in the shift register below it becomes a different circuit. This section is about that pattern, the rules around it, and the testbench that checks it.

  • Write a flip-flop and a register with always_ff @(posedge clk) and <=.
  • Explain how blocking and nonblocking assignment differ in a clocked block, and show it on two flip-flops in a row.
  • Write synchronous and asynchronous reset, and say what each sensitivity list means.
  • Write a clocked testbench: a clock, inputs that change with <= at the edges, every input defined at time zero, and an extra last cycle.
  • Parameterize a module by its width, using a reduction operator.

Intuition

Picture a row of people, each holding a card with a number, and a conductor who says "now". On "now", every person looks at the card of the person to their left and writes that number on their own card, and nobody changes their card until everyone has looked. That is a row of flip-flops: every right-hand side is read at the same instant, and only then do the left-hand sides change.

Now picture the same row without the conductor's rule. Each person copies from the left neighbor as soon as that neighbor's card is final, from left to right. By the time the second person copies, the first person's card already has the new number, and so after one "now" every card in the row holds the same number. That is what blocking assignments do when they are written in that order.

For a programmer: a, b = b, a in Python is the first kind, with the right side read before anything changes, and a = b; b = a is the second kind. The analogy breaks at one point. In a circuit nothing runs "in order": for a row of registers written in that order, the second kind of code does not describe a slow copy, it describes a different wiring, as the shift register below shows.

Definition 4.8.1(always_ff).

An always_ff @(posedge clk) block is a group of statements that runs once at every rising edge of clk. Every variable it assigns becomes the output of a flip-flop, and holds its value between edges. The part in parentheses, here posedge clk, is the block's sensitivity list: the events that make the block run.

There are two ways to read the same code, and "runs" belongs to one of them. As a description of hardware, an always_ff block says what flip-flops exist and what each one loads at an edge: nothing in the circuit runs. As the simulator executes that description, it does run the block, once for each event in the sensitivity list. The word "runs" in this chapter and the next is the simulator's reading, which is the one that explains the order of assignments and the timing of a testbench.

Definition 4.8.2(blocking and nonblocking assignment).

A blocking assignment, written =, updates its variable at once, so the next statement of the block sees the new value. A nonblocking assignment, written <=, reads its right-hand side when the statement runs but updates its variable only after every statement that was triggered by the same event has run. The nonblocking assignments of a clocked block all read the old values, and then all change together.

In a clocked block the book always uses <=, so the code behaves like flip-flops that sample together. In always_comb and in assign it uses =.

Two more points of style, which the modules of this chapter and the next follow. First, as Section 2.7 explained, a block holds one statement, and an if/else or a case counts as one. Where a block or one branch of an if needs two or more statements, begin and end turn them into one: always_ff @(posedge clk) begin ... end. The book writes begin and end where a block or branch holds more than one statement, and some earlier modules also wrap a single statement, which is allowed. When you add a second statement to a branch that has no begin and end, add them at the same time: without them, only the first statement belongs to the branch, and the second runs on every path, or the code does not compile. Second, the next value of a register can be computed in assign statements, with the always_ff block holding only the register and its reset, as in the timer of Section 4.5, or inside the always_ff block, as in the shift register below. The two styles give the same circuit. The book keeps always_ff to the register plus its reset when the next-value logic is more than one expression.

A plain always @(posedge clk) block would also simulate as flip-flops, but always_ff says that you mean flip-flops, and some tools check it. Of the four tools that check this book, Yosys rejects an always_ff block whose sensitivity list is not made of clock edges, and Icarus warns, while slang and Verilator accept it without comment. This book uses always_ff for every clocked block.

A flip-flop is one variable assigned in an always_ff block. Section 4.7 used this module for the waveforms of a single flip-flop.

d_flop.sv
// A positive-edge D flip-flop: at each rising edge of clk, q takes the value d had just before the edge.
module d_flop(input logic clk, d, output logic q);
always_ff @(posedge clk)
q <= d;
endmodule

q takes the value of d at every rising edge of clk, and nothing in the block says what happens between edges, so q keeps its value. A register is the same with buses. The register below has four bits and a synchronous reset, which is an if around the assignment:

reg4_sync.sv
// A 4-bit register: four flip-flops sharing one clock, with a synchronous reset to 0.
module reg4_sync(input logic clk, reset, input logic [3:0] d, output logic [3:0] q);
always_ff @(posedge clk)
if (reset) q <= 4'd0;
else q <= d;
endmodule

The if is part of the block, so it is looked at only at a rising edge, like d. Section 4.7 showed the drawing that this code means: a multiplexer in front of the register, with reset as its select. A 4-bit register is four flip-flops, and the tools count it that way, as its 4 storage bits.

Blocking and nonblocking in a clocked block

Section titled “Blocking and nonblocking in a clocked block”

Take two statements and put them in an always_ff block, b taking a and c taking b. With <=, this is two flip-flops in a row:

shift_nb.sv
// Two flip-flops in a row with nonblocking assignments: c lags b by one clock cycle, and b lags a by one.
module shift_nb(input logic clk, a, output logic b, c);
always_ff @(posedge clk) begin
b <= a;
c <= b;
end
endmodule

In the version with = instead of <=, with the two statements in the same order, a is 1 at a rising edge and 0 at the edges before it. When does c become 1?

Here is that version. It is a bad example, and the file carries a comment that tells Verilator to accept it. Of the four tools that check this book, only Verilator warns about a blocking assignment in a clocked block; the other three accept it, and the simulator runs it without complaint.

shift_blocking.sv
// The same two statements with blocking assignments, which is wrong in an always_ff block: c takes a in the same cycle as b.
// verilator lint_off BLKSEQ
// (Verilator is the one tool of the book that flags this; the waiver keeps the file in the book as a bad example.)
module shift_blocking(input logic clk, a, output logic b, c);
always_ff @(posedge clk) begin
b = a;
c = b;
end
endmodule

Both modules have the same testbench: a is high for two cycles, low for one, high for one, and then low. First the correct one:

Waveform ch04/shift-nb-trace: clk, a, b, cclkabc050100150200250300350400450500550time (ps)Waveform ch04/shift-nb-trace: clk, a, b, cclkabc050100150200250300350400450500550time (ps)Waveform ch04/shift-nb-trace: clk, a, b, cclkabc0100200300400500time (ps)Waveform ch04/shift-nb-trace: clk, a, b, cclkabc0100200300400500time (ps)Waveform ch04/shift-nb-trace: clk, a, b, cclkabc0100200300400500time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch04/shift-nb-trace: time in ps from 0 to 600, 12 columns of 50 ps.

clk: clock, period 100 ps, starts 0, 6 rising edges in the window (50, 150, 250, ...).

a: 0 at 0, then 1 at 50, 0 at 250, 1 at 350, 0 at 450.

b: unknown at 0, then 0 at 50, 1 at 150, 0 at 350, 1 at 450, 0 at 550.

c: unknown at 0, then 0 at 150, 1 at 250, 0 at 450, 1 at 550.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, a, b and c. a is high for two clock cycles. b follows a one cycle later, and c follows b one cycle later. b and c are unknown, drawn hatched, before the first and second rising edge.

Figure 4.8.1. Two flip-flops in a row, with nonblocking assignments. Each one is a cycle behind the one before.

In Figure 4.8.1, a rises at 50 and falls at 250, so it is high for two cycles. b is high from 150 to 350: it samples a at each edge, so it shows the same pattern one edge later. c is high from 250 to 450, another edge later. The same stimulus then gives a one-cycle pulse on a at 350, and b shows it from 450 to 550 and c from 550.

Waveform ch04/shift-blocking-trace: clk, a, b, cclkabc050100150200250300350400450500550time (ps)Waveform ch04/shift-blocking-trace: clk, a, b, cclkabc050100150200250300350400450500550time (ps)Waveform ch04/shift-blocking-trace: clk, a, b, cclkabc0100200300400500time (ps)Waveform ch04/shift-blocking-trace: clk, a, b, cclkabc0100200300400500time (ps)Waveform ch04/shift-blocking-trace: clk, a, b, cclkabc0100200300400500time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch04/shift-blocking-trace: time in ps from 0 to 600, 12 columns of 50 ps.

clk: clock, period 100 ps, starts 0, 6 rising edges in the window (50, 150, 250, ...).

a: 0 at 0, then 1 at 50, 0 at 250, 1 at 350, 0 at 450.

b: unknown at 0, then 0 at 50, 1 at 150, 0 at 350, 1 at 450, 0 at 550.

c: unknown at 0, then 0 at 50, 1 at 150, 0 at 350, 1 at 450, 0 at 550.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, a, b and c. a is high for two clock cycles. b follows a one cycle later, and c is identical to b at every moment.

Figure 4.8.2. The same two statements with blocking assignments. b and c always change together.

In Figure 4.8.2, b is exactly as before, high from 150 to 350 and from 450 to 550. But c is the same signal as b, at every instant: c = b runs after b = a at the same edge, so both take a. The two statements describe one flip-flop whose output drives both b and c, which is how Lecture 4 draws them.

Writing = does not always change the circuit, and that is part of why it is dangerous. A single register written q = d still gives one flip-flop. Even the two statements above, written in the opposite order, are not collapsed:

shift_blocking_rev.sv
// The two statements of shift_blocking in the opposite order, still with blocking assignments: c reads b before b changes.
// verilator lint_off BLKSEQ
// (Verilator still flags the blocking assignments; the waiver keeps the file in the book as an example.)
module shift_blocking_rev(input logic clk, a, output logic b, c);
always_ff @(posedge clk) begin
c = b;
b = a;
end
endmodule

Here c reads b before b changes, so c takes the old b. Its testbench uses the same input as the correct version and expects the same b and c as the <= version, and the check passes. Yosys keeps 2 storage bits after optimization for it, as for the <= version. What = takes away is the guarantee: the meaning now depends on the order of the statements, and a different block that reads these variables at the same edge can see either the old value or the new one. With <= the order does not matter and the blocks cannot race, which is why the book uses <= for every register.

Yosys counts storage bits at two moments. Right after it reads the code, every variable assigned in a clocked block still has its own flip-flop, and the count is 2 in the correct module and 2 in the one with =. Then its optimization pass looks for flip-flops with the same input and the same clock. In the correct module the two have different inputs, a and b, so both stay and the count after optimization is 2. In the module with = both flip-flops take a, so they are merged, and the count after optimization is 1.

A synchronous reset is the if of the register above. It is the book's house pattern. Here it is on a single flip-flop.

dff_sync.sv
// A D flip-flop with a synchronous reset: reset is looked at only at a rising edge of clk.
module dff_sync(input logic clk, reset, d, output logic q);
always_ff @(posedge clk)
if (reset) q <= 1'b0;
else q <= d;
endmodule

An asynchronous reset changes the sensitivity list, and nothing else.

dff_async.sv
// A D flip-flop with an asynchronous reset: q becomes 0 as soon as reset rises, without waiting for an edge of clk.
module dff_async(input logic clk, reset, d, output logic q);
always_ff @(posedge clk or posedge reset)
if (reset) q <= 1'b0;
else q <= d;
endmodule

The sensitivity list is the part to read carefully. @(posedge clk) says: run the block at a rising edge of clk. @(posedge clk or posedge reset) says: run the block at a rising edge of clk or at a rising edge of reset. When reset rises, the block runs at once, the if (reset) is true, and q becomes 0, with no clock edge. While reset stays 1, each clock edge runs the block again and keeps q at 0. When reset falls, the block does not run, so q stays 0 until the next rising edge of clk, where if (reset) is false and d is loaded. The edge in the list and the test in the if have to agree: a list with posedge reset goes with if (reset).

These two modules were driven side by side in Figure 4.7.4 and Figure 4.7.5 of Section 4.7. The asynchronous version is shown for completeness. The book's designs use a synchronous reset.

A clocked testbench needs a clock, and the simulator does not provide one. There are three common ways to write it. Each testbench below drives d_flop, the flip-flop at the start of this section, and gives it the same clock: a clock period of 100, starting at 0, so that the first rising edge is at 50. The examples use a clock period of 100, as the course's section slides do (fact ch04/clock-period-100).

The first writes both edges out:

clk_explicit_tb.sv
// Clock style 1, explicit edges: the clock is set to 1 and to 0 in turn, half a period apart.
module clk_explicit_tb();
logic clk, d, q;
d_flop dut(.*);
parameter CLOCK_PERIOD = 100;
initial
clk <= 0;
always begin
#(CLOCK_PERIOD / 2) clk <= 1;
#(CLOCK_PERIOD / 2) clk <= 0;
end
initial begin
d <= 0; @(posedge clk);
$display("DD first_rise=%0d", $time);
d <= 1; @(posedge clk); // expect: q=0
d <= 0; @(posedge clk); // expect: q=1
@(posedge clk); // expect: q=0
$stop;
end
endmodule

The second flips the clock every half period with one statement:

clk_toggle_tb.sv
// Clock style 2, toggle: one statement inverts the clock every half period.
module clk_toggle_tb();
logic clk, d, q;
d_flop dut(.*);
parameter CLOCK_PERIOD = 100;
initial
clk <= 0;
always #(CLOCK_PERIOD / 2) clk <= ~clk;
initial begin
d <= 0; @(posedge clk);
$display("DD first_rise=%0d", $time);
d <= 1; @(posedge clk); // expect: q=0
d <= 0; @(posedge clk); // expect: q=1
@(posedge clk); // expect: q=0
$stop;
end
endmodule

The third puts the same flip into a forever loop inside an initial block, which is the form used in the other testbenches of this book:

clk_forever_tb.sv
// Clock style 3, forever: the same toggle inside an initial block, which is the form the book's other testbenches use.
module clk_forever_tb();
logic clk, d, q;
d_flop dut(.*);
parameter CLOCK_PERIOD = 100;
initial begin
clk <= 0;
forever #(CLOCK_PERIOD / 2) clk <= ~clk;
end
initial begin
d <= 0; @(posedge clk);
$display("DD first_rise=%0d", $time);
d <= 1; @(posedge clk); // expect: q=0
d <= 0; @(posedge clk); // expect: q=1
@(posedge clk); // expect: q=0
$stop;
end
endmodule

Each prints the time of the first rising edge it sees, and all three agree: the first rising edge of the explicit-edge clock is at 6'd50, of the toggle clock at 6'd50, and of the forever clock at 6'd50. A plain always is allowed here because it makes a testbench clock; a design never uses one.

Waveform ch04/clk-style-trace: clk, d, qclkdq050100150200250300time (ps)Waveform ch04/clk-style-trace: clk, d, qclkdq050100150200250300time (ps)Waveform ch04/clk-style-trace: clk, d, qclkdq0100200300time (ps)Waveform ch04/clk-style-trace: clk, d, qclkdq0100200300time (ps)Waveform ch04/clk-style-trace: clk, d, qclkdq0100200300time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch04/clk-style-trace: time in ps from 0 to 350, 7 columns of 50 ps.

clk: clock, period 100 ps, starts 0, 3 rising edges in the window (50, 150, 250).

d: 0 at 0, then 1 at 50, 0 at 150.

q: unknown at 0, then 0 at 50, 1 at 150, 0 at 250.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, d and q. clk rises at 50, 150 and 250. d is 0 and rises at the first rising edge, and falls at the second. q is unknown until the first rising edge, then is 0, becomes 1 at the second rising edge and 0 at the third.

Figure 4.8.3. The run of the third testbench.

The stimulus follows a few rules, which the testbenches above all obey.

  • To move from one cycle to the next, wait for the clock with @(posedge clk), not with a delay. The simulator then waits for as many edges as the line says, whatever the period. (A # delay is for placing a change in the middle of a cycle on purpose.)
  • Change the inputs with <=. A nonblocking assignment in the testbench takes effect after the flip-flops triggered by the same edge have read their inputs, so in the simulation the design sees the value the input had before the edge. That is the simulator's idealization of an input that changes just after the edge (Section 4.7 explains how it differs from a real flip-flop's setup and hold window). A blocking assignment at the edge could win the race, and then the design would see the new value instead.
  • Give every input a value at time zero, even while resetting, so that no input is unknown.
  • Add one more cycle after the last change, so that its effect shows before $stop.
  • On a line that ends with @(posedge clk), a // expect: comment names what the design shows just before that edge, which is what the previous edge produced. On a line that ends with a # delay, it is read just before the delay ends.

On the board, the built-in 50 MHz clock of the DE1-SoC (fact ch04/clock-50mhz) is available to a top-level module as the input CLOCK_50. That is too fast to watch, so the course provides a clock_divider module that makes slower clocks from it, and the section slides recommend assigning one of its bits to an extra signal clk and using clk for the design (fact ch04/clock-divider). The whole design runs on that one clk. The divider is for the board only: it must not be used in simulation, where the testbench makes clk itself, as above. A push button is not a clock either. It is an input that changes at arbitrary times, including inside the window around a clock edge, and Chapter 6 (not written yet) shows how to bring one into a clocked design safely.

A shift register moves a pattern one place per clock cycle. This one has ten bits, one per LED, and each edge moves every bit one place toward LEDR[9] while the input in enters at LEDR[0].

led_shift.sv
// A 10-bit shift register on the LEDs: each clock cycle the lights move one place toward LEDR[9] and in enters at LEDR[0].
module led_shift(input logic clk, reset, in, output logic [9:0] LEDR);
always_ff @(posedge clk)
if (reset) LEDR <= 10'd0;
else LEDR <= {LEDR[8:0], in};
endmodule

The right-hand side {LEDR[8:0], in} is the old low nine bits followed by in, a 10-bit value. Because the statement is nonblocking, all ten flip-flops sample at once. The testbench resets, sends the bits 1, 0, 1, 1 and then zeros, and runs long enough for the first 1 to reach the far end and drop out.

led_shift_tb.sv
// Testbench for led_shift: reset, then a pattern of eleven input bits, long enough for the first bit to reach the far end, and one more cycle.
module led_shift_tb();
logic clk, reset, in;
logic [9:0] LEDR;
led_shift dut(.*);
parameter CLOCK_PERIOD = 100;
initial begin
clk <= 0;
forever #(CLOCK_PERIOD / 2) clk <= ~clk;
end
initial begin
reset <= 1; in <= 0; @(posedge clk);
reset <= 0; in <= 1; @(posedge clk); // expect: LEDR=10'b0000000000
in <= 0; @(posedge clk); // expect: LEDR=10'b0000000001
in <= 1; @(posedge clk); // expect: LEDR=10'b0000000010
in <= 1; @(posedge clk); // expect: LEDR=10'b0000000101
in <= 0; @(posedge clk); // expect: LEDR=10'b0000001011
in <= 0; @(posedge clk); // expect: LEDR=10'b0000010110
in <= 0; @(posedge clk); // expect: LEDR=10'b0000101100
in <= 0; @(posedge clk); // expect: LEDR=10'b0001011000
in <= 0; @(posedge clk); // expect: LEDR=10'b0010110000
in <= 0; @(posedge clk); // expect: LEDR=10'b0101100000
in <= 0; @(posedge clk); // expect: LEDR=10'b1011000000
@(posedge clk); // expect: LEDR=10'b0110000000
$stop;
end
endmodule
Waveform ch04/led-shift-start: clk, reset, in, LEDRclkresetinLEDR (hex)00000100200500B016050100150200250300350400450500550time (ps)Waveform ch04/led-shift-start: clk, reset, in, LEDRclkresetinLEDR (hex)00000100200500B016050100150200250300350400450500550time (ps)Waveform ch04/led-shift-start: clk, reset, in, LEDRclkresetinLEDR (hex)00000100200500B0160100200300400500time (ps)Waveform ch04/led-shift-start: clk, reset, in, LEDRclkresetinLEDR (hex)00000100200500B0160100200300400500time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch04/led-shift-start: time in ps from 0 to 600, 12 columns of 50 ps.

clk: clock, period 100 ps, starts 0, 6 rising edges in the window (50, 150, 250, ...).

reset: 1 at 0, then 0 at 50.

in: 0 at 0, then 1 at 50, 0 at 150, 1 at 250, 0 at 450.

LEDR (10 bits, hex): unknown at 0, then 000 at 50, 001 at 150, 002 at 250, 005 at 350, 00B at 450, 016 at 550.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, reset, in and LEDR in hexadecimal. LEDR is unknown until the first rising edge, then is 000. After that it takes the values 001, 002, 005, 00B and 016 at consecutive rising edges.

Figure 4.8.4. The shift register from reset. LEDR is drawn in hexadecimal; the testbench comments above give the same values in binary.

Read Figure 4.8.4 as a row of lights moving left, with the values in hexadecimal and the binary patterns in the testbench's comments. At the first edge reset is 1, so LEDR becomes all zeros. At the next edge in is 1, so a 1 appears at bit 0, which is the hexadecimal value 001. Then in is 0, and that 1 moves to bit 1 while a 0 enters, giving 002. Then two 1s enter in a row, and the pattern reads 0000000101 in binary, 005, and then 0000001011, 00B, as the older bits move up.

Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)01602C0580B01602C01806006507007508008509009501000105011001150time (ps)Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)01602C0580B01602C018060070080090010001100time (ps)Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)01602C0580B01602C018060070080090010001100time (ps)Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)01602C0580B01602C018060070080090010001100time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch04/led-shift-end: time in ps from 600 to 1200, 12 columns of 50 ps.

clk: clock, period 100 ps, starts 0, 6 rising edges in the window (650, 750, 850, ...).

reset: 0 at 600, no changes.

in: 0 at 600, no changes.

LEDR (10 bits, hex): 016 at 600, then 02C at 650, 058 at 750, 0B0 at 850, 160 at 950, 2C0 at 1050, 180 at 1150.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, reset, in and LEDR in hexadecimal. in and reset are 0 throughout. LEDR is 016 at the start and doubles at each rising edge: 02C, 058, 0B0, 160, 2C0. At the last rising edge it becomes 180, with the leading 1 gone.

Figure 4.8.5. The later part of the run, with in at 0: the pattern moves up and the top bit is lost. LEDR is in hexadecimal.

In Figure 4.8.5 in stays 0. The 1s keep moving up at each edge, and at the last edge the 1 that has reached bit 9 is shifted out and is gone: 1011000000, which is 2C0, becomes 0110000000, which is 180.

The last piece is not sequential. Section 4.4's compare_n already used ~| for its eq output; here the equality comparator is on its own, with the operator explained. It says whether two numbers are equal, for any width N. Subtracting gives zero exactly when the numbers are equal, and ~|sub is 1 exactly when no bit of sub is 1: a reduction operator, | applied across all the bits of one number, and inverted.

eq_cmp.sv
// An equality comparator for any width N. The difference of the two numbers is all zeros exactly when they are equal, and ~| is 1 when no bit is 1.
module eq_cmp #(parameter N = 4) (input logic [N-1:0] a, b, output logic eq);
logic [N-1:0] sub;
assign sub = a - b;
assign eq = ~|sub;
endmodule

N is a parameter (Definition 4.2.2), so one module serves many widths. The testbench instantiates it with #(.N(3)) and tries every pair of 3-bit numbers, 64 pairs, comparing eq with == in the testbench for each.

eq_cmp_tb.sv
// Testbench for eq_cmp with N = 3: every pair of 3-bit numbers, 64 pairs, each compared with ==, which counts mismatches.
module eq_cmp_tb();
logic [2:0] a, b;
logic eq;
int mismatches;
eq_cmp #(.N(3)) dut(.*);
initial begin
mismatches = 0;
a = 3'd0; b = 3'd0; #10;
for (int i = 0; i < 8; i++) begin
for (int j = 0; j < 8; j++) begin
a = i[2:0]; b = j[2:0]; #10;
if (eq !== (a == b)) mismatches = mismatches + 1;
end
end
$display("DD mismatches=%0d", mismatches);
$stop;
end
endmodule

The testbench counts the pairs for which eq differs from ==, and finds 1'd0. Comparing numbers by their order, less than and greater than, needs more care with signs, and Section 4.4 does that.

How CSE 369 writes this

In CSE 369 code, always_ff @(posedge clk) holds only nonblocking assignments, and always_comb holds only blocking ones. A block never mixes the two, and a variable is assigned in exactly one block. The reset is synchronous and named reset, and plain always is only for the clock of a testbench. The design runs on one clock.

Common pitfall

  • Writing = in an always_ff block. It compiles and simulates, but the result depends on the order of the statements, and with them in the wrong order a row of registers collapses: every stage takes the new value at once. With <= the order does not matter.
  • Mixing = and <= in one block, or assigning one variable from two blocks. Each variable is driven from one place.
  • Changing testbench inputs with = at the clock edge. The design may see either value, and the result depends on the order the simulator happens to run in. Use <=.
  • Putting posedge reset in the sensitivity list of a design meant to have a synchronous reset. The reset then acts at once, which is a different circuit.
  • Using a push button as the clock, or a divided clock in simulation. The design gets one clock, and the testbench makes it.
  • Stopping the testbench right after the last input change. Add a cycle so that its effect shows.
Take the quiz (4 questions)

After Yosys has optimized the module shift_blocking, the one with =, how many storage bits are left?

At what time does the first rising edge of the clock of clk_toggle_tb happen?

In always_ff @(posedge clk or posedge reset), what happens when reset falls from 1 to 0?

Why does a testbench change its inputs with <= at a rising clock edge?

Exercise 4.8.1

Say which of these blocks break the coding guidelines, and why.

  1. An always_ff @(posedge clk) block that assigns q with =.
  2. An always_comb block that assigns y with <=.
  3. Two always_ff @(posedge clk) blocks that both assign count.
  4. An always_ff @(posedge clk) block with if (reset) q <= 0; else q <= d;.
  5. An always_ff block that assigns a with <= and b with =.
Hint
The guidelines: <= in clocked blocks, = in combinational ones, no mixing in one block, one block per variable.
Solution
  1. Breaks the guideline: a clocked block uses <=.
  2. Breaks it: a combinational block uses =.
  3. Breaks it: a variable is assigned in one block only, and two blocks driving count are two drivers.
  4. Follows the guidelines: <= in a clocked block, with a synchronous reset.
  5. Breaks it: one block must not mix <= and =.

Tags: Warm-upSystemVerilog

Exercise 4.8.2

Write a module delay3 with a clock, an input in and an output out, where out shows in three clock cycles later. What would change if all three assignments were blocking?

Hint
Three flip-flops in a row, like the two of the example. Then ask what a blocking assignment does to a row of flip-flops.
Solution

Three flip-flops in a row do it: x takes in, y takes x, and out takes y, all with <= in one always_ff block.

delay3.sv
// A delay line three flip-flops long: out is in, three clock cycles later.
module delay3(input logic clk, in, output logic out);
logic x, y;
always_ff @(posedge clk) begin
x <= in;
y <= x;
out <= y;
end
endmodule

The simulation sends a single 1 in. in is 1 from 350 to 450, and x takes it at the edge at 450, y at the edge at 550 and out at the edge at 650. So out is 1 from 650 to 750: the same pulse as on in, three cycles later.

Waveform ch04/delay3-trace: clk, in, dut.x, dut.y, outclkindut.xdut.yout350400450500550600650700750800time (ps)Waveform ch04/delay3-trace: clk, in, dut.x, dut.y, outclkindut.xdut.yout350400450500550600650700750800time (ps)Waveform ch04/delay3-trace: clk, in, dut.x, dut.y, outclkindut.xdut.yout350450550650750time (ps)Waveform ch04/delay3-trace: clk, in, dut.x, dut.y, outclkindut.xdut.yout350450550650750time (ps)Waveform ch04/delay3-trace: clk, in, dut.x, dut.y, outclkindut.xdut.yout350450550650750time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch04/delay3-trace: time in ps from 350 to 850, 10 columns of 50 ps.

clk: clock, period 100 ps, starts 1, 4 rising edges in the window (450, 550, 650, ...).

in: 1 at 350, then 0 at 450.

dut.x: 0 at 350, then 1 at 450, 0 at 550.

dut.y: 0 at 350, then 1 at 550, 0 at 650.

out: 0 at 350, then 1 at 650, 0 at 750.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, in, x, y and out. in is high for one clock cycle. x goes high at the next rising edge for one cycle, then y goes high at the next, and out at the next after that.

Figure 4.8.6. The delay line. A single 1 on in comes out on out three clock cycles later.

With blocking assignments written in the same order, the second and third statements would copy the new value in the same edge. All three flip-flops would take in at once, and out would show it one cycle after, not three.

delay3_blocking.sv
// The delay line of delay3 written with blocking assignments, which is wrong in an always_ff block.
// verilator lint_off BLKSEQ
// (Verilator is the one tool of the book that flags this; the waiver keeps the file as a bad example.)
module delay3_blocking(input logic clk, in, output logic out);
logic x, y;
always_ff @(posedge clk) begin
x = in;
y = x;
out = y;
end
endmodule
Waveform ch04/delay3-blocking-trace: clk, in, dut.x, dut.y, outclkindut.xdut.yout350400450500550600650700750800time (ps)Waveform ch04/delay3-blocking-trace: clk, in, dut.x, dut.y, outclkindut.xdut.yout350400450500550600650700750800time (ps)Waveform ch04/delay3-blocking-trace: clk, in, dut.x, dut.y, outclkindut.xdut.yout350450550650750time (ps)Waveform ch04/delay3-blocking-trace: clk, in, dut.x, dut.y, outclkindut.xdut.yout350450550650750time (ps)Waveform ch04/delay3-blocking-trace: clk, in, dut.x, dut.y, outclkindut.xdut.yout350450550650750time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch04/delay3-blocking-trace: time in ps from 350 to 850, 10 columns of 50 ps.

clk: clock, period 100 ps, starts 1, 4 rising edges in the window (450, 550, 650, ...).

in: 1 at 350, then 0 at 450.

dut.x: 0 at 350, then 1 at 450, 0 at 550.

dut.y: 0 at 350, then 1 at 450, 0 at 550.

out: 0 at 350, then 1 at 450, 0 at 550.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, in, x, y and out. in is high for one clock cycle. x, y and out all go high at the next rising edge, together, for one cycle.

Figure 4.8.7. The same delay line with blocking assignments. x, y and out are identical.

Tags: StandardSystemVerilogSequential logic

Exercise 4.8.3

Write an 8-bit register with an asynchronous reset to 0. What must the sensitivity list contain, and what does the if test?

Hint
The block has to run when reset rises, as well as at a clock edge. The register of the section is the starting point.
Solution

The list must name both events: posedge clk for loading and posedge reset so that the block runs as soon as reset rises. The if tests reset, in the same polarity as the list. When it is true, the register takes 0, and otherwise it takes d.

reg8_async.sv
// An 8-bit register with an asynchronous reset to 0.
module reg8_async(input logic clk, reset, input logic [7:0] d, output logic [7:0] q);
always_ff @(posedge clk or posedge reset)
if (reset) q <= 8'd0;
else q <= d;
endmodule

Without posedge reset in the list, the same code would be a synchronous reset, since the block would run at clock edges only.

Tags: StandardSystemVerilog

Exercise 4.8.4

The figure shows the clock, reset and in of the 10-bit shift register for a later part of its run. At the start LEDR is 0000010110. Work out LEDR at each rising edge, with the simulator's rule that the register sees the value an input had before the edge (Section 4.7). Here in and reset do not change in this stretch, so nothing depends on it.

Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)6006507007508008509009501000105011001150time (ps)Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)60070080090010001100time (ps)Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)60070080090010001100time (ps)Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)60070080090010001100time (ps)Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)6008001000time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch04/led-shift-end: time in ps from 600 to 1200, 12 columns of 50 ps.

clk: clock, period 100 ps, starts 0, 6 rising edges in the window (650, 750, 850, ...).

reset: 0 at 600, no changes.

in: 0 at 600, no changes.

Empty lanes for the reader to complete: LEDR.

Dashed vertical guides mark the rising clock edges.

Description: A timing diagram of clk, reset and in, with in and reset constant, and an empty lane for LEDR.

Figure 4.8.8. The question: the clock, reset and in. The answer lane is in hexadecimal.
Hint
At each rising edge, with reset at 0, the new value is the old bits 8 down to 0 followed by in.
Solution

reset and in are 0 throughout, so each edge shifts the pattern one place up and puts a 0 at bit 0. The top bit is dropped.

  • Start: 0000010110.
  • Edge at 650: 0000101100.
  • Edge at 750: 0001011000.
  • Edge at 850: 0010110000.
  • Edge at 950: 0101100000.
  • Edge at 1050: 1011000000.
  • Edge at 1150: 0110000000, since the leading 1 of 1011000000 is shifted out.

The figure below is in hexadecimal: the six values after the start are 02C, 058, 0B0, 160, 2C0 and 180.

Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)01602C0580B01602C01806006507007508008509009501000105011001150time (ps)Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)01602C0580B01602C018060070080090010001100time (ps)Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)01602C0580B01602C018060070080090010001100time (ps)Waveform ch04/led-shift-end: clk, reset, in, LEDRclkresetinLEDR (hex)01602C0580B01602C018060070080090010001100time (ps)

Time in ps; one column is 50 ps.

Timing diagram from simulation, ch04/led-shift-end: time in ps from 600 to 1200, 12 columns of 50 ps.

clk: clock, period 100 ps, starts 0, 6 rising edges in the window (650, 750, 850, ...).

reset: 0 at 600, no changes.

in: 0 at 600, no changes.

LEDR (10 bits, hex): 016 at 600, then 02C at 650, 058 at 750, 0B0 at 850, 160 at 950, 2C0 at 1050, 180 at 1150.

Dashed vertical guides mark the rising clock edges.

Description: The full timing diagram of clk, reset, in and LEDR in hexadecimal, as in the question with the LEDR lane filled in.

Figure 4.8.9. The answer, from the simulation.

Tags: StandardWaveformsSequential logic