Simulation lab

Simulation is a controlled experiment on concurrent behavior

A productive simulation flow makes stimulus reproducible, expected behavior executable, failures visible, and scheduler assumptions explicit.

Layered digital pulse traces moving across a dark technical grid
A waveform is evidence to interpret, while a self-checking testbench decides whether the evidence satisfies the specification.

Compile, elaborate, initialize, run

Compilation parses source and establishes design units. Elaboration resolves the selected top, parameters, generate conditions, instance hierarchy, port connections, and library bindings. Initialization creates state and schedules initial activity. The event-driven run then advances through time as signal changes activate processes and those processes schedule further updates.

Keeping these phases separate improves diagnosis. A missing module is usually a source or library problem. A bad parameter or unresolved hierarchy may be an elaboration problem. An unknown value at time zero may reflect initialization or reset. A mismatch after several cycles is a behavioral or checking problem. Tool messages often name the phase; preserve that distinction in bug reports.

Reason about regions, not source order

Concurrent blocks do not execute in a reliable visual order merely because one appears earlier in a file. Blocking assignments take effect within their procedural sequence; nonblocking assignments schedule updates so clocked processes can sample a consistent old state. Monitors, assertions, and testbench stimulus must be placed so they observe the intended region and do not race with the design.

Clock generation and reset sequencing deserve explicit structure. Drive clocks from one clear source. Align or deliberately offset stimulus from sampling edges. Hold reset long enough to meet the model's assumptions, then check that all externally visible state becomes known. Do not hide an initialization defect by forcing internal values from the testbench.

Make the testbench decide

A test should state an expected outcome and fail automatically when it is not met. Directed tests are effective for named scenarios and boundaries. Constrained random tests explore combinations, but they need constraints that preserve legality and checkers that recognize failure. A reference model can be mathematical, transactional, or a simpler implementation of the same function. Assertions are well suited to local temporal rules and invariants.

Each run should report the seed, configuration, test name, relevant tool mode, and first meaningful failure. Use a nonzero process status so continuous integration can recognize failure. Track functional coverage only where it corresponds to planned behavior. Code coverage can expose untouched implementation, but it cannot establish that the observed behavior was correct.

A narrow debug loop

Reproduce the failure with the same seed and inputs. Reduce the time window and identify the first divergence, not the last corrupted output. Move backward through drivers, control decisions, and sampled transactions. Add a focused assertion or log message that would catch the divergence earlier. Once fixed, keep the reduced case as a regression.

Waveforms are most useful when signal selection follows a hypothesis. Start with interface transactions, clocks, resets, and the state controlling the failing behavior. Expand into datapath detail only as needed. Recording every signal for every test consumes storage and can slow the run without improving the first pass of triage.

Performance measurements need the same reproducibility as functional results. Separate compilation from run time, state whether tracing and assertions are enabled, and use a representative workload. A faster run that disables checking or changes the modeled semantics is not an equivalent result. Track memory and output volume alongside elapsed time when scaling regressions.

Not every engine explores the design in the same way. Event-driven simulation schedules changes as they occur; cycle-oriented approaches can optimize work around clock boundaries for suitable designs. Formal equivalence checking compares two representations against a defined relationship rather than applying test vectors. Each method produces a different kind of evidence, and its assumptions belong in the verification plan.

Regression invariant: A passing run is reproducible, self-checking, quiet enough to inspect, and explicit about its language and configuration modes.

Current tool references