AI-assisted hardware waveform debugging tool. Tsunami provides a Rust-powered query engine (using the wellen crate) with four entry points: a standalone Rust MCP server, a Python library, a Python MCP server, and a CLI.
- Rust toolchain (rustc, cargo)
- Python 3.12+ and uv (for the Python library/CLI)
cargo build --release -p tsunami-serveThe binary is at target/release/tsunami-serve.
uv sync # Install Python dependencies
uv run maturin develop # Build the Rust extension# Waveform metadata
tsunami info <file.fst>
# Signal discovery (glob patterns)
tsunami signals <file.fst> "*clk*"
# Browse hierarchy
tsunami scopes <file.fst> "tb.dut"
# Point query
tsunami value <file.fst> <signal> <time>
# Transitions in a time range
tsunami transitions <file.fst> <signal> <t0> <t1>
# Multi-signal snapshot at a single time
tsunami snapshot <file.fst> <time> <signal1> <signal2> ...
# Signal summary (period, duty cycle, anomalies)
tsunami summarize <file.fst> <signal> <t0> <t1>
# Anomaly detection (glitches, gaps, stuck signals)
tsunami anomalies <file.fst> <signal> <t0> <t1>
# Start MCP server for Claude Code
tsunami serve <file.fst>Time values accept human-readable formats: 100ps, 1284ns, 1.284us, 642cyc, or raw integers (picoseconds).
import tsunami
handle = tsunami.open("sim.fst")
info = tsunami.waveform_info(handle)
# Discover signals
signals = tsunami.list_signals(handle, "*valid*")
# Point query
val = tsunami.get_value(handle, "tb.dut.tl_a_valid", 1_284_000)
# Transitions in a window
result = tsunami.get_transitions(handle, "tb.dut.clk", 0, 10_000_000)
# Multi-signal snapshot
snap = tsunami.get_snapshot(handle, ["tb.dut.clk", "tb.dut.reset"], 1_000)
# Summarise a signal
summary = tsunami.summarize(handle, "tb.dut.clk", 0, 10_000_000)Compose multi-signal conditions that evaluate entirely in Rust in a single pass:
from tsunami.predicate import Signal, scope
with scope("tb.dut") as s:
# TileLink handshake with specific opcode
handshake = s.tl_a_valid & s.tl_a_ready & (s.tl_a_opcode == 4)
# Rising edge detection
rising = s.clk.rise()
# Sequence with time window
roundtrip = handshake >> (s.tl_d_valid & s.tl_d_ready, 50_000)
# Negated preceded-by (protocol violation)
spurious = s.tl_d_valid.rise().preceded_by(handshake, within_ps=50_000).__invert__()
# Scan entirely in Rust
matches = tsunami.find_all(handle, handshake, t0_ps=0, t1_ps=10_000_000)
first = tsunami.find_first(handle, rising, after_ps=0)Tsunami ships two MCP servers: a standalone Rust binary (tsunami-serve) and a Python-based server (tsunami serve). Both expose the same session-based tool interface over stdio.
No Python runtime needed. Build once and point Claude Code at the binary:
cargo build --release -p tsunami-serveStart with a pre-loaded waveform:
./target/release/tsunami-serve sim.fstOr start without a file and use open_waveform to load dynamically:
./target/release/tsunami-serveClaude Code MCP config (.mcp.json or ~/.claude/mcp.json):
{
"mcpServers": {
"tsunami": {
"command": "/path/to/tsunami-serve",
"args": ["sim.fst"]
}
}
}tsunami serve sim.fst{
"mcpServers": {
"tsunami": {
"command": "tsunami",
"args": ["serve", "sim.fst"]
}
}
}Both servers expose: open_waveform, waveform_info, get_waveform_length, search_signals, browse_scopes, get_signal_info, get_snapshot, get_signal_window, find_edge, find_value, find_pattern, find_first_match, find_all_matches, find_anomalies.
The MCP server is session-based. open_waveform returns a session_id, and all
other waveform tools require that session ID. If you start the server with a
file argument, the preloaded waveform is available as session default.
Claude Code / LLM
| MCP protocol (JSON, stdio)
|
βββ tsunami-serve (pure Rust binary, rmcp)
| |
βββ Python MCP Server / CLI (FastMCP)
| PyO3 calls
|
tsunami-core (pure Rust library)
| wellen crate
FST / VCD waveform files
| Layer | Technology | Responsibility |
|---|---|---|
| Rust MCP Server | tsunami-serve, rmcp |
Standalone MCP server, no Python needed |
| Python MCP Server | FastMCP | Tool definitions, Python-side time parsing |
| Core Engine | tsunami-core |
Signal queries, predicate eval, summarisation, time parsing |
| PyO3 Bindings | tsunami cdylib |
Thin wrappers exposing tsunami-core to Python |
| Waveform I/O | wellen | FST/VCD parsing, memory-mapped lazy loading |
uv run pytest tests/ -vtsunami/
βββ Cargo.toml # Workspace root
βββ pyproject.toml # maturin build backend, uv managed
βββ crates/
β βββ tsunami-core/ # Pure Rust library (no PyO3)
β β βββ src/
β β βββ lib.rs
β β βββ query.rs # Signal queries, value helpers
β β βββ predicate.rs # Expr AST, evaluation engine
β β βββ summarise.rs # Anomaly detection, period inference
β β βββ time_parse.rs # Human-readable time parser
β βββ tsunami-serve/ # Standalone Rust MCP server binary
β βββ src/
β βββ main.rs # CLI entry, stdio transport
β βββ server.rs # MCP tool handlers (rmcp)
βββ src/ # PyO3 cdylib (thin wrappers)
β βββ lib.rs # Module entry
β βββ py_query.rs # Query wrappers
β βββ py_predicate.rs # Predicate wrappers
β βββ py_summarise.rs # Summary wrappers
βββ python/tsunami/
β βββ __init__.py # Public API re-exports
β βββ _engine.pyi # Type stubs for Rust module
β βββ predicate.py # Python DSL (Expr dataclasses + operators)
β βββ time_parse.py # ns/us/cyc -> ps normalisation
β βββ server.py # Python MCP server (FastMCP)
β βββ cli.py # CLI entry point
βββ tests/
βββ test_engine.py # Rust engine integration tests
βββ test_predicate.py # DSL unit tests
βββ test_time_parse.py # Time parser tests