agentleFS
Sign inSign up

TraceWeave

gokeshenzhen/TraceWeave/AGENTS.md

TraceWeave is a local EDA/RTL simulation-debugging project for semiconductor designs and simulation artifacts that the user owns or is authorized to analyze. VCD and FSDB are local digital simulation waveform databases. In this repository, terms such as scan, sweep, handshake, protocol, interface, payload, trace, driver, load, and connectivity refer to digital-logic signals, bus behavior, waveform transitions, and elaborated-netlist relationships. protected IP means semiconductor intellectual property. narrowconditioninjection is an RTL structural-analysis category for control-logic conditions. Repository tasks are limited to local…

AGENTS.md116 starsChanged 13 days ago
  • Reads credentials
  • Installs packages
# Repository Guidance

## Domain Context and Authorized Scope

TraceWeave is a local EDA/RTL simulation-debugging project for semiconductor designs and simulation artifacts that the user owns or is authorized to analyze.

`VCD` and `FSDB` are local digital simulation waveform databases.

In this repository, terms such as `scan`, `sweep`, `handshake`, `protocol`, `interface`, `payload`, `trace`, `driver`, `load`, and `connectivity` refer to digital-logic signals, bus behavior, waveform transitions, and elaborated-netlist relationships.

`protected IP` means semiconductor intellectual property. `narrow_condition_injection` is an RTL structural-analysis category for control-logic conditions.

Repository tasks are limited to local source code, local waveform and log files, test fixtures, and locally configured EDA tools. Do not access unrelated external systems or resources.

For performance optimizations:

1. Establish a measurable baseline before making substantial implementation changes when practical.

2. Prefer preserving public MCP tool interfaces and output schemas. Intentional interface or schema changes are allowed when explicitly requested or when they provide a justified architectural benefit; update callers, tests, documentation, and compatibility handling accordingly.

3. Preserve FSDB thread-safety and observable cancellation and timeout behavior. Changes to locking, scheduling, worker, or execution models are allowed when justified by evidence and covered by focused regression tests.

4. Add regression tests appropriate to the behavioral risk of the change, and use reproducible before/after benchmarks for performance-sensitive changes.

5. Report the tested workload, benchmark conditions, measured results, memory impact when relevant, and any behavioral or compatibility trade-offs before claiming a performance improvement.

## Installation Discipline

When a user supplies this repository URL and explicitly asks to install
TraceWeave, treat that as the repository-local full EDA profile unless the user
explicitly requests the portable PyPI profile. First run the read-only check:

```bash
bash scripts/install.sh --check
```

If the full profile is missing or incompatible, run `bash scripts/install.sh`
only as part of that requested installation. It orchestrates the existing
`setup_source_graph.sh -> setup_fsdb.sh -> verify_fsdb.sh` workflow, then checks
the repository MCP runtime. It never edits shell startup files or Codex,
Claude, or Copilot configuration; `--print-config` only prints an absolute-path
template. Do not run repository FSDB scripts to extend a PyPI installation. A
portable installation uses `pip install traceweave-mcp` or
`pip install "traceweave-mcp[source-graph]"`, does not include the FSDB wrapper,
and is not the default when the repository URL was supplied. Do not install
dependencies during ordinary analysis/debug tasks, and do not modify a user's
MCP client configuration unless the user explicitly requests that additional
action.

## TraceWeave Usage

When the task involves simulation logs or waveforms (VCS/Xcelium logs, FSDB/VCD), the default toolchain is:

`get_sim_paths -> build_tb_hierarchy + scan_structural_risks -> parse_sim_log -> sweep_handshakes -> recommend_failure_debug_next_steps`

Rules:

