Skip to content
VLSI Mentor

awk Part III · Module 9 · Simulation and UVM Log Analysis

Extracting UVM Messages

Chapter 9.1 read the log's shape; this one takes it apart. A UVM record carries five pieces of structured information — severity, source location, simulation time, component hierarchy and the author's own message id — and one rule can extract all of them, replacing the four near-identical greps most people start with. The catch is that neither end of the record is at a fixed position: the message is free text so the field count varies, one record format omits the file entirely so the fields shift, and a UVM sequence hierarchy contains a double at-sign that silently defeats the obvious way to find the timestamp.

Intermediate16 min readawkPart IIIUVMParsingRegex

Part III · Module 9 · Chapter 9.2 · Extracting UVM Messages

1. Why This Matters

The usual starting point for log analysis is a pipeline of greps — one for errors, one for warnings, one for fatals — each printing whole lines for a human to read.

That works and it does not scale, for a specific reason: a whole line is not a record you can compute with. To count by message id, group by source file, or report the time of the first failure, the line has to be taken apart. One awk rule does that and does it once, which is both shorter and the only way to get the pieces.

It is rated critical because the obvious extraction is wrong in two ways that produce plausible output rather than an error — a word of prose where an id belongs, and a fragment of the message where the timestamp belongs.

2. One Rule Instead of Four Greps

The grep approach, for contrast:

Azvya Education Pvt. Ltd.VLSI Mentor
sim.log — synthetic teaching data from Chapter 9.1. Modelled on the UVM default report format.
Chronologic VCS simulator copyright 1991-2024
Compiler version X-2024.03; Runtime version X-2024.03;  Oct 12 14:30 2026
UVM_INFO @ 0: reporter [RNTST] Running test alu_smoke_test...
UVM_INFO ../tb/env/alu_env.sv(87) @ 0: uvm_test_top.env [BUILD] env built with 2 agents
UVM_INFO ../tb/seq/alu_seq.sv(42) @ 1000: uvm_test_top.env.agt.seqr@@seq [SEQ] starting 50 items
UVM_WARNING ../tb/env/alu_sb.sv(210) @ 15500: uvm_test_top.env.sb [UNUSED_CLK] clock gate never toggled
UVM_WARNING ../tb/env/alu_sb.sv(210) @ 16000: uvm_test_top.env.sb [UNUSED_CLK] clock gate never toggled
UVM_ERROR ../tb/env/alu_sb.sv(145) @ 20500: uvm_test_top.env.sb [MISMATCH] exp=32'hdead act=32'hbeef
UVM_ERROR ../tb/env/alu_sb.sv(145) @ 21000: uvm_test_top.env.sb [MISMATCH] exp=32'hcafe act=32'h0000
UVM_INFO ../tb/seq/alu_seq.sv(99) @ 30000: uvm_test_top.env.agt.seqr@@seq [SEQ] done
UVM_FATAL ../tb/env/alu_env.sv(300) @ 30500: uvm_test_top.env [DEADLOCK] no response for 500ns
--- UVM Report Summary ---
** Report counts by severity
UVM_INFO :    4
UVM_WARNING :    2
UVM_ERROR :    2
UVM_FATAL :    1
$finish called from file "../tb/top.sv", line 55.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -c UVM_ERROR sim.log
grep -c UVM_FATAL sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3
2

Three and two — both wrong, because the report summary lines match. Chapter 9.3 is about that; here it is enough to notice that four greps give four chances to make the same mistake and no way to relate their results.

One awk rule, with the positive record pattern from Chapter 9.1:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { n[$1]++ } END { for (s in n) printf "%-12s %d\n", s, n[s] | "sort" }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_ERROR    2
UVM_FATAL    1
UVM_INFO     4
UVM_WARNING  2

Correct, and the four counts now live in one array where they can be compared, totalled or weighted.

3. Why Position Does Not Work

