Skip to main content
Glama

gowin-mcp

An MCP server that turns Gowin EDA build reports into structured data.

Gowin EDA writes everything you need after a place & route — resource usage, achieved frequency per clock, the exact register-to-register path that failed setup, the pin every port landed on, the warnings that explain why a clock was never constrained — and then buries it in a multi-megabyte HTML file nobody opens. This server reads those artifacts and hands back the numbers.

Built for Sipeed Tang boards (GW1N / GW2A / GW5A) but it only cares about the report format, so any Gowin EDA project works.

What it answers

  • Did this build close timing, and if not, which path failed and by how much?

  • Could timing even be verified? (unknown is a first-class answer — see below.)

  • What is the achieved fMax on each clock domain versus its constraint, and is a shortfall real or just how Gowin reports multicycle paths?

  • How much of the device am I using — CLS, LUT, ALU, registers, BSRAM, DSP, I/O?

  • Which package pin is each port on, with what I/O standard, drive and pull?

  • Which warnings fired, how often, and which clocks are missing a constraint?

  • What did my last change cost me? (gowin_compare diffs two builds, bitstream included.)

  • Is this build good enough to merge? (gowin_check with CI thresholds.)

Related MCP server: forgejo-mcp

Install

Run it from npm, with no install step:

npx -y gowin-mcp

Or build it from the repository:

git clone https://github.com/Papipapito/gowin-mcp.git
cd gowin-mcp
npm install
npm run build

Register it. For Claude Desktop, in claude_desktop_config.json:

{
  "mcpServers": {
    "gowin": {
      "command": "node",
      "args": ["C:\\path\\to\\gowin-mcp\\dist\\index.js"]
    }
  }
}

For Claude Code:

claude mcp add gowin -- node /path/to/gowin-mcp/dist/index.js

Tools

Tool

Purpose

gowin_summary

Device, resources, fMax, timing status and violations, bitstream header, what the .sdc declares — in one call. Start here.

gowin_check

CI gate: pass / fail / unknown with the reason, optional thresholds, per-clock fMax waivers.

gowin_timing

fMax per clock, total negative slack, worst setup/hold paths, recovery/removal summary.

gowin_resources

Full resource tables, including I/O banks and clock resources.

gowin_pins

Port → package pin, bank, I/O type, drive, pull, dual-function role; global clock routing.

gowin_warnings

Synthesis and PnR log diagnostics grouped by code, with hints; clocks missing a constraint.

gowin_compare

Diff two builds: resource deltas, fMax deltas, violations introduced/fixed, same bitstream or not.

gowin_list_builds

Find every Gowin build under a directory tree (reads only report headers, so it is fast).

Every tool returns a compact text rendering by default. Pass format: "json" for the full data: the JSON comes back as the text and as structuredContent. (No tool declares an MCP outputSchema: declaring one obliges the server to return structuredContent on every call, and clients that understand it then show the JSON instead of the text.) The shapes are documented in src/schemas.ts and the tests hold the JSON to them. All tools are annotated read-only, idempotent and non-destructive.

Paths are forgiving: pass the project directory, its impl/ directory, impl/pnr/ itself, or the .rpt.txt, with either kind of slash. When one impl/pnr/ holds several builds (one per Gowin project file) the most recently written report is used and the others are named in the text and in the JSON (siblings, note); pass name to pick one explicitly. A name that does not match the .rpt.txt you pointed at is an error, not a silent substitution.

gowin_check and the unknown status

Timing has three outcomes, not two:

status

meaning

pass

A timing report was read and shows no violation.

fail

At least one violation, or a threshold breached.

unknown

Timing could not be verified: neither _tr_content.html nor <name>.tr exists (the timing engine did not run), the report layout was not recognised, or it lists no clocks at all (nothing constrained).

unknown is never reported as clean. The JSON carries status and reason; the text starts with UNKNOWN — timing not verified: ….