- `build_tb_hierarchy` and `scan_structural_risks` must run in parallel on the same `compile_log`
- `scan_structural_risks` should not be skipped by default
- It may only be skipped if the user explicitly asks to skip it
- `scan_structural_risks.analysis_mode` is a per-call argument, not an environment setting. Omit it for `auto`: run/reuse lexical rules and reuse applicable semantic results or scoped query IR without a cold frontend build. `fast` runs/reuses lexical rules only. Use `deep` when semantic evidence is needed; it may reuse an exact result or launch the budgeted, license-free Slang worker. `semantic.status="not_run"` does not mean the lexical scan was skipped or the semantic checks passed.
- Read `lexical_coverage_status`, `semantic.status/gaps` and `semantic.propagation` separately. `semantic_scope` selects basic semantic facts; frontend elaboration may still span the compile context. Explicit `propagated_constant` / `constant_control` categories require a bounded full writer inventory even for scoped output. Missing drivers, sequential/initial state and conflicting or unmodeled writes cannot become permanent ties. Facts are investigation evidence, not confirmed defects; display truncation is distinct from analysis limits.
- Structural scans cache exact source/context results before output trimming. Scoped deep scans may publish compatible compact IR for Source Graph queries through the normal identity/scope/capability checks; `semantic.query_artifact_status` reports reuse or bypass. The default parallel hierarchy/scan workflow never waits for a hierarchy or forces full-design IR. NPI retains its KDB and priority; scan/Source Graph facts do not become NPI facts. See `README.zh.md` and `docs/architecture.md` for per-call examples and budgets.
- Run `sweep_handshakes` after `parse_sim_log` whenever a failed run has a waveform; skip only when no waveform exists or the user asks. It returns AHB and valid/ready facts, not a verdict. Always inspect `coverage_status`: `zero_coverage` checked nothing; `truncated`/`degraded` is partial, so `flagged_count=0` is not clean. Retry only a `suggested_next_action` that changes scope/window/edge/interface cap; otherwise report the missing prerequisite instead of replaying the same call. Use `finding_summary` before opening all rows. Never collapse global findings plus a clean targeted interface into “protocol clean”; state both. Any `transition_data_truncated` row forces non-complete coverage—narrow the FSDB window and never treat prefix zero counts as clean.
- Do not analyze or recommend fixes before MCP output is available
- On protocol or scoreboard mismatches, carry at least two competing hypotheses and verify the opposite side with waveform evidence before assigning root cause; state which sides were checked. See `docs/workflow.md`.

## LSF-Only NPI User Setup

