Skip to content
VLSI Mentor

awk Part IV · Module 13 · Real-World AWK Engineering Projects

Project: Coverage Summary Generator

The third script, and the one with the most ways to produce a confident wrong number. It reads per-block coverage across four metrics, aggregates each one over its own population, reproduces the weighted mean the tool reports for functional coverage, flags the blocks under target, and writes both a table for a reviewer and a CSV for a dashboard. On this report the weighted figure is 65.81% and the unweighted one 55.20% — and a coverage gate written the obvious way lets through one block where three should pass.

Advanced19 min readawkPart IVProjectCoverageWeights

Part IV · Module 13 · Chapter 13.3 · Project: Coverage Summary Generator

1. What the Script Has to Do

Four outputs, and three of them have a decision in them:

  1. A per-block table with every metric, so a reviewer can see where the gaps are.
  2. One aggregate per metric, each over its own population — never pooled, never averaged.
  3. The functional figure the tool would report, which is a weighted mean, with the unweighted one beside it because they differ.
  4. A CSV, written in the same pass, so a dashboard never parses the table.

Module 11 measured a failure for each of those. This chapter builds the script that avoids all of them at once.

2. The Report

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN {
  n = split("u_alu:3:520:560:410:620:88:96:72.50|u_regfile:1:210:210:248:256:18:18:98.00|u_ctrl:2:300:420:190:380:52:88:61.00|u_io:1:96:96:64:64:0:0:|u_dma:2:180:400:120:340:30:70:44.50|u_debug:0:0:120:0:160:0:24:0.00", B, "|")
  print "========================================"
  print "Coverage Summary : soc_top"
  print "Tests merged    : 412"
  print "========================================"
  print ""
  for (i = 1; i <= n; i++) {
    split(B[i], f, ":")
    printf "Block: %s\n  Weight: %d\n", f[1], f[2]
    printf "  Line        %9s   (%d of %d)\n", (f[4] ? sprintf("%.2f%%", 100*f[3]/f[4]) : "n/a"), f[3], f[4]
    printf "  Toggle      %9s   (%d of %d)\n", (f[6] ? sprintf("%.2f%%", 100*f[5]/f[6]) : "n/a"), f[5], f[6]
    printf "  Branch      %9s   (%d of %d)\n", (f[8] ? sprintf("%.2f%%", 100*f[7]/f[8]) : "n/a"), f[7], f[8]
    printf "  Functional  %9s\n\n", (f[9] == "" ? "n/a" : sprintf("%.2f%%", f[9]))
    lc += f[3]; lt += f[4]; tc += f[5]; tt += f[6]; bc += f[7]; bt += f[8]
  }
  print "========================================"
  printf "TOTAL %-10s %9s   (%d of %d)\n", "Line", sprintf("%.2f%%", 100*lc/lt), lc, lt
  printf "TOTAL %-10s %9s   (%d of %d)\n", "Toggle", sprintf("%.2f%%", 100*tc/tt), tc, tt
  printf "TOTAL %-10s %9s   (%d of %d)\n", "Branch", sprintf("%.2f%%", 100*bc/bt), bc, bt
  print "========================================"
}' > cov.rpt
sed -n '6,12p' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Block: u_alu
  Weight: 3
  Line           92.86%   (520 of 560)
  Toggle         66.13%   (410 of 620)
  Branch         91.67%   (88 of 96)
  Functional     72.50%

Six blocks, four metrics each, a per-block weight, and three TOTAL rows the script will reconcile against. Two blocks are deliberately awkward: u_io has (0 of 0) branches and no functional score, and u_debug has weight 0 with nothing covered.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^Block: / { n++ } /^  Weight: 0$/ { z++ } /n\/a/ { na++ } END { printf "  %d block(s), %d at weight 0, %d n/a field(s)\n", n, z+0, na+0 }' cov.rpt
awk '/^TOTAL/ { printf "  %-8s %s  (%s of %s)\n", $2, $3, substr($4,2), substr($6,1,length($6)-1) }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  6 block(s), 1 at weight 0, 2 n/a field(s)
  Line     72.31%  (1306 of 1806)
  Toggle   56.70%  (1032 of 1820)
  Branch   63.51%  (188 of 296)

