The same 4-stage, 32-bit pipeline was synthesised three ways. With every flop reset, the reset net rst_n drives 136 loads; with only the valid chain and one counter reset, it drives 8, and the data that comes out is identical.
A reset is not free. Each reset flop is a larger cell or has a gate in front of it, and every one is a load on a net that has to reach the whole block within its timing window. The picture compares the three versions.
Read the top chart as logic size and the bottom chart as reset-tree size.
(a) all async and (c) control only have the same cell count. The difference is in the flop types: (a) uses 136 reset flops, (c) only 8.
(b) all sync adds 136 gates, one in front of every flop's D input, to force it to 0 when rst_n is low.
The reset net has 136 loads in (a) and (b) but 8 in (c). That is the number the reset tree has to be built and timed for.
A word enters with in_valid, passes four stages (capture, XOR with a key, rotate by a byte, add 1) and leaves with out_valid. The valid bits travel in a 4-bit shift register v beside the data. A 4-bit counter beats counts delivered words. Version (c), below, resets only v and beats.
// yosys: ok
// Version (c): only control state is reset. Data registers load when valid.
module pipe_c (
input logic clk, rst_n,
input logic in_valid,
input logic [31:0] in_data,
input logic [31:0] key,
output logic out_valid,
output logic [31:0] out_data,
output logic [3:0] beats // words delivered since reset
);
logic [3:0] v; // valid chain: control, reset
logic [31:0] d1, d2, d3, d4; // datapath: no reset
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin
v <= '0;
beats <= '0;
end else begin
v <= {v[2:0], in_valid};
if (v[3]) beats <= beats + 1'b1;
end
always_ff @(posedge clk) begin
if (in_valid) d1 <= in_data;
if (v[0]) d2 <= d1 ^ key;
if (v[1]) d3 <= {d2[23:0], d2[31:24]};
if (v[2]) d4 <= d3 + 32'd1;
end
assign out_valid = v[3];
assign out_data = d4;
endmoduleVersion (a) keeps the same control block and puts the data registers under the same asynchronous reset:
// snippet: version (a), data block; the control block is as in pipe_c
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin
d1 <= '0; d2 <= '0; d3 <= '0; d4 <= '0;
end else begin
if (in_valid) d1 <= in_data;
if (v[0]) d2 <= d1 ^ key;
if (v[1]) d3 <= {d2[23:0], d2[31:24]};
if (v[2]) d4 <= d3 + 32'd1;
endVersion (b) is version (a) with or negedge rst_n removed from both event lists, so both blocks reset on a clock edge. The three versions hold 136 flops: 128 data bits, 4 valid bits and 4 counter bits.
A plain synth -top pipe_x; stat reports 236 cells for all three versions: 100 logic gates and 136 flops. Only the flop names differ.
Version | Flops in generic synthesis | Logic | Total |
|---|---|---|---|
(a) all async | 132 | 100 | 236 |
(b) all sync | 132 | 100 | 236 |
(c) control only | 128 | 100 | 236 |
Read the names letter by letter. DFFE has an enable, PN0 means a positive clock and an active-low reset to 0, and the S in SDFF means the reset is synchronous. The generic library has a flop for every combination, so the reset cost hides inside the cell name.
To see it, map the flops to a smaller set: a plain flop $_DFF_P_ and an async-reset flop $_DFF_PN0_, with no enable and no synchronous reset built in. Anything else has to be built from gates in front of D:
yosys -p "read_verilog -sv pipe_b.sv; synth -top pipe_b; dfflegalize -cell \$_DFF_P_ x -cell \$_DFF_PN0_ x; opt -nodffe -nosdff; stat; select -count w:rst_n %co1 w:rst_n %d"
...
504 cells
30 $_AND_
136 $_DFF_P_
268 $_MUX_
2 $_NAND_
2 $_NOT_
2 $_XNOR_
64 $_XOR_
136 objects.The last line is the select -count result: the number of cells connected to rst_n, with the wire itself removed from the selection. The same two commands on all three versions give this table.
Version | Flops | Reset flops |
| Other logic | Total cells | Loads on rst_n |
|---|---|---|---|---|---|---|
(a) all async | 136 | 136 | 132 | 100 | 368 | 136 |
(b) all sync | 136 | 0 | 268 | 100 | 504 | 136 |
(c) control only | 128 | 8 | 132 | 100 | 368 | 8 |
Read the columns one at a time.
132 muxes in every version are the enables: each enabled flop now holds its value through a mux from Q back to D. They cost the same in all three.
(b) has 136 more muxes. Each one forces D to 0 while rst_n is low, so the synchronous reset becomes a gate in every data path, including the output of the 32-bit adder.
(a) and (c) have the same totals, but (a) uses 136 reset flops where (c) uses 8. In a real library a flop with a reset pin is a larger cell than a plain one, so (c) is smaller even at the same count.
Loads on rst_n fall from 136 to 8 only in (c). Removing the reset, not changing its style, is what shrinks the reset tree.
The reset tree is the cost that grows with the chip. An asynchronous reset must reach every reset pin within its recovery and removal window, so the net is buffered and balanced like a clock (see Reset tree planning). A synchronous reset needs the same net, timed as data to every gate. With thousands of data registers per block, removing their resets removes most of that tree.
A register needs a reset when its power-up value can change what the block does. A register whose value is always written before anyone reads it does not.
Register | Reset? | Why |
|---|---|---|
FSM state | Yes | A random start state can do anything, including an illegal encoding. |
Valid bits, request and handshake flags | Yes | They decide whether other registers are read. |
Counters and pointers that gate outputs | Yes | A random count produces a wrong |
Software-visible configuration | Yes | Software expects the documented reset values. |
Pipeline data qualified by a valid bit | No | It is written before it is used, and the valid bit hides it until then. |
Memories and register files | No | Too many bits; initialise them by protocol or mark them unwritten. |
The bench below runs version (c) against a reference model. It prints the first nine cycles, then sends 20 cycles of traffic with a gap every third cycle and checks every word that comes out. It also checks on every cycle that out_valid is never x.
cycle 1 rst_n=0 in_valid=0 d1=xxxxxxxx out_valid=0 out_data=xxxxxxxx
cycle 2 rst_n=0 in_valid=0 d1=xxxxxxxx out_valid=0 out_data=xxxxxxxx
cycle 3 rst_n=1 in_valid=0 d1=xxxxxxxx out_valid=0 out_data=xxxxxxxx
cycle 4 rst_n=1 in_valid=0 d1=xxxxxxxx out_valid=0 out_data=xxxxxxxx
cycle 5 rst_n=1 in_valid=1 d1=a0000000 out_valid=0 out_data=xxxxxxxx
cycle 6 rst_n=1 in_valid=1 d1=a0000001 out_valid=0 out_data=xxxxxxxx
cycle 7 rst_n=1 in_valid=0 d1=a0000001 out_valid=0 out_data=xxxxxxxx
cycle 8 rst_n=1 in_valid=1 d1=a0000003 out_valid=1 out_data=0f0f0fb0
cycle 9 rst_n=1 in_valid=1 d1=a0000004 out_valid=1 out_data=0f0f0eb0
PASS: 14 words correct; out_valid never X although data registers start as XEach line is printed 1 ns after a rising clock edge and shows the values that edge produced.
Cycles 1 to 4: d1 and out_data are x, because nothing has written them. out_valid is 0 from the first cycle, because v is reset.
Cycle 5: the first word is captured, so d1 becomes a0000000. out_data is still x, and out_valid is still 0.
Cycle 7: a gap: in_valid is 0 and d1 holds its value.
Cycle 8: the first word arrives three edges after cycle 5, with out_valid = 1 and a known value: a0000000 XOR 0f0f0f0f, rotated by one byte, plus 1, which is 0f0f0fb0.
The x on out_data in cycles 1 to 7 is real: those flops hold random values in silicon too. It is harmless because no one reads out_data while out_valid is 0. The trap is a consumer that does. If it adds out_data into a sum every cycle, simulation shows x and the bug is visible. If it tests if (out_data == K), an if treats x as false and simulation takes the else branch: the bug is masked. X-propagation discipline covers why that optimism hides bugs.
beats only counts delivered words. Why is it reset, and what would simulation show without the reset?It is a software-visible output, and anything that reads it, such as a full or interrupt check, would act on a random power-up count. Without the reset, beats starts as x and x + 1 stays x, so the counter never becomes known in simulation.
The synchronous reset is a gate in front of each flop's D input, so rst_n drives 136 gate inputs instead of 136 reset pins. The tree is the same size; it is timed as a data path instead of for recovery and removal. Only removing resets, as in (c), shrinks it.
The bench repeats pipe_c so the block runs on its own. A queue holds every word sent, and a reference function computes the expected output for each.
module pipe_c (
input logic clk, rst_n,
input logic in_valid,
input logic [31:0] in_data,
input logic [31:0] key,
output logic out_valid,
output logic [31:0] out_data,
output logic [3:0] beats
);
logic [3:0] v;
logic [31:0] d1, d2, d3, d4;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin
v <= '0;
beats <= '0;
end else begin
v <= {v[2:0], in_valid};
if (v[3]) beats <= beats + 1'b1;
end
always_ff @(posedge clk) begin
if (in_valid) d1 <= in_data;
if (v[0]) d2 <= d1 ^ key;
if (v[1]) d3 <= {d2[23:0], d2[31:24]};
if (v[2]) d4 <= d3 + 32'd1;
end
assign out_valid = v[3];
assign out_data = d4;
endmodule
Tool: Yosys 0.69+post (git sha1 143eb14f9), run on 2026-10-17, on pipe_a.sv, pipe_b.sv and pipe_c.sv (the three versions on the page). Commands, for x in a b c: yosys -p "read_verilog -sv pipe_x.sv; synth -top pipe_x; stat" and yosys -p "read_verilog -sv pipe_x.sv; synth -top pipe_x; dfflegalize -cell $_DFF_P_ x -cell $_DFF_PN0_ x; opt -nodffe -nosdff; stat; select -count w:rst_n %co1 w:rst_n %d".
Result: generic synthesis gives 236 cells for all three (100 logic, 136 flops). After the mapping: (a) 368 cells with 136 $_DFF_PN0_ and 132 $_MUX_; (b) 504 cells with 136 $_DFF_P_ and 268 $_MUX_; (c) 368 cells with 128 $_DFF_P_, 8 $_DFF_PN0_ and 132 $_MUX_. select -count reports 136, 136 and 8 objects on rst_n. The chart and tables use these numbers.
Tool: Icarus Verilog 13.0, same date. Command: iverilog -g2012 -o tb.vvp pipe_c.sv tb_pipe_c.sv && vvp -n tb.vvp. Result: the nine trace lines and PASS line on the page (14 words checked against the reference model, out_valid never x). The same bench was also run against pipe_a and pipe_b and passed, so all three deliver the same words.
Scope and limits: generic cells, no technology library, so counts are not area or timing; the mapping models a library without enable or synchronous-reset flops. Reset-tree buffering and recovery/removal timing need placement and STA, which were not run.