Skip to content
VLSI Mentor

Perl Part II · Module 18 · Log Parsing and Text Processing

Extracting a Value: Time, Seed, Count, Path

Once a parser knows which lines matter, the next job is getting the numbers out of them, and the capture itself is the easy part — one set of parentheses. The hard part is the line that matched the severity pattern and then did not contain the field you wanted, because Perl's behaviour there is genuinely surprising: a failed match does not clear the previous capture. This chapter measures what that does to a real extraction, and the answer is worse than a missing value. It is the previous line's value, reported confidently in the wrong column, in a form plausible enough that no downstream check can tell.

Intermediate13 min readPerlPart IILog Parsing

Part II · Module 18 · Chapter 18.5 · Extracting a Value: Time, Seed, Count, Path

1. The Four Fields

Nearly every value a flow script pulls out of a log is one of four shapes.

A simulation time. @ 2400 or at time 2400 ps. An integer, sometimes with a unit, and the unit matters — Chapter 14.7 measured that '3.5ns' + 1 is 4.5, with the unit silently discarded.

A seed. +ntb_random_seed=1234 or seed=1234. An integer you must preserve exactly, because it is how a failure is reproduced.

A count. UVM_ERROR : 1, 412 modules. An integer, and Chapter 18.4 showed the count line needs distinguishing from a message line.

A hierarchical path. uvm_test_top.env.sb or u_alu/A. A string with separators, and the one field you must not treat as a number.

The patterns are unremarkable:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my $TIME = qr/\@\s*(\d+)/;
my $SEED = qr/(?:ntb_random_seed|seed)\s*=\s*(\d+)/;
my $PATH = qr/pin=(\S+)/;
my $INST = qr/\b((?:\w+\.)+\w+)\b/;          # a dotted UVM path

What the chapter is about is the line that matches the severity and then does not contain one of these.

2. A Failed Match Does Not Clear the Capture

Azvya Education Pvt. Ltd.VLSI Mentor
stale-capture.pl
use strict;
use warnings;

# three lines: the second has no simulation time, the third has no pin field
my @line = (
 'UVM_ERROR tb/sb.sv(151) @ 2400: [SB] pin=u_alu/A slack=-0.03',
 'UVM_ERROR tb/sb.sv(152) @ : [SB] pin=u_alu/B slack=-0.07',
 'UVM_ERROR tb/sb.sv(153) @ 9600: [SB] no pin field here',
);
my $TIME = qr/\@\s*(\d+)/;
my $PIN  = qr/pin=(\S+)/;

print "reading \$1 after each match, WITHOUT testing whether it matched:\n";
printf "  %-5s %-12s %s\n", "line", "time", "pin";
for my $i (0 .. $#line) {
    my $l = $line[$i];
    $l =~ $TIME;  my $t = $1;      # no test -- $1 may be from an earlier line
    $l =~ $PIN;   my $p = $1;
    printf "  %-5d %-12s %s\n", $i + 1,
        (defined $t ? $t : "undef"), (defined $p ? $p : "undef");
}

print "\nthe same three lines, each capture in LIST context:\n";
printf "  %-5s %-12s %s\n", "line", "time", "pin";
for my $i (0 .. $#line) {
    my $l = $line[$i];
    my ($t) = $l =~ $TIME;         # a failed match gives an empty list
    my ($p) = $l =~ $PIN;
    printf "  %-5d %-12s %s\n", $i + 1,
        (defined $t ? $t : "(absent)"), (defined $p ? $p : "(absent)");
}

print "\nline 2 has no time; line 3 has no pin.\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
reading $1 after each match, WITHOUT testing whether it matched:
  line  time         pin
  1     2400         u_alu/A
  2     u_alu/A      u_alu/B
  3     9600         9600

the same three lines, each capture in LIST context:
  line  time         pin
  1     2400         u_alu/A
  2     (absent)     u_alu/B
  3     9600         (absent)

line 2 has no time; line 3 has no pin.

Read the first table. Line 1 is correct. Then:

Line 2 reports time=u_alu/A. The time pattern did not match, because @ : has no digits. So $1 was never assigned, and it still held u_alu/A — the pin from line 1. A hierarchical path is now the simulation time.

Line 3 reports pin=9600. The pin pattern did not match. $1 still held 9600, the time captured from the same line a moment earlier. A timestamp is now a pin name.

Both wrong values are plausible. A report with a pin name in the time column and a number in the pin column does not look corrupt; it looks like data. Nothing downstream can detect it, which makes this Chapter 14.1's third kind of failure, produced by two missing ifs.

