Tcl · Module 24
Parsing a Power Report
One report, three different units, no error. This is the chapter where dropping a two-character string inverts the engineering conclusion.
Intermediate19 min readtcledapart-iireportspowerunits
Part II · Module 24 · Chapter 24.3 · Parsing a Power Report
Timing reports are all in nanoseconds. Area reports are all in library units. A power report is not all in one unit, and that single fact is this chapter.
1. What the report actually says
=== the four lines that matter, verbatim ===
'Cell Internal Power = 8.4213 mW (62%)'
'Net Switching Power = 5.1680 mW (38%)'
'Total Dynamic Power = 13.5893 mW (100%)'
'Cell Leakage Power = 742.3100 uW'Three lines in mW. One line in uW.
That is not a quirk of the mock report used here — it is the normal shape, because the report declares its dynamic and leakage units separately in its own header:
=== the unit declarations in the report header ===
Power-specific unit information :
Dynamic Power Units = 1mW (derived from V,C,T units)
Leakage Power Units = 1pWDynamic power and leakage power differ by three to six orders of magnitude in a modern design, so a single unit would print either as a long string of zeros or in exponential notation. Reports solve that by using different units per quantity, and by saying so in a header block most people never read.
Note also that the declared leakage unit is 1pW while the leakage summary line prints uW. Both are true: the declaration governs the group table at the bottom of the report, and the summary line carries its own suffix. A parser that reads the declaration and applies it everywhere would be wrong, and a parser that reads suffixes and ignores the declaration cannot interpret the group table at all. §5 is about that split.
2. The parser that drops them
proc parse_power_naive {path} {
set fh [open $path r] ; set p [dict create]
while {[gets $fh line] >= 0} {
if {[regexp {^\s*Cell Internal Power\s+=\s+(\d+\.?\d*)} $line -> v]} { dict set p internal $v }
if {[regexp {^\s*Net Switching Power\s+=\s+(\d+\.?\d*)} $line -> v]} { dict set p switching $v }
if {[regexp {^\s*Cell Leakage Power\s+=\s+(\d+\.?\d*)} $line -> v]} { dict set p leakage $v }
}
close $fh ; return $p
}Nothing in it is wrong as Tcl. The patterns are anchored, braced and correct; each field is found; regexp returns 1 three times.
=== parsed WITHOUT the units — every number looks comparable ===
internal 8.4213
switching 5.1680
leakage 742.3100
naive total : 755.8993 <- leakage counted as if it were mW
and leakage would be 98.2% of itThis is the same failure shape as Chapter 24.1 §7's truncated slack: a successful match producing a plausible wrong number. There the cause was a regex rule; here it is throwing away part of the value. Both are invisible to every check except arithmetic that knows what the answer should be.
3. Keep the unit with the number
array set ::SCALE {W 1.0 mW 1e-3 uW 1e-6 nW 1e-9 pW 1e-12}
proc to_watts {value unit} {
if {![info exists ::SCALE($unit)]} {
error "unknown power unit '$unit'" {} [list RPT BADUNIT $unit]
}
return [expr {$value * $::SCALE($unit)}]
}proc parse_power {path} {
set fh [open $path r] ; set p [dict create]
set fields {
{internal {^\s*Cell Internal Power\s+=\s+(\d+\.?\d*)\s*([munpW]+)}}
{switching {^\s*Net Switching Power\s+=\s+(\d+\.?\d*)\s*([munpW]+)}}
{leakage {^\s*Cell Leakage Power\s+=\s+(\d+\.?\d*)\s*([munpW]+)}}
}
while {[gets $fh line] >= 0} {
foreach f $fields {
set key [lindex $f 0] ; set re [lindex $f 1]
set v "" ; set u ""
if {[regexp $re $line -> v u]} {
dict set p $key [list $v $u [to_watts $v $u]]
}
}
}
close $fh ; return $p
}=== parsed WITH the unit kept alongside ===
FIELD VALUE UNIT WATTS
internal 8.4213 mW 0.008421300
switching 5.1680 mW 0.005168000
leakage 742.3100 uW 0.000742310
TOTAL 0.014331610
=== the same question, answered correctly ===
total power : 14.3316 mW
leakage share : 5.2%
(the unit-blind parser said 98.2%)Four design decisions in that parser, each worth stating.
The unit is captured, not assumed. ([munpW]+) reads the suffix as part of the field. A parser that hard-codes "leakage is in µW" is correct until a tool release or a different setup changes it, and then it is wrong in exactly the silent way §2 describes.
Each entry stores all three forms — {742.3100 uW 0.00074231}. The original value and unit are kept so a report can be reproduced verbatim, and the normalised watts figure is kept so arithmetic is possible. Throwing away either one loses something: the raw pair cannot be summed, and the watts figure alone cannot be printed back in the units the engineer expects.
Normalisation happens once, at parse time. The alternative — converting at each use — means every consumer has to remember, and one that forgets reintroduces §2. Converting on entry makes the invariant "everything inside this dict is watts" true by construction.
The patterns live in a list of {key regex} pairs. Adding a field is one line rather than one if. The foreach over $fields with lindex is the Part I shape; note the set v ""/set u "" before each regexp, which is Chapter 13.9's rule — a failed match leaves the capture variables holding the previous line's values, so in a loop they must be cleared or a non-matching line silently reuses stale data.
4. The parse checks itself again
total power : 14.3316 mWCompare that against the report's own bottom line, which the parser never read:
=== the report's own total row ===
Total 8.4213 mW 5.1680 mW 7.4231e+05 pW 14.3316 mW14.3316 mW, to the last digit. Independently computed from three fields in two different units, and it lands exactly on the figure the tool printed.
That is Chapter 24.2 §4's free cross-check doing more work than it did there. In the area report the sum validated field extraction. Here it validates the unit conversion as well, and the margins are wide enough to be unmistakable:
=== what a WRONG leakage scale would give as the total ===
leakage scaled as uW -> total 14.3316 mW
leakage scaled as nW -> total 13.5900 mW
leakage scaled as pW -> total 13.5893 mW
leakage scaled as mW -> total 755.8993 mWOnly one row reproduces the report. Every wrong scale is caught, and no pattern test could have caught any of them — the patterns match identically in all four cases.
Worth noticing which way the errors fall. Scaling leakage too small (nW, pW) gives 13.59 mW — within 5% of the truth, which is close enough to look like a rounding difference and be accepted. Scaling it too large (mW) gives 755.9 mW, which is absurd enough that someone would notice. The dangerous direction is the one that produces a believable number, and that is the direction a cross-check against the report's own total catches and eyeballing does not.
Whenever a report states a total beside its components, compute it and compare. It is the cheapest correctness test available and it needs no reference run.
5. What a unit table cannot fix
Being honest about the limits, because a conversion table invites more trust than it earns.
A report that omits the unit. The group table at the bottom of this report prints bare numbers under Internal Power and Switching Power columns, with the unit stated only in the header block and the total row. A pattern reading those columns gets a float and no suffix, and the only thing that makes it interpretable is the declared Dynamic Power Units — which must be parsed from the header and applied. Capturing the unit from the field works for = 8.4213 mW lines and not for table columns.
A scale that is per-report, not universal. Leakage Power Units = 1pW is a setting. Another run of the same design with a different library can declare different units, so a hard-coded table is a statement about one report. The robust version reads the declaration — and the obvious way to write that pattern does not work:
regexp {Power Units\s*=\s*1(\S+?)\s} $line -> u ;# one of these fails
regexp {Power Units\s*=\s*1([a-zA-Z]+)} $line -> u ;# the other does not=== reading the declaration: one pattern works on both lines, one does not ===
Dynamic Power Units \S+?-then-\s : 1 'mW' [a-zA-Z]+ : 1 'mW'
Leakage Power Units \S+?-then-\s : 0 '' [a-zA-Z]+ : 1 'pW'The general form of the lesson: a pattern that relies on what comes after the field is making an assumption about the rest of the line, and the last line of a block, the last field of a row, and the last line of a file are exactly where that assumption fails.
Exponential notation. The group table's leakage column reads 7.4231e+05. Tcl's expr handles that string as a number without special effort, but a pattern of (\d+\.?\d*) does not capture it whole:
=== the exponent the numeric pattern silently truncates ===
plain : 1 -> '7.4231'
exp ok : 1 -> '7.4231e+05'It matched — 1 — and captured 7.4231, stopping at the e and dropping five orders of magnitude. The fix allows an exponent:
{(\d+\.?\d*(?:[eE][-+]?\d+)?)}(?:…) is a non-capturing group, so the exponent is part of the number rather than a separate capture — which matters because -> v binds capture groups positionally, and an extra group would shift every variable after it.
This is the third failure in this chapter with the same signature: regexp returns 1, a number comes back, and the number is wrong. A successful match is not evidence that the right thing was matched.
6. Refuse an unknown unit
=== an unrecognised unit is refused, not guessed ===
1 / unknown power unit 'MW'to_watts raises on a suffix it does not know, rather than defaulting to 1.0 or to the last unit seen.
That is the Chapter 23.6 argument applied to arithmetic: the discipline you can rely on is the one the code enforces. A conversion function with a fallback is a conversion function that will eventually convert something wrongly and tell nobody — and MW versus mW is exactly the kind of difference that produces a nine-order-of-magnitude error from a one-character change.
The errorCode is RPT BADUNIT MW, so a caller can distinguish "this report uses a unit I have not been taught" from a genuine parse failure and extend the table, which is Chapter 14.2's reason for machine-readable error codes.
7. Exercises
Exercise 1. Why does one power report use more than one unit?
Exercise 2. The unit-blind parser reported leakage as 98.2% of total power. What is the real figure, and why is the wrong one dangerous rather than merely inaccurate?
Exercise 3. Why capture the unit from each line instead of hard-coding "leakage is in µW"?
Exercise 4. Why store the raw value, the unit and the watts figure?
Exercise 5. Why normalise at parse time rather than at each use?
Exercise 6. Why are set v "" and set u "" needed before each regexp inside the loop?
Exercise 7. The computed total matched the report's own 14.3316 mW. What does that check that the area-report version did not, and which wrong scale would have been hardest to notice?
Exercise 8. Why does (\d+\.?\d*) mishandle 7.4231e+05, and what does it return?
Exercise 9. Why should to_watts raise on an unknown unit rather than defaulting?
Where this fits
Part of the Tcl curriculum.
