awk Part III · Module 11 · Synthesis and Coverage Report Analysis
Cell Counts
Chapter 11.1 ended with twenty-one cells that no category accounted for: combinational plus sequential plus macros came to 3073 against a reported 3094. This chapter finds them, and the answer explains why a synthesis report has two kinds of thing it calls a cell — a leaf cell that occupies silicon and a hierarchical instance that occupies none. It closes on the reason cell counts are quoted far more often than they should be: across this design the area of a cell varies nineteenfold, so ranking modules by how many cells they hold gives a different answer from ranking them by area.
Advanced17 min readawkPart IIISynthesisCell CountsHierarchy
Part III · Module 11 · Chapter 11.2 · Cell Counts
1. Why This Matters
A cell count is the cheapest number in a synthesis report to extract and the easiest to misread. It is quoted as a size, compared across runs as a proxy for area, and used to decide which module to look at first — and each of those uses has a failure mode this chapter measures.
The immediate problem is that the report uses the word cell for two different things and prints both with the same label shape, which is where Chapter 11.1's missing twenty-one come from.
2. The Count Lines
From area.rpt, published in full in Chapter 11.1 section 2:
awk '/^Number of/ { printf "%-34s %s\n", substr($0, 1, 33), $NF }' area.rptNumber of ports: 1842
Number of nets: 4127
Number of cells: 3094
Number of combinational cells: 2461
Number of sequential cells: 612
Number of macros/black boxes: 0
Number of buf/inv: 418
Number of references: 37Eight labelled counts. Three of them contain the word cells, one contains cell only as part of another word, and two of them are subsets of others. Sorting that out is the chapter.
3. /cell/ Returns an Area
for pat in '/cells/' '/cell/' '/^Number of cells:/' '/Number of/'; do
printf " %-24s " "$pat"
awk "$pat { n++; last=\$NF } END { printf \"%d line(s), last %s\n\", n+0, last }" area.rpt
done /cells/ 3 line(s), last 612
/cell/ 4 line(s), last 27793.500000
/^Number of cells:/ 1 line(s), last 3094
/Number of/ 8 line(s), last 37Three different wrong answers from three plausible patterns:
/cells/matches the threecellslines and keeps the last — 612, the sequential subtotal./cell/also matchesTotal cell area:and keeps 27793.500000, a floating-point area./Number of/matches everything and keeps 37, the reference count.
4. The Twenty-One Are Not Cells
awk '/^Number of cells:/ { t=$NF } /^Number of combinational cells:/ { c=$NF } /^Number of sequential cells:/ { s=$NF } /^Number of macros/ { m=$NF } END { printf "leaf cells %d + %d + %d = %d; Number of cells %d; difference %d\n", c, s, m, c+s+m, t, t-(c+s+m) }' area.rptleaf cells 2461 + 612 + 0 = 3073; Number of cells 3094; difference 21The hierarchical view of the same design accounts for them:
****************************************
Report : area -hierarchy
Design : alu_core
****************************************
Instance Module Cells Cell Area Total Area
--------------------------------------------------------------------
alu_core alu_core 204 1420.500 27793.500
u_regfile regfile_m 466 4980.000 4980.000
u_alu alu_m 178 1310.000 11350.000
u_add add_m 420 2840.000 2840.000
u_mul mul_m 642 5210.000 5210.000
u_shift shift_m 190 1130.000 1130.000
u_logic logic_m 140 860.000 860.000
u_ctrl ctrl_m 170 1240.000 2726.000
u_decode decode_m 118 740.000 740.000
u_fsm fsm_m 72 520.000 520.000
u_hazard hazard_m 37 226.000 226.000
u_io io_m 100 810.000 1680.000
u_pad_ring pad_ring_m 56 560.000 560.000
u_sync sync_m 42 310.000 310.000
u_dma dma_m 83 690.000 1340.000
u_desc desc_m 53 420.000 420.000
u_fifo fifo_m 9 90.000 90.000
u_arb arb_m 22 140.000 140.000
u_fpu fpu_m 27 1950.000 4114.000
u_norm norm_m 18 1240.000 1240.000
u_round round_m 8 924.000 924.000
u_debug debug_m 18 183.000 183.000
--------------------------------------------------------------------
TOTAL 3073 27793.500 27793.500awk 'NR <= 7 || /^-|^TOTAL/ { next } { rows++ } END { printf "hier.rpt lists %d module rows -> %d are instances inside a parent (the top is not)\n", rows, rows - 1 }' hier.rpthier.rpt lists 22 module rows -> 21 are instances inside a parent (the top is not)Twenty-one. The design instantiates twenty-one sub-modules, and Number of cells counts each of those instances alongside the leaf cells.
That is the distinction the rest of the chapter rests on:
- A leaf cell is a standard cell from the library — a flip-flop, an AND gate, a buffer. It occupies silicon and has an area.
- A hierarchical instance is a module boundary. It occupies no silicon of its own; its area is the sum of what is inside it.
So Number of cells: 3094 is not a count of physical objects. It is a count of things in the netlist at this level, and twenty-one of them are names rather than gates.
The two reports agree, which is worth checking rather than assuming:
awk 'NR == FNR { if (/^Total cell area:/) flat = $NF; next } $1 == "alu_core" { printf "hier top Total Area %.3f vs area.rpt Total cell area %.3f -> %s\n", $NF, flat, (flat - $NF < 1e-6 && $NF - flat < 1e-6 ? "MATCH" : "MISMATCH") }' area.rpt hier.rpthier top Total Area 27793.500 vs area.rpt Total cell area 27793.500 -> MATCHCompared with a tolerance, not ==, for the reason Chapter 11.1 section 8 measured.
5. Buf/Inv Is a Subset of the Counts Too
The same trap as Buf/Inv area, and here the arithmetic refutes it outright:
awk '/^Number of combinational cells:/ { c=$NF } /^Number of sequential cells:/ { s=$NF } /^Number of buf\/inv:/ { b=$NF } /^Number of cells:/ { t=$NF } END { printf "comb %d + seq %d = %d (+ %d instances = %d = Number of cells)\n", c, s, c+s, t-c-s, t; printf "buf/inv %d is %.1f%% of the %d combinational cells -- adding it gives %d, which EXCEEDS Number of cells %d\n", b, 100*b/c, c, c+s+b+(t-c-s), t }' area.rptcomb 2461 + seq 612 = 3073 (+ 21 instances = 3094 = Number of cells)
buf/inv 418 is 17.0% of the 2461 combinational cells -- adding it gives 3512, which EXCEEDS Number of cells 30943512 is larger than the reported total, so no interpretation in which buffers are a separate category can be right. The excess is a proof rather than an argument, and it is the general technique: when you suspect a category overlaps, add it in and see whether the total is exceeded.
Seventeen per cent of the combinational cells being buffers and inverters is also a number worth reading. It is the drive-strength and delay budget, and Chapter 11.1 section 7 watched it rise by 22.97% in a single run.
6. Number of references Is Not a Module Count
awk '/^Number of references:/ { print " Number of references: " $NF }' area.rpt
awk 'NR>7 && !/^-|^TOTAL/ { mod[$2]=1 } END { n=0; for (k in mod) n++; printf " distinct Module names in hier.rpt: %d\n", n }' hier.rpt Number of references: 37
distinct Module names in hier.rpt: 22Thirty-seven against twenty-two. They disagree, which is enough to establish that references is not counting modules.
What it counts is distinct things referenced — every module plus every library cell type used. A netlist built from twenty-two modules and fifteen different standard cells has thirty-seven references. The precise split is not in this report, so the useful statement is the measured one: the reference count exceeds the module count, and the difference is library cell types.
Where this matters is that references is the only count in the report that does not scale with design size. Doubling the design doubles cells and instances; it leaves the reference count roughly alone, because the same library cells are reused. A run-over-run comparison that includes it will show it flat and that is not a bug.
7. A Cell Count Is Not an Area Proxy
Area per cell, per module:
awk 'NR>7 && !/^-|^TOTAL/ && $(NF-2) > 0 { printf "%s\t%d\t%.3f\t%.2f\n", $1, $(NF-2), $(NF-1), $(NF-1)/$(NF-2) }' hier.rpt > apc.tsv
sort -t"$(printf '\t')" -k4,4rn -k1,1 apc.tsv | head -4 | awk -F'\t' '{ printf " %-12s %5d cells %9.3f area %7.2f area/cell\n", $1, $2, $3, $4 }'
sort -t"$(printf '\t')" -k4,4n -k1,1 apc.tsv | head -3 | awk -F'\t' '{ printf " %-12s %5d cells %9.3f area %7.2f area/cell\n", $1, $2, $3, $4 }' u_round 8 cells 924.000 area 115.50 area/cell
u_fpu 27 cells 1950.000 area 72.22 area/cell
u_norm 18 cells 1240.000 area 68.89 area/cell
u_regfile 466 cells 4980.000 area 10.69 area/cell
u_shift 190 cells 1130.000 area 5.95 area/cell
u_hazard 37 cells 226.000 area 6.11 area/cell
u_logic 140 cells 860.000 area 6.14 area/cellawk -F'\t' '{ if (NR==1 || $4+0 > hi) { hi=$4+0; hn=$1 }; if (NR==1 || $4+0 < lo) { lo=$4+0; ln=$1 } } END { printf " highest %s %.2f, lowest %s %.2f -> %.1fx spread\n", hn, hi, ln, lo, hi/lo }' apc.tsv highest u_round 115.50, lowest u_shift 5.95 -> 19.4x spreadA 19.4× spread. u_round averages 115.50 area per cell and u_shift averages 5.95 — the floating-point rounding logic is built from a handful of very large cells and the shifter from many small ones.
Which means the two rankings are different reports:
printf " rank by area: "; sort -t"$(printf '\t')" -k3,3rn -k1,1 apc.tsv | head -5 | awk -F'\t' '{ printf "[%s]", $1 } END { print "" }'
printf " rank by cells: "; sort -t"$(printf '\t')" -k2,2rn -k1,1 apc.tsv | head -5 | awk -F'\t' '{ printf "[%s]", $1 } END { print "" }' rank by area: [u_mul][u_regfile][u_add][u_fpu][alu_core]
rank by cells: [u_mul][u_regfile][u_add][alu_core][u_shift]They agree on the top three and diverge at rank four. u_fpu is the fourth-largest module by area with twenty-seven cells — it does not appear in the top five by count at all.
8. Common Mistakes
/cell/ for a cell count. It matches Total cell area: and returns a float — section 3.
/cells/ for the total. It keeps the sequential subtotal, 612.
Assuming Number of cells counts physical cells. Twenty-one of the 3094 are hierarchical instances — section 4.
Adding Number of buf/inv to the categories. The sum exceeds the reported total — section 5.
Reading Number of references as a module count. 37 against 22 modules — section 6.
Expecting the reference count to grow with the design. It tracks library variety, not size.
Using a cell count as an area proxy. Area per cell spans 19.4× here — section 7.
Comparing the two reports' totals with ==. Use a tolerance — Chapter 11.1 section 8.
9. Exercises
Exercise 1 — The cell count, anchored
awk '/^Number of cells:/ { print $NF }' area.rpt3094Exercise 2 — Leaf cells against instances
awk '/^Number of cells:/ { t=$NF } /^Number of combinational cells:/ { c=$NF } /^Number of sequential cells:/ { s=$NF } /^Number of macros/ { m=$NF } END { printf "leaf %d instances %d reported total %d\n", c+s+m, t-(c+s+m), t }' area.rptleaf 3073 instances 21 reported total 3094Exercise 3 — Debug a cell count that is not an integer
A script reports the design has 27793.500000 cells.
awk '/cell/ { n = $NF; m++ } END { printf "%d match(es), kept %s\n", m, n }' area.rpt
awk '/cell/ { printf " matched: [%s]\n", substr($0, 1, 40) }' area.rpt4 match(es), kept 27793.500000
matched: [Number of cells: ]
matched: [Number of combinational cells: ]
matched: [Number of sequential cells: ]
matched: [Total cell area: 27793.5]Four matches, the last of which is an area.
awk '/cell/ { n = $NF } END { printf "value %s -> %s\n", n, (n + 0 == int(n + 0) ? "plausible count" : "NOT A COUNT: a cell count cannot have a fractional part") }' area.rpt
awk '/^Number of cells:/ { n = $NF } END { printf "value %s -> %s\n", n, (n + 0 == int(n + 0) ? "plausible count" : "NOT A COUNT") }' area.rptvalue 27793.500000 -> NOT A COUNT: a cell count cannot have a fractional part
value 3094 -> plausible countThe type assertion from section 3 catches it without knowing anything about the report.
Exercise 4 — Debug a module list that misses the fourth-largest module
A review list ranks modules by cell count and a reviewer asks why u_fpu is absent.
awk 'NR>7 && !/^-|^TOTAL/ { printf "%s\t%d\t%.3f\n", $1, $(NF-2), $(NF-1) }' hier.rpt | sort -t"$(printf '\t')" -k2,2rn -k1,1 | head -5 | awk -F'\t' '{ printf " %d. %-12s %5d cells %9.3f area\n", NR, $1, $2, $3 }' 1. u_mul 642 cells 5210.000 area
2. u_regfile 466 cells 4980.000 area
3. u_add 420 cells 2840.000 area
4. alu_core 204 cells 1420.500 area
5. u_shift 190 cells 1130.000 areau_fpu has 27 cells, so a count-ranked list puts it far down. By area:
awk 'NR>7 && !/^-|^TOTAL/ { printf "%s\t%d\t%.3f\n", $1, $(NF-2), $(NF-1) }' hier.rpt | sort -t"$(printf '\t')" -k3,3rn -k1,1 | head -5 | awk -F'\t' '{ printf " %d. %-12s %5d cells %9.3f area\n", NR, $1, $2, $3 }' 1. u_mul 642 cells 5210.000 area
2. u_regfile 466 cells 4980.000 area
3. u_add 420 cells 2840.000 area
4. u_fpu 27 cells 1950.000 area
5. alu_core 204 cells 1420.500 areaFourth, at 1950.000 — 72 area per cell against the design's low of 5.95.
Exercise 5 — Engineering application
The count summary a synthesis review wants: every count with its label, the categories reconciled, the subset lines marked as subsets, and the type of each value asserted.
# counts.awk — reconcile the cell counts in a synthesis area report.
#
# Usage: awk -f counts.awk area.rpt
# Exit: 0 reconciled
# 1 a count is not an integer (wrong label matched)
# 2 the categories do not reconcile
/^Number of ports:/ { ports = $NF }
/^Number of nets:/ { nets = $NF }
/^Number of cells:/ { cells = $NF }
/^Number of combinational cells:/ { comb = $NF }
/^Number of sequential cells:/ { seq = $NF }
/^Number of macros/ { macro = $NF }
/^Number of buf\/inv:/ { bufinv = $NF }
/^Number of references:/ { refs = $NF }
END {
split("ports nets cells comb seq macro bufinv refs", nm, " ")
v[1] = ports; v[2] = nets; v[3] = cells; v[4] = comb
v[5] = seq; v[6] = macro; v[7] = bufinv; v[8] = refs
for (i = 1; i <= 8; i++) {
if (v[i] == "") { printf "ERROR: %s not found\n", nm[i] > "/dev/stderr"; bad = 1; continue }
if (v[i] + 0 != int(v[i] + 0)) { printf "ERROR: %s = %s is not an integer -- wrong label matched\n", nm[i], v[i] > "/dev/stderr"; bad = 1 }
}
if (bad) exit 1
leaf = comb + seq + macro
inst = cells - leaf
printf "%-22s %8d\n", "ports", ports
printf "%-22s %8d\n", "nets", nets
printf "%-22s %8d leaf cells (comb + seq + macro)\n", "leaf cells", leaf
printf "%-22s %8d hierarchical instances (no area of their own)\n", "instances", inst
printf "%-22s %8d = leaf + instances\n", "cells", cells
printf "%-22s %8d SUBSET of the %d combinational cells (%.1f%%)\n", "buf/inv", bufinv, comb, 100 * bufinv / comb
printf "%-22s %8d distinct modules AND library cells, not an instance count\n", "references", refs
if (leaf + inst != cells) { printf "ERROR: leaf %d + instances %d != cells %d\n", leaf, inst, cells > "/dev/stderr"; exit 2 }
if (inst < 0) { printf "ERROR: negative instance count -- a category was double counted\n" > "/dev/stderr"; exit 2 }
printf "# reconciled: %d leaf + %d instances = %d\n", leaf, inst, cells
}awk -f counts.awk area.rpt
echo "status: $?"ports 1842
nets 4127
leaf cells 3073 leaf cells (comb + seq + macro)
instances 21 hierarchical instances (no area of their own)
cells 3094 = leaf + instances
buf/inv 418 SUBSET of the 2461 combinational cells (17.0%)
references 37 distinct modules AND library cells, not an instance count
# reconciled: 3073 leaf + 21 instances = 3094
status: 0Every count labelled with what it is, the two subset lines flagged as subsets, and the reconciliation stated rather than implied.
The inst < 0 guard is the one worth noting, because a negative instance count is impossible and so can only mean the categories overlap. Folding buf/inv into the leaf count is exactly that mistake:
awk '/^Number of cells:/ { t=$NF } /^Number of combinational cells:/ { c=$NF } /^Number of sequential cells:/ { s=$NF } /^Number of buf\/inv:/ { b=$NF } END { leaf = c + s + b; printf "with buf/inv folded in: leaf %d, instances %d -> %s\n", leaf, t - leaf, (t - leaf < 0 ? "IMPOSSIBLE, so buf/inv is a subset" : "plausible") }' area.rptwith buf/inv folded in: leaf 3491, instances -397 -> IMPOSSIBLE, so buf/inv is a subset−397 instances. The guard fires on the first input that should fire it, which is what makes it worth having rather than decorative.
Exercise 6 — Challenge: prove the guard can fire
A guard that has never been seen to fire is not known to work. Exercise 5's inst < 0 branch has not fired on any correct report, by construction. Build the input that fires it, and show that no gentler check would have.
The mistake to simulate is folding the buf/inv subset into the leaf count:
awk '/^Number of combinational cells:/ { c=$NF } /^Number of sequential cells:/ { s=$NF } /^Number of buf\/inv:/ { b=$NF } END { printf "correct leaf: comb %d + seq %d = %d\nfolded leaf: comb %d + seq %d + buf %d = %d\n", c, s, c+s, c, s, b, c+s+b }' area.rptcorrect leaf: comb 2461 + seq 612 = 3073
folded leaf: comb 2461 + seq 612 + buf 418 = 3491awk '/^Number of cells:/ { t=$NF } /^Number of combinational cells:/ { c=$NF } /^Number of sequential cells:/ { s=$NF } /^Number of buf\/inv:/ { b=$NF } END { bad_leaf = c + s + b; printf "folded leaf %d, implied instances %d -> %s\n", bad_leaf, t - bad_leaf, (t - bad_leaf < 0 ? "IMPOSSIBLE: a category was double counted" : "plausible") }' area.rptfolded leaf 3491, implied instances -397 -> IMPOSSIBLE: a category was double countedThe guard fires. The folded leaf count of 3491 exceeds the reported 3094, so the implied instance count is −397, and a design cannot instantiate a negative number of modules.
Now the gentler checks, on the same wrong figure:
awk '/^Number of combinational cells:/ { c=$NF } /^Number of sequential cells:/ { s=$NF } /^Number of buf\/inv:/ { b=$NF } END { bad = c + s + b; printf "is it an integer? %s\nis it positive? %s\nis it within 20%% of 3073? %s\n", (bad == int(bad) ? "yes" : "no"), (bad > 0 ? "yes" : "no"), (bad < 3073 * 1.2 ? "yes" : "no") }' area.rptis it an integer? yes
is it positive? yes
is it within 20% of 3073? yesAll three pass. 3491 is a whole number, it is positive, and it is 13.6% above the true leaf count — well inside any tolerance a reviewer would set on a figure they did not already know.
So the only check that works is the one that compares the derived figure against a quantity the report states independently. 3491 is indistinguishable from a plausible leaf count in isolation; it is impossible only in the presence of Number of cells: 3094. That is the same conclusion as Chapter 11.1 section 10 and Chapter 10.7 Exercise 6, arrived at a fourth time: the report's own internal arithmetic is the only reference available, so every derived number should be checked against it rather than against expectations about its size.
10. Summary
/cell/matchesTotal cell area:and returns a float;/cells/keeps the sequential subtotal — section 3.- Assert the type. A cell count with a fractional part is the one hazard here a check on the output can catch.
- Twenty-one of the 3094 cells are hierarchical instances, which occupy no silicon — section 4.
- A leaf cell has an area; an instance's area is the sum of its contents.
buf/invis a subset of the combinational cells. Folding it in implies −397 instances — impossible, which is the proof.Number of referencesis 37 against 22 modules. It counts library cell types too, and does not grow with design size.- Area per cell spans 19.4× here, so ranking by count and by area diverge at rank four and
u_fpudisappears from the count list — section 7. - Across runs the count rose 6.72% against 2.46% of area — the pair says buffers, either alone misstates it.
- Cross-check the flat and hierarchical reports with a tolerance, not
==. - A guard that has never fired is not known to work. Build the input that fires it.
Chapter 11.3 — Hierarchy Statistics builds the per-module table from hier.rpt, and starts from the mistake the report invites: its two area columns mean different things, and summing the wrong one gives two and a half times the real total.
Where this fits
Part of the awk curriculum.
