Skip to content
VLSI Mentor

awk Part IV · Module 14 · AWK Debugging, Review, and Engineering Mastery

Interview Problems: Advanced

At this level the syntax is not in question and the problems stop having one-liners. What gets asked instead is a judgement: which of four defensible aggregates answers the question, whether a zero is a hole or an absence, what a crash on row four cost you, whether a script that behaved identically five times in a row is deterministic. Three of the eight problems here cannot be answered from the data as given, and saying so — with the measurement that shows why — is the answer. This is the closing chapter of the track.

Advanced24 min readawkPart IVInterviewDesignJudgement

Part IV · Module 14 · Chapter 14.9 · Interview Problems: Advanced

1. What Changes at This Level

The beginner problems had one right answer and the intermediate problems had one right answer with a trap in it. These have neither. An advanced awk question is usually a question about what the number means, and the strongest answer names the choice, measures both branches of it, and says which one the asker probably wanted.

Three of the eight problems below are unanswerable as posed. Recognising that is the whole point of asking them.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat > cov.rpt <<'EOF'
u_alu    75.00%   120   160
u_ctrl   40.00%    20    50
u_io    100.00%   310   310
u_mem     0.00%     0     0
EOF
printf 'u_alu\nu_ctrl\nu_io\nu_mem\nu_dma\n' > blocks.txt
printf '  %s blocks reported, %s expected by the block list\n' \
  "$(awk 'END { print NR }' cov.rpt)" "$(awk 'END { print NR }' blocks.txt)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  4 blocks reported, 5 expected by the block list

Four reported and five expected. That gap is the subject of Problem 1, and it is visible before any coverage figure is computed — which is the point: it is the cheapest question and the one most often skipped.

2. Problem 1 — "Coverage is 71.67%. Is that right?"

"Someone reported 71.67% functional coverage from this file. Verify it."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ v = $2; sub(/%$/, "", v); s += v; n++ }
     END { printf "  mean of percentages                 : %.2f%%  over %d blocks\n", s/n, n }' cov.rpt
awk '{ c += $3; t += $4 }
     END { printf "  aggregate                           : %.2f%%  (%d of %d)\n", 100*c/t, c, t }' cov.rpt
awk '$4 + 0 > 0 { v = $2; sub(/%$/, "", v); s += v; n++ }
     END { printf "  mean, excluding 0-denominator blocks: %.2f%%  over %d blocks\n", s/n, n }' cov.rpt
awk '$4 + 0 > 0 { c += $3; t += $4 }
     END { printf "  aggregate, same exclusion           : %.2f%%  (%d of %d)\n", 100*c/t, c, t }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mean of percentages                 : 53.75%  over 4 blocks
  aggregate                           : 86.54%  (450 of 520)
  mean, excluding 0-denominator blocks: 71.67%  over 3 blocks
  aggregate, same exclusion           : 86.54%  (450 of 520)

And there is a fifth fact that none of the four carries:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR == FNR { want[$1]; nw++; next } { saw[$1] }
     END { m = ""; for (k in want) if (!(k in saw)) m = m " " k
           printf "  %d block(s) expected, %d reported, missing:%s\n", nw, length(saw), (m == "" ? " none" : m) }' blocks.txt cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  5 block(s) expected, 4 reported, missing: u_dma

Five blocks were expected and four are in the report. u_dma produced no row at all — it was not simulated, or it crashed, or it was dropped from the build.

Every one of the four percentages is a figure over the four blocks that reported. None of them is wrong, and none of them is the answer to "is the design covered", because a fifth of the design is not in the file. Chapter 9.5's rule — the population comes from the list, not from what was found — is the one that produces this, and it is the single highest-value question to ask of any coverage or regression number.

