Digital Design & Verification (Verilog, SystemVerilog, VLSI) Module 2 – Verilog and SystemVerilog for design: the language core
Sequential logic, always_ff and nonblocking
Model registers with always_ff and nonblocking assignments, write synchronous and asynchronous resets and enables, and see what synthesis builds from each.
What you will learn
- Model flip-flops and registers with always_ff and nonblocking assignments
- Explain why blocking assignments in a clocked block change the hardware
- Write synchronous and asynchronous resets and clock enables correctly
- Read a waveform of registered logic, edge by edge
- Predict which flip-flop cells synthesis builds for each register style
Before you start
What you’ll learn · Before you start
What you will learn
- Model flip-flops and registers with always_ff and nonblocking assignments
- Explain why blocking assignments in a clocked block change the hardware
- Write synchronous and asynchronous resets and clock enables correctly
- Read a waveform of registered logic, edge by edge
- Predict which flip-flop cells synthesis builds for each register style
Before you start
On this page
Four lines in a clocked block, s1 <= d; s2 <= s1; s3 <= s2; q <= s3;, build four flip-flops. Change <= to = and
the same four lines build one, which Yosys counts for you below. A clocked block describes registers, and the kind of
assignment decides how many there are and when each one changes.
You will model registers with always_ff and nonblocking assignments, see what blocking assignments do to them, write synchronous and asynchronous resets and enables, read a waveform edge by edge, and predict the flip-flop cells that synthesis picks. The simulations come from Icarus Verilog 13.0 and Verilator 5.052, and the cell counts from Yosys 0.69.
Nonblocking assignments make a clocked block a chain of flip-flops
always_ff @(posedge clk) runs its body at each rising edge of clk, and each variable it writes becomes a
flip-flop: A storage element that takes the value of its input at a clock edge and holds it until the next edge. A register is a row of flip-flops clocked together.: it takes a new value at the edge and holds it until the next one. Real
flip-flops clocked by one edge all capture their inputs as they were just before it, and a
nonblocking assignment: An assignment written with <= that reads its value at once but applies it only after every process woken at the same time has run, so all of them read the old values. More in “Thinking in hardware: concurrency and 4-state logic”, <=, gives the simulation the same rule: every right
side is read first, and the updates land together afterwards. shift4.sv uses it for a
shift register: A chain of flip-flops clocked together, each loading the previous one's value at every edge, so that data moves one stage per clock cycle. of four stages:
// A 4-stage shift register: each clock edge moves every bit one stage on.
`default_nettype none
module shift4 (
input logic clk,
input logic d,
output logic q
);
logic s1, s2, s3;
always_ff @(posedge clk) begin
s1 <= d;
s2 <= s1;
s3 <= s2;
q <= s3;
end
endmodule
`default_nettype wire The order of reading and writing is the whole idea, so watch it on one edge. The program below models the four stages as a list holding a 1 in s1, applies one edge with d = 0, first the blocking way and then the nonblocking way, and lets you step through every line:
Program · edge/one_edge.py
# One rising edge of a 4-stage
# shift register, with d = 0.
stages = [1, 0, 0, 0] # s1 s2 s3 q
d = 0
# Blocking: each line reads what
# the line above has just written.
b = stages.copy()
b[0] = d
b[1] = b[0]
b[2] = b[1]
b[3] = b[2]
print("blocking: ", b)
# Nonblocking: read every old
# value, then write them together.
old = stages.copy()
nb = [d, old[0], old[1], old[2]]
print("nonblocking:", nb) Variables
- changed in this step
Printed so far
Nothing printed yetblocking: [0, 0, 0, 0] nonblocking: [0, 1, 0, 0]
Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.
The drawing shows the variables when the program ends, and the program's 12 steps are listed below.
Change the program and run it here. Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Every step as text
- Start. No line has run yet, so there are no variables. Python starts with line 3.
- Line 3.
stages = [1, 0, 0, 0]makesstagesa list of 4 items. - Line 4.
d = 0setsdto0. - Line 8.
b = stages.copy()setsbto a new list with the same items asstages,[1, 0, 0, 0]. - Line 9.
b[0] = dchangesb: it is now[0, 0, 0, 0]. - Line 10.
b[1] = b[0]leavesbas it was. The bug b[1] reads b[0], which the line above has just set to 0. The 1 that s1 held before the edge is already gone, and so is the shift. - Line 11.
b[2] = b[1]leavesbas it was. - Line 12.
b[3] = b[2]leavesbas it was. - Line 13.
print("blocking: ", b)printsblocking: [0, 0, 0, 0]. - Line 17.
old = stages.copy()setsoldto a new list with the same items asstages,[1, 0, 0, 0]. - Line 18.
nb = [d, old[0], old[1], old[2]]makesnba list of 4 items. Key point Every value on the right comes from old, the stages as they were before the edge, so the 1 moves from s1 to s2, as a flip-flop chain does. - Line 19.
print("nonblocking:", nb)printsnonblocking: [0, 1, 0, 0]. That was the last line, so the program ends. Blocking wiped the register to [0, 0, 0, 0]; nonblocking gave [0, 1, 0, 0], the 1 one stage on, which is what the four flip-flops of shift4.sv do at an edge.
The nonblocking version moved the 1 from s1 to s2, one stage per edge. tb_shift4.sv feeds a single 1 into shift4.sv and the blocking variant beside it, then prints both outputs at every falling edge, half a period after each rising edge:
hdl/shift4_blocking.sv
// The same four lines with blocking assignments: each stage copies the value the line above has just written.
`default_nettype none
module shift4_blocking (
input logic clk,
input logic d,
output logic q
);
logic s1, s2, s3;
always_ff @(posedge clk) begin
s1 = d;
s2 = s1;
s3 = s2;
q = s3;
end
endmodule
`default_nettype wire hdl/tb_shift4.sv
// Feeds a single 1 into both shift registers, then 0s, and prints both outputs at every falling edge.
module tb_shift4;
logic clk, d, q, q_blocking;
shift4 u_nb (.clk(clk), .d(d), .q(q));
shift4_blocking u_b (.clk(clk), .d(d), .q(q_blocking));
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
d = 1'b1;
#10 d = 1'b0;
repeat (5) @(negedge clk) $display("t = %0d: q = %b, q_blocking = %b", $time, q, q_blocking);
$finish;
end
endmodule Commands · hdl/shift_session.py
from session import run
# Both shift registers on both simulators. Verilator's -Wall reports the blocking assignments; -Wno-fatal lets the
# build go on.
run("""
iverilog -g2012 -Wall -o sim.vvp tb_shift4.sv shift4.sv shift4_blocking.sv
vvp -n sim.vvp
verilator --binary -j 0 -Wall --quiet -Wno-fatal tb_shift4.sv shift4.sv shift4_blocking.sv
obj_dir/Vtb_shift4 +verilator+quiet
""") Output
# Icarus Verilog 13.0 (stable) · Verilator 5.052
$ iverilog -g2012 -Wall -o sim.vvp tb_shift4.sv shift4.sv shift4_blocking.sv
$ vvp -n sim.vvp
t = 10: q = x, q_blocking = 1
t = 20: q = x, q_blocking = 0
t = 30: q = x, q_blocking = 0
t = 40: q = 1, q_blocking = 0
t = 50: q = 0, q_blocking = 0
tb_shift4.sv:15: $finish called at 50 (1s)
$ verilator --binary -j 0 -Wall --quiet -Wno-fatal tb_shift4.sv shift4.sv shift4_blocking.sv
%Warning-BLKSEQ: shift4_blocking.sv:12:8: Blocking assignment '=' in sequential logic process
: ... note: In instance 'tb_shift4.u_b'
: ... Suggest using delayed assignment '<='
12 | s1 = d;
| ^
... For warning description see https://verilator.org/warn/BLKSEQ?v=5.052
... Use "/* verilator lint_off BLKSEQ */" and lint_on around source to disable this message.
%Warning-BLKSEQ: shift4_blocking.sv:13:8: Blocking assignment '=' in sequential logic process
: ... note: In instance 'tb_shift4.u_b'
: ... Suggest using delayed assignment '<='
13 | s2 = s1;
| ^
%Warning-BLKSEQ: shift4_blocking.sv:14:8: Blocking assignment '=' in sequential logic process
: ... note: In instance 'tb_shift4.u_b'
: ... Suggest using delayed assignment '<='
14 | s3 = s2;
| ^
%Warning-BLKSEQ: shift4_blocking.sv:15:8: Blocking assignment '=' in sequential logic process
: ... note: In instance 'tb_shift4.u_b'
: ... Suggest using delayed assignment '<='
15 | q = s3;
| ^
$ obj_dir/Vtb_shift4 +verilator+quiet
t = 10: q = 0, q_blocking = 1
t = 20: q = 0, q_blocking = 0
t = 30: q = 0, q_blocking = 0
t = 40: q = 1, q_blocking = 0
t = 50: q = 0, q_blocking = 0
- tb_shift4.sv:15: Verilog $finish
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 shift_session.py
Read the q column as a waveform. The 1 enters s1 at the edge at 5 and reaches q at the edge at 35, the fourth edge, so q first shows it at t = 40 and drops back to 0 at t = 50. Before that, Icarus Verilog prints x: the stages have no reset, so q holds nothing known until data arrives. Verilator, mostly a two-state simulator, starts them at 0 instead.
Checkpoint
Check: Flip-flops from always_ff and <=
Blocking assignments in a clocked block collapse the chain
The q_blocking column shows the 1 at t = 10, after a single edge. With =, s1 = d changes s1 at once, s2 = s1
reads the s1 just written, and so on down the chain, exactly as the traced program’s first half did: every stage holds
d after one edge, so only q matters, and q is one flip-flop. Verilator saw it coming, with a BLKSEQ warning for each
line, “Blocking assignment ‘=’ in sequential logic process”. Synthesis agrees with the simulation:
from session import run
# What synthesis builds from each shift register: Yosys writes its cell statistics to a file, and grep shows the
# flip-flops.
run("""
yosys -q -p "read_verilog -sv shift4.sv; synth -top shift4; tee -q -o shift4.txt stat"
yosys -q -p "read_verilog -sv shift4_blocking.sv; synth -top shift4_blocking; tee -q -o shift4_blocking.txt stat"
grep DFF shift4.txt shift4_blocking.txt
""") Output
# Yosys 0.69 $ yosys -q -p "read_verilog -sv shift4.sv; synth -top shift4; tee -q -o shift4.txt stat" $ yosys -q -p "read_verilog -sv shift4_blocking.sv; synth -top shift4_blocking; tee -q -o shift4_blocking.txt stat" $ grep DFF shift4.txt shift4_blocking.txt shift4.txt: 4 $_DFF_P_ shift4_blocking.txt: 1 $_DFF_P_
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 stat_session.py
Four $_DFF_P_ cells, flip-flops on the rising edge, for shift4.sv, and one for shift4_blocking.sv. A design that
needed four cycles of delay, to line data up with a slower path, would get one, and everything downstream would be
three cycles early. The bug is easy to make because the code looks the same, and the rule that avoids it is short.
Key idea
In always_ff, write every register with <=: each one then reads its inputs from before the edge, and the hardware
is the same whatever the order of the lines.
Checkpoint
Check: Blocking assignments in a clocked block
A reset either waits for the clock or acts at once
A reset brings registers to a known value. A synchronous reset: A reset that a flip-flop samples at the clock edge like any other input, so it takes effect only at an edge. is one more input
of the flip-flop, sampled at the clock edge: count_sync.sv tests !rst_n inside always_ff @(posedge clk). An
asynchronous reset: A reset that acts as soon as it is asserted, without waiting for a clock edge. In RTL its edge is named in the event control, as in @(posedge clk or negedge rst_n). acts as soon as it is asserted: count_async.sv names the
reset’s falling edge in its event control, always_ff @(posedge clk or negedge rst_n), and tests it first, so that the
reset’s edge clears the register instead of counting. tb_reset.sv runs both counters and pulls the reset low between
two clock edges; $monitor prints a line whenever a value changes, a waveform as text:
hdl/count_sync.sv
// A 4-bit counter with a synchronous, active-low reset: the reset is one more input sampled at the clock edge.
`default_nettype none
module count_sync (
input logic clk,
input logic rst_n,
output logic [3:0] q
);
always_ff @(posedge clk) begin
if (!rst_n) q <= '0;
else q <= q + 4'd1;
end
endmodule
`default_nettype wire hdl/count_async.sv
// The same counter with an asynchronous, active-low reset: the falling edge of rst_n wakes the block too.
`default_nettype none
module count_async (
input logic clk,
input logic rst_n,
output logic [3:0] q
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= '0;
else q <= q + 4'd1;
end
endmodule
`default_nettype wire hdl/tb_reset.sv
// Both counters count from reset; then rst_n falls at 38, between two rising edges, and rises again at 48.
module tb_reset;
logic clk, rst_n;
logic [3:0] q_sync, q_async;
count_sync u_sync (.clk(clk), .rst_n(rst_n), .q(q_sync));
count_async u_async (.clk(clk), .rst_n(rst_n), .q(q_async));
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
rst_n = 1'b0;
#12 rst_n = 1'b1;
#26 rst_n = 1'b0;
#10 rst_n = 1'b1;
#10 $finish;
end
initial $monitor("t = %2d: clk %b rst_n %b | q_sync %2d q_async %2d", $time, clk, rst_n, q_sync, q_async);
endmodule Commands · hdl/reset_session.py
from session import run
# $monitor prints a line whenever one of its values changes: a waveform as text.
run("""
iverilog -g2012 -Wall -o sim.vvp tb_reset.sv count_sync.sv count_async.sv
vvp -n sim.vvp
""") Output
# Icarus Verilog 13.0 (stable) $ iverilog -g2012 -Wall -o sim.vvp tb_reset.sv count_sync.sv count_async.sv $ vvp -n sim.vvp t = 0: clk 0 rst_n 0 | q_sync x q_async 0 t = 5: clk 1 rst_n 0 | q_sync 0 q_async 0 t = 10: clk 0 rst_n 0 | q_sync 0 q_async 0 t = 12: clk 0 rst_n 1 | q_sync 0 q_async 0 t = 15: clk 1 rst_n 1 | q_sync 1 q_async 1 t = 20: clk 0 rst_n 1 | q_sync 1 q_async 1 t = 25: clk 1 rst_n 1 | q_sync 2 q_async 2 t = 30: clk 0 rst_n 1 | q_sync 2 q_async 2 t = 35: clk 1 rst_n 1 | q_sync 3 q_async 3 t = 38: clk 1 rst_n 0 | q_sync 3 q_async 0 t = 40: clk 0 rst_n 0 | q_sync 3 q_async 0 t = 45: clk 1 rst_n 0 | q_sync 0 q_async 0 t = 48: clk 1 rst_n 1 | q_sync 0 q_async 0 t = 50: clk 0 rst_n 1 | q_sync 0 q_async 0 t = 55: clk 1 rst_n 1 | q_sync 1 q_async 1 tb_reset.sv:17: $finish called at 58 (1s)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 reset_session.py
Find the lines at 38 and 45. At 38 the reset falls with no rising clock edge near; q_async drops to 0 on that very line, while q_sync still shows 3 until the edge at 45 samples rst_n and clears it. At the start the same thing happens in miniature: q_async is 0 at time 0, q_sync is x until the first edge at 5. Step through the reset around those lines:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- Reset from time 0. rst_n is 0 from the start. The asynchronous counter clears at once, because its block also wakes on the falling edge of rst_n. The synchronous one stays x until the first rising edge, at 5, samples rst_n = 0.
- Counting from 15. rst_n rises at 12. The rising edges at 15, 25 and 35 count both counters to 1, 2 and 3, in step.
- rst_n falls at 38. The reset falls between the edges at 35 and 45. Only a block that waits for this change can react to it now.
- The asynchronous counter clears. count_async wakes on negedge rst_n, its first test, if (!rst_n), is true, and q_async becomes 0 at time 38, with no clock edge.
- The synchronous one waits. Nothing wakes count_sync until a rising clock edge, so q_sync still shows 3 for seven more time units.
- The edge at 45. The rising edge at 45 samples rst_n = 0, and q_sync becomes 0. The asynchronous counter stays at 0, because its reset is still held.
- Release at 48, count at 55. rst_n rises at 48, between edges, which changes nothing by itself. The edge at 55 counts both to 1, together again.
Both styles are in use, and the choice between them, with the timing of a reset’s release, belongs to the reset lesson
of the clocks module. Here the point is what each one describes. A start value written in a declaration, such as
logic [3:0] q = '0;, is not a reset at all: an FPGA’s bitstream can load it at power-up, but a chip’s flip-flops
start in whatever state they settle into, so a design for silicon needs a reset.
Checkpoint
Check: Synchronous and asynchronous resets
An enable becomes a multiplexer in front of the flip-flop
A register that should load only on some edges gets a clock enable: An input that decides whether a register loads a new value at a clock edge or keeps its value. Synthesis builds it as a multiplexer in front of the flip-flop, or a flip-flop with an enable pin.:
if (en) q <= d;. When en is 0 nothing is written at the edge, and in a clocked block that simply means the flip-flop
keeps its value; there is no latch, because a flip-flop holds its value between edges anyway. reg_styles.sv writes
four 1-bit registers, one in each style, and Yosys reports which flip-flop cell it built for each:
hdl/reg_styles.sv
// Four 1-bit registers, one per style: plain, with an enable, with a synchronous reset, with an asynchronous reset.
`default_nettype none
module reg_styles (
input logic clk,
input logic en,
input logic srst_n, // synchronous reset, active low
input logic arst_n, // asynchronous reset, active low
input logic d,
output logic q_plain,
output logic q_en,
output logic q_sync,
output logic q_async
);
always_ff @(posedge clk) q_plain <= d;
always_ff @(posedge clk) begin
if (en) q_en <= d;
end
always_ff @(posedge clk) begin
if (!srst_n) q_sync <= 1'b0;
else q_sync <= d;
end
always_ff @(posedge clk or negedge arst_n) begin
if (!arst_n) q_async <= 1'b0;
else q_async <= d;
end
endmodule
`default_nettype wire Commands · hdl/styles_session.py
from session import run
# Lint, then the flip-flop cells Yosys picks for each style.
run("""
verilator --lint-only -Wall --quiet reg_styles.sv
yosys -q -p "read_verilog -sv reg_styles.sv; synth -top reg_styles; tee -q -o cells.txt stat"
grep DFF cells.txt
""") Output
# Verilator 5.052 · Yosys 0.69
$ verilator --lint-only -Wall --quiet reg_styles.sv
$ yosys -q -p "read_verilog -sv reg_styles.sv; synth -top reg_styles; tee -q -o cells.txt stat"
$ grep DFF cells.txt
1 $_DFFE_PP_
1 $_DFF_PN0_
1 $_DFF_P_
1 $_SDFF_PN0_
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 styles_session.py
The names follow the Yosys cell library, one letter or digit per property:
| Register | Cell | What the name says |
|---|---|---|
q_plain |
$_DFF_P_ |
a flip-flop on the rising (P) clock edge |
q_en |
$_DFFE_PP_ |
with an enable (E), active when it is 1 (P) |
q_sync |
$_SDFF_PN0_ |
with a synchronous reset (S), active low (N), to 0 |
q_async |
$_DFF_PN0_ |
with a reset that does not wait for the clock, active low (N), to 0 |
Yosys used a flip-flop with an enable pin for q_en; where a target has no such cell, the same code becomes a multiplexer that feeds the flip-flop its own output when en is 0. Either way the clock keeps running. Turning the clock itself off, by gating it with logic, saves power but puts logic on the clock path, which brings glitches and timing problems; the clocking lesson of this track covers when and how chips do it safely.
Checkpoint
Check: Enables and the flip-flops synthesis builds
Practise: build a counter in two steps
The lab models a counter’s register in Python, one rising edge per call, in the order of an always_ff block’s tests. The first step adds a synchronous reset, an enable and counting in both directions; the second adds a parallel load with the priorities reset, then load, then enable:
Code lab · 2 steps
Build an up/down counter
Model a counter with a reset, an enable and wrap-around, then add a parallel load that only the reset can beat.
- Model an up/down counter with a reset and an enable To do
- Add a parallel load, with the priorities of an always_ff block To do
Exercise · Easy · Python
Model an up/down counter with a reset and an enable
Model the register of a width-bit counter the way an always_ff @(posedge clk) block describes it: nothing happens between clock edges, and edge() is one rising edge.
Counter(width)starts withvalue0.edge(rst=0, en=0, up=1)applies one rising clock edge. A synchronous reset comes first: withrst1 the value becomes 0. Otherwise, withen1 the counter counts up by 1 whenupis 1 and down by 1 whenupis 0; withen0 it keeps its value.- The counter wraps like a
width-bit register: up from the largest value, 2 to the powerwidthminus 1, gives 0, and down from 0 gives the largest value.
The sample tests use a 4-bit counter, whose values run from 0 to 15.
Starter code · counter.py
class Counter:
"""A width-bit up/down counter with a synchronous reset and an enable, one rising clock edge at a time."""
def __init__(self, width):
self.width = width
self.value = 0
def edge(self, rst=0, en=0, up=1):
"""One rising clock edge: rst clears the counter; otherwise en counts up or down, wrapping.""" The sample tests · test_counter.py
from counter import Counter
def test_counts_up_when_enabled():
"""counts up by one on every edge with en = 1"""
c = Counter(4)
for expected in (1, 2, 3):
c.edge(en=1)
assert c.value == expected
def test_holds_without_enable():
"""keeps its value on an edge with en = 0"""
c = Counter(4)
c.edge(en=1)
c.edge(en=0)
c.edge()
assert c.value == 1
def test_counts_down_and_wraps():
"""counts down with up = 0, and wraps from 0 to 15"""
c = Counter(4)
c.edge(en=1, up=0)
assert c.value == 15
c.edge(en=1, up=0)
assert c.value == 14
def test_wraps_up():
"""wraps from 15 to 0 when counting up"""
c = Counter(4)
for _ in range(16):
c.edge(en=1)
assert c.value == 0
c.edge(en=1)
assert c.value == 1
def test_reset_wins():
"""clears on rst = 1, even when en is 1"""
c = Counter(4)
for _ in range(5):
c.edge(en=1)
c.edge(rst=1, en=1)
assert c.value == 0
def test_other_widths():
"""wraps at the counter's own width"""
c = Counter(3)
c.edge(en=1, up=0)
assert c.value == 7
d = Counter(8)
d.edge(en=1, up=0)
assert d.value == 255 A hint
Write edge() as the if, else if and else of the always_ff block: if rst, then elif en, then nothing. Count with self.value + 1 or self.value - 1, then keep the low width bits with % (1 << self.width), which also turns −1 into the largest value.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Exercise · Medium · Python
Add a parallel load, with the priorities of an always_ff block
Extend the counter of the first step with a parallel load: edge(rst=0, load=0, d=0, en=0, up=1).
The order of the tests in an always_ff block sets the priorities, and this counter's block reads if (rst) … else if (load) … else if (en) …:
rst1 clears the counter, whatever the other inputs say.- Otherwise
load1 storesd, cut to the counter's width as awidth-bit register would cut it: loading 20 into a 4-bit counter stores 4. - Otherwise
en1 counts up or down, wrapping as before. - Otherwise the value stays.
The sample tests cover each priority: a reset during a load, a load while enabled, and wrap-around in both directions.
Starter code · counter.py
class Counter:
"""A width-bit up/down counter with a synchronous reset, a parallel load and an enable."""
def __init__(self, width):
self.width = width
self.value = 0
def edge(self, rst=0, load=0, d=0, en=0, up=1):
"""One rising clock edge, with the priorities rst, then load, then en.""" The sample tests · test_counter.py
from counter import Counter
def test_load_stores_d():
"""stores d on an edge with load = 1"""
c = Counter(4)
c.edge(load=1, d=9)
assert c.value == 9
def test_load_cuts_to_width():
"""keeps only the low bits of a value wider than the counter"""
c = Counter(4)
c.edge(load=1, d=20)
assert c.value == 4
def test_reset_beats_load():
"""clears on rst = 1 even during a load"""
c = Counter(4)
c.edge(load=1, d=9)
c.edge(rst=1, load=1, d=7)
assert c.value == 0
def test_load_beats_enable():
"""loads instead of counting when load and en are both 1"""
c = Counter(4)
c.edge(load=1, d=5, en=1)
assert c.value == 5
def test_counts_after_load():
"""counts on from a loaded value, and wraps in both directions"""
c = Counter(4)
c.edge(load=1, d=15)
c.edge(en=1)
assert c.value == 0
c.edge(en=1, up=0)
assert c.value == 15
c.edge(en=1, up=0)
assert c.value == 14
def test_holds_when_idle():
"""keeps its value with every input at 0"""
c = Counter(4)
c.edge(load=1, d=3)
c.edge()
assert c.value == 3 A hint
Keep the structure of the first step and add one branch, elif load:, between the reset and the enable. Cutting d to the width is the same % (1 << self.width) as for counting.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Then try the self-check on flip-flops, blocking assignments, resets and enables. It starts each time with the idea you have the fewest right answers on:
Practice that adapts to you
Practise this lesson
10 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): what is the difference between blocking and nonblocking assignments?
A blocking assignment, =, changes its variable at once, so every later statement reads the new value. A nonblocking
assignment, <=, reads its right side at once but changes the variable only after every block woken at the same time
has run, so they all read the old values. That is what flip-flops clocked by one edge do, so clocked logic uses <=: a
chain of registers then behaves as a pipeline, whatever the order of the lines. Combinational logic uses =, so that
each line can use the value the line above computed. Mixing them up changes the hardware: four blocking stages of a
shift register synthesise to one flip-flop instead of four.
Two harder questions follow, on a reset that must work without a clock and on a pipeline that lost two stages. Plan your answer to each first, then check how your opening compares with the first step:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A counter must clear even while its clock is stopped, for example before the clock source has started. Which reset style does that need, how do you write it, and what new problem does releasing the reset bring?
-
Fresher to mid
A 3-stage pipeline written in one always_ff block with blocking assignments passes a test that holds its input steady, then fails in the system, where results arrive two cycles early. Explain what was built, why the test missed it, and how you fix it and guard against it.
Flashcards · 7 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
How many flip-flops do s1 <= d; s2 <= s1; s3 <= s2; q <= s3; build, and with = instead?
Four with <=, a shift register. With = each line reads the value just written, every stage holds d after one edge, and Yosys built a single flip-flop.
-
What does Verilator's BLKSEQ warning report?
A blocking assignment, =, in a block woken by a clock edge: "Blocking assignment '=' in sequential logic process".
-
How is an asynchronous active-low reset written in always_ff?
Name the reset's falling edge in the event control, always_ff @(posedge clk or negedge rst_n), and test !rst_n first, so the reset clears the register at once.
-
When does a synchronous reset clear a register?
At the next clock edge, because it is one more input sampled at the edge. q_sync kept 3 from the reset at 38 until the edge at 45, while the asynchronous q_async cleared at 38.
-
Does if (en) q <= d; in always_ff build a latch?
No. When en is 0 the flip-flop keeps its value, as it does between edges anyway. Yosys built a flip-flop with an enable pin, $_DFFE_PP_; without such a cell it becomes a multiplexer feeding q back.
-
What does the Yosys cell name $_SDFF_PN0_ say?
A flip-flop on the rising clock edge (P) with a synchronous reset (S), active low (N), that resets to 0.
-
What does a register without a reset show before data reaches it?
x on Icarus Verilog, which keeps four values, and 0 on Verilator, which is mostly two-state.
Key takeaways
- always_ff with nonblocking assignments describes flip-flops that all read their inputs from before the edge, so the 1 of tb_shift4.sv moved one stage per edge and reached q after four.
- The same four lines with blocking assignments copy d through every stage in one edge: one flip-flop instead of four, as Yosys counted, with a BLKSEQ warning from Verilator for each line.
- A synchronous reset waits for the clock, so q_sync kept 3 from 38 to 45, while an asynchronous reset, named in the event control and tested first, cleared q_async at 38.
- A register without a reset shows x on Icarus Verilog and 0 on Verilator, and a start value in a declaration is not a reset on a chip.
- An enable keeps one clock and decides whether to load, which Yosys built as $DFFE_PP; its cell names spell out the clock edge, the enable and the reset of every flip-flop.
Terms in this lesson
- asynchronous reset
- A reset that acts as soon as it is asserted, without waiting for a clock edge. In RTL its edge is named in the event control, as in @(posedge clk or negedge rst_n).
- clock enable
- An input that decides whether a register loads a new value at a clock edge or keeps its value. Synthesis builds it as a multiplexer in front of the flip-flop, or a flip-flop with an enable pin.
- flip-flop
- A storage element that takes the value of its input at a clock edge and holds it until the next edge. A register is a row of flip-flops clocked together.
- shift register
- A chain of flip-flops clocked together, each loading the previous one's value at every edge, so that data moves one stage per clock cycle.
- synchronous reset
- A reset that a flip-flop samples at the clock edge like any other input, so it takes effect only at an edge.
References
- IEEE 1800-2023: IEEE Standard for SystemVerilog, Unified Hardware Design, Specification, and Verification Language (IEEE Standards Association)
- Yosys documentation: flip-flop cells (YosysHQ)
- Verilator documentation: warnings (BLKSEQ) (Verilator)
- Verilator documentation: input languages (unknown states) (Verilator)
- Icarus Verilog: iverilog command-line flags (Icarus Verilog)
Report a problem with this lesson
Kept only in this browser. Your Learn progress