Let your AI assistant explore SystemVerilog designs — without pasting source code into the chat.
naja-scope is an MCP server that gives AI agents (Claude, and any MCP-compatible assistant) a precise, structured view of your elaborated SystemVerilog design. Instead of dumping thousands of lines of RTL into the model's context, the agent asks targeted questions — what drives this signal? what's inside this module? where does this net come from? — and gets back small, exact answers with file-and-line references.
Built on the najaeda netlist engine.
On a 17-question CVA6 benchmark, the same Claude Code agent scored 17/17 with naja-scope versus 10/17 with grep/read source tools — in 77 turns instead of 123, processing about 5× less input. How it was measured
pip install naja-scope
claude mcp add naja-scope -- naja-scope-mcpSee it in action: demo recording.
Large designs don't fit in a chat window. Pasting RTL is slow, expensive, and the model still can't reliably trace connectivity across hierarchy. naja-scope turns your design into something an agent can navigate:
- 🔎 Trace connectivity — find what drives or loads any signal, across module boundaries.
- 🌲 Walk the hierarchy — explore modules, instances, and ports on demand.
- 🎯 Jump to source — every answer comes with
file:lineranges, so the agent can quote the exact RTL that matters. - 🧩 Logic cones — trace fan-in / fan-out combinational cones up to the register boundary.
- 💡 Recover design intent — enum state names, struct/union fields, and parameter formulas that normally vanish when a design is elaborated.
Requires najaeda 0.7.33 or newer, including the CVA6 RV32 elaboration fix. See the 0.1.21 release notes for test results and snapshot migration requirements.
Works on RTL and gate-level netlists alike — load elaborated SystemVerilog, or a post-synthesis structural Verilog netlist plus its Liberty standard-cell library (see Gate-level designs). VHDL loading is in beta.
get_module_card reports direct-child register bits and inferred RAM/ROM
summaries: width, depth, capacity in bits, read/write port counts, and
initialization status. Native memory interfaces also report address_bits
and reset_mode (none, async_low, async_high, sync_low, or
sync_high). It lists up to 12 memories, with memories_truncated
when more exist; initialized memories include a 64-character init_preview
and init_truncated. Address zero occupies the least-significant word.
get_stats aggregates these counts below the selected instance using
flat_register_bits, flat_memory_instances, flat_rom_instances,
flat_ram_instances, and flat_memory_bits. Register bits count modeled
flip-flop/latch states, including multibit Liberty banks; RAM capacity is
separate. Sequential cells without a state model are counted in
register_bits_unknown_instances (flat_ in statistics), so known bit
counts remain explicit lower bounds when that count is nonzero. Memories
are recognized from native naja primitives; opaque memory macros are not
assigned an inferred capacity.
All responses are token-bounded: lists paginate, large results truncate with clear markers. Your context stays small; your answers stay accurate.
naja-scope helps most when the answer exists in the elaborated design rather
than in any single source file. In an initial 17-question run on the
cv32a6_imac_sv32 configuration of
CVA6, the same Claude Code agent was
tested with naja-scope and with source-search tools alone.
| Agent setup | Provider and models | Initial automated score | Turns | Input processed | Output tokens |
|---|---|---|---|---|---|
| Agent + naja-scope | Anthropic Claude Code; claude-sonnet-4-6 with claude-haiku-4-5-20251001 helper |
17 / 17 | 77 | 1,058,556 | 19,520 |
| Agent + grep/read source | Anthropic Claude Code; claude-sonnet-4-6 with claude-haiku-4-5-20251001 helper |
10 / 17 | 123 | 5,461,719 | 55,962 |
The difference is clearest on structural questions that source search cannot answer directly:
| CVA6 question | Agent + naja-scope | Agent + grep/read source |
|---|---|---|
Flattened register groups under ex_stage_i |
92, in 4 turns | No answer at the turn limit |
Flattened register groups under commit_stage_i |
0, in 3 turns | No answer at the turn limit |
Elaborated hpdcache_mux variants |
20, in 3 turns | No answer at the turn limit |
Source search remains the right tool for local textual questions. naja-scope adds the elaborated hierarchy, connectivity, lowered primitives, and generated or uniquified structures that are otherwise difficult to reconstruct.
See the benchmark methodology and multi-model runner and historical result record for configuration, scoring, token accounting, and reproducibility details.
pip install naja-scopeThat's it — najaeda and the MCP runtime come along automatically.
claude mcp add naja-scope -- naja-scope-mcpOr add it to any MCP client's config:
{
"mcpServers": {
"naja-scope": {
"command": "naja-scope-mcp"
}
}
}Then just ask your assistant to load a design and start exploring:
"Load my UART design from
rtl/uart.svwith topuart_top, then show me everything that drivestx_o."
The agent loads the design once and answers follow-up questions instantly — no re-reading source, no giant pastes.
ChatGPT connects to MCP servers over an HTTP endpoint (custom connectors / Developer mode), so run naja-scope as an HTTP server instead of stdio:
naja-scope-mcp --transport streamable-http --host 127.0.0.1 --port 8000This serves MCP at http://<host>:8000/mcp. Expose that URL where ChatGPT can
reach it (e.g. an ngrok/cloudflared tunnel for a local run), then in ChatGPT
open Settings → Connectors, add a custom connector, and paste the URL
(https://<your-host>/mcp). The HTTP server has no built-in auth — only expose
it over a trusted tunnel.
Already synthesized? Load the structural Verilog netlist together with the Liberty library that defines its standard cells, and navigate the gates the same way as RTL:
"Load the Liberty library
pdk/stdcells.lib, then the gate netlistbuild/top.v, and tell me what cellstopis built from and what drivesdata_out."
Hierarchy, per-cell counts, drivers/loads, and logic cones all work on the
netlist; cones stop at the sequential cells. get_source shows an object's
line in the netlist, and for Yosys output yosys_source_attributes=True adds
origins that get_source(path, origin=0) reads back in the RTL.
For mixed RTL where only some modules declare a time unit, pass
default_timescale="1ns/1ps" to load_systemverilog. It supplies a fallback
for modules without a time unit and preserves explicit declarations. The
setting survives snapshots and load_intent re-elaboration.
VHDL loading is available in beta. Call
load_vhdl(file=["pkg.vhd", "child.vhd", "top.vhd"], top="my_entity") to
explore its elaborated hierarchy and connectivity; the files may be listed in
any order. A single path per call also works (load packages first);
package-only files may return top: null until the top file is loaded. The frontend supports a restricted two-state RTL subset,
and supported constructs may change. get_intent/load_intent remain
SystemVerilog-only. get_source reads VHDL entity, port, signal, instance
and process origins, including after snapshot reloads.
Once a design is loaded, your assistant can:
- Resolve any signal or instance by hierarchical path (with glob and did-you-mean suggestions).
- Find objects design-wide by pattern.
- Show the hierarchy of any module.
- Get drivers / loads of a net — the real endpoints, across hierarchy;
literal drivers preserve four-state
0/1/X/Zvalues. - Trace logic cones (fan-in / fan-out) and see the register frontier.
- Get source — the exact SystemVerilog lines behind any object.
- Get a module card — ports, counts, clock/reset at a glance.
- Recover design intent — state-machine names, struct fields, parameter expressions lost during elaboration.
- Python 3.10+
- Works anywhere
najaedaruns (Linux, macOS, Windows)
- 🐛 Found a bug or have a feature request? Open an issue on GitHub →
- 📫 Get in touch: contact@keplertech.io
Apache-2.0. See LICENSE.
Requires naja-schematic 0.2.0 or later (the release containing the embeddable viewer API):
pip install "naja-scope[schematic]"Restart the MCP server after installation. Load a design as usual, then use:
open_schematic(path="top.u_cpu"): returns a local browser URL and focuses the instance. Omitpathfor the top. Anonymous#idinstances are supported.annotate_schematic(items=[{"path": "top.u_cpu", "message": "Check reset", "severity": "warning"}]): replaces the overlay. Usekind="term"with a pin/port path to annotate that terminal; annotations apply to the whole port, not individual bus bits. Passitems=[]to clear. At most 200 annotations, each with at most 2000 message characters.get_schematic_selection(): returns the last clicked instance as a regular naja-scope path, usable with source, hierarchy and module-card queries.
The viewer shares the existing raw naja universe; it does not elaborate again. The server binds to loopback and returns a token-bearing URL. Open that URL on the machine running the MCP server; it is not a remote or embedded MCP UI. Treat the URL as access to the loaded design. Design loads/resets refresh open viewers and clear selection, focus and annotations. The browser server stops with naja-scope. Existing tools remain usable without the schematic extra.