Skip to content
VLSI Mentor

awk Part III · Module 11 · Synthesis and Coverage Report Analysis

Hierarchy Statistics

A hierarchical area report prints two area columns per module and they mean different things: one is the silicon that module's own cells occupy, the other is that plus everything nested inside it. Adding up the second gives two and a half times the design's real area, and this chapter shows that the factor is not arbitrary — it decomposes exactly, module by module, from the nesting depth. It also covers the two ways the nesting itself gets lost: awk's default field splitting discards the indentation that encodes it, and the obvious fix returns zero for every module on a report indented with tabs.

Advanced18 min readawkPart IIISynthesisHierarchyDouble Counting

Part III · Module 11 · Chapter 11.3 · Hierarchy Statistics

1. Why This Matters

The per-module table is what turns an area number into a decision. 27793.500 says the block is too big; a table saying u_mul is 5210.000 of it says where to look.

The report that provides it is also the first one in this module whose structure carries information outside the field values. The nesting is in the indentation, which awk's default field splitting throws away, and the area is in two columns whose difference is the entire subject of the chapter.

2. Two Area Columns

hier.rpt is listed in full in Chapter 11.2 section 4. Its header names the columns:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed -n '6,9p' hier.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
  • Cell Area is the area of the leaf cells this module owns directly.
  • Total Area is that plus everything in every module nested inside it.

On alu_core the two differ enormously — 1420.500 against 27793.500 — because the top level owns almost nothing and contains everything. On u_regfile they are equal, because it has no children.

Summing each column:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { own += $(NF-1); tot += $NF } END { printf "sum of Cell Area  (own)        = %12.3f\nsum of Total Area (cumulative) = %12.3f   <- %.2fx\n", own, tot, tot/own }' hier.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sum of Cell Area  (own)        =    27793.500
sum of Total Area (cumulative) =    69376.500   <- 2.50x

The own-area column sums to the design's real total. The cumulative column sums to two and a half times it.

$(NF-1) and $NF rather than $4 and $5, because the instance column is indented and the module names vary in length — Chapter 7.1's point about counting fields from the right when the left is ragged.

3. The Factor Is Not Arbitrary

Each module's own area appears in its own Total Area, and again in the Total Area of every ancestor. So a module at depth d is counted d + 1 times:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2; a[d] += $(NF-1) }
 END { for (i = 0; i <= 2; i++) { m = i + 1; sub_ = a[i] * m; s += sub_; printf "  depth %d own %10.3f counted %d time(s) -> %11.3f\n", i, a[i], m, sub_ }
   printf "  predicted sum of Total Area = %.3f\n", s }' hier.rpt
awk 'NR>7 && !/^-|^TOTAL/ { tot += $NF } END { printf "  measured  sum of Total Area = %.3f\n", tot }' hier.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  depth 0 own   1420.500 counted 1 time(s) ->    1420.500
  depth 1 own  11163.000 counted 2 time(s) ->   22326.000
  depth 2 own  15210.000 counted 3 time(s) ->   45630.000
  predicted sum of Total Area = 69376.500
  measured  sum of Total Area = 69376.500

Predicted equals measured to the last digit.

That makes the 2.50× a property of this design's shape rather than a rule of thumb. A flat design would give 1.00×; a deeply nested one gives more. The factor is the average nesting depth plus one, weighted by area — which means you cannot correct for it with a constant, and a "divide by about two" heuristic is wrong for every design but this one.

4. The Report's Own Arithmetic

