Skip to main content
Glama
README.md
# Waveform MCP — AI-Friendly VCD/FSDB Waveform Debugger

A Model Context Protocol (MCP) server that gives AI agents the ability to read, analyze, and debug VCD/FSDB waveform files from digital circuit simulations.

Parse a waveform once, then query signals, find AXI transactions, render ASCII waveforms, and validate protocol compliance — all through structured JSON-RPC tools.

## Features



* **Stateful session**: Parse once, query many times. Large files cached in memory.

* **VCD + FSDB support**: Native VCD parsing; FSDB via Verdi's `fsdb2vcd` auto-conversion.

* **32 structured tools**: Signal search, snapshot, edge detection, transaction analysis, AXI write/read channel correlation, burst aggregation, bus-level protocol checks, clock analysis, signal activity stats, dual-waveform diff, value diff between times, ASCII rendering, multi-session comparison, signal aliases, and more.

* **Smart signal search**: Ranked relevance matching (exact > base name > suffix > component > substring), with support for bit-select suffixes like `wdata[31:0]`.

* **AXI transaction analysis**: Automatically extract valid-ready transactions from all 5 AXI channels (AW/W/B/AR/R) with captured data values. One-call `analyze_axi_channel` auto-detects signals, aggregates W beats into bursts by `wlast`, and reports throughput/gap/duration statistics. `analyze_axi_write`/`analyze_axi_read` correlate address+data+response channels into complete transfers with latency breakdown.

* **Bus-level protocol checks**: Detect AXI protocol violations including data instability while VALID=1/READY=0, BVALID before WLAST, and zero-width valid pulses.

* **Clock & activity analysis**: Auto-detect clock signals and report period, frequency, duty cycle, jitter, and gating. Find dead signals (never toggle) and rank signals by change activity.

* **Dual-waveform diff**: Compare two loaded waveforms signal-by-signal, report first mismatch time and value for each differing signal. Matches signals by base name across hierarchies.

* **Multi-session support**: Load multiple waveforms simultaneously (e.g. golden vs actual) under different session IDs, switch between them, or pass `session_id` to any tool for cross-waveform comparison.

* **Signal aliases**: Define short aliases for long hierarchical paths (e.g. `tb.dut.u_memory.wdata[31:0]` → `wdata`), usable anywhere a signal name is expected.

* **Flexible time inputs**: All time parameters accept integers (picoseconds) or human-readable strings like `"100ns"`, `"1.5us"`, `"500ps"`.

* **ASCII waveform rendering**: View timing diagrams directly in the conversation without a waveform viewer. Linear-scan rendering with collision-free time scale and right-boundary labeling.

* **Protocol compliance checking**: Detect valid-ready handshake violations.

* **Pickle cache**: Large files reload in seconds instead of minutes.

* **CLI fallback**: Human-friendly command-line interface for manual debugging.

## Architecture



```
src/waveform_mcp/core.py    (core library, pure stdlib, no MCP dependency)
   ├── src/waveform_mcp/server.py  (MCP Server — 32 tools over stdio JSON-RPC)
   └── src/waveform_mcp/cli.py     (CLI — backward-compatible, human-friendly)
```

The core library (`core.py`) contains all parsing and analysis logic. It can be used standalone in Python scripts, or exposed via MCP or CLI. The package uses a standard `src/` layout and is installable via `pip install -e .`.

## Installation

### Requirements



* Python >= 3.10