3. The Three Figures a Reviewer Could Be Given

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^  Line / && $2 != "n/a" { p = $2; sub(/%$/,"",p); s += p+0; n++; c += substr($3,2)+0; t += $5+0 }
 END { printf "  mean of %d per-block percentages : %6.2f%%\n", n, s/n
       printf "  aggregate %d of %d counts       : %6.2f%%\n", c, t, 100*c/t }' cov.rpt
awk '/^TOTAL Line/ { printf "  report TOTAL                    : %s\n", $3 }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mean of 6 per-block percentages :  68.22%
  aggregate 1306 of 1806 counts       :  72.31%
  report TOTAL                    : 72.31%

68.22% against 72.31%, with the report agreeing on the latter. Chapter 11.4 measured the same error at 76.13% against 81.70%; the gap here is 4.09 points and in the same direction, because averaging gives the small blocks the same vote as the large ones.

So the script aggregates counts. That decision is not negotiable and it is also not sufficient — the next two sections are the parts that still go wrong.

4. A Gate on the Raw Field Passes the Wrong Blocks

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^  Line / && $2 != "n/a" { if ($2 >= 80) a++; if ($2 + 0 >= 80) b++ } END { printf "  >= 80 without +0: %d block(s)   with +0: %d block(s)\n", a+0, b+0 }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  >= 80 without +0: 1 block(s)   with +0: 3 block(s)

One against three. Chapter 11.4 section 4 measured why: the % makes the field a non-numeric string, so the comparison is lexical and 100.00% sorts below 90. Here it means two fully-or-nearly covered blocks are reported as failing the gate.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^Block: / { b = $2 } /^  Line / && $2 != "n/a" { printf "  %-11s field=[%-8s] raw>=80 %-4s  (+0)>=80 %s\n", b, $2, ($2 >= 80 ? "yes" : "no"), ($2 + 0 >= 80 ? "yes" : "no") }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu       field=[92.86%  ] raw>=80 yes   (+0)>=80 yes
  u_regfile   field=[100.00% ] raw>=80 no    (+0)>=80 yes
  u_ctrl      field=[71.43%  ] raw>=80 no    (+0)>=80 no
  u_io        field=[100.00% ] raw>=80 no    (+0)>=80 yes
  u_dma       field=[45.00%  ] raw>=80 no    (+0)>=80 no
  u_debug     field=[0.00%   ] raw>=80 no    (+0)>=80 no

The raw comparison passes the 92.86% block and fails both blocks at 100.00%. The ordering is purely lexical, against the string "80":

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { split("92.86% 100.00% 71.43% 45.00% 0.00%", v, " "); for (i=1;i<=5;i++) printf "  [%-8s] first char %s, so >= \"80\" lexically: %s\n", v[i], substr(v[i],1,1), (v[i] >= 80 ? "yes" : "no") }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  [92.86%  ] first char 9, so >= "80" lexically: yes
  [100.00% ] first char 1, so >= "80" lexically: no
  [71.43%  ] first char 7, so >= "80" lexically: no
  [45.00%  ] first char 4, so >= "80" lexically: no
  [0.00%   ] first char 0, so >= "80" lexically: no

It is comparing first characters. 9 sorts above 8 so 92.86% passes; 1 sorts below 8 so 100.00% fails. Nothing about the magnitude enters into it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^  Line / && $2 != "n/a" { raw = ($2 >= 80); num = ($2 + 0 >= 80); if (raw != num) bad++; n++ } END { printf "  %d of %d block(s) get a DIFFERENT verdict from the raw field\n", bad+0, n }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  2 of 6 block(s) get a DIFFERENT verdict from the raw field

5. Weighted Against Unweighted