The relation that makes the cumulative column meaningful, checked on a parent with three children:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2; name = $1
  if (name == "u_ctrl") { pown = $(NF-1); ptot = $NF; pd = d; inp = 1; next }
  if (inp && d == pd + 1) { ksum += $NF; printf "  child %-12s Total Area %9.3f\n", name, $NF; next }
  if (inp && d <= pd) inp = 0 }
  END { printf "  u_ctrl own %9.3f + children %9.3f = %9.3f   reported Total Area %9.3f -> %s\n", pown, ksum, pown+ksum, ptot, (ptot-(pown+ksum) < 1e-6 && (pown+ksum)-ptot < 1e-6 ? "MATCH" : "MISMATCH") }' hier.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  child u_decode     Total Area   740.000
  child u_fsm        Total Area   520.000
  child u_hazard     Total Area   226.000
  u_ctrl own  1240.000 + children  1486.000 =  2726.000   reported Total Area  2726.000 -> MATCH

And on a parent with four:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2
  if ($1 == "u_alu") { pown = $(NF-1); ptot = $NF; pd = d; inp = 1; next }
  if (inp && d == pd + 1) { ksum += $NF; n++; next }
  if (inp && d <= pd) inp = 0 }
  END { printf "  u_alu own %.3f + %d children %.3f = %.3f   reported %.3f -> %s\n", pown, n, ksum, pown+ksum, ptot, (ptot-(pown+ksum) < 1e-6 && (pown+ksum)-ptot < 1e-6 ? "MATCH" : "MISMATCH") }' hier.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu own 1310.000 + 4 children 10040.000 = 11350.000   reported 11350.000 -> MATCH

The inp flag with d == pd + 1 for children and d <= pd to stop is the state-carrying pattern from Chapter 10.2 applied to a tree: a sibling or an uncle ends the parent's child list, and only a direct child counts.

Both comparisons use a tolerance rather than ==, for the reason Chapter 11.1 section 8 measured.

u_ctrl owns 1240.000 of cell area directly. Its three children u_decode, u_fsm and u_hazard contribute 740.000, 520.000 and 226.000. Those three plus the parent's own area give u_ctrl's Total Area of 2726.000.u_ctrl ownCell Area 1240.000 — itsown leaf cellsu_decodeTotal Area 740.000u_fsmTotal Area 520.000u_hazardTotal Area 226.000u_ctrl totalTotal Area 2726.000 = 1240+ 1486containssums to12
Figure 1 — The two area columns on one parent. u_ctrl owns 1240.000 directly; its three children contribute their own Total Area; the sum is u_ctrl's Total Area. Adding the parent's Total Area to the children's rows — which is what summing the cumulative column does — counts each child twice.

5. The Indentation Carries the Depth, and $1 Loses It

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { printf "[%s]", $1 } END { print "" }' hier.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[alu_core][u_regfile][u_alu][u_add][u_mul][u_shift][u_logic][u_ctrl][u_decode][u_fsm][u_hazard][u_io][u_pad_ring][u_sync][u_dma][u_desc][u_fifo][u_arb][u_fpu][u_norm][u_round][u_debug]

Twenty-two names and no structure at all. awk's default field splitting treats runs of blanks as one separator and ignores leading blanks, so the two- and four-space indents vanish with them.

The depth is recoverable from the raw record, which the splitting does not touch:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2; printf "[%d:%s]", d, $1 } END { print "" }' hier.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[0:alu_core][1:u_regfile][1:u_alu][2:u_add][2:u_mul][2:u_shift][2:u_logic][1:u_ctrl][2:u_decode][2:u_fsm][2:u_hazard][1:u_io][2:u_pad_ring][2:u_sync][1:u_dma][2:u_desc][2:u_fifo][2:u_arb][1:u_fpu][2:u_norm][2:u_round][1:u_debug]

match($0, /[^ ]/) returns the 1-based position of the first non-blank, so - 1 is the indent width and / 2 converts it to levels. $0 is the untouched record — Chapter 3.2's point that $0 and the field list are two views of the same line and only one of them has been normalised.

The per-depth rollup that section 3 relied on:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2; a[d] += $(NF-1); c[d]++ } END { for (i = 0; i <= 2; i++) printf "  depth %d: %2d module(s), own area %10.3f (%.1f%%)\n", i, c[i], a[i], 100*a[i]/27793.5 }' hier.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  depth 0:  1 module(s), own area   1420.500 (5.1%)
  depth 1:  7 module(s), own area  11163.000 (40.2%)
  depth 2: 14 module(s), own area  15210.000 (54.7%)

