# silica-eda [Health: Active]

**Category:** 📟 Embedded System  
**Repository:** https://github.com/simaanc/silica-eda  
**GitHub Stars:** 12  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/silica-eda

## Description
AI-native EDA environment: an MCP server that lets AI agents simulate, synthesize, lint, formally verify and place-and-route RTL

## Tools
Capabilities this server exposes over MCP:

- **run_simulation** — Run RTL simulation and return structured diagnostics.

        The simulator runs in the DUT file's directory, so relative paths in
        the testbench (e.g. $readmemh("../fw/firmware.hex")) resolve from
        there, with or without trace/coverage. A failing $fatal/$error comes
        back in errors with its file, line and message.

        Args:
            file: Path to the DUT source file (.sv / .v for Verilog, .vhd for
                  VHDL). May instead be a ``.f`` filelist (one source path per
                  line); its entries become the design sources.
            testbench: Path to the testbench file.
            simulator: "auto" | "verilator" | "icarus" | "iverilog" | "ghdl" |
                       "vcs" | "questa" | "xcelium". "iverilog" is accepted as
                       an alias for "icarus". Auto uses config priority order.
            top: Top module/entity name (inferred from testbench filename if omitted).
            timeout: Seconds before killing the simulator process.
            trace: When True, collect the VCD the testbench writes (it must call
                   $dumpfile/$dumpvars) into a persistent directory and return
                   its path as trace_vcd. Read it with read_waveform, or open it
                   with launch_waveform.
            extra_sources: Additional design files (submodules, packages)
                   compiled alongside the DUT. Each entry may itself be a ``.f``
                   filelist, which is expanded in place.
            coverage: When True, collect line/toggle coverage (Verilator only).
                   Returns coverage_dat in the result — pass it to
                   read_coverage for a structured report.

        Returns a SimulationResult dict:
            exit_code, errors [{file, line, severity, message}], warnings,
            raw_stdout, raw_stderr, trace_vcd (str path or None), and
            coverage_dat (str path or None, Verilator + coverage=True only)
        
- **synthesize** — Synthesize RTL and return structured utilization and timing summary.

        Args:
            top_module: Name of the top-level module to synthesize.
            files: List of source file paths (.sv / .v).
            target: Synthesis target.
                    Yosys:   "generic" | "ice40" | "ecp5" | "xilinx" | "gowin"
                    Vivado:  FPGA part string e.g. "xc7a35tcsg324-1", or shorthand
                             "artix7" | "kintex7" | "ultrascale" | "ultrascale+" | "zynq"
                    Quartus: device family e.g. "cyclone5" | "arria10" | "stratix10"
                    DC Shell: technology library path, or "generic"
            tool: "auto" | "yosys" | "vivado" | "quartus" | "dc_shell".
                  Auto uses config priority order.
            timeout: Seconds before killing the synthesis process.

        Returns a SynthesisResult dict:
            tool, target, exit_code, utilization {luts, ffs, bram, dsp, lut_pct, ff_pct},
            timing_summary {wns, tns, failing_endpoints}, netlist, memories,
            warnings, errors, raw_output

            netlist: path to a JSON netlist ready for run_place_route. Written by
            yosys for FPGA targets ("ice40" | "ecp5" | "xilinx" | "gowin");
            None for generic/commercial-tool runs and on failure.

            memories (yosys; [] for other tools and on failure): every
            inferred memory as {name (module.memory), module, depth, width,
            bits, read_ports, write_ports, has_init, type, mapped_to}. type is
            "block_ram" | "distributed_ram" | "logic" (flip-flops and LUTs) |
            "removed" (optimized away); mapped_to is the primitive yosys chose.
            has_init is False when no $readmemh/initial data reached
            synthesis; a read-only memory without init data also adds a
            warning, since its contents (usually firmware) are missing.

            files may include ``.f`` filelists (one source path per line),
            expanded in place.
        
