Every mapping result is a shopping trip through a library: the mapper matches generic gates against listed cells and keeps the cheapest cover. This lesson reads one real entry, the NAND2 cell of course.lib, field by field, then maps one NAND function twice: with the whole library, and with NAND2 banned.
Below is the NAND2 entry as it stands in course.lib, with line breaks added for reading. Area first, then the input pins, then the output pin with its function, then one timing group per input arc carrying rise and fall delay plus output slew tables.
cell(NAND2) { <= the entry the mapper trades
area : 2; <= cost of one use: 2 library units
pin(A) { direction : input; capacitance : 0.01; } <= input pin A loads its driver
pin(B) { direction : input; capacitance : 0.01; } <= input pin B loads its driver
pin(Y) {
direction : output;
max_capacitance : 1.0; <= driver limit: 1.0 pF past this pin
function : "!(A*B)"; <= what the cell computes
timing() { <= arc from input A to output Y
related_pin : "A";
timing_sense : negative_unate; <= rising input falls the output
cell_rise(dt) {
index_1("0.01,0.1"); <= rows: input slew
index_2("0.01,0.1"); <= columns: output load
values("0.08,0.1","0.11,0.14"); <= delay table, exact from the file
}
cell_fall(dt) { ... same shape ... }
rise_transition(dt) { ... output slew table ... }
fall_transition(dt) { ... output slew table ... }
}
timing() { ... same tables for input B ... }
}
}Each use of a cell costs its listed area: the mapper adds cell areas to score covers, so a cover that uses more cells usually costs more even when each cell is cheap. The exact prices live in the table below.
Capacitance 0.01 is the load each input pin adds to its driver, in the file units settled below. Fanout load is a sum: one net driving three NAND2 input pins presents three times 0.01 before any wire load, and larger loads index slower table points.
Fragment (exact from course.lib) | What the mapper does with it |
|---|---|
| Adds 2 library units per use when scoring covers. |
| Adds 0.01 pF of load to the driver for each driven pin. |
| Matches cones computing NOT (A AND B) to this cell. |
| Knows a rising input falls the output on this arc. |
| Looks up rise delay by input slew row and output load column. |
| Flags any net past 1.0 pF on this pin as a violation to repair. |
The file header declares its own units: capacitive_load_unit(1,pf) and time_unit 1ns. The 0.01 on each input pin is 0.01 pF, and the 0.08 table corner is 0.08 ns, which is 80 ps.
Later lessons quote loads in femtofarads: 0.01 pF is 10 fF, exactly one teaching-library input pin. Convert before comparing: 0.11 ns here is 110 ps wherever ABC reports it. Typical.constr writes the output load as 50, which ABC reads as 50 fF, or 0.05 pF: the same load as five 10 fF input pins.
course.lib units (from the file header, not a guess)
capacitance 0.01 = 0.01 pF = 0.01 x 1000 fF = 10 fF
delay 0.08 = 0.08 ns = 0.08 x 1000 ps = 80 ps
typical.constr load 50 = 50 fF = 0.05 pF = 5 input-pin loads of 10 fFThe script reads course.lib, writes the loaded cells out with write_json, then maps y = NOT (a AND b) three times from one generic netlist. All three mappings run under typical.constr (BUF driver, 50 fF on the output, so ABC reports area and delay) with a loose 1000 ps target that changes nothing in a one-size library. The bans use abc -dont_use, the command-line stand-in for marking a cell dont_use : true in Liberty.
read_liberty -lib course.lib
write_json lib.json
read_verilog <<EOV
module top(input a, b, output y);
assign y = ~(a & b);
endmodule
EOV
hierarchy -check -top top
proc
opt
techmap
design -save generic
tee -o abc_full.txt abc -liberty course.lib -constr typical.constr -D 1000
tee -o stat_full.txt stat -liberty course.lib
write_json net_full.json
select -assert-min 1 t:NAND2
design -load generic
tee -o abc_banned.txt abc -liberty course.lib -constr typical.constr -D 1000 -dont_use NAND2
tee -o stat_banned.txt stat -liberty course.lib
select -assert-none t:NAND2
select -assert-min 1 t:INV
design -load generic
tee -o abc_banned2.txt abc -liberty course.lib -constr typical.constr -D 1000 -dont_use NAND2 -dont_use AND2
tee -o stat_banned2.txt stat -liberty course.lib
select -assert-none t:NAND2 t:AND2
check
log PASS: each ban removes its cells and every rebuild maps cleanlyNAND2 disappears entirely. The function still maps from an AND2 plus one INV: more cells and more area for identical logic in this ABC script. A ban can never make the cover cheaper: it costs more here, while the recorded XOR2 ban in mapping-in-yosys holds area 59 across 20 cells against 18 free.
Cover | Cells | Area | Delay |
|---|---|---|---|
Full library | 1 | 2 | 93.96 ps |
NAND2 banned | 2 | 4 | 188.07 ps |
NAND2 and AND2 banned | 3 | 5 | 198.07 ps |
Read the three rows as the price of one entry: the same function from one NAND2, from an AND2 plus one INV once NAND2 is banned, or from an OR2 plus 2 INV once both are banned. Same RTL, same tool, different menu. One apparent mismatch to settle now: the full-cover path delay of 93.96 ps sits above the 80 ps table corner, because the two answer different questions. The corner is one bare cell at the smallest slew and load; the ABC number covers the whole path under typical.constr, driver stage and 50 fF output load included.
Full cover cells (recorded stat) | Count | Area |
|---|---|---|
NAND2 | 1 | 2 |
Banned cover cells (recorded stat) | Count | Area |
|---|---|---|
AND2 | 1 | 3 |
INV | 1 | 1 |
The script writes the loaded library out before any RTL is read: lib.json holds every cell as a module, all 15 of them below. When a mapping surprises you, this list is the first place to look, because the mapper cannot use what is not on it.
Library cells (recorded lib.json) | ||
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The third run is the break-it: NAND2 and AND2 banned together, so both covers in the table above are illegal. The recorded fallback is one OR2 plus 2 INV: y = NOT (a AND b) becomes (NOT a) OR (NOT b), an OR of two inversions, for 3 cells at area 5 against area 4 with one ban. Real libraries ban cells the same way: legacy cells, cells uncharacterised at a corner, unroutable footprints. Every ban shrinks the choice set, so recheck area and delay after each one.
Stacked-ban cover (recorded stat) | Count | Area |
|---|---|---|
INV | 2 | 2 |
OR2 | 1 | 3 |
It pays for rebuilding NOT (A AND B) without NAND2: an AND2 plus one INV instead of one NAND2 cell. The function is unchanged; only the cover moved, because the menu shrank by one entry.
Three times 0.01 pF, which is 0.03 pF, before any wire load. At a thousand fF per pF that is 30 fF, or three input pins of 10 fF each.
Every number and picture on this page comes from one Yosys 0.69 run of the script shown, recorded by the course checker (scripts/tools/run-rtl-testbenches.mjs with COURSE=synth): y = NOT (a AND b) maps against course.lib free, with abc -dont_use NAND2, and with NAND2 plus AND2 banned, with stat -liberty areas and a write_json cell listing (lib.json). The annotated NAND2 entry is copied exactly from scripts/tools/synth-libs/course.lib.