Chapter 9.1 section 5 established the problem; here is what it does to an extraction. The fields of each record:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { printf "NF=%-3d $2=%-28s $5=%-30s $6=%s\n", NF, $2, $5, $6 }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
NF=8   $2=@                            $5=[RNTST]                        $6=Running
NF=11  $2=../tb/env/alu_env.sv(87)     $5=uvm_test_top.env               $6=[BUILD]
NF=9   $2=../tb/seq/alu_seq.sv(42)     $5=uvm_test_top.env.agt.seqr@@seq $6=[SEQ]
NF=10  $2=../tb/env/alu_sb.sv(210)     $5=uvm_test_top.env.sb            $6=[UNUSED_CLK]
NF=10  $2=../tb/env/alu_sb.sv(210)     $5=uvm_test_top.env.sb            $6=[UNUSED_CLK]
NF=8   $2=../tb/env/alu_sb.sv(145)     $5=uvm_test_top.env.sb            $6=[MISMATCH]
NF=8   $2=../tb/env/alu_sb.sv(145)     $5=uvm_test_top.env.sb            $6=[MISMATCH]
NF=7   $2=../tb/seq/alu_seq.sv(99)     $5=uvm_test_top.env.agt.seqr@@seq $6=[SEQ]
NF=10  $2=../tb/env/alu_env.sv(300)    $5=uvm_test_top.env               $6=[DEADLOCK]

Read the first row. $2 is @, $5 is [RNTST] and $6 is Running — everything is one position to the left, because that record has no FILE(LINE).

record with a filereporter record
$1severityseverity
$2file(line)@
$3@time:
$4time:hierarchy
$5hierarchy[id]
$6[id]message word

So $6 is the id on eight records and a word of prose on the ninth:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { printf "%-12s id-by-position=%s\n", $1, $6 }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     id-by-position=Running
UVM_INFO     id-by-position=[BUILD]
UVM_INFO     id-by-position=[SEQ]
UVM_WARNING  id-by-position=[UNUSED_CLK]
UVM_WARNING  id-by-position=[UNUSED_CLK]
UVM_ERROR    id-by-position=[MISMATCH]
UVM_ERROR    id-by-position=[MISMATCH]
UVM_INFO     id-by-position=[SEQ]
UVM_FATAL    id-by-position=[DEADLOCK]

4. Extracting by Pattern

