VHDL · Chapter 15.7 · Debugging and Simulation
Assertions as Debug Instruments
End-of-test checking tells you that something went wrong, but an assertion embedded in the design tells you exactly where and when. Instead of waiting for a corrupted value to propagate to an output, you place concurrent assertions on the design's invariants, the properties that must always hold. Examples include a state vector that must stay one-hot, a FIFO that must never overflow, a pointer that must stay in range, and a request that must eventually get an acknowledge. A concurrent assertion runs continuously, so the instant an invariant breaks it fires at that exact cycle and location, giving you first-divergence localization for free. Because synthesis ignores assertions, this instrumentation costs nothing in hardware. This lesson covers embedding invariant and contract assertions, concurrent versus immediate checks, and a note on temporal properties for multi-cycle behavior.
Foundation14 min readVHDLDebuggingAssertionsInvariantsVerificationPSL
1. Engineering intuition — trip the alarm at the scene
Finding a bug by its downstream symptom is like investigating a break-in by noticing money missing weeks later. An embedded assertion is an alarm at the scene: you state a property that must always be true right where it matters — "this counter never exceeds DEPTH," "these state bits are one-hot," "we never write a full FIFO" — and the simulator trips it the moment the property is violated, at that signal, that cycle. You no longer scan for the first divergence; the assertion is the first divergence, reported automatically with a message you wrote. The mindset shift is to encode your assumptions as assertions throughout the design, so a broken assumption announces itself instead of silently corrupting a later result.
2. Formal explanation — concurrent invariant assertions
-- CONCURRENT assertions run CONTINUOUSLY (re-checked whenever their signals change) — not inside a process.
-- They watch INVARIANTS: properties that must ALWAYS hold.
-- one-hot state vector must never have != 1 bit set:
assert (not is_running) or (count_ones(state_vec) = 1)
report "state vector not one-hot" severity error;
-- a FIFO must never be written while full (overflow):
assert not (wr = '1' and full = '1')
report "FIFO overflow: write while full" severity error;
-- a pointer must stay in range:
assert to_integer(ptr) < DEPTH
report "pointer out of range" severity error;
-- Inside a clocked process, an IMMEDIATE assertion checks per-cycle:
process (clk) begin if rising_edge(clk) then
assert not (grant_a = '1' and grant_b = '1') report "two grants at once" severity error;
end if; end process;A concurrent assertion sits in the architecture body and is continuously re-evaluated, firing the instant its condition is false — ideal for invariants (one-hot, no-overflow, in-range, mutual-exclusion). An immediate assertion inside a clocked process checks a per-cycle property. Both report at the exact cycle and place of violation.
3. Production usage — contracts, guards, and temporal properties
-- INTERFACE CONTRACT: encode the protocol's rules as assertions at the boundary.
assert not (valid = '1' and reset = '1') report "valid asserted during reset" severity error;
-- ILLEGAL-STATE GUARD (9.8): a branch that must be unreachable.
when others => assert false report "FSM in illegal state" severity failure;
-- TEMPORAL / multi-cycle properties (req must be followed by ack within N cycles) need SEQUENCES.
-- Plain VHDL assert is single-cycle; for "eventually" / "next-cycle" properties use:
-- • a small checker process (count cycles since req, assert ack arrives in time), or
-- • PSL / VHDL-2008 property constructs where the tool supports them.
-- example checker:
process (clk) begin if rising_edge(clk) then
if req='1' then wait_cnt <= 0;
elsif not acked then wait_cnt <= wait_cnt + 1;
assert wait_cnt < MAX_LAT report "ack timeout after req" severity error;
end if;
end if; end process;What hardware does this become? None — assertions are ignored by synthesis, so embedding them throughout the design is free instrumentation: they enrich simulation and vanish in the netlist. Concurrent assertions catch combinational/structural invariants the instant they break; immediate assertions catch per-cycle properties; temporal properties ("req → ack within N") need a small checker process or PSL/VHDL-2008 property language. The payoff is enormous for debugging: a violated assumption is reported at its source automatically, so localization — usually the hardest step — is done for you.
4. Structural interpretation — assertions watching internal nodes
5. Simulation interpretation — the invariant trips at the exact cycle
Concurrent assertion fires the instant the one-hot invariant breaks
8 cycles6. Debugging example — the bug caught late instead of at the source
Expected: a fast, precise catch. Observed: a corrupted value is noticed only at an output many cycles later, and significant effort goes into tracing it back to where it started. Root cause: the design had no embedded assertions on its invariants, so a broken assumption (state went non-one-hot, a FIFO overflowed, a pointer wrapped out of range) propagated silently until it surfaced downstream — the localization had to be done by hand. Fix: embed concurrent assertions on the design's invariants and interface contracts so the violation is reported at its exact cycle and location the moment it happens; add immediate/temporal checks for per-cycle and multi-cycle properties. Engineering takeaway: encode invariants as assertions inside the design — they cost nothing in hardware and turn the hardest debugging step (finding the first divergence) into an automatic report at the source.
-- BUG: no invariant check → FIFO overflow corrupts data, noticed cycles later at the output.
-- (write proceeds even when full; the symptom appears far downstream)
-- FIX: a concurrent assertion fires AT the overflow, the exact cycle it happens.
assert not (wr = '1' and full = '1') report "FIFO overflow" severity error;7. Common mistakes & what to watch for
- No embedded invariants. Without assertions, bugs surface downstream; assert one-hotness, no-overflow, in-range, mutual-exclusion at the source.
- Only checking at the output. Catch violated assumptions where they occur, not after they corrupt a result.
- Single-cycle assert for temporal properties. "Eventually/within-N" needs a checker process or PSL/VHDL-2008
properties, not a bare
assert. - Wrong severity. Use
error/failureso violations halt or are scored; anoteinvariant break gets lost. - Forgetting assertions are free in hardware. Synthesis ignores them — instrument generously; they only help simulation.
8. Engineering insight & continuity
Assertions embedded in the design are debug instruments: concurrent checks on invariants (one-hot, no-overflow, in-range) and interface contracts fire the instant an assumption breaks, at the exact cycle and location — handing you first-divergence localization for free, at zero hardware cost since synthesis ignores them. They turn the hardest debugging step into an automatic report. The debugging module has covered the engine, waveforms, metavalues, deltas, mismatches, method, and instrumentation; it closes with the practical concern of running large verification efficiently — the next lesson, Simulation Performance and Logging.