Skip to content
VLSI Mentor

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

Understanding Simulation Logs

Part III applies the whole of Parts I and II to real semiconductor data, and it starts where every log-parsing job should start: reading the file's shape before writing a single pattern. A simulation log is not a uniform stream of records. It has a tool banner nobody wants, a body of UVM message records that is the actual data, and a report summary at the end that is both a trap for a careless pattern and — used deliberately — the one piece of ground truth that lets a parser check its own arithmetic.

Intermediate16 min readawkPart IIIUVMLog AnalysisVerification

Part III · Module 9 · Chapter 9.1 · Understanding Simulation Logs

1. Why This Matters

Parts I and II built the tools. Part III points them at the files an engineer actually has, and the first of those is a simulation log.

The temptation with a log is to write the pattern immediately — /UVM_ERROR/ and count. Chapter 9.3 shows what that returns and why it is wrong, and the reason is in this chapter: the log is not all one kind of line. Until you know which regions exist, a pattern that looks obviously correct will match things you did not mean.

So this chapter has no awk one-liner as its centrepiece. It has a file, read carefully.

2. A Log, Whole

Here is the fixture the rest of the module uses:

Azvya Education Pvt. Ltd.VLSI Mentor
sim.log — synthetic teaching data. Modelled on the UVM default report format; the banner and $finish lines are illustrative only.
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
wc -l < sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      18

Eighteen lines. Nine of them are message records. The other nine are not, and that ratio is typical — a short real log is mostly not data.

3. The Three Regions

One log fanning out into three regions. On the left, a single simulation log. On the right, three regions: the tool banner and compile output at the top, the body of UVM message records in the middle, and the report summary with the finish line at the bottom.One simulation log18 lines in the fixtureBanner and compiletool-specific; usuallynoiseUVM message recordsthe data — 9 of 18 linesReport summaryclaims about the middle12
Figure 1 — a simulation log in three regions. Only the middle one holds message records; the first is noise for most purposes, and the last is a set of claims about the middle that a parser can verify itself against. A pattern written without this distinction matches across all three, which is the cause of the counting error in Chapter 9.3.

Finding the boundary is one pattern:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^--- UVM Report Summary ---/ { printf "summary starts at line %d of %d\n", NR, 18; exit }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
summary starts at line 12 of 18

So lines 1–2 are the preamble, 3–11 the records, 12–18 the summary. Counting the regions directly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^--- UVM Report Summary ---/ { insum = 1 } insum { tail++; next } /^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { rec++; next } { head++ } END { printf "preamble=%d  records=%d  summary+tail=%d  (total %d)\n", head, rec, tail, NR }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
preamble=2  records=9  summary+tail=7  (total 18)

The three numbers sum to 18, which is Chapter 8.3 section 6's arithmetic check — every line accounted for by exactly one category.

4. The Record Format

A message record, decomposed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_ERROR ../tb/env/alu_sb.sv(145) @ 20500: uvm_test_top.env.sb [MISMATCH] exp=32'hdead act=32'hbeef
└───┬───┘ └──────────┬──────────┘   └─┬─┘  └────────┬───────┘ └───┬────┘ └──────────┬──────────┘
 severity        file(line)          time        hierarchy        id            message

Five pieces of structured information plus free text. Each is worth something different:

  • severity — UVM_INFO, UVM_WARNING, UVM_ERROR, UVM_FATAL. The triage axis.
  • file(line) — where the check lives, not where the bug is. Chapter 9.4 groups on it.
  • time — simulation time, which orders events and is not wall-clock.
  • hierarchy — which component reported. uvm_test_top.env.sb is the scoreboard.
  • id — the string the testbench author chose, in brackets. The most useful grouping key, because it is deliberate.
  • message — free text, often containing the actual values.

The id deserves emphasis. A verification engineer wrote [MISMATCH] on purpose, so it names a kind of failure in the project's own vocabulary. Grouping on it is grouping the way the testbench's author intended, which is why Chapter 9.4 prefers it over anything parsed out of the message text.

5. The Field Count Varies