The id is the text between the first [ and its ]. Two substitutions:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { id = $0; sub(/^[^[]*\[/, "", id); sub(/\].*$/, "", id); printf "%-12s id=%s\n", $1, id }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     id=RNTST
UVM_INFO     id=BUILD
UVM_INFO     id=SEQ
UVM_WARNING  id=UNUSED_CLK
UVM_WARNING  id=UNUSED_CLK
UVM_ERROR    id=MISMATCH
UVM_ERROR    id=MISMATCH
UVM_INFO     id=SEQ
UVM_FATAL    id=DEADLOCK

Nine for nine, including RNTST.

Reading the two patterns is worth a moment, because they are the general shape of this kind of extraction:

  • sub(/^[^[]*\[/, "", id) removes everything from the start up to and including the first [. The [^[]* is "any run of characters that are not [", which makes it stop at the first one rather than the last — the point section 5 is about.
  • sub(/\].*$/, "", id) removes the first ] and everything after it.

Work on a copy (id = $0), not on $0 itself. Chapter 3.6 established that assigning to $0 rebuilds the fields, so editing it in place would destroy $1 through $NF for any later rule.

5. The Greedy Match That Breaks

Now the finding that makes this chapter worth its rating. The obvious way to get the timestamp is to strip everything up to the @:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { t = $0; sub(/^.*@ */, "", t); sub(/:.*$/, "", t); printf "%-12s time=[%s]\n", $1, t }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     time=[0]
UVM_INFO     time=[0]
UVM_INFO     time=[seq [SEQ] starting 50 items]
UVM_WARNING  time=[15500]
UVM_WARNING  time=[16000]
UVM_ERROR    time=[20500]
UVM_ERROR    time=[21000]
UVM_INFO     time=[seq [SEQ] done]
UVM_FATAL    time=[30500]

Two records report a fragment of the message as the time.

The cause is in the hierarchy field. Isolating one record:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
echo 'UVM_INFO ../tb/seq/alu_seq.sv(42) @ 1000: uvm_test_top.env.agt.seqr@@seq [SEQ] starting' | awk '{ t = $0; sub(/^.*@ */, "", t); print "greedy result: [" t "]" }'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
greedy result: [seq [SEQ] starting]

uvm_test_top.env.agt.seqr@@seq contains @@ — which is UVM's own convention for naming a sequence within a sequencer's hierarchy. awk's regexes are greedy, so ^.*@ matches as much as possible and lands on the last @ in the line, not the first. Everything after it becomes the "time".

6. Two Correct Extractions

By pattern, using match() to locate the timestamp by its own shape:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { t = ""; if (match($0, /@ [0-9]+:/)) t = substr($0, RSTART + 2, RLENGTH - 3); printf "%-12s time=[%s]\n", $1, t }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     time=[0]
UVM_INFO     time=[0]
UVM_INFO     time=[1000]
UVM_WARNING  time=[15500]
UVM_WARNING  time=[16000]
UVM_ERROR    time=[20500]
UVM_ERROR    time=[21000]
UVM_INFO     time=[30000]
UVM_FATAL    time=[30500]

Nine for nine. match() sets RSTART and RLENGTH; the + 2 skips @ and the space, and the - 3 drops those two plus the trailing colon. Both variables are POSIX and present — Chapter 8.6 section 3 confirmed it.

match() finds the leftmost match, which is what makes this immune to a later @.

By field scan, finding the field that is exactly @:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { t = ""; for (i = 1; i <= NF; i++) if ($i == "@") { t = $(i + 1); sub(/:$/, "", t); break } printf "%-12s time=[%s]\n", $1, t }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     time=[0]
UVM_INFO     time=[0]
UVM_INFO     time=[1000]
UVM_WARNING  time=[15500]
UVM_WARNING  time=[16000]
UVM_ERROR    time=[20500]
UVM_ERROR    time=[21000]
UVM_INFO     time=[30000]
UVM_FATAL    time=[30500]

Also nine for nine, and it works for a different reason: $i == "@" requires the field to be exactly the at-sign, which seqr@@seq is not. The break stops at the first one.

Which to prefer is a judgement. match() is one line and does not care about field splitting, so it survives a change to FS. The field scan is easier to read and the break makes the leftmost-match property explicit. Both are correct; neither uses a greedy prefix.

7. Source File and Line

The file and line live in $2 on the records that have them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/)/ { fl = $2; sub(/\(/, ":", fl); sub(/\)/, "", fl); printf "%-12s %s\n", $1, fl }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     ../tb/env/alu_env.sv:87
UVM_INFO     ../tb/seq/alu_seq.sv:42
UVM_WARNING  ../tb/env/alu_sb.sv:210
UVM_WARNING  ../tb/env/alu_sb.sv:210
UVM_ERROR    ../tb/env/alu_sb.sv:145
UVM_ERROR    ../tb/env/alu_sb.sv:145
UVM_INFO     ../tb/seq/alu_seq.sv:99
UVM_FATAL    ../tb/env/alu_env.sv:300

Eight records — the reporter line is excluded because the pattern requires $2 to begin with . or /, and that record's $2 is @.

Converting (145) to :145 gives the file:line form editors can jump to, which Chapter 7.4 section 3 recommended.

The engineering caveat is worth stating because it is easy to misread: this is where the check lives, not where the bug is. alu_sb.sv:145 is the scoreboard's comparison statement. The defect is in the design or the stimulus that produced the mismatched value, and the file name tells you which checker fired rather than what is broken. Chapter 9.4 groups on it anyway, because "which checker fired" is exactly the right triage axis.

8. One Rule, Everything

Putting the pieces together — a single rule emitting a delimited record per message:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -v OFS='\t' '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ {
    sev = $1
    loc = ($2 ~ /^(\.|\/)/ ? $2 : "-"); sub(/\(/, ":", loc); sub(/\)/, "", loc)
    t = ""; if (match($0, /@ [0-9]+:/)) t = substr($0, RSTART + 2, RLENGTH - 3)
    id = $0; sub(/^[^[]*\[/, "", id); sub(/\].*$/, "", id)
    msg = $0; sub(/^[^]]*\] */, "", msg)
    print sev, t, id, loc, msg
}' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO	0	RNTST	-	Running test alu_smoke_test...
UVM_INFO	0	BUILD	../tb/env/alu_env.sv:87	env built with 2 agents
UVM_INFO	1000	SEQ	../tb/seq/alu_seq.sv:42	starting 50 items
UVM_WARNING	15500	UNUSED_CLK	../tb/env/alu_sb.sv:210	clock gate never toggled
UVM_WARNING	16000	UNUSED_CLK	../tb/env/alu_sb.sv:210	clock gate never toggled
UVM_ERROR	20500	MISMATCH	../tb/env/alu_sb.sv:145	exp=32'hdead act=32'hbeef
UVM_ERROR	21000	MISMATCH	../tb/env/alu_sb.sv:145	exp=32'hcafe act=32'h0000
UVM_INFO	30000	SEQ	../tb/seq/alu_seq.sv:99	done
UVM_FATAL	30500	DEADLOCK	../tb/env/alu_env.sv:300	no response for 500ns

Five clean columns, tab-separated, with - where a record has no file. That is a form Chapter 7.6's reasoning endorses: tabs rather than commas because the message text contains neither tabs nor the structure a CSV would need quoting for.