For CI, gowin_check also takes:

  • maxUtilization — fail when CLS, Logic or BSRAM utilization exceeds this percentage. The percentage is recomputed from used/available (the tool's own column is rounded: 29142/29952 is 97.30 %, reported as 98 %); when the two differ the rounded figure is quoted next to it;

  • minFmaxMarginMhz — fail when any clock achieves less than constraint + this margin;

  • failOnMissingTiming — default true; set false to accept an unknown build on resources alone;

  • ignoreCrossDomain — default false; set true when the paths between clock domains are declared false paths in the .sdc (see TNS versus paths);

  • fmaxWaivers — clock names (or globs with * and ?) whose fMax-only violation is waived (see fMax versus multicycle paths).

The result's ok field is the decision to act on (status says what is known, ok says whether to accept the build); reasons lists why; ignoredViolations lists what the options set aside, with a note each.

Example

Build: project  (GW5AT-60, GW5AT-LV60PG484AC1/I0)
Tool:  V1.9.11.03 Education
Built: Thu Jul 16 20:58:03 2026   PnR 0h 3m 29s   peak 1848MB
Bitstream: 0xDE97 checksum, body sha256 baffbcb9fa04… (Thu Jul 16 20:57:51 2026)

Resources
  Logic: 28034/59904 (47%)
    LUT 21883, ALU 3325, ROM16 0
  CLS: 19618/29952 (66%)
  Register: 15014/60780 (25%)
  BSRAM: 73/118 (62%)
  DSP: 10.5/118 (9%)
  I/O Port: 141/297 (48%)

Timing: 3 VIOLATIONS
  setup clk_54m    -1.826 ns  1 endpoint failing setup — worst path -1.826 ns:
                              cpu1/u0/ISet_1_s87/Q -> mem1/ram_busy_s0/CE
  setup clk_hdmi   -0.378 ns  ...
  hold  clk_27m    -0.028 ns  ...

fMax per clock
  clk_54m: 33.548 MHz vs 53.996 MHz constraint (-20.448) [FAIL]
  clk_hdmi: 72.221 MHz vs 74.25 MHz constraint (-2.029) [FAIL]
  clk_108m: 159.744 MHz vs 107.991 MHz constraint (+51.753) [ok]
  ...

A gate with thresholds:

FAIL — CLS at 76.34% (77% as the tool rounds it) exceeds maxUtilization 50%
  threshold utilization CLS at 76.34% (77% as the tool rounds it) exceeds maxUtilization 50%
  threshold fmaxMargin  clk_eng margin 13.974 MHz is below minFmaxMarginMhz 20 (53.974 vs 40 MHz)

An fMax shortfall with a clean TNS, before and after the waiver:

FAIL — 1 timing violation

Timing: 1 VIOLATION
  fmax  clk_54m   -21.541 MHz  32.455 MHz achieved against a 53.996 MHz constraint, with no
        setup violation reported (setup TNS 0.000). 18 set_multicycle_path commands in the
        .sdc name this clock; 2 of its reported setup paths are half-cycle relations (launch
        and capture on opposite edges). Gowin derives fMax from the worst path's relation
        minus its slack as if that were a single period, so paths under a multicycle or
        half-cycle relation pull the figure below the constraint although they meet it. If
        that is the case here, waive it with fmaxWaivers; otherwise check that the constraint
        covers the domain.
PASS — no timing violations
  ignored fmax clk_54m -21.541 MHz
  note: 1 fMax-only violation waived (fmaxWaivers=["clk_54m"]): clk_54m (-21.541 MHz). Only safe
        when the clock's setup TNS is 0 and the shortfall is how Gowin reports its multicycle or
        half-cycle paths; a TNS violation is never waived.

Warnings, grouped:

demo: 0 errors, 192 warnings (synthesis 186, pnr 6)
  WARN  EX3791 x70   [synthesis] Expression size 8 truncated to fit in target size 7  @ opl3/afifo.v:144, ... +17 more
        -> Right-hand side wider than the target: upper bits are silently dropped.
  WARN  TA1132 x3    [pnr] 'u_top/u_div5m4/CLKOUT.default_gen_clk' was determined to be a clock but was not created.
        -> A net drives clock pins but has no create_clock / create_generated_clock in the .sdc, ...

Clocks without a constraint (TA1132)
  u_top/u_div5m4/CLKOUT.default_gen_clk   <- also "no timing paths" in the timing report

Pins, filtered with a glob:

8 of 102 ports; 121 package pins, 19 unused
  tmds_clk_p             pin 33/5    out  LVCMOS33D  pull NONE  drive 8  IOB24[A]  pair tmds_clk_n
  tmds_data_p[0]         pin 35/4    out  LVCMOS33D  pull NONE  drive 8  IOB30[A]  cfg GCLKT_4  pair tmds_data_n[0]

What it reads

File

Used for

impl/pnr/<name>.rpt.txt

Device, tool version, resource tables, PnR runtime, pinout (sections 6-8)

impl/pnr/<name>_tr_content.html

fMax, total negative slack, setup/hold/recovery/removal path tables

impl/pnr/<name>.tr

The same timing report as plain text; read when the HTML is missing (V1.9.12.04 writes it on every run)

impl/pnr/<name>.fs

Bitstream header (tool version, device, CheckSum, UserCode, Created Time) and a SHA-256 of the body without the // header

impl/pnr/<name>.log

PnR warnings and errors (TA*, PR*, CT* codes)

impl/gwsynthesis/<name>.log

Synthesis warnings and errors (EX*, NL*, DI*, CV* codes)

the .sdc named in the report

Clocks, set_multicycle_path, set_false_path, set_clock_groups — only to explain an fMax shortfall; found at the reported path or by file name under the project. A header of --- means the build has none.

<name>.tr.html is only a ~350-byte frameset wrapper — the data is in the _tr_content sibling.

Tables are parsed structurally (tr/td in HTML, column names in the text tables, the ==== rule's column spans in the .tr), not by position, so a column added between tool versions — the CFG column that appeared in the pinout around V1.9.10 — surfaces as a missing field rather than a silently misaligned number.

Scope

This server reads results. It does not run builds.

A GW5A place & route takes minutes, which does not fit an MCP request, and driving long builds through a tool call hides their output. Keep building with gw_sh, make, or the Gowin GUI as you already do — then point this at the results.

Verified against

The parsers were run over 167 real builds (October 2026) with no parse failures, and 0.2.2 gives the same verdicts and violations as 0.2.1 on every one of them:

Verified

Tool versions

V1.9.8.10, V1.9.8.11, V1.9.9, V1.9.9 Beta-4, V1.9.9.03, V1.9.10.03, V1.9.11, V1.9.11.01, V1.9.11.02, V1.9.11.03, V1.9.12.02_SP2, V1.9.12.03, V1.9.12.04 (Education and 64-bit editions)

Devices

GW1NR-9 (Tang Nano 9K), GW2A-18 (Tang Primer 20K), GW2AR-18 (Tang Nano 20K), GW5A-25 (Tang Primer 25K), GW5AT-60 (Tang Console 60K)

Host

Windows 11

Known gaps:

  • Only the proprietary Gowin EDA flow. Output from the open-source chain (yosys / nextpnr-himbaechel / apicula) is a different format and is not parsed.

  • Only English-language reports.

  • Power reports (*.power.html) are written by the tool but not read yet.

  • The .tr and the HTML of the same build agree on everything except the third decimal of some fMax figures (Gowin rounds the two files differently by 0.001 MHz).

  • The unit tests run on trimmed fixtures from five of those builds (V1.9.12.04, V1.9.12.03 on GW2AR-18 and GW5AT-60, V1.9.11.03 and V1.9.9); the full matrix above was exercised manually.

Failures should be visible rather than silent: fields that do not parse come back absent, timing that cannot be verified comes back unknown. If you get a missing or implausible number, please open an issue with the offending .rpt.txt or _tr_content.html attached.

Notes

TNS versus paths

Gowin's Total Negative Slack summary is per clock, and it leaves out paths whose launch and capture clocks differ. A design can therefore show every TNS row at 0.000 and every fMax above its constraint while the setup table opens with a -2.09 ns path from clock_54m to clock_27m. The GUI's summary page says clean; the design is not.

Violations are collected from both places:

  • TNS summary (source: "tns"): complete for what it counts, since the path tables are capped by -max_paths at report time and would under-report a design with many failures.

  • Path tables (source: "paths"): every setup or hold path with negative slack whose clock pair no negative TNS row of the same kind accounts for. One violation per (from clock, to clock) pair, with clock rendered as from -> to, amount the worst slack, and pathCount the number of such paths — a lower bound, because of the cap. crossDomain is true when the clocks differ.

Cross-domain violations fail gowin_check by default. Whether they are real depends on the constraints: a crossing that goes through a synchroniser or an asynchronous FIFO should be declared with set_false_path or set_clock_groups -asynchronous in the .sdc, after which the timing engine stops reporting it. If the .sdc already declares them and the paths still show, or the crossing is known to be safe, pass ignoreCrossDomain: true: the violations are then listed under ignoredViolations with a note, and do not affect status. A violation the TNS does count is never ignored.

Of 159 local builds checked when this was added, 45 had negative paths the TNS left out; 30 of them had been reported as clean before.

fMax versus multicycle paths

Gowin's Max Frequency summary derives each clock's fMax from its worst path's relation minus slack, as if that were one clock period. A path under set_multicycle_path -end 2 has a relation of two periods; a path launched on one edge and captured on the other has half a period. Either way the figure lands below the constraint although every endpoint meets it — which is exactly what the clock's 0.000 setup TNS says. A GW5AT-60 build with a Z80 on clk_54m and multicycle constraints on its register file reads 32.455 MHz against 53.996 with a clean TNS.

Such a clock is reported as an fMax-only violation (source: "fmax"), which is only raised when no setup violation already covers the clock. Its detail quotes the setup TNS, how many set_multicycle_path / set_false_path commands in the .sdc name the clock, and how many of its reported setup paths are half-cycle relations (fromEdge ≠ toEdge). When that evidence is there, waive the clock with fmaxWaivers: ["clk_54m"]: the violation moves to ignoredViolations with a note, and minFmaxMarginMhz does not re-flag it. When it is not, the constraint probably does not cover the domain. A TNS violation on the same clock is never waived.

Other

  • Recovery and removal paths (asynchronous set/reset checks) are parsed into their own arrays and never mixed into the hold table.

  • gowin_compare across different devices reports the numbers but does not classify violations (comparable: false, safe: null): a violation only one side has belongs to another design, not to the change.

  • The bitstream body hash ignores the // header, so a rebuild that changed nothing but Created Time shows sameBitstream: true.

Development

npm run build      # tsc -> dist/
npm test           # node:test over tests/fixtures (62 tests)
node tests/stdio.mjs <path-to-a-real-build>   # drive the server over stdio
node tests/smoke.mjs <path-to-a-real-build>   # parse one build and print what was read

See CHANGELOG.md for what each release changed.

License

GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). See LICENSE.

You can use it freely, including at work. If you distribute a modified version, or offer it to others as a service, you must publish your changes under the same license.

Releases up to and including 0.2.0 were published under the MIT License and remain available under those terms.

Available Tools

8 tools
gowin_checkBuild gateA
Read-onlyIdempotent

Pass/fail/unknown gate for CI. Lists timing violations with the offending path for each, plus optional thresholds on utilization and fMax margin. Negative paths between clock domains count as violations even though Gowin leaves them out of the TNS summary (set ignoreCrossDomain when they are declared false paths). A clock whose fMax misses its constraint while its setup TNS is 0 is reported as an fMax-only violation with the .sdc evidence (multicycle / half-cycle relations); waive it per clock with fmaxWaivers. "unknown" means timing could not be verified (no _tr_content.html or .tr, unrecognised report, or no clocks analysed) and is never a pass.

Inputs: path (required), name, format, fmaxWaivers, +4 more.

gowin_compareCompare buildsA
Read-onlyIdempotent

Diff two builds: resource deltas, fMax change per clock, which violations were introduced, fixed, or persist, and whether the two .fs bitstreams have the same body. Use this to see what a change actually cost. Builds on different devices are reported but not classified (safe is null).

Inputs: after (required), before (required), format, afterName, +1 more.

gowin_list_buildsList Gowin buildsA
Read-onlyIdempotent

Find Gowin EDA builds under a directory tree. Returns project name, device, and when each was built, newest first.

Inputs: root (required), format, maxDepth.

gowin_pinsPin assignmentB
Read-onlyIdempotent

Pin assignment as placed: every top-level port with its package pin, bank, I/O standard, drive, pull mode and dual-function role, plus the global clock routing. Filter by port name (substring or glob).

Inputs: path (required), name, port, format, +2 more.

gowin_resourcesResource usageB
Read-onlyIdempotent

Full resource usage for one build: logic, CLS, registers, BSRAM, DSP, I/O banks and clock resources.

Inputs: path (required), name, format, includeBanks.

gowin_summaryBuild summaryA
Read-onlyIdempotent

Headline report for one build: device, resource usage, fMax per clock, every timing violation, the bitstream header (.fs) and what the .sdc declares. Start here.

Inputs: path (required), name, format.

gowin_timingTiming detailA
Read-onlyIdempotent

Timing detail for one build: fMax per clock, total negative slack, the worst setup/hold paths with their endpoints, and a summary of the recovery/removal checks. The path tables are trimmed to the worst maxPaths entries in both renderings; paths="all" returns every parsed path, paths="none" only the summaries plus worstSetup/worstHold.

Inputs: path (required), name, paths, format, +1 more.

gowin_warningsSynthesis and PnR warningsA
Read-onlyIdempotent

WARN/ERROR lines from impl/gwsynthesis/.log and impl/pnr/.log grouped by diagnostic code, with a hint for the common ones. Cross-checks TA1132 (clock detected but not created) against the timing report to list clocks that are missing a constraint.

Inputs: path (required), code, name, level, +3 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.8/5.0

    Scored across 8 tools

    Disambiguation4/5

    Each tool targets a distinct artifact or question: build discovery, headline summary, resources, timing, comparison, pins, warnings, and a CI gate. The main soft overlap is gowin_summary versus gowin_resources/gowin_timing, and gowin_check versus gowin_timing, but the descriptions explicitly position summary as the entry point and check as the pass/fail gate, which resolves most confusion.

    Naming Consistency4/5

    All tools share a consistent gowin_ prefix and snake_case, with predictable domain nouns (resources, timing, pins, warnings, summary). A few names use verb forms (check, list_builds, compare) while others are bare nouns, a minor deviation that does not harm readability.

    Tool Count5/5

    Eight tools is well-scoped for an FPGA build-analysis server: one discovery tool, four detail views, one diff, one log/pin reader, and one CI gate. No tool feels redundant or missing at this scale.

    Completeness4/5

    The surface covers the full read-only analysis lifecycle: find builds, summarize, drill into resources/timing/pins, compare builds, triage warnings, and gate CI. Gaps are minor, e.g. no tool to dump raw report/log files or inspect the .sdc directly beyond what summary reports.

    Maintenance

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers