Delays bound how much time the outside world takes; drive and load bound how fast the edges move. An input port needs a model of what drives it, because a weak driver stretches the incoming transition and a strong one sharpens it. An output port needs the capacitance it must charge, because a heavier load slows every transition the tool computes. Without those two bounds the tool still maps the logic, but it times the edges against an assumption nobody stated.
This lesson runs one cone twice under two recorded loads and reads the difference back from the tool reports. The SDC that states the bounds is illustrative, as always here, but the constraint lines the run actually consumed are recorded, and the table below keeps those two columns apart.
The SDC pair names a library cell to drive the input and a capacitance for the output. The driving-cell line says the tool should treat input a as driven by a BUF from the teaching library, which fixes the input transition it assumes. The load line says output y carries a capacitance in library units, which fixes how hard the final stage works. Units are part of both lines, and the next section shows why the load unit is the one that bites.
# not-run: no SDC reader in the open tools
set_driving_cell -lib_cell BUF [get_ports a]
set_load 0.05 [get_ports y]The open flow cannot read that SDC, so the script hands ABC the same two facts through its constraint files: typical.constr for the light run, heavy.constr for the heavy one. Both files name the same BUF driver; they differ only in the load number. The ABC log echoes each file back as it reads it, and those echo lines are recorded, so the mapping from SDC idea to consumed constraint is proven rather than asserted.
SDC idea (illustrative) | Constraint line the run consumed (recorded) |
|---|---|
| ABC: Setting driving cell to be "BUF". |
| ABC: Setting output load to be 50.000000. |
The design is a six-input OR of ANDs feeding one output: three AND pairs ORed together, small enough to draw and load-sensitive at its final stage. The script synthesises it once, saves the generic gates, then maps the same gates twice against sized.lib at the same 300 ps delay target: once with the light load, once with the heavy one. Both runs write their ABC report, their stat histogram, and their netlist. The assertions prove no generic gate survives either mapping, that the light run uses no upsized cell, and that the heavy run contains at least one BUF_X4.
read_verilog <<EOV
module cone6(input a, b, c, d, e, f, output y);
assign y = (a & b) | (c & d) | (e & f);
endmodule
EOV
synth -top cone6 -flatten
design -save generic
tee -o abc_typical.txt abc -liberty sized.lib -constr typical.constr -D 300
tee -o stat_typical.txt stat -liberty sized.lib
write_json net_typical.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_
select -assert-none t:*_X2 t:*_X4
design -load generic
tee -o abc_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 300
tee -o stat_heavy.txt stat -liberty sized.lib
write_json net_heavy.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_
select -assert-min 1 t:BUF_X4
log PASS: the same target maps one cone two ways under two loadsThe delay target stays fixed, so the load is the only variable: whatever differs in the reports below is the price of that number.
A different netlist. The light run maps to 4 cells with area 9; the heavy run needs 5 cells with area 14.2, including its asserted BUF_X4 driving the load. The load does not just slow the report; it rebuilds the output stage.
Load (recorded) | Cells (recorded) | Area (recorded) | Delay reached (recorded) | Upsized cells (recorded) | Inserted buffers (recorded) |
|---|---|---|---|---|---|
50 fF | 4 | 9 | 208 ps | 0 | 0 |
200 fF | 5 | 14.2 | 290 ps | 0 | 1 |
Read the table as cause and effect. The light run reports 4 cells (NAND2 x3, NAND3 x1) at area 9 and 208 ps, comfortably inside the 300 ps target with no upsized cell and no inserted buffer. The heavy run reports 5 cells (BUF_X4 x1, NAND2 x3, NAND3 x1) at area 14.2 and 290 ps: the same logic plus one large buffer on the output, still inside the target because the buffer was bought to stay there. That buffer is the load made visible, and the split columns keep it apart from upsizing: an inserted buffer is a new cell, not a bigger one.
The teaching library prices capacitance in picofarads, so the SDC load means 0.05 pF. ABC reads its constraint number as femtofarads, and 0.05 pF is 50 fF, which is exactly what typical.constr carries. Two different units, one correct handoff, and the run proves it: the log line in the table above reads the 50 back. State the unit on every side of the handoff, because nothing in the number itself says which unit it is.
The trap runs in both directions, one per slot. A bare 50 typed into the SDC slot, which reads library picofarads, means 50 pF: 50000 fF, a thousand times the intended load, so the tool buffers an output that needs no buffering. The library number typed unchanged into the ABC slot, which reads femtofarads, means 0.05 fF: a thousand times too light, so the tool trusts edges the real load will collapse. A bare 50 where femtofarads are read is simply 50 fF, the correct value. The program below pins the correct conversion and both thousand-fold mistakes.
import sys
# Unit handoff for set_load. The SDC slot reads library picofarads; the ABC
# slot reads femtofarads. Every check converts through the slot's own unit,
# so a conversion in the wrong direction fails instead of cancelling out.
FF_PER_PF = 1000
INTENDED_PF = 0.05
intended_ff = INTENDED_PF * FF_PER_PF
if intended_ff != 50.0:
print("FAIL: 0.05 pF converts to " + str(intended_ff) + " fF, want 50.0")
sys.exit(1)
# Trap A: a bare 50 typed into the SDC slot, which reads picofarads.
SDC_ENTRY_A = 50.0
trap_a_ff = SDC_ENTRY_A * FF_PER_PF
if trap_a_ff != 50000.0:
print("FAIL: 50 pF converts to " + str(trap_a_ff) + " fF, want 50000.0")
sys.exit(1)
if trap_a_ff / intended_ff != 1000.0:
print("FAIL: SDC-side mistype is not 1000x heavy")
sys.exit(1)
# Trap B: the library number typed into the ABC slot, which reads femtofarads.
ABC_ENTRY_B = 0.05
trap_b_pf = ABC_ENTRY_B / FF_PER_PF
if trap_b_pf != 0.00005:
print("FAIL: 0.05 fF is " + str(trap_b_pf) + " pF, want 0.00005")
sys.exit(1)
if intended_ff / ABC_ENTRY_B != 1000.0:
print("FAIL: ABC-side mistype is not 1000x light")
sys.exit(1)
print("PASS: 0.05 pF is 50 fF and each one-sided mistype misses 1000x")Read the program as the rule. Convert at the boundary, every time, and check the log echo the way the table above does. A load without a unit is an unread number, and the thousand-fold gap is what unread numbers cost.
The heavy run in the script is the break-it: the same cone, the same target, the same BUF driver, with the load quadrupled. Nothing else changed, so the whole difference is the load. The area moves from its light row to its heavy row, the delay follows, and the netlist gains exactly one cell: the BUF_X4 the assertion demands. In this run the load left the logic cover alone and paid only at the output stage; two load points cannot prove that always happens, so read it as what this cone did, not as a law of loads.
50 and 200, read as femtofarads. The heavy run contains the asserted BUF_X4, because its output works four times as hard at the same delay target.
The intent is 0.05 pF, which is 50 fF. Typed into the SDC slot, 50 means 50 pF, a thousand times heavy: needless buffering and fiction delays. Typed into the ABC slot, 0.05 means 0.05 fF, a thousand times light: edges the real load will collapse. Only 50 in the femtofarad slot is correct, as the program proves.
Cell counts, areas, delays, histograms, netlist pictures, and constraint echo lines 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 one cone against the teaching library sized.lib at a fixed 300 ps target with typical.constr (BUF driver, 50 fF load) and heavy.constr (BUF driver, 200 fF load). The library is fictional, so absolute areas and delays are teaching units; the rebuild and the unit handoff are the point. The SDC listing is illustrative: the open tools have no SDC reader.