Per-block coverage data splits into two reductions: line, toggle and branch aggregate as covered-over-total counts within each metric, while functional coverage is a weighted mean of per-block scores. Both feed the summary table and the CSV.Per-block rowsSix blocks, four metrics,one weight eachCount aggregatesLine, toggle, branch — eachover its own populationWeighted meanFunctional — the figure thetool reportsTable and CSVOne pass, both outputs, nore-parsingsumemit12
Figure 1 — The two reductions the script performs, and why they cannot be merged. Line, toggle and branch aggregate over counts in their own populations; functional coverage is a weighted mean of per-block scores, which is what the tool reports and what a bin ratio cannot reproduce.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^Block: / { b = $2 } /^  Weight: / { w[b] = $2 + 0 } /^  Functional / && $2 != "n/a" { f = $2; sub(/%$/,"",f); sc[b] = f + 0; have[b] = 1; if (!(b in seen)) { seen[b] = 1; o[++n] = b } }
 END { for (i = 1; i <= n; i++) { b = o[i]
         if (w[b] > 0) { num += w[b] * sc[b]; den += w[b] }
         unum += sc[b]; uden++ }
       printf "  weighted mean   %6.2f%% over weight %d\n", num/den, den
       printf "  unweighted mean %6.2f%% over %d scored block(s)\n", unum/uden, uden
       printf "  difference      %+6.2f points\n", num/den - unum/uden }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  weighted mean    65.81% over weight 8
  unweighted mean  55.20% over 5 scored block(s)
  difference      +10.61 points

65.81% against 55.20%, a gap of ten and a half points, and the cause is one block:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^Block: / { b = $2 } /^  Weight: / { w = $2 + 0 } /^  Functional / { printf "  %-11s weight %d  functional %s%s\n", b, w, $2, (w == 0 ? "   <- excluded from the weighted mean" : "") }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu       weight 3  functional 72.50%
  u_regfile   weight 1  functional 98.00%
  u_ctrl      weight 2  functional 61.00%
  u_io        weight 1  functional n/a
  u_dma       weight 2  functional 44.50%
  u_debug     weight 0  functional 0.00%   <- excluded from the weighted mean

u_debug is at 0.00% with weight 0. The weighted mean drops it entirely; the unweighted mean gives it a full vote. Chapter 11.7 measured this on a covergroup and the consequence is the same here one level up: a weight of zero removes a block from the score while its uncovered bins remain in the count aggregates.

Which is why the script prints both, names every zero-weighted block, and says in the output that their bins are still in the line, toggle and branch totals. That is Chapter 11.8's rule — an aggregation that changes the answer belongs in the report.

6. The Complete Script

Azvya Education Pvt. Ltd.VLSI Mentor
covsum.awk — synthetic teaching script.
# covsum.awk — coverage summary generator.
#
# Usage:  awk -f covsum.awk cov.rpt
#         awk -v gate=80 -v csv=cov.csv -f covsum.awk cov.rpt
# Exit:   0 every weighted block is at or above the gate
#         1 a weighted block is below the gate
#         2 the parse does not reconcile with the report's own TOTAL rows
#
# Design notes, each from a measured failure in Module 11:
#   - Aggregates COUNTS, never percentages: averaging per-block percentages
#     gave 68.22% against a true 72.31%.                            (11.4)
#   - Converts into a variable before comparing: a field reading 100.00%
#     fails a >= 80 gate because the percent sign makes it a string. (11.4)
#   - Treats n/a as "no coverable items", not 0%: branch coverage had three
#     different answers depending on which.                          (11.4)
#   - Reproduces the tool's WEIGHTED mean for functional coverage, and reports
#     the unweighted one beside it, because they differ by 10.6 points. (11.7)
#   - Names every zero-weighted block: weight 0 removes a block from the score
#     while its uncovered bins remain in the count aggregates.       (11.7)
#   - Reconciles against the report's own TOTAL rows -- the only check that
#     detects a parse error.                                         (11.8)

BEGIN { if (gate == "") gate = 80; split("Line Toggle Branch", metric, " ") }

/^Block: /      { b = $2; if (!(b in bseen)) { bseen[b] = 1; border[++nb] = b } }
/^  Weight: /   { wt[b] = $2 + 0 }

/^  (Line|Toggle|Branch) / {
    m = $1
    if ($2 == "n/a") { navail[b, m] = 1; next }
    c = substr($3, 2) + 0; t = $5 + 0
    cov[b, m] = c; tot[b, m] = t
    aggc[m] += c; aggt[m] += t
}

/^  Functional / {
    if ($2 == "n/a") { fna[b] = 1; next }
    f = $2; sub(/%$/, "", f); fscore[b] = f + 0; fhave[b] = 1
}

/^TOTAL / { rc[$2] = substr($4, 2) + 0; rt[$2] = $6 + 0 }

END {
    if (nb == 0) { print "ERROR: no blocks found" > "/dev/stderr"; exit 2 }

    # reconcile first -- a table built from a bad parse is worse than none
    for (i = 1; i <= 3; i++) { m = metric[i]
        if (m in rc && (aggc[m] != rc[m] || aggt[m] != rt[m])) {
            printf "ERROR: %s parsed %d of %d, report says %d of %d\n", m, aggc[m], aggt[m], rc[m], rt[m] > "/dev/stderr"
            bad++
        }
    }
    if (bad) exit 2

    printf "%-11s %3s %18s %18s %18s %10s\n", "block", "wt", "line", "toggle", "branch", "functional"
    for (j = 1; j <= nb; j++) {
        b = border[j]
        out = sprintf("%-11s %3d", b, wt[b])
        for (i = 1; i <= 3; i++) { m = metric[i]
            if ((b SUBSEP m) in navail || tot[b, m] == 0) out = out sprintf(" %8s %9s", "n/a", "(0 of 0)")
            else out = out sprintf(" %7.2f%% %9s", 100 * cov[b, m] / tot[b, m], sprintf("(%d/%d)", cov[b, m], tot[b, m]))
        }
        out = out sprintf(" %9s", (b in fhave ? sprintf("%.2f%%", fscore[b]) : "n/a"))
        if (b in fhave && wt[b] > 0 && fscore[b] < gate) { out = out "  BELOW"; nbelow++ }
        if (wt[b] == 0) { out = out "  [weight 0]"; nzero++ }
        print out
    }

    print ""
    print "AGGREGATE  (counts summed, never percentages averaged)"
    for (i = 1; i <= 3; i++) { m = metric[i]
        printf "  %-8s %5d of %5d = %6.2f%%\n", m, aggc[m], aggt[m], 100 * aggc[m] / aggt[m] }

    for (j = 1; j <= nb; j++) { b = border[j]
        if (b in fhave && wt[b] > 0) { fnum += wt[b] * fscore[b]; fden += wt[b] }
        if (b in fhave) { unum += fscore[b]; uden++ } }
    printf "  %-8s weighted mean %6.2f%% over weight %d   (unweighted mean of %d block(s): %6.2f%%)\n",
           "Funct", (fden ? fnum / fden : 0), fden, uden, (uden ? unum / uden : 0)

    printf "#\n# %d block(s); %d below the %d%% functional gate; %d at weight 0\n", nb, nbelow + 0, gate, nzero + 0
    if (nzero) { printf "# weight 0 blocks are absent from the weighted mean while their bins remain in the aggregates:"
                 for (j = 1; j <= nb; j++) if (wt[border[j]] == 0) printf " %s", border[j]
                 print "" }

    if (csv != "") {
        print "block,weight,line_cov,line_tot,toggle_cov,toggle_tot,branch_cov,branch_tot,functional" > csv
        for (j = 1; j <= nb; j++) { b = border[j]
            printf "%s,%d,%d,%d,%d,%d,%d,%d,%s\n", b, wt[b],
                   cov[b,"Line"] + 0, tot[b,"Line"] + 0, cov[b,"Toggle"] + 0, tot[b,"Toggle"] + 0,
                   cov[b,"Branch"] + 0, tot[b,"Branch"] + 0, (b in fhave ? sprintf("%.2f", fscore[b]) : "") > csv }
        close(csv)
        printf "# csv written to %s\n", csv
    }
    if (nbelow) exit 1
}

Three structural points. The reconciliation runs before any output, so a bad parse produces a diagnostic and nothing else. The table and the CSV come from one pass over the same arrays, so a dashboard never re-parses the formatted table — Chapter 12.4's rule, and the mistake Chapter 13.1 made in its own exercises. And close(csv) matters: without it the file may still be buffered when the script exits, which Chapter 7.3 measured.

7. Running It

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -v gate=80 -v csv=cov.csv -f covsum.awk cov.rpt
echo "status: $?"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
block        wt               line             toggle             branch functional
u_alu         3   92.86% (520/560)   66.13% (410/620)   91.67%   (88/96)    72.50%  BELOW
u_regfile     1  100.00% (210/210)   96.88% (248/256)  100.00%   (18/18)    98.00%
u_ctrl        2   71.43% (300/420)   50.00% (190/380)   59.09%   (52/88)    61.00%  BELOW
u_io          1  100.00%   (96/96)  100.00%   (64/64)      n/a  (0 of 0)       n/a
u_dma         2   45.00% (180/400)   35.29% (120/340)   42.86%   (30/70)    44.50%  BELOW
u_debug       0    0.00%   (0/120)    0.00%   (0/160)    0.00%    (0/24)     0.00%  [weight 0]

AGGREGATE  (counts summed, never percentages averaged)
  Line      1306 of  1806 =  72.31%
  Toggle    1032 of  1820 =  56.70%
  Branch     188 of   296 =  63.51%
  Funct    weighted mean  65.81% over weight 8   (unweighted mean of 5 block(s):  55.20%)
#
# 6 block(s); 3 below the 80% functional gate; 1 at weight 0
# weight 0 blocks are absent from the weighted mean while their bins remain in the aggregates: u_debug
# csv written to cov.csv
status: 1
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat cov.csv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
block,weight,line_cov,line_tot,toggle_cov,toggle_tot,branch_cov,branch_tot,functional
u_alu,3,520,560,410,620,88,96,72.50
u_regfile,1,210,210,248,256,18,18,98.00
u_ctrl,2,300,420,190,380,52,88,61.00
u_io,1,96,96,64,64,0,0,
u_dma,2,180,400,120,340,30,70,44.50
u_debug,0,0,120,0,160,0,24,0.00

The CSV carries counts, not percentages, so a dashboard can compute any aggregation it wants and none of them is baked in — Chapter 11.8's rule. u_io's functional field is empty rather than 0, which is the only way a consumer can tell "no score" from "a score of zero".

8. The Reconciliation Refusal

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^TOTAL Line/ { sub(/1806/, "9999") } { print }' cov.rpt > badtot.rpt
grep '^TOTAL Line' badtot.rpt
awk -v gate=80 -f covsum.awk badtot.rpt 2>&1 >/dev/null
echo "status: $?"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TOTAL Line          72.31%   (1306 of 9999)
ERROR: Line parsed 1306 of 1806, report says 1306 of 9999
status: 2

The guard fires, which Chapter 11.2 Exercise 6 argued is the only way to know a guard works.

One note on how that mutation was made. An earlier attempt used sed with the full line text typed out and did not match, because the generated column widths differ from what was typed. awk '/^TOTAL Line/ { sub(/1806/, "9999") }' matches on the row and substitutes the number, which does not depend on spacing at all — the same reasoning as Chapter 11.6's match() over a field index.

9. Every Decision and Its Source

decisionalternative that failsmeasured in
aggregate counts per metricaveraging gave 68.22% against 72.31%11.4
convert into a variable, then comparethe raw field misjudged 4 of 6 blocks11.4
n/a means no coverable itemscounting it as 0% gave three different answers11.4
reproduce the tool's weighted meana bin ratio does not reproduce it11.7
print the unweighted mean beside itthey differ by 10.61 points here11.7
name every zero-weighted blockits uncovered bins stay in the aggregates11.7
reconcile against the report's TOTALnothing else detects a parse error11.8
refuse before emitting anythinga partial table invites use10.2
CSV of counts, not percentagespercentages discard the weights11.8
empty CSV field for a missing score0 and "no score" become indistinguishable11.4
close(csv) before exitthe file may still be buffered7.3
one pass for both outputsre-parsing your own table shifts fields12.4, 13.1

10. Common Mistakes

Averaging per-block percentages. 68.22% against a true 72.31% — section 3.

Comparing the raw percentage field. It misjudged four of six blocks — section 4.

Treating n/a as 0%. It means there was nothing to cover.

Reporting the unweighted mean as the coverage. 55.20% against the tool's 65.81%.

Dropping the weight column. A zero-weighted block vanishes from the score.

Pooling metrics. Statements, bits and branches are different populations.

Emitting the table before reconciling. A bad parse should produce no table.

Writing percentages to the CSV. The consumer then cannot re-aggregate.