The second table shows the fix working: line 2 reports the time as absent and keeps the correct pin; line 3 keeps the correct time and reports the pin as absent.

3. The List-Context Capture

The fix measured above is one character of punctuation:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my ($time) = $line =~ $TIME;        # parentheses on the LEFT

A match in list context returns the captures, or an empty list when it fails — so $time becomes undef rather than keeping a previous value. Chapter 11.10 is where the captures themselves are taught.

Three variants, each appropriate somewhere:

One field, list context. As above. The cleanest form when you want "the value or nothing".

Several fields at once.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my ($file, $ln, $time) = $line =~ /^UVM_\w+\s+(\S+)\((\d+)\)\s+\@\s*(\d+)/;

All or nothing: a line that does not match the whole shape leaves all three undefined, which is usually the right semantics — a half-parsed line is not useful.

Guarded with an if, when you want to act only on a match:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (my ($time) = $line =~ $TIME) { ... }

Careful with that one: it tests the captured value, so a legitimate capture of 0 is false and the branch does not run. For a simulation time of 0 — the first line of every UVM log — that matters. The safe form tests the match itself:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if ($line =~ $TIME) { my $time = $1; ... }     # fine: $1 read immediately

which is correct because the read is inside the branch that proved the match succeeded.

Named captures are worth the extra characters once there are more than two fields:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if ($line =~ /^UVM_(?<sev>\w+)\s+(?<file>\S+)\((?<ln>\d+)\)\s+\@\s*(?<time>\d+)/) {
    my %f = %+;                                 # copy out immediately
    ...
}

Chapter 11.11 covers %+. Two notes from experience: copy %+ into your own hash before doing anything else that might match, for exactly the staleness reason above; and a named capture that did not participate is absent from %+ rather than present-and-undef, so exists $f{time} is the test.

4. Validating What You Captured

A capture succeeding does not mean the value is usable, and three checks are worth making at the point of extraction.

Is it the right shape? \d+ already guarantees digits, so a $TIME capture is safe to use numerically. But a looser pattern is not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my ($time) = $line =~ /\@\s*(\S+)/;        # \S+ captures "2400:" or "ns"

That captures the colon too, and "2400:" + 0 is 2400 with no warning under no warnings, or a warning you may not see. Chapter 14.7 measured '3.5ns' + 1 giving 4.5. Capture exactly what you mean: (\d+) for an integer, (-?\d+(?:\.\d+)?) for a slack value.

Is it in range? A simulation time of 0 is normal; a seed of 0 is normal; a slack of -9999 usually means the tool printed a sentinel rather than a measurement. Range-check where you know the range, and say what you found rather than silently clamping it.

Did you get all of them? This is the one people miss. A line containing two pin fields gives you only the first with a single capture:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my @pins = $line =~ /pin=(\S+)/g;          # /g in list context: ALL of them
printf STDERR "line %d: %d pin fields, expected 1\n", $., scalar @pins
    if @pins > 1;

A log line with two of a field you assumed was unique is a sign the format changed — which is Chapter 18.8's subject, and noticing it here is much cheaper than discovering it from a wrong total.

5. What To Remember

  • Four field shapes cover nearly everything: a simulation time, a seed, a count, a hierarchical path.
  • A hierarchical path is the one field never to treat as a number.
  • Measured: reading $1 unguarded reported a pin name as the time and a timestamp as the pin.
  • A failed match leaves $1 UNCHANGED, not undefined — the usual summary of this is wrong.
  • So the bug does not reproduce on a one-line test; it needs an earlier successful match, which is why it reaches production.
  • Both wrong values are plausible, so nothing downstream can detect them.
  • Capture in list context — my ($time) = $line =~ $TIME; — and a failed match gives undef.
  • Capture several fields at once for all-or-nothing semantics on a line shape.
  • if (my ($t) = ...) tests the VALUE, so a legitimate 0 is false. Test the match and read $1 inside the branch instead.
  • Use named captures past two fields, copy %+ out immediately, and test with exists.
  • Capture exactly what you mean — (\d+), not (\S+) which takes the trailing colon.
  • Range-check where you know the range, and report rather than clamp.
  • Use /g in list context to count occurrences and warn when a field you assumed unique appears twice — that is a format change (18.8).
  • Treat the seed as a string: never %d it, never drop a leading zero, preserve it verbatim for the reproduction command.

Chapter 18.6 takes on the record that does not fit on one line. A UVM message with a continuation and an STA path block are both single facts spread over several lines, and a line-at-a-time loop has to be taught to accumulate.

Where this fits

Part of the Perl curriculum.