Skip to content

UVM

Logging Optimization

The reporting system as a cross-cutting performance concern — why logging costs CPU to construct, I/O to write, and storage to keep, so verbose-by-default logging dominates runtime and storage; the cardinal disciplines that a disabled log must cost nothing (guard expensive construction behind the verbosity check) and the system must be quiet by default and detailed on demand (run regression at low verbosity, turn it up only for the component under debug); logging to file not console, avoiding per-message flushing, and bounding log size so the reporting system doesn't become the bottleneck.

Simulation Performance Optimization · Module 28 · Page 28.3

The Engineering Problem

Logging sits at the intersection of runtime and memory — it costs CPU to construct, I/O to write, and storage to keep — and it's the single most common performance problem in a UVM environment, because it's paid on every message on every transaction and it feels free. The previous chapters showed logging as a runtime bottleneck (28.1, a filtered message's construction dominating runtime) and touched it as a memory cost (28.2). This chapter confronts the reporting system as a performance concern in its own right: a testbench that logs verbosely pays three costsconstructing the message ($sformatf, convert2string — CPU), writing it (to console or file — I/O, often the slowest part), and accumulating it (a log file that grows to tens of gigabytes, filling the disk and taking forever to write and search) — millions of times, for messages nobody reads. The two traps are opposite: under-control on the disabled side — a filtered message that still costs its construction (28.1's trap), so "off" logging isn't free — and over-volume on the enabled side — running regression at a verbosity that prints per-transaction detail, so the log is slow to produce (I/O-bound), huge to store (disk-filling), and useless to read (drowned in noise). The problem this chapter solves is logging optimization: the three costs of logging, the cardinal disciplinesa disabled log must cost nothing (guard construction) and the system must be quiet by default and detailed on demand (verbosity as a volume knob) — and the I/O specifics (file not console, no per-message flush, bounded size) that keep the reporting system from becoming the bottleneck it so often is.

Logging optimization is controlling the reporting system's three costsconstruction (CPU), I/O (write), and storage (disk/memory) — so logging costs nothing when disabled and the system is quiet by default, detailed on demand. The three costs, paid per message: construction (building the message string — $sformatf, convert2string — which runs before the verbosity filter, so it's paid even when the message is discarded — 28.1's trap); I/O (writing the message — to console (especially slow) or file — often the dominant cost when messages do print); and storage (the log accumulating — a multi-gigabyte file that's slow to write, store, and search). The cardinal disciplines: a disabled log must cost nothingguard expensive construction behind a verbosity check (uvm_report_enabled) so the $sformatf only runs when the message will print; and quiet by default, detailed on demandrun regression at low verbosity (errors and key events only), and turn verbosity up only for the component under debug (ID/hierarchy-scoped), never globally in regression. The I/O specifics: log to file, not console (console rendering is slow); avoid per-message flushing (forcing a disk sync each message is slow); bound log size. The cardinal failure: verbose-by-default logging that drowns the simulation — per-transaction messages printing in regression, paying all three costs millions of times for messages nobody reads. This chapter is logging optimization: the three costs, cost-nothing-when-disabled, quiet-by-default, the I/O specifics, and not drowning the sim.

Why does logging cost CPU to construct, I/O to write, and storage to keep — so verbose-by-default logging dominates runtime and storage — and how do you make a disabled log cost nothing (guard construction) and keep the system quiet by default and detailed on demand (verbosity as a volume knob), so the reporting system isn't the bottleneck?

Motivation — why logging is the bottleneck it so often is

Logging feels free — it's just a print — but it's paid per message on every transaction, across three resources, and over-logging is the single most common performance problem. The reasons:

  • Three costs, paid per message, on every transaction. Each uvm_info constructs (CPU), writes (I/O), and accumulates (storage) — and in a per-transaction log, that's millions of times. Three costs × millions is why logging dominates.
  • "Off" logging isn't free by default. A filtered message still constructs its string (the macro builds it before checking verbosity — 28.1's trap), so a disabled log costs its construction. Turning verbosity down doesn't eliminate the construction cost of expensive messages.
  • Over-logging is I/O-bound, so compute doesn't help. When messages do print, the bottleneck is I/O (writing) and storage (disk) — so adding farm capacity (compute) doesn't help, which misleads teams into scaling the wrong resource.
  • A drowned log is useless to read. Beyond the performance cost, over-logging defeats logging's purpose: the one error you need is buried in millions of routine lines, so the log can't be used to debugworse than less logging.
  • The right balance is quiet-by-default. Too little logging means re-running to debug a failure; too much means slow, huge, useless. The balanceerrors and key events always, per-transaction detail on demand — must be designed, not defaulted to verbose.