- **run_place_route** — Place and route an FPGA netlist and return timing and utilization.

        Takes a Yosys JSON netlist (from synthesize with target "ice40"/"ecp5"/
        "xilinx"/etc.). The output is a bitstream ready for program_device
        when the packing tool is available: a .bit for xilinx (openXC7's
        fpga-as), a .bin for ice40 (icepack) and a .bit for ecp5 (ecppack).
        Without it, the output is nextpnr's FASM or text config (.asc /
        .textcfg) and a warning says what is missing.

        Args:
            netlist: Path to Yosys JSON netlist (produced by the synthesize tool).
            arch: FPGA architecture — "ice40" | "ecp5" | "nexus" | "machxo2" |
                  "generic" | "xilinx".
            device: Device variant within the architecture:
                    ice40: "hx1k" | "hx4k" | "hx8k" | "up5k" | "lp1k" | "lp8k" | ...
                    ecp5:  "LFE5U-12F" | "LFE5U-25F" | "LFE5U-45F" | "LFE5U-85F",
                           also LFE5UM-/LFE5UM5G- parts; a speed grade suffix
                           (e.g. "LFE5U-25F-6") sets --speed.
                    xilinx: full 7-series part with speed grade, e.g.
                            "xc7a100tcsg324-1" (Arty A7-100T). Uses openXC7's
                            nextpnr-xilinx; its chip database is found through
                            NEXTPNR_XILINX_CHIPDB_DIR or <FAMILY>_CHIPDB, and
                            the FASM is assembled into a .bit with fpga-as when
                            PRJXRAY_DB_DIR is set. A chip database path also
                            works as device (the result is then FASM).
            package: Package code, e.g. "ct256" (ice40), "CABGA381" (ecp5). Optional.
            constraint: Path to physical constraint file (.pcf for ice40, .lpf for ecp5,
                        .xdc for xilinx).
            output: Output file path for the bitstream. Auto-placed in temp dir
                    if omitted — the path is returned in the result. A path
                    ending in .asc/.textcfg/.fasm keeps nextpnr's file there
                    and writes the bitstream beside it.
            freq: Target clock frequency in MHz for timing checks.
            tool: "auto" | "nextpnr". Auto uses config priority order.
            timeout: Seconds before killing the tool.

        Returns a PlaceRouteResult dict:
            tool, target, exit_code,
            timing_summary {clocks [{clock, max_freq_mhz, pass}], wns, failing_endpoints},
            utilization {<cell_type>: {used, total, pct}},
            output (path to the bitstream, or to nextpnr's file if it
                    couldn't be packed),
            workdir, warnings, errors, raw_output
        
- **program_device** — Program a bitstream to an FPGA device with openFPGALoader.

        Loads volatile SRAM by default, which is safe to repeat. For Xilinx
        devices success means the FPGA asserted DONE; a rejected bitstream is
        reported as a failure even though openFPGALoader exits 0. The design
        starts running as soon as loading finishes, so boot output is usually
        missed by a serial capture started afterwards (see capture_serial).

        Args:
            bitstream: Path to a loadable bitstream from run_place_route:
                       .bit (Xilinx, ECP5) or .bin (iCE40). nextpnr's
                       .asc/.textcfg text configs can't be loaded directly.
            board: Board preset name, e.g. "arty_a7_100t", "arty_a7_35t",
                   "basys3", "cmod_a7", "nexys4ddr", "ice40_hx8k_b_evn".
                   Plain "arty" is the 35T. Run openFPGALoader --list-boards
                   for all.
            cable: JTAG cable, e.g. "ft2232", "digilent_hs2", "jlink", "olimex".
                   Run openFPGALoader --list-cables for all options.
            device: Explicit FPGA part number override, e.g. "xc7a35t". Optional.
            spi_flash: When True, program SPI flash (persistent across power
                       cycles), replacing whatever design the board boots.
                       Only do this when the user asks. Default False: SRAM
                       (volatile, erased on power cycle).
            tool: "auto" | "openfpgaloader". Auto uses config priority order.
            timeout: Seconds before killing the programmer.

        Returns a ProgramResult dict:
            tool, exit_code, success, errors [{message}], raw_output
        
- **lint** — Lint a SystemVerilog/Verilog file and return structured warnings.

        Args:
            file: Path to the source file to lint. May instead be a ``.f``
                  filelist (one path per line); each listed file is linted.
            ruleset: "default" | "all" | comma-separated rule list (tool-specific).
            tool: "auto" | "verible" | "verilator" | "svlint" | "spyglass".
                  Auto uses config priority order.
            timeout: Seconds before killing the linter (applied per file).
            extra_files: Additional files to lint. Each may itself be a ``.f``
                  filelist, expanded in place. Warnings across all files are
                  merged into one result. Verilator elaborates all the files
                  together, so pass a design's submodules here when linting
                  its top level.

        Returns a LintResult dict:
            warnings [{file, line, severity, rule_id, message}], tool, raw_output
        
- **format_rtl** — Auto-format a SystemVerilog/Verilog file and return a unified diff.

        Call with dry_run=True first to review the diff, then dry_run=False to
        write the formatted source back. The file must stay inside the project
        directory (same confinement as write_rtl).

        Args:
            file: Path to the source file to format.
            dry_run: If True (default), return the diff without writing. If
                     False, overwrite the file with the formatted output.
            tool: "auto" | "verible". Auto uses config priority order.
            timeout: Seconds before killing the formatter.

        Returns:
            {dry_run, written, diff, lines_changed, path, tool} on success, or
            {"error": str, ...} if the file escapes the project dir, the
            formatter is missing/disabled, or formatting fails (e.g. a syntax
            error — the formatter refuses rather than mangle the source).
        
- **run_formal** — Run formal verification or equivalence checking and return structured results.

        Two usage modes for property verification (sby/jaspergold/vc_formal):
          1. Pass sby_file — run an existing .sby or .tcl config file directly.
          2. Pass files + top — auto-generate a config and run it.

        For equivalence checking (tool="eqy"):
          1. Pass sby_file pointing to an .eqy config file.
          2. Pass gold_files + gate_files + top — auto-generate an .eqy config.

        Args:
            sby_file: Path to an existing config file (.sby, .eqy, or .tcl).
            files: RTL source files for property verification (when sby_file is omitted).
            top: Top module name.
            mode: "prove" | "bmc" | "cover" | "live". Default: "prove". (sby only)
            depth: Bound depth for bmc/prove. Default: 20. (sby only)
            engine: "auto" or explicit e.g. "smtbmc z3". (sby only)
            tool: "auto" | "sby" | "eqy" | "jaspergold" | "vc_formal".
                  Auto uses config priority order.
            timeout: Seconds before killing the tool. Default: 300.
            gold_files: Gold (reference) design files for equivalence checking (eqy only).
            gate_files: Gate (implementation) design files for equivalence checking (eqy only).

        Returns a FormalResult dict:
            status: "PASS" | "FAIL" | "ERROR" | "UNKNOWN"
            rc: exit code
            failures: [{property, file, line}]
            traces: [path to counterexample VCD — open with launch_waveform] (sby only)
            induction_failures, induction_traces: assertions the k-induction
                step could not prove and its trace (sby only). They explain an
                UNKNOWN prove and are not reachable failures.
            warnings, errors, workdir, raw_output
        
- **get_project_structure** — List the project's RTL and constraint files.

        Start here in an unfamiliar project to find the sources to pass to
        lint, run_simulation and synthesize, and the constraint file for
        run_place_route.

        Args:
            root: Directory to scan, inside the project (defaults to the
                  project root).

        Returns:
            root: the directory scanned (absolute)
            rtl_files: relative paths of .v/.sv/.svh/.vh/.vhd/.vhdl files
            constraint_files: relative paths of .xdc, .lpf, .sdc, .upf and
                .pdc files (iCE40 .pcf files are not listed)
            file_count: number of RTL files
            error: present only when root escapes the project directory
        
- **read_rtl** — Read an RTL file, optionally restricted to a line range.

        Args:
            file: Path to the file; must stay inside the project directory.
            start_line: First line to return (1-indexed, inclusive).
            end_line: Last line to return (1-indexed, inclusive).

        Returns:
            content: file text with line numbers, path, total_lines
        
- **write_rtl** — Write (or diff) proposed RTL content to a file.

        Always call with dry_run=True first to review the unified diff before committing.

        Args:
            file: Path to the target file; must stay inside the project directory.
            content: Complete new file content.
            dry_run: If True (default), return a unified diff without writing. If False, write.

        Returns:
            diff: unified diff string (dry_run=True) or empty string after write
            lines_changed: number of changed lines
            written: True if file was actually written
        
- **search_rtl** — Grep across project RTL files with surrounding context.

        Args:
            query: Search string (literal, not regex).
            root: Directory to search, inside the project (defaults to the
                  project root).
            scope: "all" | "sv" | "v" — filter by file extension.

        Returns:
            matches: [{file, line, context}] — capped at 50 entries
            match_count: total matches found (may exceed len(matches))
            truncated: True when matches were dropped by the 50-entry cap
        
- **get_constraints** — Read and return the content of a constraint file (.xdc / .sdc / .lpf / .upf).

        Args:
            file: Path to the constraint file; must stay inside the project directory.

        Returns:
            content: raw file text, path, format (inferred from extension)
        
- **launch_waveform** — Open a VCD/FST/FSDB trace file in a waveform viewer (fire-and-forget).

        Typical flow:
            1. result = run_simulation(..., trace=True)
            2. launch_waveform(result["trace_vcd"])

        Args:
            trace_file: Absolute path to the .vcd, .fst, or .fsdb trace file.
            viewer: "auto" | "surfer" | "gtkwave" | "verdi".
                    Auto picks from config priority order.

        Returns:
            {"viewer": str, "launched": bool, "trace": str}
            or {"launched": False, "error": str} on failure.
        
- **read_waveform** — Read signal values from a VCD or FST trace — no viewer needed.

        Typical debug flow:
            1. result = run_simulation(..., trace=True)   # note failure time T
            2. read_waveform(result["trace_vcd"])          # discover signal names
            3. read_waveform(result["trace_vcd"],
                             signals=["dut.state", "dut.count"],
                             time_start=T-100, time_end=T+10)

        Args:
            trace_file: Path to the trace. VCD is read directly; .fst is
                        converted via the OSS CAD Suite's fst2vcd first.
            signals: Signal names to read. A name matches on the full
                     hierarchical path ("tb.dut.count") or any trailing
                     component ("count", "dut.count"). Omit to list the
                     signals available in the trace instead.
            time_start: Window start in VCD ticks (multiply timescale to get
                        time). Each signal's value just before the window is
                        included so state at window start is known.
            time_end: Window end in VCD ticks.
            max_transitions: Per-signal cap; "truncated" is set when hit.

        Returns:
            Discovery mode: {trace, timescale, end_time, signal_count,
                             signals [{name, width}]}
            With signals:   {trace, timescale, end_time,
                             signals [{name, width,
                                       transitions [{time, value}],
                                       truncated}],
                             not_found}
            {"error": str} on failure. Values are bit strings ("0101",
            may contain x/z); times are VCD ticks.
        
- **read_coverage** — Summarize a Verilator coverage.dat as structured line coverage.

        Typical flow:
            1. result = run_simulation(..., simulator="verilator", coverage=True)
            2. read_coverage(result["coverage_dat"])   # find the weak spots

        Args:
            coverage_dat: Path to the coverage.dat produced by a coverage run
                          (the run_simulation result's coverage_dat field).
            max_uncovered: Cap on uncovered line numbers reported per file;
                           "uncovered_truncated" is set when the cap is hit.
            timeout: Seconds before killing verilator_coverage.

        Returns:
            {coverage, total {lines_total, lines_covered, coverage_pct},
             files [{file, lines_total, lines_covered, coverage_pct,
                     uncovered_lines, uncovered_truncated?}], file_count}
            or {"error": str} on failure.
        
- **run_cocotb** — Run a cocotb (Python) testbench against an HDL design.

        cocotb writes testbenches as Python coroutines instead of an HDL
        testbench; this builds the design and runs the cocotb tests via
        cocotb's runner, returning per-test pass/fail.

        Args:
            sources: HDL design files. May include ``.f`` filelists (one path
                     per line), expanded in place.
            test_module: Path to the Python cocotb test file (contains the
                     @cocotb.test() coroutines).
            toplevel: Top-level HDL module cocotb instantiates.
            simulator: "icarus" | "verilator" | "ghdl" | "questa" | "xcelium"
                     | "vcs". Icarus is the most common cocotb pairing.
            timeout: Seconds before killing the build+test run.

        Returns:
            {simulator, passed, failed, tests [{name, status, time, message}],
             raw_stdout, raw_stderr} or {"error": str} (cocotb/simulator
             missing, module not found, or build failed before any test ran).
        
- **capture_serial** — Capture a board's UART output, optionally sending text first.

        Use after program_device to check what the design prints, e.g. a
        firmware banner, or that typed characters are echoed. Only the port
        you name is opened; silica never scans for serial ports.

        Args:
            port: Serial device to open, e.g. "/dev/ttyUSB1" or
                  "/dev/serial/by-id/usb-...-if01-port0" (Linux), "COM4" (Windows).
                  On boards with an FTDI JTAG+UART bridge the UART is usually
                  the second interface (if01).
            baud: Baud rate; 8N1, no flow control.
            duration: Seconds to listen (at most 600).
            send: Text to write once send_delay has passed, e.g. "hello\r".
            send_delay: Seconds to listen before sending.
            expect: Regex searched for in the captured text, e.g. "Silica .* SoC".
            stop_on_match: Stop as soon as expect matches (default) instead of
                  listening for the full duration.

        Returns a SerialCapture dict:
            port, baud, elapsed_s, bytes_received, bytes_sent,
            lines [{t, text, partial?}] (t = seconds from the start of the
            capture to the line's first byte), text (everything received,
            UTF-8 with replacement characters), expect, matched (bool, or
            None without expect), match (the matched text).
            On failure also error (message) and, when there is one, fix
            (a command or step to resolve it, e.g. granting port access).
        
- **doctor** — Report silica's version and which EDA tools it can use, with a fix for each gap.

        Call this first in a new environment, or when a tool call says a tool
        is missing, to see what is installed before choosing a flow. It finds
        tools the same way the other tools do (PATH, then an OSS CAD Suite
        install) and asks each open-source tool for its version. Commercial
        tools (VCS, Questa, Xcelium, Vivado, ...) are only looked up, never
        run, so no license is checked out. Takes a few seconds.

        Args:
            check_updates: Also ask PyPI (3 s timeout) whether a newer
                silica-eda exists. Offline or blocked networks just report
                the check as unavailable. Set SILICA_NO_UPDATE_CHECK=1 to
                skip it everywhere.

        Returns:
            silica {version, latest, update_available, update_check, fix?},
            python, platform, executable, oss_cad_suite (path or None),
            tools [{name, used_by, found, path, version, min_version,
                    status: "ok" | "found" (version unknown) | "missing" |
                    "outdated", fix?, note?}],
            python_extras [{name, extra, used_by, installed, version, fix?}]
                (cocotb for run_cocotb, pyserial for capture_serial),
            commercial [{name, category, found, path}],
            summary {ok, missing, outdated} (tool names).
            Every missing or outdated entry carries a concrete "fix": an
            install command for this platform or the upgrade to make.
        

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `uvx` (confidence: high):

```json
"mcpServers": {
  "silica-eda": {
    "command": "uvx",
    "args": ["silica-eda@latest"]
  }
}
```

## Documentation

## What silica-eda does

silica-eda exposes an EDA workflow to MCP-compatible coding agents. Its tools can inspect a project, read and update RTL, search source files, review constraints, format SystemVerilog or Verilog, lint designs, simulate HDL, run cocotb tests, and perform formal property or equivalence checks.

The flow continues beyond verification. Synthesis returns utilization, timing information, inferred memories, and, for supported Yosys FPGA targets, a JSON netlist for implementation. Place-and-route consumes that netlist and reports timing, resource use, and the generated configuration file or bitstream. A separate programming tool loads supported bitstreams through openFPGALoader, while serial capture can check UART output from the running board.

Results are parsed into fields such as source locations, severities, failing endpoints, counterexample traces, coverage percentages, and memory mappings rather than exposing only tool logs.

## How it works

The server runs external EDA programs installed by the user. Open-source dependencies are commonly supplied through OSS CAD Suite; commercial programs are used when available on `PATH`. The `doctor` tool identifies detected tools, versions, missing dependencies, and suggested fixes. It can also report the Python extras needed by cocotb and serial capture.

Project paths are generally confined to the project directory for source inspection and modification. A typical workflow starts with `get_project_structure`, then passes discovered RTL and constraint files to lint, simulation, synthesis, or place-and-route. Simulation can produce VCD traces and Verilator coverage data. Those artifacts can be inspected with `read_waveform` and `read_coverage`, or opened with a configured waveform viewer.

Formal verification accepts existing `.sby`, `.eqy`, or `.tcl` configurations, or generates configurations from source files and top-level information. An `UNKNOWN` proof with induction failures indicates that the induction step did not close; it is not reported as a reachable design failure.

## Setup and configuration

Install `uv`, then configure an MCP client to launch:

```bash
uvx silica-eda@latest
```

The repository documents setup for Claude Code, Claude Desktop, Codex CLI, Cursor, VS Code with GitHub Copilot, and Gemini CLI. Claude Code can also install the project plugin. The first `uvx` launch downloads the package into a cache, so clients configured this way need network access unless an offline installation workaround is used.

Install the EDA tools separately. OSS CAD Suite covers many open-source tools, while Verible, svlint, openXC7, and commercial tools may require separate installation. Run `doctor` after setup to check the environment. Set `SILICA_NO_UPDATE_CHECK=1` to disable the optional PyPI update check.

## Tools and capabilities

- Inspect RTL, constraints, and project structure with `get_project_structure`, `read_rtl`, `search_rtl`, and `get_constraints`.
- Review or commit source changes through dry-run diffs from `write_rtl` and `format_rtl`.
- Lint multi-file Verilog and SystemVerilog designs with Verible, Verilator, svlint, or SpyGlass adapters.
- Run Verilog, SystemVerilog, VHDL, and cocotb simulations with supported simulators, including Icarus, Verilator, GHDL, VCS, Questa, and Xcelium.
- Collect waveform traces and Verilator line or toggle coverage, then inspect them as structured data.
- Run property checking with SymbiYosys, JasperGold, or VC Formal, and RTL equivalence with eqy.
- Synthesize for generic, FPGA, or selected commercial targets and inspect timing, utilization, and inferred memory details.
- Place and route supported iCE40, ECP5, Nexus, MachXO2, and Xilinx architectures with nextpnr.
- Program FPGA SRAM or SPI flash with openFPGALoader and capture explicitly selected UART ports.

## Limitations and notes

Vivado is supported for synthesis only; it does not provide an implementation or bitstream stage through this server. Adapters for VCS, Questa, Xcelium, Quartus, DC Shell, JasperGold, VC Formal, SpyGlass, and Verdi have been tested with mocked calls rather than real installations. iCE40 and ECP5 packing is covered, but physical-board programming with openFPGALoader has not been validated on those board families.

Cocotb depends on compatibility between the Python glibc and the simulator being loaded. Deep bounded formal checks can take substantial time. `capture_serial` requires an explicitly supplied port with access permissions, and a boot message may occur before capture begins. Project structure discovery does not list iCE40 `.pcf` files. Windows has limited serial pre-check and pseudo-terminal test coverage.

_Full upstream README: https://allmcps.com/mcp/silica-eda/readme_