3. Problem 2 — "u_mem is 0.00%. Is that a coverage hole?"

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "  %-8s pct=%-8s covered=%-4s coverable=%-4s -> %s\n", $1, $2, $3, $4,
         ($4 + 0 == 0 ? "NO COVERABLE ITEMS -- not a hole" : ($3 + 0 == 0 ? "a real hole" : "partial")) }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu    pct=75.00%   covered=120  coverable=160  -> partial
  u_ctrl   pct=40.00%   covered=20   coverable=50   -> partial
  u_io     pct=100.00%  covered=310  coverable=310  -> partial
  u_mem    pct=0.00%    covered=0    coverable=0    -> NO COVERABLE ITEMS -- not a hole

No. u_mem is 0 of 0 — the tool found nothing to cover, which usually means the block has no covergroups instantiated, or was excluded, or is empty. A block that is 0 of 450 is a coverage hole; a block that is 0 of 0 is a measurement hole, and the two need different people to fix them.

The 0.00% in the percentage column cannot distinguish them, which is Chapter 11.4's finding: n/a converts to 0, and a report that writes 0.00% for both cases has destroyed the difference before you see it. Only the denominator answers the question.

4. Problem 3 — "It crashed on row four. What did you lose?"

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%.2f%%\n", 100 * $3 / $4 }' cov.rpt > o.txt 2> e.txt
printf "  rc=%s  rows produced=%s\n  stderr: %s\n" "$?" "$(wc -l < o.txt | tr -d ' ')" "$(head -1 e.txt)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  rc=2  rows produced=3
  stderr: awk: division by zero

Three rows of four, and rc=2. Division by zero is fatal in awk — it does not produce inf or nan and it does not skip the record. It terminates the program, so every record after the offending one is never processed and END never runs.

That is the answer to "what did you lose": not one row, but every row after it, plus the summary. On a 400-block report with the zero-denominator block at position 12, a parser loses 388 blocks and reports the 11 it managed, with a non-zero exit nobody checked.

The two follow-ups the problem is really asking:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%.2f%%\n", 100 * $3 / $4 }' cov.rpt 2>/dev/null | wc -l | sed 's/^/  rows through a pipe: /'
awk '$4 + 0 == 0 { printf "  %-8s SKIPPED (no coverable items)\n", $1; next }
     { printf "  %-8s %.2f%%\n", $1, 100 * $3 / $4 }' cov.rpt > o.txt 2> e.txt
printf "  guarded: rc=%s rows=%s\n" "$?" "$(wc -l < o.txt | tr -d ' ')"
sed 's/^/  /' o.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  rows through a pipe:        3
  guarded: rc=0 rows=4
    u_alu    75.00%
    u_ctrl   40.00%
    u_io     100.00%
    u_mem    SKIPPED (no coverable items)

Through a pipe the three rows arrive and the status is the pipeline's, so a wrapper that does awk … | sort sees success. Chapter 12.1 section 7 measured that $? after a pipeline is the last command's, and under /bin/sh there is no pipestatus — a redirect is the portable remedy.

The guard costs one rule and produces four rows, rc=0, and an explicit SKIPPED label that distinguishes "no items" from "no coverage" — which is Problem 2's distinction surviving into the output.

5. Problem 4 — "Port this to POSIX awk"

"This runs on our build machines and not on the customer's. Fix it."

Azvya Education Pvt. Ltd.VLSI Mentor
gawkish.awk — synthetic review specimen. Contains portability defects.
BEGIN { PROCINFO["sorted_in"] = "@ind_str_asc" }
{ c[$1] = $2 }
END { printf "  keys=%d\n", length(c)
      for (k in c) printf "  %s %s\n", k, c[k] }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -f gawkish.awk cov.rpt 2>&1 | sed 's/^/  /'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    keys=4
    u_io 100.00%
    u_alu 75.00%
    u_ctrl 40.00%
    u_mem 0.00%
Azvya Education Pvt. Ltd.VLSI Mentor
posix.awk — the ported version.
{ if (!($1 in c)) ord[++n] = $1; c[$1] = $2 }
END { printf "  keys=%d\n", n
      for (i = 1; i <= n; i++) printf "  %s %s\n", ord[i], c[ord[i]] }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -f posix.awk cov.rpt | sed 's/^/  /'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    keys=4
    u_alu 75.00%
    u_ctrl 40.00%
    u_io 100.00%
    u_mem 0.00%

Four keys and a different order — input order, which is what the original script was trying and failing to impose. Three changes:

  • ord[++n] records first-seen order and n is the key count, so length() is not needed.
  • !($1 in c) is the membership test that does not create the element (Chapter 6.1).
  • The for loop is over a dense integer index, so the order is a property of the program rather than of the implementation.

If the requirement really is sorted rather than input order, the portable answer is a pipe to sort or an explicit sort in END — and Chapter 14.5 measured that the pipe costs 1.14×, which is not a reason to write one.

6. Problem 5 — "It gave the same answer five times. Is it deterministic?"

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for i in 1 2 3 4 5; do
  printf "  run %d: " "$i"
  awk '{ c["blk_" $1] } END { s = ""; for (k in c) s = s " " k; print substr(s, 2) }' cov.rpt
done
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  run 1: blk_u_alu blk_u_mem blk_u_io blk_u_ctrl
  run 2: blk_u_alu blk_u_mem blk_u_io blk_u_ctrl
  run 3: blk_u_alu blk_u_mem blk_u_io blk_u_ctrl
  run 4: blk_u_alu blk_u_mem blk_u_io blk_u_ctrl
  run 5: blk_u_alu blk_u_mem blk_u_io blk_u_ctrl

Five runs, byte-identical. The honest answer is no, and this is the evidence that makes the question worth asking.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for pre in u_ blk_ mod_; do
  printf "  prefix %-5s -> " "$pre"
  awk -v P="$pre" '{ c[P $1] } END { s = ""; for (k in c) s = s " " k; print substr(s, 2) }' cov.rpt
done
printf "  4 keys: "; awk '{ c[$1] } END { s = ""; for (k in c) s = s " " k; print substr(s, 2) }' cov.rpt
printf "  5 keys: "; (cat cov.rpt; echo "u_dma 50.00% 5 10") | awk '{ c[$1] } END { s = ""; for (k in c) s = s " " k; print substr(s, 2) }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  prefix u_    -> u_u_alu u_u_mem u_u_io u_u_ctrl
  prefix blk_  -> blk_u_alu blk_u_mem blk_u_io blk_u_ctrl
  prefix mod_  -> mod_u_io mod_u_alu mod_u_ctrl mod_u_mem
  4 keys: u_io u_alu u_ctrl u_mem
  5 keys: u_io u_dma u_alu u_ctrl u_mem

7. Problem 6 — "This gate passes. Ship it?"

Azvya Education Pvt. Ltd.VLSI Mentor
gate.awk — the coverage gate as submitted.
{ v = $2; sub(/%$/, "", v); if (v + 0 < 90 && $4 + 0 > 0) { bad++; printf "  BELOW: %s %s\n", $1, $2 } }
END { printf "  %d block(s) below gate\n", bad + 0; exit (bad ? 1 : 0) }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -f gate.awk cov.rpt > o.txt 2> e.txt; printf "  cov.rpt     rc=%s\n" "$?"; sed 's/^/  /' o.txt
awk '{ print $1, "95.00%", $3, $4 }' cov.rpt > allpass.rpt
awk -f gate.awk allpass.rpt > o.txt 2> e.txt; printf "  allpass.rpt rc=%s\n" "$?"; sed 's/^/  /' o.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  cov.rpt     rc=1
    BELOW: u_alu 75.00%
    BELOW: u_ctrl 40.00%
    2 block(s) below gate
  allpass.rpt rc=0
    0 block(s) below gate

The gate is proven able to fail in both directions: rc=1 with two blocks named on the real report, rc=0 with none on a file where every block is at 95%. That is the first thing to establish about any gate, because a rule that never fires and a rule that always passes produce identical output on a passing run.