The motivation, in one line: logging is the most common performance problem because it's paid per message across three resources (construction, I/O, storage) on every transaction, "off" isn't free (construction runs anyway), over-logging is I/O-bound (so compute doesn't help) and useless to read (drowned) — so you must make disabled logs cost nothing and keep the system quiet by default, detailed on demand.

Mental Model

Hold logging as a CCTV surveillance system — record enough to investigate later, but recording everything at max resolution fills storage, saturates bandwidth, and makes review impossible:

You run a surveillance system so that, if something goes wrong, you have footage to investigate what happened. The footage is valuable — without it, you'd have to re-stage the incident to understand it, which you often can't. But recording has real costs: bandwidth and processing to capture and encode the video, and storage to keep it. So the naive approach — every camera recording at maximum resolution, every frame, all the time, kept forever — fails in three ways at once. It saturates the network and the encoders, it fills the storage drives in days, and, most ironically, it makes the footage useless: when an incident finally happens, you have millions of hours of mostly-empty hallway footage to wade through, and the ten seconds that matter are buried in it. The well-designed system is the opposite. By default, cameras record at low resolution, or only on motion, capturing just enough to know something happened. When you're actively watching a specific area — investigating a live situation — you crank that one camera to high resolution, and only that one. You retain footage for a bounded window and roll off the old. And critically, you never capture footage you're going to immediately throw away: a camera that records full-resolution video and then deletes it because nobody's watching has paid the entire cost — bandwidth, encoding, the write — for nothing. The motion sensor decides before the camera records, not after. The whole discipline is: record little by default, record a lot only where and when you're looking, keep a bounded amount, and never pay to capture what you'll discard. You run a surveillance system so that, if something goes wrong, you have footage to investigate. The footage is valuablewithout it, you'd have to re-stage the incident. But recording has real costs: bandwidth and processing to capture, and storage to keep. So the naive approachevery camera at maximum resolution, every frame, all the time, kept foreverfails three ways at once: it saturates the network and encoders, fills the storage in days, and, most ironically, makes the footage uselessthe ten seconds that matter are buried in millions of hours of empty-hallway footage. The well-designed system is the opposite: by default, low resolution or motion-triggered (just enough to know something happened); when actively investigating, you crank one camera to high resolution, only that one; you retain a bounded window; and critically, you never capture footage you'll immediately throw away — a camera that records then deletes because nobody's watching has paid the entire cost for nothing; the motion sensor decides before the camera records, not after. The discipline: record little by default, a lot only where and when you're looking, keep a bounded amount, and never pay to capture what you'll discard.

So logging is a CCTV system: the log messages are footage, logging is recording, reading the log to debug a failure is reviewing footage to investigate an incident, and the three costs are the CCTV's: construction (encoding the video — CPU), I/O (the write/bandwidth), and storage (the drives — disk). The disciplines map exactly: record low-resolution by default (regression at low verbosity — errors and key events only); crank one camera high only when watching (verbosity up only for the component under debug, ID/hierarchy-scoped); retain a bounded window (bounded log size, file not console); and — the key onenever capture footage you'll discard (guard expensive message construction behind the verbosity check, so a filtered message isn't built then thrown away, exactly like a camera that records-then-deletes). The cardinal failure is the naive CCTV: every camera at max resolution always (regression at high verbosity) — saturating bandwidth (I/O-bound), filling storage (disk), and burying the incident (the one error drowned). Run logging like a well-designed surveillance system: low-resolution by default, high only where you're looking, bounded retention, and never pay to capture what you'll discard — because recording everything at max resolution saturates the system and buries the one thing you needed to see. Record little by default; never capture what you'll throw away.

Visual Explanation — the three costs and the controls

The defining picture is the three costs of loggingconstruction, I/O, storage — and the controls that bound each.

Three costs of logging and their controlsVerbosity — the volume knob over all threequiet by default (regression at low verbosity, errors and key events only); turned up only for the component under debugquiet by default (regression at low verbosity, errors and key events only); turned up only for the component under debugConstruction (CPU) — guard itbuilding the string (sformatf/convert2string) runs before the filter, so it costs even when discarded — guard behind a verbosity checkbuilding the string (sformatf/convert2string) runs before the filter, so it costs even when discarded — guard behind a verbosity checkI/O (write) — file not console, no flushwriting to console is especially slow; log to file, avoid per-message flushingwriting to console is especially slow; log to file, avoid per-message flushingStorage (disk) — bound the sizethe log accumulating to gigabytes is slow to write, store, and search — bound log size and verbositythe log accumulating to gigabytes is slow to write, store, and search — bound log size and verbosity
Figure 1 — the three costs of logging and the controls that bound them. Construction (CPU): building the message string with sformatf and convert2string, which runs before the verbosity filter, so it costs even when the message is discarded — controlled by guarding expensive construction behind a verbosity check. I/O (write): writing the message to console (especially slow) or file — controlled by logging to file not console and avoiding per-message flushing. Storage (disk/memory): the log accumulating to gigabytes, slow to write, store, and search — controlled by bounding log size and verbosity. Over it all, verbosity is the volume knob: quiet by default for regression, turned up only for the component under debug. Verbose-by-default logging pays all three costs on every message on every transaction.

The figure shows the three costs and the controls. Verbosity (the brand-coloredthe volume knob over all three): quiet by default (regression at low verbosity, errors and key events only); turned up only for the component under debug. Construction (CPU) — guard it (the warning-colored): building the string runs before the filter, so it costs even when discardedguard behind a verbosity check. I/O (write) — file not console, no flush (success-colored): writing to console is especially slow; log to file, avoid per-message flushing. Storage (disk) — bound the size (default-colored): the log accumulating to gigabytes is slow to write, store, and searchbound log size and verbosity. The crucial reading is that logging is not one cost but three, each paid per message, and each with its own control — and verbosity sits above all three as the master control (it determines whether a message is processed at all). The warning-colored construction is highlighted because it's the counterintuitive cost: it's paid even when the message doesn't print (the string is built before the filter), so turning verbosity down doesn't eliminate it for expensive messages — it must be separately guarded. The I/O and storage costs, by contrast, are paid only when the message prints — so they're controlled by verbosity (don't print it) and by where/how it's written (file not console, no flush, bounded). The relationship is that verbosity controls I/O and storage (a filtered message isn't written or stored) but not construction (a filtered message's string is still built) — which is exactly why construction needs a separate guard. The diagram is the logging cost model: verbosity over construction (CPU), I/O (write), storage (disk), each with its control — and verbose-by-default pays all three millions of times. Logging costs construction (CPU, paid even when filtered — guard it), I/O (write — file not console, no flush), and storage (disk — bound it), all under verbosity as the master volume knob.

RTL / Simulation Perspective — quiet by default, guarded, file not console

In code, the disciplines are concrete: guard expensive construction, set regression verbosity low, scope verbosity to the component under debug, log to file. The example shows them.

logging disciplines: guard construction, quiet by default, scope verbosity, file not console
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// === COST-NOTHING-WHEN-DISABLED: guard expensive construction behind the verbosity check ===
//   the message is built BEFORE uvm_info checks verbosity → guard it so $sformatf only runs if it prints
if (uvm_report_enabled(UVM_HIGH, UVM_INFO, "DRV"))
  `uvm_info("DRV", $sformatf("txn=%s", req.convert2string()), UVM_HIGH)   // built only when it would print
 
// === QUIET BY DEFAULT: errors and key events are always visible; per-transaction detail is HIGH ===
`uvm_error("SB", "data mismatch")                         // UVM_NONE-ish: always shown — the signal
`uvm_info("AGENT", "reset complete", UVM_LOW)             // key event: shown in regression
`uvm_info("DRV", $sformatf("txn=%s", ...), UVM_HIGH)      // per-transaction: HIDDEN in regression (verbosity LOW)
 
// === RUN REGRESSION AT LOW VERBOSITY (not high) ===
//   regression:  +UVM_VERBOSITY=UVM_LOW     → only errors + key events print → quiet, fast, small log
//   ✗ leaving +UVM_VERBOSITY=UVM_HIGH in the regression script prints per-txn detail → drowns the sim (DebugLab)
 
// === DETAILED ON DEMAND: scope verbosity to the component under debug, not globally ===
//   debug one failing test/component without flooding everything:
//   +uvm_set_verbosity=uvm_test_top.env.agent.driver,_ALL_,UVM_HIGH,time,0   // just the driver, at HIGH
//   (or in code: driver.set_report_verbosity_level(UVM_HIGH);)
 
// === I/O: log to FILE, not console; avoid per-message flush ===
//   console output is the slow path (terminal rendering); in regression, redirect to a file
//   the report server can write to a file; avoid forcing a disk sync on every message
 
// === RESULT: regression is quiet (small fast log); a failing test is re-run with scoped HIGH verbosity ===

The code shows the disciplines. Cost-nothing-when-disabled: guard the expensive constructionif (uvm_report_enabled(UVM_HIGH, ...)) before the uvm_info, so the $sformatf/convert2string only runs when the message would print. Quiet by default: uvm_error (always shown — the signal) and a key-event uvm_info at UVM_LOW (shown in regression) are visible, but per-transaction detail at UVM_HIGH is hidden in regression (verbosity LOW). Run regression at low verbosity: +UVM_VERBOSITY=UVM_LOW (only errors + key eventsquiet, fast, small log); leaving UVM_HIGH in the regression script prints per-txn detaildrowns the sim (the DebugLab). Detailed on demand: scope verbosity to the component under debug+uvm_set_verbosity=...driver,_ALL_,UVM_HIGH,... (just the driver, at HIGH), not globally. I/O: log to file, not console (console is the slow path); avoid per-message flush. The shape to carry: guard expensive construction (cost-nothing-when-disabled), make errors/key-events always-visible but per-transaction detail HIGH-only (quiet by default), run regression at LOW and scope HIGH to the component under debug (detailed on demand), and log to file without per-message flush (I/O). The combination makes regression quiet, fast, and small, while a failing test is re-run with scoped HIGH verbosity to get the detail. Guard expensive construction, run regression at low verbosity with errors and key events always visible, scope high verbosity to the component under debug, and log to file without per-message flush.

Verification Perspective — over-logging is I/O-bound, so compute doesn't help

The defining surprise of over-logging is that it's I/O- and storage-bound, not compute-bound — so the intuitive fix (more compute) doesn't help. Seeing which resource over-logging saturates clarifies why you reduce logging, not add cores.

Over-logging is I/O-bound, not compute-boundmessage on every transactionmessage onevery…I/O saturates (waiting on writes)I/O saturates(waiting on…storage fills (GB logs)storagefills (GB…adding coresdoesn't helplow verbosity removes the costlow verbosityremoves the…Regression at HIGHverbosityper-transaction messages printI/O + storage per messagewrite + keep, millions of timesI/O saturatedsim waits on writesDisk fillingGB log filesMore cores: no helpcores wait on I/ORegression at LOWverbosityerrors + key events onlyFast, small logI/O + storage cost removed12
Figure 2 — over-logging saturates I/O and storage, not compute, so adding compute doesn't help. When regression runs at high verbosity, a message prints on every transaction, and each printed message pays I/O to write and storage to keep. Across millions of transactions and thousands of tests, the I/O bandwidth saturates (the simulation waits on writes) and the storage fills (gigabyte log files fill the disk). The bottleneck is I/O and storage, not compute — so adding farm capacity, more cores, does nothing, because the cores sit waiting on I/O. The fix is to reduce the logging (run at low verbosity), which removes the I/O and storage cost directly. Scaling compute against an I/O-bound problem is the wrong resource.

The figure shows over-logging saturating I/O and storage, not compute. When regression runs at high verbosity, a message prints on every transaction, and each printed message pays I/O to write and storage to keep. Across millions of transactions and thousands of tests, the I/O bandwidth saturates (the simulation waits on writes) and the storage fills (gigabyte log files fill the disk). The verification insight is that the bottleneck is I/O and storage, not compute — so adding farm capacity (more cores) does nothing, because the cores sit waiting on I/O. This is the classic "wrong resource" mistake: a slow regression intuitively suggests "need more compute", so teams add cores — but if the bottleneck is I/O (writing logs), more cores don't help (they're idle, blocked on writes), and the disk keeps filling regardless. The warning high-verbosity → default per-message → warning I/O-saturated/disk-filling path shows the I/O-and-storage saturation, with "more cores: no help" making the wrong-resource point explicit; the success low-verbosity → fast-small-log path shows the right fixreduce the logging, which removes the I/O and storage cost directly. The crucial point is the diagnostic distinction: a compute-bound slowness responds to more cores; an I/O-bound slowness doesn't — and over-logging is I/O-bound, so the fix is to log less (lower verbosity, file not console, no flush), not to scale compute. The way to tell is to look at where the time goes (the simulation waiting on writes) and whether the disk is filling — both signatures of I/O/storage-bound logging, not compute. The diagram is the wrong-resource trap: over-logging saturates I/O and storage, so adding compute doesn't help — the fix is to reduce logging. Over-logging is I/O- and storage-bound, not compute-bound, so adding cores doesn't help — the simulation waits on writes and the disk fills; the fix is to log less (low verbosity, file not console), not to scale compute.

Runtime / Execution Flow — diagnosing a logging bottleneck

At run time, diagnosing a logging bottleneck follows a measure-then-control discipline: recognize the I/O/storage signature, identify the cost, apply the control. The flow shows it.

Diagnosing a logging bottlenecksignature: slow regression + GB logs + disk filling + compute doesn't help (I/O-bound) → identify the cost (construction vs I/O+storage) → apply the control (guard construction, low regression verbosity, file not console, no flush) → re-measure (small fast log) → debug: scoped high verbosity on a re-runsignature: slow regression + GB logs + disk filling + compute doesn't help (I/O-bound) → identify the cost (construction vs I/O+storage) → apply the control (guard construction, low regression verbosity, file not console, no flush) → re-measure (small fast log) → debug: scoped high verbosity on a re-run1Recognize the I/O/storage signatureslow regression, GB log files, disk filling, more compute doesn'thelp — I/O- and storage-bound, not compute.2Identify the costconstruction (built even when filtered), I/O and storage (printingbecause verbosity is high), or both.3Apply the controlguard construction; regression verbosity low; file not console; noper-message flush.4Re-measure → debug on demandlog small and fast; for a failure, re-run with verbosity scopedhigh to the component under debug.
Figure 3 — diagnosing a logging bottleneck. Recognize the signature: a regression that is slow and produces gigabyte log files, where the simulation waits on writes and the disk fills, and more compute doesn't help — that is I/O- and storage-bound logging, not compute. Identify the cost: is it construction (expensive messages built even when filtered), I/O and storage (messages printing because verbosity is high), or both. Apply the control: guard expensive construction behind a verbosity check; set regression verbosity low so per-transaction messages don't print; log to file not console and avoid per-message flushing. Re-measure: the log should be small and fast, with the simulation no longer waiting on writes. For debug, re-run the failing test with verbosity scoped high to just the component under investigation.

The flow shows the logging diagnostic. Recognize (step 1): slow regression, GB log files, disk filling, more compute doesn't helpI/O- and storage-bound, not compute. Identify (step 2): construction (built even when filtered), I/O and storage (printing because verbosity is high), or both. Control (step 3): guard construction; regression verbosity low; file not console; no per-message flush. Re-measure → debug on demand (step 4): log small and fast; for a failure, re-run with verbosity scoped high to the component under debug. The runtime insight is the distinction between the two cost regimes: construction cost is paid even when filtered (so it's present regardless of verbosity — fixed by guarding), while I/O and storage cost is paid only when printing (so it's driven by verbosity — fixed by lowering it). The signature distinguishes which: if the log is huge (GB files, disk filling), the messages are printingI/O/storagelower verbosity; if the log is small but the sim is still slow in a hot loop, the construction is the cost even at low verbosityguard it. So you identify the regime before applying the control. The re-measure confirms: a correctly-controlled logging scheme produces a small, fast log with the sim no longer waiting on writes. And the debug-on-demand step preserves logging's value: you didn't lose the detail — you moved it to on-demand (re-run the failing test with scoped high verbosity on the component under investigation), so you get full detail when you need it without paying for it in regression. The brand (signature) → success (identify/control) → warning (re-measure/debug) flow shows the controls applied and the value preserved. The flow is the logging diagnostic: I/O-bound signature → identify the cost regime → apply the control → re-measure → debug on demand. Diagnose a logging bottleneck by its I/O-and-storage signature, identify whether the cost is construction or printing, apply the matching control (guard, lower verbosity, file not console), and preserve debuggability by re-running failures with scoped high verbosity.

Waveform Perspective — a message on every transaction drowns the signal

The over-logging failure is visible: a message is written on every transaction, burying the one error that matters in a floodunlike a quiet default where only errors and key events write. The waveform shows the flood.

At high verbosity a message is written every transaction, burying the one error that matters

12 cycles
At high verbosity a message is written every transaction, burying the one error that mattershigh verbosity: a log message is written on EVERY transaction (log_written)high verbosity: a log …I/O on every transaction saturates writes; storage fills with routine detailI/O on every transacti…the one error that matters (err) is written too — but buried in the floodthe one error that mat…at low verbosity, log_written would fire only on err — sparse, fast, readableat low verbosity, log_…clktxnlog_writtenerrt0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — a message on every transaction drowns the signal. At high verbosity in regression, a log message is written on every transaction (log_written pulses every cycle), so the log fills with routine per-transaction detail. The one event that matters — an error (err) — is written too, but it is buried in the flood of routine messages, and the constant writing saturates I/O and fills storage. At low verbosity, log_written would fire only on errors and key events, so the err pulse would stand alone, the log would be small and fast, and the simulation would not wait on writes. The signature of over-logging is a log write on every transaction; the signature of healthy logging is sparse writes that stand out.

The waveform shows a message on every transaction drowning the signal. At high verbosity in regression, a log message is written on every transaction (log_written pulses every cycle), so the log fills with routine per-transaction detail. The one event that matters — an error (err) — is written too, but it's buried in the flood of routine messages, and the constant writing saturates I/O and fills storage. The crucial reading is the contrast between log_written (constant) and err (sparse): log_written fires every cycle (the flood — every transaction logged), while err fires once (the signal — the thing you actually need). At high verbosity, the signal is drowned in the flood: the err pulse is one line among millions of routine lines, impossible to find, and the flood costs I/O and storage on every transaction. At low verbosity, log_written would fire only on err (and key events) — so the err pulse would stand alone, the log would be small and fast, and the simulation wouldn't wait on writes. The picture to carry is that over-logging has two costs at once: a performance cost (log_written every cycleI/O + storage on every transaction) and a usability cost (the signal err buried in the flood) — so over-logging is bad on both axes: it slows and fills the system and makes the log useless to read. Reading the waveform this way — is a message written on every transaction, or only on the events that matter?distinguishes over-logging (constant log_written) from healthy logging (sparse log_written, standing out). The log write on every transaction is the signature of over-logging; sparse writes that stand out is the signature of healthy logging. A log write on every transaction is the signature of over-logging — it saturates I/O and storage and buries the one error in the flood; healthy logging writes sparsely (errors and key events), so the signal stands out and the log is small and fast.

DebugLab — the regression drowned by a debug verbosity left in the run script

A regression that was slow and filled the disk because high debug verbosity was left in the run script

Symptom

A team's nightly regression had become painfully slow and was filling the disk — individual test log files were tens of gigabytes, the shared scratch storage was running out of space nightly, and the regression took far longer than the compute time of the tests should have required. The team's first interpretation was compute: the DUT had grown, the test count had grown, so naturally the regression needed more horsepower — and they requested and added farm capacity (more cores). The regression didn't get faster. The disk kept filling. Adding compute had done nothing, which was confusingmore cores should make a slow thing faster. Looking closer at where the time went, the team found the simulations were spending enormous time waiting on log writes, and the gigabyte logs were full of per-transaction debug messagestxn=... lines, one per transaction, millions per test. The regression was not compute-bound at all; it was drowning in its own logging.

Root cause

A high debug verbosity setting (+UVM_VERBOSITY=UVM_HIGH) had been left in the regression run scriptleftover from a debug session — so per-transaction debug messages printed on every transaction of every test, paying I/O and storage on every message, making the regression I/O- and storage-bound (so adding compute didn't help) and the logs gigabytes of routine detail:

why a debug verbosity left in the regression script drowned it (and compute didn't help)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
✗ HIGH verbosity in the regression run script — per-transaction messages PRINT on every transaction:
  // run script (leftover from debugging):  +UVM_VERBOSITY=UVM_HIGH
  // every `uvm_info(..., UVM_HIGH)` in every per-transaction loop now PRINTS
  // → millions of messages/test × thousands of tests → GB logs, I/O-bound sim, disk filling
  // → team added COMPUTE (more cores) → NO HELP: the bottleneck is I/O + storage, not compute
  //   (cores sit idle waiting on log writes; the disk fills regardless)
 
✓ LOW verbosity in regression; HIGH scoped on demand for a failing test:
  // regression run script:  +UVM_VERBOSITY=UVM_LOW    → only errors + key events print
  //   → small fast logs, sim not waiting on writes, disk not filling
  // to debug a specific failure, RE-RUN that test with scoped high verbosity on the component:
  //   +uvm_set_verbosity=uvm_test_top.env.agent.driver,_ALL_,UVM_HIGH,time,0
  //   → full detail for the driver, only on the re-run, only where you're looking
 
  // (also: log to FILE not console, and guard expensive $sformatf construction behind a verbosity check)

This is the over-logging / high-verbosity-in-regression bug — a cardinal logging-performance failure, and the wrong-resource trap. A high debug verbosity (+UVM_VERBOSITY=UVM_HIGH) had been left in the regression run script, leftover from a debug session where someone needed the per-transaction detail. In regression, that setting made every UVM_HIGH message print — and since per-transaction debug messages are at UVM_HIGH, every transaction logged a line: millions per test, thousands of tests, gigabytes of routine detail. The consequences hit all three logging costs at scale: construction (building each line), I/O (writing each line — the dominant cost, saturating writes so the sim waited), and storage (the gigabyte logs filling the disk). The wrong-resource trap followed: the slow regression looked compute-bound ("bigger DUT, more tests, need more cores"), so the team added compute — which did nothing, because the bottleneck was I/O and storage, not compute (the added cores sat idle waiting on log writes, and the disk filled regardless). The deep error was not recognizing the I/O/storage signature: gigabyte logs + disk filling + sim waiting on writes + compute not helping is the unmistakable signature of over-logging, not a compute shortage — and the cause was visible in the logs (per-transaction lines) and the run script (UVM_HIGH). The fix is quiet by default, detailed on demand: set the regression run script to UVM_LOW (only errors and key events printsmall fast logs, no I/O saturation, no disk filling), and to debug a specific failure, re-run that test with verbosity scoped high to the relevant component (+uvm_set_verbosity=...driver,...,UVM_HIGH,...) — full detail, only on the re-run, only where you're lookingplus log to file not console and guard expensive construction. The general lesson, and the chapter's thesis: logging must be quiet by default and detailed on demand — run regression at low verbosity, and turn verbosity up only for the component under debug — because running regression at high verbosity prints per-transaction detail on every transaction, paying all three logging costs (construction, I/O, storage) millions of times for messages nobody reads, making the regression I/O- and storage-bound (so adding compute doesn't help) and the logs gigabytes of useless detail; a debug verbosity left in the regression script is a common cause, so recognize the I/O/storage signature (gigabyte logs, disk filling, compute not helping) as over-logging, not a compute shortage. A slow regression with gigabyte logs is drowning in its own logging, not short of compute — set regression verbosity low and scope high verbosity to the component under debug.

Diagnosis

The tell is a slow regression with gigabyte logs and a filling disk, where more compute doesn't help. Diagnose over-logging:

  1. Check the I/O and storage signature. Gigabyte log files, a filling disk, and the simulation waiting on writes point to I/O- and storage-bound logging, not a compute shortage.
  2. Check the regression verbosity setting. A high verbosity left in the run script prints per-transaction detail; regression should run at low verbosity.
  3. Look at the log contents. Per-transaction routine lines, millions per test, mean per-transaction messages are printing when they shouldn't.
  4. Confirm compute doesn't help. If adding cores doesn't speed up the regression, the bottleneck is I/O or storage, which means logging, not compute.
Prevention

Quiet by default, detailed on demand:

  1. Run regression at low verbosity. Set the regression run script to a low verbosity so only errors and key events print, keeping logs small and fast.
  2. Scope high verbosity to the component under debug. To debug a failure, re-run that test with verbosity raised only for the relevant component, not globally.
  3. Log to file, not console, and avoid per-message flush. Console rendering and per-message disk syncs are slow; write to a file without forcing a flush each message.
  4. Guard expensive construction. Wrap costly message construction behind a verbosity check so a filtered message costs nothing.

The one-sentence lesson: logging must be quiet by default and detailed on demand — run regression at low verbosity and scope high verbosity to the component under debug — because running regression at high verbosity prints per-transaction detail on every transaction, paying construction, I/O, and storage millions of times for messages nobody reads, which makes the regression I/O- and storage-bound (so adding compute doesn't help) and the logs gigabytes of useless detail; recognize the gigabyte-logs-and-filling-disk signature as over-logging, not a compute shortage.

Common Mistakes

  • Running regression at high verbosity. Per-transaction messages print on every transaction, drowning the sim in I/O and storage; run regression at low verbosity.
  • Leaving a debug verbosity in the run script. A high verbosity left over from debugging floods regression; reset to low verbosity for regression.
  • Adding compute for an I/O-bound logging problem. More cores don't help when the bottleneck is log writes and storage; reduce the logging instead.
  • Unguarded expensive construction. A filtered message still builds its string; guard costly construction behind a verbosity check so a disabled log costs nothing.
  • Logging to console and flushing per message. Console rendering and per-message disk syncs are slow; log to file and avoid per-message flush.
  • Turning verbosity up globally to debug. Global high verbosity floods everything; scope high verbosity to the component under investigation.

Senior Design Review Notes

Interview Insights

Logging has three costs, each paid per message: construction, which is CPU to build the message string; I/O, which is the cost to write the message; and storage, which is keeping the accumulated log. Construction is building the message — the sformatf and any convert2string calls that format a transaction into a string. The crucial property is that this runs before the verbosity filter, because the message argument is evaluated before uvm_info is called, so construction is paid even when the message is filtered out and never printed. It's controlled by guarding expensive construction behind an explicit verbosity check — using uvm_report_enabled to test whether the message would print before building it — so the sformatf runs only when the message will actually be shown. I/O is writing the message to its destination. Writing to the console is especially slow, because terminal rendering is expensive and often unbuffered. Writing to a file is faster but still costs, and forcing a disk sync — a flush — on every message is slow. It's controlled by logging to file rather than console in regression, and avoiding per-message flushing. I/O is paid only when the message actually prints, so it's also controlled by verbosity — a filtered message isn't written. Storage is the log accumulating. A verbose log grows to gigabytes per test, which is slow to write, fills the disk, and is slow to search later. It's controlled by bounding log size and, again, by verbosity — fewer printed messages means a smaller log. Over all three sits verbosity as the master volume knob: it determines whether a message is processed for I/O and storage at all, though notably not whether its construction runs, which is why construction needs the separate guard. So the relationship is: verbosity controls I/O and storage directly — a filtered message isn't written or stored — but it does not control construction, because the string is built before the filter checks it. That's the key asymmetry, and it's why the two cardinal disciplines are distinct: guard construction so disabled logs cost nothing, and set verbosity low so enabled logging stays quiet. Verbose-by-default logging pays all three costs on every message on every transaction, millions of times, which is why logging is the most common performance problem in a UVM environment.

A disabled log must cost nothing because logging is paid per message on every transaction, so any nonzero cost per disabled message — multiplied by millions of transactions — becomes a major runtime cost, even though the messages never print. The problem is that, by default, a disabled log is not free, because of evaluation order. When you write a uvm_info with a message built by sformatf and convert2string, the arguments are evaluated before uvm_info is called — that's how function calls work. So the string is fully constructed, and only then is it passed to uvm_info, which checks the verbosity and discards it if it's filtered. The expensive construction — formatting a transaction into a string — already ran. So a filtered debug message at, say, UVM_HIGH, sitting in a per-transaction loop, costs its full construction on every transaction even when verbosity is low and nothing prints. Across millions of transactions, that can dominate runtime, as the runtime-bottlenecks chapter showed with a filtered log that was sixty percent of runtime. The CCTV analogy is a camera that records full-resolution video and then deletes it because nobody's watching — it paid the entire capture and encode cost for footage immediately thrown away. You achieve cost-nothing-when-disabled by guarding the expensive construction behind an explicit verbosity check, so the construction runs only when the message will print. Concretely, you wrap the uvm_info in an if that calls uvm_report_enabled with the same verbosity, ID, and severity, so the sformatf only executes when that level is actually enabled. This moves the verbosity check before the construction rather than after it — the motion sensor decides before the camera records, not after. With the guard, a disabled message costs essentially nothing: the if evaluates a cheap boolean and skips the construction entirely. You don't need to guard every message — a simple message with a cheap or no construction cost is fine unguarded, because there's nothing expensive to skip. You guard the ones where construction is expensive: anything calling convert2string on a large object, or doing significant formatting, especially in a hot per-transaction loop. The principle is that logging's construction cost is decoupled from whether it prints, so for expensive messages you must explicitly gate construction on the verbosity, ensuring that turning a message off actually turns its cost off.

Quiet by default, detailed on demand means regression runs at low verbosity so only errors and key events print, while full per-transaction detail is available when you need it by raising verbosity, scoped to the component you're debugging, on a re-run — rather than logging verbosely all the time. The tension it resolves is that logging has two opposing failure modes. Too little logging means that when a test fails, you don't have enough information to debug it, so you have to reproduce the failure, which is costly and sometimes hard. Too much logging means the log is slow to produce, huge to store, and useless to read, because the one event that matters is buried in millions of routine lines. The resolution is to make the default quiet and the detail available on demand. By default — in regression, where thousands of tests run nightly — verbosity is low: errors are always shown, and a small number of key events like reset-complete are shown, but per-transaction detail is not printed. This keeps regression logs small and fast, and keeps the simulation from being I/O-bound on writes. The per-transaction detail still exists in the code, at a high verbosity level, but it's filtered out in regression. Then, when a specific test fails and you need to debug it, you re-run just that test with verbosity raised — and crucially, scoped to the component under investigation, not globally. UVM lets you set verbosity for a specific component by hierarchy, so you can raise the driver to high verbosity while everything else stays low. That gives you full detail exactly where you're looking, only on the re-run, without flooding everything. The CCTV analogy is exact: cameras record low-resolution by default, you crank one camera to high-resolution only when actively watching that area, and you don't run every camera at max resolution all the time. The discipline preserves logging's value — you can still get full detail to debug — while avoiding its cost in the common case, regression, where that detail isn't being read. The anti-pattern is the opposite: running regression at high verbosity, often from a debug setting left in the run script, which floods every test with per-transaction detail, makes the regression I/O-bound, fills the disk, and buries the errors. So the rule is: low verbosity in regression, high verbosity scoped to a component on a debug re-run.

Adding compute doesn't fix a logging-bound regression because the bottleneck is I/O and storage, not compute — when messages print on every transaction, the simulation spends its time waiting on log writes and the disk fills, and adding cores just gives you more cores sitting idle waiting on the same I/O. The intuition that a slow regression needs more compute is usually right, but it fails for logging because logging is an I/O- and storage-bound activity, not a compute-bound one. When regression runs at high verbosity, a message prints on every transaction. Each printed message has to be written — to a file or, worse, the console — and that write is I/O. Across millions of transactions per test and thousands of tests, the cumulative write volume saturates the I/O bandwidth: the simulation process blocks waiting for writes to complete, so wall-clock time is dominated by I/O wait, not computation. Simultaneously, the messages accumulate into gigabyte log files that fill the shared storage. Now consider adding compute. More cores let you run more simulations in parallel, or a single simulation isn't compute-limited to begin with — but each simulation is still blocked on its own log writes, and the storage still fills at the same rate per unit of simulation. The added cores don't address the actual constraint; they sit idle waiting on I/O, or they make the I/O and storage contention worse by writing even more logs in parallel. So the regression doesn't get faster, and the disk keeps filling. This is the wrong-resource trap: a problem that looks like it needs more of resource A — compute — actually needs less consumption of resource B — I/O and storage. The way to recognize it is the signature: gigabyte log files, a filling disk, the simulation waiting on writes, and compute not helping. Those together say I/O- and storage-bound logging, not a compute shortage. The fix is to reduce the logging — run at low verbosity so per-transaction messages don't print, which removes the I/O and storage cost directly — plus log to file instead of console and avoid per-message flushing. That makes the logs small and fast and frees the simulation from waiting on writes. The general lesson is to diagnose what resource is actually saturated before scaling, because scaling the wrong resource spends real money and effort for no improvement, which is the same measure-don't-guess discipline from runtime profiling applied to the choice of which resource to add.

The I/O specifics that matter are where you write — console versus file — whether you flush per message, and how large the log is allowed to grow. Console versus file is the biggest one. Writing to the console — the terminal — is the slow path, because terminal rendering is expensive: the terminal has to process and display each line, often synchronously, and if the output is being captured through layers like SSH or a CI system, each write goes through more overhead. Writing to a file is much faster, especially with buffering, because it's a straightforward append to disk without rendering. So in regression, you log to a file, not the console. UVM's report server can be configured to write to a file. Per-message flushing is the next. Flushing forces the buffered output to be physically written to disk immediately — a disk sync. If you flush on every message, you pay a disk sync per message, which is slow, because it defeats the buffering that makes file writes fast and forces the OS to commit each write. You generally don't want to flush per message; you let the OS buffer and flush periodically. The trade-off is that if the simulation crashes, the last unflushed messages might be lost, so for the specific case of debugging a crash you might flush more aggressively, but for regression throughput you don't flush per message. Log size is the third. A log that grows unbounded to gigabytes is slow to write, fills the disk, and is slow to search when you later need to find something in it. You bound it by keeping verbosity low so fewer messages are written, and in some setups by rotating or capping log files. The reason these specifics matter is that when messages do print — when you're not just paying construction but actually emitting — the write path is often the dominant cost, more than the construction. So even with verbosity controlled, the where and how of writing affects performance: file beats console, buffered beats per-message flush, bounded beats unbounded. These are the knobs on the I/O and storage costs, complementary to verbosity which controls how many messages reach the write path at all. Together — low verbosity, file not console, no per-message flush, bounded size — they keep the reporting system's I/O and storage footprint small.

Exercises

  1. Guard or not. For each — a simple "reset complete" message, a per-transaction message that calls convert2string on a large object, an error message — decide whether to guard the construction and why.
  2. Set the verbosity. Describe the verbosity settings for a nightly regression versus a debug re-run of a single failing test, and how you scope the debug verbosity.
  3. Diagnose the resource. Given a slow regression with gigabyte logs where adding cores didn't help, identify the bottleneck and the fix.
  4. The three costs. For a per-transaction uvm_info at high verbosity printing in regression, name the construction, I/O, and storage cost and how each is controlled.

Summary

  • Logging optimization controls the reporting system's three costsconstruction (CPU: building the string, paid even when filtered because the macro builds it before the verbosity check), I/O (writing: console especially slow, or file), and storage (the log accumulating to gigabytes) — paid per message on every transaction.
  • The cardinal disciplines: a disabled log must cost nothingguard expensive construction behind a verbosity check (uvm_report_enabled) so the $sformatf runs only when it prints; and quiet by default, detailed on demandrun regression at low verbosity (errors and key events only), turn verbosity up only for the component under debug.
  • Over-logging is I/O- and storage-bound, not compute-bound — so adding compute doesn't help (cores wait on writes, the disk fills); the fix is to log less (low verbosity, file not console, no per-message flush, bounded size).
  • A drowned log fails on two axes: a performance cost (I/O + storage on every transaction) and a usability cost (the one error buried in millions of routine lines) — over-logging is bad even before you count the cycles.
  • The durable rule of thumb: run logging like a well-designed surveillance system — quiet by default (regression at low verbosity, errors and key events only), detailed on demand (verbosity scoped high to the component under debug on a re-run), guarding expensive message construction so a disabled log costs nothing, logging to file not console, and avoiding per-message flushing; verbose-by-default logging pays construction, I/O, and storage on every transaction for messages nobody reads, making the regression I/O-bound (so adding compute doesn't help) and the logs gigabytes of useless detail that bury the one error you needed — never capture footage you'll immediately discard, and never record every camera at max resolution all the time.

Next — Performance Profiling: the module closes by making profiling itself systematic. The earlier chapters profiled runtime and memory in isolation; the last chapter turns profiling into a repeatable methodology — what to measure (CPU, memory, I/O), the tools that measure each, how to read a profile to find the dominant cost, how to set performance budgets and catch regressions before they ship, and how to make performance a continuous property of the environment rather than a crisis addressed once.