# Architecture
This file holds the internal engineering view of wavepeek: non-functional requirements, module boundaries, dependencies, execution strategy, and testing strategy. It does not restate the exact CLI flag surface. For command semantics and machine-output guarantees, use `docs/public/reference/command-model.md` and `docs/public/reference/machine-output.md`.
## Non-Functional Requirements
### Performance
Performance is the highest implementation priority. Rust is used specifically to keep waveform parsing and query execution fast on large dumps.
Benchmarks are maintained through `bench/e2e/perf.py` for end-to-end CLI scenarios.
### Compatibility
The default VCD/FST tool is intended to stay OS-agnostic across Linux, macOS, and Windows. Optional FSDB support is Linux x86_64-only because it links against the local Verdi FSDB Reader SDK; see `fsdb.md`.
### Output Stability
Identical inputs must produce deterministic output.
### LLM Agent Integration
The repository ships agent-facing workflow assets plus deterministic `--json` and waveform `--jsonl` contracts so LLM clients can consume output without ad hoc parsing.
## Technical Architecture
### Technology Stack
| Language | Rust stable (MSRV 1.93) | Performance, memory safety, and predictable resource use on large dumps |
| CLI framework | `clap` derive API | Self-documenting command definitions with compile-time validation |
| Waveform parsing | `wellen` | Unified VCD/FST interface used successfully by existing waveform tooling |
| Serialization | `serde` + `serde_json` | Standard JSON rendering for machine contracts and schema export |
| Pattern matching | `regex` | Shared filtering surface for hierarchy and signal discovery |
| Error handling | `thiserror` | Typed error enums without runtime boxing |
| Build automation | Cargo + just | Cargo owns compilation; the root `justfile` exposes repository quality gates |
### High-Level Execution Layers
wavepeek is organized as three execution layers plus two shared support modules. Data flows top-down: the CLI parses arguments, the engine executes command logic, waveform commands query the waveform layer, docs/skill helpers query embedded Markdown assets, and the output module renders results.
1. **CLI layer** (`src/cli/`) parses arguments, owns help text, normalizes clap errors, and dispatches typed command structs.
2. **Engine layer** (`src/engine/`) implements command behavior, shared time handling, shared value formatting, expression-runtime helpers, and command dispatch.
3. **Waveform layer** (`src/waveform/`) is the backend-neutral facade for file opening, format detection, hierarchy traversal, sampled-value access, and candidate-time queries. Default builds dispatch VCD/FST work to the Wellen backend; feature-enabled FSDB builds can dispatch `.fsdb` inputs to the FSDB backend and native shim. FSDB-specific build and SDK details live in `fsdb.md`.
4. **Embedded docs runtime** (`src/docs/`) loads packaged public topics and the packaged agent skill from repository Markdown assets.
5. **Output module** (`src/output.rs`) owns stdout rendering for human mode, strict JSON envelope mode, and JSONL stream records.
Key architectural consequences:
- Execution is stateless. Every command opens the dump, runs once, and exits.
- The engine is format-agnostic for waveform commands. VCD/FST Wellen handling and optional FSDB Reader handling stay behind the waveform facade.
- Docs and skill helper surfaces keep their source of truth in packaged Markdown instead of duplicated Rust string tables.
- JSON contracts are stabilized through code-generated schema snapshots: `schema/output.json`, `schema/stream.json`, `schema/input.json`, and `schema/catalog.json`.
### Module Structure
```text
src/
├── lib.rs # Crate entrypoint (`run_cli`) + module ownership
├── main.rs # Thin binary wrapper around `wavepeek::run_cli()`
├── cli/ # CLI layer: argument definitions, help text, dispatch
│ ├── mod.rs # Top-level CLI struct, parse-error normalization, output handoff
│ ├── limits.rs # Shared bounded-output flag parsing (`--max`, `--max-depth`)
│ ├── info.rs # `info` command args + clap help
│ ├── scope.rs # `scope` command args + clap help
│ ├── signal.rs # `signal` command args + clap help
│ ├── value.rs # `value` command args + clap help
│ ├── change.rs # `change` command args + clap help
│ ├── property.rs # `property` command args + clap help
│ ├── extract.rs # `extract` command namespace and subcommand args + clap help
│ ├── schema.rs # `schema` command args + clap help
│ ├── docs.rs # `docs` helper command family args + clap help
│ └── skill.rs # `skill` helper command args + clap help
├── engine/ # Business logic per command
│ ├── mod.rs # Command dispatch + shared result types
│ ├── info.rs # Dump metadata extraction
│ ├── scope.rs # Hierarchy traversal with depth/filter
│ ├── signal.rs # Signal listing within scope
│ ├── value.rs # Value extraction at time point
│ ├── change.rs # Value-change tracking and engine dispatch
│ ├── expr_runtime.rs # Shared typed-expression binding/evaluation helpers
│ ├── time.rs # Shared time token parsing/validation/alignment helpers
│ ├── value_format.rs # Shared Verilog literal formatting helpers
│ ├── property.rs # Property runtime entrypoint and capture-mode execution
│ ├── extract.rs # Generic event-row extraction runtime
│ ├── schema.rs # JSON schema export
│ ├── docs.rs # Embedded docs topics/search/show/export runtime
│ └── skill.rs # Packaged agent skill print runtime
├── docs/ # Embedded docs asset runtime and export helpers
│ └── mod.rs # Topic catalog loading, search, export, and packaged skill source
├── schema_contract.rs # Canonical schema URLs and embedded schema artifacts
├── expr/ # Expression engine shared by `change`, `property`, and `extract`
│ ├── mod.rs # Public typed facade for parsing/binding/evaluation
│ ├── ast.rs # Spanned expression AST types
│ ├── diagnostic.rs # Parse/semantic/runtime diagnostic contract
│ ├── lexer.rs # Spanned tokenizer for event/logical parsing
│ ├── parser.rs # Strict typed parser
│ ├── host.rs # Host trait + signal/type/value bridge types
│ ├── sema.rs # Typed binders for event and logical expressions
│ └── eval.rs # Typed event matcher and logical evaluator
├── waveform/ # Backend-neutral waveform facade plus concrete backends
│ ├── mod.rs # Public facade, backend dispatch, and query helpers
│ ├── types.rs # Shared waveform metadata, signal, sample, and backend-facing types
│ ├── wellen_backend.rs # Default VCD/FST backend using `wellen`
│ ├── fsdb_disabled.rs # Default-build diagnostics for FSDB-looking inputs
│ ├── fsdb_backend.rs # Feature-gated FSDB backend over the native Reader shim
│ ├── fsdb_native.rs # Feature-gated Rust FFI wrapper for the native shim
│ ├── fsdb_hierarchy.rs # FSDB hierarchy normalization and kind/value mapping
│ ├── fsdb_time.rs # FSDB time-unit parsing and conversion helpers
│ └── expr_host.rs # Waveform-backed expression host bridge
├── output.rs # Shared output formatting (human, JSON envelope, JSONL)
└── error.rs # `WavepeekError` enum and exit mapping
```
### Separation of Concerns
| `cli/` | clap, dispatch, help text | waveform parsing internals, output serialization details |
| `engine/` | domain logic, waveform API, shared semantics helpers | clap parsing flow |
| `expr/` | expression AST, types, evaluation | CLI, output formatting, `wellen` |
| `waveform/` | backend dispatch, Wellen VCD/FST access, optional FSDB Reader access | CLI behavior, output formatting |
| `output` | JSON and human rendering | waveform access, clap parsing |
| `error` | all stable error variants | everything else |
### Key Dependencies
| `wellen` | ~0.20 | VCD and FST parsing | Core default waveform dependency |
| `clap` | ~4 | CLI argument parsing | Derive API for declarative CLI definitions |
| `serde` | ~1 | Serialization | Used for machine-readable output structures |
| `serde_json` | ~1 | JSON output | Envelope rendering, JSONL records, and generated schema serialization |
| `schemars` | 1.2.1 | JSON Schema generation | Derives and custom schema implementations for contract DTO definitions |
| `regex` | ~1 | Pattern matching | Shared filter support |
| `thiserror` | ~2 | Error derivation | Typed errors with explicit exit mapping |
| `cc` | ~1 | Native build integration | Build dependency used only when compiling optional FSDB support |
Development dependencies include `assert_cmd`, `predicates`, `tempfile`, and `insta` to cover integration tests, fixture handling, and snapshots.
## Expression Engine Architecture
The `change`, `property`, and `extract` commands share a typed expression stack in `src/expr/`. The language contract itself lives in `docs/public/reference/expression-language.md`; this section describes how the implementation is arranged.
The pipeline is:
input string → lexer → parser → AST → typed binding → runtime evaluation against sampled waveform values
The main components are:
- **Lexer and parser** for event and logical expression text.
- **AST and semantic types** that give the rest of the system a stable internal representation.
- **Typed binder and evaluator** that resolve names against waveform metadata and compute runtime results.
- **Waveform-backed host bridge** in `src/waveform/expr_host.rs` that exposes dump metadata and sampled values to the typed runtime without widening the public facade unnecessarily.
The current implementation status is:
- typed standalone event and logical runtimes are implemented under `src/expr/`,
- rich metadata is bridged into those runtimes through the waveform host adapter,
- production `change`, `property`, and `extract` execution reuses the same typed parser, binder, and evaluator path, and
- the older transitional compatibility parser has been retired.
## Error Handling Strategy
### Principles
- **Fail fast.** The first error stops execution.
- **Machine-parseable failures and diagnostics.** Process-level failures follow a stable `fatal: <category>: <message>` shape. Successful command diagnostics use typed JSON objects and coded human lines such as `warning[WPK-W0002]: <message>`.
- **No panics in production paths.** Recoverable failures use `Result<T, WavepeekError>`.
### Exit Behavior
`src/error.rs` owns the process-level mapping from error variants to categories and exit codes.
- Exit code `0` means success.
- Exit code `1` means user-facing errors such as bad arguments, missing signals, or invalid expressions.
- Exit code `2` means file-level failures such as open or parse errors.
Non-fatal diagnostics do not change the exit code.
## `change` Command Execution Architecture
`change` keeps one user-visible contract while choosing among several internal execution strategies.
Execution engines:
- **Baseline engine** for conservative low-overhead execution on small or simple workloads.
- **Fused engine** for broader candidate sets where more work can be shared across signals.
- **Edge-fast engine** for dense edge-trigger workloads that benefit from trigger-focused filtering.
The dispatcher chooses between those engines from internal workload estimates such as window size, candidate density, requested signal count, and trigger shape. This policy is intentionally internal and may evolve without changing the user contract.
The reason for the multi-engine design is simple: a single internal strategy could not keep latency consistently low across both tiny and large-window scenarios.
For `--jsonl`, `change` emits snapshots through a sink while the selected engine runs instead of collecting the complete result set solely for output. The human and `--json` paths use the same sink interface with a collector so they preserve the existing complete-result behavior. `property` uses the same pattern for captured rows.
## Testing Strategy
### Test Levels
| Unit tests | Individual helpers and modules in `engine/`, `expr/`, and `waveform/` | `#[cfg(test)]` plus `cargo test` | Hand-crafted inline or small `.vcd` fixtures |
| Integration tests | Full CLI invocations | `assert_cmd` suites under `tests/` | Hand fixtures plus container-provisioned artifacts under `RTL_ARTIFACTS_DIR` |
| Expression tests | Parser, binder, and evaluator behavior | Unit tests in `src/expr/` plus integration-style suites in `tests/` | Pure string cases and structured expression fixtures |
### Fixture Strategy
wavepeek uses two fixture sources:
1. **Hand-crafted VCD fixtures** for edge cases, tiny examples, and direct unit coverage.
2. **Container-provisioned representative fixtures** for realistic integration and performance scenarios.
Runtime test execution does not fetch those larger fixtures dynamically; they are provisioned by the devcontainer and CI image.
### What Integration Tests Must Assert
- deterministic stdout behavior,
- exit codes,
- stderr formatting for error cases,
- `--json` payload conformance to the envelope schema contract,
- `--jsonl` record conformance and stream-order invariants,
- human-output stability when a command-level contract explicitly fixes that formatting, and
- VCD/FST parity where equivalent queries should return the same result.
## Practical Ownership Boundaries
The architectural split matters for docs maintenance:
- `src/cli/`, `wavepeek --help`, and `wavepeek <command> --help` are the exact CLI surface authority.
- The current schema snapshots, `schema/output.json`, `schema/stream.json`, and `schema/input.json`, plus `wavepeek schema`, `wavepeek schema --stream`, and `wavepeek schema --input`, are the machine-readable contract authorities. `schema/catalog.json` maps schema families to exact published URLs.
- `docs/public/reference/` documents the user-visible semantics that code and schema alone do not explain well enough.
- this file documents internals that help contributors change implementation safely without regrowing a monolithic design doc.