So the threshold logic is sound. The answer has two parts anyway:

  • The gate is correctly built in one respect: $4 + 0 > 0 excludes the zero-denominator block, so u_mem at 0.00% does not fail a gate it cannot meet. That is Problem 2's distinction encoded as a condition.
  • The gate is silent about u_dma, which is not in the file at all. A gate that enumerates the report cannot fail on a block that produced no row, so the regression where a block was never simulated passes. Chapter 14.6 stage 5 is this exact defect: the check has no way to say nothing can be concluded.

So: no. Not because the threshold logic is wrong, but because the gate's population is the report rather than the block list, and a missing block is the failure mode most likely to be mistaken for success.

8. Problem 7 — "Make it ten times faster"

"This takes four minutes on the nightly log. Get it under thirty seconds."

The honest answer is that the program is almost certainly not where the time is, and Chapter 14.5 measured the ceiling: across fourteen variants on a 400,000-record, 24.5 MB log, the fastest was 0.86 s and the slowest 1.43 s. The entire range of in-program optimisation is 1.66×. Ten times is not available there.

The levers that are available, with the measured factors:

changemeasured effect
a selective grep prefilter (rare pattern, 800 of 400,000)2.49×
the same prefilter on a pattern matching 98.75% of records1.05× — none
merging two passes over the file into one program1.74×
replacing a regex over $0 with index() for a fixed stringrecovers +0.19 s of +0.26 s
guarding a gsub with the pattern it substitutesrecovers +0.20 s of +0.57 s
anchoring a regex0.01 s — nothing
avoiding $NF0.04 s — nothing

So the answer names the diagnosis before the fix: how many times is the file read, and how much of it reaches awk? Two passes and no prefilter is 2.45 s where one pass with a selective prefilter is 0.45 s, and that is 5.4× from structure alone, before a single expression is rewritten.

And if the file is read once, already filtered, and still slow, the remaining honest answer is that awk is not the bottleneck — the disk, the network mount, or the process that writes the log is. Chapter 14.5's measurement that grep -c alone accounts for 0.44 s of a 0.45 s pipeline is the shape of that conclusion.

9. Problem 8 — "Would you ship this script?"

"Here is a working parser. Review it."

The answer is a procedure, not a verdict, and Chapter 14.6 established it as six questions in a fixed order:

  1. What input does it assume? Field count, separator, line endings, optional columns. Unstated means unchecked.
  2. Which records does it select? Print NR for every match and compare against an independently derived set — never compare counts.
  3. Which field, and is it numeric? One $x + 0 == $x test.
  4. What is the denominator? Aggregate or mean, over which population.
  5. Does it refuse? No input, empty input, truncated input, missing expected rows.
  6. Can the next engineer change it?

The order matters because a wrong answer at any stage makes the later stages meaningless, and the empirical argument for it is Chapter 14.6's own measurement: a linter built from the mechanisable checks found three of six planted defects, and the three it missed were all stage 2 and stage 4 — the ones needing an expected answer from outside the file.

Applied to this chapter's material, the three questions that would have caught the most are:

  • Stage 4 on Problem 1. Four defensible percentages, and the one reported was the most assumption-laden of them.
  • Stage 5 on Problem 6. The gate cannot fail on a block that produced no row.
  • Stage 2 on Problem 5. The output order is a property of the implementation, not of the program.

10. Common Mistakes

Reporting a percentage without its numerator and denominator. One file supported 53.75%, 86.54% and 71.67%.

Assuming the mean and the aggregate are interchangeable. The aggregate was immune to the inclusion question; the mean moved 20.00 points.

Taking the report as the population. Five blocks expected, four reported, and no percentage mentions u_dma.

Reading 0.00% as a coverage hole. 0 of 0 is a measurement hole and needs a different person to fix it.

A classifier with fewer labels than cases. A block at 310 of 310 was labelled partial.