And it is now data. Everything in Chapters 9.3 to 9.6 operates on this shape, and a downstream consumer — a Python script, a spreadsheet, another awk — no longer needs to know the UVM format at all.

Two details in that rule. loc uses a ternary rather than a second pattern, so one rule still handles both record formats. And msg strips everything up to the first ] with [^]]*, which is the same bounded-class technique as section 4 rather than a greedy .*\].

9. Common Mistakes

Four greps instead of one rule. Four chances at the same mistake, and no way to relate the results.

Extracting the id as $6. The reporter record has no file, so $6 is a message word — section 3.

Counting fields from the right. The message is free text; NF varies 7 to 11.

sub(/^.*@ */, …) for the time. Greedy, and a sequence hierarchy contains @@ — section 5.

Expecting .*? to exist. POSIX awk has no non-greedy quantifier.

Describing what you discard rather than what you keep. ^.*@ lets the data decide; @ [0-9]+: does not.

Editing $0 in place to extract. It rebuilds the fields and breaks later rules — Chapter 3.6.

Reading file:line as the location of the bug. It is where the check lives — section 7.

A greedy .*\] for the message. Use [^]]* so it stops at the first bracket.

10. Exercises

Exercise 1 — Count by severity in one rule

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { n[$1]++ } END { for (s in n) printf "%-12s %d\n", s, n[s] | "sort" }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_ERROR    2
UVM_FATAL    1
UVM_INFO     4
UVM_WARNING  2

Exercise 2 — Debug an id extraction

A triage table contains a signature called Running.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { printf "%-12s %s\n", $1, $6 }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     Running
UVM_INFO     [BUILD]
UVM_INFO     [SEQ]
UVM_WARNING  [UNUSED_CLK]
UVM_WARNING  [UNUSED_CLK]
UVM_ERROR    [MISMATCH]
UVM_ERROR    [MISMATCH]
UVM_INFO     [SEQ]
UVM_FATAL    [DEADLOCK]