The format is regular and the field count is not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { printf "NF=%-3d %s\n", NF, substr($0, 1, 60) }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
NF=8   UVM_INFO @ 0: reporter [RNTST] Running test alu_smoke_test..
NF=11  UVM_INFO ../tb/env/alu_env.sv(87) @ 0: uvm_test_top.env [BUI
NF=9   UVM_INFO ../tb/seq/alu_seq.sv(42) @ 1000: uvm_test_top.env.a
NF=10  UVM_WARNING ../tb/env/alu_sb.sv(210) @ 15500: uvm_test_top.e
NF=10  UVM_WARNING ../tb/env/alu_sb.sv(210) @ 16000: uvm_test_top.e
NF=8   UVM_ERROR ../tb/env/alu_sb.sv(145) @ 20500: uvm_test_top.env
NF=8   UVM_ERROR ../tb/env/alu_sb.sv(145) @ 21000: uvm_test_top.env
NF=7   UVM_INFO ../tb/seq/alu_seq.sv(99) @ 30000: uvm_test_top.env.
NF=10  UVM_FATAL ../tb/env/alu_env.sv(300) @ 30500: uvm_test_top.en

Seven to eleven fields for records of the same format, because the message is free text and contributes a variable number of whitespace-separated words.

That rules out one whole approach immediately. Anything counted from the right — $NF, $(NF-1) — lands on a different part of the message on every record. Chapter 3.2 made $NF the idiom for "the last field"; here the last field is a word of prose.

And there is a worse case:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR == 3 || NR == 4 { printf "line %d: $2=[%s] $3=[%s]\n", NR, $2, $3 }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
line 3: $2=[@] $3=[0:]
line 4: $2=[../tb/env/alu_env.sv(87)] $3=[@]

Line 3 has no file. A message issued by the reporter component before any file context exists omits the FILE(LINE) piece entirely, so every field after the severity shifts by one.

So positional extraction is wrong in two independent ways: the right end moves with the message length, and the left end moves depending on whether a file is present. Chapter 9.2 is about extracting by pattern instead.

6. The Summary Is Ground Truth

The last region is the part most parsers ignore and the most useful thing in the file.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^--- UVM Report Summary ---/ { insum = 1; next } insum && /^UVM_[A-Z]+ *:/ { gsub(/ /, ""); split($0, p, ":"); printf "%-12s log claims %s\n", p[1], p[2] }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     log claims 4
UVM_WARNING  log claims 2
UVM_ERROR    log claims 2
UVM_FATAL    log claims 1

UVM counts its own messages and prints the totals. So the log contains an independent statement of what a parser should find — and comparing the two is the strongest self-check available:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^--- UVM Report Summary ---/ { insum = 1; next } insum && /^UVM_[A-Z]+ *:/ { gsub(/ /, ""); split($0, p, ":"); claimed[p[1]] = p[2] } !insum && /^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { counted[$1]++ } END { for (s in claimed) printf "%-12s counted=%d  log says=%d  %s\n", s, counted[s] + 0, claimed[s], (counted[s] + 0 == claimed[s] + 0 ? "agree" : "DISAGREE") }' sim.log | sort
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_ERROR    counted=2  log says=2  agree
UVM_FATAL    counted=1  log says=1  agree
UVM_INFO     counted=4  log says=4  agree
UVM_WARNING  counted=2  log says=2  agree

Four for four. The parser's count and the log's own count agree on every severity.

7. What to Check in Your Own Logs

The method, as a short list to run against a real log before writing anything:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk 'NR <= 3 { printf "%d: %s\n", NR, substr($0, 1, 70) } NR == 4 { exit }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1: Chronologic VCS simulator copyright 1991-2024
2: Compiler version X-2024.03; Runtime version X-2024.03;  Oct 12 14:30 2
3: UVM_INFO @ 0: reporter [RNTST] Running test alu_smoke_test...

Where do the records start? Read the first few lines. The preamble length is tool-specific and sometimes hundreds of lines.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_/ { n[$1]++ } END { for (s in n) printf "%-14s %d line(s) begin with this\n", s, n[s] | "sort" }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_ERROR      3 line(s) begin with this
UVM_FATAL      2 line(s) begin with this
UVM_INFO       5 line(s) begin with this
UVM_WARNING    3 line(s) begin with this

Does a bare severity pattern over-count? It does here — 3 errors where there are 2, which is Chapter 9.3's subject and visible in one command.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^--- / || /^\*\* / || /^\$finish/ { printf "%d: %s\n", NR, $0 }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
12: --- UVM Report Summary ---
13: ** Report counts by severity
18: $finish called from file "../tb/top.sv", line 55.

What structural markers exist? These are the lines a parser uses to find region boundaries, and their exact text is tool-specific.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { if (NF > mx) mx = NF; if (mn == 0 || NF < mn) mn = NF } END { printf "record field count ranges %d to %d\n", mn, mx }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
record field count ranges 7 to 11

How much does NF vary? A wide range confirms that positional extraction from the right is unsafe.

8. Common Mistakes

Writing the pattern before reading the file. The log has regions; a pattern does not know that.

Assuming the whole file is records. Nine of eighteen lines here.

Counting with a bare /UVM_ERROR/. The report summary matches it — Chapter 9.3.

Anchoring and assuming that fixes it. The summary line also starts with the severity.

Using $NF or $(NF-1). The message is free text, so the field count varies — section 5.

Assuming every record has a file. A reporter message does not, and the fields shift — section 5.

Ignoring the report summary. It is an independent count of what you are trying to parse — section 6.

Requiring the report summary. A log that died has none, and those matter most.

Treating these fixtures as a real tool's format. The record shape is UVM's; everything around it varies — section 1.

9. Exercises

Exercise 1 — Find the regions

Report how many lines are preamble, records and summary.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^--- UVM Report Summary ---/ { insum = 1 } insum { tail++; next } /^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { rec++; next } { head++ } END { printf "preamble=%d records=%d summary=%d total=%d sum=%d\n", head, rec, tail, NR, head + rec + tail }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
preamble=2 records=9 summary=7 total=18 sum=18

The categories sum to the line count, so nothing was missed.

Exercise 2 — Decompose a record

Print the severity and the raw second field for every record, showing which records lack a file.

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

One record in nine has no file — enough to break any positional script.

Exercise 3 — Measure the field-count spread

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { n[NF]++ } END { for (k in n) printf "NF=%s on %d record(s)\n", k, n[k] | "sort -t= -k2 -n" }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
NF=7 on 1 record(s)
NF=8 on 3 record(s)
NF=9 on 1 record(s)
NF=10 on 3 record(s)
NF=11 on 1 record(s)

Five distinct field counts across nine records — and the two most common, 8 and 10, account for only six of them. There is no modal width to rely on.

Exercise 4 — Read the log's own summary

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^--- UVM Report Summary ---/ { insum = 1; next } insum && /^UVM_[A-Z]+ *:/ { gsub(/ /, ""); split($0, p, ":"); printf "%-12s %s\n", p[1], p[2] }' sim.log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_INFO     4
UVM_WARNING  2
UVM_ERROR    2
UVM_FATAL    1

gsub(/ /, "") strips all spaces so split on : gives clean halves — the summary's column alignment uses a variable number of spaces.

Exercise 5 — Engineering application

Write a log profiler: given a log you have not seen, report its regions, its record format spread, and whether a bare severity pattern would over-count.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '
/^--- UVM Report Summary ---/ { insum = 1 }
insum                         { tail++ }
insum && /^UVM_[A-Z]+ *:/     { gsub(/ /, ""); split($0, p, ":"); claimed[p[1]] = p[2] }
insum                         { next }
/^UVM_(INFO|WARNING|ERROR|FATAL) (\.|\/|@)/ { rec++; counted[$1]++; if (NF > mx) mx = NF; if (!mn || NF < mn) mn = NF; next }
/^UVM_/                       { strays++; next }
                              { head++ }
END {
    printf "lines=%d  preamble=%d  records=%d  summary=%d  stray UVM_ lines=%d\n", NR, head + 0, rec + 0, tail + 0, strays + 0
    printf "record NF ranges %d..%d\n", mn + 0, mx + 0
    for (s in claimed) {
        bare = counted[s] + strays_by[s]
        printf "  %-12s parsed=%d  log claims=%s  %s\n", s, counted[s] + 0, claimed[s], (counted[s] + 0 == claimed[s] + 0 ? "agree" : "DISAGREE")
    }
}' sim.log | sort
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  UVM_ERROR    parsed=2  log claims=2  agree
  UVM_FATAL    parsed=1  log claims=1  agree
  UVM_INFO     parsed=4  log claims=4  agree
  UVM_WARNING  parsed=2  log claims=2  agree
lines=18  preamble=2  records=9  summary=7  stray UVM_ lines=0
record NF ranges 7..11

Everything a reader needs before writing a real pattern: the region sizes, the field-count spread, and a four-way agreement with the log's own counts.

Note stray UVM_ lines=0: the rule /^UVM_/ { strays++ } catches any line beginning with a severity that the record pattern rejected outside the summary. Zero here means the positive pattern did not miss a record format — and a non-zero value would be the signal to widen it.

The engineering caveat this whole module carries: these counts are of message records, not of defects. UVM_ERROR : 2 means the report server printed two error messages, which Chapter 9.3 shows can be one bug or two.

Exercise 6 — Challenge: make the cross-check fail

A self-check that has never failed is a comment. Break the record pattern and confirm the cross-check notices.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
awk '/^--- UVM Report Summary ---/ { insum = 1; next } insum && /^UVM_[A-Z]+ *:/ { gsub(/ /, ""); split($0, p, ":"); claimed[p[1]] = p[2] } !insum && /^UVM_(INFO|WARNING|ERROR|FATAL) \.\./ { counted[$1]++ } END { for (s in claimed) printf "%-12s counted=%d  log says=%d  %s\n", s, counted[s] + 0, claimed[s], (counted[s] + 0 == claimed[s] + 0 ? "agree" : "DISAGREE") }' sim.log | sort
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
UVM_ERROR    counted=2  log says=2  agree
UVM_FATAL    counted=1  log says=1  agree
UVM_INFO     counted=3  log says=4  DISAGREE
UVM_WARNING  counted=2  log says=2  agree

Narrowing the pattern to require a relative path (\.\.) dropped the reporter record, and the cross-check caught it: UVM_INFO counted=3, log says 4.

That is the check doing exactly its job — it found a record format the pattern did not cover, which is the failure mode no amount of reading the script would reveal. Three of the four severities still agree, so the disagreement even localises the problem to UVM_INFO.

The lesson carries to any log format: a parser should agree with something the file asserts about itself. Where no such assertion exists, Chapter 9.5 builds the substitute — comparing the set of logs against the set of tests that were supposed to run.

10. Summary

  • Read the log's shape before writing a pattern. It has regions, and a pattern does not know that.
  • Three regions: tool preamble, UVM message records, report summary and tail. Nine of eighteen lines here are records.
  • The record format is SEVERITY FILE(LINE) @ TIME: HIERARCHY [ID] message, which is a UVM library convention rather than a tool behaviour.
  • The id in brackets is the best grouping key, because a verification engineer chose it deliberately.
  • NF varies — 7 to 11 here — because the message is free text. Never count fields from the right.
  • A reporter record has no file, so the fields shift. Never extract by position.
  • The report summary is the log's own count of the records you are parsing — an independent second measurement, free.
  • Cross-check against it when present, and do not require it: a log that died has none, and those matter most.
  • A count of records is not a count of defects.
  • Everything except the record shape is tool-specific. Profile your own logs with section 7's commands.

You can now read a log before parsing it. Chapter 9.2 — Extracting UVM Messages does the parsing: pulling severity, file, line, id and message out of a record with one rule instead of four near-identical greps, and doing it by pattern so the reporter record and the free-text message cannot shift the result.

Where this fits

Part of the awk curriculum.