Expecting a division by zero to skip the record. It is fatal: three rows of four, rc=2, no END.

Capturing that status through a pipe. The pipeline's status is the last command's, and three rows arrive looking complete.

Believing a port runs because it works. PROCINFO was accepted, sorted nothing, and the script claimed to be sorted.

Trusting length(array). It works here and is not POSIX — count as you insert.

Taking five identical runs as determinism. The order changed under a different key prefix and when one key was added.

A gate whose population is the report. It cannot fail on a missing block, so the worst regression passes.

Optimising expressions to get 10×. The whole in-program range is 1.66×; structure gave 5.4×.

Reviewing style before selection. The mechanisable checks found three of six defects; the misses were all about which records and which denominator.

11. Exercises

Exercise 1 — All four coverage figures, side by side

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ v = $2; sub(/%$/, "", v)
       s += v; n++; c += $3; t += $4
       if ($4 + 0 > 0) { s2 += v; n2++; c2 += $3; t2 += $4 } }
     END { printf "  mean all        : %.2f%% over %d\n", s/n, n
           printf "  aggregate all   : %.2f%% (%d of %d)\n", 100*c/t, c, t
           printf "  mean, nonzero   : %.2f%% over %d\n", s2/n2, n2
           printf "  aggregate nonzero: %.2f%% (%d of %d)\n", 100*c2/t2, c2, t2 }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mean all        : 53.75% over 4
  aggregate all   : 86.54% (450 of 520)
  mean, nonzero   : 71.67% over 3
  aggregate nonzero: 86.54% (450 of 520)

One pass, all four figures, each with its population printed. This is the output a coverage script should produce — not one number, and not four numbers without their denominators.

Exercise 2 — The missing block

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR == FNR { want[$1]; next } { saw[$1] }
     END { n = split("u_alu u_ctrl u_io u_mem u_dma", o, " ")
           for (i = 1; i <= n; i++) printf "  %-8s %s\n", o[i], ((o[i] in saw) ? "reported" : "NO ROW IN THE REPORT") }' blocks.txt cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu    reported
  u_ctrl   reported
  u_io     reported
  u_mem    reported
  u_dma    NO ROW IN THE REPORT

Note the loop is over a fixed name list, not for (k in want) — Problem 5 is the reason.

Exercise 3 — Fix the classifier

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ cov = $3 + 0; able = $4 + 0
       if (able == 0)        lab = "NO COVERABLE ITEMS"
       else if (cov == able) lab = "complete"
       else if (cov == 0)    lab = "a real hole"
       else                  lab = "partial"
       printf "  %-8s %-4s/%-4s -> %s\n", $1, $3, $4, lab }' cov.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  u_alu    120 /160  -> partial
  u_ctrl   20  /50   -> partial
  u_io     310 /310  -> complete
  u_mem    0   /0    -> NO COVERABLE ITEMS

Four labels for four cases, ordered so exactly one can fire, and u_io is now complete. The order is not arbitrary: able == 0 must precede cov == able, because 0 == 0 is true and the empty block would otherwise be labelled complete — which is the same ordering requirement Chapter 14.4 section 9 found in its reason-code classifier.

This is written as an if/else if chain rather than a nested ternary for a reason measured on the way:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'BEGIN { x = 0; print "  one line  : " (x == 0 ? "A" : x == 1 ? "B" : "C") }'
awk 'BEGIN { x = 0; print "  split line: " (x == 0 ? "A" :
      x == 1 ? "B" : "C") }' 2>&1 | head -1 | sed 's/^/  /'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  one line  : A
  awk: syntax error at source line 2

A nested ternary works on one line and is a syntax error when broken after the :. awk's grammar does not allow a newline there, so the readable multi-line layout — which is how a four-case classifier wants to be written — does not parse. The if/else if chain has no such restriction and is what to reach for past two cases.