Three depths, 5.1% / 40.2% / 54.7%, adding to 100% because own areas partition the design.

6. Tabs Return Zero for Everything

The depth rule counts spaces. On a report indented with tabs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed 's/^    /\t\t/; s/^  /\t/' hier.rpt > hier_tab.rpt
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2; printf "%d", d } END { print "" }' hier_tab.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0000000000000000000000

Every module reads as depth 0. A tab is not a space, so /[^ ]/ matches it and match returns 1 for every line.

Expanding the tabs first restores the real depths:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { s = $0; sub(/[^ \t].*$/, "", s); gsub(/\t/, "  ", s); d = length(s) / 2; printf "%d", d } END { print "" }' hier_tab.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0112222122212212221221

Which is the sequence section 5 measured on the space-indented original.

7. A Bare Instance Name Is Not a Key

Two modules can instantiate submodules with the same name:

Azvya Education Pvt. Ltd.VLSI Mentor
dup.rpt — synthetic teaching data. Two distinct u_fifo instances, one under u_tx and one under u_rx.
Instance             Module         Cells  Cell Area  Total Area
--------------------------------------------------------------------
top                  top_m             12    100.000    1300.000
  u_tx               tx_m              40    300.000     700.000
    u_fifo           fifo_m            30    400.000     400.000
  u_rx               rx_m              50    200.000     500.000
    u_fifo           fifo_m            45    300.000     300.000
--------------------------------------------------------------------
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>2 && !/^-/ { n[$1]++ } END { for (k in n) if (n[k] > 1) printf "  %s appears %d times\n", k, n[k] }' dup.rpt
awk 'NR>2 && !/^-/ { a[$1] += $(NF-1) } END { printf "  u_fifo = %.3f   (two distinct instances of 400.000 and 300.000)\n", a["u_fifo"] }' dup.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_fifo appears 2 times
  u_fifo = 700.000   (two distinct instances of 400.000 and 300.000)

The two merge into one 700.000 row — a module that does not exist, with an area neither instance has.

Rebuilding the full path from the depth keeps them apart:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>2 && !/^-/ { d = (match($0, /[^ ]/) - 1) / 2; path[d] = $1; p = path[0]; for (i = 1; i <= d; i++) p = p "/" path[i]; a[p] += $(NF-1); if (!(p in seen)) { seen[p]=1; o[++n]=p } } END { for (i=1;i<=n;i++) printf "  %-22s %9.3f\n", o[i], a[o[i]] }' dup.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  top                      100.000
  top/u_tx                 300.000
  top/u_tx/u_fifo          400.000
  top/u_rx                 200.000
  top/u_rx/u_fifo          300.000

path[d] = $1 keeps the current name at each level and the loop joins levels 0 to d. Because the report is depth-first, the ancestors of any row are the last names seen at the shallower depths — which is the same insight as Chapter 10.2's carried header, with one slot per level instead of one variable.

Note that the duplicate check is worth running even when you expect unique names. u_fifo is a completely ordinary thing to call two FIFOs, and a design with a transmit and a receive path will usually have a matched pair.

8. Common Mistakes

Summing Total Area. It gives 2.50× the real total here — section 2.

Correcting for it with a constant. The factor is this design's area-weighted nesting depth plus one, not a universal — section 3.

$4 and $5 for the area columns. The instance column is indented and the module names vary; count from NF — section 2.

Reading depth from $1. Default field splitting discards the indentation — section 5.

A space-based depth rule on a tab-indented report. Every module reads as depth 0 and the hierarchy collapses silently — section 6.

Expanding tabs across the whole line. It breaks the column alignment $(NF-1) depends on; normalise the leading whitespace only.

Keying a table on the bare instance name. Two u_fifo instances merge into one 700.000 row — section 7.