The reporter record has no file(line), so $6 is the first word of its message.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { id = $0; sub(/^[^[]*\[/, "", id); sub(/\].*$/, "", id); printf "%-12s %s\n", $1, id }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     RNTST
UVM_INFO     BUILD
UVM_INFO     SEQ
UVM_WARNING  UNUSED_CLK
UVM_WARNING  UNUSED_CLK
UVM_ERROR    MISMATCH
UVM_ERROR    MISMATCH
UVM_INFO     SEQ
UVM_FATAL    DEADLOCK

Exercise 3 — Debug a time extraction

A report shows message text in its time column.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { t = $0; sub(/^.*@ */, "", t); sub(/:.*$/, "", t); printf "%-12s [%s]\n", $1, t }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     [0]
UVM_INFO     [0]
UVM_INFO     [seq [SEQ] starting 50 items]
UVM_WARNING  [15500]
UVM_WARNING  [16000]
UVM_ERROR    [20500]
UVM_ERROR    [21000]
UVM_INFO     [seq [SEQ] done]
UVM_FATAL    [30500]

The two sequence records contain seqr@@seq, and ^.*@ is greedy so it matched to the last at-sign.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { t = ""; if (match($0, /@ [0-9]+:/)) t = substr($0, RSTART + 2, RLENGTH - 3); printf "%-12s [%s]\n", $1, t }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     [0]
UVM_INFO     [0]
UVM_INFO     [1000]
UVM_WARNING  [15500]
UVM_WARNING  [16000]
UVM_ERROR    [20500]
UVM_ERROR    [21000]
UVM_INFO     [30000]
UVM_FATAL    [30500]

Exercise 4 — Errors with their location and time

Report every error and fatal with its time, id and source location.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(ERROR|FATAL) (\.|\/|@)/ { t = ""; if (match($0, /@ [0-9]+:/)) t = substr($0, RSTART + 2, RLENGTH - 3); id = $0; sub(/^[^[]*\[/, "", id); sub(/\].*$/, "", id); loc = $2; sub(/\(/, ":", loc); sub(/\)/, "", loc); printf "%-10s @%-7s %-12s %s\n", $1, t, id, loc }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_ERROR  @20500   MISMATCH     ../tb/env/alu_sb.sv:145
UVM_ERROR  @21000   MISMATCH     ../tb/env/alu_sb.sv:145
UVM_FATAL  @30500   DEADLOCK     ../tb/env/alu_env.sv:300

Three records, ordered by time as they appeared in the log.

Exercise 5 — Engineering application

Convert the log into a delimited record stream another tool can consume, and verify the conversion against the log's own report summary.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk -v OFS='\t' '
/^--- UVM Report Summary ---/ { insum = 1 }
insum && /^UVM_[A-Z]+ *:/ { line = $0; gsub(/ /, "", line); split(line, p, ":"); claimed[p[1]] = p[2]; next }
insum { next }
/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ {
    counted[$1]++
    loc = ($2 ~ /^(\.|\/)/ ? $2 : "-"); sub(/\(/, ":", loc); sub(/\)/, "", loc)
    t = ""; if (match($0, /@ [0-9]+:/)) t = substr($0, RSTART + 2, RLENGTH - 3)
    id = $0; sub(/^[^[]*\[/, "", id); sub(/\].*$/, "", id)
    msg = $0; sub(/^[^]]*\] */, "", msg)
    print $1, t, id, loc, msg > "records.tsv"
}
END {
    bad = 0
    for (s in claimed) if (counted[s] + 0 != claimed[s] + 0) { printf "DISAGREE %s: parsed %d, log says %s\n", s, counted[s] + 0, claimed[s] > "/dev/stderr"; bad++ }
    printf "%d record(s) written; %d severity mismatch(es)\n", counted["UVM_INFO"] + counted["UVM_WARNING"] + counted["UVM_ERROR"] + counted["UVM_FATAL"], bad
    if (bad) exit 1
}' sim.log
echo "status: $?"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
9 record(s) written; 0 severity mismatch(es)
status: 0
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
head -3 records.tsv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO	0	RNTST	-	Running test alu_smoke_test...
UVM_INFO	0	BUILD	../tb/env/alu_env.sv:87	env built with 2 agents
UVM_INFO	1000	SEQ	../tb/seq/alu_seq.sv:42	starting 50 items

Nine records extracted, written to a TSV, and cross-checked against the log's own counts before the script claims success — Chapter 9.1 section 6's device, now guarding a real conversion rather than a demonstration.

The exit status makes it usable in a flow: a disagreement means the parser missed a record format, which is a reason to stop rather than to hand a downstream tool an incomplete file.

Exercise 6 — Challenge: prove the extraction is position-independent

A pattern-based extraction should survive a change that breaks a positional one. Show it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '{ sub(/^UVM_INFO ..\/tb\/env\/alu_env.sv\(87\) /, "UVM_INFO "); print }' sim.log > shifted.log
awk 'NR == 4 { print }' shifted.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO @ 0: uvm_test_top.env [BUILD] env built with 2 agents

Record 4 has had its file(line) removed, so now two records lack a file.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { printf "%-12s %s\n", $1, $6 }' shifted.log | head -3
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     Running
UVM_INFO     env
UVM_INFO     [SEQ]

The positional extraction now produces two bad ids — Running and env.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { id = $0; sub(/^[^[]*\[/, "", id); sub(/\].*$/, "", id); printf "%-12s %s\n", $1, id }' shifted.log | head -3
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     RNTST
UVM_INFO     BUILD
UVM_INFO     SEQ

The pattern-based extraction is unchanged — RNTST, BUILD, SEQ, all correct.

That is the property worth having and the way to test for it: modify the input in a way the format permits, and check which extraction still works. The positional version degrades once per affected record, silently; the pattern version does not degrade at all. The same argument appeared in Chapter 8.5 section 7 — feed the script data on which the two candidate behaviours differ.

11. Summary

  • One rule replaces four greps, and puts the counts in one array where they can be related.
  • A UVM record has five structured pieces plus free text: severity, file(line), time, hierarchy, [id].
  • The reporter record has no file, so every field shifts and $6 is a message word rather than an id.
  • NF varies 7 to 11, so nothing can be counted from the right either.
  • Extract the id with [^[]*\[ and \].*$ on a copy of $0, never on $0 itself.
  • A greedy ^.*@ returns part of the message, because a UVM sequence hierarchy contains @@.
  • POSIX awk has no non-greedy quantifier. Match what you want, not what you discard.
  • match() with RSTART and RLENGTH finds the leftmost match and is immune to a later delimiter.
  • A field scan for $i == "@" works for a different reason — exact equality excludes seqr@@seq.
  • file:line is where the check lives, not where the bug is.
  • Emit a delimited record stream and everything downstream stops needing to know the UVM format.

The records are now data. Chapter 9.3 — Counting Warnings and Errors reduces them to the two or three numbers a triage actually reads — and shows why the obvious count is wrong, why anchoring does not fix it, and why a count of message lines is not a count of failures.

Where this fits

Part of the awk curriculum.