How much slower does a driver get as its load grows? The library answers with a table over input slew and output load, recoverable with a straight line between two points. This lesson fits that line through the real NAND2 row copied exactly from the library file, checks the fit in Python, then measures the heavier output load on a real mapped gate: the line prices the load's share, and the repair prices the rest.
The sized library describes each timing arc as a small table over input slew and output load. For the NAND2 rise arc at 0.01 ns input slew, the row reads 0.08 ns at 0.01 pF and 0.10 ns at 0.1 pF, copied exactly from the file; the same arc at the slower 0.1 ns slew reads 0.11 and 0.14 ns. Everything here is built from those four digits plus the constraint loads, 0.05 and 0.2 pF.
Input slew (ns) | Load 0.01 pF | Load 0.1 pF |
|---|---|---|
0.01 | 0.08 ns | 0.10 ns |
0.10 | 0.11 ns | 0.14 ns |
Fix the slew at 0.01 ns and read delay against load only. Two points make a line: the slope is the delay difference divided by the load difference, 0.02 ns over 0.09 pF, worked out in the arithmetic below. The model is the base delay plus the slope times the extra load past the first table point. It is exact at both table points by construction, and it is a fit, not the library: between the points the real cell interpolates exactly the same way at a fixed slew, past 0.1 pF the table ends and the line extrapolates, which the break-it section prices honestly.
hand arithmetic from the copied fast-slew row:
slope k = (0.10 - 0.08) / (0.1 - 0.01) = 0.02 / 0.09 = 0.2222 ns per pF
model: D(C) = 0.08 + k x (C - 0.01), ns and pF
typical output load: D(0.05) = 0.08 + k x 0.04 = 0.0889 ns (about 89 ps)
heavy output load: D(0.2) = 0.08 + k x 0.19 = 0.1222 ns (about 122 ps, extrapolated)The script below maps a single NAND2 twice at a loose target, changing only the constraint file: typical at 0.05 pF, then heavy at 0.2 pF. Both runs keep every logic cell at size 1, as asserted, but the mapper may add its own answer on top, and under the heavy load it does. The program after the script checks the linear model against the copied row digits.
read_verilog <<EOV
module one(input a, input b, output y);
assign y = ~(a & b);
endmodule
EOV
synth -top one -flatten
design -save generic
tee -o abc_typ.txt abc -liberty sized.lib -constr typical.constr -D 1000
tee -o stat_typ.txt stat -liberty sized.lib
select -assert-count 1 t:NAND2 t:NAND2_X2 t:NAND2_X4
select -assert-none t:*_X2 t:*_X4
design -load generic
tee -o abc_heavy.txt abc -liberty sized.lib -constr heavy.constr -D 1000
tee -o stat_heavy.txt stat -liberty sized.lib
select -assert-count 1 t:NAND2 t:NAND2_X2 t:NAND2_X4
select -assert-none t:*_X2 t:*_X4
log PASS: the same gate reports a longer delay under the heavier output loadimport sys
# Copied exactly from the NAND2 cell_rise row at 0.01 ns input slew in sized.lib:
# index_2 loads 0.01 and 0.1 pF give delays 0.08 and 0.10 ns.
C0, D0 = 0.01, 0.08
C1, D1 = 0.1, 0.10
K = (D1 - D0) / (C1 - C0)
def model(c):
return D0 + K * (c - C0)
# Every check below names a literal with four decimals, so any drift in the
# copied digits or the arithmetic fails loudly instead of comparing true.
checks = [
(round(K, 4), 0.2222),
(round(model(0.01), 4), 0.08),
(round(model(0.05), 4), 0.0889),
(round(model(0.1), 4), 0.1),
(round(model(0.2), 4), 0.1222),
]
for i in range(len(checks)):
got, want = checks[i]
if got != want:
print("FAIL: check " + str(i + 1) + " got " + str(got) + " want " + str(want))
sys.exit(1)
print("PASS: fast-slew slope reproduces the copied NAND2 row")No, on both counts. Delay is a base plus slope times load, so quadrupling the load adds only the slope term, about 33 ps for the extra 0.15 pF. And the heavy run is not even the same netlist: it carries an inserted buffer the typical run does not need.
Run | Cells | Area | Arrival reported |
|---|---|---|---|
typical load (0.05 pF) | 1 | 2 | 94 ps |
heavy load (0.2 pF) | 2 | 4 | 234 ps |
Read the table against the model, and read the heavy netlist first. ABC's load-driven buffer step (the buffer command in the abc -D script) put a size-1 BUF in front of the 0.2 pF output, so the NAND2 now drives only the buffer's input pin and its own stage delay falls instead of rising. The 33 ps the model prices for the extra 0.15 pF never lands on the NAND2 at all. What the arrival prices instead is the repair: two cells where the typical run maps to one, doubling the area. An unbuffered gate at the heavy load models at about 127 ps, the recorded typical arrival plus the slope term, against about 234 ps recorded buffered: the model prices the repair's share at about 107 ps. Buffering made this path slower, with no rule violated, since 0.2 pF sits well under the 1.0 pF pin limit. The step cannot be switched off through this runner (abc -script is rejected, and excluding BUF cells crashes ABC in this build), so the unbuffered number stays a model estimate, not a recording.
The chart plots the fitted line at four loads: the two copied points, the typical load between them, and the heavy load past the table edge as extrapolated.
The second program reruns the fit on the slow-slew row instead: 0.11 ns at 0.01 pF and 0.14 ns at 0.1 pF. The slope comes out half again as steep, so one line per slew is the honest unit, and the full table carries both axes for exactly this reason.
import sys
# Copied exactly from the NAND2 cell_rise row at 0.1 ns input slew in sized.lib:
# index_2 loads 0.01 and 0.1 pF give delays 0.11 and 0.14 ns.
C0, D0 = 0.01, 0.11
C1, D1 = 0.1, 0.14
KS = (D1 - D0) / (C1 - C0)
def model(c):
return D0 + KS * (c - C0)
checks = [
(round(KS, 4), 0.3333),
(round(model(0.05), 4), 0.1233),
(round(model(0.2), 4), 0.1733),
]
for i in range(len(checks)):
got, want = checks[i]
if got != want:
print("FAIL: check " + str(i + 1) + " got " + str(got) + " want " + str(want))
sys.exit(1)
print("PASS: slow-slew slope 0.3333 ns per pF checked against the copied row")The 0.02 ns rise over the 0.09 pF span, worked out in the arithmetic above. At 0.05 pF the model gives 0.0889 ns, about 89 ps, and the Python checks replay exactly these digits.
The buffer replaced the load instead of adding to it: the NAND2 now drives only the buffer's input pin, so its own stage delay falls rather than rising by the model's slope share. The arrival is slower anyway because the script's load-driven buffer step adds about 107 ps on the model here, with no rule violated: 0.2 pF is under the 1.0 pF limit, so this buffer is default buffering, not a rule fix.
Every number and picture on this page comes from one Yosys 0.69 run of the script shown plus two stdlib-only Python checks, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): ABC maps one NAND2 under typical.constr and heavy.constr, inserting a buffer in the heavy run, and the programs replay the NAND2 rise rows copied exactly from sized.lib at fast and slow input slews. The unbuffered heavy-load arrival quoted on the page is a labelled model estimate, not a run: switching the buffer step off is not possible through the runner. The libraries are fictional teaching files, so the fitting method, not the digits, is the point.