Comparing a parent's total against the children's sum with ==. Use a tolerance — Chapter 11.1 section 8.

9. Exercises

Exercise 1 — The per-module own-area table

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { printf "%-14s %5d %10.3f\n", $1, $(NF-2), $(NF-1) }' hier.rpt | head -6
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
alu_core         204   1420.500
u_regfile        466   4980.000
u_alu            178   1310.000
u_add            420   2840.000
u_mul            642   5210.000
u_shift          190   1130.000

Exercise 2 — Depth and full path

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2; path[d] = $1; p = path[0]; for (i = 1; i <= d; i++) p = p "/" path[i]; printf "%d  %s\n", d, p }' hier.rpt | head -7
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0  alu_core
1  alu_core/u_regfile
1  alu_core/u_alu
2  alu_core/u_alu/u_add
2  alu_core/u_alu/u_mul
2  alu_core/u_alu/u_shift
2  alu_core/u_alu/u_logic

Exercise 3 — Debug a total that is 2.5× too large

A per-module script reports the design at 69376.500 where synthesis says 27793.500.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { own += $(NF-1); tot += $NF } END { printf "own column %.3f, cumulative column %.3f, ratio %.2f\n", own, tot, tot/own }' hier.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
own column 27793.500, cumulative column 69376.500, ratio 2.50

The cumulative column was summed. Each module's area is in its own row and in every ancestor's:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2; a[d] += $(NF-1) } END { for (i=0;i<=2;i++) s += a[i] * (i+1); printf "predicted from depth multiplicity: %.3f\n", s }' hier.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
predicted from depth multiplicity: 69376.500

Exercise 4 — Debug a hierarchy that came out flat

A per-module table shows twenty-two modules all at the top level.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed 's/^    /\t\t/; s/^  /\t/' hier.rpt > hier_tab.rpt
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2; if (d > md) md = d; rows++ } END { printf "%d module rows, maximum depth %d -> %s\n", rows, md, (md == 0 && rows > 1 ? "SUSPECT: a multi-module design with no nesting" : "plausible") }' hier_tab.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
22 module rows, maximum depth 0 -> SUSPECT: a multi-module design with no nesting
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed -n '9,11p' hier_tab.rpt | cat -t
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
^Iu_regfile          regfile_m        466   4980.000    4980.000
^Iu_alu              alu_m            178   1310.000   11350.000
^I^Iu_add            add_m            420   2840.000    2840.000

cat -t shows ^I — the indentation is tabs, which /[^ ]/ matches, so match returns 1 on every line. Everything else about the report is intact: u_alu still reports its own 1310.000 and its cumulative 11350.000, and the columns still line up. Only the depth is lost.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { s = $0; sub(/[^ \t].*$/, "", s); gsub(/\t/, "  ", s); d = length(s) / 2; if (d > md) md = d; rows++ } END { printf "%d module rows, maximum depth %d\n", rows, md }' hier_tab.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
22 module rows, maximum depth 2

Exercise 5 — Engineering application

The per-module table a synthesis review reads: full paths so nothing merges, own area so nothing double-counts, the parent/child arithmetic checked, and the flat-hierarchy precondition enforced.

Azvya Education Pvt. Ltd.VLSI Mentor
hierstats.awk — synthetic teaching script.
# hierstats.awk — per-module statistics from a hierarchical area report.
#
# Usage:  awk -f hierstats.awk hier.rpt
#         awk -v top=5 -f hierstats.awk hier.rpt
# Exit:   0 ok
#         1 the own-area column does not match the top row's Total Area
#         2 the hierarchy looks collapsed (indentation was not understood)

BEGIN { TOL = 1e-6 }

# the column header and the rules are the only lines that are not modules
/^Instance|^-|^TOTAL|^\*|^Report|^Design|^$/ { next }

