Waveform Viewer (rb wave)
Integration type: Integrated tool.
rb waveis built around Surfer today; Vaporview / VS Code support is on the roadmap — tracked in issue #84.External binary required: Surfer, built from the
rtl-buddy/surferfork on thertl-buddybranch. Mainline Surfer works for basic FST viewing but not for WCP signal-value annotation. See Surfer build.Editor integration: nvim for the full annotation round-trip (declaration jump + live signal values + add-from-editor). Any editor can be configured via
editor-cmdfor one-way "open at line".See also: Installation — External tools by feature.
rb wave opens the Surfer waveform viewer for a test and connects it to your editor via the WCP (Waveform Client Protocol). When you right-click a signal in Surfer and choose Go to declaration, rtl-buddy:
- Resolves the signal to its source file and line number
- Opens (or reuses) your editor at that location
- Annotates the signal's waveform value at the current cursor position as inline virtual text
Basic usage
cd verif/sandbox
uv run rb wave basic # runs debug sim if no FST exists, then opens Surfer
uv run rb wave basic --resim # force re-run of debug sim
Signal layout files are loaded automatically: if basic.surfer exists next to tests.yaml, Surfer opens with those signals pre-loaded.
Configuration
Add a cfg-surfer section to root_config.yaml:
cfg-surfer:
- name: "surfer-default"
path: "../surfer/target/release/surfer" # or bare name on PATH
wcp-port: 0 # 0 = OS assigns a free port
editor-cmd: "nvim +%l %f" # %f = file, %l = line (schema default is "vim +%l %f")
editor-terminal: "tmux" # tmux | iterm2 | terminal | ""
editor-sock: "~/.local/share/rtl-buddy/wave-nvim.sock" # enables nvim reuse
ctrl-sock: "~/.local/share/rtl-buddy/wave-ctrl.sock" # enables nvim → Surfer
See YAML Formats for all fields.
Signal value annotation
How it works
When a goto_declaration event arrives from Surfer, rtl-buddy:
- Reads the signal value at the cursor timestamp from the FST via pywellen
- Enumerates all other signals in the same module scope using the FST hierarchy
- Runs a single bulk grep across the SV source files to map every signal to its declaration line (result cached for the session)
- Pushes all values to the editor as EOL virtual text
Moving the Surfer time cursor fires a cursor_moved WCP event, which re-reads all scope signal values and updates the annotations live — no interaction required.
Two signals declared on the same source line are combined into one annotation:
logic a, b; ▶ a=8'h0a b=8'h05 [i_dut]
logic clk; ▶ 1'b0 [i_dut]
Active scope
Clicking any signal in Surfer's signal list sets the active scope — the module instance used to resolve signal names. rtl-buddy updates the scope cache automatically via the scope_changed WCP event, so annotation context is always current without requiring a "Go to declaration".
nvim setup
The annotation feature requires a small nvim plugin. Install it once:
rb wave-install-nvim
This copies rtl_buddy_wave.lua to ~/.local/share/nvim/site/plugin/, which nvim auto-sources at startup. No init.lua changes are needed. Reinstall after rtl-buddy upgrades with --force:
rb wave-install-nvim --force
If the plugin is missing when rb wave starts with editor-sock configured, a warning is shown:
WARNING nvim plugin not installed — run "rb wave-install-nvim" to enable wave annotations
Adding signals to Surfer from nvim
With ctrl-sock configured, place the cursor on any signal name in nvim and press <Space>wa (<leader>wa) to add it to Surfer's waveform view.
The signal is resolved using the active scope — click a signal in Surfer first to establish the instance context (e.g. clicking tb_top.i_dut.clk sets scope tb_top.i_dut), then add signals freely from nvim.
nvim: cursor on "rst" → <Space>wa → Surfer adds tb_top.i_dut.rst to waveform
The keymap requires ctrl-sock to be set in cfg-surfer and rb wave to be running. A warning is shown if the socket is unreachable.
Single-signal mode
To annotate only the signal you right-clicked (not the whole scope):
uv run rb wave basic --focused-signal
Editor socket reuse
When editor-sock is set, rtl-buddy launches nvim with --listen <sock> on first use. Subsequent goto_declaration and cursor_moved events reuse the running instance via --remote-expr nvim_exec2(...) — no new windows, no command-line flicker.
The socket is probed with a 300 ms timeout. If the socket is stale (nvim has been closed), the next goto_declaration opens a fresh nvim window.
Opening FPV counterexamples (rb wave-fpv)
rb wave-fpv <verif_name> opens the SymbiYosys counterexample VCD for a failed formal verification in Surfer:
uv run rb wave-fpv demo_fpv_counter_safety
It reads the same fpv.yaml (-c/--fpv-config, default fpv.yaml) to resolve the verification name, then opens the trace at <dir of fpv.yaml>/artefacts/<verif>/sby_workdir/engine_<N>/trace.vcd (first engine in sorted order). It opens the VCD in the cfg-surfer entry named surfer-default unless you pass --surfer <name>. Unlike rb wave, it just opens the VCD — there is no WCP annotation round-trip — so mainline Surfer suffices. It raises a clear error if the verification has not been run, the proof passed (no counterexample), or no engine produced a trace.
Hub integration
When a project coordination hub is running, rb wave opportunistically connects to it as the wave-origin peer (the bridge in tools/wave_hub_bridge.py; the hub is discovered via $RTL_BUDDY_HUB or by walking up to .rtl-buddy/hub.json). The bridge forwards Surfer events to the hub (cursor moves → cursor_time_changed, plus scope_changed, signal_selected, and a wave_values_changed snapshot on cursor move) and serves hub requests back to Surfer (wave_add_variables, wave_set_cursor, wave_set_scope, wave_set_viewport, wave_zoom_to_range, wave_zoom_to_fit). Time is exchanged in femtoseconds (time_unit=fs). If no hub is reachable the bridge stays silent and rb wave runs fully standalone — the hub is never required.
Surfer build
The annotation features require Surfer built from the rtl-buddy branch:
git clone https://github.com/rtl-buddy/surfer.git ../surfer
cd ../surfer && git checkout rtl-buddy
cargo build --release
Point cfg-surfer.path at ../surfer/target/release/surfer (relative to root_config.yaml) or install the binary on PATH.