Testbench idioms compile, simulate beautifully, and describe no hardware at all. The failure mode is silent: the tool ignores the offending construct, keeps the surrounding register, and the chip disagrees with the testbench on day one. Four constructs get a recorded run each next to the same 8-bit register, a delay, an initial block, a real parameter, and a display call, plus a clock-free oscillator on its own. Each runs through read_verilog and proc with stat and the JSON netlist saved. A note on honesty: each script runs to its PASS line, which proves elaboration continued past the construct, but warnings are not recorded, so the verdicts rest on the netlists. The delay run records 1 $dff and nothing else: that lone flop is the register, never the construct.
None of them. The recorded totals are 1 for the delay run, 1 for the initial run, 1 for the real run, 2 for the display run, and 0 for the oscillator run: the register flop every time it exists, plus a $print marker in the display run, and nothing anywhere else. A real variable is rejected at parse here, so the run uses the closest form that elaborates: a real parameter.
Each of the four register scripts isolates one construct next to the same ordinary 8-bit register; the oscillator runs alone. Each elaborates, runs proc, and records stat plus the netlist. The assertions are deliberately narrow: one flop for the register, no $dlatch anywhere. Anything the construct added would show up as an extra cell in the recorded netlist, which is what makes the table checkable instead of rhetorical.
read_verilog <<EOV
module delay_nb (
input wire clk,
input wire [7:0] d,
output reg [7:0] q
);
always @(posedge clk) begin
q <= #5 d;
end
endmodule
EOV
hierarchy -check -top delay_nb
proc
tee -o stat_delay.txt stat
write_json net_delay.json
select -assert-count 1 t:$dff
select -assert-none t:$dlatch
log PASS: delay is ignored and the flop is still inferredread_verilog <<EOV
module init_nb (
input wire clk,
input wire [7:0] d,
output reg [7:0] q
);
initial q = 8'd0;
always @(posedge clk) begin
q <= d;
end
endmodule
EOV
hierarchy -check -top init_nb
proc
opt
tee -o stat_init.txt stat
write_json net_init.json
select -assert-count 1 t:$dff
select -assert-none t:$dlatch
log PASS: initial block leaves one flop inferred with no latchread_verilog -sv <<EOV
module real_nb (
input wire clk,
input wire [7:0] d,
output reg [7:0] q
);
parameter real GAIN = 1.5;
always @(posedge clk) begin
if (GAIN > 1.0)
q <= d;
else
q <= 8'd0;
end
endmodule
EOV
hierarchy -check -top real_nb
proc
tee -o stat_real.txt stat
write_json net_real.json
select -assert-count 1 t:$dff
select -assert-none t:$dlatch
log PASS: real parameter folds away and the flop is still inferredread_verilog <<EOV
module disp_nb (
input wire clk,
input wire [7:0] d,
output reg [7:0] q
);
always @(posedge clk) begin
q <= d;
$display("q=%h", q);
end
endmodule
EOV
hierarchy -check -top disp_nb
proc
tee -o stat_disp.txt stat
write_json net_disp.json
select -assert-count 1 t:$dff
select -assert-none t:$dlatch
log PASS: display call leaves the flop unchanged with no latchread_verilog <<EOV
module osc_nb (
output reg clk
);
initial clk = 0;
always #5 clk = ~clk;
endmodule
EOV
hierarchy -check -top osc_nb
proc
opt
tee -o stat_osc.txt stat
write_json net_osc.json
select -assert-none t:$dlatch
select -assert-none t:$dff
log PASS: delay oscillator elaborates to no cellsThe table is the whole lesson in five rows. The behaviour column states only what the recorded netlist proves: cell counts from the JSON, plus the init-attribute reading where one exists.
Construct | Recorded netlist | Tool behaviour |
|---|---|---|
| 1 cell: the register flop, nothing else | Ignored: the flop is inferred exactly as without it. |
| 1 cell: the register flop plus a power-up value | Kept as metadata, not logic: init=00000000 on net q beside the register flop, no extra cell. FPGA flows honour it and ASIC flows do not. |
| 1 cell: the register flop, parameter folded | Acceptable: the parameter folds before mapping and leaves no cell. |
| 2 cells: the register flop plus a $print marker | Simulation-only: a $print marker rides along beside the logic. |
Delay oscillator, no clock | no cells | Vanishes: the toggle loop leaves no cell and the output dangles. |
Two rows deserve a second sentence. The delay row is harmless to synthesis and lethal to understanding: a reader sees sequencing where the netlist has simultaneity. The initial row is easy to misread as a reset: it runs once at time zero in simulation, while a reset runs on every asserted edge, and nothing on the chip performs the initial step unless the flow documents power-up values.
Each ignored construct fails for a different structural reason. A delay advances a simulation time wheel the synthesis model never builds. An initial block fires once at time zero while hardware sequencing needs repetition, so it can at most donate the recorded power-up value. The display call drives a simulator transcript the netlist has no port for. The real parameter succeeds for the opposite reason: it is an elaboration-time constant, so no real value reaches the cells, only the branch it selected.
Treating an initial block as a maybe-reset is the expensive mistake to avoid: an async reset is checked against recovery and removal on deassertion, while a power-up value is a hope about the first cycle that no timing report covers. When in doubt, write the reset and delete the initial block.
The testbench below is the classic delay idiom: a free-running toggle with no clock input, which oscillates in simulation and describes no hardware. It runs here in simulation only, while the osc_nb script above runs the same idiom through Yosys and records no cells: simulation toggles, elaboration leaves the output dangling.
module tb_osc;
reg clk;
integer n;
initial begin
clk = 0;
n = 0;
#100;
if (n >= 8) $display("PASS: delay oscillator toggled %0d times in simulation", n);
else $display("FAIL: oscillator stalled at %0d edges", n);
$finish;
end
always #5 begin
clk = ~clk;
n = n + 1;
end
endmoduleAn unbounded while loop is the construct this flow rejects outright. Run the listing below by hand: Yosys stops with the error "While loops are only allowed in constant functions" (hand-observed). The runner fails any block that prints an ERROR line, so this listing stays hand-run. The fixed-bound loop above unrolls because the tool can count its iterations; the while loop cannot be counted.
# not-run: Yosys rejects the loop with an ERROR line, which fails a recorded block, so run this listing by hand
read_verilog <<EOV
module wloop_nb (
input wire clk,
input wire go,
input wire [7:0] d,
output reg [7:0] q
);
always @(posedge clk) begin
while (go) q <= d;
end
endmodule
EOV
hierarchy -check -top wloop_nb
proc
log PASS: unbounded while loop listing is rejectedThe table below extends the lesson past what was recorded. Each construct is legal Verilog or SystemVerilog that this Yosys frontend rejects or cannot resolve; none can ship with a PASS line because the runner fails any block that prints an ERROR line. They are Yosys frontend behaviour, not recordings.
Construct | What this Yosys frontend does | Status |
|---|---|---|
Unbounded | Rejected at elaboration: while loops are allowed only in constant functions. | Yosys frontend behaviour: hand-run listing above. |
| Rejected at parse: | Yosys frontend behaviour, confirmed by hand run. |
| Rejected at parse in this frontend. | Yosys frontend behaviour, confirmed by hand run. |
File I/O ( | Rejected at elaboration: the function name cannot be resolved. | Yosys frontend behaviour, confirmed by hand run. |
None. The recorded delay run totals 1 cell: the delay advances simulation time only and never reaches the netlist.
The block is testbench code. The recorded initial run totals 1 cell with init=00000000 on q, so nothing on the chip performs that step unless the flow honours power-up values; use reset logic instead.
Every row of the table comes from Yosys 0.70+post runs recorded by the course checker: delay, initial, display and oscillator variants run read_verilog with proc, and the real variant runs read_verilog -sv with a real parameter steering an elaboration-time branch; stat plus write_json are recorded per variant. Each of the four register runs records the register flop and nothing else for its construct, and the recorded totals carry the numbers. A real variable declaration does not parse in this Yosys frontend, so the parameter is the runnable representative, and an unbounded while loop ships as a hand-run listing because its ERROR line fails a recorded block. The initial verdict rests on the init attribute proc_init records on the net, read from the recorded JSON. Anything about warnings is stated only as observed continuation to PASS, and the extra constructs are marked as Yosys frontend behaviour, not recordings.