A tied input looks harmless: one pin strapped to 1, one to 0. But each constant turns its gate into a simpler function, and the simpler function feeds the next gate, which may fold in turn. One tie-off can therefore delete a whole cone of logic, and this lesson measures exactly that on a real run.
Constants arrive from several places: pins strapped on the board, parameters fixed for a build, width masks that keep some bits and kill others, and flops loaded with a constant. The tool treats them all the same way, folding forward until no gate has a constant input left. The script flattens the hierarchy first because opt does not push constants through module ports on its own.
The design below computes one bit as y = (a & one) | (b & zero) inside a submodule, with the tie-offs strapped at the top level, alongside a 4-bit bus masked by a constant vector. The constants cross the hierarchy as port values rather than literals, so elaboration keeps the gates and the before picture shows them. The flattened script saves the generic netlist, records a stat report, runs opt, and records the second report. Mapping the folded result with ABC gives the final cover. The two assertions pin the outcome: no generic AND, OR or NOT cell survives.
read_verilog <<EOV
module constfold_core(input wire a, input wire b, input wire [3:0] d, input wire one, input wire zero, input wire [3:0] mask, output wire y, output wire [3:0] q);
assign y = (a & one) | (b & zero);
assign q = d & mask;
endmodule
module constfold(input wire a, input wire b, input wire [3:0] d, output wire y, output wire [3:0] q);
constfold_core core (.a(a), .b(b), .d(d), .one(1'b1), .zero(1'b0), .mask(4'b1010), .y(y), .q(q));
endmodule
EOV
hierarchy -check -top constfold
proc
flatten
delete t:$scopeinfo
design -save pre
tee -o stat_before.txt stat
write_json net_before.json
design -load pre
opt
opt_clean
check
tee -o stat_after.txt stat
write_json net_opt.json
techmap
tee -o abc_after.txt abc -liberty course.lib -constr typical.constr -D 1000
tee -o stat_mapped.txt stat -liberty course.lib
write_json net_after.json
select -assert-none t:$and t:$or t:$not
check
log PASS: constants fold away leaving wires that map to no cellsopt, and what does ABC cover?None of them. The recorded netlist after optimisation holds no AND cells and no OR cells, down from 3 and 1, and ABC maps the remainder to no cells at all. Folding (a & one) to a and (b & zero) to 0 leaves y = a | 0, which is the wire a, and the mask resolves bit by bit in the same way.
Stage | Generic AND | Generic OR | Mapped cover |
|---|---|---|---|
Before opt (recorded netlist) | 3 | 1 | not mapped yet |
After opt (recorded netlist) | 0 (everything folded) | 0 (everything folded) | not mapped yet |
After ABC to course.lib | 0 (nothing left to map) | 0 (nothing left to map) | no cells: y is the wire a |
Read the table as cause and effect. Before optimisation the recorded netlist holds 3 generic AND cells and 1 generic OR cell: two ANDs plus the OR from the single bit, and one wide AND from the mask. After optimisation no generic cells remain, each fold having replaced a gate with an equal function.
recorded stat before opt
5. Printing statistics.
=== constfold ===
+----------Local Count, excluding submodules.
|
17 wires
35 wire bits
13 public wires
28 public wire bits
5 ports
11 port bits
4 cells
3 $and
1 $orrecorded stat after opt
10. Printing statistics.
=== constfold ===
+----------Local Count, excluding submodules.
|
13 wires
28 wire bits
13 public wires
28 public wire bits
5 ports
11 port bitshand count for the single bit, from the course.lib cell table (AND2 and OR2 cost 3 units each)
before: 2 AND2 + 1 OR2 = 2 x 3 + 1 x 3 = 9 area units
after: y is the wire a = 0 area units
saving: 9 area units, the whole coneThe second output applies the same idea four times at once: q = d & mask with the top level driving 4'b1010. Each bit folds independently against its own mask bit. Bit 3 and bit 1 meet a 1 and survive as wires, while bit 2 and bit 0 meet a 0 and become constant drivers. The wide AND in the before picture is the elaborated form of this expression, and it is gone in the after picture for exactly this reason.
Masks like this appear wherever software writes a control register with reserved bits: a constant vector folds lane by lane, so a masked bus costs nothing after optimisation. Parameters behave the same way, which is why changing one re-opens the cone and moves the area.
The second script ties a constant to a register instead of a gate: a flop whose data input is always 0. There is no combinational fold to perform, so a different pass does the work. opt_dff deletes the flop cell itself once its data input is constant, provided the flop's initial or reset value is undefined or already equals that constant (a flop initialized to 1 with data tied to 0 is kept, since deleting it would change the reset behaviour), and opt_clean then removes the dangling wires.
read_verilog <<EOV
module flopconst(input wire clk, output wire q);
reg qq;
always @(posedge clk) qq <= 1'b0;
assign q = qq;
endmodule
EOV
hierarchy -check -top flopconst
proc
write_json net_flop_before.json
tee -o log_dff.txt opt_dff
opt_clean
check
write_json net_flop_after.json
select -assert-none t:$dff
log PASS: flop with constant input becomes a constant driverrecorded opt_dff log: banner plus the action line
5. Executing OPT_DFF pass (perform DFF optimizations).
Setting constant 0-bit at position 0 on $procdff$2 ($dff) from module flopconst.The recorded netlists hold 1 flop before the pass and 0 after it: the register is gone and the output is a constant wire. Resets and enables fold the same way whenever they are stuck: a flop that can never change state is not storage, it is a tie-off with a clock pin, and the tool treats it as one.
Now change one thing about the setup: leave the second AND input floating. An undriven input is not a constant; it is a wire with no driver, so it carries no value at all: not 0, not 1, and not even an x the folder could resolve. The third script builds y = a & u with u undriven, runs the same opt, and asserts that the AND gate is still there.
read_verilog <<EOV
module floatin(input wire a, output wire y);
wire u;
assign y = a & u;
endmodule
EOV
hierarchy -top floatin
proc
opt
write_json net_float.json
select -assert-count 1 t:$and
log PASS: undriven input has no driver so its gate survives optimisationThe recorded break-it netlist keeps 1 AND cell after optimisation: nothing folded, because opt folds only known constants and an undriven wire is not one. Contrast that with the main run, where b and zero folded on a known value. Giving the wire a value takes an explicit step such as opt -undriven or setundef, so drive every input explicitly and never depend on a default you have not read.
Read the after picture bit by bit before quoting any saving: the 1-bits survive as wires, the 0-bits as constants, and neither shows up in the cell count.
y = (a & one) | (b & zero). What does the tool keep, and what does the mapped report show?Just the wire a. The ANDs and the OR fold away, and the mapped report lists no cells at all.
Known against driverless. The main run folds on the known 0 and 1, while the break-it input has no driver and no value, so there is nothing to fold. An explicit opt -undriven or setundef step would give it one.
Every number and picture on this page comes from three Yosys 0.69+post runs recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): a tied-input plus mask design through proc, opt and abc -liberty course.lib with stat and netlists before and after; a constant flop through opt_dff; an undriven-input design through opt. course.lib is fictional, so areas are teaching units.