0 for a missing functional score. Use an empty field.

Re-parsing your own formatted table to build the CSV — section 6.

11. Exercises

Exercise 1 — Blocks below the gate, correctly

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -v gate=80 '/^Block: / { b = $2 } /^  Weight: / { w = $2 + 0 } /^  Functional / && $2 != "n/a" { p = $2 + 0; if (w > 0 && p < gate) printf "  %-11s %6.2f%% (weight %d)\n", b, p, w }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu        72.50% (weight 3)
  u_ctrl       61.00% (weight 2)
  u_dma        44.50% (weight 2)

Three blocks, each with its weight so the reviewer knows which one moves the aggregate most. u_debug at 0.00% is absent because its weight is zero — it is reported separately rather than silently included or silently dropped.

Exercise 2 — The CSV, re-aggregated downstream

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -v gate=80 -v csv=cov.csv -f covsum.awk cov.rpt > /dev/null
awk -F, 'NR > 1 { lc += $3; lt += $4; if ($9 != "" && $2 > 0) { num += $2 * $9; den += $2 } }
 END { printf "  from the CSV: line %d of %d = %.2f%%, functional weighted %.2f%%\n", lc, lt, 100*lc/lt, num/den }' cov.csv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  from the CSV: line 1306 of 1806 = 72.31%, functional weighted 65.81%

Both figures reproduced from the CSV alone, which is the test of whether the CSV carried enough — and it did, because it carries counts and weights rather than percentages.

Exercise 3 — Debug a gate that fails a fully covered block

A coverage gate reports u_regfile below an 80% target. Its line coverage is 100.00%.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^  Line / { printf "  NF=%d [%s][%s][%s][%s][%s]\n", NF, $1, $2, $3, $4, $5; exit }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  NF=5 [Line][92.86%][(520][of][560)]

The field layout is fine — NF=5 with the percentage at $2, the covered count at substr($3, 2) and the denominator at $5. And the conversion is well behaved on every value:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^Block: / { b = $2 } /^  Line / && $2 != "n/a" { printf "  %-11s %-8s -> %g\n", b, $2, $2 + 0 }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu       92.86%   -> 92.86
  u_regfile   100.00%  -> 100
  u_ctrl      71.43%   -> 71.43
  u_io        100.00%  -> 100
  u_dma       45.00%   -> 45
  u_debug     0.00%    -> 0

Six correct conversions — + 0 parses the numeric prefix and stops at the %, which Chapter 11.4 measured. So neither the data nor the conversion is at fault; the defect is entirely in the comparison, and the fix is to compare the converted value:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -v gate=80 '/^Block: / { b = $2 } /^  Line / && $2 != "n/a" { p = $2 + 0; if (p < gate) printf "  %-11s %7.2f%% below %d%%\n", b, p, gate }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_ctrl        71.43% below 80%
  u_dma         45.00% below 80%
  u_debug        0.00% below 80%

Three blocks, and neither 100.00% block among them.

Exercise 4 — Debug a weighted figure that excludes a failing block

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^Block: / { b = $2 } /^  Weight: / { w[b] = $2 + 0 } /^  Functional / && $2 != "n/a" { f = $2; sub(/%$/,"",f); sc[b] = f+0; if (!(b in s)) { s[b]=1; o[++n]=b } }
 END { for (i=1;i<=n;i++) { b=o[i]; if (w[b] > 0) { a += w[b]*sc[b]; d += w[b] } else printf "  excluded: %-11s %6.2f%% at weight 0\n", b, sc[b]; u += sc[b]; uc++ }
       printf "  weighted %.2f%% (den %d)   unweighted %.2f%% (n %d)\n", a/d, d, u/uc, uc }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  excluded: u_debug       0.00% at weight 0
  weighted 65.81% (den 8)   unweighted 55.20% (n 5)

One block excluded, and it is the one at 0.00% — which is exactly the block whose exclusion moves the figure most.

Exercise 5 — Engineering application

The script is the application. What remains is the wiring, with the same status discipline as Chapter 13.2:

Azvya Education Pvt. Ltd.VLSI Mentor
check_cov.sh — synthetic teaching script.
#!/bin/sh
# check_cov.sh — run the summary generator and route the outcome.