Exercise 4 — What a division by zero costs

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ printf "%s %.2f%%\n", $1, 100 * $3 / $4 }' cov.rpt > o.txt 2> e.txt
printf "  unguarded: rc=%s rows=%s last-row=[%s]\n" "$?" "$(wc -l < o.txt | tr -d ' ')" "$(tail -1 o.txt)"
awk '$4 + 0 == 0 { next } { printf "%s %.2f%%\n", $1, 100 * $3 / $4 }' cov.rpt > o.txt 2> e.txt
printf "  skipped  : rc=%s rows=%s last-row=[%s]\n" "$?" "$(wc -l < o.txt | tr -d ' ')" "$(tail -1 o.txt)"
awk '$4 + 0 == 0 { printf "%s n/a\n", $1; next } { printf "%s %.2f%%\n", $1, 100 * $3 / $4 }' cov.rpt > o.txt 2> e.txt
printf "  labelled : rc=%s rows=%s last-row=[%s]\n" "$?" "$(wc -l < o.txt | tr -d ' ')" "$(tail -1 o.txt)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  unguarded: rc=2 rows=3 last-row=[u_io 100.00%]
  skipped  : rc=0 rows=3 last-row=[u_io 100.00%]
  labelled : rc=0 rows=4 last-row=[u_mem n/a]

The first two produce the same three rows and differ only in the exit status. That is the trap: a next guard silences the crash and loses the block, so the output is indistinguishable from a successful run of an incomplete parser.

The third keeps the block with an explicit n/a, which is the only version where the row count matches the input. Chapter 11.4 measured the other end of that choice — n/a read back as 0 — so the label has to be handled on the way in by whatever consumes this.

Exercise 5 — Engineering application

Azvya Education Pvt. Ltd.VLSI Mentor
covreport.awk — synthetic teaching script.
# covreport.awk — report coverage with every assumption stated.
#
# Usage:  awk -f covreport.awk blocks.txt cov.rpt
#
# blocks.txt lists the blocks the run was supposed to cover, one per line.
# Problem 1 measured why it is required: four defensible percentages can be
# computed from the report alone and none of them mentions a block that
# produced no row.
#
# Exit:  0 every expected block reported and no block below the gate
#        1 a block is below the gate
#        2 an expected block produced no row -- nothing can be concluded
#
# Every figure is printed with its population. Chapter 13.5's rule: store the
# numerator and the denominator, never the percentage.

BEGIN { GATE = 90 }

FILENAME == ARGV[1] { want[$1]; nwant++; next }

{ saw[$1]
  v = $2; sub(/%$/, "", v); v += 0
  cov = $3 + 0; able = $4 + 0

  all_s += v; all_n++
  all_c += cov; all_t += able

  if (able == 0) { nomeasure++; next }

  nz_s += v; nz_n++
  nz_c += cov; nz_t += able
  if (v < GATE) { below++; blocked = blocked " " $1 }
}