When a user says Verdi/NPI licenses are available only on LSF execution nodes, explain that TraceWeave remains local by default and give this direct setup (replace `digital` with the user's licensed queue):

```bash
export TRACEWEAVE_NPI_EXECUTION=lsf
export TRACEWEAVE_NPI_LSF_QUEUE="digital"
```

Do not tell the user to create or overwrite a generic `LSF_QUEUE`. TraceWeave does not read it. Only when the site already exports `LSF_QUEUE` may the user optionally map that existing value:

```bash
export TRACEWEAVE_NPI_LSF_QUEUE="$LSF_QUEUE"
```

For a terminal-launched Codex whose parent already contains the namespaced queue, forward it with `env_vars = ["TRACEWEAVE_NPI_LSF_QUEUE"]` and put `TRACEWEAVE_NPI_EXECUTION = "lsf"` under `[mcp_servers.TraceWeave.env]`. If the Codex parent does not inherit that environment, omit the queue from `env_vars` and set a fixed `TRACEWEAVE_NPI_LSF_QUEUE = "digital"` under the same `env` table. For Claude Code, put both fixed namespaced values directly in the TraceWeave server's `"env"` object. Restart or reconnect the MCP server after changing environment/configuration. The compile log, source/include inputs, TraceWeave installation, staging directory, and `TRACEWEAVE_CACHE_DIR` (including the generated KDB) must be visible at the same absolute paths on submission and compute nodes. After setup, run an explicit driver/load/path query and confirm `backend_status` reports `execution_mode="lsf"`, `scheduler_status="completed"`, `worker_status="completed"`, and `actual_backend="verdi_npi"`; otherwise inspect `fallback_reason`. On an Xcelium cache miss, also run `build_kdb` and confirm its top-level execution receipt reports the same LSF completion labels. See `README.md#lsf-only-npi-licenses` or `README.zh.md#仅执行节点可用的-npi-license` for complete bash/tcsh examples and optional settings.

## Debug Discipline

`docs/debug-discipline.md` is the module-type-agnostic debug discipline — a reusable, copy-paste prompt for any failure (protocol/bus, datapath/algorithm, or control/FSM), not only protocol mismatches. Follow it before assigning a root cause. Its through-line: good discipline, not more tool output, is what turns correct perception into a correct root cause — so ground every claim in a trace/log fact, backtrace the symptom to its originating driver, respect a tool's objective exclusions, keep two hypotheses alive and check the opposite side, and label uncertainty honestly. The doc holds the canonical five rules, maps each to the relevant TraceWeave tools, and specializes "the opposite side" per module type; the protocol-specific form of the same discipline lives in `docs/workflow.md`.

## First-Read Files

For any new session, read these files first to build the project map:

1. `docs/architecture.md`
2. `README.md`
3. `server.py`
4. `config.py`
5. `src/path_discovery.py`
6. `src/compile_log_parser.py`
7. `src/tb_hierarchy_builder.py`
8. `src/analyzer.py`
9. `src/log_parser.py`
10. `src/fsdb_parser.py`
11. `src/vcd_parser.py`
12. `src/fsdb_signal_index.py`
13. `src/signal_driver.py`
14. `src/signal_load.py`
15. `src/connectivity_backend.py`
16. `src/connectivity_ir.py`
17. `src/connectivity_query.py`
18. `src/source_graph_adapter.py`
19. `src/source_graph_backend.py`
20. `src/source_graph_contract.py`
21. `src/source_graph_production.py`
22. `src/source_graph_runtime.py`
23. `src/source_graph_worker.py`
24. `src/slang_connectivity_projector.py`
25. `src/verdi_backend.py`
26. `src/verdi_npi_backend.py`
27. `src/npi_lsf.py`
28. `src/npi_worker.py`
29. `src/kdb_builder.py`
30. `src/waveform_batch.py`
31. `src/structural_scanner.py`
32. `src/x_trace.py`
33. `src/cycle_query.py`
34. `src/schemas.py`
35. `src/problem_hints.py`
36. `src/hierarchy_provider.py`
37. `src/hierarchy_handles.py`
38. `src/handle_tools.py`
39. `src/cursor_store.py`
40. `src/timespec.py`
41. `src/verify_condition.py`
42. `src/cancellation.py`
43. `src/operation_metrics.py`
44. `src/compile_source_index.py`
45. `src/compile_source_runtime.py`
46. `src/compile_session_snapshot.py`
47. `src/design_identity.py`
48. `src/structural_scan_runtime.py`
49. `src/structural_semantics.py`
50. `src/structural_semantic_runtime.py`
51. `src/structural_semantic_worker.py`
52. `src/structural_artifact.py`
53. `src/structural_propagation.py`

If the task involves FSDB or native integration, also read:

- `fsdb_wrapper.cpp`
- `scripts/build_wrapper.sh`

If the task involves behavior validation or regression checks, also read:

- `tests/test_log_parser.py`
- `tests/test_compile_log_parser.py`
- `tests/test_fsdb_parser.py`
- `tests/test_fsdb_runtime.py`
- `tests/test_vcd_parser.py`
- `tests/test_tb_hierarchy_builder.py`
- `tests/test_path_discovery.py`
- `tests/test_analyzer.py`
- `tests/test_signal_driver.py`
- `tests/test_signal_load.py`
- `tests/test_connectivity_backend.py`
- `tests/test_verdi_backend.py`
- `tests/test_verdi_npi_backend.py`
- `tests/test_npi_lsf.py`
- `tests/test_kdb_builder.py`
- `tests/test_waveform_batch.py`
- `tests/test_structural_scanner.py`
- `tests/test_structural_scan_runtime.py`
- `tests/test_structural_semantics.py`
- `tests/test_structural_artifact.py`
- `tests/test_structural_propagation.py`
- `tests/test_x_trace.py`
- `tests/test_cycle_query.py`
- `tests/test_schemas.py`
- `tests/test_problem_hints.py`
- `tests/test_server.py`
- `tests/test_server_concurrency.py`
- `tests/test_diagnostic_snapshot.py`
- `tests/test_operation_metrics.py`

## Repository Focus

- `server.py` is the composition root and MCP entry point.
- `explain_signal_driver`, `find_signal_loads`, `trace_signal_path`, and `trace_x_source` route through `trusted Verdi NPI -> bounded on-demand Source Graph -> Legacy Static`. The adapter builds content-fingerprinted requests without full-design enumeration. `compile_session_snapshot` captures private digest/stat/marker facts during hierarchy reads; the first Source Graph call reuses current digests and hashes only unseen inputs, while changed/incomplete capture reports `compile_session_snapshot_changed` and requires a hierarchy rebuild. Production uses isolated workers, bounded compact-IR memory caching, one-build admission, and exact-key single-flight. An incomplete key may share only its live flight; one successful content-anchored result may provide a consumed-once 60-second/512-MiB exact handoff that stays `bypass_incomplete_key`, never dominates another scope, and never enters memory/disk cache. One waiter cancellation preserves shared work; final-waiter cancellation stops it. The bounded persistent Slang semantic session is default-off and gated by `scripts/soak_source_graph_semantic_session.py`. The optional exact disk tier (`TRACEWEAVE_SOURCE_GRAPH_DISK_CACHE=1`) performs no startup scan, validates fresh ordered inputs/options/tops/snapshots before every hit, keeps entries private, and treats corruption as a miss. Adapter/query/Static work never holds a wave lock; cancellation never advances fallback. Receipts expose backend attempts, fixed reasons, coverage/fingerprints, and `effective_timeout_sec`; facts come from one backend/artifact only. `trace_x_source` rebuilds an exact ancestor union and restarts from the original signal whenever a new X target falls outside the current artifact.
- `src/cancellation.py` + `server._run_in_wave_thread`: synchronous wave scans run off the event loop. Locks are acquired inside the worker—one process-global lock for all FSDB handles, one per VCD path; dual-path diff locks in stable order. `trace_x_source` releases the wave lock before connectivity work. Static/Source Graph are lock-free cancellable workers; local NPI remains synchronous and LSF keeps its worker path. Any backend change discards the partial chain and restarts from the original signal; incomplete positive Source Graph edges remain partial, but only a complete negative may terminate. Cancellation sets a per-call event; long scan/discovery loops checkpoint and re-raise `OperationCancelled`. Native search is interrupted only after the native call returns. A cancelled lock waiter never touches the parser; an interactive FSDB request may preempt a background sweep at its next checkpoint without overlapping FFR access. Operation metrics accept only whitelisted numeric aggregates/fixed phases, never paths/scopes/names/values. Worker results return to the event loop, which remains the sole writer of dispatch/session state. Preserve regressions in `tests/test_server_concurrency.py` and `tests/test_source_graph_trace_public_routing.py`.
- Full-sweep operation metrics add only privacy-safe aggregates: total sweep time, planned/attempted/completed interface counts, unique clock/signal counts, aggregate/max inspect time, clock-vs-signal transition read count/total/max, edge-extraction/value-sampling time, and transition-truncated interface count. FSDB sweeps also expose aggregate native phase timings, group/fallback counts, transition/output volume, sampling shape, cache peaks, result build/serialization cost, and process RSS start/peak/end. They remain numeric/fixed-label only. They never record paths, scopes, signal names, search keywords, or values.
- `src/path_discovery.py` owns compile/sim/wave path discovery.
- `src/compile_source_index.py` + `src/compile_source_runtime.py` provide the
  transient compile-source sharing layer used by the default parallel
  `build_tb_hierarchy` / `scan_structural_risks` workflow. Exact compile
  identities single-flight one bounded preload (128 MiB / 32,768 files by
  default); both consumers reuse immutable decoded text plus its exact raw-byte
  digest/stat/marker evidence, and the final lease immediately clears all
  source bodies. Capacity/config failure is an optimization bypass, not a
  functional blocker. Cancellation of one waiter preserves other consumers;
  final-waiter cancellation stops preload. An explicit bounded bootstrap may
  join an already-active exact index but never creates a full-design preload on
  a miss. Public metrics contain numeric counters and fixed dispositions only.
- `src/compile_log_parser.py` and `src/tb_hierarchy_builder.py` drive compile-log-based hierarchy extraction. Repeated module/UVM descendants retain the compatibility nested-dict schema but share immutable children mappings as an internal object DAG. Logical stats memoize shared subtree summaries; build metrics distinguish logical nodes, reachable physical nodes, allocations, cache hits, and reuse. Full-build instance candidates/nodes also carry fixed edge origin/status/gaps: only complete/positive-local edges enter the tree; explicit/implicit generate, instance-array, and bind candidates stay diagnostic rather than becoming fictitious flat paths; parameter overrides keep the safe direct edge while Slang owns specialization; duplicate definitions stop at an ambiguous node without guessed descendants. Query-relevant hierarchy gaps flow into Source Graph objective exclusions, while the parameter-only gap is informational because the semantic frontend elaborates it. Bounded bootstrap uses the same positive-only rule and blocks a direct unresolved semantic edge. The optional path-specific NPI `file:line` overlay detects aliases and uses copy-on-write only on annotated paths, so provenance never bleeds between repeated instances. `scripts/benchmark_hierarchy_materialization.py` provides the fresh-process eager/shared scaling oracle; `scripts/benchmark_tb_hierarchy.py --no-hierarchy-template-sharing` is the real-design A/B control.
- `src/hierarchy_provider.py` is the internal bounded hierarchy contract. The compile-log provider wraps the compatibility `component_tree` and resolves a target by exact child lookup without sibling/full-design enumeration. The Connectivity-IR provider reuses the prepared query engine's immutable instance/definition indexes, preserves generate scopes and parameter specializations through `InstanceDecl` parent bindings, and lazily indexes direct children. The NPI provider is an explicit development/differential path: before any KDB load it caps the target's dotted prefixes (default 256, hard maximum 1,024), then consumes only exact `get_inst()` + direct definition/source facts; it never performs a top/sibling walk and always carries `npi_hierarchy_fragment_bounded`, so its negative coverage is incomplete. Provider-local stable instance IDs are scoped by immutable design identity. Basic `build_tb_hierarchy` never imports or requires the optional Slang frontend, the normal hierarchy build never invokes the NPI semantic provider, and public hierarchy/Source Graph receipt schemas remain unchanged. `scripts/benchmark_hierarchy_provider_soc.py` is the opt-in fresh-process NPI/Slang differential harness and emits only hashed facts plus numeric resource measurements.
- `src/analyzer.py` and `src/log_parser.py` contain the core failure analysis logic.
- `src/signal_driver.py` backtracks RTL drivers from waveform signal paths.
- `src/signal_load.py` resolves load/fanout for a signal — the symmetric counterpart to `signal_driver`.
- `src/connectivity_backend.py` defines the `ConnectivityBackend` protocol; `select_backend()` returns local Verdi NPI when a KDB is found, optional LSF NPI when `TRACEWEAVE_NPI_EXECUTION=lsf`, otherwise Static. NPI/worker failures degrade transparently; the dispatch layer never sees Verdi- or scheduler-specific exceptions.
- `src/verdi_backend.py` probes for Verdi KDB / license environment; emits per-simulator `kdb_hint` when KDB is missing.
- `src/npi_lsf.py` + `src/npi_worker.py` implement opt-in LSF placement for licensed Verdi/NPI work: explicit NPI driver/load/path queries plus `build_kdb` cache misses and forced rebuilds. Default execution is `local`; `lsf` reads its queue only from the namespaced `TRACEWEAVE_NPI_LSF_QUEUE`. Sites may map a team variable in shell startup (for example `export TRACEWEAVE_NPI_LSF_QUEUE="$LSF_QUEUE"`), but TraceWeave does not interpret generic scheduler variables. The parent writes a versioned request in a private shared staging directory and submits one `bsub -K` worker with `shell=False`. Connectivity workers call the exact local NPI core and never Static; only the parent falls back. KDB workers run the exact `vericom` + `elabcom` builder and never fall back to a local licensed build. Exact KDB cache hits remain local filesystem reads and submit no job. Scheduler wait runs under `server._run_in_cancellable_thread`, and cancellation/timeout uses an identity-free random job name for bounded `bkill -J` plus local waiter termination. Public receipts contain fixed `execution_mode`/`scheduler_status`/`worker_status`/`fallback_reason` labels only — never queue, host, command, or license text. The initial scope deliberately excludes the optional hierarchy source overlay so `build_tb_hierarchy` does not submit an implicit batch job.
- `src/verdi_npi_backend.py` implements NPI driver/load/path queries and the optional hierarchy `file:line` overlay; it lazily loads `$VERDI_HOME` `pynpi` and reuses loaded designs. NPI may cross instance boundaries with `fan_in_reg_list`; path uses `sig_to_sig_conn_list`, while Static honestly reports `static_backend_no_path_api`. Preserve the TB-driver misattribution guard: before any recursive fan-in, compare NPI's **original** `driver_list` with the same net's own `load_list`, normalized only for bit indexing. A byte-identical driver/load is a consumer alias, not a source; prefer another genuine runtime driver, otherwise return `driver_status="testbench_driven"`, `cross_check.conflict`, and `driver_is_load_real_driver_is_testbench`. Never key this decision on recursive fan-in or widen the comparison to fan-out: the former misses downstream-load aliases and the latter false-positives self-feedback counters. Initial-value blocks are not genuine runtime drivers. Keep this pure logic covered without a live KDB, including `recursive=True`.
- Recursive NPI driver traversal is bounded inside native `fan_in_reg_list()` through the official global `FAN_IN` callback: at most 4,096 admitted states and 32 public driver facts. Registration/traversal/reset is process-serialized; reset runs on success, failure, and cancellation. Missing or failed callback support never restores an unbounded call. NPI and Source Graph driver results share the additive backend-neutral `traversal` receipt; bounded positive facts use `driver_status="partial"` and remain authoritative, while zero-fact/incomplete negatives continue through the fallback chain. `trace_x_source` preserves that receipt and stops with `driver_traversal_incomplete` rather than presenting a bounded prefix as an exclusive root cause.
- `src/connectivity_query.py` keeps Source Graph path search resource-safe after IR preparation. Its deterministic BFS queues only the current `SignalSelection`; each first-discovered state retains one predecessor hop, and a found shortest path is reconstructed once with cancellation checkpoints. Never put a complete path prefix in every queue entry: the 4,096-edge comb benchmark demonstrated both superlinear CPU and roughly 2x process RSS from shared-prefix duplication. Public path ordering, coverage, traversal/output caps, schemas, and fingerprints are unchanged.
- `src/kdb_builder.py` provides the `build_kdb` MCP tool: when a Verdi KDB is missing (typical for Xcelium / `xrun` flows), it runs `vericom -kdb` + `elabcom -elab kdb` against the file list parsed from the compile log, caches the result under `$TRACEWEAVE_CACHE_DIR/kdb/<hash>/`, and writes a runnable `build.sh` reproducer. In `TRACEWEAVE_NPI_EXECUTION=lsf` mode, a cache miss or forced rebuild is sent through the LSF worker and cannot silently build locally; a cache hit stays local because it invokes no Verdi executable or license. The probe in `verdi_backend.py` picks up the cache transparently as `kdb_flow: "traceweave_cached"`. Default-on; opt out with `TRACEWEAVE_AUTO_KDB=0`.
- `src/waveform_batch.py` exposes `WaveformBatchReader` for time-window multi-signal reads, with FSDB and VCD implementations sharing one shape.
- `src/structural_scanner.py` and `src/x_trace.py` are first-class analysis capabilities.
- `src/cycle_query.py` provides cycle-aligned signal sampling. `get_signals_by_cycle` slices by cycle index (capped); `sample_signals_on_edges` samples every clock edge inside a *time window* (the shared substrate for window-scoped relational analysis like `inspect_handshake`). Both reuse one private edge-sampling core. A full sweep may pass a private `EdgeSamplingSession`: all interfaces in one clock group reuse the clock transition list, extracted edges, and sample-time vector; only signals with multiple consumers are cached, and a remaining-consumer count evicts each cached signal immediately after its last use. Sessions are consumed one clock group at a time so large transition lists do not accumulate across clocks. This internal path preserves standalone sampler behavior, cancellation checkpoints, transition-truncation propagation, and public schemas. It also provides `annotate_center_transients`: a pure post-process over a `get_signals_around_time` result that flags a `value_at_center` which is a **sub-cycle transient** (the unmistakable dip-and-return `X→glitch→X` signature of a combinational mux re-settling to idle for ~1ns at the clock edge), setting `transient_note` + per-signal `center_transient`/`center_settles_to`/`center_settle_ps`. `server.call_tool` runs it on every `get_signals_around_time` result so a point sample at the edge is not misread as the settled protocol value (the failure mode that led a model to blame an interconnect mux for a 1ns glitch). Zero-FP: only the dip-and-return pattern is flagged.
- `src/schemas.py` is the single source of truth for tool output contracts.
- `src/problem_hints.py` provides lightweight failure symptom annotations.
- `src/hierarchy_handles.py` owns the in-process `HandleStore` and content-addressed handle derivation. `build_tb_hierarchy` returns a slim payload + `hierarchy_handle`; the full hierarchy is registered here and resolved by the handle tools. Handles are not persisted — server restart drops them.
- `src/handle_tools.py` implements `get_tb_subtree`, `lookup_tb_files`, `find_tb_instance`, `get_tb_file_detail`, `get_tb_class_hierarchy`, `dump_tb_section` as pure functions over a resolved full hierarchy dict. `lookup_tb_files` requires at least one filter; `get_tb_file_detail` returns `did_you_mean` basename suggestions when the path is not in the compile set (multi-version safety net).
- `src/fsdb_parser.py` and `fsdb_wrapper.cpp` define the Python/native FSDB boundary. FSDB tags are **tick counts, not picoseconds** (real time = tick x header scale, read at `fsdb_open` via `ffrGetScaleUnit()`, e.g. `100fs`); every tick<->ps conversion goes through exactly two helpers in `fsdb_wrapper.cpp` (`_ToTag` floor on input / `_TagToPs` ceil on output, integer-fs internal base so sub-ps scales lose no precision) — never hand-roll a `<<32|` time conversion. An unreadable scale refuses time-based queries (`FSDB_ERR_SCALE_UNKNOWN`) instead of assuming 1ps; `get_waveform_summary` exposes `scale_unit`/`scale_fs_per_tick` as the self-check. Public `get_signal_transitions.transitions` is a strict closed-window list on both FSDB and VCD; the last value-change strictly before the start is a separate `predecessor` field. `cycle_query` must seed first-edge direction and pre-first-sample values from that field before falling back to a point query — especially inside an active FSDB group, where an independent point query would load/unload and invalidate resident group state. `get_signals_around_time` likewise keeps strict in-window transitions separate from chronological pre-window history. For full sweeps, the optional transition-group ABI adds resolved signals and calls `ffrLoadSignals()` once per bounded clock group, reads every signal independently through the existing reusable 64 MiB per-call buffer, and unloads in `finally`; the default group cap is 16 (`TRACEWEAVE_FSDB_GROUP_MAX_SIGNALS`, clamped 1..256). Oversized groups, begin errors, and old wrappers fall back to legacy per-signal loading. Cancellation and exceptions must still unload, and no native group may outlive the process-global FSDB lock. Cross-scale regression lives in `tests/test_fsdb_timescale.py` (fixtures `scale_100fs.fsdb`/`scale_1ns.fsdb`).
- `src/cursor_store.py` owns the in-process `CursorStore` — named, process-scoped time anchors (`cursor_set`/`cursor_list`/`cursor_delete`). Same lifetime semantics as `HandleStore`: not persisted, dropped on restart, no "active cursor" (references are always explicit `@name`).
- `src/timespec.py` resolves a TimeSpec (raw ps int, `@cursor` ref, or unit literal like `12.34ns`) to picoseconds. `server._resolve_time` wires it into every time-taking tool input (`get_signal_at_time`, `get_signal_transitions`, `get_signals_around_time`, `trace_x_source`, `diff_first_divergence`, `period`). Arithmetic (`@c ± cycle(clk)`) is intentionally NOT implemented yet (reserved for a future Lark grammar).
- `src/verify_condition.py` implements `diff_first_divergence`, `period`, and `inspect_handshake`; all read existing waveforms and register an evidence cursor. `inspect_handshake` reports cycle-level stalls/backpressure, payload-hold violations, and premature valid/htrans deassertion before acceptance. For AHB, derive valid with `valid_htrans` + `htrans_rule`; its payload is address-phase control only. `x_while_valid` applies only to AHB control, not literal-valid data lanes that may legally be X. Check HWDATA via the separate `hwrite` + `write_data` data-phase hold check, and only on a mechanically confirmed initiator/producer bundle. Read `protocol_semantics`, `coverage`, `accepted_before_deassert`, and `violating_signal`; coverage names only checks actually run. X-while-valid, payload-hold, and premature deassertion are one-sided producer/valid-driver violations: the ready driver is exonerated, so follow `next_actions` to the producer (master on AXI AW/AR/W, slave on R/B; AHB HTRANS is master-driven). A UVM producer may correctly resolve as `testbench_driven`; do not reinterpret a landed DUT load as its driver. A plain stall remains two-sided and targets `ready`. `diff_value_distribution` remains intentionally unregistered; do not expose it without a demonstrated use case.
- `src/window_verify.py` implements `verify_window`: templated temporal checks, not a DSL. Terms are `{signal,op,value}`, predicates are implicit AND, and modes are `always`, `never`, `eventually`, `implication`, and `sequence`. Use `overlap=false` (`|=>`, `within_cycles>=1`) for hold/stability properties; the default overlapping form can pass vacuously on the antecedent cycle, so never use a reported `vacuous=true`/`VACUOUS PASS` as exclusion evidence. `sequence.delta` supports caller-supplied `modulo` for WRAP and `restart_when` for burst starts; first/restart beats seed, gate-false waits preserve the predecessor, and X/Z breaks continuity. Unknown and end-window implication cases remain explicit (`unknown_cycles`/`inconclusive_count`). A sequence violation links its `violating_signal` to `explain_signal_driver`; protocol-side attribution and hypothesis generation remain the LLM's job.
- `src/handshake_suggest.py` provides `suggest_handshakes` (T2 of the protocol-debug plan, the "self-serve multiplier"): reads bounded scope metadata pages (legacy wrappers use `search_signals`) and proposes ready-to-use `inspect_handshake` bundles — pairs `*valid`/`*ready` by scope + stem, locates the clock (same scope or nearest ancestor), and groups unambiguous same-channel payload fields with compatible directions. It also provides `suggest_protocol_bundles` for AHB/APB discovery: AHB returns `valid_htrans`-based `inspect_handshake` args (payload = address-phase control only — HADDR/HWRITE/HSIZE/HBURST/HPROT; HWDATA/HRDATA are excluded as data-phase, so payload-hold cannot false-positive on the address/data phase offset — HWDATA is instead surfaced as `hwrite`+`write_data` for the write data-phase hold check, but ONLY on a mechanically-confirmed initiator-side interface: on a responder/consumer interface HWDATA is a combinational interconnect-mux output that glitches to its idle value for ~1 cycle at each clock edge, which the edge sampler reads as a spurious change, so hwrite/write_data are withheld on responder/unknown interfaces to keep the check zero-FP); APB returns `psel`/`penable`/`pready` facts and marks the missing derived-valid step. AHB results also carry a `next_step` field with a copy-paste-ready `inspect_handshake(...)` call per candidate (via `_inspect_handshake_relay`) — discovery only LOCATES the interface, `inspect_handshake` is the analysis; weak models stop at discovery unless the next call is spelled out at the one point its args first exist (here, not at parse time — parse has no signal paths). Direction tags are emitted only from discovery-layer mechanical evidence and degrade to `unknown` rather than guessing. Core proposal functions are pure over `{path,name,width,var_type}` descriptors (fully unit-tested).
- `src/handshake_sweep.py` implements `sweep_handshakes`, the whole-design anomaly scan over every discovered valid/ready and AHB interface (APB lacks the required derived-valid form and is excluded). Clocking-block `*_cb` mirrors are dropped. It returns an ordered comparative fact table, not a root-cause verdict: on a backpressured pipeline the top row may be the propagation front, while the cause is at the stall-to-starvation boundary. `ready_without_valid` is legal idle-ready behavior in both families and is excluded from flags and ranking. Preserve one cursor, public schemas, shared per-clock sampling, bounded FSDB grouping, the process-global FSDB lock, and cancellation checks. Always interpret `coverage_status`: `zero_coverage` checked nothing; `truncated` exceeded `max_interfaces`; `degraded` has skipped/incomplete rows; only `complete` supports a clean conclusion. Retry only a suggested action that changes scope/window/edge/interface cap; otherwise report the missing prerequisite instead of replaying the same call.
- Discovery uses bounded native/VCD scope pages and one request snapshot shared by both sweep families. Preserve exact scope/index identity, byte/visit/memory caps, `discovery.scope_total` versus its lower bound, and legacy-wrapper fallback. Port suffix normalization retains paths/directions; ambiguous clocks/payload stay unresolved. Req/ack naming requires explicit valid-hold semantics; idle-ready counts never contribute sweep flags/ranking. A bounded FSDB transition prefix sets per-row `transition_data_truncated=true`, increments `transition_truncated_count`, and forces non-`complete` coverage.
- `src/txn_reconstruct.py` provides `reconstruct_transactions` (the id-correlated transaction layer): walks a request handshake channel + a completion channel over the whole window, matches accepted beats by an `id` field, and returns per-transaction latency + aggregate facts. **One generic core, not a tool per protocol**: AXI read = AR→R (`cmp_last`=rlast, id=arid/rid); AXI write = AW→B (id=awid/bid) PLUS an optional unindexed W-data channel (`data_valid`/`data_ready`/`data_last` + `data_fields`; W carries no id so beats attach in order to the oldest data-incomplete request, matching real interconnect); any id'd req/resp; CHI-like. `req_id`/`cmp_id` are optional — omit both for an unindexed in-order stream (AXI-Lite, APB), which pairs requests and completions in FIFO order and reports txn id as null. AHB/APB phase tracking is otherwise out of scope. An optional `reset` (`reset_active_low`) clears in-flight state so a txn straddling reset is not a phantom hang (correctness, emits `reset_clears`); `capture_beats` (off by default → only `beat_count`) returns per-beat `data_beats[]` for data-integrity debug. An optional `req_len` (AxLEN = arlen/awlen) checks each txn's observed `beat_count` against `req_len+1`: a mismatch (early/late LAST, dropped/extra beat) is a real burst-length violation surfaced per-txn (`expected_beats`, `beat_count_mismatch`) and as `beat_count_mismatch_count` (x/z len → not checked, never a FP; with no `req_len` the count is 0 = "not checked", not a clean verdict). Facts not verdict: `latency` distribution (min/median/max/mean) not an "outlier" label; `outstanding_at_end`/`max_outstanding`/`max_outstanding_per_id`; `reorder_count` (informational, legal in AXI); `timeout_cycles`→`slow_count`; unmatched req/cmp surfaced loudly (the hang signature); one cursor (first never-completed request > peak outstanding). Out-of-order completion across ids via per-id FIFO. Returns objective facts only and leaves protocol-semantic interpretation (response-code decode, burst-type decode, outlier judgement) to the caller. Reuses `sample_signals_on_edges` + `_resolve_signal_path` + `_hs_truth`/`_hs_repr`.
- `src/usage_telemetry.py` provides passive, local-only usage telemetry: when enabled, `server.call_tool` appends JSONL to `$TRACEWEAVE_CACHE_DIR/telemetry/usage.jsonl` (tool, arg keys + fixed decision labels, bytes, latency, ok/blocked, classified errors; never argument values/paths or exception text). SDK/client validation failures are outside this handler recorder. Long wave calls may include the privacy-safe `operation_metrics` diagnostics above. Source Graph's independent persistent allowlist accepts numeric timings/counts/bytes and fixed phase/cache-tier/disk-validation labels; it rejects fingerprints, paths, signal/scope/value content and free-form diagnostics. `aggregate()` and `scripts/telemetry_report.py` report tier/tool counts, exact disk hit rate, validation outcomes, build skips, capacity/evictions and p50/p95 tier latency without scanning caches. Adjacent same-session calls within 60 seconds give an upper-bound semantic-session opportunity, not proof of eligible context. `src/telemetry_context.py` privately registers exact simulation/formal discovery files with stat identities and random session ids; shared readers pin the requested artifact at entry and recheck versions at completion. Unknown, changed or conflicting ownership never inherits the most recent case. Retention caps at 128 owners/4,096 bindings, then stays unknown until restart. New records have fixed artifact-domain/attribution labels and no case basename. Readback counters distinguish returned point/batch/cycle state from metadata-only sessions, without claiming model use; new unknown calls have no session denominator. Recording is best-effort, default-off and never an MCP tool; opt in with `TRACEWEAVE_TELEMETRY=1` and restart/reconnect.
- `config.py` centralizes environment-sensitive paths and behavior constants.

## Working Rule

Before making non-trivial changes, build a quick mental model from the files above instead of editing from local assumptions.

## Documentation Rule

When a behavior change requires doc updates, **only touch documents tracked in git**. Run `git ls-files | grep -E '\.md$'` to see the canonical doc set (currently `README.md`, `README.zh.md`, `AGENTS.md`, `CLAUDE.md`, `docs/architecture.md`, `docs/workflow.md`). Untracked files under `docs/` are local drafts, RFCs, and session notes — do not edit them as part of code changes and do not create new ones unless the user explicitly asks. This applies to every agent working in this repository (Claude, Codex, others).

`CLAUDE.md` links to `AGENTS.md`; after changing either, resolve the target and require `wc -m` to be at most 40,000 characters.

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.