awk -v gate=80 -v csv=cov.csv -f covsum.awk cov.rpt > cov_report.txt 2> cov_errors.txt
status=$?
nbelow=$(awk '/below the/ { print $4 }' cov_report.txt)

case $status in
  0) echo "COVERAGE OK: every weighted block at or above the gate" ;;
  1) echo "COVERAGE BELOW GATE: $nbelow block(s); see cov_report.txt and cov.csv" ;;
  2) echo "COVERAGE REPORT UNUSABLE:"; cat cov_errors.txt ;;
  *) echo "GENERATOR FAILED (status $status)" ;;
esac
exit $status
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sh check_cov.sh
echo "status: $?"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
COVERAGE BELOW GATE: 3 block(s); see cov_report.txt and cov.csv
status: 1

status is captured on the line immediately after the command, and the case reads the variable — so the command substitution that counts the blocks cannot disturb it.

Exercise 6 — Challenge: show that the CSV is sufficient and the table is not

Exercise 2 recomputed both aggregates from the CSV. Show that the same recomputation from the formatted table is not possible.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -v gate=80 -v csv=cov.csv -f covsum.awk cov.rpt > cov_report.txt
awk 'NR > 1 && /^u_/ { printf "  %-11s field 2 = [%s]\n", $1, $2 }' cov_report.txt | head -3
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu       field 2 = [3]
  u_regfile   field 2 = [1]
  u_ctrl      field 2 = [2]

The weight is readable. The counts are not, because they were formatted into a parenthesised pair inside a wider column:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR > 1 && /^u_alu/ { printf "  NF=%d: ", NF; for (i = 1; i <= NF; i++) printf "[%s]", $i; print "" }' cov_report.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  NF=10: [u_alu][3][92.86%][(520/560)][66.13%][(410/620)][91.67%][(88/96)][72.50%][BELOW]

The pair (520/560) is one field containing a slash, so recovering the two numbers needs a split on / after stripping the parentheses — and the field positions shift by one on any row carrying BELOW or [weight 0]:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR > 1 && /^u_/ { printf "  %-11s NF=%d\n", $1, NF }' cov_report.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu       NF=10
  u_regfile   NF=9
  u_ctrl      NF=10
  u_io        NF=11
  u_dma       NF=10
  u_debug     NF=11

Three different field counts across six rows — 9, 10, 10, 11, 10, 11 — because the trailing annotations are optional and u_io's n/a (0 of 0) has a different word count from a (520/560) pair. Any index-based parse of this table is wrong on some row.

So the CSV is not a convenience, it is the interface: fixed field count, one record per block, counts rather than percentages, and an empty field where a value is absent. The table is for a person and is deliberately not machine-readable — which is Chapter 12.4's conclusion applied to the script's own output, and the reason section 6 builds both from one pass over the same arrays rather than deriving one from the other.

12. Summary

  • Four outputs: the per-block table, one aggregate per metric, the weighted functional figure with the unweighted one beside it, and a CSV.
  • Aggregate counts, never percentages — averaging gave 68.22% against a true 72.31%.
  • Convert into a variable before comparing. The raw field misjudged four of six blocks and passed the one it passed for the wrong reason.
  • n/a means nothing to cover. u_io has (0 of 0) branches and no functional score.
  • Weighted 65.81% against unweighted 55.20% — a 10.61-point gap caused by one block at 0.00% with weight 0.
  • Name every zero-weighted block and say that its bins remain in the count aggregates.
  • Reconcile against the report's own TOTAL rows before emitting anything.
  • The CSV carries counts and weights, so any aggregation can be recomputed downstream; an empty field distinguishes "no score" from zero.
  • Build the table and the CSV in one pass. The table has 9, 10 or 11 fields per row and cannot be parsed by index.
  • When a computed value contradicts the input you can see, print the inputs — Exercise 3's 45.00% -> 100 was a draft error, not a behaviour.

Chapter 13.4 — Project: Synthesis Area Analyzer builds the fourth script: per-module area and cell counts, compared against a previous run, with the modules that grew listed — and the floating-point comparison that makes "grew" harder to define than it looks.

Where this fits

Part of the awk curriculum.