Debug with waves
You will learn
How to go from a symptom to a signal, cycle by cycle, on a real measured bug.
Here is a bug found with waves, end to end. A UART link on the bench requested 115,200 baud and the wire carried 115,385 -- a +0.16 % error from the clock divider, invisible in code, obvious in a capture. Requested 921,600, the wire carries 923,077, the same fractional error; 3,000,000 divides exactly and is exact on the wire. The measurement names the fix: choose divisors that land on integers. Waves turn a symptom (bytes lost) into a number (0.16 %) into an edit.
Try it
Compute what divider would put 115,200 on the wire exactly; check it against the 921,600 and 3,000,000 rows in the widget.

CP2102N on a direct USB port, no hub. Requested 115,200 puts 115,385 on the wire (+0.16 %), 921,600 puts 923,077 (+0.16 %), 3,000,000 is exact.
specs/fpga/testbench/vcd_trace_tb.t27
// SPDX-License-Identifier: Apache-2.0
// t27/specs/fpga/testbench/vcd_trace_tb.t27
// VCD Trace Testbench
// Tests waveform dump generation, signal hierarchy, and timestamp management
// phi^2 + 1/phi^2 = 3 | TRINITY
module VCD_Trace_Testbench {
use fpga::vcd_trace::VcdTrace;
const CLK_PERIOD : u32 = 20;
const MAX_SIGNALS : u32 = 256;
const TIMESTAMP_RES_PS : u32 = 10;
var clk : bool = false;
var rst_n : bool = false;
var trace_en : bool = false;
var signal_count : u32 = 0;
var timestamp_ps : u32 = 0;
var dump_complete : bool = false;
var test_passed : u32 = 0;
var test_failed : u32 = 0;
fn tick() {
clk = false;
clk = true;
timestamp_ps = timestamp_ps + TIMESTAMP_RES_PS;
}
fn reset() {
rst_n = false;
tick();
tick();
rst_n = true;
tick();
}
fn advance_time(steps : u32) -> u32 {
var i : u32 = 0;
while i < steps {
tick();
i = i + 1;
}
return timestamp_ps;
}
fn format_timestamp(ps : u32) -> u32 {
return ps / 1000;
}
test test_reset_state {
reset();
invariant trace_en == false;
invariant dump_complete == false;
}
test test_timestamp_advance {
timestamp_ps = 0;
var t : u32 = advance_time(10);
invariant t == 100;
}
test test_format_timestamp {
var ns : u32 = format_timestamp(10000);
invariant ns == 10;
}
test test_max_signals {
invariant MAX_SIGNALS == 256;
}
test test_timestamp_resolution {
invariant TIMESTAMP_RES_PS == 10;
}
test test_tick {
tick();
invariant clk == true;
invariant timestamp_ps == 10;
}
invariant max_signals_positive : MAX_SIGNALS > 0;
invariant resolution_positive : TIMESTAMP_RES_PS > 0;
bench bench_vcd_trace {
reset();
trace_en = true;
advance_time(100);
trace_en = false;
}
}
All lessons
Module 1 · Why verify
Designs that compile and are wrong, the model that decides, and the plan written before the code.
Module 2 · Testbenches
Stimulus, checks and a verdict, written as one spec beside the design it judges.
Module 3 · Waveforms
A trace of every signal, read the way a hardware engineer reads it, and two runs compared.
Module 4 · Conformance vectors
Cases with the answer written beside them, kept where the compiler can reach them.
Module 5 · Cosimulation
Spec, simulator and board agreeing on the bench Artix-7 XC7A200T, and what to do when they do not.
Module 6 · Coverage
What the tests touched: lines, toggles, states, and what that number hides.
Module 7 · Formal
Assertions that hold every cycle, bounded search for a counterexample, and why a proof needs induction.
Module 8 · Mutation
Break the design on purpose and count what the tests catch.
Module 9 · Sign-off
One command, every receipt, a clean verdict you can show.