{"id":34096338,"url":"https://github.com/cxzzzz/wavekit","last_synced_at":"2026-04-11T05:02:56.213Z","repository":{"id":247318982,"uuid":"823169409","full_name":"cxzzzz/wavekit","owner":"cxzzzz","description":"a fundamental Python package for digital circuit waveform analysis","archived":false,"fork":false,"pushed_at":"2026-03-09T06:40:03.000Z","size":152,"stargazers_count":9,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-09T11:04:54.661Z","etag":null,"topics":["analysis","fsdb","python","vcd"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cxzzzz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-07-02T14:23:19.000Z","updated_at":"2026-03-09T06:25:25.000Z","dependencies_parsed_at":"2026-03-09T08:07:51.286Z","dependency_job_id":null,"html_url":"https://github.com/cxzzzz/wavekit","commit_stats":null,"previous_names":["cxzzzz/wavekit"],"tags_count":3,"template":false,"template_full_name":"microsoft/python-package-template","purl":"pkg:github/cxzzzz/wavekit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cxzzzz%2Fwavekit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cxzzzz%2Fwavekit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cxzzzz%2Fwavekit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cxzzzz%2Fwavekit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cxzzzz","download_url":"https://codeload.github.com/cxzzzz/wavekit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cxzzzz%2Fwavekit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31669117,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-10T17:19:37.612Z","status":"online","status_checked_at":"2026-04-11T02:00:05.776Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analysis","fsdb","python","vcd"],"created_at":"2025-12-14T15:33:30.050Z","updated_at":"2026-04-11T05:02:56.204Z","avatar_url":"https://github.com/cxzzzz.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# wavekit\n\n[![CI](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml/badge.svg)](https://github.com/cxzzzz/wavekit/actions/workflows/python-package.yml)\n[![PyPI version](https://img.shields.io/pypi/v/wavekit.svg)](https://pypi.org/project/wavekit/)\n[![Python Versions](https://img.shields.io/pypi/pyversions/wavekit.svg)](https://pypi.org/project/wavekit/)\n[![License](https://img.shields.io/github/license/cxzzzz/wavekit.svg)](LICENSE)\n\n**Wavekit** is a fundamental Python library for digital waveform analysis. By seamlessly converting VCD and FSDB data into Numpy arrays, it empowers engineers to perform high-performance signal processing, protocol analysis, and automated verification with ease.\n\n\u003e 🤖 **AI Integration**: [wavekit-mcp](https://github.com/cxzzzz/wavekit-mcp) — MCP server for AI-assisted waveform analysis. Let AI load signals and run pattern matching — no manual coding required.\n\n## ✨ Features\n\n- **High Performance \u0026 Easy Loading**: Cython-optimized VCD/FSDB parsers with Numpy-backed storage for speed and memory efficiency, plus flexible batch signal extraction via brace expansion, integer ranges, and regular expressions.\n- **Rich Analysis Tools**: Numpy-like API for arithmetic, masking, bit-field manipulation, edge detection, and time/cycle slicing — compose complex signal queries in just a few lines.\n- **Pattern Matching**: NFA-based temporal pattern engine that scans waveforms in a single pass to extract protocol transactions, measure latencies, and detect timing violations.\n\n## 📦 Installation\n\n```bash\npip install wavekit\n```\n\n**Note**: To read FSDB files, the Verdi runtime library (`libNPI.so`) must be available at runtime. Configure via:\n- `WAVEKIT_NPI_LIB` — direct path to `libNPI.so`\n- `VERDI_HOME` — Verdi installation directory (searches `$VERDI_HOME/share/NPI/lib/...`)\n- `LD_LIBRARY_PATH` — system library search path\n\n## 🚀 Quick Start\n\n\u003e The examples below use placeholder filenames such as `sim.vcd`. Replace them with the path to your own VCD or FSDB file, and adjust signal paths to match your design hierarchy.\n\n### 1. Batch Signal Extraction\n\nUse brace expansion or regular expressions to load multiple related signals in one call.\n\n```python\nfrom wavekit import VcdReader\n\nwith VcdReader(\"jtag.vcd\") as f:\n    # Brace expansion: load J_state and J_next in one call\n    # Returns: { ('state',): Waveform, ('next',): Waveform }\n    waves = f.load_matched_waveforms(\n        \"tb.u0.J_{state,next}[3:0]\",\n        clock_pattern=\"tb.tck\",\n    )\n\n    # Regex mode (@ prefix): capture groups become dict keys\n    waves = f.load_matched_waveforms(\n        r\"tb.u0.@J_([a-z]+)\",\n        clock_pattern=\"tb.tck\",\n    )\n```\n\n---\n\n### 2. Signal Analysis\n\nWaveforms support Numpy-style arithmetic, masking, and edge detection out of the box.\n\n```python\nimport numpy as np\nfrom wavekit import VcdReader\n\nwith VcdReader(\"fifo_tb.vcd\") as f:\n    clock = \"fifo_tb.clk\"\n    depth = 8\n\n    w_ptr = f.load_waveform(\"fifo_tb.s_fifo.w_ptr[2:0]\", clock=clock)\n    r_ptr = f.load_waveform(\"fifo_tb.s_fifo.r_ptr[2:0]\", clock=clock)\n    wr_en = f.load_waveform(\"fifo_tb.s_fifo.wr_en\",      clock=clock)\n\n    occupancy = (w_ptr + depth - r_ptr) % depth\n    print(f\"Average occupancy: {np.mean(occupancy.value):.2f}\")\n\n    # Filter to cycles where a write is active\n    write_occ = occupancy.mask(wr_en == 1)\n\n    # Detect write bursts\n    burst_cycles = wr_en.rising_edge()\n```\n\n---\n\n### 3. Expression Evaluation\n\nCompute waveform expressions directly from signal path strings without loading each signal manually.\n\n```python\nfrom wavekit import VcdReader\n\nwith VcdReader(\"fifo_tb.vcd\") as f:\n    # Single mode: paths must each match exactly one signal\n    occupancy = f.eval(\n        \"fifo_tb.s_fifo.w_ptr[2:0] - fifo_tb.s_fifo.r_ptr[2:0]\",\n        clock=\"fifo_tb.clk\",\n    )\n\n    # Zip mode: brace patterns expand per key, evaluated once per match\n    # Returns: { (0,): Waveform, (1,): Waveform, (2,): Waveform, (3,): Waveform }\n    occupancies = f.eval(\n        \"tb.fifo_{0..3}.w_ptr[2:0] - tb.fifo_{0..3}.r_ptr[2:0]\",\n        clock=\"tb.clk\",\n        mode=\"zip\",\n    )\n```\n\n---\n\n### 4. Pattern Matching\n\nDescribe a temporal sequence of events; the engine finds all matching transactions in one pass.\n\n**AXI-lite Read Latency**\n\n```python\nfrom wavekit import VcdReader, Pattern\n\nwith VcdReader(\"axi_tb.vcd\") as f:\n    clk     = \"tb.clk\"\n    arvalid = f.load_waveform(\"tb.dut.arvalid\",     clock=clk)\n    arready = f.load_waveform(\"tb.dut.arready\",     clock=clk)\n    rvalid  = f.load_waveform(\"tb.dut.rvalid\",      clock=clk)\n    rready  = f.load_waveform(\"tb.dut.rready\",      clock=clk)\n    rdata   = f.load_waveform(\"tb.dut.rdata[31:0]\", clock=clk)\n\nresult = (\n    Pattern()\n    .wait(arvalid \u0026 arready)   # AR handshake → start\n    .wait(rvalid  \u0026 rready)    # R  handshake → end\n    .capture(\"rdata\", rdata)\n    .timeout(256)\n    .match()\n)\n\nvalid = result.filter_valid()\nprint(f\"Read latencies (cycles): {valid.duration.value}\")\nprint(f\"Read data: {valid.captures['rdata'].value}\")\n```\n\n**AXI Write Burst (multi-beat)**\n\n```python\nbeat = Pattern().wait(wvalid \u0026 wready).capture(\"beats[]\", wdata)\n\nresult = (\n    Pattern()\n    .wait(awvalid \u0026 awready)   # AW handshake → burst start\n    .loop(beat, until=wlast)   # collect beats until wlast\n    .timeout(512)\n    .match()\n)\n\nfor i, inst in enumerate(result.filter_valid()):\n    print(f\"Burst {i}: {len(inst.captures['beats'])} beats\")\n```\n\n**Stall Detection**\n\n```python\nstall = valid \u0026 (ready == 0)\n\nresult = (\n    Pattern()\n    .wait(stall.rising_edge())             # stall begins\n    .loop(Pattern().delay(1), when=stall)  # wait while stalling\n    .match()\n)\n\nstalls = result.filter_valid()\nprint(f\"Stall durations: {stalls.duration.value} cycles\")\n```\n\n---\n\n## 📖 API Reference\n\n### Reader\n\n| Method | Description |\n|--------|-------------|\n| `VcdReader(file)` / `FsdbReader(file)` | Open a waveform file. Use as a context manager. `FsdbReader` requires Verdi runtime (`WAVEKIT_NPI_LIB`, `VERDI_HOME`, or `LD_LIBRARY_PATH`). |\n| `reader.load_waveform(signal, clock, ...)` | Load one signal sampled on every clock edge. Returns `Waveform`. |\n| `reader.load_matched_waveforms(pattern, clock_pattern, ...)` | Batch-load signals matching a brace/regex pattern. Returns `dict[tuple, Waveform]`. |\n| `reader.eval(expr, clock, mode='single'\\|'zip', ...)` | Evaluate an arithmetic expression with embedded signal paths. |\n| `reader.get_matched_signals(pattern)` | Resolve a pattern to signal paths without loading data. |\n| `reader.top_scope_list()` | Return root `Scope` nodes of the signal hierarchy. |\n\n**Pattern syntax** used in signal paths:\n\n| Syntax | Example | Effect |\n|--------|---------|--------|\n| `{a,b,c}` | `sig_{read,write}` | Enumerate named variants |\n| `{N..M}` | `fifo_{0..3}.ptr` | Integer range |\n| `{N..M..step}` | `lane_{0..6..2}` | Stepped range |\n| `@\u003cregex\u003e` | `@([a-z]+)_valid` | Regex with capture groups |\n| `$ModName` | `tb.$fifo_unit.ptr` | Match a direct-child scope by module/definition name (FSDB only) |\n| `$$ModName` | `tb.$$fifo_unit.ptr` | Match any-depth descendant scope by module/definition name (FSDB only) |\n\n---\n\n### Waveform\n\nA `Waveform` wraps three parallel numpy arrays (`.value`, `.clock`, `.time`). All operations return a new `Waveform`.\n\n**Arithmetic \u0026 comparison**: `+`, `-`, `*`, `//`, `%`, `**`, `/`, `\u0026`, `|`, `^`, `~`, `==`, `!=`, `\u003c\u003c`, `\u003e\u003e`\n\n**Filtering \u0026 slicing**\n\n| Method | Description |\n|--------|-------------|\n| `wave.mask(mask)` | Keep samples where a boolean Waveform or array is True |\n| `wave.filter(fn)` | Keep samples where `fn(value)` is True |\n| `wave.cycle_slice(begin, end)` | Trim to clock cycle range `[begin, end)` |\n| `wave.time_slice(begin, end)` | Trim to simulation time range |\n| `wave.slice(begin_idx, end_idx)` | Trim by array index |\n| `wave.take(indices)` | Select samples at given indices |\n\n**Transformation**\n\n| Method | Description |\n|--------|-------------|\n| `wave.map(fn, width, signed)` | Element-wise transform |\n| `wave.unique_consecutive()` | Remove consecutive duplicates |\n| `wave.downsample(chunk, fn)` | Aggregate into chunks |\n| `wave.as_signed()` / `wave.as_unsigned()` | Reinterpret signedness |\n\n**Bit manipulation**\n\n| Method / Syntax | Description |\n|-----------------|-------------|\n| `wave[high:low]` | Extract bit field (Verilog convention, returns unsigned) |\n| `wave[n]` | Extract single bit |\n| `wave.split_bits(n)` | Split into n-bit groups (LSB first) |\n| `Waveform.concatenate([w0, w1, ...])` | Concatenate (w0 = LSB) |\n| `wave.bit_count()` | Population count |\n\n**Edge detection** (1-bit only)\n\n| Method | Description |\n|--------|-------------|\n| `wave.rising_edge()` | True at 0→1 transitions |\n| `wave.falling_edge()` | True at 1→0 transitions |\n\n---\n\n### Pattern\n\n| Method | Description |\n|--------|-------------|\n| `.wait(cond, guard=None, channel=None)` | Block until `cond` is True. `guard` is checked each waiting cycle. `channel` enforces FIFO ordering among concurrent instances. |\n| `.delay(n, guard=None)` | Advance `n` cycles. `delay(0)` is a no-op. |\n| `.capture(name, signal)` | Record signal value at current cycle. `name[]` appends to a list. |\n| `.require(cond)` | Assert condition; fail with `REQUIRE_VIOLATED` if False. |\n| `.loop(body, *, until=None, when=None)` | `until`: do-while (exit when True after body). `when`: while (exit when False before body). |\n| `.repeat(body, n)` | Execute body exactly `n` times. `n` may be a callable. |\n| `.branch(cond, true_body, false_body)` | Conditional branch. |\n| `.timeout(max_cycles)` | Terminate unfinished instances with `TIMEOUT`. |\n| `.match(start_cycle=None, end_cycle=None)` | Run the engine; return `MatchResult`. |\n\n**`MatchResult`**\n\n| Field | Description |\n|-------|-------------|\n| `.start` / `.end` | Start and end cycle of each match (both inclusive). |\n| `.duration` | `end - start + 1` cycles. |\n| `.status` | `MatchStatus.OK`, `TIMEOUT`, or `REQUIRE_VIOLATED`. |\n| `.captures` | `dict[str, Waveform]` of captured values. |\n| `.filter_valid()` | Return only `OK` matches. |\n\n---\n\n## 🛠️ Development\n\nThis project uses [Poetry](https://python-poetry.org/) for dependency management and packaging.\n\n### Setup\n\n```bash\ngit clone https://github.com/cxzzzz/wavekit.git\ncd wavekit\npoetry install\n```\n\n### Testing\n\nTests are located in the `tests/` directory and run with [pytest](https://pytest.org/).\n\n```bash\n# Run all tests\npoetry run pytest\n\n# Run a specific test file\npoetry run pytest tests/test_pattern.py\n\n# Run with verbose output\npoetry run pytest -v\n```\n\n### Linting \u0026 Formatting\n\nThis project uses [Ruff](https://github.com/astral-sh/ruff) for linting and formatting.\n\n```bash\n# Check for lint errors\npoetry run ruff check .\n\n# Check formatting (no changes)\npoetry run ruff format --check .\n\n# Auto-fix formatting\npoetry run ruff format .\n```\n\n### Type Checking\n\n```bash\npoetry run mypy .\n```\n\n## 🤝 Contributing\n\nContributions are welcome! Please open an issue to discuss a bug or feature request before submitting a pull request. When contributing code, make sure all tests pass and the linter reports no errors:\n\n```bash\npoetry run pytest\npoetry run ruff check .\npoetry run ruff format --check .\n```\n\n## 📄 License\n\nThis project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcxzzzz%2Fwavekit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcxzzzz%2Fwavekit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcxzzzz%2Fwavekit/lists"}