{
    lead = $0; sub(/[^ \t].*$/, "", lead); gsub(/\t/, "  ", lead)
    d = length(lead) / 2
    if (d > maxd) maxd = d
    path[d] = $1
    p = path[0]
    for (i = 1; i <= d; i++) p = p "/" path[i]

    rows++
    own[p] = $(NF-1); cum[p] = $NF; cells[p] = $(NF-2); depth[p] = d
    order[rows] = p
    ownsum += $(NF-1)
    bydepth[d] += $(NF-1); ndepth[d]++
    if (d == 0) topcum = $NF
}

END {
    if (maxd == 0 && rows > 1) {
        printf "ERROR: %d module rows and maximum depth 0 -- the indentation was not understood\n", rows > "/dev/stderr"
        exit 2
    }
    if (!(ownsum - topcum < TOL && topcum - ownsum < TOL)) {
        printf "ERROR: own areas sum to %.3f but the top row reports %.3f\n", ownsum, topcum > "/dev/stderr"
        exit 1
    }

    printf "%-26s %2s %6s %11s %11s %7s\n", "path", "d", "cells", "own area", "total area", "share"
    lim = (top == "" ? rows : top)
    shown = 0
    for (i = 1; i <= rows && shown < lim; i++) {
        p = order[i]; shown++
        printf "%-26s %2d %6d %11.3f %11.3f %6.1f%%\n", p, depth[p], cells[p], own[p], cum[p], 100 * own[p] / ownsum
    }
    if (shown < rows) printf "... %d more module(s)\n", rows - shown
    printf "#\n# own area by depth:\n"
    for (i = 0; i <= maxd; i++) printf "#   depth %d: %2d module(s) %11.3f (%.1f%%)\n", i, ndepth[i], bydepth[i], 100 * bydepth[i] / ownsum
    printf "# %d module(s), own areas sum to %.3f = the top row's Total Area\n", rows, ownsum
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -v top=6 -f hierstats.awk hier.rpt
echo "status: $?"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
path                        d  cells    own area  total area   share
alu_core                    0    204    1420.500   27793.500    5.1%
alu_core/u_regfile          1    466    4980.000    4980.000   17.9%
alu_core/u_alu              1    178    1310.000   11350.000    4.7%
alu_core/u_alu/u_add        2    420    2840.000    2840.000   10.2%
alu_core/u_alu/u_mul        2    642    5210.000    5210.000   18.7%
alu_core/u_alu/u_shift      2    190    1130.000    1130.000    4.1%
... 16 more module(s)
#
# own area by depth:
#   depth 0:  1 module(s)    1420.500 (5.1%)
#   depth 1:  7 module(s)   11163.000 (40.2%)
#   depth 2: 14 module(s)   15210.000 (54.7%)
# 22 module(s), own areas sum to 27793.500 = the top row's Total Area
status: 0

The share column is each module's own area as a fraction of the design — which sums to 100% because own areas partition it, and which the cumulative column could never give.

The reconciliation is the exit-1 condition: own areas must sum to the top row's Total Area. That single equation catches a dropped row, a misread column and a double-counted parent at once, and it is available in the report itself rather than from an expectation.

And the tab-indented variant, which is the case section 6 showed defeats a naive depth rule:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -v top=3 -f hierstats.awk hier_tab.rpt
echo "status: $?"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
path                        d  cells    own area  total area   share
alu_core                    0    204    1420.500   27793.500    5.1%
alu_core/u_regfile          1    466    4980.000    4980.000   17.9%
alu_core/u_alu              1    178    1310.000   11350.000    4.7%
... 19 more module(s)
#
# own area by depth:
#   depth 0:  1 module(s)    1420.500 (5.1%)
#   depth 1:  7 module(s)   11163.000 (40.2%)
#   depth 2: 14 module(s)   15210.000 (54.7%)
# 22 module(s), own areas sum to 27793.500 = the top row's Total Area
status: 0

Identical to the space-indented run, down to the per-depth breakdown — because the script normalises the leading whitespace before measuring it and reads the columns from NF rather than by position. The depth rule in section 6 that returned all zeros is the one this script does not use, and the difference is the gsub(/\t/, " ").

That is the whole value of the normalisation, and it is worth seeing it succeed rather than only seeing the naive version fail. The collapse guard never fires here — which is correct, and is also why Exercise 6 exists: a guard that passes is only reassuring once you know what would make it fire.

Exercise 6 — Challenge: show that the flat-hierarchy check is not enough

Exercise 5's maxd == 0 && rows > 1 guard catches a total collapse. Construct a report where the indentation is misread but the guard passes, and find what catches it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed 's/^  \(u_regfile\)/\1/' hier.rpt > shift.rpt
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2; if (d > md) md = d; rows++ } END { printf "%d rows, max depth %d -> %s\n", rows, md, (md == 0 && rows > 1 ? "SUSPECT" : "passes the collapse check") }' shift.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
22 rows, max depth 2 -> passes the collapse check