* `mcp >= 2.0` (for MCP Server; CLI and core library don't need it)

* Verdi with `fsdb2vcd` in PATH (only for FSDB files; VCD files don't need it)

### Install (package mode, optional)

```
pip install -e ".[mcp]"     # installs core + MCP SDK
pip install -e .             # core only (zero dependencies, for CLI/scripts)
```

### Install MCP dependency (manual mode)

```
pip install "mcp[cli]"
```

### Clone / copy the project

```
# All files are self-contained in this directory
cd D:\workspace\waveform_mcp
```

No build step required. The project is pure Python.

## MCP Server Deployment

### Claude Desktop

Edit `claude_desktop_config.json` (location varies by OS):

**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`

**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`

**Linux:** `~/.config/Claude/claude_desktop_config.json`



```
{

  "mcpServers": {

    "waveform-debugger": {

      "command": "python",

      "args": \["D:\\\workspace\\\waveform\_mcp\\\waveform\_mcp.py"]

    }

  }

}
```

Restart Claude Desktop. The server will appear in the MCP status indicator with 17 tools.

### Cursor

Create or edit `.cursor/mcp.json` in your project root:



```
{

  "mcpServers": {

    "waveform-debugger": {

      "command": "python",

      "args": \["D:\\\workspace\\\waveform\_mcp\\\waveform\_mcp.py"]

    }

  }

}
```

Restart Cursor. Enable the server in Settings > MCP.

### Other MCP-compatible clients

Any MCP 2.x-compatible client (Cline, Continue, custom agents) can connect via stdio:



```
{

  "command": "python",

  "args": \["D:\\\workspace\\\waveform\_mcp\\\waveform\_mcp.py"]

}
```

### Verify the server is working



```
\# List registered tools (should show 17)

python -c "import asyncio, sys; sys.path.insert(0,'.'); from waveform\_mcp import server; print(asyncio.run(server.list\_tools()).\_\_len\_\_())"

\# Run full MCP communication test

python test\_mcp\_server.py
```

## Tool Reference

### Session Management

| Tool                                  | Description                                                       |
| ------------------------------------- | ----------------------------------------------------------------- |
| `load_waveform(path, use_cache=true, session_id?)` | Load a VCD/FSDB file and parse into memory. Must be called first. `session_id` for multi-waveform comparison. |
| `get_waveform_info(session_id?)`     | Get signal count, module count, max time, timescale, module list. |
| `unload_waveform()`                   | Unload all sessions and free memory.                              |
| `list_sessions()`                     | List all loaded sessions and the active session ID.               |
| `switch_session(session_id)`          | Switch the active session.                                        |
| `close_session(session_id?)`          | Close a specific session and free its memory.                     |

### Signal Aliases

| Tool                                  | Description                                                       |
| ------------------------------------- | ----------------------------------------------------------------- |
| `set_alias(alias, signal_name, session_id?)` | Define a short alias for a long hierarchical signal path. Usable anywhere a signal name is expected. |
| `list_aliases(session_id?)`           | List all user-defined aliases.                                    |
| `remove_alias(alias, session_id?)`    | Remove a user-defined alias.                                      |

### Signal Browsing & Search



| Tool                                                                  | Description                                                                         |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `list_modules()`                                                      | List all module hierarchy paths.                                                    |
| `list_signals(module?, pattern?)`                                     | List signals, optionally filtered by module or name pattern.                        |
| `search_signals(pattern, match_mode="auto", exclude_constants=false)` | Search signals by name. `auto` mode ranks by relevance. Supports `[31:0]` suffixes. |

**Match modes:**



* `auto` — ranked relevance (recommended)

* `exact` — exact full path match

* `suffix` — base name ends with pattern (supports bit-select suffixes)

* `contains` — case-insensitive substring

* `regex` — regular expression

### Time Queries

| Tool                                                            | Description                                                               |
| --------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `snapshot(time, signals, session_id?)`                          | Get signal values at a specific time (bin/hex/dec). `time` accepts int (ps) or "100ns"/"1.5us". |
| `watch_signals(signals, trigger?, time_window?, max_events=10, trigger_on="first", session_id?)` | Track values over time. `trigger_on="any"` reports when ANY signal changes; each event includes `triggered_by`. |
| `next_edge(signal, from_time, edge="rising", session_id?)`     | Find next rising/falling edge after a time.                               |
| `prev_edge(signal, from_time, edge="rising", session_id?)`     | Find previous rising/falling edge before a time.                          |
| `find_edges(signal, edge="both", max_n=100, session_id?)`      | Find all edges of a signal.                                               |

### Transaction Analysis

| Tool                                                                     | Description                                                          |
| ------------------------------------------------------------------------ | -------------------------------------------------------------------- |
| `find_transactions(valid_signal, data_signals, ready_signal?, max_n=10, session_id?)` | Find valid-ready transactions and capture data values at each start. Registered-ready handshake time is correct (end = ready assertion, not valid fall). |
| `analyze_axi_channel(channel_prefix, max_n=100, session_id?)`          | One-call AXI channel analysis: auto-detect signals, extract transactions, aggregate W beats into bursts by `wlast`, report throughput/gap/duration stats. Channel: aw/w/b/ar/r. |
| `get_transaction_stats(valid_signal, data_signals, ready_signal?, max_n=100, bytes_field?, session_id?)` | Extract transactions and compute cross-transaction stats: count, duration min/avg/max, gap min/avg/max, total bytes, throughput Mbps. |
| `compare_transaction_fields(transactions, expected_field, actual_field)` | Compare two fields across all transactions, find mismatches.         |
| `analyze_axi_write(max_n=100, session_id?)` | Correlate AW+W+B channels into complete write transfers with latency breakdown (AW->W, W-burst, WLAST->B, total). Time-proximity matching handles unequal channel counts. |
| `analyze_axi_read(max_n=100, session_id?)` | Correlate AR+R channels into complete read transfers with latency breakdown (AR->R-first, R-burst, total). |

### Protocol, Clock & Activity Analysis

| Tool                                                     | Description                                          |
| -------------------------------------------------------- | ---------------------------------------------------- |
| `check_axi_protocol(channel="write", session_id?)`     | AXI bus-level protocol checks: data stability while VALID=1/READY=0, BVALID before WLAST, zero-width valid pulses. |
| `analyze_clock(clock_signal?, session_id?)`             | Auto-detect clock and report period, frequency, duty cycle, jitter, and gating periods. |
| `signal_activity_stats(module?, top_n=20, session_id?)` | Rank signals by change count, find dead signals (never toggle), report average changes per signal. |

### Value Diff & Comparison

| Tool                                                     | Description                                          |
| -------------------------------------------------------- | ---------------------------------------------------- |
| `value_diff_between_times(t1, t2, signals?, session_id?)` | Find all signals whose value changed between two time points. Useful for "what changed after reset?". |
| `compare_waveforms(session_a, session_b, signals?, time_window?)` | Compare two loaded waveforms signal-by-signal, report first mismatch time and value for each differing signal. |

### Visualization

| Tool                                                             | Description                                  |
| ---------------------------------------------------------------- | -------------------------------------------- |
| `render_ascii_waveform(signals, time_start, time_end, width=80, session_id?)` | Render ASCII timing diagram with collision-free time scale and right-boundary label. Linear-scan for performance. |

### Validation & Checking

| Tool                                                     | Description                                          |
| -------------------------------------------------------- | ---------------------------------------------------- |
| `check_signal_value(signal, expected_value, at_time, session_id?)` | Check if signal equals expected value (bin/hex/dec). |
| `find_signal_mismatch(signal_a, signal_b, time_window?, session_id?)` | Find times when two signals differ.                  |
| `check_valid_ready_protocol(valid_signal, ready_signal, session_id?)` | Detect valid-ready handshake violations.             |

## Example Workflow (Agent Debugging AXI2MEM)

Here's how an agent would use the tools to debug a waveform:



```
1\. load\_waveform(path="/path/to/axi2mem\_tb.vcd")

   → 671 signals, 66 modules, 475us max time

2\. search\_signals(pattern="awvalid", exclude\_constants=true)

   → \["axi2mem\_tb.awvalid", ...]

3\. find\_transactions(

     valid\_signal="awvalid",

     data\_signals=\["awaddr", "awlen", "awsize", "awburst"],

     ready\_signal="awready",

     max\_n=5

   )

   → 5 write address transactions with addresses and lengths

4\. find\_transactions(

     valid\_signal="wvalid",

     data\_signals=\["wdata", "wstrb", "wlast"],

     ready\_signal="wready",

     max\_n=5

   )

   → 5 write data transactions:

     \[0] 115ns wdata=0xDEADBEEF wstrb=0xF wlast=1

     \[1] 385ns wdata=0xA0000000 wstrb=0xF wlast=0

     ...

5\. render\_ascii\_waveform(

     signals=\["clk", "awvalid", "awready", "wvalid", "wready"],

     time\_start=90000, time\_end=110000, width=80

   )

   → ASCII timing diagram showing handshake timing

6\. check\_valid\_ready\_protocol(valid\_signal="wvalid", ready\_signal="wready")

   → 0 violations (clean protocol)
```

## CLI Usage

The CLI (`src/waveform_mcp/cli.py`) provides a human-friendly interface for manual debugging. It is backward-compatible with the original script.

### List signals



```
python debug\_waveform.py --vcd waveform.vcd --list-signals

python debug\_waveform.py --vcd waveform.vcd --list-signals --pattern valid
```

### Snapshot at a time



```
python debug\_waveform.py --vcd waveform.vcd --watch result mode --time 95000

python debug\_waveform.py --vcd waveform.vcd --watch result mode --time 95000 --json
```

### Time-series with trigger



```
python debug\_waveform.py --vcd waveform.vcd --watch result mode --trigger valid -n 20
```

### Time window trace



```
python debug\_waveform.py --vcd waveform.vcd --watch valid result --time 95000-97500
```

### Transaction analysis (new)



```
python debug\_waveform.py --vcd waveform.vcd \\

  \--transactions valid\_out \\

  \--data result mode overflow \\

  \--ready ready\_in \\

  -n 5
```

### ASCII waveform (new)



```
python debug\_waveform.py --vcd waveform.vcd \\

  \--ascii clk valid\_out ready\_in result \\

  \--time 90000-110000 \\

  \--width 100
```

### FSDB support



```
python debug\_waveform.py --vcd waveform.fsdb --watch result --time 95000
```

Requires `fsdb2vcd` from Verdi in PATH.

### Cache (new)



```
python debug\_waveform.py --vcd huge.vcd --cache --watch result --time 95000
```

First load parses and saves cache; subsequent loads are \~5x faster.

## Python API Usage

Use the core library directly in scripts:



```
from waveform\_core import WaveformSession

\# Load waveform

session = WaveformSession("/path/to/waveform.vcd", use\_cache=True)

\# Search signals

valid\_signals = session.search\_signals("valid", exclude\_constants=True)

\# Snapshot

snap = session.snapshot(95000, \["result", "mode"])

print(snap\["signals"]\["result"]\["hex"])

\# Find AXI transactions

txs = session.find\_transactions(

    "awvalid", \["awaddr", "awlen"],

    ready\_signal="awready", max\_n=10

)

for tx in txs:

    print(f"  addr={tx\['data']\['awaddr']\['hex']} len={tx\['data']\['awlen']\['hex']}")

\# ASCII waveform

wave = session.render\_ascii(\["clk", "valid", "ready"], 90000, 110000, width=80)

print(wave)

\# Protocol check

violations = session.check\_valid\_ready\_protocol("valid", "ready")

session.unload()
```

## FSDB Support

FSDB (Fast Signal Database) is Synopsys Verdi's proprietary format. Since it's not open, this project uses Verdi's `fsdb2vcd` command-line tool for conversion:



1. When you load a `.fsdb` file, the server automatically calls `fsdb2vcd` to convert it to a temporary VCD.

2. The converted VCD is then parsed normally.

3. The temporary file is cleaned up on unload.

**Requirements:**



* Verdi installed with `fsdb2vcd` in PATH, or

* Set `VERDI_HOME` environment variable, or

* Pass `fsdb2vcd_path` explicitly in Python API

**Limitations:**



* Conversion adds overhead (proportional to file size).

* FSDB-specific features (like signal hierarchy browsing in Verdi) are not available.

* For very large FSDB files, consider converting to VCD manually first.

## Testing

### Core library tests (synthetic VCD)



```
python test\_waveform\_core.py
```

12 tests covering: load/parse, search, snapshot, transactions, edges, ASCII rendering, protocol check, cache, watch signals, mismatch detection, module listing.

### MCP communication tests



```
python test\_mcp\_server.py
```

8 tests covering the full stdio JSON-RPC lifecycle: initialize, tools/list, load\_waveform, search, snapshot, transactions, ASCII rendering, unload.

### End-to-end agent workflow test



```
python test\_e2e\_mcp.py                          # auto-detects real VCD, falls back to synthetic

python test\_e2e\_mcp.py /path/to/your/waveform.vcd
```

15-step simulation of a full agent debugging session over real stdio JSON-RPC: initialize → tools/list → load → search (7 patterns) → list\_modules → snapshot → find\_transactions (all AXI channels) → render\_ascii → find\_edges/next\_edge → protocol\_check → watch\_signals (trigger) → check\_signal\_value → unload. Uses real AXI2MEM VCD when available.

### Real VCD tests



```
python test\_real\_vcd.py /path/to/your/waveform.vcd
```

10 tests against a real waveform: load performance, module/signal inventory, search patterns, snapshot at multiple times, edge detection, watch with trigger, AXI transaction analysis (5 channels), ASCII rendering, protocol check, cache performance.

### Generate a test VCD



```
python generate\_test\_vcd.py
```

Creates `test_waveform.vcd` with a simple valid-ready pipeline for quick testing.

## File Structure



```
waveform\_mcp/

├── AGENTS.md                    # Agent rules file (project context, run commands, conventions)

├── README.md                    # This file

├── WAVEFORM\_MCP\_DESIGN.md      # Detailed design document (architecture, API, roadmap)

├── waveform\_core.py             # Core library: parsing, querying, analysis (no MCP dep)

├── waveform\_mcp.py              # MCP Server: 17 tools over stdio JSON-RPC

├── debug\_waveform.py            # CLI: human-friendly command-line interface

├── generate\_test\_vcd.py         # Test VCD generator

├── test\_waveform\_core.py        # Core library test suite (12 tests)

├── test\_mcp\_server.py           # MCP communication test suite (8 tests)

├── test\_e2e\_mcp.py              # End-to-end agent workflow test (15 steps, real VCD support)

├── test\_real\_vcd.py             # Real VCD test suite (10 tests)

└── test\_waveform.vcd            # Generated test VCD (small, synthetic)
```

## Performance

Measured on a 22MB VCD (671 signals, 475us simulation):



| Operation                   | Time      |
| --------------------------- | --------- |
| Initial parse               | 2.0s      |
| Cache load (2nd time)       | 0.75s     |
| Snapshot query              | 0.1-0.6ms |
| Transaction analysis (5 tx) | 0.2ms     |
| ASCII render (80 chars)     | 0.3ms     |
| Protocol check              | 0.3ms     |

For files >100MB, use `--cache` or `use_cache=True` to avoid re-parsing.

## Troubleshooting

### "No module named 'mcp'"

Install the MCP SDK:



```
pip install "mcp\[cli]"
```

### "fsdb2vcd not found"



* Ensure Verdi is installed and `fsdb2vcd` is in PATH.

* Or set the `VERDI_HOME` environment variable.

* Or convert FSDB to VCD manually: `fsdb2vcd -i input.fsdb -o output.vcd`

### MCP server not showing up in client



* Check the path in the config is correct (use absolute paths).

* Check Python is in PATH (use full path to python.exe if needed).

* Run `python src/waveform_mcp/server.py` directly — it should start and wait for input (no errors).

* Check client logs for connection errors.

### Signal search returns too many results



* Use `exclude_constants=true` to filter out parameters and constants.

* Use more specific patterns (e.g., `awvalid` instead of `valid`).

* Use `match_mode="suffix"` for exact base-name suffix matching.

### ASCII waveform looks wrong



* Ensure the time window is correct (in picoseconds).

* For multi-bit signals, values are shown as hex labels at change points.

* Increase `width` for more detail.

## Design Document

See [docs/DESIGN.md](docs/DESIGN.md) for the full design document, including:



* Detailed architecture and design principles

* Complete API specification

* Tool-by-tool parameter reference

* FSDB support design (two approaches)

* Deployment configuration for all clients

* Extension roadmap (v1.1, v1.2, v2.0)

* Verification checklist

## License

Internal use.