END {
    missing = 0
    for (k in want) if (!(k in saw)) missing++

    printf "  expected=%d reported=%d missing=%d no-coverable-items=%d\n",
           nwant + 0, length(saw), missing, nomeasure + 0
    printf "  aggregate      : %d of %d", all_c + 0, all_t + 0
    if (all_t > 0) printf " = %.2f%%", 100 * all_c / all_t
    printf "\n"
    printf "  mean of blocks : %.2f%% over %d (all)", (all_n ? all_s / all_n : 0), all_n + 0
    if (nz_n) printf ", %.2f%% over %d (measurable)", nz_s / nz_n, nz_n
    printf "\n"
    printf "  below gate %d%% :%s (%d block(s), of %d measurable)\n",
           GATE, (blocked == "" ? " none" : blocked), below + 0, nz_n + 0

    if (missing) { printf "  REFUSING: %d expected block(s) produced no row\n", missing; exit 2 }
    if (below)   { exit 1 }
    exit 0
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -f covreport.awk blocks.txt cov.rpt > out.txt 2> err.txt
rc=$?
printf "  rc=%s\n" "$rc"; sed 's/^/  /' out.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  rc=2
    expected=5 reported=4 missing=1 no-coverable-items=1
    aggregate      : 450 of 520 = 86.54%
    mean of blocks : 53.75% over 4 (all), 71.67% over 3 (measurable)
    below gate 90% : u_alu u_ctrl (2 block(s), of 3 measurable)
    REFUSING: 1 expected block(s) produced no row

All four figures with their populations, the two blocks below the gate named, and then rc=2 because a fifth block produced no row.

The precedence is deliberate: the refusal overrides the gate result. Two blocks are genuinely below 90%, and reporting rc=1 would tell a caller "the gate failed, fix those two" when the honest statement is "a fifth of the design is not in this file and no gate verdict is available". Chapter 12.6's grading again — 2 means nothing can be concluded, and it outranks 1.

Note the capture: awk … > out.txt 2> err.txt on one line, rc=$? on the next, nothing between them. Chapter 14.8 Exercise 5 is where a sed between the command and the read cost a diagnosis.

Exercise 6 — Challenge: demonstrate all three exit paths

A documented status that no input produces is dead. Construct an input for each of 0, 1 and 2.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ print $1, "95.00%", $4, $4 }' cov.rpt > good.rpt
awk '{ print $1, ($1 == "u_ctrl" ? "40.00%" : "95.00%"), ($1 == "u_ctrl" ? "20" : $4), $4 }' cov.rpt > gated.rpt
printf 'u_alu\nu_ctrl\nu_io\nu_mem\n' > blocks4.txt
awk -f covreport.awk blocks.txt cov.rpt > out.txt 2> err.txt
rc=$?; printf "  blocks.txt + cov.rpt    rc=%s  last=[%s]\n" "$rc" "$(tail -1 out.txt | sed 's/^ *//')"
awk -f covreport.awk blocks4.txt good.rpt > out.txt 2> err.txt
rc=$?; printf "  blocks4.txt + good.rpt  rc=%s  last=[%s]\n" "$rc" "$(tail -1 out.txt | sed 's/^ *//')"
awk -f covreport.awk blocks4.txt gated.rpt > out.txt 2> err.txt
rc=$?; printf "  blocks4.txt + gated.rpt rc=%s  last=[%s]\n" "$rc" "$(tail -1 out.txt | sed 's/^ *//')"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  blocks.txt + cov.rpt    rc=2  last=[REFUSING: 1 expected block(s) produced no row]
  blocks4.txt + good.rpt  rc=0  last=[below gate 90% : none (0 block(s), of 3 measurable)]
  blocks4.txt + gated.rpt rc=1  last=[below gate 90% : u_ctrl (1 block(s), of 3 measurable)]

Three for three — 2, 0 and 1 — each from a purpose-built input. No branch is dead, and the rc=0 run is the control that shows the other two are not just "always non-zero".

The gated.rpt run in full, because its figures are the ones a reader would act on:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -f covreport.awk blocks4.txt gated.rpt > out.txt 2> err.txt
rc=$?
printf "  rc=%s\n" "$rc"; sed 's/^/  /' out.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  rc=1
    expected=4 reported=4 missing=0 no-coverable-items=1
    aggregate      : 490 of 520 = 94.23%
    mean of blocks : 81.25% over 4 (all), 76.67% over 3 (measurable)
    below gate 90% : u_ctrl (1 block(s), of 3 measurable)

rc=1, missing=0, and the aggregate at 94.23% while one block sits below the gate. That pairing is the reason the gate exists: a design can be 94% covered in aggregate and still have a block nobody exercised, and the aggregate alone would have been signed off.

Note also of 3 measurable on the gate line. The gate's denominator is the three blocks with coverable items, not the four reported and not the four expected — stated in the output rather than left for a reader to assume, which is Problem 1's conclusion applied to the gate's own figure.

