Reset costs a pin, routing, and area on every flop that carries it, so wide datapaths reset only what correctness needs: state, valid flags, and control. This lesson mixes a 16-bit reset-less datapath with a sync-reset flag in one module, prices a 16-bit register with and without sync reset from the recorded stat reports, follows an initial value into the netlist attribute it becomes, and watches an early read spread unknown in simulation. The verdict it builds toward: a reset-less data-path flop is fine, provided nothing reads it before its first load.
Data registers that are always written before they are read need no reset: their first useful value comes from a load, not from power-up. What must reset is the knowledge of whether the data is meaningful: the valid flag, the state register, the pointers. The first script mixes both families in one 16-bit pipeline stage: the data vector loads unconditionally while a valid flag carries the sync reset. It asserts one $dff beside one $sdff, proving the two families coexist in one module with no async-reset or enabled cell anywhere.
read_verilog <<EOV
module pipe16(input clk, input srst, input fill, input [15:0] d, output reg [15:0] q, output reg valid);
always @(posedge clk) q <= d;
always @(posedge clk) if (srst) valid <= 1'b0; else valid <= fill;
endmodule
EOV
hierarchy -check -top pipe16
proc
opt
select -assert-count 1 t:$dff
select -assert-count 1 t:$sdff
select -assert-none t:$adff
select -assert-none t:$dffe
tee -o stat_pipe16_generic.txt stat
write_json net_pipe16_generic.json
log PASS: reset-less datapath infers $dff beside a sync-reset $sdff flagThe reset-less form reports chip area 128 in plain DFFs, while the reset form reports 178: the same storage plus data-path gating on D. Skipping reset on the datapath saves 50 units, and the flag beside it keeps the saving safe.
Pipeline stage piece | Generic cells (recorded) | Reading |
|---|---|---|
16-bit data vector | 1 $dff cell | Reset-less: loaded before read, never cleared. |
Valid flag | 1 $sdff cell | Sync reset: says whether the data means anything. |
The second script maps the 16-bit data vector twice: once plain and once with a sync reset, through synth, dfflegalize, dfflibmap, abc, and stat. Both runs assert their storage so the comparison is exact. The recorded totals read 128 without reset against 178 with it, so the reset costs 50 units on this width: plain DFFs plus a NOR-and-inverter gate on every D input. No DFFR appears in either row: a sync reset is a plain flop plus gates on D, never the async cell's area.
read_verilog <<EOV
module data16(input clk, input [15:0] d, output reg [15:0] q);
always @(posedge clk) q <= d;
endmodule
EOV
read_liberty -lib course.lib
hierarchy -check -top data16
synth -top data16 -flatten
dfflegalize -cell $_DFF_P_ 01 -cell $_DFF_PN0_ 01
dfflibmap -liberty course.lib
tee -o abc_data16.txt abc -liberty course.lib -constr typical.constr -D 1000
tee -o stat_data16.txt stat -liberty course.lib
write_json net_data16.json
select -assert-count 16 t:DFF
design -reset
read_verilog <<EOV
module data16r(input clk, input srst, input [15:0] d, output reg [15:0] q);
always @(posedge clk) if (srst) q <= 16'd0; else q <= d;
endmodule
EOV
read_liberty -lib course.lib
hierarchy -check -top data16r
synth -top data16r -flatten
dfflegalize -cell $_DFF_P_ 01 -cell $_DFF_PN0_ 01
dfflibmap -liberty course.lib
tee -o abc_data16r.txt abc -liberty course.lib -constr typical.constr -D 1000
tee -o stat_data16r.txt stat -liberty course.lib
write_json net_data16r.json
select -assert-none t:$dlatch
log PASS: reset-less and sync-reset datapaths map with recorded costs16-bit datapath | Mapped library cells (recorded) | Chip area (stat -liberty) |
|---|---|---|
Without reset | 16 DFF | 128 |
With sync reset | 16 DFF + 16 NOR2 + 16 INV + 1 BUF | 178 |
An initial assignment sets the simulation value at time zero. Yosys honours it during elaboration: the third script gives an 8-bit register the initial value 8'hA5, runs proc, and asserts the usual single $dff. The recorded netlist carries that value as an init attribute on the output net, reading 10100101 in binary. Whether the attribute reaches silicon depends on the target: FPGA flows such as synth_ice40 honour init values by configuring the flop state, while an ASIC flow has no such cell and the value buys nothing. The lesson states that split as standard background, not as a run: the recording stops at the init attribute, and only one mapping family is run on this page.
read_verilog <<EOV
module initreg(input clk, input [7:0] d, output reg [7:0] q);
initial q = 8'hA5;
always @(posedge clk) q <= d;
endmodule
EOV
hierarchy -check -top initreg
proc
select -assert-count 1 t:$dff
tee -o stat_initreg.txt stat
write_json net_initreg.json
log PASS: initial value rides along as an init attribute on the flopInitial-value question | Recorded answer |
|---|---|
Generic family | 1 $dff: init changes no family. |
Where the value lands | Output-net |
FPGA flows | Honour it: configuration sets the flop state. |
ASIC flows | Ignore it: there is no such cell; add a reset if power-up matters. |
A flop with no reset reads unknown (x) in simulation until its first load. Silicon powers up to an unpredictable but resolved 0 or 1, so any bit read before its first write is still a real bug even though no simulator flag survives to the chip. The two testbenches below show both sides on Icarus, which keeps four-state values: first the safe pattern, where the consumer waits for the valid flag, then the break-it, where it reads too early.
// engine: icarus
module pipe8 (
input logic clk, input logic srst, input logic fill,
input logic [7:0] d, output logic [7:0] q, output logic valid
);
always @(posedge clk) q <= d;
always @(posedge clk) if (srst) valid <= 1'b0; else valid <= fill;
endmodule
module tb_x;
logic clk = 0, srst = 1, fill = 0;
logic [7:0] d, q;
logic valid;
pipe8 dut (.*);
always #5 clk = ~clk;
int errors = 0;
initial begin
#3;
if (^q !== 1'bx) begin $display("FAIL: q not x before first load"); errors++; end
if (valid !== 1'bx) begin $display("FAIL: valid not x before first edge"); errors++; end
@(posedge clk); #1;
if (valid !== 1'b0) begin $display("FAIL: valid not 0 under reset"); errors++; end
if (^q !== 1'bx) begin $display("FAIL: q resolved with no data written"); errors++; end
srst = 0; fill = 1; d = 8'hA5;
@(posedge clk); #1;
@(posedge clk); #1;
if (q !== 8'hA5) begin $display("FAIL: q did not load"); errors++; end
if (valid !== 1'b1) begin $display("FAIL: valid did not assert"); errors++; end
if (errors == 0) $display("PASS: reset-less q starts x, then loads beside its valid flag");
$finish;
end
endmoduleThe second testbench drops the valid flag and samples the data through combinational logic from time zero. Before the first edge the output is unknown, and the bench asserts exactly that: the early sample must read as x, and only the loaded value afterwards must match. The passing run proves the consequence rather than describing it: an unread-before-write window is harmless only when nothing observes it, and this consumer observes it.
// engine: icarus
module pipe8b (
input logic clk, input logic [7:0] d, output logic [7:0] q
);
always @(posedge clk) q <= d;
endmodule
module tb_early;
logic clk = 0;
logic [7:0] d = 8'hA5, q, y;
pipe8b dut (.*);
assign y = q & 8'hF0;
always #5 clk = ~clk;
int errors = 0;
initial begin
#3;
if (^y !== 1'bx) begin $display("FAIL: early read not x"); errors++; end
@(posedge clk); #1;
if (y !== (8'hA5 & 8'hF0)) begin $display("FAIL: loaded value wrong"); errors++; end
if (errors == 0) $display("PASS: reading before the first load spreads x downstream");
$finish;
end
endmoduleResetting the datapath but not its valid flag leaves stale data marked valid after reset. Reset the control that says the data is meaningful, even when the data bits themselves skip reset. The twin mistake is trusting an initial value on an ASIC: simulation starts at A5 while silicon starts anywhere, so a power-up read without reset or a first load is a bug no init attribute fixes.
An extra 50 units: 178 with reset against 128 without. The saving is the reset gating on every bit, while the valid flag beside it keeps the saving safe.
x on a reset-less data bus for the first cycles, then valid data. Is the design broken?Not necessarily. If the consumer ignores the bus until its valid flag asserts, the x window is harmless, as the first testbench records. If anything reads it first, that read is a bug, as the break-it testbench proves by asserting the x.
Generic families and mapped areas on this page come from three Yosys 0.70 runs recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): a mixed 16-bit stage through proc and opt, 16-bit plain and sync-reset datapaths through synth, dfflegalize, dfflibmap -liberty course.lib, abc -liberty course.lib -constr typical.constr -D 1000 and stat -liberty course.lib, and an 8-bit register with an initial value through proc with its init attribute read from the recorded netlist. The x-propagation runs are Icarus testbenches; the FPGA-against-ASIC split is standard background, not a run. The library is fictional, so absolute areas are teaching units.