A larger gate drives its load faster and occupies more area: that is the whole trade behind sizing. The first script maps an arithmetic cone of AND-OR and XOR stages twice against sized.lib, once with a very loose target and once at -D 400. The loose run is the baseline: 6 cells at area 17 and 422 ps, not one of them upsized. Against -D 400 the same function maps to 6 cells at area 25 and 344 ps, and the histogram names the price: 1 XNOR2_X4 cell where a plain cell stood. Delay falls by 78 ps for 8 area units, spent gate by gate on the slowest stages.
read_verilog <<EOV
module arithcone(input [7:0] x, output y);
assign y = ((x[0] & x[1]) | (x[2] & x[3])) ^ ((x[4] & x[5]) | (x[6] & x[7]));
endmodule
EOV
synth -top arithcone -flatten
design -save arith
tee -o arith_loose.txt abc -liberty sized.lib -constr typical.constr -D 100000
tee -o arith_loose_stat.txt stat -liberty sized.lib
write_json arith_net.json
select -assert-none t:*_X2 t:*_X4
design -load arith
tee -o arith_tight.txt abc -liberty sized.lib -constr typical.constr -D 400
tee -o arith_tight_stat.txt stat -liberty sized.lib
write_json arith_tight_net.json
select -assert-min 1 t:*_X2 t:*_X4 %u
design -reset
read_verilog <<EOV
module ctrlcone(input [5:0] x, output y);
assign y = (x[0] & x[1] & x[2]) | (x[3] & x[4]) | x[5];
endmodule
EOV
synth -top ctrlcone -flatten
design -save ctrl
tee -o ctrl_loose.txt abc -liberty sized.lib -constr typical.constr -D 100000
tee -o ctrl_loose_stat.txt stat -liberty sized.lib
select -assert-none t:*_X2 t:*_X4
design -load ctrl
tee -o ctrl_tight.txt abc -liberty sized.lib -constr typical.constr -D 400
tee -o ctrl_tight_stat.txt stat -liberty sized.lib
select -assert-none t:*_X2 t:*_X4
design -load ctrl
tee -o ctrl_vtight.txt abc -liberty sized.lib -constr typical.constr -D 100
tee -o ctrl_vtight_stat.txt stat -liberty sized.lib
select -assert-min 1 t:*_X2 t:*_X4 %u
design -reset
read_verilog <<EOV
module both(input [7:0] x, input [5:0] c, output y_arith, output y_ctrl);
wire aa = ((x[0] & x[1]) | (x[2] & x[3])) ^ ((x[4] & x[5]) | (x[6] & x[7]));
wire cc = (c[0] & c[1] & c[2]) | (c[3] & c[4]) | c[5];
assign y_arith = aa;
assign y_ctrl = cc;
endmodule
EOV
synth -top both -flatten
tee -o both_tight.txt abc -liberty sized.lib -constr typical.constr -D 400
tee -o both_tight_stat.txt stat -liberty sized.lib
write_json both_net.json
select -assert-none t:$and t:$or t:$xor t:$xnor t:$mux t:$not
select -assert-none t:$_AND_ t:$_OR_ t:$_XOR_ t:$_XNOR_ t:$_MUX_ t:$_NOT_ t:$_NAND_ t:$_NOR_
check
select -assert-min 1 t:*_X2 t:*_X4 %u
log PASS: one target sizes only the violating coneThe select lines assert this: no upsized cell in the loose cone, at least one in the tight cone and in the combined design. In a commercial flow this loop runs inside the tool, guided by slack: upsize the slowest stage of the worst path, re-rank, repeat until the slack closes or the library runs out of sizes. Yosys and ABC run the same loop behind a single -D target shared by every output, which is why the lesson's second half matters: one number cannot say which path deserves the next upsize.
The same script maps a second, smaller cone: three AND-OR stages behind one output. Its recorded reports read identically at both targets: 3 cells at area 8 and 208 ps, loose or tight, with no upsized cell either way. The cone already arrives in 208 ps against a 400 ps target, so there is no violation to spend on. Sizing is violation-driven: a path that meets its target draws no effort no matter how tight the rest of the design is.
Cone (recorded ABC report) | Cells | Area | Delay reached | Upsized cells |
|---|---|---|---|---|
Arithmetic, loose target | 6 | 17 | 422 ps | 0 |
Arithmetic, -D 400 | 6 | 25 | 344 ps | 1 |
Control, loose target | 3 | 8 | 208 ps | 0 |
Control, -D 400 | 3 | 8 | 208 ps | 0 |
Only the arithmetic cone changes: 1 cell in its cone grows while the control cone keeps all plain cells. The tool sizes the violator and leaves the path that already meets its target alone, even though both share one constraint.
The last design in the script joins both cones behind two outputs of one top module and maps them together at -D 400. Splitting the recorded netlist by output cone confirms the prediction: 1 upsized cell in the arithmetic cone, 0 upsized cells in the control cone. The figure shows it as an effort map: orange only where the violation was.
Violation-driven effort has a blind spot: when two paths both fail, the tool must choose, and one number cannot express that the core datapath matters more than a slow peripheral bus. Commercial flows answer with path groups: named sets of paths with a shared target and a weight, so the cost function the tool minimises prices each group's violation by its weight. The SDC below is shown for reading only: the open tools in this course have no SDC reader, and Yosys and ABC have no path groups at all. Here every output shares one -D target and one load; there is no weight to set and no group to join. What follows is the idea those commands express, demonstrated with a model, not a measurement.
# not-run: no SDC reader in the open tools (commercial syntax, shown for reading only)
group_path -name core -weight 10 -to [get_cells data_reg*]
group_path -name slow -weight 1 -to [get_ports status_*]
# cost minimised: 10 * violation(core) + 1 * violation(slow)The program below runs that cost function on model numbers: two failing groups, a core group violating by 80 ps at weight 10 against a slow group violating by 40 ps at weight 1, and three effort units that erase 30 ps each. Each unit goes to the group with the largest weight times remaining violation, and the checks pin the order and the leftover: three units to core before slow sees any, because 10 times 80 outranks 1 times 40 from the first step. Labels matter here: every figure in this section is a model, while every area and delay in the tables above is a recording.
import sys
# MODEL of group-weighted cost (illustrative numbers, not tool output):
# two failing groups, weights 10 and 1, three effort units of 30 ps each.
start = {"core": 80, "slow": 40}
viol = dict(start)
weight = {"core": 10, "slow": 1}
STEP = 30
order = []
for _ in range(3):
pick = max(viol, key=lambda g: weight[g] * viol[g])
order.append(pick)
viol[pick] = max(0, viol[pick] - STEP)
if order != ["core", "core", "core"]:
print("FAIL: effort order " + str(order) + " want all core")
sys.exit(1)
if viol != {"core": 0, "slow": 40}:
print("FAIL: leftover " + str(viol) + " want core 0 slow 40")
sys.exit(1)
cost = lambda v: sum(weight[g] * v[g] for g in v)
cost_before = cost(start)
cost_after = cost(viol)
if not (cost_after < cost_before):
print("FAIL: weighted cost did not fall from " + str(cost_before))
sys.exit(1)
print("PASS: weights send all three effort units to core, cost " + str(cost_before) + " to " + str(cost_after))Read the model's lesson back onto the recordings: the arithmetic cone drew effort because it violated, the control cone drew none because it met. Weights would only change the order if both had violated. The starvation warning comes free with the model: a weight of 1 against a weight of 10 means the slow group waits several passes, which is correct when the project says so and a bug when nobody set out to say it.
The script's final single-cone run constrains the control cone alone at -D 100, far tighter than its 208 ps natural arrival. Now the easy cone is the violator, and the recorded result flips: 3 cells at area 14.8, reaching 147 ps against -D 100, still a miss at 47 ps over, but upsized where the -D 400 runs stayed plain. Effort follows the violation, not the cone's reputation: whichever path misses draws the next upsize. That is the mechanism weights modulate: a single target already implements the greedy half, and weights add the priority half for passes where everything fails at once.
It already arrives in 208 ps against a 400 ps target, so its slack is positive and there is no violation to spend on. Sizing follows violations, and this cone has none.
That the -D 400 result was never about the cone being unsizable: the same cells upsize readily once the cone actually violates. Effort follows the violation, and weights only reorder violations that arrive together.
Areas, delays, cell counts and netlist pictures on this page come from one Yosys 0.70 run of the script shown, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): ABC maps an arithmetic cone and a control cone against sized.lib loose and tight, plus both cones together under one target. Cone splits are hand-derived from the recorded netlist files. The group-weight program is a labelled model with illustrative numbers, and the group_path listing is commercial syntax shown for reading only. The library is fictional, so absolute figures are teaching units.