Agent skill

Infrastructure

by hdl-tools in hdl-tools/digital-chip-design-agents

EDA tool detection, wrapper deployment, and MCP configuration for digital chip design environments.

MITAuto-check: notes

Install Infrastructure

skills CLI
$ npx skills add hdl-tools/digital-chip-design-agents --skill infrastructure -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install hdl-tools/digital-chip-design-agents infrastructure --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/hdl-tools/digital-chip-design-agents.git skills-src && mkdir -p .claude/skills && cp -r skills-src/plugins/infrastructure/skills/infrastructure .claude/skills/infrastructure && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
infrastructure
GitHub stars
212
Token cost
~18k tokens
SKILL.md length
8,133 words
Files
1
Skills in repo
17
Repo updated
First seen
Licence
MIT

At a glance

EDA tool detection, wrapper deployment, and MCP configuration for digital chip design environments.

  • Works in 4 steps: Tier 2 session MCP — if the tool… → Tier 1 batch MCP — if the tool has a… → Wrapper script — if MCP is not… → …
  • Setting up a new workstation
  • SKILL.md covers Invocation, Purpose, Supported EDA Tools and MCP Architecture — Two Tiers, plus 4 more sections
  • Calls uv, make and bash; reaches astral.sh

What it does

Infrastructure is an agent skill from hdl-tools/digital-chip-design-agents. EDA tool detection, wrapper deployment, and MCP configuration for digital chip design environments. Use when setting up a new workstation, verifying tool availability before a domain flow, or generating per-tool install scripts with TCL modulefiles.

Its SKILL.md is about 18k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It works with Model Context Protocol. The repository describes itself as: Digital HDL Design Full-stack Agents. The licence is MIT.

When your agent uses it

  • Setting up a new workstation
  • Verifying tool availability before a domain flow
  • Generating per-tool install scripts with TCL modulefiles

Example prompts

  • “/infrastructure”

Requirements

  • Python 3
  • Pre-approved tools (allowed-tools): Read, Write, Bash

Workflow steps

4 steps, taken from the first numbered list in SKILL.md.

  1. Tier 2 session MCP — if the tool supports a session and the design is already loaded
  2. Tier 1 batch MCP — if the tool has a batch MCP server configured
  3. Wrapper script — if MCP is not configured; wrapper emits compact JSON
  4. Direct execution — last resort; raw logs consume significant context

What it can do on your machine

Read from SKILL.md and the folder at commit 38736b1. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves these tools, so the agent can use them without asking each time:

    • Read
    • Write
    • Bash

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • uv
    • make
    • bash
    • pip
    • curl
    • sh

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • astral.sh

    Also links to:

    • github.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Infrastructure loads about 18k tokens when it runs. Until then it costs about 66 tokens; SKILL.md has 8,133 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~66
When it runs · the whole SKILL.md, loaded when a task matches
~18k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check: notes