One module's indentation was removed, so u_regfile now reads as a second top-level module. The maximum depth is still 2, so the collapse check sees nothing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { d = (match($0, /[^ ]/) - 1) / 2; if (d == 0) { n0++; printf "  depth 0: %s\n", $1 } } END { printf "  %d module(s) at depth 0 -> %s\n", n0, (n0 == 1 ? "ok" : "A REPORT HAS EXACTLY ONE TOP MODULE") }' shift.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  depth 0: alu_core
  depth 0: u_regfile
  2 module(s) at depth 0 -> A REPORT HAS EXACTLY ONE TOP MODULE

Two modules at depth 0, which a single-top report cannot have. That is the check, and it is a structural fact about the report rather than a threshold.

The own-area reconciliation does not catch this one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR>7 && !/^-|^TOTAL/ { own += $(NF-1); if ((match($0, /[^ ]/) - 1) / 2 == 0 && !seen++) topcum = $NF } END { printf "own sum %.3f vs top Total Area %.3f -> %s\n", own, topcum, (own - topcum < 1e-6 && topcum - own < 1e-6 ? "RECONCILES" : "mismatch") }' shift.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
own sum 27793.500 vs top Total Area 27793.500 -> RECONCILES

It reconciles perfectly, because de-indenting a row changes no area — only the tree. So the two checks are independent and both necessary: the reconciliation validates the numbers and the single-top rule validates the structure, and each is blind to the other's failure.

That pairing is the module's recurring shape. Chapter 11.1 needed a residual check and an anchored pattern; Chapter 11.2 needed a type assertion and the leaf/instance reconciliation; here it is areas and topology. One check is a property; two independent checks are a parser.

10. Summary

  • Cell Area is own area; Total Area includes everything nested inside. On the top row they are 1420.500 and 27793.500.
  • Summing Total Area gives 2.50× the real total — section 2.
  • The factor decomposes exactly — depth 0 counted once, depth 1 twice, depth 2 three times, predicting 69376.500 against a measured 69376.500. It is this design's shape, not a constant.
  • A parent's Total Area = own Cell Area + children's Total Area — verified on a three-child and a four-child parent.
  • Own areas partition the design, so a per-module share column sums to 100%.
  • Count fields from NF. The instance column is indented and module names vary in width.
  • Default field splitting discards the indentation. Recover depth with (match($0, /[^ ]/) - 1) / 2 on $0.
  • On a tab-indented report that rule returns 0 for every module and the hierarchy collapses silently, with every number still correct.
  • A bare instance name is not a key — two u_fifo instances merge into a 700.000 row that does not exist. Rebuild the path with path[d] = $1.
  • Two independent checks are needed: own areas must sum to the top's total, and exactly one module may sit at depth 0. Each is blind to the other's failure.

Chapter 11.4 — Line Coverage leaves synthesis for verification, and starts with the difference that makes coverage reports harder than area reports: an area is a measurement, and a coverage percentage is a ratio whose denominator the report may not print.

Where this fits

Part of the awk curriculum.