Three deliberate choices in how those three runs were invoked:

  • Three explicit commands, not a loop. A for pair in "a b" "c d"; do set -- $pair; … loop depends on whether the shell word-splits an unquoted expansion, and that differs between shells — it works under /bin/sh and a bare $pair is a single word in zsh, so the same loop silently passes one nonexistent filename and exits 2 for every pair. Writing the three invocations out removes the question.
  • rc=$? on its own line, immediately after the command, with the printf after it. Four of this module's apparent defects turned out to be a status read after an intervening command rather than a fault in the program.
  • stdout and stderr to separate files. 2>&1 into one file merges two differently buffered streams, and a merged capture cannot be read as an ordering.

That last group is the smallest and most reusable thing in the chapter: cmd > out 2> err, then rc=$?, then read them. Every status-capture fault in Module 14 — four of them, in four different disguises — violated one of those three lines.

12. Summary

  • One coverage report supported four defensible percentages — 53.75%, 86.54%, 71.67%, 86.54% — differing by the mean/aggregate choice and the zero-denominator inclusion.
  • The aggregate is immune to the inclusion question and the mean is not, which is one reason to report 450 of 520.
  • No percentage mentioned u_dma. Five blocks expected, four reported. The population comes from the list.
  • 0 of 0 is a measurement hole, not a coverage hole, and 0.00% cannot distinguish them — only the denominator can.
  • A classifier with three labels and four cases called a 310-of-310 block partial. Enumerate the cases, order the tests, let exactly one fire.
  • Division by zero is fatal: three rows of four, rc=2, END never ran. Through a pipe the status reads as the pipeline's.
  • A next guard and the crash produced the same three rows and differed only in the exit status; the labelled version is the only one whose row count matches the input.
  • PROCINFO["sorted_in"] was accepted and sorted nothing, so a script claiming sorted output emitted for-in order with exit 0.
  • length(array) works here and is not POSIX. A missing gawk function is fatal; a missing gawk variable is a silent no-op.
  • Five byte-identical runs are not determinism. The for-in order changed under a different key prefix and when one key was added.
  • A gate whose population is the report cannot fail on a missing block — the worst regression passes.
  • Ten times faster is not available in the program. The whole in-program range is 1.66×; a selective prefilter gave 2.49× and merging passes 1.74×.
  • Review in a fixed order, because the mechanisable checks found three of six defects and every miss was about which records or which denominator.
  • A nested ternary is a syntax error when broken after the :. Use if/else if past two cases.
  • Prove the gate fires both ways — rc=1 with two blocks named, rc=0 on an all-passing file — and demonstrate every documented exit status: 2, 0 and 1 from three purpose-built inputs.
  • cmd > out 2> err, then rc=$? on its own line, then read them. Three explicit invocations beat a loop whose word-splitting is shell-dependent.

13. The End of the Track

This is chapter 90 of 90, and the thread that runs through all four parts is one sentence: a count summarises a set and cannot identify it.

Part I built the language. Part II built the field and record model and found that a missing field is not an unset variable. Part III turned it on real EDA output — timing, area, coverage, regression logs, pipelines — and found at every level that the aggregate stayed plausible while the selection, the denominator or the population underneath it was wrong. Part IV debugged that, reviewed it, measured it, and closes here.

The habits worth keeping are short:

  • Print the records, not the count. It is one print NR, and it is the only check that finds a disjoint selection.
  • Carry the denominator everywhere. A percentage without its numerator and denominator is not a measurement.
  • Enumerate inputs, not outputs. A script that counts what it found can never report what is missing.
  • Refuse. Exit 2 where nothing can be concluded, and never let an absence read as good news.
  • Prove the check can fail. A rule that never fires and a rule that always passes produce identical output.
  • Probe before you write. Every result in this module was run first, and the ones that were reasoned instead were wrong often enough to make the rule unconditional.

Where this fits

Part of the awk curriculum.