The automated check noted patterns worth knowing about, such as sudo or a known installer.

  • NotePipes a well-known installer script into a shellSKILL.md:820
    | `uv` | `curl -LsSf https://astral.sh/uv/install.sh \| sh` (standalone) or `<PYTHON_EXEC> -m pip install uv` | `uv` (br
  • NoteRuns commands with sudoSKILL.md:832
    `sudo chmod +x` instructions
  • NotePre-approves every shell command (allowed-tools: Bash)SKILL.md
    allowed-tools: Read, Write, Bash

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from hdl-tools/digital-chip-design-agents at commit 38736b1, republished under its MIT licence (© hdl-tools). 8,133 words, ~17,632 tokens.

Download SKILL.mdSave it as .claude/skills/infrastructure/SKILL.md (or your agent's skills folder).
name
infrastructure
description
EDA tool detection, wrapper deployment, and MCP configuration for digital chip design environments. Use when setting up a new workstation, verifying tool availability before a domain flow, or generating per-tool install scripts with TCL modulefiles.
allowed-tools
Read, Write, Bash
version
1.0.0
author
chuanseng-ng
license
MIT

Skill: Infrastructure Setup

Invocation

  • If invoked by a user presenting a setup task: immediately spawn the digital-chip-design-agents:infrastructure-orchestrator agent and pass the full user request and any available context. Do not execute stages directly.
  • If invoked by the infrastructure-orchestrator mid-flow: do not spawn a new agent. Treat this file as read-only — return the requested stage rules, sign-off criteria, or loop-back guidance to the calling orchestrator.

Spawning the orchestrator from within an active orchestrator run causes recursive delegation and must never happen.

Purpose

Detect open-source and proprietary EDA tools, generate an installation script for missing tools, deploy output-filtering shell wrappers that emit compact JSON instead of raw 10,000–50,000-line logs, configure MCP server templates, and validate the complete environment before any domain orchestrator begins work.


Supported EDA Tools

Open-Source
  • Verilator (verilator) — fast RTL simulator and linter
  • Slang (slang) — SystemVerilog compiler and language server
  • Surelog (surelog) — SystemVerilog pre-processor and parser
  • sv2v (sv2v) — SystemVerilog to Verilog converter
  • Icarus Verilog (iverilog) — Verilog simulator
  • Yosys (yosys) — open synthesis framework
  • ABC (abc) — logic synthesis and verification tool
  • OpenROAD (openroad) — RTL-to-GDS flow
  • LibreLane / OpenLane2 (openlane) — open-source ASIC flow
  • KLayout (klayout) — GDS/OASIS viewer and DRC engine
  • OpenSTA (sta) — gate-level static timing analysis
  • SymbiYosys (sby) — formal hardware verification
  • gem5 (gem5) — full-system micro-architectural simulator
  • Bambu HLS (bambu-hls) — high-level synthesis from C/C++
  • nextpnr (nextpnr) — FPGA place-and-route
  • openFPGALoader (openFPGALoader) — FPGA programming tool
  • cocotb (Python package cocotb) — Python-based RTL co-simulation
  • LLVM (llvm-config) — compiler infrastructure
  • GCC (gcc) — GNU compiler collection
  • OpenOCD (openocd) — on-chip debugger
  • xschem (xschem) — schematic capture and simulation netlist tool
  • GTKWave (gtkwave) — waveform viewer for VCD/FST simulation output
  • uv (uv) — fast Python package and project manager (required for cocotb installs)
Roles and dialects (open-source)

role groups tools that do the same job. dialect names the command-line option and source-language vocabulary a tool accepts. Two tools with the same role and a different dialect are not substitutable — a command line built for one is not valid for the other.

Tool commandroledialect
verilatorrtl_simulatorverilator
iverilogrtl_simulatoricarus
yosyssynthesisyosys
openroadphysical_designopenroad
openlanephysical_designopenlane
stastaopensta
sbyformalsymbiyosys
klayoutdrc_lvsklayout
bambu-hlshlsbambu
gem5arch_simulatorgem5
nextpnrfpga_pnrnextpnr

Every other open-source tool — slang, surelog, sv2v, abc, openFPGALoader, cocotb, llvm-config, gcc, openocd, xschem, gtkwave, uv, python3 — has no same-role peer here and is recorded "role": null, "dialect": null. Never invent a role to fill the field: a null cannot produce a false conflict.

Proprietary (detect only — never install)
ToolCommand (alt)roledialectVersion probe (license-free)
Synopsys VCSvcsrtl_simulatorsynopsysvcs -ID — parse the Compiler version line only
Cadence Xceliumxrun (xmsim)rtl_simulatorcadencexrun -version — parse the TOOL:<tab>xrun(64)<tab><version> line
Mentor QuestaSimvsim (questa, questasim, vlog, vcom)rtl_simulatorsiemensnone — UNVERIFIED
Synopsys Design Compilerdc_shell (dc_shell-t)synthesissynopsysdc_shell -version — parse the dc_shell version line
Cadence Genusgenussynthesiscadencegenus -version — parse the Program Name: line
Synopsys Fusion Compilerfc_shellsynthesissynopsysnone — UNVERIFIED
Cadence Innovusinnovusphysical_designcadencenone — UNVERIFIED
Synopsys IC Compiler 2icc2_shellphysical_designsynopsysnone — UNVERIFIED
Synopsys PrimeTimept_shell (pt_shell64)stasynopsyspt_shell -version — parse the pt_shell version line
Cadence Tempustempusstacadencenone — UNVERIFIED
Synopsys Formalityfm_shell (formality)lecsynopsysfm_shell -version — parse the Formality (R) Version line
Cadence Conformalconformalleccadencenone — UNVERIFIED
Cadence JasperGoldjgformalcadencenone — UNVERIFIED
Synopsys VC Formalvcfformalsynopsysnone — UNVERIFIED
Siemens Questa Formalqformalformalsiemensnone — UNVERIFIED
Synopsys TetraMAXtmaxatpgsynopsysnone — UNVERIFIED
Cadence Modus Testmodusatpgcadencenone — UNVERIFIED
Siemens Tessenttessentatpgsiemensnone — UNVERIFIED
Synopsys SpyGlassspyglasslint_cdcsynopsysnone — UNVERIFIED
Xilinx Vitis HLSvitis_hlshlsxilinxnone — UNVERIFIED
Cadence Stratusstratushlscadencenone — UNVERIFIED
Siemens Catapultcatapulthlssiemensnone — UNVERIFIED
Xilinx Vivadovivadofpga_pnrxilinxnone — UNVERIFIED
Intel Quartusquartus_shfpga_pnrintelnone — UNVERIFIED
Microchip Liberoliberofpga_pnrmicrochipnone — UNVERIFIED
ARM Artisanartisanmem_compilerarmnone — UNVERIFIED
Synopsys SiliconSmartsiliconsmartcharacterisationsynopsysnone — UNVERIFIED
Cadence Liberateliberatecharacterisationcadencenone — UNVERIFIED
Synopsys StarRCstarrcextractionsynopsysnone — UNVERIFIED
Cadence Voltusvoltuspower_analysiscadencenone — UNVERIFIED
Arm Compiler 6armccembedded_compilerarmnone — UNVERIFIED
SEGGER J-Link GDB ServerJLinkGDBServerdebug_probeseggernone — UNVERIFIED
Lauterbach TRACE32t32marmdebug_probelauterbachnone — UNVERIFIED
Arm Development Studioarmdsdebug_probearmnone — UNVERIFIED

vcs -ID also prints a FLEXlm host ID. Parse and store the Compiler version value only — never write the host ID into tool-status.json.

A probe flag is listed only once confirmed on a real install — a guessed flag can open an interactive shell or check out a license. The flags above were each measured: exit 0 with the named line present, in 1–26 s, no license queue. dc_shell -version prints an unrelated ASLR advisory first, so parse the line containing version, never the first line of output. genus -version was measured the same way: 26 s cold, 12 s warm, which is why rule 4 below sets the probe timeout at 60 s rather than the original 10 s.

The two original UNVERIFIED rows are unverified upstream, not an oversight. Neither could be confirmed on the reference host, and in both cases the obstacle was the install rather than the flag: innovus -version exits 0 reporting an expired build authorisation, and vsim -version exits 0 failing to load libXext.so.6. A site with a working install of either should confirm the flag and contribute it here.

The remaining UNVERIFIED rows above have no probe evidence at all, which is worth recording precisely so a later contributor does not re-measure it blind: voltus -version and tempus -version each exit 0 but with cds_plat.dat absent, the same shape as the innovus failure. joules, conformal, starrc, liberate, and jg did not resolve to a binary under <root>/linux/<version>/bin/ on the reference host at all. Three of the exit-0 cases print no version line, which is exactly why rule 4's WARN conditions below treat "exits 0, no version line" as its own case rather than a pass.

Note on Formality: the primary command is fm_shell. formality is the legacy GUI wrapper and is absent from recent installs, so it is listed as the alternate.

Same role with a different dialect means the two tools are not interchangeable however similar their purpose. environment_validation reports such coexistence.

Single-member roles are intentional, not an oversight — each is a role whose only plausible peers are already claimed by their primary role, so the role can never produce a dialect-conflict WARN today:

  • lint_cdc holds only spyglass. jg and vsim also do clock-domain-crossing work, but jg is recorded under formal and vsim under rtl_simulator — a tool carries one role in tool-status.json, and each already does its primary job elsewhere.
  • mem_compiler holds only artisan. Synopsys's memory compiler shares a binary with siliconsmart, which is recorded under characterisation — the job that binary performs.
  • extraction holds only starrc. No skill in this repo names a second extraction tool.
  • power_analysis has three single-command peers plus three commandless ones (below); none of voltus, primepower, joules, or powerpro has a second same-dialect alternative.
  • embedded_compiler holds only armcc with a command; Green Hills and IAR are its commandless peers.

Products named in domain skills with no documented command, so not detectable by this table (the table is keyed on which <command>; a row with no command cannot be probed): Synopsys Platform Architect (arch_simulator/synopsys), ARM Performance Models (arch_simulator/arm), Cadence Virtual System Platform (arch_simulator/cadence), Green Hills MULTI (embedded_compiler/greenhills), IAR Embedded Workbench (embedded_compiler/iar), Synopsys Synplify (synthesis/synopsys), Siemens Aprisa (physical_design/siemens), Synopsys PrimePower, Cadence Joules RTL, and Siemens Questa PowerPro (all power_analysis), Synopsys VC Static (lint_cdc/synopsys). A command name is not guessed here any more than a probe flag is above; each is instead given its role and dialect at the point of use in its domain skill.


MCP Architecture — Two Tiers

Tier 1: Batch MCP servers (short, self-contained runs)

Use these for tools whose output fits inside a single request/response cycle (seconds to a few minutes). Each call spawns the wrapper script, captures its compact JSON output, and returns it.

MCP configToolTypical duration
mcp-yosys.jsonYosys synthesisseconds–minutes
mcp-openroad.jsonSingle OpenROAD stageminutes
mcp-opensta.jsonOpenSTA batch reportseconds–minutes
mcp-klayout.jsonKLayout DRC/LVSminutes
mcp-verilator.jsonVerilator lint (mode: "lint") or a compiled sim binary (mode: "sim")seconds–minutes
mcp-bambu.jsonBambu HLS synthesisminutes
mcp-gem5.jsongem5 short benchmark runminutes (set TOOL_TIMEOUT_S)
mcp-symbiflow.jsonSymbiYosys bounded proofminutes–hours (set TOOL_TIMEOUT_S)

The adapter is plugins/infrastructure/tools/mcp-adapter.py. Each server runs the one wrapper named by --wrapper, except verilator: mode: "lint" runs wrap-verilator-lint.sh from the same directory as the configured wrap-verilator-sim.sh, because the sim wrapper's first argument is a compiled simulation binary. The 8 batch servers therefore use 9 of the 11 wrapper scripts; wrap-vcs-compile.sh and wrap-xrun-compile.sh are invoked directly, not through MCP.

Tier 2: Interactive session MCP servers (stateful, query-based)

Use these when an agent iterates many times over an already-loaded design (e.g. ECO timing loops). The process stays alive between calls — no re-loading per query.

MCP configToolExposed tools
mcp-openroad-session.jsonOpenROAD Tcl sessionload_design, query_timing, query_drc, get_design_area, get_power, run_tcl, close_design
mcp-opensta-session.jsonOpenSTA Tcl sessionload_design, report_timing, report_slack_histogram, check_timing, run_tcl, close_design

The adapter is plugins/infrastructure/tools/mcp-session-adapter.py.

Full-flow tools — do NOT use MCP

These tools run for 30 min–2+ hours and produce structured output files on disk. Agents must launch them via Bash and read the output files directly.

ToolLaunch commandRead these files
LibreLane / OpenLane 2openlane config.jsonruns/<design>/<tag>/metrics.json
ORFS / OpenROAD Flow Scriptsmake DESIGN_CONFIG=... finishreports/<platform>/<design>/metrics.json
gem5 full-system simulationgem5 config.py ...m5out/stats.txt, m5out/simout
Execution Hierarchy (per domain agent)
  1. Tier 2 session MCP — if the tool supports a session and the design is already loaded
  2. Tier 1 batch MCP — if the tool has a batch MCP server configured
  3. Wrapper script — if MCP is not configured; wrapper emits compact JSON
  4. Direct execution — last resort; raw logs consume significant context

Domain agents must check whether the relevant MCP server is active in .claude/settings.json before falling back to the wrapper or direct execution.


Stage: tool_discovery

Domain Rules
  1. Run which <command> and <command> --version (or -version) for every open-source tool

  2. Python interpreter detection — run once at the start of tool_discovery, before checking any Python packages. Detection order (first match wins):

    Step A — Module system probe (runs before PATH check): a. Classify the module system and prove it is invocable using the detection rules and the invocation ladder in module_discovery below. Do not re-derive either here, and never treat $MODULESHOME alone as proof that module can be run. b. If a listing was obtained (module_listing is "LISTED"), search it for entries matching python or python3 (case-insensitive). c. If one or more Python module entries are found: select one per Module version selection in module_discovery below — never by a plain sort — then load it via module load <python-module> and run which python3 to resolve PYTHON_EXEC. The non-release exclusion matters more here than anywhere else: an interpreter chosen here stays loaded for the whole run, so a _test or -debug build selected at this step becomes the Python every later stage resolves against. Set python_env.type = "module" and record python_env.module_name with the loaded module name. Keep the module loaded for the entire orchestrator run — do not unload it; subsequent stages (tool_installation, wrapper_deployment, environment_validation) all depend on PYTHON_EXEC being resolvable. The generated load-modules.sh handles persistent loading for future shell sessions. d. Proceed to Step B where module_system is "none", where the ladder was exhausted (module_listing is "UNAVAILABLE"), or where the listing holds no Python module entries. A detected-but-unlistable module system is not the same as no module system: it still falls through to the PATH interpreter here, but the WARN it owes is emitted by module_discovery and is not suppressed because this step recovered an interpreter.

    Step B — PATH-based fallback: e. Run which python3 to get the interpreter path; store as PYTHON_EXEC. f. If which python3 fails or returns empty: record python3 as MISSING in tool-status.json and FAIL immediately (required for all wrapper scripts and Python packages). g. Classify the interpreter:

    • system → PYTHON_EXEC == /usr/bin/python3
    • custom → any other path (pyenv, conda, virtualenv, custom prefix, etc.)

    Step C — Finalize: h. Set PYTHON_BIN_DIR = $(dirname "$PYTHON_EXEC") regardless of how PYTHON_EXEC was resolved. i. Record under a top-level key python_env in tool-status.json:

    json
    {
      "python_env": {
        "exec": "<absolute path>",
        "type": "module | system | custom",
        "bin_dir": "<absolute dir>",
        "module_name": "<module name, or null if not module-based>"
      }
    }

    j. Capture "$PYTHON_EXEC" --version and store it as a regular entry in the tools array (with "tool": "python3", "command": "python3", "status": "FOUND", "version": "<output>", "path": "<PYTHON_EXEC>"). This makes python3 visible to module_discovery for module-status upgrades (FOUND_PREFER_MODULE).

  3. Exception — Python packages:

    • cocotb: detection depends on the Python interpreter type determined in rule 2:
      • If python_env.type == "custom" or "module": check "$PYTHON_BIN_DIR/cocotb-config" --version first; if that exits zero, record FOUND with the returned version string. Only if absent or non-zero, fall back to cocotb-config --version (PATH-based).
      • If python_env.type == "system": use cocotb-config --version (PATH-based) only.
      • Report MISSING if all attempted checks fail.
    • openlane: run "$PYTHON_EXEC" -m pip show openlane 2>/dev/null; if exit code 0 and Name: openlane appears in output, record FOUND and extract the Version: field; otherwise MISSING.
    • uv: if python_env.type == "custom" or "module": check "$PYTHON_BIN_DIR/uv" --version first; fall back to uv --version (PATH). If python_env.type == "system": use uv --version only.
  4. Proprietary tools: check PATH using which <primary-executable> (primary and alternate names are in the Proprietary table above); never attempt install; record as PROPRIETARY_ONLY if found, MISSING otherwise. For a tool found in PATH that has a version probe in that table, run the probe with a 60 s timeout, parse the documented line, and store the parsed string in version. Do not use a short timeout: a Cadence Common UI tool starts a full shell to answer -version and takes tens of seconds on a cold NFS cache (genus -version measured 26 s cold, 12 s warm). For vcs, store the Compiler version value only — discard the FLEXlm host ID.

    Leave version empty and emit a WARN naming the tool and the reason when the table lists no probe, when the probe times out, when it exits non-zero, or when it exits 0 without printing the documented line. Record a version only when that line was actually found: exit 0 does not mean a version was printed.

    Measured on the reference host by invoking each tool directly: innovus -version answers 0 with an expired build authorisation, voltus -version answers 0 with "cannot find a proper installation", and vsim -version answers 0 having failed to load a shared library. None of those three has a probe in the table above, so this stage reaches an empty version for them through the no-probe condition, not the last one. They are cited as evidence that a broken install answers 0 whatever the vendor — which is why the last condition has to guard the five tools that do carry a probe, whose installs can break the same way. A version probe never fails the stage.

  5. Record each tool as one of: FOUND, MISSING, or PROPRIETARY_ONLY

  6. Capture the exact version string for every tool found in PATH — FOUND tools via rule 1, and PROPRIETARY_ONLY tools that have a version probe in the Proprietary table via rule 4

  7. Set role and dialect on every entry in the tools array from the Proprietary and Roles-and-dialects tables above; use null for both where the tool appears in neither

  8. Never attempt installation in this stage

  9. Write results to tool-status.json before advancing

QoR Metrics to Evaluate
  • tools_detected: count of FOUND tools (target ≥ 10 for a functional open-source flow)
  • tools_missing: count of MISSING open-source tools
  • proprietary_found: count of PROPRIETARY_ONLY tools detected in PATH
  • proprietary_versioned: count of PROPRIETARY_ONLY tools with a non-empty version

dialect_conflicts is not this stage's metric, although it reads the role and dialect recorded here: environment_validation rule 7 computes it and declares it.

Output Required
  • tool-status.json — contains two top-level keys:
    • python_env: { "exec": "", "type": "module|system|custom", "bin_dir": "", "module_name": "" } — populated by rule 2
    • tools: array of { "tool": "", "command": "", "status": "FOUND|MISSING|PROPRIETARY_ONLY", "version": "", "path": "", "role": null, "dialect": null }

role and dialect are always present on every entry — a string from the tables above, or null. version is non-empty for every FOUND tool and for every PROPRIETARY_ONLY tool whose probe succeeded.

tool and command both hold the command — the executable name which is run against, as listed in backticks in the Open-Source and Proprietary tables above. module-status.json records the same value ("tool": "<command>"), which is what lets module_discovery match its entries back into this file. Product names belong in prose and in the first column of the Proprietary table; never in a field a rule matches on. A rule that names a product cannot find its entry wherever the two differ — OpenSTA's command is sta, Formality's is fm_shell — and "no entry found" is not the same answer as "tool missing".

This applies to tool-status.json and module-status.json only. The Wrapper JSON Output Schema below deliberately uses a different convention — "tool": "<tool-name>", a wrapper label such as opensta or verilator-sim — because that field identifies which wrapper produced the record, not an entry to match. Do not "correct" the wrappers to emit commands.

Note: module-based availability (FOUND_PREFER_MODULE, MISSING_LOAD_MODULE) and the module_names/versions_available fields are added in the next stage (module_discovery).


Stage: module_discovery

Domain Rules
Module system detection

A set environment variable is not proof that module can be run. Classify first, then prove invocability with the ladder below — the ladder decides, not the classification.

  1. modulecmd is in PATH ⇒ module_system: "tclmod". Record in module_system_detail whether $MODULESHOME is also set.
  2. $MODULESHOME is set and modulecmd is not in PATH ⇒ module_system: "custom". Read this as not confirmed Environment Modules, never as proven bespoke: a classic install can have modulecmd off PATH, and rung 3 below is what tells the two apart.
  3. Neither ⇒ module_system: "none", module_listing: "UNAVAILABLE", module_listing_error: "no module system present"; emit the "none" WARN below, skip the remaining rules, write module-status.json, advance to tool_installation.
Module invocation ladder

module is frequently a shell function or alias, not a binary, so it is absent from a non-interactive shell even on a host where a module system is plainly installed. Probe the invocation; never infer it from an env var. Try these in order, stop at the first rung that succeeds by the test below, and record that rung verbatim in module_invocation.

#InvocationWhen, and what it proves
1module availthe orchestrator's own shell may already define module, if it was started as an interactive or login shell
2modulecmd bash availonly where modulecmd is in PATH; a listing here confirms tclmod
3. "$MODULESHOME/init/bash"; module availclassic Environment Modules ships init/bash; a listing here upgrades custom to tclmod, and module_system_detail records that modulecmd was merely off PATH
4. "$MODULESHOME/module.sh"; module availa site wrapper's sh init. Measured on the reference host: module becomes a shell function and module avail exits 0 in 159.5 s with 3813 lines, 3812 of them entry-shaped. Classification stays custom
5MODULESHOME="$MODULESHOME" "$MODULESHOME/module" availlast resort, where the wrapper is itself an executable script. Export in addition any path variable the wrapper's own init script sets, which a site wrapper commonly uses in place of $MODULESHOME
  • A rung succeeds only where it exits 0 and its output holds at least one entry-shaped line. Either test alone is insufficient; a failed rung means advance to the next, never conclude the host has no modules. Both halves are measured. Rung 5 on the reference host exits 1 and prints Usage: [-n] subcommand [arguments ...] — and that usage text itself contains the lines modulename/scope, modulename/version and modulename/scope/version, so an output-shape test on its own reads a failed rung as a successful listing of three modules named modulename. The converse case — exit 0 with no listing — is the failure mode already documented for version probes in tool_discovery rule 4, where a broken install answers 0 whatever the vendor.
  • Wrap the shell, not the command. timeout execs a binary and cannot wrap a shell function: measured, timeout 60 module avail returns 127 with timeout: failed to run command 'module' on a host where rung 4 had just succeeded. Run every rung as timeout 300 bash -c '<rung>; module avail </dev/null 2>&1'.
  • Budget 300 s, not the 60 s used for version probes. The listing walks every modulefile tree: 159.5 s measured on a warm NFS cache, emitting ls: cannot access ... noise from stale paths on the way. A short budget records a working module system as unprobeable.
  • Always redirect stdin from /dev/null. A wrapper that prompts would otherwise block the whole run and produce no output to diagnose.
  • Where every rung is exhausted: set module_listing: "UNAVAILABLE" and put the last rung's stderr in module_listing_error. Only an exhausted ladder is a WARN — an individual failed rung is not.
Listing entry format

Entries hold two or more /-separated segments and may carry a trailing parenthesised annotation. Measured on the reference host: klayout/adi/0.29.0, verilator/adi/3.922, klayout/adi/0.27.11 (adi default). Take the version as the last /-separated segment after removing any trailing (...) annotation, and keep the entry string without that annotation in module_names. Never assume <name>/<version> — that records adi as the version of klayout/adi/0.29.0.

The annotation is data, not noise. An entry whose annotation contains default (case-insensitive) is the module system's own default for that tool — on the reference host klayout/adi/0.27.11 (adi default) marks it. Record which entry carried it; the selection rule below prefers it over any version this stage computes.

Match the mapping table against the entry read as a list of /-delimited tokens, case-insensitively: a pattern matches only where it covers whole tokens — one token, or a contiguous run of them for a pattern that itself contains /. Never match an arbitrary substring. Measured against a real 3812-entry listing, substring matching produced two classes of false positive: a module whose name merely ends with a mapped command (an optimisation tool named ...slang matched slang), and a module whose name or version string merely contains one (a verification tool whose version ends _python2, and a GUI tool bundle whose name contains python3, both matched python3). The second class is the damaging one: tool_discovery Step A loads the selected matched Python module and keeps it loaded for the whole orchestrator run, and the top-ranking substring match was that GUI bundle rather than an interpreter. Whole-token matching drops both classes and leaves the 12 genuine tools.

Module version selection

Wherever this skill chooses a version for a tool — tool_discovery rule 2 Step A c and rule 9 below — it uses this rule. Do not restate it at the call sites and do not substitute a plain sort: "highest lexicographic" and "newest" are different answers, and on a real tree the lexicographic one is wrong: measured on the reference host, a 13-version Python tree spanning 3.6.x–3.14.x has newest 3.14.6 and lexicographic maximum 3.9.7 — the fourth-oldest — because "9" > "1" at the second segment.

The result is the version this host should use, which is not always the newest one: where the module system marks a default, that default wins, and a site default is commonly an older, qualified build. On the same host the annotated default differed from the newest available for every tool checked, by several versions for some. That is intended, not a bug to work around.

Selection order. Take the first that applies:

  1. the entry the listing annotates as the system's default, where it marks one — that is the site's own answer and outranks anything computed here;
  2. otherwise the highest release version by the comparator below;
  3. otherwise the highest version at all — only where every candidate is non-release.

Non-release versions are those with any segment equal to dev, test, debug, rc, alpha, beta, snapshot or nightly, case-insensitively. They stay in versions_available — they exist and a user may want one — but they are never selected automatically while a release exists. This matters because such builds sit in the same trees as releases: the reference host carries 3.12.2_test, 3.6.2-debug and 5.24-dev alongside normal versions. Note the list is deliberately short: a site revision tag such as 3.12.2.R10, or a patch letter such as 3.9.7n, is a release and is ordered by the comparator like any other.

Comparator. Split each version on ., - and _, then compare segment by segment:

  • both segments numeric — compare as integers, so 3.14.6 > 3.9.7;
  • a segment of digits followed by letters (7n, 03b) — compare its numeric prefix first, then its letter suffix as text, so 3.9.7n > 3.9.7;
  • one numeric and one not — the numeric ranks higher;
  • neither numeric — compare as text.

Where every shared segment is equal, the version with more segments ranks higher — both 3.12.2.1 > 3.12.2 and 3.12.2.R10 > 3.12.2, since an extra segment denotes a revision of the shorter version. Do not special-case a non-numeric extra segment to rank lower: the case that motivates it, 5.24 over 5.24-dev, is already handled by the non-release exclusion above, and inside the release pool the only remaining extras are post-release revision tags, for which longer genuinely is newer.

This set is the one that breaks every naive form, and is worth checking any implementation against — 3.9.7, 3.9.7n, 3.12.2, 3.12.2.1, 3.12.2.R10, 3.14.6, 5.048, 5.24-dev: text comparison picks 3.9.7, splitting on . and comparing as integers raises on R10, and comparing by segment count alone picks 5.24-dev unless the non-release exclusion has already removed it.

Record the outcome per tool in module-status.json as selected, selected_basis and candidates, so a wrong pick is visible in the artifact rather than only in the loaded environment.

WARN taxonomy
ConditionSeveritydescription / fix
module_system: "none"WARN, proceed"no module system found - $MODULESHOME unset and modulecmd not in PATH; no tools surveyed via modules" / "no action needed if every required tool is in PATH"
module_system is not "none" and module_listing: "UNAVAILABLE"WARN, proceed"<module_system> module system detected (<module_system_detail>) but no invocation produced a listing (<module_listing_error>) - tools_via_modules is empty because the listing failed, not because no modules exist" / "re-run from a shell where module resolves, or source $MODULESHOME/module.sh (bash) or $MODULESHOME/module.csh (tcsh) first, then re-run module_discovery"
the row above and a critical-path tool (yosys, verilator, openroad, sta) is MISSING in tool-status.jsonWARN and escalatethat description with " - critical tool(s) <list> are MISSING and could not be checked against the module listing" appended; failure_class: "tool_error", suggested_next_step: "escalate"

An empty tools_via_modules is evidence that the host offers no modules only when module_listing is "LISTED". Never report an exhausted ladder as "no modules found": the two are indistinguishable in the artifact unless module_listing is read.

The fix text names both init scripts because the probes all run under bash -c regardless of the user's login shell, but a user re-running by hand may be in either bash or tcsh.

Module-to-tool mapping table
Tool commandModule name patterns to match (case-insensitive substring)
vcsvcs, synopsys-vcs, synopsys/vcs
xrunxcelium, cadence-xcelium, cadence/xcelium
dc_shelldesign-compiler, synopsys/dc, dc_shell
innovusinnovus, cadence/innovus, cadence-innovus
vsimquesta, questasim, mentor/questa
pt_shellprimetime, synopsys/pt, pt_shell
fm_shellformality, synopsys/formality, fm_shell
genusgenus, cadence-genus, cadence/genus
fc_shellfusion-compiler, synopsys/fc, fc_shell
icc2_shellic-compiler, icc2, synopsys/icc2
tempustempus, cadence-tempus, cadence/tempus
conformalconformal, cadence-conformal, cadence/conformal
jgjaspergold, cadence-jg, cadence/jg
vcfvc-formal, vcf, synopsys/vcf
qformalquesta-formal, qformal, mentor/qformal
tmaxtetramax, tmax, synopsys/tmax
modusmodus, cadence-modus, cadence/modus
tessenttessent, siemens-tessent, siemens/tessent
spyglassspyglass, synopsys-spyglass, synopsys/spyglass
vitis_hlsvitis, vitis-hls, xilinx/vitis
stratusstratus, cadence-stratus, cadence/stratus
catapultcatapult, siemens-catapult, siemens/catapult
vivadovivado, xilinx/vivado
quartus_shquartus, intel/quartus
liberolibero, microchip/libero
artisanartisan, arm-artisan, arm/artisan
siliconsmartsiliconsmart, synopsys/siliconsmart
liberateliberate, cadence-liberate, cadence/liberate
starrcstarrc, synopsys-starrc, synopsys/starrc
voltusvoltus, cadence-voltus, cadence/voltus
armccarmcc, arm-compiler, arm/armcc
JLinkGDBServerjlink, segger, segger/jlink
t32marmtrace32, lauterbach, lauterbach/trace32
armdsarmds, arm-ds, arm/armds
verilatorverilator
yosysyosys
openroadopenroad
klayoutklayout
iverilogicarus, iverilog
staopensta
gccgcc
llvm-configllvm
xschemxschem
gtkwavegtkwave
uvuv
python3python, python3
slangslang
surelogsurelog
sv2vsv2v
sbysymbiyosys, sby, yosyshq/sby
bambu-hlsbambu, bambu-hls, panda-bambu
nextpnrnextpnr
openFPGALoaderopenfpgaloader
openocdopenocd
Rules
  1. Classify the module system using the detection rules above
  2. Obtain a listing using the invocation ladder above; record module_invocation and module_listing, and on exhaustion module_listing_error
  3. If module_listing is "UNAVAILABLE": emit the matching WARN from the taxonomy above — escalating first where a critical-path tool is MISSING — write module-status.json with tools_via_modules: [], and advance. Skip rules 4-9: no tool can be matched against a listing that was never obtained, and an unmatched tool is not evidence that no module provides it
  4. For each entry in the listing, test against the mapping table (case-insensitive)
  5. For each matched tool, collect all available version strings per the entry-format rules above
  6. Write module-status.json before advancing
  7. For each tool marked FOUND in tool-status.json that also has a module available: change status to FOUND_PREFER_MODULE, add module_names and versions_available fields, include in load-modules.sh — module takes precedence over PATH version
  8. For each tool marked MISSING in tool-status.json that has modules available: change status to MISSING_LOAD_MODULE, add module_names and versions_available fields
  9. Generate load-modules.sh for all FOUND_PREFER_MODULE and MISSING_LOAD_MODULE tools; choose each tool's version per Module version selection above; comment out alternative versions inline, and state the basis in the selected line's trailing comment
  10. Never auto-run load-modules.sh — print: "Review and source load-modules.sh to load EDA modules, then re-run the flow"
Extended tool-status.json schema

Fields added by this stage to each entry in the tools array (backward-compatible additions):

json
{
  "tool": "",
  "command": "",
  "status": "FOUND | FOUND_PREFER_MODULE | MISSING | MISSING_LOAD_MODULE | PROPRIETARY_ONLY",
  "version": "",
  "path": "",
  "role": null,
  "dialect": null,
  "module_names": [],
  "versions_available": []
}

Note: The top-level python_env object is preserved unchanged during this stage — only entries in the tools array are modified. The python3 tool entry is treated like any other: if python_env.type == "module", its tools array status is upgraded from FOUND to FOUND_PREFER_MODULE and it is included in load-modules.sh. The version, role and dialect fields written by tool_discovery are also preserved unchanged — this stage adds module fields and may change status, nothing else.

Show full SKILL.md (3,250 more words)Show less
QoR Metrics to Evaluate
  • module_system_detected: bool — true when module_system is not "none"; a custom wrapper counts
  • module_listing_ok: bool — true when module_listing is "LISTED". tools_found_via_modules: 0 is only meaningful when this is true; where it is false the count says nothing about what the host offers
  • tools_found_via_modules: count of tools with status MISSING_LOAD_MODULE or FOUND_PREFER_MODULE
Stage Output Summary

Print a human-readable table before advancing:

text
Module system     : custom - $MODULESHOME=<site path>, no modulecmd in PATH, no init/bash
Module listing    : LISTED via `. "$MODULESHOME/module.sh"; module avail` (3813 entries, 160s)
Tools in PATH     : 12
Tools via modules : 5
  vcs        — selected synopsys/vcs/2021.01 (highest_release, 2 candidates); also 2020.03
  xrun       — selected cadence/xcelium/20.09 (site_default, 1 candidate)
  dc_shell   — selected synopsys/dc/2022.03 (site_default, 3 candidates); also 2021.06, 2020.09
  innovus    — selected cadence/innovus/21.1 (highest_release, 2 candidates); also 20.12
  pt_shell   — selected synopsys/primetime/2022.06 (site_default, 2 candidates); also 2021.06

Always print the basis and the candidate count. A site_default that is several versions behind the newest available is normal and intended; highest_prerelease is not, and is the one to notice.

Where the invocation ladder was exhausted, the second line reads Module listing : UNAVAILABLE (<module_listing_error>) and the Tools via modules count is printed as not surveyed — never as 0, which reads as a surveyed host that offers nothing.

Output Required
  • module-status.json — module system details and per-tool module listings
  • Updated tool-status.json — statuses and module fields added for matched tools
  • load-modules.sh — generated when any tool has status FOUND_PREFER_MODULE or MISSING_LOAD_MODULE; omitted when no tool qualifies, which includes but is not limited to module_system: "none". Never infer the omission from module_system alone: a detected system whose module_listing is "UNAVAILABLE" also yields no qualifying tools, and that is the WARN case above, not a clean skip

module-status.json schema:

json
{
  "module_system": "tclmod | custom | none",
  "module_system_detail": "<evidence: $MODULESHOME value, whether modulecmd was in PATH, what was found under $MODULESHOME>",
  "module_system_version": "",
  "module_listing": "LISTED | UNAVAILABLE",
  "module_invocation": "<the ladder rung that produced the listing, verbatim, or null>",
  "module_listing_error": null,
  "tools_via_modules": [
    {
      "tool": "<command>",
      "module_names": ["synopsys/vcs/2020.03", "synopsys/vcs/2021.01"],
      "versions_available": ["2020.03", "2021.01"],
      "selected": "synopsys/vcs/2021.01",
      "selected_basis": "site_default | highest_release | highest_prerelease",
      "candidates": 2
    }
  ]
}

module_system says what is installed; module_listing says whether tools_via_modules can be trusted. The two are independent: a tclmod host can be UNAVAILABLE, and a custom host can be LISTED. module_system: "none" always pairs with module_listing: "UNAVAILABLE" and module_listing_error: "no module system present", so one field answers trustworthiness in every case.

load-modules.sh format:

bash
#!/usr/bin/env bash
# Generated by module_discovery — source this file to load EDA tool modules
# Usage: source load-modules.sh

module load synopsys/vcs/2021.01       # selected: highest_release; alternatives: 2020.03
module load cadence/xcelium/20.09
# module load cadence/innovus/21.1     # uncomment if needed

Stage: tool_installation

Domain Rules
  1. Never auto-run installs — only generate per-tool install scripts
  2. If python3 is missing: FAIL immediately and escalate — required for all wrapper scripts
  3. Generate one install-<toolname>.sh for every tool with status MISSING; skip tools with status FOUND, FOUND_PREFER_MODULE, MISSING_LOAD_MODULE, or PROPRIETARY_ONLY
  4. Each script follows the Per-Tool Script Structure below
  5. Use the Package Name Mapping Table to emit correct install commands for the detected OS/PM
  6. Python package install scripts (openlane, cocotb, uv): read python_env.exec from tool-status.json and substitute it for <PYTHON_EXEC> (and python_env.bin_dir for <PYTHON_BIN_DIR>). At the top of each Python package install script, emit:
    bash
    PYTHON_EXEC="<value of python_env.exec>"
    # Verify this is the intended interpreter before running
    Use "$PYTHON_EXEC" -m pip install <package> as the install command. Never use bare pip install when python_env.type is "custom" or "module".
  7. Proprietary tools: no script generated — record a note in the sign-off summary only
  8. Modulefile format: always TCL classic (no file extension); always generate modulefiles unconditionally — a modulefile that may need adapting is more useful than none. Which case the user is in decides the WARN, one message per module_system value:
    • tclmod: no WARN — Environment Modules reads TCL classic
    • module_system == "none" or module_system == "custom" — see the WARN text under the Per-Tool Script Structure below. Both still get the modulefile
  9. Each script ends with registration guidance, conditioned on module_system:
    • tclmod: register $EDA_MODULEFILES_ROOT in $MODULEPATH if not already present
    • custom: say that a site wrapper registers modulefiles its own way — it may not read $MODULEPATH at all — so the mechanism must be taken from the site's own documentation. Do not emit the $MODULEPATH line as the instruction
    • none: say there is no module system to register with yet; the $MODULEPATH line applies once Environment Modules is installed
  10. Write all install-<toolname>.sh scripts to the install-missing-tools/ directory; create the directory if it does not exist; do not create the directory or any scripts if no tools are MISSING
QoR Metrics to Evaluate
  • install_scripts_generated: count of install-<toolname>.sh scripts written to install-missing-tools/ — one per MISSING tool, so it equals tools_missing; 0 when nothing is missing
Common Issues & Fixes
IssueFix
python3 not foundEscalate immediately — all wrapper scripts depend on it
openroad (OpenROAD): build requiredRefer to https://github.com/The-OpenROAD-Project/OpenROAD
bambu-hls (Bambu HLS): Linux onlyWrap with status: WARN on macOS/Windows
Install Directory Layout
text
$EDA_TOOLS_ROOT/                         (default: /tools)
  <toolname>/<version>/                  e.g. /tools/verilator/5.028/
    bin/
    lib/
    share/

$EDA_MODULEFILES_ROOT/                   (default: /tools/toolmgr/env/modulefiles)
  <toolname>/<version>                   e.g. /tools/toolmgr/env/modulefiles/verilator/5.028

Both roots are read from env vars at script runtime with the defaults above.

Per-Tool Script Structure

Each install-<toolname>.sh — file: $EDA_MODULEFILES_ROOT/<toolname>/<version> (TCL, no extension):

bash
#!/usr/bin/env bash
# install-<toolname>.sh — generated by infrastructure-orchestrator
# Installs <Tool Full Name> to $EDA_TOOLS_ROOT/<toolname>/<version>
# and generates a TCL modulefile at $EDA_MODULEFILES_ROOT/<toolname>/<version>
set -euo pipefail

TOOL_NAME="<toolname>"
TOOL_VERSION="<detected-or-latest>"
EDA_TOOLS_ROOT="${EDA_TOOLS_ROOT:-/tools}"
EDA_MODULEFILES_ROOT="${EDA_MODULEFILES_ROOT:-/tools/toolmgr/env/modulefiles}"
INSTALL_DIR="${EDA_TOOLS_ROOT}/${TOOL_NAME}/${TOOL_VERSION}"

# --- Install ---
# Build-from-source: pass --prefix="${INSTALL_DIR}" to configure/cmake
# Package manager: use apt-get/brew/pacman (see mapping table below)

# --- Generate TCL modulefile ---
MODFILE_DIR="${EDA_MODULEFILES_ROOT}/${TOOL_NAME}"
mkdir -p "${MODFILE_DIR}"

cat > "${MODFILE_DIR}/${TOOL_VERSION}" <<EOF
#%Module1.0
proc ModulesHelp { } {
    puts stderr "<Tool Full Name> ${TOOL_VERSION}"
}
module-whatis "<Tool Full Name> ${TOOL_VERSION} — <one-line description>"

set prefix ${INSTALL_DIR}
prepend-path PATH            \$prefix/bin
prepend-path LD_LIBRARY_PATH \$prefix/lib
prepend-path MANPATH         \$prefix/share/man
# add PYTHONPATH, PKG_CONFIG_PATH, or tool-specific setenv as needed
EOF

echo "Modulefile written: ${MODFILE_DIR}/${TOOL_VERSION}"

# --- Register modulefiles root (if not already set) ---
# tclmod only — see rule 9 for custom and none:
# export MODULEPATH=${EDA_MODULEFILES_ROOT}:${MODULEPATH}
# Add the above line to ~/.bashrc or /etc/profile.d/eda-modules.sh

Still generate the modulefile block in every case, and emit a WARN in the stage output when:

  • module_system == "none": "no module system detected — automatic module loading is unavailable; the generated modulefile is usable once Environment Modules is installed"
  • module_system == "custom": "generated modulefiles are TCL classic; the detected module system is a site wrapper, not confirmed Environment Modules, and may not read them — see module_system_detail in module-status.json for what module_discovery found, and adapt the modulefile to the site's format if module load fails"

Modulefile content rules:

  • Minimum env vars in every modulefile: PATH, LD_LIBRARY_PATH
  • Add where applicable: MANPATH, PKG_CONFIG_PATH, PYTHONPATH
  • Tool-specific root vars (set these when present). Rows are keyed by the command — the tool value in tool-status.json — never the product name, which is in parentheses for the reader. For a Python package that value is the package name, so the cocotb row matches cocotb, not its detection probe cocotb-config:
    • verilator (Verilator) → VERILATOR_ROOT
    • yosys (Yosys) → YOSYS_DATDIR
    • llvm-config (LLVM) → LLVM_DIR
    • cocotb (cocotb, Python package) → COCOTB_SHARE_DIR
Package Name Mapping Table
Tool commandapt package(s)brew formulapacman pkgfallback / notes
verilatorverilatorverilatorverilator—
slangbuild-from-source——https://github.com/MikePopoloski/slang
surelogbuild-from-source——https://github.com/chipsalliance/Surelog
sv2vbuild-from-source——https://github.com/zachjs/sv2v
iverilogiverilogicarus-verilogiverilog—
yosysyosysyosysyosys—
abcbuild-from-sourceberkeley-abc—https://github.com/berkeley-abc/abc
openroadbuild-from-source——https://github.com/The-OpenROAD-Project/OpenROAD
openlane<PYTHON_EXEC> -m pip install openlane<PYTHON_EXEC> -m pip install openlane<PYTHON_EXEC> -m pip install openlanePython package; substitute <PYTHON_EXEC> with python_env.exec from tool-status.json
klayoutklayoutklayoutklayout (AUR)—
stabuild-from-source——https://github.com/The-OpenROAD-Project/OpenSTA
sbysymbiyosys——https://github.com/YosysHQ/sby
gem5build-from-source——https://github.com/gem5/gem5
bambu-hlsvendor download (Linux only)——https://github.com/ferrandi/PandA-bambu; WARN on macOS/Windows
nextpnrnextpnr-ice40 nextpnr-ecp5nextpnrnextpnr—
openFPGALoaderopenfpgaloader——https://github.com/trabucayre/openFPGALoader
cocotb<PYTHON_EXEC> -m pip install cocotb<PYTHON_EXEC> -m pip install cocotb<PYTHON_EXEC> -m pip install cocotbPython package; use <PYTHON_BIN_DIR>/cocotb-config or cocotb-config to detect
llvm-configllvm-devllvmllvm—
gccbuild-essentialgccgcc—
openocdopenocdopen-ocdopenocd—
xschemxschembuild-from-source—https://github.com/StefanSchippers/xschem
gtkwavegtkwavegtkwavegtkwave—
uvcurl -LsSf https://astral.sh/uv/install.sh | sh (standalone) or <PYTHON_EXEC> -m pip install uvuv (brew) or <PYTHON_EXEC> -m pip install uv<PYTHON_EXEC> -m pip install uvPrefer standalone astral.sh installer; pip fallback when custom/module Python is active
Output Required
  • One install-<toolname>.sh per MISSING tool written to install-missing-tools/ (no single combined script)

Stage: wrapper_deployment

Domain Rules
  1. Deploy all 11 wrapper scripts to plugins/infrastructure/tools/
  2. Run chmod +x on every wrapper; if permission denied: FAIL and escalate with sudo chmod +x instructions
  3. Every wrapper must emit JSON conforming to the schema below regardless of exit code
  4. Test each wrapper with --version or --help after deploy; tolerate MISSING tools (wrappers must handle tool-not-found gracefully with status: "FAIL"). A --version run prints no design result, so it returns status: "WARN" with verified: false — that is the expected outcome and confirms the wrapper runs and emits valid JSON. wrap-verilator-sim.sh takes a simulation binary, not tool arguments, so it answers --version / --help itself with the same WARN. A FAIL from this test therefore always means a missing tool or a broken wrapper, never a mis-invocation
  5. Never suppress the tool's original exit code
  6. Never report PASS without a result: a wrapper that finds nothing it recognises in the tool's output reports WARN with verified: false, even when the tool exited 0
Wrapper JSON Output Schema

Every wrapper script must print exactly this JSON structure to stdout:

json
{
  "tool": "<tool-name>",
  "exit_code": 0,
  "status": "PASS|FAIL|WARN",
  "verified": true,
  "summary": {},
  "errors": [],
  "warnings": [],
  "raw_log": "/tmp/<tool>-XXXXXX.log"
}

Fields:

  • status, evaluated in this order:
    1. FAIL if exit_code != 0, the output contains an error, a tool-specific fail marker is present, or the tool was not found
    2. WARN if exit_code == 0 but the wrapper found no result it recognises in the output (verified: false); the first entry in warnings says so
    3. WARN if a result was found and the output contains warnings
    4. PASS if a result was found and there are no warnings
  • verified: true when status rests on something the wrapper observed — a result parsed from the output, or a failure. false when the tool exited 0 without a recognisable result, or did not run. A result with verified: false is not a pass: read raw_log, or the tool's own report file, before assigning a stage status
  • summary: tool-specific metrics (cells, timing, coverage, etc.). A metric the wrapper could not find is omitted or null, never 0
  • raw_log: absolute path to temp file containing full unfiltered output

The MCP adapter (mcp-adapter.py) returns status: "FAIL" with verified: false when a wrapper prints nothing, prints something that is not JSON, or prints JSON without a valid status: the wrapper contract is to emit this JSON on every run, so anything else means the wrapper itself produced no result.

QoR Metrics to Evaluate
  • wrappers_deployed: count of wrapper scripts with executable bit set (target: 11)
Output Required
  • 11 executable wrapper scripts in plugins/infrastructure/tools/

Stage: mcp_configuration

Domain Rules
  1. Emit MCP config snippets for the 10 tool servers (8 batch + 2 session). Also resolve the path in mcp-memory.json, the optional chip-design-memory server that provides the query_experiences tool the orchestrators' "semantic experience lookup" uses. It is not a tool server and is not counted, so plugins/infrastructure/mcp/ holds 11 files.
  2. All batch configs use "command": "python3" with mcp-adapter.py — never point directly to the wrapper script as the command; wrapper scripts are not MCP servers
  3. Session configs use mcp-session-adapter.py with --tool openroad or --tool opensta. mcp-memory.json runs mcp-memory.py directly, with no --wrapper or --tool
  4. Resolve the absolute adapter, wrapper and mcp-memory.py paths at runtime using realpath or pwd — never leave the placeholder /absolute/path/to/ in the emitted snippets. No installer does this: install.sh, install.ps1 and bin/install.mjs never touch the MCP configs
  5. Print each snippet with explicit instruction: "Paste the mcpServers block into your .claude/settings.json"
  6. Write the snippet files to plugins/infrastructure/mcp/
  7. Do not modify .claude/settings.json automatically — user must do this manually
MCP Config Templates

The command is always python3, and the first argument is the MCP server script — never a wrap-*.sh (rule 2). The files in plugins/infrastructure/mcp/ are the reference for each tool's --description and env; edit their paths, not their shape.

Batch (Tier 1) — mcp-adapter.py runs the wrapper named by --wrapper:

json
{
  "mcpServers": {
    "<tool>": {
      "type": "stdio",
      "command": "python3",
      "args": [
        "/absolute/path/to/plugins/infrastructure/tools/mcp-adapter.py",
        "--wrapper", "/absolute/path/to/plugins/infrastructure/tools/wrap-<tool>.sh",
        "--tool", "<tool>",
        "--description", "<what the tool returns>"
      ],
      "env": {
        "TOOL_TIMEOUT_S": "300"
      }
    }
  }
}

Session (Tier 2) — mcp-session-adapter.py keeps the tool process alive:

json
{
  "mcpServers": {
    "openroad-session": {
      "type": "stdio",
      "command": "python3",
      "args": [
        "/absolute/path/to/plugins/infrastructure/tools/mcp-session-adapter.py",
        "--tool", "openroad"
      ],
      "env": {
        "SESSION_TIMEOUT_S": "120"
      }
    }
  }
}

Memory (optional) — mcp-memory.py is the server itself:

json
{
  "mcpServers": {
    "chip-design-memory": {
      "type": "stdio",
      "command": "python3",
      "args": [
        "/absolute/path/to/plugins/infrastructure/tools/mcp-memory.py"
      ],
      "env": {}
    }
  }
}
QoR Metrics to Evaluate
  • mcp_servers_configured: count of tool-server snippet files written (target: 10; mcp-memory.json is optional and not counted)
Output Required

Batch MCP configs (Tier 1):

  • plugins/infrastructure/mcp/mcp-yosys.json
  • plugins/infrastructure/mcp/mcp-openroad.json
  • plugins/infrastructure/mcp/mcp-opensta.json
  • plugins/infrastructure/mcp/mcp-klayout.json
  • plugins/infrastructure/mcp/mcp-verilator.json
  • plugins/infrastructure/mcp/mcp-bambu.json
  • plugins/infrastructure/mcp/mcp-gem5.json
  • plugins/infrastructure/mcp/mcp-symbiflow.json

Session MCP configs (Tier 2):

  • plugins/infrastructure/mcp/mcp-openroad-session.json
  • plugins/infrastructure/mcp/mcp-opensta-session.json

Optional memory server config (not a tool server, not counted):

  • plugins/infrastructure/mcp/mcp-memory.json

Adapter scripts (required — MCP servers will not start without these):

  • plugins/infrastructure/tools/mcp-adapter.py
  • plugins/infrastructure/tools/mcp-session-adapter.py

Printed MCP config snippets for each tool with resolved absolute paths


Stage: environment_validation

Domain Rules

Critical-path tools are the tool-status.json entries whose tool is yosys, verilator, openroad or sta — Yosys, Verilator, OpenROAD and OpenSTA. Every rule below that tests them matches on that key, never on the product name: OpenSTA's command is sta, so a rule looking for "OpenSTA" matches no entry and cannot conclude the tool is missing. On a FAIL gate that is a silent pass.

  1. Python environment check — before any other check: read python_env from tool-status.json:

    • If python_env.type == "module": run which python3 to verify the module is still loaded. If it fails, FAIL immediately with: "Python environment not active — source load-modules.sh (module: <python_env.module_name>) and re-run environment_validation."
    • If python_env.type == "custom" or "system": run which python3 and verify the path matches python_env.exec; emit WARN if it differs.
  2. Re-run tool presence checks using exactly the detection tool_discovery rules 2–3 perform — including their PATH fallbacks, and including their python_env.type branches. A stage that detects the same tool by a different method will disagree with tool_discovery on real installs, and the disagreement is silent:

    • openlane: "$PYTHON_EXEC" -m pip show openlane — unchanged, rule 3 has no fallback for it
    • cocotb, uv: where python_env.type is custom or module, try "$PYTHON_BIN_DIR/<tool>" first and fall back to the PATH command; where it is system, use the PATH command only — rule 3 never builds a $PYTHON_BIN_DIR probe in that case, so neither may this rule.

    Record for each package which path resolved it, as resolved_via in tool-manifest.json. Where a package resolved via the PATH fallback while python_env.type is custom or module, emit a WARN — the tool exists, so this is never an absence:

    • description: "<tool> resolved on PATH at <path>, outside the active Python environment's bin dir (<python_env.bin_dir>) — it may target a different interpreter than <python_env.exec>"
    • fix: "confirm <tool> operates on the intended interpreter, or reinstall it into the active environment with \"$PYTHON_EXEC\" -m pip install <tool>"

    This is the hazard the probe is for, and it is common: tool_installation prefers the standalone astral.sh installer for uv, which installs into its own prefix rather than $PYTHON_BIN_DIR. Measured on the reference host, uv resolves from a shared bin directory unrelated to the active interpreter's prefix, which is exactly this case. Never emit it for python_env.type == "system", where PATH is the active environment.

    Compare results against tool-status.json — written by tool_discovery, updated in place by module_discovery, and the only tool record that exists when this rule runs. Never compare against tool-manifest.json: that is this stage's own output, so on any run there is nothing to compare against. This stage reads tool-status.json and does not write it; a disagreement is a WARN, never a status downgrade.

  3. Verify all 11 wrapper scripts exist and have executable bit set

  4. Verify MCP snippet files are present in plugins/infrastructure/mcp/ (all 10 tool-server snippets; mcp-memory.json is optional and its absence is not a FAIL) and that mcp-adapter.py + mcp-session-adapter.py are present in plugins/infrastructure/tools/

  5. FAIL if any critical-path tool (yosys, verilator, openroad, sta) is still MISSING. Escalate — do not loop back to tool_installation: that stage only generates install-<toolname>.sh and never executes it (tool_installation rule 1), so no retry can change a tool's status. Report the missing commands and tell the user to review and run the generated scripts in install-missing-tools/, then re-run environment_validation.

  6. For each tool with status MISSING_LOAD_MODULE in tool-status.json: emit a WARN issue with description "<tool> not in PATH — available via module" and fix "source load-modules.sh, then re-run environment_validation"

  7. Dialect-conflict check — group by role every tool whose status is FOUND, FOUND_PREFER_MODULE, PROPRIETARY_ONLY or MISSING_LOAD_MODULE, ignoring entries whose role is null. A MISSING_LOAD_MODULE tool counts: it is one the user will load and build a command line for, which is exactly what this check warns about. Only MISSING is excluded. Emit one WARN issue per role that holds two or more distinct dialect values and at least one PROPRIETARY_ONLY tool. One WARN per role, never one per pair: a role with five members yields ten pairs saying the same thing, and that volume is what makes a warning ignorable.

    • description: "role <role> is held by tools of differing dialects: <tool_a> (<dialect_a>), <tool_b> (<dialect_b>), … — command lines are not portable between them"
    • fix: "pick one dialect per role for this flow and build command lines from that vendor's option set; do not reuse a command line across dialects"

    List every member tool in the one description, so the reader sees the whole conflicting set. Report the number of such roles — not pairs, not tools — as dialect_conflicts. This is WARN only: it never FAILs the stage and never blocks environment.signoff. Coexistence is normal — what is not normal is assuming substitutability. Requiring at least one proprietary tool in the group keeps the WARN rare enough to be read; two open-source simulators are on nearly every host.

  8. If any critical-path tool (yosys, verilator, openroad, sta) has status MISSING_LOAD_MODULE: emit WARN and set suggested_next_step: "escalate" with message "Critical tool <tool> requires module load before downstream flows can run. Source load-modules.sh and re-run environment_validation."

  9. Simulator compile smoke test (opt-in) — gated by design_state.pipeline_config.simulator_smoke_test (boolean; absent or false is the default). Compiling consumes a license seat that tool_discovery/tool_installation never touch, so this never runs unless explicitly enabled.

    • Disabled (default): emit WARN — description "simulator invocation not verified", fix "set design_state.pipeline_config.simulator_smoke_test = true to compile-check detected simulators". Never FAIL for this.
    • Enabled: for every tool-status.json entry whose role is rtl_simulator and status is FOUND or FOUND_PREFER_MODULE, compile a 3-line placeholder module (module adice_smoke_test; endmodule) through that tool's wrapper — wrap-vcs-compile.sh for vcs, wrap-xrun-compile.sh for xrun, wrap-verilator-lint.sh for verilator. vsim and iverilog have no wrapper: record UNVERIFIED with no command, same as an unprobed proprietary tool. Read the wrapper's own status, never the exit code alone — the reason this check exists at all: innovus -version, vsim -version and voltus -version all exit 0 on this host while reporting an expired license, a missing shared library, or a platform install error.
      • status: "FAIL" → FAIL this stage: the tool cannot compile a trivial module.
      • status: "WARN", verified: false, with a license-queue or timeout signal in warnings → WARN, not FAIL, so a license-starved site can still sign off.
      • status: "PASS", or "WARN" with verified: true → smoke test passed. Record each probed simulator as {tool, status, verified} under simulator_smoke_test.results in tool-manifest.json.
  10. Print final sign-off summary: tools detected, tools via modules, wrappers deployed, MCP servers configured, simulator smoke test result

QoR Metrics to Evaluate
  • dialect_conflicts: count of roles (not pairs) held by detected tools of two or more differing dialect values where at least one is PROPRIETARY_ONLY — computed by rule 7. WARN only; it never blocks sign-off
  • simulator_smoke_tests_run: count of rtl_simulator-role tools actually compile-checked by rule 9 when pipeline_config.simulator_smoke_test is enabled; 0 when disabled or none were FOUND. A per-tool compile FAIL is reported through rule 9's stage FAIL path, not through this count.

The rest of this stage's verdict is in tool-manifest.json (Output Required) rather than in QoR counts: Python env liveness, wrapper executable bits, MCP file presence and the critical-path check.

Sign-off Checklist
  • tool-status.json written with all tools surveyed (includes python_env object)
  • module-status.json written (even if module_system is "none")
  • install-<toolname>.sh scripts generated for all MISSING tools in install-missing-tools/ (auto-run is user's choice)
  • load-modules.sh generated if any module-available tools found (auto-run is user's choice)
  • tool-manifest.json written by this stage, matching the schema in Output Required
  • All 11 wrappers deployed and executable
  • Simulator compile smoke test run and recorded, or explicitly WARNed as unverified (gated by design_state.pipeline_config.simulator_smoke_test)
  • mcp-adapter.py and mcp-session-adapter.py present in plugins/infrastructure/tools/
  • All 10 tool-server MCP config snippets written with resolved absolute paths and printed
  • No critical-path tools with status MISSING or MISSING_LOAD_MODULE
  • role and dialect recorded on every entry in tool-status.json, and every same-role/different-dialect coexistence involving a proprietary tool reported
Output Required
  • Printed environment validation report
  • tool-manifest.json — written by this stage. There is no prior manifest to update: no earlier stage produces one, so this stage creates it.

tool-manifest.json is a validation receipt, not a tool list. It records what this stage measured and references per-tool state rather than copying it — tool-status.json already carries every per-tool field (tool, command, status, version, path, role, dialect, module_names, versions_available) and python_env, and module_discovery sets the precedent that a later stage updates that file in place rather than forking a parallel copy. A manifest that restated those fields would be a second source of truth for the same facts.

What this stage establishes that tool-status.json cannot express is python_env liveness (the recorded module may no longer be loaded, the recorded exec may no longer match), wrapper executable bits, MCP artifact presence, the computed dialect-conflict set, and the sign-off verdict:

json
{
  "run_id": "",
  "timestamp": "<ISO-8601>",
  "host": "",
  "tool_status_source": "tool-status.json",
  "python_env_live": {
    "type": "module | system | custom",
    "exec_expected": "",
    "exec_actual": "",
    "matches": false
  },
  "python_packages": [
    {
      "tool": "",
      "resolved_via": "python_env_bin_dir | path | pip_show",
      "path": ""
    }
  ],
  "wrappers": { "expected": 11, "executable": 0, "missing": [] },
  "mcp": { "snippets_expected": 10, "snippets_present": 0, "adapters_present": false, "missing": [] },
  "dialect_conflicts": [
    { "role": "", "members": [ { "tool": "", "dialect": "" } ] }
  ],
  "simulator_smoke_test": { "enabled": false, "results": [] },
  "critical_path": { "required": ["yosys", "verilator", "openroad", "sta"], "missing": [] },
  "signoff": false
}

python_packages[].resolved_via is the record behind rule 2's WARN: it is what makes "found, but outside the active environment" auditable after the run instead of print-only. dialect_conflicts lists the members per role, not just the count rule 7 reports, so the conflict can be read back without re-deriving it. simulator_smoke_test.results is a list of {tool, status, verified} — one entry per rtl_simulator-role tool rule 9 probed — so a license-queue WARN is distinguishable from a tool that was never probed because the gate was disabled.

© hdl-tools, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in plugins/infrastructure/skills/infrastructure of hdl-tools/digital-chip-design-agents.

Open the folder on GitHubat commit 38736b1

Compare with similar skills

Infrastructure next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Infrastructure compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Infrastructure this skillhdl-tools/digital-chip-design-agents212—~18kAutomated safety check: NotesMIT
MCP Server Builderanthropics/skills180k64 repos~2.3kAutomated safety check: PassApache-2.0
MCP Server BuildershareAI-lab/learn-claude-code78k5 repos~1.2kAutomated safety check: PassMIT
MCP Integration for Pluginsanthropics/claude-plugins-official38k11 repos~3.1kAutomated safety check: PassApache-2.0
Figma use_figma Plugin API Ruleswarpdotdev/warp65k4 repos~4.4kAutomated safety check: PassAGPL-3.0
Stitch to Remotion Walkthrough Videosgoogle-labs-code/stitch-skills8.4k6 repos~3.2kAutomated safety check: NotesApache-2.0

Similar skills

  • MCP Server Builder

    anthropics/skills

    Official

    Guides the design and implementation of Model Context Protocol servers in TypeScript or Python, from tool naming and error messages to evaluation.

    180k GitHub starsUsed in 64 repos~2.3k tokens
    Agent WorkflowsAuto-check passed
  • MCP Server Builder

    shareAI-lab/learn-claude-code

    Walks through building MCP servers in Python or TypeScript that expose tools, resources and prompts to Claude, with templates, registration and testing.

    78k GitHub starsUsed in 5 repos~1.2k tokens
    Agent WorkflowsAuto-check passed
  • MCP Integration for Plugins

    anthropics/claude-plugins-official

    Official

    Explains how to bundle Model Context Protocol servers in a Claude Code plugin, covering config files, stdio, SSE, HTTP and WebSocket server types, and authentication.

    38k GitHub starsUsed in 11 repos~3.1k tokens
    Agent WorkflowsAuto-check passed
  • Required groundwork before any use_figma call: the rules and reference files for running JavaScript in a Figma file through the Plugin API without common failures.

    65k GitHub starsUsed in 4 repos~4.4k tokens
    Frontend & DesignAuto-check passed
  • Stitch to Remotion Walkthrough Videos

    google-labs-code/stitch-skills

    Official

    Builds walkthrough videos from Stitch design projects using Remotion, with transitions, zoom effects and text overlays on each screen.

    8.4k GitHub starsUsed in 6 repos~3.2k tokens
    Media & CreativeAuto-check: notes
  • MCP Development

    coollabsio/coolify

    A skill your agent uses for Laravel MCP development. An agent skill from coollabsio/coolify.

    63k GitHub starsUsed in 1 repo~949 tokens
    Frontend & DesignAuto-check passed

More from hdl-tools/digital-chip-design-agents

All 17 skills in this repo
  • Architecture

    hdl-tools/digital-chip-design-agents

    Microarchitecture exploration, PPA estimation, risk assessment, and architecture sign-off for digital chip design.

    212 GitHub stars~3.7k tokensUpdated 5 days ago
    Auto-check: notes
  • Compiler Toolchain

    hdl-tools/digital-chip-design-agents

    Compiler toolchain development for custom processor ISAs — LLVM/GCC backend, assembler, linker scripts, runtime libraries, and regression validation.

    212 GitHub stars~2.8k tokensUpdated 5 days ago
    Auto-check: notes
  • Dft

    hdl-tools/digital-chip-design-agents

    Design for Test — scan architecture planning, scan insertion, ATPG pattern generation, MBIST for embedded memories, and JTAG boundary scan.

    212 GitHub stars~3.3k tokensUpdated 5 days ago
    Auto-check: notes
  • Embedded Firmware

    hdl-tools/digital-chip-design-agents

    Embedded firmware and device drivers — BSP development, peripheral driver implementation (UART, SPI, I2C, GPIO, DMA, Timer), RTOS integration (FreeRTOS, Zephyr), and system validation.

    212 GitHub stars~2.6k tokensUpdated 5 days ago
    Auto-check: notes
  • Formal Verification

    hdl-tools/digital-chip-design-agents

    Formal property verification (FPV) and logical equivalence checking (LEC).

    212 GitHub stars~3.4k tokensUpdated 5 days ago
    Auto-check: notes
  • Fpga Emulation

    hdl-tools/digital-chip-design-agents

    FPGA prototyping — ASIC-to-FPGA RTL adaptation, multi-FPGA partitioning, synthesis and timing closure on FPGA, hardware bring-up, and software validation on the prototype.

    212 GitHub stars~3.4k tokensUpdated 5 days ago
    Auto-check: notes

Questions about Infrastructure

What does Infrastructure do?

EDA tool detection, wrapper deployment, and MCP configuration for digital chip design environments. Infrastructure is an agent skill from hdl-tools/digital-chip-design-agents. EDA tool detection, wrapper deployment, and MCP configuration for digital chip design environments.

When should I use Infrastructure?

Infrastructure fits situations like: setting up a new workstation; verifying tool availability before a domain flow; generating per-tool install scripts with TCL modulefiles.

How do I install Infrastructure in Claude Code?

Run `npx skills add hdl-tools/digital-chip-design-agents --skill infrastructure -a claude-code`. Or copy the skill folder (plugins/infrastructure/skills/infrastructure in hdl-tools/digital-chip-design-agents) into .claude/skills/infrastructure in your project. Claude Code loads it when a task matches its description.

How do I install Infrastructure in Codex?

Run `npx skills add hdl-tools/digital-chip-design-agents --skill infrastructure -a codex`. Or copy the skill folder (plugins/infrastructure/skills/infrastructure in hdl-tools/digital-chip-design-agents) into .agents/skills/infrastructure in your project. Codex loads it when a task matches its description.

Can I use Infrastructure in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add hdl-tools/digital-chip-design-agents --skill infrastructure -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/infrastructure, .gemini/skills/infrastructure, .github/skills/infrastructure and .opencode/skills/infrastructure in your project.

What does Infrastructure need to run?

Going by SKILL.md and its folder, Infrastructure needs the command-line tools its instructions call (uv, make, bash, pip, curl and sh). Our summary lists: Python 3. Its frontmatter pre-approves these tools: Read, Write, Bash.

Does Infrastructure access the network?

SKILL.md names 2 domains. In commands or code: astral.sh; the agent is likely to contact it when it follows the instructions. As links in the text: github.com. This is read from the text; nothing was executed.

Is Infrastructure safe to install?

Our automated static check of SKILL.md found notes only (pipes a well-known installer script into a shell; runs commands with sudo; pre-approves every shell command (allowed-tools: bash)), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.

What licence does Infrastructure use?

Infrastructure is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Infrastructure use?

About 18k tokens (SKILL.md is roughly 71k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Infrastructure?

Skills that share tags, products or a category with Infrastructure: MCP Server Builder (anthropics/skills, 180k stars), MCP Server Builder (shareAI-lab/learn-claude-code, 78k stars), MCP Integration for Plugins (anthropics/claude-plugins-official, 38k stars) and Figma use_figma Plugin API Rules (warpdotdev/warp, 65k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Infrastructure?

hdl-tools (a GitHub organization) maintains it in hdl-tools/digital-chip-design-agents, which has 212 GitHub stars. The repository holds 17 skills in this directory. The repository was last updated on October 3, 2026.

Source: hdl-tools/digital-chip-design-agents on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.