Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Vyges CLI

vyges is the single command-line entry point to the Vyges hardware-IP toolchain. It promotes a small set of tools and dispatches, git-style, to external vyges-<name> binaries on your PATH:

CommandBinaryWhat it does
vygesvygesTop-level CLI: list modules, agent guide, bug/feature/sponsor
vyges pdk-storevyges-pdk-storeConsistent PDK presentation + resolution
vyges catalogvyges-catalogSearch and fetch IPs from the Vyges IP catalog

These ship together in a single release — along with the components behind vyges mcp, vyges metadata and vyges model — so installing vyges installs the whole set at one version (see Installation).

This documentation is built from the CLI itself — the command reference pages are generated from each binary’s --help output.

Installation

Vyges is distributed as prebuilt binaries (the source is private). You need neither Rust nor a package manager to install it.

Every release bundles six binaries — vyges, vyges-catalog, vyges-mcp, vyges-metadata, vyges-model and vyges-pdk-store — and installs them into ~/.vyges/bin. They move as a set: an install or upgrade replaces all six, so the components never drift apart.

Homebrew (macOS / Linux)

brew install vyges/tap/vyges

This installs all six binaries in one shot.

curl installer (macOS / Linux)

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/vyges-tools/cli/releases/latest/download/vyges-installer.sh | sh

The installer places the binaries in ~/.vyges/bin and adds that directory to your PATH by appending a line to your shell startup files (~/.profile, ~/.bashrc, ~/.zshrc, and fish’s conf.d).

To leave your shell configuration untouched, set VYGES_NO_MODIFY_PATH=1:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/vyges-tools/cli/releases/latest/download/vyges-installer.sh | VYGES_NO_MODIFY_PATH=1 sh

Worth doing if you build EDA tools from source and do not want an installed vyges-<engine> shadowing your own build. You then put the directory on PATH yourself, per shell or per command:

export PATH="$HOME/.vyges/bin:$PATH"

The Vyges Loom engines (on demand)

The sign-off engines — the Vyges Loom suite — are not bundled with the CLI. Fetch the whole suite when you want it, then invoke any engine through vyges:

vyges install loom        # downloads the Loom engines into ~/.vyges/bin
vyges install physical    # opt-in: the construction engines (ifp, tap, pdn, ppl, pad, fin)
vyges loom sta-si demo    # run any engine as: vyges loom <engine> ...
vyges opendb info --input design.odb   # including the design database itself

Each engine is also a standalone vyges-<engine> binary on your PATH (e.g. vyges-sta-si), which is what vyges loom <engine> dispatches to and what flow authors integrate against directly.

Verify

vyges --version
vyges modules        # shows which Vyges modules are installed (incl. the loom suite)

vyges modules also reports each engine’s contract — the descriptor version it speaks. 1.1 is current. An engine showing pre predates the convention: it still runs, and reinstalling it picks up a build that declares one.

Upgrading

vyges update

One command on every platform. It checks for a newer release, replaces all six binaries together, and reports what moved; if you are already current it says so and does nothing. It leaves your shell startup files alone.

Available from 0.1.26. Earlier builds do not ship the updater, so vyges update there reports unknown command 'update'. Upgrade once with the installer below and vyges update works from then on.

Re-running the installer also works, and is the way to upgrade a pre-0.1.26 install:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/vyges-tools/cli/releases/latest/download/vyges-installer.sh | sh

If you installed with Homebrew, use Homebrew instead — writing into a brew-managed prefix by hand gets undone by its next upgrade:

brew upgrade vyges/tap/vyges

The Loom engines upgrade separately, since they release from their own repos:

vyges install loom        # re-run to refresh the suite

Installing a specific version

Every release keeps its own installer, so a version is pinned by asking for its tag instead of latest — which is what CI should do, and what to reach for if a new release regresses something you depend on:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/vyges-tools/cli/releases/download/v0.1.24/vyges-installer.sh | sh

Published releases are never deleted or rewritten, so a pinned version stays available.

Uninstalling

The installer only adds files under ~/.vyges and a PATH line to your shell profile:

rm -rf ~/.vyges/bin        # the CLI, its components, and any installed engines
rm -rf ~/.vyges            # also removes caches, PDK descriptors and catalog config

Then delete the ~/.vyges/bin line from whichever profile picked it up (~/.profile, ~/.zshrc, ~/.bashrc, or ~/.config/fish/conf.d/). Homebrew installs uninstall with brew uninstall vyges.

Supported platforms

  • macOS (Apple Silicon)
  • Linux (x86-64 and arm64)

Windows is not supported. The suite installs opendb, which builds on OpenROAD’s libodb — and upstream odb has no Windows support at all: no _WIN32 handling in its CMake, and POSIX-only headers across more than a dozen files. Supporting Windows would mean forking and maintaining a port of upstream C++ against a moving target, and a CLI that cannot install half the suite is worse than no CLI. On Windows, use WSL2, where the Linux build runs unchanged.

Output & logging

Vyges follows the Unix output contract, so its output composes cleanly with pipes, redirection, and other tools:

  • stdout — data. Command results: tables, JSON, resolved paths, URLs. This is what you capture, pipe, or parse.
  • stderr — diagnostics. Progress notes, hints, warnings, and errors. This is what you read while a command runs, or send to a log.

Because the two streams are separate, you control logging with the shell — there is no --log-file flag, and you don’t need one.

Structured events (Loom engines). The Loom sign-off engines additionally emit machine-readable structured events on stderr (vyges-events/1.0 JSONL) — the causal trail the MCP layer and vyges model consume. Filter them with VYGES_LOG and pick text vs JSONL with VYGES_LOG_FORMAT; see that page.

Redirecting output

vyges catalog search uart > results.txt      # data only → file
vyges catalog search uart 2> run.log         # diagnostics only → file
vyges catalog search uart > out.txt 2>&1     # both → one file (data + diagnostics)
vyges catalog search uart 2>&1 | tee run.log # both to screen AND a log
vyges catalog search uart --json | jq '.[]'  # data is clean JSON → pipe to a parser

Because data and diagnostics are separated, ... > results.txt gives you a file with only the results — no progress chatter mixed in — while 2> run.log keeps a clean log of what happened. This is the recommended way to “save a log”: let the shell do it.

Tip: for machine consumption, prefer --json (supported by most commands) over scraping human output. Combined with 2>/dev/null you get a pure JSON stream.

Verbosity

How much goes to stderr is controlled by a level. It never affects stdout — your data is never suppressed.

FlagLevelShows on stderr
-q -q -qoffnothing (exit code still signals errors)
-q -qerrorerrors only
-qwarnerrors + warnings
(default)infoerrors + warnings + info/hints
-vdebug+ debug detail
-v -vtrace+ trace detail

-v and -q are repeatable and combine (the net of the two steps from info).

The VYGES_LOG environment variable

Set the level by name or number (05) without flags:

export VYGES_LOG=debug      # or: off | error | warn | info | trace | 0..5
vyges pdk-store resolve sky130A lib --corner tt_025C_1v80

VYGES_LOG is inherited by child processes, so when vyges <tool> … dispatches to a vyges-<tool> binary, the whole command tree runs at the same level — set it once.

Precedence (highest first): -v/-q flags → VYGES_LOG → default (info). So a flag on an invocation overrides the environment for that process; the environment configures everything else, including dispatched tools.

A flag binds to the process you give it to. vyges -v <tool> … makes vyges verbose; the flag is consumed before dispatch, so the child tool is unaffected. To make the child verbose, either set VYGES_LOG (it covers the whole tree) or pass the flag after the tool name so it goes through to the child:

VYGES_LOG=debug vyges pdk-store list    # whole tree verbose (env — simplest)
vyges -v modules                        # the `vyges` command itself, verbose
vyges pdk-store -v list                 # flag passes through → vyges-pdk-store verbose

AI IDE integration — vyges mcp

vyges mcp starts a local, no-auth MCP server that exposes your installed Loom engines to an AI IDE (Claude Code, the VS Code Claude extension, or any MCP client). The agent calls the engines as tools; the engines run on your machine, over stdio — no port, no auth, nothing leaves your box (your own binaries, your own agent).

It is the execution companion to the CLI’s “deterministic core, agent tail” philosophy (vyges agents): instead of the agent shelling vyges <tool>, the tools are MCP tools.

Do you need an LLM? Two ways if you do

The engines and tools are plain, deterministic CLIs — run them directly (vyges drc run …) or script them, no LLM required. That is one of three ways to reach the same Loom engines — manual, your own model via an AI IDE, or a model vyges drives for you:

   1. MANUAL                2. AI IDE  (Mode 1)         3. vyges model run  (Mode 2)
   no LLM · scriptable      bring your own model        registered model, headless
        │                          │                            │
        │                   Claude Code / Cursor          vyges driver
        │                   (any MCP host)                (openai-compat | anthropic)
        │                          │                            │
        │                          └────────────┬───────────────┘
        │                                        ▼
        │                        ┌──────────────────────────────┐
        │                        │   vyges mcp  ·  tool server   │
        │                        │       (model-agnostic)        │
        │                        └──────────────────────────────┘
        │                                        │
        ▼                                        ▼
   ┌────────────────────────────────────────────────────────────┐
   │        Loom engines  —  the deterministic core             │
   │   ground truth · the same engines every path drives,       │
   │   with or without an LLM on top · reproducible             │
   └────────────────────────────────────────────────────────────┘

The two AI paths go through the model-agnostic vyges mcp tool server; the manual path calls the engines directly. Reach for an LLM only when you want reasoning over the tools («which engine, why, and fix the violations»), not a fixed sequence — then there are two ways to connect one:

  • Mode 1 — bring your own AI IDE (available today). Point Claude Code, Cursor, or any MCP client at vyges mcp. Your IDE’s model reasons and calls the tools — you bring the model, vyges brings the tools. This is the recommended interactive path, and the rest of this page covers it.
  • Mode 2 — register a model, vyges drives it (headless). Register a local or cloud model with vyges model, then vyges model run <name> "<task>" runs the reason → tool-call → observe loop over these tools itself, with no IDE — for headless / CI / air-gapped / autonomous flows. Because the model is a registered, swappable choice, point it at a local model so nothing leaves your machine. (v1 uses JSON tool-calling and passthrough tool args; native provider tool-calling and finer controls are refinements.)

Both modes drive the same vyges mcp tools below — the tool server is model-agnostic.

Quickstart — hook your AI IDE to your local Loom

# 1. Install the engines (once).
vyges install loom

# 2. Register with every AI IDE on your machine (user scope).
vyges mcp setup

vyges mcp setup detects the AI IDEs you have installedClaude Code, Cursor, and VS Code — and registers vyges mcp with each (merge-safe: it never disturbs your other MCP servers, e.g. VyContext). Preview it with vyges mcp setup --dry-run; reverse it with vyges mcp setup --uninstall.

First-run prompt: the very first time you run vyges interactively, it offers to do this for you (once). Decline and it won’t ask again; run vyges mcp setup yourself anytime. Set VYGES_NO_PROMPT=1 to silence it.

Now open a project in your IDE — the installed engines appear as tools. Verify with:

claude mcp list        # → vyges: … ✔ Connected
vyges mcp --list       # the tools this server advertises

Just one project instead? vyges mcp install writes a project-scoped .mcp.json in the current directory (equivalently: claude mcp add vyges -- vyges mcp).

What you get

  • One tool per installed engine (drc, lvs, sta-si, gds-view, …). The tool set is discovered from vyges modules — install more engines and they appear; nothing to configure.

  • Calling a tool runs the real vyges-<engine> and returns a structured result: the engine’s own --json output, wrapped in a loom-result envelope (status, engineering, a content-addressed input_hash, provenance). Errors come back as a structured envelope too — a bad call never crashes the session.

  • Two independent status axes. status says whether the process ran (ok | error); engineering.status says what the evidence supports about your design (pass | fail | unknown | not_applicable). They are deliberately separate: a DRC run that exits cleanly having found 3 violations is status: "ok" with engineering.status: "fail" — a successful call reporting a failed check. An engine that crashes is unknown, never fail, because a tooling defect is not a design defect; so is an engine that declares no assertion. Nothing is ever reported as passing on absent evidence.

    { "status": "ok",
      "engineering": { "status": "fail", "assertion": "drc-clean",
                       "summary": "3 rule violation(s) found" } }
    
  • Typed arguments. Engines self-describe (vyges-<engine> --describe), so their tools expose real per-parameter schemas — drc takes gds, deck, top, not an opaque string. Tools without a descriptor (and external tools like yosys) fall back to an args array of the engine’s own CLI arguments; --json is added automatically either way. The descriptor format is documented in Tool descriptor — --describe, and the result shape in Result envelope — loom-result.

Read-only vs mutating tools — VYGES_MCP_PROFILE

Not every engine is equal: sign-off, verification, and query engines only read your design (sta-si, drc, lvs, extract, power, em-ir, thermal, cdc, glitch, lec, gds-view), while optimizers edit it (resize, vt-swap, buffer-insert, hold-fix, remap). vyges mcp classifies each tool automatically — from what it produces — and lets you choose how much of the surface an agent sees:

VYGES_MCP_PROFILEExposesUse it for
full (default)every toolunrestricted local use — unchanged behavior
coreread-only engines only (sign-off / verify / query)let an agent analyze and explain your design with no way to change it
proread-only + mutating engines behind an approving transactionagent-driven optimization with a human in the loop
VYGES_MCP_PROFILE=core vyges mcp     # read-only surface: the agent can run sign-off, never edit

vyges mcp --list shows each tool’s read/mutate classification and whether it’s exposed under the active profile. (External and passthrough tools — which can write — are treated as mutating, so a core surface is strictly the engines proven read-only.)

Approving a change — transactions (pro)

Under pro, a mutating engine runs only inside an open transaction — opening one is your explicit go-ahead to let the agent change the design this session:

txn.begin      # authorize mutations for this session
   … agent runs resize / hold-fix / … — each change is staged, not adopted …
txn.status     # review what's staged
txn.commit     # accept the staged changes      —or—     txn.rollback   # discard them
audit.events   # the full trail of what ran this session

Because the optimizers are non-destructive (they write a new netlist and never touch your input), txn.rollback simply discards the candidates — there is nothing to restore.

Keep-best timing closure — flow.close_timing

flow.close_timing runs the safe loop for you: it signs off the design, then tries each optimizer in turn, re-signs-off the result, and keeps a candidate only if timing did not regress — threading the adopted netlist into the next step. The agent proposes; the deterministic engine decides. You give it a sign-off job (and optionally the steps to try); it returns baseline vs final and which steps were kept:

flow.close_timing  { "job": "counter.sta", "steps": ["resize", "hold-fix"] }
   → { baseline: {wns_ns …}, final: {wns_ns …}, improved_ns, kept_any, steps:[…] }

It writes candidate netlists, so it runs under the same gate as the optimizers (an open transaction under pro).

Commands

vyges mcp                       start the stdio server (an MCP client spawns this)
vyges mcp setup [--dry-run] [--uninstall]
                                register with every detected AI IDE (user scope)
vyges mcp install [dir]         register in <dir>/.mcp.json (one project)
vyges mcp uninstall [dir]       remove the project registration
vyges mcp --list                list the tools this server would advertise

Both setup and install are safe to run alongside VyContext and any other MCP servers — they read-modify-write each config, preserving existing entries. (Antigravity is detected but not yet wired — its MCP config path is being confirmed.)

Your tools, your workflow — open or commercial

vyges mcp is tool-agnostic: beyond the Loom engines, any resolvable non-Vyges EDA tool is advertised too, so an agent drives your whole flow, not just Loom.

  • Open source — yosys, verilator, klayout, openroad, magic, netgen.
  • Commercial (COTS) — Synopsys (primetime, starrc, fusioncompiler), Cadence (genus, innovus, tempus, quantus), Siemens (calibre) — each advertised when it’s resolvable on your host.

Arguments pass through verbatim and output comes back as text (structured per-tool schemas are a later refinement). Point each tool at its install — including a licensed commercial tool on an NFS-mounted toolshed — in tools.json (next section), with license variables via env; paths, versions, and license settings depend on your site’s install layout and deployment architecture and are tuned to match. The agent then drives one interface across Loom, open-source, and commercial tools — your existing methodology wrapped, not replaced.

Pinning versions (and containers)

vyges mcp setup already drops a starter ~/.vyges/tools.json (carrying the $schema line) in place, so your editor and AI assistant can autocomplete pins immediately — nothing extra to run. (vyges mcp tools --init writes the same starter on demand.)

Hosts often carry several versions of a tool. Pin exactly which one an adapter uses in that tools.json — user-scope ~/.vyges/tools.json or project-scope .vyges/tools.json (project wins), with an env override VYGES_TOOL_<NAME> on top and PATH as the fallback:

{
  "env": { "SNPSLMD_LICENSE_FILE": "27020@lic.corp" },   // license servers (forwarded to every tool)
  "tools": {
    "yosys":     { "path": "/opt/yosys-0.40/bin/yosys" },
    "primetime": { "path": "/eda/synopsys/pt/T-2022.03/bin/pt_shell" },  // licensed tool on an NFS toolshed
    "klayout": {
      "container": {
        "runtime": "podman",
        "image": "klayout:0.28.17",
        "entrypoint": "klayout",
        "mounts": ["/pdk:/pdk:ro", "/data:/data"]
      }
    }
  }
}

A container-backed tool runs via podman/docker run with your working directory auto-mounted 1:1 (so paths in the args resolve inside), and the image tag is the pinned version:

  • mounts — extra -v host:container[:ro] volumes. This is the common enterprise case: mount your PDK, a license directory, or a shared data volume into the container; append :ro for read-only.
  • entrypoint — the command inside the container (default: the tool name). Set it to "" to use the image’s own ENTRYPOINT, or to a full in-container path.

Check how everything resolves on a host — engines and external tools, with the pinned path/image, version, and which source each came from — with:

vyges mcp tools

Safe to hand-edit. A malformed tools.json is never fatal — it’s validated and any problems (bad JSON, a container with no image, an unknown key, path and container together) are reported by vyges mcp tools and at server startup, with resolution simply falling back to PATH. For editor autocomplete + as-you-type checking, add a $schema line pointing at the shipped schema:

{ "$schema": "https://vyges.com/schema/v1/tools.schema.json", "tools": { … } }

The resolved binary/image and its version are recorded in every result and folded into the content hash, so swapping a version can never silently reuse a stale result.

Agentic feedback — loom.feedback

Beyond the per-engine tools, the server exposes loom.feedback — the agent’s “eyes” for a layout iteration: one bundle of a render (gds-view), categorized DRC verdicts, and a score (violation counts, and correlation to a golden if you pass one). It’s also a CLI:

vyges mcp feedback design.gds --rules sky130.drc [--top TOP] [--golden 0]

An agent calls loom.feedback, reads { render, verdicts, score }, decides the next edit, and re-runs — the closure loop, on your machine.

Trust & tiers

This is the free, local, no-auth tier: it exposes your own installed binaries to your own local agent — the same trust model as any local dev MCP server. Even here you stay in control of what an agent can touch: the read-only core profile and per-session transactions above are yours by default. Governed, distributed execution (RBAC, audit, running tools across a fleet) is a separate enterprise tier (Vyges Mill), not this server.

See the vyges mcp command reference for the generated help.

vyges mcp

Generated from vyges mcp --help — this page is the tool’s own output, verbatim.

vyges mcp — local stdio MCP server exposing the installed Vyges Loom engines

usage:
  vyges mcp                 start the stdio MCP server (an MCP client spawns this)
  vyges mcp tools           show how each engine + external tool resolves on this host
  vyges mcp tools --init    write a starter ~/.vyges/tools.json (with $schema) to pin from
  vyges mcp tools --upgrade migrate an older tools.json to the current schema (backs up .bak)
  vyges mcp feedback <gds> --rules <deck> [--top C] [--golden G] [-o SVG]
                            agentic feedback bundle: render + DRC verdicts + score
  vyges mcp emap <netlist> --top T --lib L.lib [--lib …] --lef X.lef [--lef …]
                 [--sdc S] [-o out.v] [--no-multioutput] [--scene fast]
                            multi-output techmap (OpenROAD resynth_emap): before/after/delta
  vyges mcp setup [--dry-run] [--uninstall]
                            detect installed AI IDEs (Claude Code / Cursor / VS Code)
                            and register `vyges mcp` with each (user scope)
  vyges mcp install [dir]   register `vyges mcp` in <dir>/.mcp.json (project scope)
  vyges mcp uninstall [dir]  remove it from <dir>/.mcp.json
  vyges mcp --list          print the tools this server would advertise, then exit
  vyges mcp --version       print the version
  vyges mcp --help          show this help

No port, no auth: the server talks JSON-RPC over stdin/stdout and exposes your
own installed `vyges-<engine>` binaries to your own local agent. Install engines
with `vyges install loom`, then `vyges mcp setup` (all your AI IDEs) or
`vyges mcp install` (just this project).

vyges model

A model/provider registry for the Vyges agentic layer. It names AI models and resolves their connection details (backend · endpoint · model id · tool-calling capability · local/cloud) so the model is a registered, swappable choice — the same thin-descriptor + resolve pattern as vyges pdk-store, applied to models.

This is the configuration used by Mode 2 — when vyges itself drives the tools with a model you choose. (In Mode 1, your AI IDE owns the model and no registration is needed.) Register a model, then drive the tools with vyges model run.

Local models come first

Vyges is model-agnostic, and local models are the primary path — the design data your agent reasons over never leaves your machine. Serve an open model on an OpenAI-compatible endpoint (ollama, llama.cpp-server, vLLM, TGI — each exposes /v1) and register it with --local:

vyges model add semikong --backend openai-compat \
      --endpoint http://127.0.0.1:11434/v1 --model semikong-8b-q4 --tool-calling json --local

vyges model check   semikong          # is the endpoint reachable?
vyges model resolve semikong endpoint # http://127.0.0.1:11434/v1

Cloud models — API keys and URLs

Cloud models (Anthropic, OpenAI, …) are fully supported and are often the easiest way to start when your organization already has a subscription or corporate agreement. Two things to configure: a key and, optionally, a base URL.

Keys are a reference, never the secret

--api-key stores an environment-variable reference (e.g. $ANTHROPIC_API_KEY) — not the key itself. The driver expands it from the environment at call time, so no secret ever lands in model.json (safe to commit ./.vyges/model.json to a repo). Export the real key in your shell or CI secret store:

export ANTHROPIC_API_KEY=sk-ant-…
export OPENAI_API_KEY=sk-…

Per provider

Anthropic (Claude) — the default endpoint is api.anthropic.com, so omit --endpoint:

vyges model add claude --backend anthropic --model claude-sonnet-5 \
      --api-key '$ANTHROPIC_API_KEY' --tool-calling native --cloud

Route Claude through a corporate gateway / Bedrock- or Vertex-style proxy that speaks the Anthropic Messages API by pointing --endpoint at its base URL (the driver appends /v1/messages; a full …/messages URL is used as-is):

vyges model add claude-gw --backend anthropic --endpoint https://claude-gw.corp.com \
      --model claude-sonnet-5 --api-key '$CLAUDE_GW_KEY' --tool-calling native --cloud

OpenAI:

vyges model add gpt --backend openai-compat --endpoint https://api.openai.com/v1 \
      --model gpt-4o --api-key '$OPENAI_API_KEY' --tool-calling native --cloud

Grok (xAI) — OpenAI-compatible, just a different base URL:

vyges model add grok --backend openai-compat --endpoint https://api.x.ai/v1 \
      --model grok-2 --api-key '$XAI_API_KEY' --tool-calling native --cloud

Azure OpenAI / a corporate OpenAI gateway — point --endpoint at your deployment base (the driver appends /chat/completions):

vyges model add azure --backend openai-compat \
      --endpoint https://my-resource.openai.azure.com/openai/deployments/gpt4o \
      --model gpt-4o --api-key '$AZURE_OPENAI_KEY' --tool-calling native --cloud

Per-invocation / CI override

Override any model inline — without editing model.json — with a VYGES_MODEL_<NAME> env var holding a JSON object (handy in CI, where the endpoint/key vary per environment):

export VYGES_MODEL_GROK='{"backend":"openai-compat","endpoint":"https://api.x.ai/v1","model":"grok-2","api_key":"$XAI_API_KEY","local":false}'

Drive the tools (Mode 2)

Once a model is registered, run a task — vyges presents the installed vyges mcp tools to the model and runs the reason → tool-call → observe loop until the model signals done:

vyges model run semikong "check DRC on block1.gds with the sky130 deck and report the count"

Point it at a local model and the design data your agent reasons over never leaves your machine. (v1 uses JSON tool-calling and passthrough tool arguments; native provider tool-calling and finer controls are refinements.)

Commands

vyges model list                     list registered models
vyges model add <name> …             register into ~/.vyges/model.json
vyges model resolve <name> <key>     print one field (endpoint | model | tool_calling | local | …)
vyges model check <name>             report reachability of the model endpoint
vyges model run <name> "<task>"      drive the vyges mcp tools with the model (Mode 2)

model.json

Registrations live in a models map. Resolution order (first hit wins), mirroring tools.json:

  1. env VYGES_MODEL_<NAME> — a JSON object (per-invocation / CI override)
  2. project ./.vyges/model.json — a repo pins its model
  3. user ~/.vyges/model.json — the host default

Two backends cover every provider: openai-compat (OpenAI, Grok/xAI, and OSS servers — ollama, llama.cpp-server, vLLM, TGI) and anthropic (Claude native).

{
  "$schema": "https://vyges.com/schema/v1/model.schema.json",
  "models": {
    "semikong": { "backend": "openai-compat", "endpoint": "http://127.0.0.1:11434/v1",
                  "model": "semikong-8b-q4", "tool_calling": "json", "local": true },
    "claude":   { "backend": "anthropic", "model": "claude-sonnet-5", "api_key": "$ANTHROPIC_API_KEY",
                  "tool_calling": "native", "local": false, "egress": "metadata-only" }
  }
}

Local vs cloud — and data egress

Mark each model --local or --cloud so it is explicit which models can see your design data. Local is the recommended default: the data your agent reasons over never leaves the machine — the same local-first trust model as the rest of the CLI.

For a cloud model the data leaves your boundary, so govern which models are allowed. Vyges is deliberately not a data-loss / egress firewall: local models keep data on the machine by construction, and for cloud models you enforce with your organization’s existing egress controls (proxy / CASB / DLP). Vyges records which model each call used; it does not claim to prevent leakage.

vyges

Generated from vyges --help — this page is the tool’s own output, verbatim.

Vyges — one CLI for the Vyges hardware-IP toolchain.

`vyges modules` lists the promoted tools; `vyges agents` prints the agent guide. An
unknown subcommand dispatches to an external `vyges-<name>` binary on PATH (e.g.
`vyges pdk-store …` runs vyges-pdk-store). Upgrade the CLI itself with `vyges update`.
Report bugs/features centrally with
`vyges --bug-report` / `--feature-request`, or support Vyges with `vyges --sponsor`.

Usage: vyges [OPTIONS] [COMMAND]

Commands:
  modules  List the promoted Vyges modules and whether each is installed
  agents   Print the operating guide for AI IDE agents
  install  Install an on-demand engine: fetch the prebuilt `vyges-<tool>` from its public `vyges-tools/<tool>` GitHub release into `~/.vyges/bin`. `vyges install loom` installs the whole Vyges Loom suite at once
  cache    Inspect or clear the Liberty parse cache (`~/.vyges/cache/liberty`) — the shared parse-once store the timing/power engines use
  update   Update the vyges CLI itself to the latest release (runs `vyges-update`)
  help     Print this message or the help of the given subcommand(s)

Options:
  -v, --verbose...
          Increase diagnostic verbosity (stderr): -v debug, -vv trace. Repeatable

  -q, --quiet...
          Reduce diagnostics (stderr): -q warnings-only, -qq errors-only, -qqq silent. Repeatable. Overrides VYGES_LOG. (Data on stdout is never suppressed.)

      --bug-report
          File a bug report (central — vyges/community issues)

      --feature-request
          File a feature request (central — vyges/community issues)

      --sponsor
          Sponsor Vyges (github.com/sponsors/vyges-ip)

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

vyges pdk-store

Generated from vyges pdk-store --help — this page is the tool’s own output, verbatim.

vyges pdk-store — consistent PDK presentation + resolution

usage:
  vyges pdk-store list                                   list registered PDKs
  vyges pdk-store show     <name|file.vyges-pdk.json>          show a PDK + its collateral
  vyges pdk-store validate <file.vyges-pdk.json>               schema-validate a descriptor
  vyges pdk-store add      <name|file.vyges-pdk.json> [--force]  register into the user store
  vyges pdk-store deregister <name>                      remove a registration (keeps data)
  vyges pdk-store use      <name> [--local]              set the selected (*) PDK (global, or ./.vyges)
  vyges pdk-store resolve  <name|file> <key> [--corner C] [--library L]
                                                         print collateral path(s) for a flow
  vyges pdk-store verify   <name|file>                   check collateral paths exist
  vyges pdk-store fetch    <name|file> [--dry-run]       materialize PDK data (git mirror / Ciel)
  vyges pdk-store refresh  [--dry-run]                   refresh the catalog from vyges-tools/pdk-catalog

keys for resolve: tech_lef | lvs_device_rules | drc_deck | lvs_deck | primitives_spice |
                  models (per --corner) | lib (per --corner + --library) |
                  spice | gds | lef | verilog (per --library) | <any collateral key>

note: lvs_device_rules is vyges-lvs's device-recognition deck (GDS->SPICE layer/datatype
      mapping). It was called extract_rules, which read as parasitic extraction; the old
      name still resolves but is deprecated. RC parasitics are NOT collateral --
      vyges-extract derives them from the tech LEF (+ an OpenRCX captable when the LEF is
      geometry-only) and caches them beside that LEF.

flags: --corner <name> · --library <name> · --force · --dry-run · --local · -h/--help · -V/--version
       -v/--verbose · -q/--quiet (stderr diagnostics; or set VYGES_LOG=off|error|warn|info|debug|trace)

vyges catalog

Generated from vyges catalog --help — this page is the tool’s own output, verbatim.

vyges catalog — search the Vyges IP catalog

usage:
  vyges catalog search <query> [--category C] [--keyword K] [--json]
                                             find IPs by name / category / keyword
  vyges catalog show     <name> [--json]     show one IP (incl. license)
  vyges catalog license  <name>              print one IP's license (SPDX)
  vyges catalog categories [--json]          list categories (with counts)
  vyges catalog sources  [--json]            list configured sources + cache status
  vyges catalog refresh  [--source N] [--dry-run]
                                             fetch source indexes (like `apt update`)

  vyges catalog fetch    <ip[@version]> [--force] [--dry-run]
                                             materialize an open IP into ~/.vyges/catalog/ip/<name>/<version>
  vyges catalog fetch --private <url|path>[@version]
                                             materialize a private/NDA IP into ~/.vyges/catalog/private_ip/ (segregated)
                                             from a repo URL/org-repo (clone) or a local directory (import);
                                             version optional — derived from vyges-metadata.json if omitted
  vyges catalog cached   [--json]            list locally installed (cached) IPs + versions
                                             (aliases: installed, ls)
  vyges catalog path     <ip[@version]>      print a cached IP's path (for flows)
  vyges catalog verify   <ip[@version]>      check a cached IP is intact
  vyges catalog rm       <ip[@version]>      remove cached version(s) of an IP
  vyges catalog prune    [--dry-run]         remove the whole local IP cache

Search-first by design (no bulk dump). Sources: a built-in `oss` catalog plus any
enterprise catalogs in ~/.vyges/catalog/sources.conf (line: `name url [TOKEN_ENV]`).
flags: --category <c> · --keyword <k> · --source <n> · --private · --force · --json · --dry-run · -h · -V
       -v/--verbose · -q/--quiet (stderr diagnostics; or set VYGES_LOG=off|error|warn|info|debug|trace)

vyges metadata

Generated from vyges metadata --help — this page is the tool’s own output, verbatim.

vyges metadata — work with an IP's vyges-metadata.json

usage:
  vyges metadata reconcile <path> [options]   check metadata against its RTL
  vyges metadata update    <path> [options]   reconcile + WRITE: fill gaps AND
                                              resolve drift (destructive)
  vyges metadata validate  <path> [options]   check metadata against the schema
  vyges metadata create    <path> [options]   generate a metadata skeleton from RTL
  vyges metadata schema    [field] [--json]   describe the schema's required/optional
                                              fields (introspection; no file needed)
  vyges metadata --version                    binary version + schema version

create options:
  --rtl <dir>          RTL source directory (default: <path>/rtl)
  --name <name>        IP name (default: the directory name)
  --metadata <file>    output filename (default: vyges-metadata.json)
  --force              overwrite an existing metadata file

validate options:
  --metadata <file>   metadata filename to read (default: vyges-metadata.json)
  --json / --quiet     machine-readable / only invalid IPs
  --fail-on-error      exit non-zero if any IP fails the schema (CI gate)

reconcile options:
  --rtl <dir>         RTL source directory (default: <path>/rtl)
  --rtl-test <dir>    testbench directory (reserved — used by `create`)
  --metadata <file>   metadata filename to read in each IP dir
                      (default: vyges-metadata.json; point at e.g.
                      my_test_metadata.json to compare an alternative for drift)
  --json              machine-readable output
  --quiet, -q         only report IPs that have issues
  --fail-on-drift     exit non-zero if any metadata/RTL drift is found (CI gate)
  --write             merge the proposed additions (clock/reset/top_module) into
                      the metadata file; refreshes 'updated'. Asks before writing.
                      Refuses an IP if the change introduces NEW schema errors.
  --yes, -y           with --write, skip the per-IP confirmation prompt
  --no-schema-gate    with --write, skip the schema delta check

<path> is a single IP directory (with the metadata file) or a directory of
cached IPs (each subdir an IP). Reports per dimension: ok / GAP (RTL has it,
metadata omits it) / DRIFT (metadata claims it, the RTL doesn't).

Vyges Loom — the open EDA sign-off & optimization suite

The Loom suite, under vyges. Install once — vyges install loom — then run any engine as vyges loom <engine> (e.g. vyges loom sta-si run top.sta). Each engine is also a standalone vyges-<engine> binary on your PATH, which is what the dispatch calls and the integration contract flow authors target directly (see integrating into a flow).

Commercial-grade engines — sign-off that analyzes and optimizers that act on the result — each driven by a declarative job file that produces a standard artifact. They share one cross-engine data spine (vyges-loom) — the output of one is the input of the next, with no glue scripts:

.ext  ─► vyges loom extract ─► .spef ──┐
.char ─► vyges loom char    ─► .lib  ──┼─► vyges loom sta-si ─► WNS / TNS ─► resize · vt-swap · buffer-insert ─► fixed netlist
                                       └─► vyges loom power ─► activity ─┬─► vyges loom em-ir   ─► IR-drop + EM
                                                                        └─► vyges loom thermal ─► temp / hotspot
.gds + rules ─► vyges loom lvs ─► MATCH / MISMATCH
EngineCommandJob fileOutput
charvyges loom char.char / .charlibLiberty .lib
extractvyges loom extract.extSPEF
powervyges loom power.pwrpower report + activity map
sta-sivyges loom sta-si.staWNS / TNS / worst path
em-irvyges loom em-ir.emirIR-drop map + EM check
thermalvyges loom thermal.thermaltemperature field + hotspot
lvsvyges loom lvs.lvsMATCH / MISMATCH + diagnostics

Optimizers — they read the timer’s verdict and edit the netlist to fix it:

EngineCommandJob fileOutput
resizevyges loom resize.resizeresized netlist (drive sizing)
vt-swapvyges loom vt-swap.vtswapresized netlist (Vt / leakage)
buffer-insertvyges loom buffer-insert.bufinsbuffered netlist (transition fix)

Physical construction — they build the design database the engines above read. ⚠️ Not part of vyges install loom: install the group with vyges install physical, or one with vyges install <engine>. None has a published release yet, so vyges modules lists them as unreleased and the installer says so rather than failing on a missing download.

EngineCommandBuilds
ifpvyges ifpdie area, core area and the standard-cell rows
mplvyges mplhard-macro placement, before the cells go round them
tapvyges tapwell taps, endcaps, and row cutting around macros
pdnvyges pdnpower rings, straps, follow pins and their vias
pplvyges pplIO pin slots and the assignment onto them
padvyges padthe IO pad ring, bumps and RDL routes
dplvyges dpllegal detailed placement, and the check for one
finvyges findensity fill, to meet per-layer density rules

Verification + utilities — prove correctness, or view the layout:

EngineCommandInputOutput
drcvyges loom drcGDS + .drc deckgeometry violations
cdcvyges loom cdcnetlist + lib + SDCclock-domain crossings
glitchvyges loom glitchnetlist + Libertyreconvergent-fanout hazards
lecvyges loom lectwo netlists + libEQUIVALENT / NOT + counter-ex.
gds-viewvyges loom gds-viewGDS (+ marks)layered SVG with overlay
opendbvyges opendb <cmd>.odb / .3dbxECO surgery, DEF I/O, 3D assembly checks

opendb is the odd one out: it takes subcommands, not a job file, because it is the design database the other engines read and edit rather than an analysis over one. It is also the only engine with a native (C++/libodb) dependency, which is why the suite is unix-only.

Domain coverage. The suite is a digital sign-off / optimization flow; on top of that, lvs, layout, em-ir, thermal and extract additionally cover analog / mixed-signal physical & integrity verification — LVS, geometry, IR / EM, thermal, and RC. Analog functional / timing sign-off is out of scope.

Open core

Every engine is Apache-2.0 and runnable today. The per-foundry calibration plugins (NDA) stay private — the engine is open, the fab-specific accuracy is gated.

One declarative job in, one standard artifact out

No Tcl, no glue scripts: describe the job, get a standard sign-off file. The timing and power engines exit non-zero on a violation, so they gate CI directly. See job-file formats.

vyges-opendb — the OpenDB substrate

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges opendb <subcommand>. It’s also a standalone vyges-opendb binary on your PATH (the integration contract for flow authors). Unlike the other engines it takes subcommands rather than a job file — it is the design database the other engines read and edit, not an analysis over one.

vyges-opendb is the in-memory design database layer of Loom: a safe Rust API over OpenROAD’s OpenDB (libodb), built standalone — no Tcl, no SWIG, no OpenROAD engines. Every Loom step that reads or edits a placed/routed design (ECO, audits, extraction feeders) goes through it, and it carries LEF/DEF/GDS/CDL I/O with it.

Two crates (both Apache-2.0, on github.com/vyges-tools):

CrateRepoRole
vyges-opendb-libvyges-tools/opendb-libstandalone libodb build + low-level cxx FFI
vyges-opendbvyges-tools/opendbsafe, ergonomic API (Db, ECO write path)

How the build works — pinned + sparse, no fork

vyges-opendb-lib pins an OpenROAD commit (matching the vyges-opendb distribution) and does a blobless, cone-sparse checkout of only src/odb + src/utl + cmake (~24 MB, not the ~1.8 GB tree), then compiles a standalone libodb.a (C++20). fmt / spdlog / abseil are built from source at pinned versions and static-linked — never the host’s (often older) copies — so the binary is portable across distros with the only runtime floor being glibc (Boost is header-only, zlib dynamic). An on-demand GitHub workflow publishes the per-target bundle with a provenance manifest.json (OpenROAD SHA + dep versions + measured glibc floor). Proven on linux/x86_64, linux/arm64, and macOS/Apple Silicon.

The API

#![allow(unused)]
fn main() {
use vyges_opendb::Db;

let mut db = Db::open("design.odb")?;
println!("{} — {} insts, {} nets", db.block_name(), db.num_insts(), db.num_nets());

// ECO: splice a buffer onto a pin (legalization delegated to the engines separately)
let buf = db.find_master("buf");
db.insert_buffer("inst42", "A", &buf, "eco_buf0", 10_000, 10_000)?;
db.write("design_eco.odb")?;
}
  • &self reads, &mut self edits — the borrow checker enforces no read-while-mutate.
  • Typed Error carrying the OpenDB message.
  • Write primitives: create_net, create_inst, set_inst_location, connect, disconnect, plus the composed insert_buffer.

The vyges-opendb CLI

vyges install opendb puts a single vyges-opendb multi-tool on the path. Subcommands:

# read path — one-line design summary
vyges opendb info --input design.odb
# design.odb: block=counter insts=229 nets=52 bterms=13

# ECO step — mirrors LibreLane's Odb.InsertECOBuffers (same INSERT_ECO_BUFFERS config)
vyges opendb insert-eco-buffers --input in.odb --output out.odb --config eco.json
# eco.json: { "INSERT_ECO_BUFFERS": [ { "target": "inst42/A", "buffer": "sky130_fd_sc_hd__buf_2" } ] }

# ECO step — mirrors LibreLane's Odb.InsertECODiodes (antenna diodes)
vyges opendb insert-eco-diodes --input in.odb --output out.odb --config eco.json
# eco.json: { "INSERT_ECO_DIODES": [ { "target": "inst42/A", "diode": "sky130_fd_sc_hd__diode_2" } ] }

insert-eco-buffers splices a buffer (create buffer + net, rewire the pin’s driver through it); insert-eco-diodes ties an antenna diode onto the target pin’s net as a leaf — no new net, no rewiring. Both place at the target’s location; downstream grt/dpl legalization runs as a separate engine step. Loom steps invoke vyges-opendb <step> ….

16 of LibreLane’s 21 Odb.* steps are implemented as vyges-opendb subcommands (all with a --describe JSON contract). Surgery/placement steps take --input/--output; report steps emit JSON/text to stdout:

SubcommandLibreLane step(s)
insert-eco-buffersOdb.InsertECOBuffers
insert-eco-diodesOdb.InsertECODiodes
diodes-on-portsOdb.DiodesOnPorts (≈ PortDiodePlacement)
set-power-connectionsOdb.SetPowerConnections
add-obstructions / remove-obstructionsOdb.{Add,Remove}{PDN,Routing}Obstructions
custom-io-placementOdb.CustomIOPlacement
manual-global-placementOdb.ManualGlobalPlacement
manual-macro-placementOdb.ManualMacroPlacement
cell-frequency-tablesOdb.CellFrequencyTables (report)
report-disconnected-pinsOdb.ReportDisconnectedPins (report)
write-verilog-headerOdb.WriteVerilogHeader
report-wire-lengthOdb.ReportWireLength (report)
apply-def-templateOdb.ApplyDEFTemplate

DEF I/O landed with libodb v1, so read-def / write-def are available alongside these, and report-connectivity dumps the netlist graph as JSON. The steps still outstanding are the antenna checks and the antenna-driven diode heuristics, which need OpenROAD’s antenna module.

Every step is self-describing — vyges opendb insert-eco-buffers --describe emits a JSON step contract (identity, CLI args, config schema) so an orchestrator can introspect it without running it. Those contracts are rendered per step in the CLI reference — config keys, types and defaults included.

2.5D / 3D chiplet assemblies

Three subcommands go beyond LibreLane’s step set, because LibreLane has no equivalent: they work on a chiplet assembly — several dies and how they stack — rather than on one design.

vyges opendb read-3dblox  --input stack.3dbx --output stack.odb   # assembly file -> database
vyges opendb check-3dblox --input stack.odb                       # -> findings, as JSON
vyges opendb view-3dblox  --input stack.3dbx --output stack.svg   # -> a drawing

read-3dblox reads 3Dblox, the 2.5D/3D interchange format — .3dbx for the assembly, .3dbv for the chiplet definitions it includes — and builds the chips, bonding regions and die-to-die connections in the database.

Anything the database cannot hold is named rather than silently dropped: virtual bonds (bot: ~) have no bottom die to attach to, a non-rectangular region collapses to its bounding rectangle, and a stack whose dies sit on different processes cannot be fully represented because a database carries one technology. That last one is an upstream limit, and it is why this is honest about being an assembly description rather than a heterogeneous stack.

Where the external files are looked for

A 3Dblox assembly is not one file. The .3dbx include:s one or more .3dbv definitions, and each .3dbv points at collateral of its own — bmap, APR_tech_file, LEF_file. Once bump maps are being read, how those paths resolve stops being a detail: a path that resolves against the wrong directory fails at open time, far from the line that caused it.

The rule is one sentence: every relative path resolves against the file that names it, never against your working directory.

designs/stack.3dbx          include: dies.3dbv        -> designs/dies.3dbv
designs/dies.3dbv           bmap: maps/mem.bmap       -> designs/maps/mem.bmap
designs/dies.3dbv           APR_tech_file: [../ng45/*_tech.lef]
                                                      -> designs/../ng45/*_tech.lef

So you can run vyges opendb check-d2d --input designs/stack.3dbx from anywhere and get the same answer. Absolute paths are taken as-is.

Two things happen before resolution:

  • #!define macros are expanded. A .3dbv may open with #!define NG45 ../nangate45 and then write APR_tech_file: [NG45/*_tech.lef]. The substitution happens first, so the macro never reaches the filesystem.
  • * and ? in APR_tech_file are real globs, expanded in the directory the pattern names. A pattern matching nothing is reported, not silently treated as “no technology”.

A bmap that cannot be opened is reported and the assembly still loads — the geometry is worth having, and the report says which map was missed. That distinction matters: a bump map that failed to load would otherwise leave every bump check with nothing to look at and a clean verdict to show for it.

check-3dblox runs seven structural checks over the assembly — logical connectivity, floating chips, overlapping dies, unused internal_ext regions, connection-region overlap and mating-surface gap versus connection thickness, bump alignment, and alignment markers — and emits the findings themselves, not just counts:

{ "violations": 1,
  "categories": [
    { "category": "Floating chips", "count": 1,
      "markers": [ { "name": "u_base", "comment": "Isolated chip set starting with u_base" } ] } ] }

It is a checker, not a repairer: it annotates the in-memory database and never modifies the design. OpenDB’s own [WARNING ODB-nnnn] lines go to stderr, leaving stdout parseable.

view-3dblox draws the assembly as a single self-contained SVG — no server, no GUI toolkit, no X. Two views, because one is not enough: a plan view shows footprints and overhang, but it cannot show stacking order, die thickness, bond gaps, or which face is bonded, which is the whole subject. So the primary view is a cross-section, with the plan below it and the linter’s findings listed underneath — the engines say what, the drawing says where.

The Z axis is scaled to fit the page and prints its own factor, because a die is millimetres across and microns thick and every package cross-section is drawn with a non-uniform Z scale. The difference is saying so on the drawing.

Both commands that construct an assembly need a build with --features gen-write; released binaries have it.

Die-to-die interface checking

check-d2d compares the bump maps of two mating faces — the .bmap files a 3Dblox .3dbv points at — and reports what does not agree:

vyges opendb check-d2d --input stack.3dbx     # every bonded pair; frame from the assembly
vyges opendb check-d2d --top logic.bmap --bottom mem.bmap --offset-x -120.5 --flip-x

With --input the bump maps and the die placements both come from the 3Dblox assembly, so nothing about how the dies sit has to be stated on the command line. A bonded pair whose regions declare no bmap is listed as skipped rather than counted clean.

It reports unmated bumps (a signal that leaves one die and arrives nowhere), misaligned pairs with the distance between them, net mismatch (mated bumps carrying different signals), and cell mismatch (a microbump against a C4). Both sides are walked, so an unmated bump on the lower die is reported too.

This is not covered elsewhere. check-3dblox’s Logical Connectivity check compares only bumps that already land on precisely the same point and skips anything without a counterpart, and its sibling checkNetConnectivity is an empty function body. Measured on assemblies built for the purpose:

interfacecheck-3dbloxcheck-d2d
a top bump with no mating bump at all01
everything mated and exactly aligned00
a mating pair off by 1 nm01
a mating pair off by 5 µm02

Two deliberate limits. In the two-file form the relative placement is not inferred — pass --offset-x / --offset-y in microns and --flip-x for a face-to-face bond. Either way the frame is echoed in every report, because “no violations” means nothing without knowing what frame produced it. And the tolerance is derived, not invented: half the smaller bump pitch by default, so a match cannot be ambiguous, with --tolerance to override and the report saying which applied.

--input is the better entry point precisely because the orientation is easy to get wrong: MZ does not mirror X. It flips the die’s face and leaves the bump field’s handedness alone, so a face-to-face die is usually MZ_MY. That was measured against odb’s own unfolded bump positions, not read off the names, and writing MZ where MZ_MY was meant turns a correct interface into four net mismatches. An orientation the mapping has not been verified against is refused rather than processed, since odb silently treats an unrecognised one as R0.

Install

vyges install loom now fetches opendb along with the rest of the suite, since the CLI is unix-only too and their platform support is identical. It stays individually installable, like every other engine:

vyges install loom       # the whole suite, opendb included
vyges install opendb     # or just this one: libodb + the odb/ECO/3D steps
cargo install --git https://github.com/vyges-tools/opendb   # or use the crate directly (unix)

It is still not compiled into the core vyges binary. That distinction survives the platform change: libodb is native C++/cmake and version-sensitive, so it stays a separately-versioned installable rather than making every vyges build carry a C++ toolchain. Being installed with the suite and being linked into the CLI are different things, and only the first changed.

Building from source

The first two forms above download a prebuilt binary and need nothing else. cargo install compiles libodb from source, so it needs a C++20 toolchain and the headers it includes:

# Debian / Ubuntu
sudo apt-get install -y cmake g++ bison libboost-dev libboost-iostreams-dev libbz2-dev zlib1g-dev

# macOS
brew install cmake boost bison
export PATH="$(brew --prefix bison)/bin:$PATH"   # see below — this line is not optional

The bison PATH line matters. macOS ships bison 2.3 from 2006, and the LEF/DEF grammars need 3.x. Homebrew’s bison is keg-only, so it is not on PATH by default — without that export the build fails deep inside the parser generator with an error that says nothing about bison’s version. The release workflow exports it for exactly this reason.

Boost is header-only here, so only its include path is needed, not a library to link. zlib is the one dynamic link. Everything else — fmt, spdlog, abseil — is built from source at pinned versions and statically linked, deliberately, so the result does not pick up whatever older copies the host happens to have.

vyges itself is unix-only — see Installation. On Windows, use WSL2.

Status

Read + ECO write path over the db core, DEF I/O, and a 2.5D/3D chiplet path (read an assembly, lint it, check its die-to-die interfaces, draw it). GDS I/O and richer traversal follow; OpenROAD’s own odb C++ GTests are the planned CI conformance gate. OpenROAD is BSD-3-Clause; these crates are Apache-2.0.

vyges opendb — CLI reference

Generated from vyges opendb --help — this page is the tool’s own output, verbatim.

vyges opendb — OpenROAD's OpenDB (libodb) design database

usage:
  vyges opendb <command> [options]

commands:
  info                --input <f.odb>
                      Print a one-line summary: block name + inst/net/bterm counts.

  insert-eco-buffers  --input <in.odb> --output <out.odb> [--config <eco.json>]
                      Insert ECO buffers (INSERT_ECO_BUFFERS in the config) into the design.

  insert-eco-diodes   --input <in.odb> --output <out.odb> [--config <eco.json>]
                      Tie antenna diodes (INSERT_ECO_DIODES in the config) onto target nets.

  manual-global-placement  --input <in.odb> --output <out.odb> [--config <cfg.json>]
                      Set instance origins (MANUAL_GLOBAL_PLACEMENT in the config).

  manual-macro-placement   --input <in.odb> --output <out.odb> [--config <cfg.json>]
                      Place + orient macros (MANUAL_MACRO_PLACEMENT in the config).

  diodes-on-ports     --input <in.odb> --output <out.odb> [--config <cfg.json>]
                      Tie antenna diodes onto I/O port nets (DIODES_ON_PORTS in the config).

  cell-frequency-tables     --input <f.odb>
                      Print a JSON table of instance count per master cell (report).

  report-disconnected-pins  --input <f.odb>
                      Print a JSON list of pins/ports with no net (report).
  place-diodes              --input <f.odb> [--output <f.odb>] [--threshold UM]
                      Insert antenna diodes on long nets (LibreLane Odb.FuzzyDiodePlacement /
                      Odb.PortDiodePlacement). Geometric heuristic, not ratio analysis.
  check-antenna-properties  --input <f.odb> [--cell NAME]...
                      Report pins whose LEF states no antenna gate/diffusion area (report).
                      Defaults to every master the design instantiates.

  set-power-connections     --input <in.odb> --output <out.odb> [--config <cfg.json>]
                      Wire instance pins to (power) nets (SET_POWER_CONNECTIONS in the config).

  add-obstructions          --input <in.odb> --output <out.odb> [--config <cfg.json>]
                      Add routing/PDN obstruction rects (OBSTRUCTIONS in the config).

  remove-obstructions       --input <in.odb> --output <out.odb>
                      Remove all obstructions.

  write-verilog-header      --input <f.odb> [--output <f.v>]
                      Emit a Verilog module header (ports + directions).

  report-wire-length        --input <f.odb>
                      Print the total routed wire length as JSON (report).

  report-connectivity       --input <f.odb>
                      Dump the netlist connectivity graph as JSON (report).

  read-3dblox               --input <f.3dbx> --output <out.odb> [--into <in.odb>]
                      Read a 3Dblox assembly (the 2.5D/3D interchange format) into a
                      database, so it can be linted or queried. Reports anything the
                      format expresses and the database cannot.
  view-3dblox               --input <f.3dbx|f.odb> --output <out.svg|out.png> [--heatmap]
                            [--top <chip>] [--scale <n>]
                      Draw the assembly: cross-section + plan, with any check-3dblox
                      findings listed on it. Format follows the output extension.
                      --heatmap shades MEASURED die-to-die misalignment onto the
                      plan view (from check-d2d); it is not a yield prediction.
  check-d2d                 --input <stack.3dbx> | --top <a.bmap> --bottom <b.bmap>
                            [--offset-x <um>] [--offset-y <um>] [--flip-x]
                            [--tolerance <um>]
                      Check a die-to-die interface: unmated bumps, misalignment, net
                      and bump-cell mismatch across the bond. Emits JSON.
  check-3d-nets             --input <stack.3dbx> [--tolerance <um>] [--no-tsv-inference]
                      Check net continuity across the whole stack: a net a die cannot
                      carry from one face to the other, and nets the bonding shorts
                      together. Emits JSON.
  check-3dblox              --input <f.odb>
                      3D/chiplet structural sign-off lint; reports violations as JSON (check).

  apply-eco-plan            --input <in.odb> --plan <plan.json> --output <out.odb>
                      Replay a timing-repair plan (all-or-nothing) into the design.

  custom-io-placement       --input <in.odb> --output <out.odb> [--config <cfg.json>]
                      Place I/O port pins (CUSTOM_IO_PLACEMENT in the config).

  write-def                 --input <f.odb> --output <f.def>
                      Export the design to a DEF 5.8 file (libodb v1 LEF/DEF I/O).

  read-def                  --input <in.odb> --def <f.def> --output <out.odb>
                      Import a DEF into the design (libodb v1 LEF/DEF I/O).

  import                    --lef <tech.lef> [--lef <lib.lef>]... [--def <f.def>]
                            [--verilog <f.v>] --output <out.odb>
                      Build a database from LEF + DEF or a structural Verilog netlist,
                      starting from nothing. The FIRST --lef creates the tech.

  apply-def-template        --input <in.odb> --template <f.def> --output <out.odb>
                      Apply a template DEF's floorplan (Odb.ApplyDEFTemplate).

  fields              [--class <dbClass>] [--writable]
                      List the generated instrumentation fields (discovery; JSON).

  get                 --input <f.odb> --class <dbClass> --field <name> [--key <k>]...
                      Read any generated field by (class, field) + addressing keys (JSON).

  set                 --input <in.odb> --output <out.odb> --class <dbClass> --field <name>
                      [--key <k>]... [--value <v>]...
                      Apply a generated setter (requires a build with --features gen-write).

  --version, -V       Print the version.
  --help,    -h       Print this help.

Step contracts

Generated from vyges opendb <step> --describe.

insert-eco-buffers

Splice ECO buffers into a placed .odb (database surgery; legalization is a separate step).

LibreLane step: Odb.InsertECOBuffers · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathyesoutput .odb after ECO
--configpathnoJSON with INSERT_ECO_BUFFERS (default: no-op)

Config keys

  • INSERT_ECO_BUFFERS (array) — buffers to insert; each rewires the target pin’s driver through a new buffer
    • target (string) — instance/pin to buffer, e.g. inst42/A
    • buffer (string) — library cell master, e.g. sky130_fd_sc_hd__buf_2

insert-eco-diodes

Tie antenna diodes onto target nets in a placed .odb (database surgery; a diode is a leaf, no rewiring).

LibreLane step: Odb.InsertECODiodes · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathyesoutput .odb after ECO
--configpathnoJSON with INSERT_ECO_DIODES (default: no-op)

Config keys

  • INSERT_ECO_DIODES (array) — diodes to insert; each ties an antenna diode onto the target pin’s net (no rewiring)
    • target (string) — instance/pin whose net gets a diode, e.g. inst42/A
    • diode (string) — antenna-diode master, e.g. sky130_fd_sc_hd__diode_2

manual-global-placement

Set instance origins in a .odb before global placement (database surgery).

LibreLane step: Odb.ManualGlobalPlacement · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathyesoutput .odb after placement
--configpathnoJSON with MANUAL_GLOBAL_PLACEMENT (default: no-op)

Config keys

  • MANUAL_GLOBAL_PLACEMENT (array) — instances to fix at an origin
    • instance (string) — instance name
    • x (integer) — origin x in DBU
    • y (integer) — origin y in DBU

manual-macro-placement

Place + orient macros in a .odb (database surgery).

LibreLane step: Odb.ManualMacroPlacement · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathyesoutput .odb after placement
--configpathnoJSON with MANUAL_MACRO_PLACEMENT (default: no-op)

Config keys

  • MANUAL_MACRO_PLACEMENT (array) — macros to place + orient
    • instance (string) — macro instance name
    • x (integer) — origin x in DBU
    • y (integer) — origin y in DBU
    • orient (string) — R0/R90/R180/R270/MX/MY/MXR90/MYR90 (optional)

diodes-on-ports

Tie antenna diodes onto I/O port nets in a placed .odb (database surgery).

LibreLane step: Odb.DiodesOnPorts · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathyesoutput .odb after ECO
--configpathnoJSON with DIODES_ON_PORTS (default: no-op)

Config keys

  • DIODES_ON_PORTS (object) — tie an antenna diode onto each selected port’s net
    • diode (string) — antenna-diode master, e.g. sky130_fd_sc_hd__diode_2
    • ports (array) — specific port names; omitted/empty = all ports

cell-frequency-tables

Report instance count per master cell as JSON (read-only).

LibreLane step: Odb.CellFrequencyTables · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design

Output: JSON array of { master, count } on stdout, most-used first

report-disconnected-pins

Report instance pins + ports that carry no net, as JSON (read-only).

LibreLane step: Odb.ReportDisconnectedPins · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design

Output: JSON array of strings on stdout: “inst/pin” and “port:name”

set-power-connections

Wire instance pins to (power) nets in a .odb (database surgery).

LibreLane step: Odb.SetPowerConnections · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathyesoutput .odb
--configpathnoJSON with SET_POWER_CONNECTIONS (default: no-op)

Config keys

  • SET_POWER_CONNECTIONS (array)
    • instance (string) — instance name
    • pin (string) — power/ground pin, e.g. VPWR
    • net (string) — net to connect it to, e.g. VDD

add-obstructions

Add routing/PDN obstruction rectangles to a .odb (database surgery).

LibreLane step: Odb.AddPDNObstructions / Odb.AddRoutingObstructions · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathyesoutput .odb
--configpathnoJSON with OBSTRUCTIONS (default: no-op)

Config keys

  • OBSTRUCTIONS (array)
    • layer (string) — tech layer name, e.g. met1
    • llx (integer) — lower-left x (DBU)
    • lly (integer) — lower-left y (DBU)
    • urx (integer) — upper-right x (DBU)
    • ury (integer) — upper-right y (DBU)

write-verilog-header

Emit a Verilog module header (ports + directions) from a .odb (read-only).

LibreLane step: Odb.WriteVerilogHeader · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathnowrite here instead of stdout

Output: Verilog module header text

report-wire-length

Report the total routed wire length (DBU) as JSON (read-only).

LibreLane step: Odb.ReportWireLength · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design

Output: JSON { total_wire_length_dbu } on stdout

read-3dblox

read a 3Dblox 2.5D/3D assembly description into an OpenDB database

Maturity: experimental

read-3dblox --input {input} --output {output}
InputTypeRequiredDescription
inputstringyes3Dblox assembly file (.3dbx)
outputstringyesdatabase to write
intostringnostart from this database instead of an empty one

Artifacts: odb (output)

Provenance limitations

  • Nested instance paths and virtual bonds (bot: ~) are read and reported as unrepresented.
  • Polygonal regions collapse to their bounding rectangle; each loss is reported by name.
  • One technology per database: a stack whose dies use different processes cannot be fully represented.

view-3dblox

draw a chiplet assembly as SVG or PNG: cross-section, plan, linter findings, and an optional die-to-die misalignment heat map

Maturity: experimental

view-3dblox --input {input} --output {output}
InputTypeRequiredDescription
inputstringyes3Dblox assembly (.3dbx) or database (.odb)
outputstringyesfile to write; .svg or .png picks the format
topstringnotop chip name; required for .odb input
scalenumbernoPNG device pixels per drawing unit; ignored for SVG

Artifacts: drawing (output)

Provenance limitations

  • The Z axis is exaggerated so the stack is legible; the factor is printed on the drawing and dimensions must not be measured off it.
  • Geometry only: no routing, no bumps drawn individually, no per-die layer stack.
  • –heatmap shows MEASURED die-to-die misalignment, not predicted yield. Yield needs process inputs (particle density, Cu recess, surface roughness) that no layout carries; this is the layout-side input such a model consumes.
  • –heatmap needs a .3dbx input with bump maps on both mating faces; without them the drawing is produced without a map and a note is written to stderr.
  • Heat-map samples are drawn at a legible minimum size, so a dense bump field merges into regions rather than resolving individual bumps.
  • A .odb input needs –top because the database has no top-chip getter.

check-d2d

check a die-to-die interface from two bump maps: unmated bumps, misalignment, net and cell mismatch

Maturity: experimental

check-d2d --input {input}
InputTypeRequiredDescription
inputstringno3Dblox assembly (.3dbx) — checks every bonded pair, deriving each die’s frame from its placement
topstringnobump map of the upper die (.bmap)
bottomstringnobump map of the lower die (.bmap)
offset_xnumbernoshift the bottom map, microns
offset_ynumbernoshift the bottom map, microns
flip_xbooleannomirror the bottom map in X (face-to-face bonding)
tolerancenumbernomatch radius in microns; default is half the bump pitch

Output: JSON on stdout. Two shapes: with –input, { interfaces: […], interfaces_checked, interfaces_skipped, violations }; with –top/–bottom, one interface object directly. An interface carries { violations, by_kind, top_bumps, bottom_bumps, matched, tolerance_um, tolerance_source, frame, transform, findings, parse_errors }. Every finding is DATA, not only prose: { kind, message, x_um, y_um } always, plus distance_um and signed dx_um/dy_um for ‘misaligned’, and top/bottom bump objects { inst, cell, x_um, y_um, port, net } for every paired kind. Exits non-zero when violations > 0.

Provenance limitations

  • With –input the frame comes from the assembly. In the two-file form the relative placement is NOT inferred — pass –offset-x/–offset-y/–flip-x. Either way the frame used is echoed in the report.
  • A bonded pair whose regions declare no bmap is listed under interfaces_skipped, not counted as clean.
  • Compares bump maps, not extracted layout: it checks what the maps claim, not what was fabricated.
  • Default tolerance is half the smaller bump pitch, derived from the maps; –tolerance overrides.

check-3d-nets

check net continuity across a whole chiplet stack: a net a die cannot carry from one face to the other, and nets the bonding shorts together

Maturity: experimental

check-3d-nets --input {input}
InputTypeRequiredDescription
inputstringyes3Dblox assembly (.3dbx)
tolerancenumbernobump match radius in microns; default is half the bump pitch, per bond
no_tsv_inferencebooleannodo not join a TSV die’s two faces by matching net name

Output: JSON { violations, by_kind, nets, bumps, groups, unnetted_bumps, net_source, tsv_inference, interfaces_checked, bonds, interfaces_skipped, regions_skipped, findings, parse_errors } on stdout. Finding kinds: severed and net-merged are violations; unresolved and tsv-unused are informational. Exits non-zero when violations > 0.

Provenance limitations

  • Net names come from the .bmap files the assembly points at, not from a netlist or a loaded database — the report always states net_source.
  • A netName belongs to its own die’s netlist, so net identity comes from the graph (same name within one die, plus whatever the bonding mates) and never from name equality across unbonded dies. Anything needing an assembly netlist is declined rather than guessed.
  • A through-path inside a TSV die is inferred from net names matching across the die’s two faces. 3Dblox and odb’s 3D chip schema carry only a per-die tsv boolean, no TSV positions; odb can hold TSV shapes on a dbTechLayer of LEF58 type TSV/TSVMETAL, but that is the LEF_file/DEF_file leg this reader does not read. –no-tsv-inference turns the inference off.
  • A bond whose surfaces declare no bmap, a virtual bond, and a nested instance path are listed under interfaces_skipped, not counted as clean.
  • Read-only: it never modifies the assembly or any database.

check-3dblox

3D/chiplet structural sign-off lint over a multi-die assembly: logical connectivity, floating chips, overlapping dies, unused internal_ext regions, connection-region overlap and mating-surface gap vs connection thickness, bump alignment, and alignment markers. Read-only: reports violations as markers, never modifies the design.

Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design

Output: JSON { violations, categories: [{ category, count, markers: [{ name, comment }] }] } on stdout; exit 0 regardless of findings

apply-eco-plan

Replay a timing-repair ECO plan (vyges-eco-plan-v1, as emitted by vyges-sta-si) into the design. All-or-nothing: any failing fix rolls the whole plan back. Does NOT legalize — run detailed placement, re-extract parasitics and re-time afterwards.

Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--planpathyesECO plan JSON (vyges-eco-plan-v1)
--outputpathyesoutput .odb
--any-designboolnoskip the plan/design name check

Output: JSON { applied, inserted: [names] } on stdout

report-connectivity

Dump the netlist connectivity graph (per-net sig-type, special flag, and pins) as JSON, highest-degree net first (read-only).

Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design

Output: JSON array of { net, sig_type, special, iterms, bterms, degree } on stdout

custom-io-placement

Place I/O port pins at fixed locations/layers in a .odb (database surgery).

LibreLane step: Odb.CustomIOPlacement · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathyesoutput .odb
--configpathnoJSON with CUSTOM_IO_PLACEMENT (default: no-op)

Config keys

  • CUSTOM_IO_PLACEMENT (array)
    • port (string) — port (bterm) name
    • layer (string) — tech layer, e.g. met3
    • llx (integer) — lower-left x (DBU)
    • lly (integer) — lower-left y (DBU)
    • urx (integer) — upper-right x (DBU)
    • ury (integer) — upper-right y (DBU)

write-def

Export a placed design to a DEF 5.8 file (libodb v1 LEF/DEF I/O).

LibreLane step: odb write_def · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathyesoutput .def file

read-def

Import a DEF into an existing design (its tech/libs) — libodb v1 LEF/DEF I/O.

LibreLane step: odb read_def · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb (provides tech + libs)
--defpathyesDEF file to import
--outputpathyesoutput .odb

import

Build a design database from LEF plus a DEF or a structural Verilog netlist, with no OpenROAD in the loop.

apply-def-template

Apply a template DEF’s floorplan (DIEAREA/TRACKS/ROWS/COMPONENTS/PINS) to a design.

LibreLane step: Odb.ApplyDEFTemplate · Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--templatepathyestemplate DEF (floorplan)
--outputpathyesoutput .odb

fields

List the generated instrumentation fields (class, field, value/keys) for discovery.

Unix only

ArgumentTypeRequiredDescription
--classstringnorestrict to one dbClass
--writableboolnolist settable fields (needs gen-write)

Output: JSON array of { class, field, value|values, keys } on stdout

get

Read any generated field by (class, field) with string-encoded addressing keys.

Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--classstringyesdbClass, e.g. dbInst
--fieldstringyesfield name, e.g. get_orient
--keystringnoaddressing key (repeatable, in order)

Output: the field value as JSON on stdout

set

Apply a generated setter by (class, field). Requires a –features gen-write build (L2/write).

Unix only

ArgumentTypeRequiredDescription
--inputpathyesinput .odb design
--outputpathyesoutput .odb
--classstringyesdbClass
--fieldstringyessetter field, e.g. set_weight
--keystringnoaddressing key (repeatable, in order)
--valuestringnovalue to set (repeatable, in order)

Output: writes the edited .odb; a one-line confirmation on stderr

No published contract: info, check-antenna-properties, place-diodes, remove-obstructions — run vyges opendb <step> --help for these.

vyges-char — standard-cell characterization

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom char. It’s also a standalone vyges-char binary on your PATH (the integration contract for flow authors).

vyges-char generates a Liberty (.lib) timing & power library from SPICE models, orchestrating the SPICE runs in parallel from one declarative job file.

Run it

vyges install loom                            # one-time: fetch the Loom suite
vyges loom char demo                          # instant sample .lib (no sim)
vyges loom char run     inv.char -o inv.lib   # one cell → Liberty
vyges loom char library cells.charlib -o out/ # many cells, in parallel → merged .lib
vyges loom char check   inv.char              # validate the job

See the full CLI reference (generated from --help).

Where it sits

.char job in → .lib out. That .lib feeds vyges-sta-si, and its per-switch energy feeds vyges-em-ir. See the data spine.

Domain coverage

vyges-char produces Liberty standard-cell timing & power models — NLDM / CCS tables, timing arcs, slew × load delay surfaces — for a library of fixed-footprint logic cells. It is a digital sign-off input and does not apply to analog / mixed-signal blocks, whose behavior has no standard-cell or Liberty-arc analogue. For analog / MS physical & integrity verification, see lvs (connectivity), layout (geometry), em-ir (IR / EM), thermal, and extract (RC).

Source & releases

vyges loom char — CLI reference

Generated from vyges loom char --help — this page is the tool’s own output, verbatim.

vyges loom char — standard-cell timing characterization (SPICE -> Liberty)

usage:
  vyges loom char run     JOB      [-o OUT] [--json] [--jobs N]
                                   [--sparse RxC [--verify K]] | [--auto [--target PCT]]
                                                       characterize one cell. --sparse: simulate a
                                                       coarse RxC grid, surrogate-fill the rest.
                                                       --auto: self-tuning — keep sampling the
                                                       biggest gap until CV error <= target, fill.
                                                       --jobs: parallelize the per-point sweep.
  vyges loom char library MANIFEST  [-o DIR]           characterize many cells (parallel) -> merged .lib
  vyges loom char dataset [JOB]    [-o OUT] [--format csv|jsonl] [--clean]
                                                       flatten characterization to a tidy
                                                       training table (no JOB = offline demo)
  vyges loom char surrogate [JOB]  [--degree D] [--metric M] [--log] [--json]
                                                       fit a CPU surrogate on a grid subset,
                                                       report held-out error (no JOB = demo)
  vyges loom char check   JOB
  vyges loom char demo    [-o OUT] [--json]

flags:
  -o FILE          write output to FILE (default: stdout)
  --json           characterization summary as JSON instead of Liberty
  --format FMT     dataset format: csv (default) or jsonl
  --clean          dataset: drop flagged (non-physical, e.g. negative-delay) rows
  --degree D       surrogate polynomial degree per axis (default 2)
  --metric M       surrogate: restrict to one metric (e.g. cell_rise)
  --log            surrogate: fit in log-log space (NLDM grids are log-spaced)
  --sparse RxC     run: simulate only a coarse RxC grid, surrogate-fill the dense .lib
  --verify K       run --sparse: re-simulate K un-fitted points, report the real error
  --jobs N         run: parallelize the per-point ngspice sweep across N threads (N=auto: all cores)
  --auto           run: self-tuning active sampling to a target accuracy, then surrogate-fill
  --target PCT     run --auto: stop when LOO-CV error <= PCT% of peak (default 2.0)
  --max-points N   run --auto: cap simulated points (default: the full grid)
  --seed RxC       run --auto: initial seed grid (default 3x3)
  -q, --quiet      suppress non-essential output
  -v, --verbose    extra detail on stderr
  --describe       print a machine-readable JSON description of the command
  -h, --help       show this help
  -V, --version    show version
  --bug-report     file a bug (central: vyges/community)
  --feature-request request a feature (central)
  --sponsor        sponsor Vyges (github.com/sponsors/vyges-ip)
  --star           star this tool on GitHub ⭐

Contract

Generated from vyges loom char --describe.

char

standard-cell timing characterization (SPICE -> Liberty)

Maturity: structured

run {job}
InputTypeRequiredDescription
jobstringyespath to the characterization job file (JOB)
outstringnowrite output to FILE instead of stdout
jobsstringnoparallelize the per-point ngspice sweep across N threads (N or ‘auto’)
sparsestringnosimulate only a coarse RxC grid, surrogate-fill the dense .lib
verifystringnowith –sparse: re-simulate K un-fitted points, report the real error
autobooleannoself-tuning active sampling to a target accuracy, then surrogate-fill
targetstringnowith –auto: stop when LOO-CV error <= PCT% of peak (default 2.0)
max_pointsstringnowith –auto: cap simulated points (default: the full grid)
seedstringnowith –auto: initial seed grid (default 3x3)
degreestringnosurrogate polynomial degree per axis, used with –sparse or –auto (default 2)

Consumes: spice

Artifacts: liberty (out)

Assertion: characterization — none; this operation establishes no engineering claim

Provenance limitations

  • The job names the SPICE decks; input_hash covers the job path and arguments, not their contents, and .include chains are not followed.

vyges-extract — parasitic extraction

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom extract. It’s also a standalone vyges-extract binary on your PATH (the integration contract for flow authors).

vyges-extract reads a routed layout and the PDK’s parasitic rules and produces SPEF — the resistance and capacitance of every net — so timing and power analysis reason about the real interconnect, not an idealized one.

Run it

vyges install loom                                 # one-time: fetch the Loom suite
vyges loom extract demo                            # instant sample SPEF (no setup)
vyges loom extract run  design.ext -o design.spef  # extract RC → SPEF
vyges loom extract run  design.ext --json          # per-net R/C summary
vyges loom extract check design.ext                # validate the job

See the full CLI reference (generated from --help).

Where it sits

.ext job in → .spef out, read directly by vyges-sta-si. Extraction accuracy is governed by the PDK’s parasitic rules — the engine is open, the calibration is foundry-specific. See the data spine.

Digital and analog / mixed-signal

The RC math is geometry × rules only — area / fringe capacitance and sheet / via resistance off the routed shapes, with no standard-cell or clocked-netlist assumption. So an analog / mixed-signal routed layout extracts exactly as a digital one does. Supplied as DEF it extracts today (the new examples/bias_gen/ is an analog bias-generator block); for GDS-only analog a new connectivity-tracing adapter (src/gds.rs) builds the DefNet graph straight from geometry, sitting above the unchanged RC core.

vyges loom extract run examples/bias_gen/bias_gen.ext -o bias_gen.spef   # analog bias-gen layout → SPEF

Honest bounds, the same for analog and digital: shapes are treated as axis-aligned rectangles, a via is a layer overlap, and pins lump into the net’s SPEF node. Scope here is physical parasitic extraction; analog functional / timing sign-off is out of scope (that leans on external SPICE / behavioral tools).

Source & releases

vyges loom extract — CLI reference

Generated from vyges loom extract --help — this page is the tool’s own output, verbatim.

vyges loom extract — foundry-correlated RC parasitic extraction (DEF -> SPEF)

usage:
  vyges loom extract run    JOB [-o OUT] [--json] [--pdk NAME | --tech-lef PATH] [--refresh]
                                [--captable PATH | --allow-incomplete-rc]
                                [--cell-lef CL --lib LIB]   # std-cell *CONN hookup
  vyges loom extract gen-rc (--pdk NAME | --tech-lef PATH) [--refresh]
  vyges loom extract check  JOB
  vyges loom extract demo   [-o OUT] [--json]
  vyges loom extract klayout2spef --gds F --layermap M [--top CELL] [-o OUT] [--geom-out G]
                                  [--def D --cell-lef CL --lib LIB]
                                  [--runner podman [--image REF] [--mount DIR] | --python CMD]
                                  [--routing-only] [--from-dump F] [--self-test]

klayout2spef drives a headless KLayout (LayoutToNetlist) over a GDS to write SPEF +
an EM geometry sidecar (per-segment layer/width/length) for current-density sign-off.
--runner podman wraps the driver in the vyges-klayout container; --self-test and
--from-dump run the parse→SPEF pipeline offline (no KLayout).

RC rules come from a job's `rules:`, or are DERIVED from the PDK tech LEF via
--pdk / --tech-lef (the metal stack is discovered, not hand-listed) and cached
as vyges-additions/<pdk>/vyges-extract-rc.rules (regenerate with --refresh). When
a tech LEF is geometry-only (no R/C), an OpenRCX captable supplies the numbers;
`run` refuses zero-R/C rules rather than report parasitics that are all zero.

flags:
  --pdk NAME       derive RC rules from the PDK tech LEF (resolved via pdk-store)
  --tech-lef PATH  derive RC rules from this tech LEF directly
  --captable PATH  OpenRCX rules file for R/C the LEF lacks (else pdk-store captable)
  --refresh        re-derive the cached RC rules
  --allow-incomplete-rc  extract even when a layer has no R/C (understates parasitics)
  -o FILE          write output to FILE (default: stdout)
  --json           per-net parasitics summary as JSON instead of SPEF
  -q, --quiet      suppress non-essential output
  -v, --verbose    extra detail on stderr
  -j, --threads N  parallel worker threads (default: all cores; 1 = serial)
  --describe       print a machine-readable JSON description of the command
  -h, --help       show this help
  -V, --version    show version
  --bug-report     file a bug (central: vyges/community)
  --feature-request request a feature (central)
  --sponsor        sponsor Vyges (github.com/sponsors/vyges-ip)
  --star           star this tool on GitHub ⭐

Contract

Generated from vyges loom extract --describe.

extract

foundry-correlated RC parasitic extraction (DEF -> SPEF)

Maturity: workflow-validated

run {job}
InputTypeRequiredDescription
jobstringyespath to the extract job file (design, def, rules, corner, temp)
outstringnowrite the SPEF to FILE instead of stdout

Consumes: def, gds

Artifacts: spef (out)

Assertion: parasitic-extract — none; this operation establishes no engineering claim

Provenance limitations

  • The job names the DEF/LEF and the rules or captable; input_hash covers the job path and arguments, not their contents.

vyges-power — power analysis

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom power. It’s also a standalone vyges-power binary on your PATH (the integration contract for flow authors).

vyges-power computes a design’s power — leakage, internal, and switching — from a gate-level netlist and the Liberty models, using either a measured activity file (VCD) or a vectorless probabilistic toggle factor. It also emits the per-instance activity / current map that vyges-em-ir consumes, so IR-drop is solved from real per-instance current — closing the char → power → em-ir loop.

Run it

vyges install loom                              # one-time: fetch the Loom suite
vyges loom power demo                            # instant: built-in design → power report
vyges loom power run  block.pwr                  # leakage + internal + switching report
vyges loom power run  block.pwr --fail-on-budget # exit 3 over the job's power budget (CI gate)
vyges loom power check block.pwr                 # validate the job

See the full CLI reference (generated from --help).

Where it sits

Netlist + .lib + activity (VCD or vectorless) → a power report, plus the activity map em-ir reads. It can also read extracted wire caps (.spef) from vyges-extract for switching power. Designed to gate CI: over-budget returns a distinct non-zero exit code. See the data spine.

Domain coverage

vyges-power sums per-cell Liberty leakage and internal energy weighted by toggle activity over a gate-level netlist — it is digital sign-off. It does not apply to analog / mixed-signal blocks, whose power has no standard-cell or Liberty analogue (an analog block’s operating-point current is an input to em-ir, not something this engine computes). For analog / MS physical & integrity verification, see lvs (connectivity), layout (geometry), em-ir (IR / EM), thermal, and extract (RC).

Source & releases

vyges loom power — CLI reference

Generated from vyges loom power --help — this page is the tool’s own output, verbatim.

vyges loom power — gate-level power analysis (leakage + dynamic) with a CI gate

usage:
  vyges loom power run   JOB [-o OUT] [--json] [--fail-on-budget]
  vyges loom power check JOB
  vyges loom power demo       [-o OUT] [--json]

A JOB is a small declarative `.pwr` file (netlist + lib(s) + clock + activity).
With `vcd:` or `saif:` it uses measured per-net toggle rates; otherwise a
vectorless `activity:` factor × clock. With `emit_activity:` it writes the
per-instance map that vyges-em-ir consumes (closing char -> power -> em-ir).

With `activity_sweep: <from> <to> <window> [<step>]` (VCD only; `to` may be
`end`) the same run reports power over the workload — one row per window, from
a single parse of the dump — names the PEAK window, and hands em-ir the peak's
activity map rather than the dump average. `emit_power_vcd:` then writes that
curve as a copy of the dump, viewable beside the workload in any VCD viewer.

flags:
  -o FILE             write the report to FILE (default: stdout)
  --json              machine-readable JSON instead of the text report
  --fail-on-budget    exit 3 if total power exceeds the job's power_budget_mw
  -q, --quiet         suppress non-essential output
  -v, --verbose       extra detail on stderr
  --describe          print a machine-readable JSON description of the command
  -h, --help          show this help
  -V, --version       show version
  --bug-report        file a bug (central: vyges/community)
  --feature-request   request a feature (central)
  --sponsor           sponsor Vyges (github.com/sponsors/vyges-ip)
  --star              star this tool on GitHub ⭐

Contract

Generated from vyges loom power --describe.

power

gate-level power analysis (leakage + dynamic), per-group and over time, with a CI gate

Maturity: workflow-validated

run {job}
InputTypeRequiredDescription
jobstringyespath to the .pwr job file (netlist + lib(s) + clock + activity; an activity_sweep: key reports power per window over the workload and gates on the peak)
outstringnopath to write the report to (default: stdout)

Consumes: netlist, liberty, vcd

Artifacts: power_report (out)

Assertion: power-analysis — none; this operation establishes no engineering claim

Provenance limitations

  • The job names the netlist, Liberty and any VCD/SAIF activity; input_hash covers the job path and arguments, not their contents.

vyges-sta-si — timing with signal integrity

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom sta-si. It’s also a standalone vyges-sta-si binary on your PATH (the integration contract for flow authors).

vyges-sta-si checks whether a design meets timing — reporting worst negative slack (WNS), total negative slack (TNS), and the critical path — while accounting for signal-integrity coupling between nets. It reads the .lib from vyges-char and the .spef from vyges-extract.

Run it

vyges install loom                                  # one-time: fetch the Loom suite
vyges loom sta-si demo                              # instant: built-in design → WNS/TNS
vyges loom sta-si run  top.sta -o top.rpt           # analyze → timing report
vyges loom sta-si run  top.sta --fail-on-violation  # exit 3 if WNS < 0 (CI gate)
vyges loom sta-si check top.sta                      # validate the job

See the full CLI reference (generated from --help).

Where it sits

.lib + .spef + design & constraints → WNS / TNS / worst path. Designed to gate CI: a timing violation returns a distinct non-zero exit code. See the data spine.

Domain coverage

vyges-sta-si builds a timing graph over Liberty cell arcs and propagates slews and arrivals through a clocked gate-level netlist — it is digital sign-off. It does not apply to analog / mixed-signal blocks, whose timing and behavior have no standard-cell or Liberty-arc analogue. For analog / MS physical & integrity verification, see lvs (connectivity), layout (geometry), em-ir (IR / EM), thermal, and extract (RC).

Source & releases

vyges loom sta-si — CLI reference

Generated from vyges loom sta-si --help — this page is the tool’s own output, verbatim.

vyges loom sta-si — sign-off static timing analysis with signal integrity

usage:
  vyges loom sta-si run      JOB    [-o OUT] [--json] [--fail-on-violation] [--sdf FILE]
  vyges loom sta-si sdc-lint JOB    [-o OUT] [--json] [--fail-on-violation]
                                    (a job with `metadata: vyges-metadata.json` also checks the
                                     SDC against the IP's declared clock_domains)
  vyges loom sta-si check    JOB
  vyges loom sta-si demo            [-o OUT] [--json]
  vyges loom sta-si tcl      SCRIPT [-o OUT] [--json] [--fail-on-violation]   (experimental)

`sdc-lint` checks the SDC for completeness/consistency (unconstrained I/O, a clock with
no period, duplicate clocks, a clock on a port the design lacks) — independent of timing.

`tcl` runs an OpenSTA-style TCL *subset* (read_liberty/verilog/spef/sdc + inline SDC +
report_checks/report_wns/report_tns) through the Vyges engine — EXPERIMENTAL; not a TCL
interpreter and not a drop-in for LibreLane's corner.tcl. See docs/opensta-integration.md.

flags:
  -o FILE               write output to FILE (default: stdout)
  --json                machine-readable JSON instead of the text report
  --fail-on-violation   exit 3 if WNS < 0 (CI timing gate)
  --pdk NAME           resolve liberty from pdk-store (lib) when the job has none
  --corner C           PDK corner for --pdk (default: the PDK's default corner)
  --liberty-nldm-only   skip CCS (receiver_capacitance + output_current) at Liberty
                        load — faster/smaller for NLDM-only runs; forces the NLDM delay path
  --emit-liberty-json FILE  dump the merged Liberty IR (the shared model the timer +
                        vyges-power consume) as JSON for inspection / MCP, then run
  --sdf FILE            also write an SDF back-annotation file (IOPATH + setup/hold,
                        + INTERCONNECT from SPEF) — feeds gate-level / back-annotated sim
  -q, --quiet           suppress non-essential output
  -v, --verbose         extra detail on stderr
  --describe            print a machine-readable JSON description of the command
  -h, --help            show this help
  -V, --version         show version
  --bug-report     file a bug (central: vyges/community)
  --feature-request request a feature (central)
  --sponsor        sponsor Vyges (github.com/sponsors/vyges-ip)
  --star           star this tool on GitHub ⭐

Contract

Generated from vyges loom sta-si --describe.

sta-si

static timing analysis with signal integrity (job → report)

Maturity: workflow-validated

run {job}
InputTypeRequiredDescription
jobstringyesthe timing job file
sdfstringnooptional SDF delays file
outstringnowrite the report to FILE instead of stdout

Consumes: netlist, liberty, spef

Artifacts: timing_report (report_path), sdf (sdf)

Assertion: timing-met — passes when timing_met is true

Provenance limitations

  • The job names the netlist, Liberty and SPEF; input_hash covers the job path and arguments, not their contents, and Liberty include files are not enumerated.

vyges-em-ir — power integrity

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom em-ir. It’s also a standalone vyges-em-ir binary on your PATH (the integration contract for flow authors).

vyges-em-ir takes the design’s power-distribution network (PDN) and solves it — reporting IR-drop (supply voltage lost across the grid) and checking electromigration (EM) current limits against a budget. Its dynamic solve is fed by the per-switch energy from vyges-char.

Run it

vyges install loom                                    # one-time: fetch the Loom suite
vyges loom em-ir demo                                 # instant: built-in PDN → IR/EM
vyges loom em-ir run  block.emir -o block.rpt         # analyze → report
vyges loom em-ir run  block.emir --fail-on-violation  # exit 3 if over budget (CI gate)
vyges loom em-ir check block.emir                      # validate the job

See the full CLI reference (generated from --help).

Where it sits

design + PDN + IR/EM budget → IR-drop map + EM check. Turns power integrity from a late, manual sign-off step into a deterministic, CI-gated check. See the data spine.

Digital and analog / mixed-signal

The solve is G·V = I on a generic conductance matrix — there is no standard-cell or Liberty assumption in the physics, only resistors, pads and per-node currents. Besides the DEF path, the engine reads a generic .pdn resistor-network description (used when def: is empty), so a hand-built analog / mixed-signal supply mesh solves exactly as a digital PDN does. The examples/analog_bias/ job is a .pdn mesh with pads and analog op-point load currents standing in for the per-instance digital activity.

vyges loom em-ir run examples/analog_bias/analog_bias.emir   # analog bias mesh → IR-drop + EM

Power-integrity (IR / EM) applies to any grid; the only difference is where the node currents come from — digital switching activity or an analog operating point. Scope here is physical power-integrity; analog functional / timing sign-off is out of scope (that leans on external SPICE / behavioral tools).

Source & releases

vyges loom em-ir — CLI reference

Generated from vyges loom em-ir --help — this page is the tool’s own output, verbatim.

vyges loom em-ir — EM / IR-drop power-integrity sign-off (PDN -> report)

usage:
  vyges loom em-ir run   JOB [-o OUT] [--json] [--fail-on-violation]
  vyges loom em-ir check JOB
  vyges loom em-ir demo  [-o OUT] [--json]
  vyges loom em-ir em-density (--geom G | --spef S) --lef TECH.lef
                              [--current-map C | --net-current mA] [--fail-on-violation]

em-density is EM sign-off on *extracted SPEF*: it screens each metal segment's
current density (from the loom EM geom sidecar: layer + width) against the tech
LEF DCCURRENTDENSITY (and AC RMS/PEAK when the current map supplies them).

flags:
  -o FILE               write output to FILE (default: stdout)
  --json                machine-readable JSON instead of the text report
  --fail-on-violation   exit 3 if IR drop exceeds the limit or any EM segment fails
  -q, --quiet           suppress non-essential output
  -v, --verbose         extra detail on stderr
  --describe            print a machine-readable JSON description of the command
  -h, --help            show this help
  -V, --version         show version
  --bug-report     file a bug (central: vyges/community)
  --feature-request request a feature (central)
  --sponsor        sponsor Vyges (github.com/sponsors/vyges-ip)
  --star           star this tool on GitHub ⭐

Contract

Generated from vyges loom em-ir --describe.

em-ir

EM / IR-drop power-integrity sign-off (PDN -> report)

Maturity: workflow-validated

run {job}
InputTypeRequiredDescription
jobstringyesPath to the EM/IR-drop job file (PDN + limits).
outstringnoWrite output to FILE instead of stdout.

Consumes: pdn, power_report, spef, emgeom

Artifacts: emir_report (report_path)

Assertion: power-integrity-met — passes when pi_met is true

Provenance limitations

  • The job names the PDN or DEF/LEF and any power and decap maps; input_hash covers the job path and arguments, not their contents.
  • Solved on a real block: a routed sky130 PDN extracting to 5308 nodes solves in 0.46 s. The solver is conjugate gradient with a Jacobi preconditioner; it was Gauss-Seidel, which on that same block stopped short of tolerance after 50000 sweeps and returned an error rather than a result.
  • Partially correlated against OpenROAD PDNSim on one routed sky130 block, at the level of the extracted network rather than the solved voltages (the solver above does not get that far). Per-layer total resistance against PDNSim’s own network: met1 1.003x, met4 1.005x, met5 1.013x, and vias exact at 1635.725 ohm over 1895 vias against 1635.7249999999501. No voltage or IR-drop figure has been correlated against any other tool.
  • Wire resistance (rho_sq * L/W) and the per-square resistance itself agree with PDNSim’s model under a default-RC flow; a flow that sets custom layer RC moves PDNSim’s resistance and not ours, and neither tool reports the divergence.
  • Via resistance is the cut layer’s per-cut LEF RESISTANCE divided by the cut count taken from the DEF VIAS definition. Keyed on the layer pair as well as the point, because a PDN via stack places several definitions at one coordinate.
  • Voltage sources are the power pin’s port shapes where the design declares them, falling back to every pad_layer node only when it does not – PDNSim’s own precedence. On the correlated block the declared pin covers 683 grid nodes where the fallback holds 28, and it moves the answer by 4.1x: worst drop 0.27% under the fallback against 0.06% under the declared pin. Which way that runs is DESIGN-DEPENDENT, turning on how much of the grid a pin covers, so it is measured per design rather than assumed.
  • Correlated against OpenROAD PDNSim across SIX routed sky130 blocks, fed the same per-instance currents and compared against a PDNSim run from the same build: worst-IR-drop ratios 1.019, 1.001, 0.994, 0.997, 1.018 and 1.015 – all six within 1.9%, spanning 35 uA to 1.16 mA and 2500 to 19700 grid nodes. PDNSim’s values carry two to three significant figures at these magnitudes, so this is near the floor the comparison resolves.
  • Instance current enters the rail at the CELL CENTRE when a cell_lef supplies MACRO SIZE, else at the DEF origin. Both were measured against PDNSim: landing current on the nearest pre-existing grid node under-reported worst IR drop by 3.2x, and using the DEF origin rather than the cell centre displaced every load by half a cell width, worth 8.4% on a block of wide cells and invisible on a block of small ones. Supply a cell_lef for wide-cell designs.
  • Only the WORST node is comparable between the two engines: PDNSim’s voltage file reports one row per INSTANCE TERMINAL, this engine reports one row per GRID NODE, and those sample the same field at different places and in different proportions. On one block PDNSim’s median drop is exactly 0.0, because most of its rows sit on the filler and decap cells packed against the supply straps, and the p75 ratio reads 11.3. So percentile-to-percentile comparison is not like-for-like at any percentile; an earlier version of this descriptor read a 4-8% one-sided residual out of exactly such a comparison, and that is withdrawn as an artefact.
  • Precision bound: PDNSim’s voltage file prints six decimals, so at these magnitudes 1 uV quantisation is a few tenths of a percent even at the worst node. Agreement is to the precision the oracle publishes.
  • The oracle is REGENERATED per run from the same binary. Archived LibreLane net-*.csv voltage files are not a safe baseline: on three of six blocks a fresh PDNSim run on the same .odb disagreed with the archived one by 1.32x, 1.37x and 10x. The builds differ (archived reports lack the Total power line a current build prints); the cause of the disagreement is not established.
  • Instance current enters at a tap point on the rail: the instance’s placement projected onto its nearest rail segment, which is split there. Landing current on the nearest PRE-EXISTING node instead under-reported worst IR drop by 3.2x, because the current never crossed the rail resistance between the cell and that node. Projection is axis-aligned only; an instance that cannot be projected falls back to the nearest node and is counted.
  • Node counts are not comparable with PDNSim by construction: it resamples nodes on a minimum pitch, this engine places one per polyline point.
  • Dynamic (transient) IR has NO oracle: PDNSim is static-only, so nothing exists to correlate it against. It is checked instead against exact analytic cases (with no decap the solve is exactly quasi-static, peak = ipk*R to 1e-9) and construction invariants (linear in switch energy, coincident switches superpose, decap monotonically removes droop), all mutation-checked. Runs at scale: 13292 nodes in 22 s, 248 MB.
  • Transient limits, which matter more than its accuracy: every instance switches at ONE global switch_t_ns, so the result is worst-case-simultaneous switching – a strict upper bound, not a waveform; the timestep is implicit at min(switch duration)/10 and cannot be set, so accuracy cannot be traded for runtime and convergence cannot be demonstrated; and only the worst droop is reported, with no waveform exposed.
  • EM: PDNSim reports per-segment current but applies no current-density limit and issues no verdict, so only the numerator can be correlated. Maximum segment current per layer, which is what a limit is compared against, across three routed sky130 blocks: met1 1.022/1.026/1.035, via 1.008/1.016/0.992. On the ~10% of segments with an exact geometric counterpart, restricted to those carrying at least 1% of peak current, 92-99% agree within 10% (median 0.997-1.003). The DC/RMS/peak LIMIT check has no counterpart and remains this engine’s own.

vyges-thermal — on-chip thermal

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom thermal. It’s also a standalone vyges-thermal binary on your PATH (the integration contract for flow authors).

vyges-thermal lands the design’s power on the die as heat and solves the steady state — reporting the peak temperature, the hotspot location, per-block temperatures, and a PASS/FAIL against a temperature limit. It is the thermal dual of vyges-em-ir: the same grid solve, temperature where em-ir has voltage. The per-block power comes from vyges-power; with temperature-dependent leakage it runs the electro-thermal loop to a fixed point (leakage heats the die, the hotter die leaks more), so you get the real operating temperature — closing char → power → em-ir → thermal → power.

Run it

vyges install loom                                       # one-time: fetch the Loom suite
vyges loom thermal demo                                  # instant: built-in floorplan → temp + heat map
vyges loom thermal run  block.thermal -o block.rpt       # analyze → report
vyges loom thermal run  block.thermal --fail-on-violation # exit 3 if over the limit (CI gate)
vyges loom thermal check block.thermal                    # validate the job

Job file

A .thermal job declares the die, the solver grid, the material parameters (k_si, thickness_um, theta_ja), the ambient, and the temperature limit; a floorplan: (.flp) places the blocks and their power (with an optional per-block leakage portion for the coupling). Standard CLI: --json, --quiet, --verbose — and like the rest of the suite it fails CI (exit 3) when the peak exceeds t_limit_c.

See the CLI reference for the full flag list, and Job-file formats for the .thermal / .flp shapes.

Digital and analog / mixed-signal

The solve is G_th·ΔT = P on a tile grid built from a generic .flp block list — each block is just name x y w h power, with no notion of where the power came from. It is fully decoupled from the power source, so an analog / mixed-signal floorplan lands its heat and solves exactly as a digital one does. The examples/pa/ job is an RF power-amplifier floorplan whose concentrated dissipation produces a classic analog PA hotspot.

vyges loom thermal run examples/pa/pa.thermal   # RF power-amplifier floorplan → temp + PA hotspot

Heat flow is heat flow; the block powers can come from digital activity or an analog op-point. Scope here is physical thermal analysis; analog functional / timing sign-off is out of scope (that leans on external SPICE / behavioral tools).

Correlation baseline: HotSpot (the canonical open on-chip thermal simulator) — there is no thermal tool inside OpenLane/OpenROAD, so vyges-thermal fills that gap.

vyges loom thermal — CLI reference

Generated from vyges loom thermal --help — this page is the tool’s own output, verbatim.

vyges loom thermal — steady-state on-chip thermal analysis (floorplan -> temperature)

usage:
  vyges loom thermal run   JOB [-o OUT] [--json] [--fail-on-violation]
  vyges loom thermal check JOB
  vyges loom thermal demo  [-o OUT] [--json]

A JOB is a small declarative `.thermal` file (die + grid + material params +
a `floorplan:` of blocks with placement and power). With per-block leakage and
`leak_alpha_per_c` it runs the electro-thermal coupling loop. The report gives
the peak temperature, the hotspot location, per-block temperatures, and a
PASS/FAIL against `t_limit_c`.

flags:
  -o FILE               write output to FILE (default: stdout)
  --json                machine-readable JSON instead of the text report
  --fail-on-violation   exit 3 if the peak temperature exceeds t_limit_c
  -q, --quiet           suppress non-essential output
  -v, --verbose         extra detail on stderr
  --describe            print a machine-readable JSON description of the command
  -h, --help            show this help
  -V, --version         show version
  --bug-report      file a bug (central: vyges/community)
  --feature-request request a feature (central)
  --sponsor         sponsor Vyges (github.com/sponsors/vyges-ip)
  --star            star this tool on GitHub ⭐

Contract

Generated from vyges loom thermal --describe.

thermal

steady-state on-chip thermal analysis (floorplan -> temperature)

Maturity: workflow-validated

run {job}
InputTypeRequiredDescription
jobstringyespath to a .thermal job file (die + grid + material params + a floorplan of blocks with placement and power)
outstringnowrite the report to FILE instead of stdout

Consumes: floorplan, power_report

Artifacts: thermal_report (report_path)

Assertion: thermal-within-limit — passes when pass is true

Provenance limitations

  • The job names the floorplan and per-block power; input_hash covers the job path and arguments, not their contents.

vyges-lvs — layout-vs-schematic

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom lvs. It’s also a standalone vyges-lvs binary on your PATH (the integration contract for flow authors).

vyges-lvs answers one question: does the layout implement the schematic? It matches the two netlists as graphs — name-independent colour-refinement — and returns a MATCH / MISMATCH verdict. When they diverge, it names the unmatched devices and nets, where the open incumbent (Netgen) prints a terse “do not match.” It can also extract the layout netlist straight from a GDS, so you don’t need a separate extractor in the loop.

Run it

vyges install loom                              # one-time: fetch the Loom suite
vyges loom lvs demo                              # instant: built-in netlist pair → verdict
vyges loom lvs run  block.lvs                    # MATCH / MISMATCH + diagnostics
vyges loom lvs run  block.lvs --fail-on-mismatch # exit 3 if not equivalent (CI gate)
vyges loom lvs extract block.gds --rules sky130.rules --top block   # native GDS → SPICE

See the full CLI reference (generated from --help).

Where it sits

Layout netlist (or GDS + a layer .rules deck) + the schematic → MATCH / MISMATCH. It’s the layout-correctness check at sign-off, beside the timing / power / PI engines. Designed to gate CI: a mismatch returns a distinct non-zero exit code. See the data spine.

Digital and analog / mixed-signal

The compare is device-kind-agnostic — it matches the netlist graph over the generic SPICE primitives M/Q/R/C/L/D/X, with no standard-cell, Liberty, or clocked-netlist assumption. So vyges loom lvs runs on analog and mixed-signal blocks exactly as it does on digital ones (the one device-specific rule, MOSFET source/drain symmetry, is what analog needs too).

vyges loom lvs run examples/inv_chain/match.lvs    # digital: standard-cell inverter chain
vyges loom lvs run examples/bandgap/match.lvs      # analog: bandgap (Q/R/C + PMOS mirror) → MATCH
vyges loom lvs run examples/bandgap/mismatch.lvs   # analog: mis-wired sense resistor → MISMATCH

The bandgap example exercises bipolar transistors, resistors and a capacitor — device kinds a digital LVS never sees. Scope here is physical connectivity (LVS); analog functional / timing sign-off is out of scope (that leans on external SPICE / behavioral tools).

Correlated against the golden tools

On real sky130, vyges-lvs is checked against the reference open tools: native GDS extraction is net-level identical to Magic on a standard cell (a full LVS MATCH, hvt included); on a placed-and-routed block (the counter through OpenLane, 229 cell instances) it reaches exact device parity with Magic — 842 transistors, 421 n / 421 p — in ~1.5 s; and on the verdict it agrees 3/3 with Netgen, while naming the unmatched classes. Detail lives in the repo’s correlation/.

Source & releases

vyges loom lvs — CLI reference

Generated from vyges loom lvs --help — this page is the tool’s own output, verbatim.

vyges loom lvs — layout-vs-schematic netlist comparison with clear divergence diagnostics

usage:
  vyges loom lvs run     JOB [-o OUT] [--json] [--fail-on-mismatch]
  vyges loom lvs extract GDS (--rules RULES | --pdk NAME) [--top CELL] [-o out.spice]
  vyges loom lvs check   JOB
  vyges loom lvs demo         [-o OUT] [--json]

A JOB is a small declarative `.lvs` file: the layout side as a SPICE netlist
(`layout:`) OR a GDS to extract natively (`layout_gds:` + `rules:`/`pdk:`), plus
the `schematic:` and an optional `top:`. The compare is name-independent (graph
colour-refinement); a mismatch reports the unmatched device/net classes.
`extract` runs native device extraction (GDS/OASIS -> SPICE) on its own.

Extraction rules come from `--rules`/`rules:` directly, or are resolved from a
PDK by `--pdk`/`pdk:` NAME via the installed pdk-store (its `lvs_device_rules`).

flags:
  --pdk NAME           resolve extraction rules from pdk-store (vs --rules)
  -o FILE              write the report to FILE (default: stdout)
  --json               machine-readable JSON instead of the text report
  --fail-on-mismatch   exit 3 if the netlists are not equivalent (CI gate)
  -q, --quiet          suppress non-essential output
  -v, --verbose        extra detail on stderr
  --describe           print a machine-readable JSON description of the command
  -h, --help           show this help
  -V, --version        show version
  --bug-report         file a bug (central: vyges/community)
  --feature-request    request a feature (central)
  --sponsor            sponsor Vyges (github.com/sponsors/vyges-ip)
  --star               star this tool on GitHub ⭐

Contract

Generated from vyges loom lvs --describe.

lvs

layout-vs-schematic comparison (job → report)

Maturity: workflow-validated

run {job}
InputTypeRequiredDescription
jobstringyesthe LVS job file
outstringnowrite the report to FILE instead of stdout

Consumes: gds, schematic

Artifacts: lvs_report (report_path)

Assertion: lvs-match — passes when lvs_met is true (summary from note)

Provenance limitations

  • The job names the layout, schematic and extraction rules; input_hash covers the job path and arguments, not their contents, and SPICE .include chains are not followed.

vyges-resize — gate sizing

Part of the Vyges Loom suite (optimizer). Install once with vyges install loom, then run vyges loom resize. It’s also a standalone vyges-resize binary on your PATH.

Where the sign-off engines say what’s wrong, the optimizers fix it. vyges-resize picks a better drive strength for each cell — upsizing cells on the critical path to close setup violations, downsizing cells with slack to recover area — then emits the resized netlist. The logic never changes; only the cell variant does, and every candidate is scored by the vyges-sta-si timer. Run it pre-place (ideal interconnect) or as a post-place ECO by naming a spef: so sizing is scored against the real wire RC.

Run it

vyges install loom                                    # one-time: fetch the Loom suite
vyges loom resize demo                                # instant: built-in example closes a violation
vyges loom resize run  top.resize -o sized.v          # size → resized netlist
vyges loom resize run  top.resize --fail-on-violation # exit 3 if still violating (CI gate)
vyges loom resize check top.resize                    # validate the job

A .resize job is a superset of a .sta job plus group: (interchangeable cell families, weakest → strongest), objective: (timing | area), effort:, and dont_touch:.

See the full CLI reference (generated from --help).

Where it sits

netlist + .lib + constraints (+ optional .spef) → resized netlist + before/after WNS/TNS. It decides sizes, not locations — placement/routing stay the flow’s job. See the data spine.

Domain coverage

vyges-resize swaps standard-cell drive strengths, scored by the digital vyges-sta-si timer — it is digital optimization. It does not apply to analog / mixed-signal blocks, which have no interchangeable standard-cell drive variants and no Liberty-arc timing to score against. For analog / MS physical & integrity verification, see lvs (connectivity), layout (geometry), em-ir (IR / EM), thermal, and extract (RC).

Source & releases

vyges loom resize — CLI reference

Generated from vyges loom resize --help — this page is the tool’s own output, verbatim.

vyges loom resize — STA-driven gate sizing (drive-strength resize / Vt-swap to close timing)

usage:
  vyges loom resize run   JOB  [-o OUT] [--json] [--fail-on-violation]   size a netlist -> resized netlist
  vyges loom resize check JOB                                            validate the job
  vyges loom resize demo                                                 size a built-in example (no files)

flags:
  -o FILE              write the resized netlist to FILE (default: stdout)
  --json               emit the before/after report as JSON
  --fail-on-violation  exit 3 if the result still has negative setup slack (CI gate)
  -q, --quiet          suppress non-essential output
  -v, --verbose        extra detail on stderr
  --describe           print a machine-readable JSON description of the command
  -h, --help           show this help
  -V, --version        show version
  --bug-report         file a bug (central: vyges/community)
  --feature-request    request a feature (central)
  --sponsor            sponsor Vyges (github.com/sponsors/vyges-ip)
  --star               star this tool on GitHub ⭐

Contract

Generated from vyges loom resize --describe.

resize

STA-driven gate sizing (drive-strength resize / Vt-swap to close timing)

Maturity: structured

run {job}
InputTypeRequiredDescription
jobstringyespath to the resize job file (design, netlist, lib, STA config, sizing config)
outstringnopath to write the resized netlist (default: stdout)

Consumes: netlist, liberty, timing_report

Artifacts: netlist (out)

Assertion: gate-sizing — none; this operation establishes no engineering claim

Provenance limitations

  • The job names the netlist, Liberty and timing report; input_hash covers the job path and arguments, not their contents.

vyges-vt-swap — threshold-voltage swapping

Part of the Vyges Loom suite (optimizer). Install once with vyges install loom, then run vyges loom vt-swap. It’s also a standalone vyges-vt-swap binary on your PATH.

The sibling of vyges-resize: same timer-scored cell-swap loop, but it trades threshold voltage (leakage/speed) instead of drive strength. A higher-Vt flavor of a gate is slower but leaks far less; a lower-Vt flavor is faster but leakier. Two objectives:

  • leakage (default) — on a timing-met design, push every cell with positive slack to the highest-Vt (lowest-leakage) flavor that still meets timing. Free leakage recovery.
  • timing — drop critical-path cells to a faster (lower-Vt) flavor to close setup.

It reports total cell leakage before and after (from the .lib’s cell_leakage_power) alongside the timing it preserved.

Run it

vyges install loom                                      # one-time: fetch the Loom suite
vyges loom vt-swap demo                                 # instant: built-in example recovers leakage
vyges loom vt-swap run  top.vtswap -o swapped.v         # swap → resized netlist
vyges loom vt-swap run  top.vtswap --json               # before/after timing + leakage
vyges loom vt-swap check top.vtswap                     # validate the job

A .vtswap job lists the iso-footprint Vt families fastest → slowest (low-Vt → high-Vt), plus objective: (leakage | timing), effort:, and dont_touch:.

See the full CLI reference (generated from --help).

Where it sits

netlist + .lib + constraints (+ optional .spef) → resized netlist + before/after WNS and leakage. Same footprint, so placement/routing are untouched. See the data spine.

Domain coverage

vyges-vt-swap trades standard-cell Vt flavors for leakage / setup, scored by the digital vyges-sta-si timer — it is digital optimization. It does not apply to analog / mixed-signal blocks, which have no iso-footprint Vt cell families and no Liberty-arc timing to score against. For analog / MS physical & integrity verification, see lvs (connectivity), layout (geometry), em-ir (IR / EM), thermal, and extract (RC).

Source & releases

vyges loom vt-swap — CLI reference

Generated from vyges loom vt-swap --help — this page is the tool’s own output, verbatim.

vyges loom vt-swap — STA-driven threshold-voltage swapping (cut leakage / close setup, iso-footprint)

usage:
  vyges loom vt-swap run   JOB  [-o OUT] [--json] [--fail-on-violation]   swap Vt -> resized netlist
  vyges loom vt-swap check JOB                                            validate the job
  vyges loom vt-swap demo                                                 swap a built-in example (no files)

flags:
  -o FILE              write the resized netlist to FILE (default: stdout)
  --json               emit the before/after report as JSON
  --fail-on-violation  exit 3 if the result still has negative setup slack (CI gate)
  -q, --quiet          suppress non-essential output
  -v, --verbose        extra detail on stderr
  --describe           print a machine-readable JSON description of the command
  -h, --help           show this help
  -V, --version        show version
  --bug-report         file a bug (central: vyges/community)
  --feature-request    request a feature (central)
  --sponsor            sponsor Vyges (github.com/sponsors/vyges-ip)
  --star               star this tool on GitHub ⭐

Contract

Generated from vyges loom vt-swap --describe.

vt-swap

STA-driven threshold-voltage swapping (cut leakage / close setup, iso-footprint)

Maturity: structured

run {job}
InputTypeRequiredDescription
jobstringyesPath to the Vt-swap job file (STA design, cell groups, objective, effort).
outstringnoPath to write the resized netlist to (default: stdout).

Consumes: netlist, liberty, timing_report

Artifacts: netlist (out)

Assertion: vt-swapping — none; this operation establishes no engineering claim

Provenance limitations

  • The job names the netlist, Liberty and timing report; input_hash covers the job path and arguments, not their contents.

vyges-buffer-insert — buffer insertion

Part of the Vyges Loom suite (optimizer). Install once with vyges install loom, then run vyges loom buffer-insert. It’s also a standalone vyges-buffer-insert binary on your PATH.

The third Loom optimizer. Where vyges-resize and vyges-vt-swap swap a cell for one with the same footprint, this one adds cells. When a net is so heavily loaded that its driver’s output transition exceeds the limit, vyges-buffer-insert splits the net: a fresh buffer takes over a share of the sinks so the original driver sees less load and switches faster. It keeps an insertion only if the worst transition dropped without breaking setup, scored by the vyges-sta-si timer.

Run it

vyges install loom                                          # one-time: fetch the Loom suite
vyges loom buffer-insert demo                               # instant: relieve a heavy net
vyges loom buffer-insert run  top.bufins -o buffered.v      # buffer → netlist
vyges loom buffer-insert run  top.bufins --json             # before/after slew + timing
vyges loom buffer-insert check top.bufins                   # validate the job

A .bufins job names the buffer: cell to insert, a max_slew: transition limit, a min_fanout: threshold, effort:, and dont_touch:.

See the full CLI reference (generated from --help).

Where it sits

netlist + .lib + constraints (+ optional .spef) → buffered netlist + before/after worst transition and WNS. A pre-place structural fixup — it decides where in the logical net to split and hands placement of the new buffer back to the flow. See the data spine.

Domain coverage

vyges-buffer-insert splits over-slew nets in a gate-level netlist with standard-cell buffer cells, scored by the digital vyges-sta-si timer — it is digital optimization. It does not apply to analog / mixed-signal blocks, which have no standard-cell buffers or Liberty-arc transition limits to drive the fix. For analog / MS physical & integrity verification, see lvs (connectivity), layout (geometry), em-ir (IR / EM), thermal, and extract (RC).

Source & releases

vyges loom buffer-insert — CLI reference

Generated from vyges loom buffer-insert --help — this page is the tool’s own output, verbatim.

vyges loom buffer-insert — STA-driven buffer insertion (split over-transition / high-fanout nets)

usage:
  vyges loom buffer-insert run   JOB  [-o OUT] [--json] [--fail-on-violation]   buffer -> resized netlist
  vyges loom buffer-insert check JOB                                            validate the job
  vyges loom buffer-insert demo                                                 buffer a built-in example (no files)

flags:
  -o FILE              write the buffered netlist to FILE (default: stdout)
  --json               emit the before/after report as JSON
  --fail-on-violation  exit 3 if the result still has negative setup slack (CI gate)
  -q, --quiet          suppress non-essential output
  -v, --verbose        extra detail on stderr
  --describe           print a machine-readable JSON description of the command
  -h, --help           show this help
  -V, --version        show version
  --bug-report         file a bug (central: vyges/community)
  --feature-request    request a feature (central)
  --sponsor            sponsor Vyges (github.com/sponsors/vyges-ip)
  --star               star this tool on GitHub ⭐

Contract

Generated from vyges loom buffer-insert --describe.

buffer-insert

STA-driven buffer insertion (split over-transition / high-fanout nets)

Maturity: structured

run {job}
InputTypeRequiredDescription
jobstringyesPath to the buffer-insert job file (design, netlist, lib, clock, buffer cell, max_slew, min_fanout).
outstringnoPath to write the buffered netlist (default: stdout).

Consumes: netlist, liberty, timing_report

Artifacts: netlist (out)

Assertion: buffer-insertion — none; this operation establishes no engineering claim

Provenance limitations

  • The job names the netlist, Liberty and timing report; input_hash covers the job path and arguments, not their contents.

vyges-hold-fix — post-route hold-fix ECO

Part of the Vyges Loom suite (optimizer). Install once with vyges install loom, then run vyges loom hold-fix. It’s also a standalone vyges-hold-fix binary on your PATH.

The hold counterpart to the Loom optimizers. Where vyges-resize, vyges-vt-swap, and vyges-buffer-insert all fix the late / setup corner, hold violations are the opposite problem — data reaches a capture flop too early on the min-delay path — and open place-and-route flows often leave a residue of them after detailed routing. vyges-hold-fix adds delay: it inserts a delay cell in series on the net feeding each hold-violating capture pin, lifting that pin’s earliest arrival until its hold constraint is met. Each round is scored by the vyges-sta-si timer, and an insertion is kept only if the worst hold slack improved and setup stays met — so a slow clock with ample setup slack trades a little of it for hold closure, never the reverse.

Run it

vyges install loom                                       # one-time: fetch the Loom suite
vyges loom hold-fix demo                                 # instant: close a two-flop hold violation
vyges loom hold-fix run  top.holdfix -o fixed.v          # delay → hold-fixed netlist
vyges loom hold-fix run  top.holdfix --json              # before/after WHS + WNS
vyges loom hold-fix run  top.holdfix --fail-on-violation # exit 3 if hold still negative (CI gate)
vyges loom hold-fix check top.holdfix                    # validate the job

A .holdfix job is a superset of a vyges-sta-si .sta job (same design/netlist/lib/ clock or sdc/spef keys) plus the hold knobs: the buffer: (delay cell to insert), hold_margin: (target hold slack), rounds: (low/medium/high), and dont_touch:.

See the full CLI reference (generated from --help).

Where it sits

routed netlist + .lib + .spef + constraints → hold-fixed netlist + before/after WHS and WNS. A post-route ECO — with spef: present it scores the fix against real routed parasitics. It decides which capture pins to delay and how deep a chain each needs; placement + routing of the inserted cells is handed back to the flow. See the data spine.

Domain coverage

vyges-hold-fix inserts standard-cell delay/buffer cells on data paths in a gate-level netlist, scored by the digital vyges-sta-si timer — it is digital optimization. It does not apply to analog / mixed-signal blocks. For analog / MS physical & integrity verification, see lvs, layout, em-ir, thermal, and extract.

Source & releases

vyges loom hold-fix — CLI reference

Generated from vyges loom hold-fix --help — this page is the tool’s own output, verbatim.

vyges loom hold-fix — post-route hold-fix ECO (insert series delay on hold-violating capture pins)

usage:
  vyges loom hold-fix run   JOB  [-o OUT] [--json] [--fail-on-violation]   hold-fix -> delayed netlist
  vyges loom hold-fix check JOB                                            validate the job
  vyges loom hold-fix demo                                                 hold-fix a built-in example (no files)

flags:
  -o FILE              write the hold-fixed netlist to FILE (default: stdout)
  --json               emit the before/after report as JSON
  --eco FILE           write the ECO manifest (insertions) as JSON — for a physical applier
  --fail-on-violation  exit 3 if the result still has negative hold slack (CI gate)
  -q, --quiet          suppress non-essential output
  -v, --verbose        extra detail on stderr
  --describe           print a machine-readable JSON description of the command
  -h, --help           show this help
  -V, --version        show version

Contract

Generated from vyges loom hold-fix --describe.

hold-fix

post-route hold-fix ECO (insert series delay on hold-violating capture pins)

Maturity: structured

run {job}
InputTypeRequiredDescription
jobstringyespath to the hold-fix job file (design, netlist, lib, clock, buffer, hold margin)
outstringnowrite the hold-fixed netlist to FILE (default: stdout)
ecostringnowrite the ECO manifest (insertions) as JSON, for a physical applier

Consumes: netlist, liberty, timing_report

Artifacts: netlist (out), eco_manifest (eco)

Assertion: hold-eco — none; this operation establishes no engineering claim

Provenance limitations

  • The job names the netlist, Liberty and timing report; input_hash covers the job path and arguments, not their contents.
  • KNOWN DEFECT: on at least one real block this toolchain reports hold violations that OpenSTA and sign-off both say do not exist, and the CAUSE IS NOT YET IDENTIFIED. Measured on a routed sky130 block with the same netlist, SDC, SPEF and liberty given to both: setup WNS 6.7322 against OpenSTA 6.85 (1.7% apart), but hold WHS -1.0682 against OpenSTA +0.88 and sign-off +0.8821 – out by 1.95 ns and disagreeing about the sign. Asked about the endpoint this toolchain calls worst, OpenSTA answers No paths found. On that block this engine inserts 599 delay cells into a design already hold-clean by 0.88 ns.
  • Two explanations have been tested and REJECTED, recorded so they are not re-run: it is not the async check taxonomy, since vyges-sta-si implements recovery and removal, reads the removal table rather than the data hold table, and has a test asserting it; and it is not unconstrained input ports, since that block’s SDC constrains the port in question at 12 ns and varying the job-level input_delay does not move the result at all.
  • Until the cause is found: cross-check the worst hold endpoints against your sign-off timer before applying a plan; treat async reset endpoints as suspect, since that is where this has been seen; and exclude instances with dont_touch, which accepts globs. The plan-and-apply split keeps it recoverable – this engine emits a plan and never mutates a design – but the plan must be reviewed.

vyges-drc — design-rule check

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom drc. It’s also a standalone vyges-drc binary on your PATH (the integration contract for flow authors).

vyges-drc answers the geometric half of physical verification: does the layout obey the foundry’s rules? It reads a laid-out GDS and a small per-layer rule deck and returns the list of violations. It is the geometric sibling of vyges-lvs — LVS asks does the layout implement the schematic?, DRC asks does it obey the rules? — and rides the same vyges-layout GDS/boolean/flatten kernel.

Run it

vyges install loom                                       # one-time: fetch the Loom suite
vyges loom drc demo                                       # built-in layout with violations
vyges loom drc check block.gds --rules sky130.drc         # -> violations report
vyges loom drc check block.gds --rules sky130.drc --fail-on-violation   # exit 3 (CI gate)
vyges loom drc fill  block.gds --rules sky130.drc -o filled.gds         # metal-fill generator

A rule deck keys on the GDS layer number; today’s classes are width, spacing, area, windowed metal density, per-net antenna ratio, and enclosure — plus a fill rule that drives the metal-fill generator (vyges loom drc fill … -o out.gds):

# rule     layer  args
width      66     170             # min width on layer 66
space      68     140             # min spacing on layer 68
area       68     20000           # min polygon area (dbu²)
density    68     20 70 100000    # coverage on 68 must be 20–70% per 100000-dbu window
connect    5      68              # layers connect where they overlap (via/contact)
antenna    68     5  400          # per net: conductor area ≤ 400 × gate area
enclosure  68     66 40           # layer-66 shapes enclosed by layer-68 with ≥40 margin
fill       70     30 100000 600 400   # generate fill: top layer 70 to 30% per window

See the full CLI reference (generated from --help).

Where it sits

GDS + a .drc deck → violations (text or --json, with a CI exit code). It’s the layout-rule check at sign-off, beside LVS. The deck is the plugin boundary: an open reference deck ships for the open PDKs; a certified per-foundry deck stays private under that foundry’s terms. Same-layer pre-merge is the next depth pass on the same engine.

vyges loom drc — CLI reference

Generated from vyges loom drc --help — this page is the tool’s own output, verbatim.

vyges loom drc — geometric design-rule check (GDS/OASIS + rule deck -> violations)

usage:
  vyges loom drc check GDS --rules DECK [--top CELL] [-o OUT] [--json] [--fail-on-violation]
  vyges loom drc fill  GDS --rules DECK [--top CELL] -o OUT.gds     # metal-fill generator
  vyges loom drc demo  [--json]

The input layout may be GDSII (.gds) or OASIS (.oas/.oasis) — picked by extension.

flags:
  --rules DECK          the .drc rule deck (required for `check` / `fill`)
  --pdk NAME            resolve the deck from pdk-store (drc_deck) instead of --rules
  --top CELL            top cell to flatten (default: the sole cell)
  -o FILE               write the report (or, for `fill`, the filled GDS) to FILE
  --json                machine-readable JSON instead of text
  --fail-on-violation   exit 3 when any violation is found (CI gate)
  --views DIR    render ranked violation views (PNG) into DIR — diagnostic evidence,
                 not sign-off; capped, and the number dropped is reported
  --describe            print a machine-readable JSON description of the command
  -h, --help · -V, --version

Contract

Generated from vyges loom drc --describe.

drc

geometric design-rule check (GDS/OASIS layout + rule deck)

Maturity: structured

check {gds} --rules {deck}
InputTypeRequiredDescription
gdsstringyeslayout file to check (.gds or .oas)
deckstringyesthe .drc rule deck
topstringnotop cell to flatten (default: the sole cell)
outstringnowrite the report to FILE instead of stdout

Artifacts: drc_report (report_path), drc_view (view_paths)

Assertion: drc-clean — passes when clean is true (summary from verdict_summary)

Provenance limitations

  • input_hash covers the argument vector, not the content of the GDS or rule deck it names.
  • A rule deck that includes other decks is not enumerated, so those are outside the hash.

vyges-ant — antenna ratio sign-off

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom ant. It’s also a standalone vyges-ant binary on your PATH (the integration contract for flow authors).

vyges-ant answers: will this routing damage a gate during manufacture? While a chip is being built, a metal shape already connected to a transistor gate but not yet to a diffusion path collects charge. If that shape is large relative to the gate, the accumulated charge breaks the gate oxide — a failure that no amount of post-silicon testing can repair. The check is a ratio of collected metal to gate area, evaluated per routing layer against limits the PDK’s LEF states.

Run it

vyges install loom                                   # one-time
vyges loom ant check routed.odb                      # -> verdict, exit 0/1
vyges loom ant check routed.odb -o antenna.json      # report to a file
{
  "status": "clean",
  "count": 0,
  "nets_checked": 42,
  "nets_no_gate": 8,
  "nets_unrouted": 2,
  "layers_without_rules": [],
  "no_rules_found": false,
  "violations": []
}

Exit status is the verdict: 0 clean, 1 violations, 2 error — so CI can gate on it without parsing prose.

See the full CLI reference (generated from --help).

What it computes

For each net, walking routing layers bottom-up:

RatioNumerator
PARmetal on this layer alone
CARmetal on this layer and every layer below
PSRside area (perimeter × layer thickness) on this layer
CSRside area cumulative to this layer

The cumulative forms are not redundant. A net legal on every layer taken individually can still violate CAR, because the charge a gate sees is what the whole connected stack collected, not the worst single layer.

Where it sits

A routed .odb in, an antenna verdict out. It reads the routed database — the same substrate OpenROAD’s ant module uses — rather than a streamed GDS. That choice is the point: a GDS answer arrives after the last stage at which a violation could still be repaired by inserting a protection diode. vyges-drc computes an antenna ratio too, over GDS polygons, post-stream; same ratio, different substrate, different job.

It is a checker. It reports and never modifies a design; repair is a separate, reviewable plan replayed by an applier — the same split the Loom optimizers use, for the same reason.

Two forms of limit, and why it matters

LEF states antenna limits two ways, and a checker that reads only one finds nothing on technologies that use the other:

  • Plain ratios (ANTENNAAREARATIO …) — a constant per layer.
  • Diffusion-dependent PWL ratios (ANTENNADIFFAREARATIO …) — the limit as a piecewise-linear function of the diffusion area connected to the net. More diffusion permits a higher ratio, which is exactly how a protection diode earns relief.

Both are read; where a technology states a diff curve it takes precedence, since that is the limit the foundry characterised for a net carrying that much diffusion. Outside a curve’s stated range the limit is clamped, not extrapolated — a LEF table covers the diffusion areas the foundry characterised, and inventing values beyond either end would be manufacturing an answer the technology never gave.

On sky130 this is the whole check: every routing layer carries an antenna rule object, yet none states a plain ratio. sky130 declares exactly one limit, DiffPSR, as a 4-point curve identical on met1–met3.

Verdicts that are not verdicts

Two situations are deliberately not reported as clean, because nothing was actually checked:

  • no_rules_found — the technology states no antenna limit in either form. Exit 2, not 0. A design whose PDK sets no limits has not passed anything.
  • nets_no_gate — a net with routed metal but no gate area on any connected pin. With no denominator there is no ratio. A large count here means the standard-cell library is missing antenna models and the check covers less than it appears to, which is why the number is in the report rather than swallowed.

Known bounds

Both over-report rather than under-report, so a clean verdict is trustworthy and a reported violation may be spurious:

  1. Metal area double-counts overlap — shapes are summed as raw rectangles, not unioned.
  2. Layer order is routing level, not a manufacturing step model (the standard CAR approximation).

And one gap that belongs to the technology rather than the tool: a ratio stated in neither form is not checked. On sky130 that means only PSR is evaluated.

vyges loom ant — CLI reference

Generated from vyges loom ant --help — this page is the tool’s own output, verbatim.

vyges loom ant — antenna ratio sign-off over the routed design database

USAGE:
  vyges loom ant check <design.odb> [-o FILE] [--json]
  vyges loom ant explain <design.odb> --net NAME
  vyges loom ant --describe
  vyges loom ant --help
  vyges loom ant --version

OPTIONS:
  --net NAME            (explain) dump one net's per-gate, per-stage attribution
  -o FILE               write the report to FILE instead of stdout
  --json                emit JSON (the default for `check`)
  --describe            print a machine-readable JSON description of the command

EXIT STATUS:
  0  clean          no violation found
  1  violations     at least one net exceeds a LEF antenna limit
  2  vacuous        nothing was checked -- no antenna rule in the technology, or no routed
                    metal in the database (a global-route .odb has none). NOT a pass.
  2  error          usage error, unreadable database, or no DBU scale

Contract

Generated from vyges loom ant --describe.

ant

antenna ratio sign-off (PAR/CAR/PSR/CSR) over the routed design database

Maturity: structured

check {odb}
InputTypeRequiredDescription
odbstringyespath to the routed design database (.odb)
outstringnowrite the report to FILE instead of stdout

Consumes: odb

Artifacts: antenna_report (report_path)

Assertion: antenna-clean — passes when status equals clean

Provenance limitations

  • input_hash covers the argument vector, not the content of the .odb it names.
  • Both the plain and the diffusion-dependent (PWL) LEF ratio forms are read; where a technology states a diff curve it takes precedence, and outside the curve’s stated range the limit is clamped rather than extrapolated.
  • A ratio the technology states in neither form is not checked. On sky130 only DiffPSR is stated, so PAR, CAR and CSR are unlimited there; layers_without_rules and no_rules_found report this rather than leaving it implied by the exit code.
  • status is one of clean, violations, vacuous or error. VACUOUS IS NOT CLEAN: it means nothing was checked, either because no layer states an antenna rule (no_rules_found) or because no net carries routed metal (no_routing_found, which usually means a global-route database was passed to a checker that reads routed geometry). The declared assertion passes only on clean, so a vacuous run fails it rather than signing off a design nothing was verified on. Exit status is 2 for both vacuous and error, 1 for violations, 0 for clean.
  • Correlated against OpenROAD check_antennas, RE-MEASURED 2026-08-23 against a freshly generated reference on a build carrying OpenROAD PR 11125. Reference: check_antennas at OpenROAD 945a9f4. Engine: vyges-ant 802e66b. Database: a detail-routed sky130 block of 10918 nets, of which 9677 checked, 751 with no gate, 490 unrouted. Result: 44 reference violations, 43 matched, 1 missed, 0 added, 43 of 43 matched values within 2%. All 44 are PSR. Both sides are deterministic – repeat runs on the same .odb return byte-identical output. A NUMBER HERE MEANS NOTHING WITHOUT THE BUILD AND THE DATABASE: the reference’s own answer moves between OpenROAD builds, and an earlier measurement against a pre-11125 build showed 10 violations this engine reported that the reference did not, all of which are gone against a current reference. Treat as a strong screen, not a sign-off gate: run check_antennas for sign-off, and if the two disagree check which build you are comparing against. Give it a DETAIL-routed database – on a global-route .odb the reference synthesises wires from routing guides while this engine reads the routed database, finds no routing, and refuses the verdict as vacuous.
  • The ratio is charged per CONDUCTOR: metal reachable from the gates over layers at or below the one being deposited, divided by the summed gate area of the gates on that conductor. Measured as the exact union of the rectangles, so overlap and abutment count once.
  • The diffusion-dependent limit is indexed by each conductor’s own diffusion, matching OpenROAD’s per-node iterm_diff_area. Every terminal is anchored to a conductor, not only the gates, since a diode pin carries diffusion without carrying a gate.
  • The conductor graph follows AntennaChecker: vias decomposed onto the layers they occupy, pin metal subtracted so pins cut the wire into antenna regions, components labelled per layer, layers joined through the cut between them, and terminals attached to the fragments their own pin boxes touch.
  • Cut layers (mcon/via/via2) are not checked; routing layers only.
  • Diffusion area is applied net-wide, where the real limit varies per layer as the path to diffusion completes.
  • A terminal whose pin metal touches no routing is attached to nothing and, if it is a gate, counted in gates_unanchored rather than silently skipped.
  • Layer accumulation order is dbTechLayer routing level, not a manufacturing step model.

vyges-cdc — clock-domain-crossing check

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom cdc. It’s also a standalone vyges-cdc binary on your PATH (the integration contract for flow authors).

vyges-cdc finds the spots where a signal launched in one clock domain is captured in another, and checks each is properly synchronized. It is a purely structural graph analysis — which signals cross domains, and through what? — and notably a question a lockstep gate-level simulator structurally cannot answer. It assigns each flop a domain by tracing its clock pin to an SDC clock source, walks each capture flop’s data cone to its launch flops, and reports every cross-domain pair.

Run it

vyges install loom                                                  # one-time
vyges loom cdc check design.v --lib cells.lib --sdc design.sdc       # -> crossings report
vyges loom cdc check design.v --lib cells.lib --sdc design.sdc --fail-on-violation  # exit 3

Each create_clock in the SDC is a clock domain; the Liberty identifies the flops and their clock/data pins. A crossing is OK when it is a clean two-flop synchronizer, and a violation when there is no synchronizer or combinational logic sits on the crossing path.

See the full CLI reference (generated from --help).

Where it sits

Netlist + Liberty + SDC → the list of domain crossings (text or --json, with a CI exit code). It’s an early structural lint that complements simulation — the crossings sim can’t see. Handshake / gray-code recognition and reconvergence are the depth passes.

vyges loom cdc — CLI reference

Generated from vyges loom cdc --help — this page is the tool’s own output, verbatim.

vyges loom cdc — structural clock- and reset-domain-crossing checks

usage:
  vyges loom cdc check NETLIST --lib L.lib --sdc S.sdc [-o OUT] [--json] [--fail-on-violation]
  vyges loom cdc rdc   NETLIST --lib L.lib           [-o OUT] [--json] [--fail-on-violation]

`check` finds CLOCK-domain crossings; `rdc` finds RESET-domain crossings — a flop
asynchronously reset by one reset feeding a flop reset by another. A single-clock design is
CDC-clean by construction and can still fail `rdc`, so they are separate reports. `rdc` needs
no SDC: reset domains are structural, traced from the Liberty ff group's clear/preset pins.

flags:
  --lib FILE            Liberty (identifies flops + clock/data/reset pins) — required
  --sdc FILE            SDC clock definitions (the domains) — required by `check`
  -o FILE               write the report to FILE (default: stdout)
  --json                machine-readable JSON instead of text
  --fail-on-violation   exit 3 if any unsynchronized crossing is found (CI gate)
  --fail-on-multibit    exit 3 on a multi-bit crossing too. Opt-in, because a gray-coded
                        or handshake-qualified bus looks identical here — waive the ones
                        you have reviewed with --waivers
  --waivers FILE        findings the team has accepted, each with a reason (and optionally
                        an approver and an expiry). A lapsed waiver stops applying
  --as-of YYYY-MM-DD    evaluate waiver expiry as of this date instead of today, so a
                        sign-off run reproduces
  --describe            print a machine-readable JSON description of the command
  -h, --help · -V, --version

Contract

Generated from vyges loom cdc --describe.

cdc

structural clock-domain-crossing check

Maturity: structured

check {netlist} --lib {lib} --sdc {sdc}
InputTypeRequiredDescription
netliststringyesgate-level netlist to analyze
libstringyesLiberty file identifying flops and clock/data pins
sdcstringyesSDC file defining clock domains
outstringnowrite the report to this file instead of stdout

Artifacts: cdc_report (report_path)

Assertion: cdc-synchronized — passes when unsynchronized equals 0

Provenance limitations

  • input_hash covers the argument vector, not the content of the netlist, Liberty or SDC it names.
  • Liberty include files are not enumerated.

vyges-glitch — static glitch / hazard analysis

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom glitch. It’s also a standalone vyges-glitch binary on your PATH (the integration contract for flow authors).

vyges-glitch finds reconvergent-fanout hazards — the spots where one signal reaches a combinational endpoint by more than one path and can momentarily glitch before it settles. That is exactly what a lockstep gate-level simulator cannot see: it samples one settled value per tick and steps over the intermediate glitch. Catching it is a structural + timing question — parity from each Liberty arc’s timing_sense, the glitch window from the same delay tables vyges-sta-si uses.

Run it

vyges install loom                                              # one-time
vyges loom glitch check design.v --lib cells.lib                 # -> hazard report
vyges loom glitch check design.v --lib cells.lib --fail-on-violation   # exit 3

A source reaching an endpoint by ≥2 paths is flagged static (the paths differ in inversion parity, so a single edge can drive the endpoint the wrong way for a moment) or dynamic (same parity, different delay — a glitch over the settling window). A balanced reconvergence is not flagged.

See the full CLI reference (generated from --help).

Where it sits

Netlist + Liberty → hazards (text or --json, with a CI exit code). It fills the glitch blind spot of simulation and leans on the same timing data the STA engine uses. SAT/BDD path sensitization and function hazards are the depth passes.

vyges loom glitch — CLI reference

Generated from vyges loom glitch --help — this page is the tool’s own output, verbatim.

vyges loom glitch — static glitch / hazard analysis (reconvergent fanout)

usage:
  vyges loom glitch check NETLIST --lib L.lib [-o OUT] [--json] [--fail-on-violation]

flags:
  --lib FILE            Liberty (cell parity via timing_sense + delays) — required
  -o FILE               write the report to FILE (default: stdout)
  --json                machine-readable JSON instead of text
  --fail-on-violation   exit 3 if any hazard is found (CI gate)
  --describe            print a machine-readable JSON description of the command
  -h, --help · -V, --version

Contract

Generated from vyges loom glitch --describe.

glitch

static glitch / hazard analysis (reconvergent fanout)

Maturity: structured

check {netlist} --lib {lib}
InputTypeRequiredDescription
netliststringyesNetlist file to analyze for reconvergent-fanout hazards
libstringyesLiberty file (cell parity via timing_sense + delays), required
outstringnoWrite the report to this file instead of stdout

Artifacts: hazard_report (report_path)

Assertion: glitch-free — passes when glitch_free is true

Provenance limitations

  • input_hash covers the argument vector, not the content of the netlist or Liberty it names.
  • Liberty include files are not enumerated.

vyges-lec — combinational logic equivalence

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom lec. It’s also a standalone vyges-lec binary on your PATH (the integration contract for flow authors).

vyges-lec answers: do two gate-level netlists compute the same function? It is the formal sibling of simulation — a simulator shows two designs agree on the vectors you ran; LEC proves they agree on all of them, or hands you the input where they don’t. It builds a canonical ROBDD for every endpoint of both designs over one shared variable order; equal functions share a node, so the check is exact, and a mismatch’s miter walks straight to a counter-example.

Run it

vyges install loom                                                   # one-time
vyges loom lec check golden.v revised.v --lib cells.lib               # -> verdict
vyges loom lec check golden.v revised.v --lib cells.lib --fail-on-diff # exit 3 if differ
vyges-lec — NOT EQUIVALENT ✗  (1 compared, 1 differ)
  differ at `f`   when a=1 b=0

Sequential designs are cut at the registers — flop Q nets are free inputs, flop D nets are endpoints — so it proves the combinational logic between registers (registers matched by name).

See the full CLI reference (generated from --help).

Where it sits

Golden + revised netlists + Liberty → an equivalence verdict (text or --json, with a CI exit code). It guards every synthesis, ECO, and hand-edit. Gate functions come from a standard-cell primitive map (an unknown cell is a hard error, never a silent wrong answer); the Liberty function-attribute path and an AIG+SAT scaling path are the depth passes.

vyges loom lec — CLI reference

Generated from vyges loom lec --help — this page is the tool’s own output, verbatim.

vyges loom lec — combinational logic equivalence check (golden vs revised)

usage:
  vyges loom lec check GOLDEN.v REVISED.v --lib L.lib [-o OUT] [--json] [--fail-on-diff]

flags:
  --lib FILE       Liberty (pin directions + comb/seq split) — required
  -o FILE          write the report to FILE (default: stdout)
  --json           machine-readable JSON instead of text
  --fail-on-diff   exit 3 if the designs are not equivalent (CI gate)
  --describe            print a machine-readable JSON description of the command
  -h, --help · -V, --version

Contract

Generated from vyges loom lec --describe.

lec

combinational logic equivalence check (golden vs revised)

Maturity: structured

check {golden} {revised} --lib {lib}
InputTypeRequiredDescription
goldenstringyespath to the golden (reference) gate-level netlist
revisedstringyespath to the revised gate-level netlist to compare
libstringyespath to the Liberty file (pin directions + comb/seq split)
outstringnowrite the report to FILE instead of stdout

Artifacts: equivalence_report (report_path)

Assertion: logic-equivalent — passes when equivalent is true

Provenance limitations

  • input_hash covers the argument vector, not the content of the two netlists or the Liberty it names.
  • Liberty include files are not enumerated.

vyges-gds-view — headless GDS layout viewer

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom gds-view. It’s also a standalone vyges-gds-view binary on your PATH (the integration contract for flow authors).

vyges-gds-view renders a laid-out GDS to a single self-contained SVG — every shape coloured by its layer, a layer legend, the Y axis flipped so up is up — with an optional violation overlay. Where vyges-drc and vyges-lvs tell you what is wrong, this shows you where: render the GDS, drop the violation coordinates on top, open it in any browser, commit it to a report, or diff it in CI. No display server, no GUI toolkit; it rides the same vyges-layout kernel.

Run it

vyges install loom                                                # one-time
vyges loom gds-view demo -o demo.svg                               # built-in sample layout
vyges loom gds-view render block.gds -o block.svg                  # flatten top cell -> SVG
vyges loom gds-view render block.gds --marks viols.txt -o block.svg  # overlay violations

The marks file is one violation per line — x0 y0 x1 y1 [label] in GDS db units — the trivial format any engine can emit, so a DRC-then-look pass is two commands.

See the full CLI reference (generated from --help).

Where it sits

GDS (+ optional violation marks) → a layered SVG. It’s the visual companion to the geometry engines — the headless, mask-level renderer for reports and CI, distinct from any interactive block-level explorer. Datatype colour keys and hierarchical views are the depth passes.

vyges loom gds-view — CLI reference

Generated from vyges loom gds-view --help — this page is the tool’s own output, verbatim.

vyges loom gds-view — headless layout viewer (GDS or OASIS in, layered SVG out)

usage:
  vyges loom gds-view render LAYOUT [--top CELL] [--layers L1,L2] [--marks FILE] [-o OUT.svg]
  # LAYOUT is GDSII (.gds) or OASIS (.oas/.oasis) — picked by extension
  vyges loom gds-view demo [-o OUT.svg]

flags:
  --top CELL     top cell to flatten + render (default: last cell in the GDS)
  --layers LIST  comma-separated GDS layer numbers to show (default: all)
  --marks FILE   overlay violation boxes; each line: `x0 y0 x1 y1 [label...]`
  -o FILE        write to FILE (default: stdout). SVG, or a PNG if FILE ends in .png
  --png          force PNG (bounded raster thumbnail — for dense real blocks)
  --width N      PNG fit size in pixels (default: 700)
  --window BOX   PNG only: frame this db-unit region instead of the whole cell,
                 as `x0,y0,x1,y1` — the occurrence-level view for one violation
  --describe            print a machine-readable JSON description of the command
  -h, --help · -V, --version

Contract

Generated from vyges loom gds-view --describe.

gds-view

headless layout viewer — GDS/OASIS → layered SVG

Maturity: structured

render {gds}
InputTypeRequiredDescription
gdsstringyeslayout file (.gds or .oas)
topstringnotop cell (default: the sole cell)
layersstringnocomma-separated layers to draw, e.g. 66,68
marksstringnoa marks file to overlay
outstringnowrite the SVG to this path (default: stdout)

Artifacts: svg (out)

Assertion: layout-render — none; this operation establishes no engineering claim

Provenance limitations

  • input_hash covers the argument vector, not the content of the layout or layer map it names.

vyges-meas — closed measurement kernels

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom meas. It’s also a standalone vyges-meas binary on your PATH (the integration contract for flow authors).

vyges-meas answers: what does this capture actually measure? Two families of scalars — coherent single-tone SNR, SINAD, THD, SFDR from a captured time series, and gain, bandwidth, unity-gain frequency, phase margin from an AC sweep.

Why the definition is the product

SNR names a ratio, not a measurement. Whether the fundamental is excluded from noise, how many harmonics are counted, whether DC is in the band, where the band ends, what happens to a harmonic that aliases back below Nyquist — every one of those changes the number, and two honest tools can differ by several dB while both being “right”. A converter datasheet is comparable to a simulation only if both state the same choices.

So each kernel fixes one method, documents every choice, and refuses inputs it cannot measure that way. A refusal is a result: it says the number would have been meaningless.

Run it

vyges install loom                                                    # one-time

# spectral — one scalar per call, each independently checked
vyges loom meas spectral capture.samples --fundamental-bin 37 \
      --metric thd --harmonics 2,3,4,5

# AC transfer
vyges loom meas transfer opamp.ac --metric phase-margin --target 45

SERIES is one sample per line in capture order. SWEEP is hz gain_db phase_deg per line, strictly increasing. Both accept # comments. Add --json for machine output; -o FILE writes a report while the JSON still goes to stdout, so asking for the file never costs you the parsed result.

The spectral method

choicethis kernel
record lengthpower of two, 8 to 65,536 samples
samplingcoherent — the fundamental lands exactly on a DFT bin, and you say which
windownone. A rectangular window is exact for a coherent capture and wrong otherwise, so a non-coherent capture is refused, not smeared
DCthe mean is removed and the DC bin is excluded from every partition
harmonicsfolded into the first Nyquist zone — an aliased harmonic’s power is really in the record
integration widthzero bins: each component is exactly one bin, never a skirt
collisionsa harmonic landing on DC, the fundamental, or another harmonic is refused — counting one bin twice would double-count its power
clippinga clipped record is refused: it describes the acquisition, not the device
SNR   = 10 log10(p_f / p_n)      noise only, declared harmonics excluded
SINAD = 10 log10(p_f / p_r)      everything that is not the fundamental
THD   = 10 log10(p_h / p_f)      harmonics against the fundamental (negative dB)
SFDR  = 10 log10(p_f / p_s)      distance to the worst single spur

The AC method

Values between swept points are interpolated in (log10 f, dB) and (log10 f, degrees) — the space these are plotted in, so a coarse sweep does not read low. Nothing is extrapolated: a crossing outside the swept range is reported as absent, because a sweep that stopped too early is a fixable mistake and a guessed number is not.

Bandwidth is referenced to the peak gain, not the first point. A response that peaks before rolling off has its −3 dB corner relative to that peak, and referencing the first point would be wrong for exactly the circuits where the number matters most.

The verdict

With --target, the result carries a pass/fail: SNR, SINAD, SFDR and the AC metrics want at least the target; THD wants at most it. Without a target there is no claim to make, so met is null and the result envelope reports unknown rather than a pass nobody asked for.

A refused input also resolves to unknown — the engine ran, and the evidence does not support a conclusion.

How much a result claims

A number and a standard’s name printed near each other read as a conformance claim whether or not one was meant. So every result carries a machine-readable statement of exactly how much it claims:

levelmeans
vyges-definitionthe method is ours, complete and versioned. No external standard is claimed.
candidatethe application lies inside a named standard’s published scope, but no clause-level review has been done
revieweda crosswalk records the exact edition, clauses, choices, deviations, reviewer and artifact
conformantan independently reviewed profile and a conformance suite

--application decides which scope a result may name. It defaults to generic, because the tool cannot infer from a list of numbers what device produced them and guessing would manufacture a standards claim out of nothing:

--applicationreachesagainst
generic (default)vyges-definition
adccandidateIEEE 1241-2023
daccandidateIEEE 1658-2023
recordercandidateIEEE 1057-2017
"alignment": {
  "level": "candidate",
  "edition": "IEEE 1241-2023",
  "application": "adc",
  "statement": "candidate (IEEE 1241-2023) — … NO clause-level review has been performed, so this is not a conformance claim"
}

The ladder is enforced, not merely documented. reviewed and conformant can only be built from a crosswalk, and a crosswalk cannot exist without every field of the evidence it stands for — edition, clauses, choices, deviations, reviewer, artifact. A claim stronger than the evidence has no constructor. Nothing here is reviewed or conformant, and nothing becomes so by editing a label.

The published scopes of those editions are the limit of what is asserted; the normative clauses needed for a conformance claim are not public, so candidate is the honest ceiling today.

IEEE 519 is not authority here. It governs harmonic control in electric power systems at a point of common coupling — a different quantity, measured elsewhere, for a different purpose. A THD figure from this engine must never be cited as 519-anything. Common enough on datasheets that the exclusion is recorded in code.

vyges loom meas — CLI reference

Generated from vyges loom meas --help — this page is the tool’s own output, verbatim.

vyges loom meas — closed measurement kernels (coherent single-tone spectral, AC transfer)

usage:
  vyges loom meas spectral SERIES --fundamental-bin N --metric snr|sinad|thd|sfdr
                                  [--harmonics 2,3,4,5] [--clip LEVEL] [--target DB]
  vyges loom meas transfer SWEEP  --metric gain|bandwidth|unity-frequency|phase-margin
                                  [--target VALUE]
  vyges loom meas demo            measure a synthesized coherent tone (no input files)

SERIES is one sample per line, in capture order. The record must be a power of two
between 8 and 65536 samples and must be COHERENTLY sampled: the fundamental has to
land exactly on a DFT bin, and --fundamental-bin says which. There is no window
function — a rectangular window is exact for a coherent capture, and a non-coherent
one is refused rather than silently smeared.

SWEEP is `hz gain_db phase_deg` per line, strictly increasing in frequency. Values
between points are interpolated in (log10 f, dB); nothing is extrapolated past the
swept range.

Every choice the method makes is documented in `vyges-meas --describe` and in the
module docs. These are Vyges definitions, NOT a claim of IEEE 1241/1658/1057
conformance.

flags:
  --fundamental-bin N   DFT bin the fundamental sits on (spectral; required)
  --metric M            which scalar to measure (required)
  --harmonics LIST      harmonic orders to account for, e.g. 2,3,4,5 (spectral)
  --clip LEVEL          treat |sample| >= LEVEL as clipped and refuse to measure
  --application WHAT    what the record measures: generic (default) | adc | dac | recorder.
                        Declaring it is what lets the result name a standard's scope; the tool
                        cannot infer from a list of numbers what device produced them, and
                        guessing would manufacture a standards claim out of nothing.
  --target VALUE        pass/fail threshold; SNR/SINAD/SFDR/gain want >= , THD <=
  -o FILE               write the report to FILE (default: stdout)
  --json                machine-readable JSON instead of the text report
  --describe            print a machine-readable JSON description of the command
  -h, --help · -V, --version

Contract

Generated from vyges loom meas --describe.

meas

closed measurement kernels (coherent single-tone spectral, AC transfer)

Maturity: structured

spectral {series} --fundamental-bin {fundamental_bin} --metric {metric}
InputTypeRequiredDescription
seriesstringyescaptured time series, one sample per line
fundamental_binstringyesDFT bin the fundamental sits on
metricstringyessnr | sinad | thd | sfdr
harmonicsstringnoharmonic orders to account for, e.g. 2,3,4,5
clipstringnotreat |sample| >= this as clipped and refuse
applicationstringnogeneric | adc | dac | recorder — decides which standard’s scope the result may name
targetstringnopass/fail threshold in dB
outstringnowrite the report to FILE instead of stdout

Consumes: series, ac_sweep

Artifacts: measurement_report (report_path)

Assertion: measurement-meets-target — passes when met is true

Provenance limitations

  • input_hash covers the argument vector, not the content of the series or sweep file it names.
  • The measurement describes the record it was given; it cannot tell whether that record was captured coherently, and a non-coherent capture is refused rather than detected.
  • The application (adc/dac/recorder) is taken from the caller, not detected: the alignment claim is only as sound as that declaration.

vyges-remap — multi-output technology re-mapping

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges loom remap. It’s also a standalone vyges-remap binary on your PATH (the integration contract for flow authors).

vyges-remap answers: can this netlist be mapped better using cells a single-output mapper cannot reach? Given an AIGER netlist (or Verilog RTL, from which Yosys extracts one) and a technology genlib, it runs a single-output baseline and a multi-output pass, writes the remapped Verilog, and reports the before/after cell and area delta — including how many multi-output cells (adders, compressors) were mapped that a single-output mapper would have missed.

Run it

vyges install loom                                                   # one-time
vyges loom remap emap --verilog design.v --top top --genlib cells.genlib -o mapped.v

Every remap is gated by an equivalence check

A transform that changes the netlist has to prove it did not change the function. After mapping, remap runs an ABC combinational equivalence check of the mapped netlist against the input AIG. A failed check makes the run an error, and the netlist is rejected rather than handed on.

The verdict is tri-state, and the middle state matters:

equivalentwhen
truethe check ran and the mapped netlist is equivalent
falsethe check ran and it is not — the remap is rejected
nullthe check did not run (--no-cec) or ABC was inconclusive

null resolves to unknown in the result envelope. A remap that was not checked is not a remap that is correct — nothing has established that the mapped netlist still computes the input function, and a consumer reading only the execution status would see ok for an unverified transform. That conflation is exactly what the two status axes exist to prevent.

Provenance

The mapping and the equivalence check are performed by external emap, abc and yosys binaries resolved at run time. Their versions are not part of input_hash, so the same arguments can produce a different result under a different toolchain build — the engine declares this in its descriptor, and it travels with every result as provenance.limitations.

vyges loom remap — CLI reference

Generated from vyges loom remap --help — this page is the tool’s own output, verbatim.

vyges loom remap — file-level multi-output technology re-mapping (mockturtle emap)

usage:
  vyges loom remap emap (--verilog <d.v> --top T | --aig <a.aig>) (--genlib <g> | --liberty <lib>) [-o out.v] [--no-cec] [--json]
  vyges loom remap --describe        structured tool contract (for `vyges mcp`)
  vyges loom remap --version | --help

Extracts an AIG (Yosys, from --verilog) or takes one (--aig), then runs the
vyges-emap driver twice (single-output baseline + multi-output) and reports the
before/after cell/area delta. Tools resolve via $VYGES_EMAP / $VYGES_YOSYS / $VYGES_ABC.

Contract

Generated from vyges loom remap --describe.

remap

File-level multi-output technology re-mapping (mockturtle emap): AIGER + genlib -> mapped netlist + before/after cell/area delta.

Maturity: structured

emap --verilog {verilog} --top {top} --genlib {genlib}
InputTypeRequiredDescription
aigstringnoalternative to verilog: a pre-made AIGER netlist
genlibstringyestechnology genlib (multi-output cells are derived from xor/maj gates)
outstringnopath to write the remapped Verilog netlist
topstringyestop module name
verilogstringyesVerilog RTL of the logic to remap (Yosys extracts the AIG)

Artifacts: netlist (out_netlist)

Assertion: remap-equivalent — passes when equivalent is true

Provenance limitations

  • input_hash covers the argument vector, not the content of the Verilog, AIGER or genlib it names.
  • The mapping and the equivalence check are performed by external emap/abc/yosys binaries resolved at run time; their versions are not part of input_hash, so the same arguments can produce a different result under a different toolchain build.

vyges-ifp — initialize the floorplan

Physical construction — opt-in, and not part of vyges install loom. These engines build the design database that the sign-off and verification engines read, so they install as their own group:

vyges install physical   # all six
vyges install ifp        # just this one

It is also a standalone vyges-ifp binary on your PATH, which is what the dispatch calls and what a flow author targets directly. Source: vyges-tools/ifp.

vyges-ifp turns an empty design into one with a shape: a die area, a core area inside it, and the rows of standard-cell sites that fill the core. Everything downstream — placement, the power grid, taps — is positioned against those rows, so this is the first thing that runs and the thing every later stage assumes.

Run it

vyges ifp run design.odb --die-area '0 0 200 200' --core-area '10 10 190 190' --site unithd

Rows tile the core at the site’s height. --additional-sites tiles hybrid rows for libraries with more than one row height; --row-parity trims the row count to an odd or even total; --flip-sites shifts the orientation phase for named sites.

Where it sits

Empty database in, floorplanned database out. It is the entry point of the construction group: no other engine here has anything to place against until the rows exist.

Known bounds

  • Hybrid row patterns are tiled uniformly rather than from a row pattern. Row counts agree on the cases measured, but the construction differs — do not rely on it for hybrid libraries.
  • UPF power domains are not inserted. The floorplan geometry matches; the instance census that follows from domain insertion does not.
  • The -utilization form (derive the die from a target utilization) is not implemented; give the areas explicitly.

See the full CLI reference (generated from --help and --describe).

vyges physical ifp — CLI reference

Generated from vyges physical ifp --help — this page is the tool’s own output, verbatim.

vyges physical ifp — initialize the floorplan: die area, core area, and rows

USAGE:
  vyges physical ifp run <design.odb> --die-area 'x1 y1 x2 y2' --core-area 'x1 y1 x2 y2' --site NAME
  vyges physical ifp run <design.odb> --utilization PCT --core-space 'b t l r' --site NAME
  vyges physical ifp make-rows <design.odb> --core-area 'x1 y1 x2 y2' --site NAME
  vyges physical ifp make-tracks <design.odb> [--track LAYER:xoff,xpitch,yoff,ypitch]... [--out-odb FILE]
  vyges physical ifp --describe
  vyges physical ifp --help

MAKE-ROWS:
  Rows on a die that is ALREADY set: same options as run minus the die, with the core given
  either explicitly (--core-area) or as margins off the die (--core-space).

MAKE-TRACKS:
  Routing tracks over the die, from the technology's own pitches. With no --track, every ROUTING
  layer with a non-zero routing level is taken from the LEF; --track gives one layer explicitly,
  in MICRONS, which is the form a technology's .tracks file uses. Repeatable.

OPTIONS:
  --die-area 'x1 y1 x2 y2'   die rectangle, in MICRONS
  --utilization PCT          derive the die from the placed cell area instead of giving it
  --aspect-ratio R           height/width for the derived core (default 1.0)
  --core-space 'b t l r'     margins in MICRONS, or ONE value for all four; required with
                             --utilization and refused with --die-area
  --core-area 'x1 y1 x2 y2'  core rectangle, in MICRONS
  --site NAME                the base site whose height sets the row pitch
  --additional-sites A,B     also tile rows for these sites (hybrid rows)
  --row-parity NONE|ODD|EVEN trim the row count to a parity (default NONE)
  --flip-sites A,B           shift the row-orientation phase for these sites
  --gap MICRONS              margin around a voltage domain (default: 6 x the site height)
  --out-odb FILE             write the database here (default: IN PLACE, over the input)
  --dry-run                  plan and report, write nothing
  -o FILE                    write the report to FILE instead of stdout
  --json                     emit JSON (the default)
  --describe                 print a machine-readable JSON description of the command

EXIT STATUS:
  0  applied     the floorplan was built and written
  1  refused     the design cannot be floorplanned as asked (empty die, core outside the
                 die, degenerate or mismatched site, or no row fits)
  2  error       usage error, unreadable database, no DBU scale, or a failed write

Contract

Generated from vyges physical ifp --describe.

ifp

floorplan initialization: die area, site-grid snapping, rows, and the core area they cover

Maturity: structured

run {odb}
InputTypeRequiredDescription
odbstringyespath to the design database (.odb)
die_areastringyesdie rectangle in microns, ‘x1 y1 x2 y2’
core_areastringyescore rectangle in microns, ‘x1 y1 x2 y2’
sitestringyesbase site name
out_odbstringnowrite the database here instead of in place
outstringnowrite the report to FILE instead of stdout

Consumes: odb

Artifacts: floorplan_report (report_path)

Assertion: floorplan-built — passes when status equals applied

Provenance limitations

  • input_hash covers the argument vector, not the content of the .odb it names.
  • Implements BOTH forms of initialize_floorplan. Give –die-area and –core-area explicitly, or give –utilization with –core-space and the die is derived from the placed cell area. The two are mutually exclusive, as upstream has them: –die-area with –utilization is refused (IFP-14), and so is –core-area (IFP-20).
  • The utilization form is TWO steps and the intermediate matters. The die is derived first – core_width from sqrt(design area / utilization / aspect ratio) TRUNCATED to a whole DBU, core_height ROUNDED from that already-truncated width – and then snapped to the manufacturing grid. The core is taken back off the SNAPPED die by subtracting the same margins, so it is not the rectangle the die computation laid out. Both are upstream behaviours.
  • Areas are given in MICRONS, matching the upstream Tcl argument, and converted with the database’s dbu_per_micron. A database with no DBU scale is an error rather than an assumed scale.
  • The core’s lower left is snapped UP to the site grid while the upper right is left where it was; the core area finally stored is what the rows COVER, not what was asked for. Both are upstream behaviors and both are load-bearing – a caller that reads back the core area will not always get its own argument.
  • Rows are named globally across sites (ROW_0, ROW_1, …) rather than restarting per site, so adding a site renumbers the rows that follow it.
  • Existing rows are cleared before the new ones are built. Anything already placed on the old row grid is not re-legalized by this engine.
  • Written against the upstream ifp regression goldens at pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c. The algorithm is reimplemented from the published behavior, not transliterated; where the two disagree the goldens are the arbiter.
  • MEASURED against that suite at the same pinned commit, re-run 2026-09-01, on THREE axes. Log lines: 23 cases reproduce every compared IFP-* line exactly, 0 fail, 17 not comparable (6 utilization form, 6 polygon floorplans, 3 that never call initialize_floorplan, 2 that need UPF). Track patterns: 8 of the 8 cases that call make_tracks match the reference database exactly, none skipped. Rows and die area, against the DEF goldens upstream ships: 21 comparable, of which 5 differed until the row cutting and the voltage-domain split landed. The log-line number alone was green throughout all five, which is why it is quoted last.
  • HYBRID SITES are supported: a site with a row pattern tiles the core from that pattern (IFP-0049) and every hybrid site additionally gets rows spanning a whole pattern each (IFP-0050), offset to where its pattern occurs in the base pattern – matching as written (R0) or reversed with orientations mirrored (MX). Row parity is REFUSED on a hybrid floorplan (IFP-0051), because parity would have to trim whole patterns rather than rows.
  • Sites are visited in NAME order and deduplicated by name, not in the order given on the command line – row numbering and log order both follow from this. The site set also includes sites used by placed instances that were never named as arguments (upstream addUsedSites), excluding blocks.
  • VOLTAGE AND POWER DOMAINS split the rows. A row crossing a domain group’s region, or lying within a margin of it, is replaced by up to three pieces: one left of the domain, one right of it, and – only where the row lies wholly inside the domain’s y range – one across the domain itself. The margin is –gap, or 6x the minimum site height when none is given. Rows on PAD sites are never touched. The split happens AFTER the core area and the per-site row counts are settled, so IFP-0001 and IFP-0102 report the floorplan before it.
  • ROWS ARE CUT against the block’s placement blockages, using OpenDB’s own cutRows rather than a reimplementation of it. This runs last and unconditionally; a design that declares no blockage is unaffected.
  • SCOPE – upstream ifp exposes FOUR commands and this engine implements THREE: initialize_floorplan is run, make_rows is make-rows, make_tracks is make-tracks. insert_tiecells is NOT implemented.
  • make-rows builds rows on a die the database already holds and never writes a die of its own. The core is given explicitly or as margins off that die, and an empty die is refused with IFP-63 or IFP-64 depending on which of the two forms was used – upstream uses two codes for the one condition.
  • Known gap – UPF POWER DOMAINS. Upstream’s floorplan inserts power-domain instances and its instance census rises accordingly (16 to 40 on upf_test); this engine inserts none. All floorplan GEOMETRY matches exactly on those cases; what differs is the instance census that follows from the count – IFP-0103, IFP-0104 and IFP-0105 together.
  • A macro larger than the core area is refused with IFP-0002 before anything is snapped or written, matching upstream’s ordering: the die checks come first, so a design with both an empty die and an oversized macro reports the die. Pads and covers are exempt, and a master with R90 symmetry is measured against the core’s larger dimension because it is free to rotate.
  • The instance census (IFP-0103 total instance area, IFP-0104 effective utilization) counts EVERY instance’s master area, including the pads and covers the fit check skips – it is a census of the design, not a question about the core. Utilization is omitted rather than printed as infinity when the core area is zero.
  • make-tracks covers BOTH pitch forms. A layer whose technology carries LEF58_PITCH (FIRSTLASTPITCH) is not one grid at the layer pitch: it expands into a stack of patterns, one per track within the cell row, each repeating on the CORE ROW HEIGHT, with one further pattern past the end. A layer whose x or y offset runs past the die is skipped ENTIRELY – both axes, not just the one that overran.
  • The default output is IN PLACE, over the input database. Pass –out-odb to write elsewhere, or –dry-run to plan without writing.

vyges-mpl — macro placement

Physical construction — opt-in, and not part of vyges install loom. These engines build the design database that the sign-off and verification engines read, so they install as their own group:

vyges install physical   # all of them
vyges install mpl        # just this one

It is also a standalone vyges-mpl binary on your PATH, which is what the dispatch calls and what a flow author targets directly. Source: vyges-tools/mpl.

vyges-mpl places hard macros onto a floorplan — the SRAMs, the CPU core, the crossbars — before standard cells are placed around them. Everything after it is positioned against where the macros landed, so a macro in the wrong place is not something a later stage recovers from.

Place macros where the design says

vyges mpl place-macro design.odb \
  --macro u_cpu=1353,2096,N \
  --macro u_sram0=100,2520 \
  --out-odb placed.odb

X,Y is the macro origin in microns, as a floorplan config states it; pass --dbu to give database units instead. The orientation is R0/R90/R180/R270/MX/MY/MXR90/MYR90, and defaults to R0. Each macro is left FIXED, which is the status a harden flow expects so later placement cannot move it.

This is the manual placement a real flow uses: it corresponds to LibreLane Classic’s Odb.ManualMacroPlacement, not to OpenROAD’s place_macro, and it therefore does no track snapping and no overlap check.

⛔ Macros must arrive UNPLACED

A macro read from a DEF as + FIXED is LOCKED, and OpenDB refuses to move a locked instance — you get ODB-0359 Attempt to change the origin of LOCKED instance. Strip the placement first, or place onto a database that never had one. That is what a floorplan flow hands this step.

What this engine does NOT do yet

  • run is not wired. The hierarchical RTL macro placer is implemented and correlated against OpenROAD — nine gates, byte-exact DEFs — but it is driven by the cluster-dump binary the correlation gates use. --describe advertises a run entry point this binary does not yet keep, and says so.
  • TritonPart (par) is not implemented yet. A flat cluster over the level threshold is refused, never approximated. That is published as a limits field so a caller can test for it rather than meet it mid-run. It matters on a flat synthesized netlist: a design read from a single-module netlist puts every cell in one cluster and reaches that path.

Both are in --describe, which is the contract — read it rather than inferring from this page.

Where it sits

ifp  ->  mpl  ->  tap  ->  pdn

ifp makes the rows; mpl puts the macros on them; tap cuts the rows around what landed and inserts taps and endcaps; pdn builds the grid over the result.

vyges physical mpl — CLI reference

Generated from vyges physical mpl --help — this page is the tool’s own output, verbatim.

vyges physical mpl — hierarchical macro placement

USAGE:
  vyges physical mpl place-macro <design.odb> --macro NAME=X,Y[,ORIENT] [--macro ...]
                                 [--dbu] [--out-odb FILE]
  vyges physical mpl --describe
  vyges physical mpl --help

NOT WIRED:
  `run` -- the automatic hierarchical placement -- is NOT reachable from this command line and
  exits 2 if you ask for it. The pipeline IS implemented and correlated against OpenROAD; it is
  driven by the `cluster-dump` binary, which every correlation gate uses. What is missing is the
  entry point, not the algorithm. It is listed here rather than in USAGE because a usage line is
  a promise.

PLACE-MACRO:
  LibreLane `Classic` step 16, `Odb.ManualMacroPlacement` -- the manual placement a real harden
  flow uses. X,Y are the macro ORIGIN in MICRONS (as the MACROS config states them); pass --dbu
  to give database units instead. ORIENT is R0/R90/R180/R270/MX/MY/MXR90/MYR90, default R0.
  Each macro is left FIXED, which is the status LibreLane's step produces.

STATUS:
  0 applied   macros placed and committed
  1 refused   the design cannot be processed as asked (see LIMITS)
  2 usage     bad arguments, or the design could not be read or written
  3 vacuous   nothing to do -- no unfixed macros

LIMITS:
  This engine does not implement TritonPart (OpenROAD's `par`). A FLAT cluster -- one with no
  module children -- whose leaf standard cells exceed the level threshold is REFUSED rather
  than approximated. Upstream's own mpl suite never reaches that path; a large flat block does.

Contract

Generated from vyges physical mpl --describe.

mpl

hierarchical macro placement over the design database

Maturity: structured

place-macro {design}
InputTypeRequiredDescription
dbubooleannoread –macro coordinates as database units, not microns
designstringyespath to the design database (.odb)
macrostringyesNAME=X,Y[,ORIENT] — repeatable; X,Y is the macro ORIGIN in microns unless –dbu
out_odbstringnowrite the database here instead of in place

Consumes: odb

Artifacts: odb (out_odb)

Assertion: macros-placed — passes when status equals applied

Provenance limitations

  • input_hash covers the argument vector, not the content of the .odb it names.
  • THE run COMMAND IS NOT WIRED. This binary exposes place-macro only – LibreLane Classic step 16, Odb.ManualMacroPlacement, the manual placement a real harden flow uses. The automatic hierarchical pipeline IS implemented and correlated, but it is driven by the cluster-dump binary, which is what every correlation gate uses. place-macro places the macros it is told to place and asserts nothing about where they should go.
  • TritonPart (OpenROAD’s par) is NOT implemented. A FLAT cluster – one with no module children – whose leaf standard cells exceed the level threshold is REFUSED rather than approximated. Upstream’s own mpl suite never reaches that path; a large flat block does, so a refusal here is a real design shape and not a corner case.
  • Correlation is measured against upstream’s own 36-design regression suite at the pin above, per stage rather than on the final output: physical hierarchy and design report 34 of 34 byte-exact each; coarse shaping, boundary push and orientation byte-exact on every case that emits a trace; golden DEFs – macro positions, temporary standard cells, halo blockages and clustering groups – 34 of 34 exact. Those gates drive cluster-dump, not place-macro.
  • Every score is true of ONE upstream commit, named in openroad_pin. The reference moves: a score quoted without its pin says nothing.
  • consumes is the generic odb on purpose. mpl supports running BEFORE or AFTER pin placement and the two give DIFFERENT placements, so a fixed predecessor list would be the wrong shape of claim rather than merely the wrong list.

vyges-tap — well taps and endcaps

Physical construction — opt-in, and not part of vyges install loom. These engines build the design database that the sign-off and verification engines read, so they install as their own group:

vyges install physical   # all six
vyges install tap        # just this one

It is also a standalone vyges-tap binary on your PATH, which is what the dispatch calls and what a flow author targets directly. Source: vyges-tools/tap.

vyges-tap places the physical cells a row needs to be manufacturable but that carry no logic: well taps at a stated pitch, endcaps at the ends of every row and around every macro, and the row cutting that opens holes for macros in the first place.

Run it

vyges tap cut-rows design.odb --endcap-master TAPCELL_X1
vyges tap tapcell  design.odb --distance 20 --tapcell-master TAPCELL_X1 --endcap-master TAPCELL_X1

cut-rows cuts the rows around placed macros; place-endcaps and place-tapcells do one job each; tapcell does the whole sequence in the order the reference does. ripup removes what a previous run inserted, matched by name prefix.

Where it sits

Floorplanned database in, the same database with physical cells inserted out. It runs after ifp and before the power grid, because a tap cell occupies row space the grid must not fight over.

Known bounds

  • Taps and endcaps use different name prefixes (TAP_ and PHY_) so a rip-up can remove one without the other. Passing one prefix for both puts every cell in the right place with the wrong name.
  • ⚠️ An empty rip-up prefix removes nothing, not everything: the literal reading of “every name starts with the empty string” would delete the design.

See the full CLI reference (generated from --help and --describe).

vyges physical tap — CLI reference

Generated from vyges physical tap --help — this page is the tool’s own output, verbatim.

vyges physical tap — row cutting and physical-cell insertion

USAGE:
  vyges physical tap cut-rows <design.odb> [--halo-x UM] [--halo-y UM] [--row-min-width UM]
                                       [--endcap-master NAME]
  vyges physical tap place-tapcells <design.odb> --master NAME [--distance UM] [--tap-prefix P]
  vyges physical tap place-endcaps <design.odb> [--corner NAME] [--edge-corner NAME]
                               [--endcap-horizontal A,B] [--endcap-vertical NAME]
                               [--left-top-corner NAME] ... [--prefix P]
  vyges physical tap tapcell <design.odb> --tapcell-master NAME --endcap-master NAME
                          [--distance UM] [--halo-width-x UM] [--halo-width-y UM]
                          [--cnrcap-nwin-master NAME] [--tap-nwintie-master NAME] ...
  vyges physical tap ripup <design.odb> [--tap-prefix TAP_] [--endcap-prefix PHY_]
  vyges physical tap boundary <design.odb>
  vyges physical tap --describe
  vyges physical tap --help

OPTIONS:
  --halo-x UM            keep-out around a macro, horizontally, in MICRONS (default 2)
  --halo-y UM            keep-out around a macro, vertically, in MICRONS (default 2)
  --row-min-width UM     do not leave a row narrower than this, in MICRONS
  --row-min-height UM    do not leave a row region shorter than this, in MICRONS
  --endcap-master NAME   reserve room for one endcap at each end of every row
  --out-odb FILE         write the database here (default: IN PLACE, over the input)
  --out-def FILE         also write the result as DEF (for diffing against a golden)
  --dry-run              report what would be cut, write nothing
  -o FILE                write the report to FILE instead of stdout
  --json                 emit JSON (the default)
  --describe             print a machine-readable JSON description of the command

EXIT STATUS:
  0  applied     rows were cut and the database written
  0  vacuous     the run changed nothing -- NOT a transformation; read the count
  1  refused     the design cannot be processed as asked
  2  error       usage error, unreadable database, no DBU scale, or a failed write

Contract

Generated from vyges physical tap --describe.

tap

row cutting around macros, and physical-cell insertion (well taps, endcaps)

Maturity: structured

cut-rows {odb}
InputTypeRequiredDescription
odbstringyespath to the design database (.odb)
out_odbstringnowrite the database here instead of in place
outstringnowrite the report to FILE instead of stdout

Consumes: odb

Artifacts: tap_report (report_path)

Assertion: rows-cut — passes when status equals applied

Provenance limitations

  • input_hash covers the argument vector, not the content of the .odb it names.
  • The boundary verb is INSPECT-ONLY: it reports the row-region, edge and corner classification and writes nothing. Every other verb that places (place-endcaps, place-tapcells, tapcell) does mutate the design – they create physical instances, orient, locate, lock and mark them – as does cut-rows, and ripup removes them. This line previously read “NOTHING IS PLACED from it yet, ONLY cut-rows mutates”, which was true of an early build and stayed here after placement shipped and was measured exact; it is corrected rather than deleted because a reader who saw the old text needs to know it moved, not wonder which of two claims to believe.
  • Unnamed endcap positions are filled from the library’s own LEF58 master types (odb reports these as space-separated strings like “ENDCAP LEFTBOTTOMCORNER”, not the enum spelling). Two masters claiming one position is an ERROR naming both, not a coin flip: a wrong endcap is a well-tie fault nobody sees until silicon. A position nothing fills stays empty and places nothing.
  • Taps and endcaps use DIFFERENT default name prefixes – TAP_ and PHY_ – because they are separate namespaces that can be ripped up independently.
  • MEASURED 2026-08-23 against the upstream goldens at pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c: cut_rows 10 of 10 comparable cases exact (DEF ROW diff), and endcap placement 9 of 9 exact – every physical cell matching the golden in master, position and orientation.
  • status is one of applied, planned, vacuous or error. VACUOUS IS NOT APPLIED: it means the run changed nothing – no row cut, no cell inserted, none removed – and the declared assertion passes only on applied, so a no-op fails it rather than reporting a transformation that did not happen. Zero may still be the right answer for the design; read the count and decide. A dry run reports planned, which never claimed to have applied anything.
  • All five commands are implemented: cut-rows, place-endcaps, place-tapcells, the combined tapcell, and ripup. Rip-up matches by NAME PREFIX, which is the only mark these cells carry – they are physical-only instances with no nets. An EMPTY prefix removes nothing rather than everything, which is the difference between undoing a tap step and destroying the design.
  • COMBINED TAPCELL IS 20 OF 20 AT THIS PIN. It was 16 of 16 at the previous pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c and read 14 of 20 the moment the pin moved, with nothing in this engine changed: upstream had reworked endcap placement across sixteen commits and added four regression cases. A SCORE IS ONLY TRUE OF ONE COMMIT – quote the pin beside it. The last case closed by walking a hole the way the reference walks it: the reference receives holes wound like outer boundaries and walks every ring counter-clockwise, while this engine winds holes clockwise, so the classification agreed and the placement ORDER did not – and where two cells contend for one position, whichever is walked to first keeps it.
  • Row cutting itself is odb’s own cutRows from odb/util.h, not a reimplementation: it is odb’s algorithm on odb’s rows, and OpenDB is the substrate. What this engine decides is the policy around it – which instances are blockages, the halo, and the minimum row width.
  • Blockages are placed macros (dbInst::isBlock). A macro that is NOT placed is skipped and reported by name (upstream TAP-32), never silently ignored, because rows would otherwise be left crossing wherever it lands.
  • The minimum row width is the LARGER of two endcap widths and any –row-min-width given, so a caller’s floor cannot quietly produce rows too narrow to cap.
  • Halos and widths are given in MICRONS and converted with the database’s dbu_per_micron. A database with no DBU scale is an error rather than an assumed scale.
  • Written against the upstream tap regression goldens at pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c (vyges-openroad 2026.08.0). Conformance for the implemented subset is measured by diffing the DEF ROWS section against each case’s .defok: 10 of 10 comparable cases exact. ⚠️ A correlation result is a statement about ONE upstream commit – this one was re-measured when the pin moved, and combined tapcell regressed from exact.
  • The corner classification is no longer checked by a separate harness. That check compared this engine’s corner CENSUS against the corner CELLS a golden holds and reported 4 exact and 6 unresolved; it was retired on 2026-08-23 because those 6 could never resolve – upstream classifies every corner too and filters only at placement (getRow), so a corner no row reaches leaves no trace in any golden on either side. All 10 of its cases are compared cell by cell by the DEF gates instead, and the corner TYPE is part of the instance name those compare (PHY_CORNER_ROW_0_OuterBottomLeft_0), alongside master, position and orientation.
  • The default output is IN PLACE, over the input database. Pass –out-odb to write elsewhere, or –dry-run to report without writing.

vyges-pdn — the power distribution network

Physical construction — opt-in, and not part of vyges install loom. These engines build the design database that the sign-off and verification engines read, so they install as their own group:

vyges install physical   # all six
vyges install pdn        # just this one

It is also a standalone vyges-pdn binary on your PATH, which is what the dispatch calls and what a flow author targets directly. Source: vyges-tools/pdn.

vyges-pdn builds the metal that carries power across the die: rings around the core, straps on a pitch across it, follow pins along every standard-cell row, and the vias that stitch the layers together. It is the largest engine in the construction group and the one with the most geometry to get exactly right.

Run it

vyges pdn generate design.odb --out-def pdn.def --power VDD --ground VSS \
  --followpins metal1 \
  --stripe metal4:1.6:56.0:2.0 \
  --ring metal5,metal6:2.0:2.0:2.0

Each --stripe, --ring and --followpins may repeat; --grid partitions the options into several grids, as a design with macros needs.

Where it sits

Floorplanned and tapped database in, power geometry out — as a DEF, or written back into the database. Everything that later analyses power integrity (em-ir) reads what this produced.

Verdicts that are not verdicts

status is generated, vacuous or error. vacuous is not a pass: it means the run laid no metal at all, which usually means an option did not arrive rather than that the design needed nothing. The declared assertion passes only on generated.

Known bounds

  • Argument validation, connect rules and the runtime checks are implemented as diagnostics with the reference’s own message codes; other codes are reported as unimplemented rather than silently skipped.

See the full CLI reference (generated from --help and --describe).

vyges physical pdn — CLI reference

Generated from vyges physical pdn --help — this page is the tool’s own output, verbatim.

usage: vyges-pdn generate <db> --out-def <def> --power <net> --ground <net>
         [--starts-with power|ground]   (default ground)
         [--domain <region>:<power>:<ground>]  (per grid; region domain)
         [--followpins <layer>[:<extend>[:<width>]]]   (micron, repeatable)
         [--stripe <layer>:<width>:<pitch>:<offset>[:<extend>[:<count>[:<snap>[:<spacing>]]]]]
         [--ring <layer0>,<layer1>:<width>:<spacing>:<offset>[:boundary]]
         [--pins <layer>[,<layer>...]]  (shapes there are never shrunk)
         [--split-cuts <layer>:<pitch>[:stagger]]   (micron, repeatable)

   vyges-pdn global-connect <db> --connect NET:PINPAT:INSTPAT:power|ground|signal
         [--connect ...]  [--force]  [--out-odb FILE]
         creates the supply nets and connects matching instance pins to them.
         Patterns are FULL matches, as OpenROAD's are. Without this, `generate`
         refuses: there is no net to build a grid on.
         status is applied or vacuous. VACUOUS IS NOT APPLIED: the rules matched no
         pin and nothing was connected. Exit is still 0 -- a design already wired
         correctly connects nothing on a second run -- so read connections.

   vyges-pdn --describe | --help | --version

⚠️ Shapes are emitted BEFORE trimming, which belongs with the via stage. Compare against
the reference run with `pdngen -skip_trim`.

Contract

Generated from vyges physical pdn --describe.

pdn

power distribution network generation: rings, straps, follow pins and the vias between them

Maturity: structured

generate {odb}

Consumes: odb

Artifacts: pdn_def (def_written)

Assertion: pdn-generated — passes when status equals generated

Provenance limitations

  • input_hash covers the argument vector, not the content of the .odb it names.
  • MEASURED 2026-08-23 against the upstream pdn goldens at pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c: 110 of 110 comparable cases exact on shapes, vias and block terminals, 0 failing, and 9 of 9 on the diagnostic a refused command raises. A SCORE IS ONLY TRUE OF ONE COMMIT – quote the pin beside it.
  • 36 of the suite’s cases are skipped rather than passed, and the reasons are counted, not hidden: 29 build no grid at all, 2 have a reference that built no grid, 2 compute a -pitch in Tcl this translation cannot read, and one each use -existing, repair_pdn_vias and add_sroute_connect.
  • Diagnostics implemented so far: PDN-0003, 0004, 0005 (connect rules), 0106, 0107, 0108, 0114, 0117, 0118, 0191 (argument validation), 0185 and 0215 (runtime). A case whose golden names any other code is skipped with that code named, never silently passed.
  • status is one of generated, vacuous or error. VACUOUS IS NOT GENERATED: it means the run laid no metal at all, and this assertion passes only on generated, so a no-op fails it rather than reporting a grid that was never built. Zero can still be the right answer for the design; read shapes and decide.
  • The engine validates inside the ordinary build path, as the reference does inside addRing and addStrap, so every design it accepts has passed those checks too – the diagnostics are not a separate check mode.
  • Written against the upstream pdn regression suite. The algorithm is reimplemented from the published behaviour and the goldens’ implementation-defined details (snapping, tie-breaks, rounding), not transliterated from the source.

vyges-ppl — IO pin placement

Physical construction — opt-in, and not part of vyges install loom. These engines build the design database that the sign-off and verification engines read, so they install as their own group:

vyges install physical   # all six
vyges install ppl        # just this one

It is also a standalone vyges-ppl binary on your PATH, which is what the dispatch calls and what a flow author targets directly. Source: vyges-tools/ppl.

vyges-ppl decides where a block’s IO pins sit on its boundary. It builds the legal slots from the routing tracks on each layer, then assigns pins onto them to minimise the wire needed to reach them — honouring constraints, groups, exclusions and fixed pins.

Run it

vyges ppl place-pins design.odb --hor-layers metal3 --ver-layers metal2
vyges ppl slots      design.odb --hor-layers metal3 --ver-layers metal2   # inspect the lattice

Where it sits

Floorplanned database in, placed pins out. It runs against the same rows and die that ifp created, and its result is what a router later has to reach.

Known bounds

  • The annealing placement path is not reproduced; the deterministic (Hungarian) assignment is. Cost-equal ties may place a pin at a different legal position than the reference chose, which is a tie rather than a shortfall.
  • Constraints live in the database, not the command line — set_io_pin_constraint writes them onto the ports, and this engine reads them from there.

See the full CLI reference (generated from --help and --describe).

vyges physical ppl — CLI reference

Generated from vyges physical ppl --help — this page is the tool’s own output, verbatim.

vyges physical ppl — IO pin placement: pins on the die boundary, where the wiring is cheapest

USAGE:
  vyges physical ppl slots      <design.odb> --hor-layers L[,L…] --ver-layers L[,L…] [options]
  vyges physical ppl place-pins <design.odb> --hor-layers L[,L…] --ver-layers L[,L…] [options]
  vyges physical ppl --describe
  vyges physical ppl --help

OPTIONS:
  --hor-layers L,…       layers carrying pins on the LEFT and RIGHT edges (required)
  --ver-layers L,…       layers carrying pins on the BOTTOM and TOP edges (required)
  --min-distance D       minimum spacing between pins, in MICRONS
                         (omitted: candidates every 2 tracks — not 'no spacing')
  --min-distance-in-tracks   read --min-distance as a count of candidate slots instead
  --corner-avoidance D   keep pins this far from each corner, in MICRONS
                         (omitted: 2 tracks, capped at 1um)
  --hor-multiplier M     widen pins on the left/right edges by this factor
  --ver-multiplier M     widen pins on the bottom/top edges by this factor
  --slots-per-section N  slots per matching section (default 200)
  --annealing            place by simulated annealing instead of optimal matching
  --temperature T        annealing start temperature (default 1.0)
  --max-iterations N     annealing temperature steps (default 2000)
  --perturb-per-iter N   perturbations per step (default: scaled to the pin count)
  --alpha A              annealing cooling rate (default 0.985)
  --random-seed N        annealing seed (default 42)
  --evaluate FILE        also score a reference placement under the same cost model.
                         FILE is JSON mapping each pin name to an [x, y] pair in DBU.
                         Reports reference_hpwl, so a placement that merely DIFFERS
                         can be told from one that is WORSE.
  -o FILE                write the report to FILE instead of stdout
  --json                 emit JSON (the default)
  --describe             print a machine-readable JSON description of the command

EXIT STATUS:
  0  ok        slots were generated / every pin was placed
  1  refused   no legal pin position on the layers given, or not enough room for the pins
  2  error     usage error, unreadable database, or no DBU scale

Contract

Generated from vyges physical ppl --describe.

ppl

IO pin placement: pins on the die boundary, positioned to minimise the wire needed to reach them

Maturity: structured

place-pins {odb}
InputTypeRequiredDescription
odbstringyespath to the design database (.odb)
hor_layersstringyescomma-separated layers for the left/right edges
ver_layersstringyescomma-separated layers for the bottom/top edges
min_distancestringnominimum pin spacing in microns
corner_avoidancestringnoclearance from each corner in microns
outstringnowrite the report to FILE instead of stdout

Consumes: odb

Artifacts: slot_report (report_path)

Assertion: pins-placed — passes when status equals ok

Provenance limitations

  • input_hash covers the argument vector, not the content of the .odb it names.
  • SCOPE: this build implements slot generation, EXCLUDED REGIONS (exclude_io_pin_region), REGION CONSTRAINTS (set_io_pin_constraint -region edge:lo-hi, by pin name or by direction), PIN GROUPS (-group/-order, including fallback placement for groups too large for a section), MIRRORED PIN PAIRS (-mirrored_pins), ports already fixed by place_pin, and the deterministic assignment of the remaining pins – sections plus optimal (Hungarian) matching within each section. TOP-LAYER placement (define_pin_shape_pattern + -region up:), and the deterministic assignment of the rest. POLYGON (rectilinear) dies, SIMULATED ANNEALING for plain pins, and the deterministic assignment of the rest.
  • ANNEALING (--annealing) reproduces the reference EXACTLY, including its random stream: the reference draws from Boost, whose algorithms are specified and portable, so the engine, both distributions and the shuffle are reimplemented bit-for-bit. Verified by comparing all 86000 perturbations of a run against the reference own debug trace – every cost and delta identical. Scope: plain pins only. A design with groups, constraints or mirrored pairs is REFUSED rather than annealed, because those add move types whose draws would desynchronise the stream and yield a plausible wrong answer.
  • ⚠️ The committed annealing goldens in the reference test suite are STALE: a live run of the pinned build disagrees with annealing1.defok on 49 of 54 pins. Compare annealing against a live run, never against those files.
  • A POLYGON die has no named edges, so its boundary is handled as a list of segments: an edge is a segment, its direction comes from the order of its endpoints, and sections are cut per segment. Five points is a RECTANGLE (the ring repeats its first point); more than five takes the polygon path. LIMITATION: edge-named region constraints (-region bottom:...) are REPORTED AND IGNORED on a polygon die rather than reinterpreted against the bounding box, which would satisfy a constraint the design did not ask for.
  • TOP-LAYER pins are placed on a 2-D lattice INSIDE the die rather than on its boundary, so almost none of the edge rules apply to them: there is no direction to order a group along and no opposite side to mirror to. A lattice position is legal only if a pin of the declared size FITS there – inside the die, and clear of routing blockages, the power grid and fixed ports on that layer by at least the keepout. Non-rectangular grid regions are not handled.
  • A MIRRORED pair is one decision, not two: only one half competes for a position and it is costed for both, its partner taking the reflection of whatever it gets. Mirrored pins are placed before free ones – they need two positions open at once, so they have the least room to manoeuvre. If a reflection is unavailable, BOTH halves are reported unplaced; half a pair is a broken symmetry, not a partial success.
  • MEASURED: all 62 comparable reference cases match the reference total wirelength or beat it – 25 of them position-for-position, the rest by a cost-equal tie. No case is worse, none violates a constraint, and none leaves a pin unplaced.
  • A slot is unusable for two independent reasons, both read from the block: it falls inside an EXCLUDED region, or it is covered by the metal of a port already placed FIXED. An excluded region is strict at both ends – a slot exactly on the boundary is still usable, which is the reference’s own convention.
  • A pin GROUP occupies a contiguous run of slots and is placed before any individual pin, because a single pin dropped into the only long enough run destroys it irrecoverably. -order fixes the sequence, and only changes the result on the top and left edges, whose slot lists run in the opposite direction.
  • A group larger than one section takes a FALLBACK path: the first contiguous run long enough, searched over slot indices rather than sections, so the run may cross an edge. On the top and left edges that path reverses the group unconditionally, where the matched path reverses only when -order is given – a difference inherited from the reference, not a rule with a stated reason.
  • Constraints are read back from the DATABASE, where set_io_pin_constraint stores them on the ports – they are not command-line arguments here. A constrained pin is placed BEFORE any free pin, into sections cut from its own region, and the slots it takes are withdrawn; the reverse order would let a free pin occupy a region a constrained pin has no alternative to.
  • Where two constraint regions OVERLAP, the one with more room per pin is served first, since whoever is served first takes the shared slots. Non-overlapping constraints keep the design’s own order.
  • A constrained pin that does not fit its region is reported UNPLACED, not relocated: the design asked for a region, and somewhere else is not a smaller version of that answer.
  • It reports the chosen pin positions; it does not yet write them to the database, because the pin RECTANGLE depends on pin length and extension handling that is not built.
  • Assignment is optimal WITHIN a section and greedy BETWEEN sections: pins are routed to the cheapest section with room, and only then matched optimally inside it. This is the reference decomposition, not an approximation introduced here.
  • Cost is half-perimeter wirelength over the net bounding box. A net whose driver or loads are unplaced uses the die centre for them, as the reference does.
  • The optimal assignment COST is unique but the optimal PAIRING is not: where two pairings cost the same, this and the reference may place two pins in swapped slots and both be correct. Compare total cost before treating a difference as a defect – --evaluate scores a reference placement under the same cost model for exactly this.
  • Slots are generated on the DIE boundary from each layer’s routing track patterns, minus corner avoidance, minus half the pin width at each end, minus the requested minimum distance.
  • PARTIAL: slot availability accounts for excluded regions and for fixed ports’ metal, but NOT yet for macros or routing obstructions. Where a macro abuts the boundary, availability remains optimistic.
  • The default corner avoidance is resolved once from a layer’s FIRST track pattern and reused for the rest, which is upstream’s behavior and is observable on layers carrying mixed-pitch patterns. Reproduced deliberately.
  • -min_distance_in_tracks with a distance of 0 is a division by zero upstream; here it keeps every candidate. A deliberate divergence, on an input that has no defined meaning.
  • Written against the upstream ppl sources at pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c. The algorithm is reimplemented from the published behavior, not transliterated.

vyges-pad — the IO pad ring and bumps

Physical construction — opt-in, and not part of vyges install loom. These engines build the design database that the sign-off and verification engines read, so they install as their own group:

vyges install physical   # all six
vyges install pad        # just this one

It is also a standalone vyges-pad binary on your PATH, which is what the dispatch calls and what a flow author targets directly. Source: vyges-tools/pad.

vyges-pad builds the chip’s outer edge: the pad ring and its cells, corner and filler pads, the bump array for a flip-chip part, and the RDL routes that connect bumps to pads.

Run it

vyges pad place    design.odb --config pads.json
vyges pad ring     design.odb --config pads.json
vyges pad connect  design.odb --config pads.json

Where it sits

Floorplanned database in, a ringed and bumped database out. It is the outermost structure on the die, and the one that decides where the package can reach.

Known bounds

  • Row bounds for pad placement are computed per strategy; a derived class shadowing the row-start and row-end accessors is the class of bug this engine has been audited for specifically.

See the full CLI reference (generated from --help and --describe).

vyges physical pad — CLI reference

Generated from vyges physical pad --help — this page is the tool’s own output, verbatim.

vyges physical pad — IO pad and bump placement: the ring around the die, and what sits in it

USAGE:
  vyges physical pad make-fake-io-site <design.odb> --name N --width W --height H  (microns)
  vyges physical pad make-io-sites  <design.odb> --horizontal-site S --vertical-site S
                                --corner-site S --offset D [options]
  vyges physical pad place-corners  <design.odb> --master M [--ring-index N] [options]
  vyges physical pad place-pad      <design.odb> --row R --location D [--master M]
                                [--mirror] --inst NAME [options]
  vyges physical pad make-io-bump-array <design.odb> --bump M --origin 'X Y' --rows N
                                   --columns N --pitch 'DX [DY]' [--prefix P] [options]
  vyges physical pad place-pads <design.odb> --row R --insts 'A B C' [--mode M] [options]
  vyges physical pad place-io-fill <design.odb> --row R --masters 'A B C'
                              [--permit-overlaps 'M'] [options]
  vyges physical pad place-io-terminals <design.odb> --pins 'PATTERN...'
                                   [--allow-non-top-layer] [options]
  vyges physical pad rdl-route <design.odb> --layer L [--width W] [--spacing S]
                          [--allow45] [--grid-only] [options]
  vyges physical pad assign-io-bump <design.odb> --bump INST --net N
                               [--terminal INST/PIN] [--dont-route] [options]
  vyges physical pad connect-by-abutment <design.odb> [options]
  vyges physical pad place-bondpad <design.odb> --bond M --insts 'PATTERN...'
                               [--offset 'X Y'] [--rotation R] [--prefix P] [options]
  vyges physical pad remove-io-bump <design.odb> --inst NAME [options]
  vyges physical pad remove-io-bump-array <design.odb> --bump M [options]
  vyges physical pad --describe
  vyges physical pad --help

OPTIONS:
  --horizontal-site S    site tiling the LEFT and RIGHT rows (required)
  --vertical-site S      site tiling the BOTTOM and TOP rows (required)
  --corner-site S        site tiling the four corners (required)
  --offset D             inset from the die on every edge, in MICRONS (required)
  --rotation-horizontal R  rotation applied to the left/right rows (default R0)
  --rotation-vertical R    rotation applied to the bottom/top rows (default R0)
  --rotation-corner R      rotation applied to the corners (default R0)
  --ring-index N         suffix the row names with _N, for a design with several rings
  --master M             the cell to place
  --row R                the IO row to place into (place-pad)
  --location D           where along that row, in MICRONS (place-pad)
  --inst NAME            the instance to place or create (place-pad)
  --mirror               mirror the pad about the row
  --bump M               the bump master (make-io-bump-array)
  --origin 'X Y'         the lower-left bump, in MICRONS
  --rows N / --columns N the shape of the array
  --pitch 'DX [DY]'      spacing in MICRONS; one value means both axes
  --prefix P             instance name prefix (default BUMP_)
  --insts 'A B C'        the pads to spread along a row (place-pads)
  --mode M               uniform | linear | bump_aligned | placer | default
  --out-odb FILE         write the database here (default: IN PLACE, over the input)
  --out-def FILE         also write the result as DEF (for diffing against a golden)
  --dry-run              report the ring, write nothing
  -o FILE                write the report to FILE instead of stdout
  --json                 emit JSON (the default)
  --describe             print a machine-readable JSON description of the command

EXIT STATUS:
  0  ok       the ring was created
  1  refused  the die cannot carry a ring with these sites and offsets
  2  error    usage error, unreadable database, unknown site, or a failed write

Contract

Generated from vyges physical pad --describe.

pad

IO pad and bump placement: the ring of IO rows around the die, and the cells placed into it

Maturity: structured

make-io-sites {odb}
InputTypeRequiredDescription
odbstringyespath to the design database (.odb)
horizontal_sitestringyessite for the left and right rows
vertical_sitestringyessite for the bottom and top rows
corner_sitestringyessite for the four corners
offsetstringyesinset from the die on every edge, in microns
out_odbstringnowrite the database here instead of in place
outstringnowrite the report to FILE instead of stdout

Consumes: odb

Artifacts: ring_report (report_path)

Assertion: ring-created — passes when status equals ok

Provenance limitations

  • input_hash covers the argument vector, not the content of the .odb it names.
  • SCOPE: every command listed in –help is implemented and dispatched – the fake IO site (make-fake-io-site), the ring (make-io-sites), corner and pad placement (place-corners, place-pad, place-pads), IO fill, bond pads, terminals, the bump array and its removal, bump assignment, connection by abutment, and RDL routing. WHAT DIFFERS ACROSS THEM IS EVIDENCE, NOT EXISTENCE: the ring and corner/pad placement are measured against upstream cases (see MEASURED below); the rest are tested but not yet scored against a reference. An earlier version of this line claimed five of these were not implemented long after they shipped – read a scope claim as a statement about EVIDENCE and check it against –help, which is generated from the same dispatch.
  • A cell is refused a position by a LAYER-AWARE check, not a bounding-box one: a fixed instance blocks by box refined by its OVERLAP-layer outline where either side declares one, and anything sharing a layer blocks when the moving cell’s shapes, grown by that layer’s spacing, reach it. A COVER master (a bump) never blocks by box – only by shared metal.
  • SIMPLIFICATION: shape nets are not carried, so two shapes on the same net are treated as a conflict. The reference lets them touch. A cell being created has no nets, which is why every supported case is unaffected; a command placing already-connected cells would need them.
  • ABSENCE, stated because the SCOPE line above lists what EXISTS and is therefore silent about what does not. ONE of upstream’s fifteen commands has no counterpart here: remove_io_rows, which upstream ships and nothing tests. make_fake_io_site WAS also absent and is now implemented as make-fake-io-site, which is what unblocks the two largest pad designs upstream ships – skywater130_caravel and skywater130_coyote_tc both open with it. Three OPTIONS of commands that do exist are not honoured: rdl-route --bump-via and --pad-via name an access via this engine does not build and are now REFUSED with exit 3 rather than accepted and ignored; assign-io-bump --dont-route writes nothing, which is faithful – upstream stores it only in ICeWall::routing_map_, a member of the command object – but it therefore cannot reach rdl-route, which is a separate process here, so a bump upstream would leave alone is routed. Upstream has the same hole across its own write_db.
  • RDL ROUTING is implemented here (rdl-route), not deferred elsewhere: bumps are connected to pads across the face of the die on one thick layer, over a graph built from that layer track grid and thinned so neighbouring wires cannot come closer than the requested spacing. This limitation previously said the router was deliberately out of scope, which stopped being true when it was built.
  • The ring is the die area inset by the offset, corners sized from the corner site, and four edges truncated to WHOLE sites – a remainder that does not fill a site is given up rather than rounded out.
  • A corner’s WIDTH is the larger of the corner site’s width and the horizontal row’s depth, so the row abutting it can be what sets the corner size.
  • The left and right rows are laid on their side when the horizontal and vertical sites are THE SAME SITE, and upright when they differ. The reference compares the site objects; this command compares the names it was given, which is the same thing for a name that resolves to one site.
  • MEASURED: the ring reproduces the reference row output exactly – name, site, origin, orientation, direction, site count and pitch – on all 26 cases that build one, including three real sky130 designs. Pad and corner placement match on all 6 comparable cases.
  • Written against the upstream pad sources at pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c. The algorithm is reimplemented from the published behavior, not transliterated.

vyges-dpl — detailed placement

Physical construction — opt-in, and not part of vyges install loom. These engines build the design database that the sign-off and verification engines read, so they install as their own group:

vyges install physical   # all of them
vyges install dpl        # just this one

It is also a standalone vyges-dpl binary on your PATH, which is what the dispatch calls and what a flow author targets directly. Source: vyges-tools/dpl.

vyges-dpl does two related things: it checks whether a placement is legal, and it legalizes one that is not. Global placement leaves cells overlapping and off-grid; detailed placement snaps every cell onto a site, into a row whose power rails match, without overlapping a neighbour — and moves it as little as it can while doing so.

Run it

vyges dpl check-placement    design.odb                      # is this placement legal?
vyges dpl detailed-placement design.odb --out-odb legal.odb  # make it legal

The checker needs no legalizer to be useful — it is the oracle you point at somebody else’s placement — which is why it is a command in its own right rather than a stage of the other one.

Two legalizers, and the default matters

Negotiated congestion is the default, because it is what upstream defaults to. Cells are allowed to overlap; contested sites accumulate a history cost; each iteration rips every active cell up and re-places it, until nothing overlaps. --use-diamond-legalizer selects the other one — a diamond search outward from each cell’s own position, seating each cell once.

⚠️ The two produce different placements. The report names which ran, because comparing one legalizer’s output against the other’s expected result measures nothing.

The tunables are upstream’s, with upstream’s defaults: --max-displacement (500 sites, 100 rows), --site-search-window (20), --row-search-window (5), --drc-penalty (5) and --disable-window-extension.

Where it sits

Placed database in, legalized database out. It runs after global placement and again after any stage that moves cells — buffer insertion, gate sizing, hold repair — because each of those puts cells back on top of one another.

Verdicts that are not verdicts

check-placement reports clean, violations, vacuous or error; detailed-placement reports legalized, failed, vacuous or error. vacuous is not a pass: it means the run examined or moved no cell at all, and a design with no instances is an absent placement rather than a legal one.

Two fields are emitted on every run, empty or not:

  • not_done / not_checked — the families this engine does not implement. A clean verdict from a partial tool must not read as a complete one.
  • filtered_out — every instance the model filter excluded, counted by master type and placement status. A filter that drops instances silently is indistinguishable from a design that has none of them, and one that dropped 255 tap cells once cost three correct fixes before it was noticed.

Correlation

Legalization matches OpenROAD on 28 of 28 comparable cases from its own regression suite at pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c, including aes (21,340 components), ibex (34,184) and gcd (549).

🔑 The agreement is sweep-level, not final-placement only: the reference’s own per-iteration debug trace and this engine’s match line for line — same cell, same order, same chosen position, every iteration. A matching output can be coincidence; a matching decision sequence is the algorithm.

That is a claim about what the corpus asks, not about every design. 35 of upstream’s 63 detailed_placement cases are outside it and are not scored: 12 ship no golden, 8 need filler placement, 7 declare regions or groups, 7 need placement padding values, 1 needs both.

⚠️ Every score is scoped to one upstream commit. A score quoted without its pin says nothing: the reference moves.

Known bounds

  • Regions and groups are not modelled, so a design declaring them is refused rather than placed approximately.
  • Placement padding values (set_placement_padding) are not implemented. The padding rule runs, with zero padding, so class-pair conflicts are still caught.
  • Two of the four DRC terms are not evaluated — checkEdgeSpacing, which needs each master’s LEF58 cell-edge list, and checkBlockedLayers. Nothing in the comparable corpus exercises either, so their absence is invisible to the score rather than shown to be harmless.
  • Incremental placement (-incremental) is not implemented.
  • -disallow_one_site_gaps has no equivalent on purpose: upstream deprecated it and derives the setting from hasOneSiteMaster(), so the flag cannot change the result. Passing it here is refused with that explanation rather than accepted and ignored.

See the full CLI reference (generated from --help and --describe).

vyges physical dpl — CLI reference

Generated from vyges physical dpl --help — this page is the tool’s own output, verbatim.

vyges physical dpl — detailed placement: legality checking and legalization over the design database

USAGE:
  vyges physical dpl check-placement    <design.odb> [--json] [-o FILE]
  vyges physical dpl detailed-placement <design.odb> [--out-odb FILE] [--dry-run] [OPTIONS]
  vyges physical dpl --describe | --help | --version

OPTIONS:
  --out-odb FILE            write the legalized database here (default: nothing is written)
  --dry-run                 legalize and report, write no database
  --use-diamond-legalizer   use the diamond search instead of negotiation (upstream's flag)
  --max-displacement N[,M]  cap the move at N sites and M rows (default: 500,100)
  --site-search-window N    base search width along the row, in sites (default: 20)
  --row-search-window N     base search height, in rows (default: 5)
  --drc-penalty F           cost added per DRC violation at a candidate site (default: 5)
  --disable-window-extension  do not widen the search window past a macro or a wall
  -o FILE                   write the report to FILE instead of stdout
  --json                    emit JSON (the default)
  --describe                print a machine-readable JSON description of the command

EXIT STATUS:
  0  legalized   every cell was seated; the database was written unless --dry-run
  0  clean       check-placement found no violation
  0  vacuous     the run placed nothing -- NOT a completed legalization; read the count
  1  failed      a cell could not be seated, or a check family found violations
  2  error       usage error, an unreadable database, or a failed write

⛔ SCOPE: legalization runs the NEGOTIATION legalizer, which is upstream's default path;
   `--use-diamond-legalizer` selects the diamond one, as upstream's own flag does. Whichever
   runs, what it does NOT implement is named in `not_done` on every run rather than omitted,
   and every instance the model filter excluded is named in `filtered_out`.

⚠️ Seven of upstream's nine check families are evaluated. `region_placement` and `edge_spacing`
   are reported in `not_checked` rather than passed over in silence, and a family that ran under
   a restriction says so in `limitations`.

ℹ️ `-disallow_one_site_gaps` has no equivalent here ON PURPOSE: upstream deprecated it and
   derives the setting from `hasOneSiteMaster()`, so the flag cannot change the result.
   `-incremental` is not implemented and is named in `not_done`.

Contract

Generated from vyges physical dpl --describe.

dpl

detailed placement: legality checking and legalization over the design database

Maturity: structured

check-placement {odb}
InputTypeRequiredDescription
odbstringyesthe design database to check or legalize
out_odbstringnowrite the legalized database here
max_displacementstringnomove cap, ‘SITES’ or ‘SITES,ROWS’
site_search_windowintegernobase search width in sites
row_search_windowintegernobase search height in rows
drc_penaltynumbernocost per DRC violation at a candidate
outstringnowrite the report to FILE instead of stdout

Consumes: odb

Artifacts: placement_check (report_path), odb (out_odb)

Assertion: placement-legal — passes when status equals clean

Provenance limitations

  • input_hash covers the argument vector, not the content of the .odb it names.
  • LEGALIZATION is implemented and is the default path: the NEGOTIATION legalizer, which is what upstream’s detailed_placement runs when -use_diamond_legalizer is absent. --use-diamond-legalizer selects the other one. The two produce DIFFERENT placements, so the report names which ran.
  • Correlated at pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c: detailed_placement matches the reference on 28 of 28 comparable cases from its own regression suite, including aes (21340 components), ibex (34184) and gcd (549). The agreement is sweep-level, not final-placement only – upstream’s per-iteration debug trace and this engine’s match line for line.
  • BUT 35 of upstream’s 63 detailed_placement cases are OUTSIDE that number and are not scored at all: 12 ship no golden, 8 need filler placement, 7 declare REGIONS/GROUPS, 7 need placement padding values, 1 needs both. 28 of 28 is a claim about what the corpus asks, not about every design.
  • Two of countDRCViolations four terms are NOT evaluated – checkEdgeSpacing (needs each master’s LEF58 cell-edge list) and checkBlockedLayers. Nothing in the comparable corpus exercises either, so their absence is invisible to the score rather than proven harmless.
  • Every instance the model filter excluded is named and counted in filtered_out on every run. A filter that drops instances silently is indistinguishable from a design that has none of them.
  • -disallow_one_site_gaps is NOT accepted: upstream deprecated it (DPL-3/DPL-4) and derives the setting from hasOneSiteMaster(), so the flag cannot change the result. -incremental is not implemented and is named in not_done.
  • SEVEN of upstream’s NINE check families are evaluated: site alignment, placed, overlap, in_rows, padding, blocked_layers and one_site_gap. What is NOT evaluated is named in the report’s not_checked field on every run – region_placement needs regions, edge_spacing needs each master’s LEF58 cell-edge list – because a clean verdict from a partial checker must not read as a complete one. Families that ran under a restriction are named in limitations rather than left to be inferred.
  • Site alignment is CORE-RELATIVE: upstream compares cell->getLeft() % siteWidth where getLeft() is relative to core_.xMin(). Measured on aes.defok, reading it as an absolute coordinate reports every one of 21340 cells misaligned on a design the reference calls clean.
  • A site-alignment failure removes the cell from the overlap comparison entirely. That is a side effect of upstream’s continue, not a separate rule: checkOverlap is what paints a cell into its pixels, so a cell that was skipped is never there for a later cell to collide with.
  • The OVERLAP ACCELERATION differs from upstream deliberately: a rectangle sweep here, a pixel walk there. The predicate is identical and the failing SET matches, but which partner is reported can differ, because upstream reports whichever cell already owns the pixel and that depends on visit order.
  • status is one of clean, violations, vacuous or error. VACUOUS IS NOT CLEAN: it means the run examined no cell, and a design with no instances is an absent placement rather than a legal one.
  • Correlated at pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c in both directions on three designs: aes.defok gives 21340 cells and 0 violations against the reference’s clean verdict; cell_on_block1.def gives 4 site-align failures against Site aligned check failed (4); fragmented_row04.def gives 1 in_rows failure against Placed in rows check failed (1).

vyges-fin — density fill

Physical construction — opt-in, and not part of vyges install loom. These engines build the design database that the sign-off and verification engines read, so they install as their own group:

vyges install physical   # all six
vyges install fin        # just this one

It is also a standalone vyges-fin binary on your PATH, which is what the dispatch calls and what a flow author targets directly. Source: vyges-tools/fin.

vyges-fin fills the empty space on each metal layer with fill shapes, so every layer meets the foundry’s density rules. It is a finishing step: it runs on a routed design and adds metal that carries no signal.

Run it

vyges fin density-fill design.odb --rules fill.json
vyges fin density-fill design.odb --rules fill.json --area '100 100 400 400'   # a sub-region

The rules file states, per layer, the shape sizes allowed and the spacing to keep from anything already there.

Where it sits

Routed database in, the same database with fill added out. Fill is regenerated wholesale rather than patched, so re-running is idempotent rather than cumulative.

Verdicts that are not verdicts

status is filled, planned, vacuous or error. vacuous is not a pass: it means no shape was placed at all. Zero can be right — a design already above every density floor needs no fill — so read fills and layers_filled and decide.

Known bounds

  • prune() is conservative: it forbids fill near two regions closer together than the fill spacing, which may exclude a position that would in fact have been legal.
  • The tiling is a fixed grid anchored at each sub-area’s bounding box, rather than sweeping the tile origin to find a denser packing.

See the full CLI reference (generated from --help and --describe).

vyges physical fin — CLI reference

Generated from vyges physical fin --help — this page is the tool’s own output, verbatim.

vyges physical fin — density fill: metal shapes in the gaps, to meet per-layer density rules

USAGE:
  vyges physical fin density-fill <design.odb> --rules FILE [--area 'lx ly ux uy']
  vyges physical fin --describe
  vyges physical fin --help

OPTIONS:
  --rules FILE     JSON fill rules, per layer (required)
  --area 'l b r t' fill this rectangle, in MICRONS (default: the core area)
  --out-odb FILE   write the database here (default: IN PLACE, over the input)
  --out-def FILE   also write the result as DEF (for diffing against a golden)
  --dry-run        report what would be filled, write nothing
  -o FILE          write the report to FILE instead of stdout
  --json           emit JSON (the default)
  --describe       print a machine-readable JSON description of the command

EXIT STATUS:
  0  filled      fill was placed and the database written
  0  vacuous     the run placed nothing -- NOT a completed fill; read the count
  1  refused     the design cannot be filled as asked
  2  error       usage error, unreadable database or rules, no DBU scale, or a failed write

Contract

Generated from vyges physical fin --describe.

fin

density fill: metal fill shapes placed in the gaps to meet per-layer density rules

Maturity: structured

density-fill {odb}
InputTypeRequiredDescription
odbstringyespath to the design database (.odb)
rulesstringyespath to the JSON fill rules
areastringnofill rectangle in microns, ‘lx ly ux uy’
out_odbstringnowrite the database here instead of in place
outstringnowrite the report to FILE instead of stdout

Consumes: odb

Artifacts: fill_report (report_path)

Assertion: fill-placed — passes when status equals filled

Provenance limitations

  • input_hash covers the argument vector, not the content of the .odb or the rules file it names.
  • status is one of filled, planned, vacuous or error. VACUOUS IS NOT FILLED: it means the run placed no shape at all, and the declared assertion passes only on filled, so a no-op fails it rather than reporting a fill that did not happen. Zero can still be the right answer – a design already above every density floor needs no fill – so read fills and layers_filled and decide. A dry run reports planned, which never claimed to have filled anything.
  • Validated against OpenROAD density fill at a pinned commit across six cases covering a power grid, a macro, a non-rectangular core, a restricted –area and a multi-mask rules file: every fill shape matches in layer, mask, OPC flag and coordinates.
  • Also checked against invariants that need no reference: every fill is a whole shape of a size the rules declare, and no two fills on a layer overlap. Those hold of any correct fill.
  • Existing fill is CLEARED before filling: fill is regenerated wholesale, never patched, so re-running is idempotent rather than cumulative.
  • Non-fill area is the union, per layer, of every placed instance’s shapes, every net’s routed wire boxes (vias decomposed), and every obstruction. A layer the rules do not mention is skipped and reported.
  • The tiling is a fixed grid anchored at each sub-area’s bounding box. Upstream notes KLayout sweeps the tile origin looking for maximum fill and does not do so; neither does this, so fill density is not maximal by construction.
  • prune() is conservative: it forbids fill near two regions that are closer than the fill spacing, which may exclude a position that would in fact have been legal.
  • OPC fill is placed only where the rules state an opc section, and only after non-OPC fill, clearing both the design and the fill just placed.
  • Correlated at pin 945a9f48dc6e5cc91d865daa92c45a1094cb682c: 6 of 6 cases reproduce OpenROAD density fill exactly, fill for fill (26360, 7518, 25095, 12437, 758, 26360 shapes). Only one case has an upstream golden; the rest are ours, scored against an oracle run at our own pin. The algorithm is reimplemented from the published behaviour, not transliterated.
  • MASK NUMBERING is scored, and nothing upstream reaches it: every shipped rules file states datatype as a bare number, which makes num_masks 1 and writes mask 0 on every fill. The sixth case states datatype as a list of three, which is the only thing that turns the path on. Mask assignment restarts in each sub-area and numbers rectangles y-major, matching the reference exactly; both were wrong before that case existed, on identical geometry.

vyges loom — CLI reference

Generated from vyges loom --help — this page is the tool’s own output, verbatim.

vyges-loom 0.1.36 (d0043e7)

The shared design-data foundation — the "loom" the engines weave on. Parses the
standard formats once into a shared in-memory design database. Common, design-wide
commands live here; tool-specific verbs (timing, power, extraction, …) belong to
the engines (built on the vyges_loom library).

USAGE:
  vyges loom <command> [files...] [options]

COMMANDS:
  inspect <files...>   parse files into the design DB and summarize
  check   <files...>   parse-validate files; non-zero exit on any parse error
  version              print version
  help                 print this help

FILE TYPES (by extension):
  .v / .sv   netlist       .json  netlist (Yosys write_json)
  .lib       liberty       .sdc   constraints
  .spef      parasitics    .lef/.tlef  tech-LEF   .def  DEF

OPTIONS:
  --json        machine-readable output (inspect)
  -q, --quiet   fewer diagnostics (repeatable)
  -v, --verbose more diagnostics (repeatable)
  -h, --help    this help
  -V, --version version

Report a bug:      https://github.com/vyges/community/issues/new?labels=bug
Request a feature: https://github.com/vyges/community/issues/new?labels=enhancement
© 2026 Vyges. All Rights Reserved.  https://vyges.com

Contract

Generated from vyges loom --describe.

loom

shared design-data foundation — parse netlist / Liberty / SDC / SPEF / LEF / DEF into the design DB

Maturity: structured

Assertion: none; this operation establishes no engineering claim

Provenance limitations

  • Reports what the named files contain; it does not verify that they describe the same design, nor that they are the files a flow actually consumed.

vyges-layout — the geometry kernel

Foundation, not a sign-off engine. vyges-layout is the layout geometry kernel the other layout-side tools ride on — it does not produce a sign-off verdict or standard artifact the way the Loom engines do, and it’s not promoted as a vyges loom subcommand (it’s an internal foundation, used as a standalone vyges-layout binary). It’s listed here because it’s the substrate beneath the suite.

Everything on the layout side is geometry: LVS device extraction (GDS → devices + nets), DRC (width / spacing), metal fill, and viewing a die — they all need to read GDSII, do boolean operations on shapes, and flatten hierarchy. vyges-layout is that shared base: a clean-room, memory-safe Rust kernel, std-only and auditable, peer to KLayout’s database and gdstk (which are excellent but C++). It is the dependency that unblocks vyges-lvs native GDS extraction, and the future DRC, fill, and chip viewer.

Run it

vyges-layout info    block.gds                 # cells, layers, areas, bbox (+ --json)
vyges-layout boolean block.gds --top top --op and --a 67/20 --b 68/20 --out 100/0 -o and.gds
vyges-layout flatten block.gds --top top -o flat.gds
vyges-layout demo                              # built-in, no files

See the full CLI reference (generated from --help).

What it does (v0)

  • GDSII read/write — round-trips a library (BOUNDARY / PATH / SREF / AREF / BOX).
  • info — cells, per-cell element counts, layers, per-layer area — text or JSON.
  • boolean — AND / OR / NOT / XOR between two layers via a vertical scanline on rectilinear (Manhattan) polygons; integer coordinates → exact.
  • flatten — expand SREF / AREF hierarchy into one cell (composed transforms, arrays, cycle-guarded).

Honest bounds. v0 boolean is Manhattan — exact for any rectilinear polygon, returned as a set of tiling rectangles (contour-tracing into merged polygons is depth); general-angle geometry is bbox-approximated and counted, never silently dropped. General clipping (Vatti) is the depth pass.

Digital and analog / mixed-signal

The kernel has zero digital-vs-analog notion — it operates on raw GDS layers, datatypes and polygons, not on standard cells or any clocked abstraction. So an analog / mixed-signal layout round-trips and booleans exactly as a digital one does. The examples/analog/ fixture proves it on the shapes an analog designer actually draws — a guard ring (a donut polygon), a MIM capacitor (two overlapping plates on different layers), and a poly resistor snake — and tests/analog.rs checks exact write→read round-trip plus a cross-layer boolean (top-plate AND bottom-plate = the MIM cap area).

cargo run --example gen_analog                                   # generate examples/analog/analog.gds
vyges-layout info    examples/analog/analog.gds                  # cells, layers, areas — analog block
vyges-layout boolean examples/analog/analog.gds --top analog --op and --a 66/20 --b 65/20 --out 100/0 -o mim.gds

The Manhattan / rectilinear bound noted above is geometric, not domain-related — it applies to digital and analog shapes alike. Scope here is physical geometry; analog functional / timing sign-off is out of scope (that leans on external SPICE / behavioral tools).

Source & releases

vyges-layout — CLI reference

Generated from vyges-layout --help — this page is the tool’s own output, verbatim.

vyges-layout — layout geometry kernel (GDSII + OASIS read/write, boolean ops, flatten)

usage:
  vyges-layout info    LAYOUT [--json]
  vyges-layout boolean LAYOUT --op and|or|not|xor --a L/D --b L/D --out L/D -o OUT [--top C]
  vyges-layout flatten LAYOUT --top CELL -o OUT
  vyges-layout demo

LAYOUT / OUT may be GDSII (.gds) or OASIS (.oas/.oasis) — the format is picked by
extension, so the tools double as a GDS↔OASIS converter. `L/D` is layer/datatype,
e.g. 68/20. `boolean` operates on one cell's own shapes (flatten first for
hierarchy); v0 boolean is Manhattan (axis-aligned rectangles).

flags:
  --op OP        and | or | not (A−B) | xor
  --a L/D        first layer/datatype     --b L/D   second layer/datatype
  --out L/D      output layer/datatype    --top C   cell to operate on
  -o FILE        output GDS / report file
  --json         machine-readable output (info)
  -q/--quiet · -v/--verbose · -h/--help · -V/--version
  --bug-report · --feature-request · --sponsor · --star ⭐

vyges-events — structured events & logging

Foundation, not a sign-off engine. vyges-events is the shared structured-event and logging contract that every Loom engine emits — and the substrate the MCP layer and cross-stage analysis consume. Like vyges-layout it’s a foundation crate, not a promoted vyges loom subcommand.

In an LLM/MCP-driven flow you need a queryable trail of what each tool did and why, so a late failure can be traced back to its early cause. vyges-events is that trail: each engine emits one structured vyges-events/1.0 event per finding, plus a completion event, on stderr, as newline-delimited JSON. The report/data on stdout is never touched.

Logs and events are one path

A plain log line is just an event with no code/objects. The same records render two ways, controlled by two environment knobs:

VariableEffect
VYGES_LOG=<level>severity filter — trace|debug|info|warn|error (default info)
VYGES_LOG_FORMAT=json|textforce the rendering; default auto — human text at a terminal, JSONL when piped
vyges loom drc check block.gds --rules deck.rules      # report → stdout; events → stderr
#  at a terminal:   [WARN  vyges-drc DRC-WIDTH] width < min on layer 66  [layer:66]
#  when piped:      {"schema":"vyges-events/1.0",…,"code":"DRC-WIDTH","objects":["layer:66"]}

VYGES_LOG=error vyges loom drc check block.gds --rules deck.rules   # only errors on stderr

The event

FieldMeaning
schemaalways vyges-events/1.0
ts_msunix epoch milliseconds
toolemitting engine (vyges-drc, vyges-lvs, …)
severitytrace/debug/info/warn/error
codestructured code (DRC-WIDTH, LVS-MISMATCH, …) — the clustering key
objectsdesign objects named (net:data[3], cell:sram0, layer:66) — the cross-stage co-reference key
raw_msg / msg_templatethe message (full / with variable parts masked)
run_id / step_index / stageorchestration context (stamped by the runner)
filesource location, if applicable

The full JSON Schema is self-published by the crate — dump it with vyges-events --schema.

How it flows (the causal trail)

  • Engines emit events on stderr.
  • The MCP layer (vyges mcp / vyges model) parses them into the loom-result envelope’s logs block (a compact summary — counts by severity, the codes seen — plus the events), and streams each line live as an MCP notifications/message while a long tool call runs, so you see progress instead of a capture-at-end blob.
  • A vyges model run aggregates every step’s events into ~/.vyges/runs/<run_id>/events.jsonl — one ordered event stream across the whole multi-step flow, each event stamped with its run_id and step_index.

Because every event carries the design objects it touched, a late failure (e.g. a routing DRC error) can be linked back to the early event that caused it (e.g. a synthesis warning on the same net) — cause to effect, across stages.

For engine authors

Depend on the crate (vyges-tools/events, Apache-2.0) and emit via standard tracing macros:

#![allow(unused)]
fn main() {
tracing::warn!(code = "DRC-0142", objects = "net:data[3],macro:sram0", "spacing < min");
}

or build an Event directly. See the crate README for the tracing bridge and the sink (default: JSONL on stderr). Emission is daemonless — each engine writes locally; the orchestrator aggregates.

Job-file formats

Every engine is driven by a small declarative job file — you describe the job, not the script. Each format is documented with worked examples in its engine’s repository (the examples/ directory, linked from each engine’s chapter).

Sign-off · analyze — describe the job, get a standard sign-off artifact:

FormatEngineDeclares
.char / .charlibvyges-charcorner(s), cells, SPICE collateral
.extvyges-extractdesign, PDK parasitic rules, settings
.pwrvyges-powernetlist, libraries, activity source, budget
.stavyges-sta-silibraries, parasitics, constraints
.emirvyges-em-irdesign, PDN, IR/EM budget
.thermalvyges-thermaldie + grid + material params, floorplan, limit
.lvsvyges-lvslayout (GDS) + schematic netlist

Optimizers · act — netlist in, better netlist out (each move scored by sta-si):

FormatEngineDeclares
.resizevyges-resizenetlist, libraries, (SPEF), timing / area goal
.vtswapvyges-vt-swapnetlist, libraries, (SPEF), leakage / timing goal
.bufinsvyges-buffer-insertnetlist, libraries, (SPEF), transition limit

Shared CLI conventions

Every engine shares the same surface (<engine> = vyges loom <name>, e.g. vyges loom sta-si):

vyges loom <engine> run   JOB [-o OUT] [--json]   run the job → standard artifact (or JSON)
vyges loom <engine> check JOB                      parse + validate the job
vyges loom <engine> demo                           built-in example, no inputs

Common flags: --json, -q/--quiet, -v/--verbose, -h/--help, -V/--version, plus the central --bug-report / --feature-request / --sponsor. The engines that gate on a budget/limit add a fail flag — sta-si, em-ir, thermal use --fail-on-violation; power uses --fail-on-budget — returning a distinct non-zero exit code so a violation fails CI automatically.

-V/--version prints the exact build commit (e.g. vyges-char 0.0.1 (abc1234)) so a bug report traces back to a precise build.

Integrating the engines into a flow

The engines are the Vyges Loom suite — end users run them as vyges loom <engine> after a one-time vyges install loom. Under the hood each is a standalone binary that cooperates by handing the next one standard files (Liberty, SPEF, SPICE) — see the data spine. This page is for tool and flow authors (OpenROAD / LibreLane / OpenLane 2 / custom orchestrators): where each engine plugs in, and how to call it directly as a binary (the leanest integration — no vyges front door required).

Where each engine plugs into an OpenROAD / LibreLane flow

There is no single integration point — each engine is a drop-in second opinion / alternative at its own stage:

EngineFlow stageSits beside (OSS)Adds
vyges-charcell characterization (upstream)Liberatea .lib when you don’t already have one
vyges-extractparasitic extraction (post-route)OpenRCXcoupling-aware SPEF (the SI input)
vyges-powerpower analysis (post-synth / post-route)OpenSTA report_powerleakage + dynamic, and the activity map em-ir needs
vyges-sta-sistatic timing (signoff)OpenSTASI/crosstalk + statistical (AOCV/POCV-LVF) OCV
vyges-em-irpower integrity (PDN)PDNSimIR-drop / EM second opinion
vyges-lvslayout-vs-schematic (signoff)NetgenMATCH/MISMATCH with the divergence named; native GDS extraction

“Just integrate sta-si?” Start there — it’s the highest-value single drop-in (the SI-aware timing answer base OpenSTA can’t give). Then adopt the rest à la carte, each at its own stage — not “through sta-si”: extract deepens sta-si’s SI with coupling SPEF; power feeds both a power number and em-ir; em-ir is parallel (power integrity, not timing); lvs is layout-correctness at sign-off; char only if you lack a .lib.

Already on OpenLane / LibreLane? Drop these in.

These are sign-off engines, not another flow — single std-only binaries, seconds not stages, no nix store and no 60-stage orchestration just to sanity-check timing. You keep running LibreLane for place-and-route; you just enter it cleaner and verify its output with a faster, more legible checker. Because their inputs differ, they sit at two points:

Right after Yosys (pre-P&R) — shift the gate left. Catch a broken constraint, a timing wall, or a power problem before you spend an hour in P&R, so you re-spin the heavy flow fewer times.

EngineRuns onCatches early
vyges-sta-sisynth netlist + .lib + SDCbroken constraints, a timing wall, bad clock setup
vyges-powersynth netlist + .lib + VCD/vectorlessa power / activity problem while it’s still cheap to fix

After the flow produces layout — independent fast sign-off. Run on LibreLane’s own DEF/GDS for a second opinion — faster than re-spinning the flow, and it names what diverges.

EngineRuns onRole
vyges-extractrouted DEFSPEF — second opinion vs OpenRCX (0.997 on a routed block)
vyges-sta-sinetlist + real SPEFpost-route timing with the actual parasitics
vyges-em-irPDN + currentsIR-drop / electromigration
vyges-lvsextracted GDS + schematicMATCH / MISMATCH with readable diagnostics vs Netgen

Be precise about “faster convergence.” These don’t touch LibreLane’s optimizer, so they don’t make P&R itself faster. The honest mechanism is two-fold, and both are real: (1) cleaner entry → fewer expensive flow spins — catching a broken SDC or a timing/power wall post-Yosys means you don’t discover it 50 minutes into P&R and re-spin; (2) an independent verdict that catches what the flow’s own tools miss or report cryptically (vyges-lvs vs Netgen, vyges-extract vs OpenRCX, vyges-sta-si vs OpenSTA). char isn’t in this loop — it builds libraries, not per-design checks.

Three ways to integrate — leanest first

Every engine is a plain binary: a declarative job in, the standard artifact + JSON out, a CI-gating exit code. Any tool, any language, just shells out:

vyges loom sta-si  run timing.sta --json  # → {"wns_ns": -0.05, "tns_ns": -0.2, ...}  exit 3 if WNS<0
vyges loom extract run top.ext            # → SPEF
vyges loom char    run cells.char         # → Liberty .lib
vyges loom em-ir   run top.emir           # → IR/EM report

No plugin, no interpreter, no linking — the binary is the contract.

1. An adapter for the incumbent’s script format (where one exists)

For teams with existing scripts: e.g. vyges-sta-si ships an experimental OpenSTA-TCL-subset adapter (vyges loom sta-si tcl script.tcl) so an existing OpenSTA script runs through the engine with zero rewrite. (See the sta-si repo’s docs/opensta-integration.md.)

2. An orchestrator step — for native flow metrics

For LibreLane flows that want the result as native metrics next to OpenSTA’s, the sta-si repo ships a LibreLane plugin Step (integrations/librelane/) that reads the flow State and emits vyges__… metrics — the SI second opinion becomes a metric comparison.

We want to help

Integrating a Vyges engine directly into your tool — and have a question, a missing job-file field, or a JSON-schema tweak you need? We’re happy to help and to shape the interface to your flow. Reach us at https://vyges.com/contact.

Deeper, per-engine integration docs (the OpenSTA boundary, the LibreLane step, the full cross-engine map) live in each engine’s repository under docs/ and integrations/ — linked from that engine’s chapter above.

Tool descriptor — --describe

Every Vyges engine can describe itself: run vyges-<engine> --describe and it prints one JSON object saying what it is, how to call it, what it produces, and what claim its result may establish about your design.

vyges loom drc --describe

One probe is enough to drive an engine you have never seen. That is what lets vyges mcp hand an AI IDE a typed tool for each engine — real per-parameter schemas rather than an opaque argument string — and it is equally usable from your own orchestrator, CI job, or script.

The descriptor is emitted by the binary, so it cannot drift from the code that implements it. If an engine gains a flag, its descriptor gains it in the same build.

The schema field

Every descriptor names the format it speaks:

"schema": "vyges-tool-descriptor/1.1"

Check the major, ignore the minor. The format grows additively, so a reader that understands 1.x can consume a 1.7 payload by ignoring fields it does not recognise; a 2.x payload is the one that needs attention. This is deliberately not the engine’s release version — engines release from their own repos, and a newer engine driven by an older caller is fine as long as both speak the same descriptor format.

A payload with no schema predates the convention. Treat it as an older build rather than a broken one: read what you recognise and carry on. vyges modules reports this per engine in its CONTRACT column, where pre means exactly that.

Who this page is for: anyone driving the engines programmatically — flow authors, orchestrators, CI, and agent tooling. If you just want to run an engine by hand, --help is the friendlier surface.

A complete descriptor

This is the real output of vyges loom em-ir --describe:


{
  "schema": "vyges-tool-descriptor/1.1",   // the descriptor format this payload speaks
  "name": "em-ir",
  "summary": "EM / IR-drop power-integrity sign-off (PDN -> report)",
  "maturity": "workflow-validated",
  "provenance_limitations": [
    "The job names the PDN or DEF/LEF and any power and decap maps; input_hash covers the job path and arguments, not their contents.",
    "Solved on a real block: a routed sky130 PDN extracting to 5308 nodes solves in 0.46 s. The solver is conjugate gradient with a Jacobi preconditioner; it was Gauss-Seidel, which on that same block stopped short of tolerance after 50000 sweeps and returned an error rather than a result.",
    "Partially correlated against OpenROAD PDNSim on one routed sky130 block, at the level of the extracted network rather than the solved voltages (the solver above does not get that far). Per-layer total resistance against PDNSim's own network: met1 1.003x, met4 1.005x, met5 1.013x, and vias exact at 1635.725 ohm over 1895 vias against 1635.7249999999501. No voltage or IR-drop figure has been correlated against any other tool.",
    "Wire resistance (rho_sq * L/W) and the per-square resistance itself agree with PDNSim's model under a default-RC flow; a flow that sets custom layer RC moves PDNSim's resistance and not ours, and neither tool reports the divergence.",
    "Via resistance is the cut layer's per-cut LEF RESISTANCE divided by the cut count taken from the DEF VIAS definition. Keyed on the layer pair as well as the point, because a PDN via stack places several definitions at one coordinate.",
    "Voltage sources are the power pin's port shapes where the design declares them, falling back to every pad_layer node only when it does not -- PDNSim's own precedence. On the correlated block the declared pin covers 683 grid nodes where the fallback holds 28, and it moves the answer by 4.1x: worst drop 0.27% under the fallback against 0.06% under the declared pin. Which way that runs is DESIGN-DEPENDENT, turning on how much of the grid a pin covers, so it is measured per design rather than assumed.",
    "Correlated against OpenROAD PDNSim across SIX routed sky130 blocks, fed the same per-instance currents and compared against a PDNSim run from the same build: worst-IR-drop ratios 1.019, 1.001, 0.994, 0.997, 1.018 and 1.015 -- all six within 1.9%, spanning 35 uA to 1.16 mA and 2500 to 19700 grid nodes. PDNSim's values carry two to three significant figures at these magnitudes, so this is near the floor the comparison resolves.",
    "Instance current enters the rail at the CELL CENTRE when a cell_lef supplies MACRO SIZE, else at the DEF origin. Both were measured against PDNSim: landing current on the nearest pre-existing grid node under-reported worst IR drop by 3.2x, and using the DEF origin rather than the cell centre displaced every load by half a cell width, worth 8.4% on a block of wide cells and invisible on a block of small ones. Supply a cell_lef for wide-cell designs.",
    "Only the WORST node is comparable between the two engines: PDNSim's voltage file reports one row per INSTANCE TERMINAL, this engine reports one row per GRID NODE, and those sample the same field at different places and in different proportions. On one block PDNSim's median drop is exactly 0.0, because most of its rows sit on the filler and decap cells packed against the supply straps, and the p75 ratio reads 11.3. So percentile-to-percentile comparison is not like-for-like at any percentile; an earlier version of this descriptor read a 4-8% one-sided residual out of exactly such a comparison, and that is withdrawn as an artefact.",
    "Precision bound: PDNSim's voltage file prints six decimals, so at these magnitudes 1 uV quantisation is a few tenths of a percent even at the worst node. Agreement is to the precision the oracle publishes.",
    "The oracle is REGENERATED per run from the same binary. Archived LibreLane net-*.csv voltage files are not a safe baseline: on three of six blocks a fresh PDNSim run on the same .odb disagreed with the archived one by 1.32x, 1.37x and 10x. The builds differ (archived reports lack the Total power line a current build prints); the cause of the disagreement is not established.",
    "Instance current enters at a tap point on the rail: the instance's placement projected onto its nearest rail segment, which is split there. Landing current on the nearest PRE-EXISTING node instead under-reported worst IR drop by 3.2x, because the current never crossed the rail resistance between the cell and that node. Projection is axis-aligned only; an instance that cannot be projected falls back to the nearest node and is counted.",
    "Node counts are not comparable with PDNSim by construction: it resamples nodes on a minimum pitch, this engine places one per polyline point.",
    "Dynamic (transient) IR has NO oracle: PDNSim is static-only, so nothing exists to correlate it against. It is checked instead against exact analytic cases (with no decap the solve is exactly quasi-static, peak = ipk*R to 1e-9) and construction invariants (linear in switch energy, coincident switches superpose, decap monotonically removes droop), all mutation-checked. Runs at scale: 13292 nodes in 22 s, 248 MB.",
    "Transient limits, which matter more than its accuracy: every instance switches at ONE global switch_t_ns, so the result is worst-case-simultaneous switching -- a strict upper bound, not a waveform; the timestep is implicit at min(switch duration)/10 and cannot be set, so accuracy cannot be traded for runtime and convergence cannot be demonstrated; and only the worst droop is reported, with no waveform exposed.",
    "EM: PDNSim reports per-segment current but applies no current-density limit and issues no verdict, so only the numerator can be correlated. Maximum segment current per layer, which is what a limit is compared against, across three routed sky130 blocks: met1 1.022/1.026/1.035, via 1.008/1.016/0.992. On the ~10% of segments with an exact geometric counterpart, restricted to those carrying at least 1% of peak current, 92-99% agree within 10% (median 0.997-1.003). The DC/RMS/peak LIMIT check has no counterpart and remains this engine's own."
  ],
  "invocation": {
    "args_template": [
      "run",
      "{job}"
    ],
    "optional": [
      {
        "arg": "out",
        "flag": "-o"
      }
    ],
    "emits_json": true
  },
  "inputs": {
    "type": "object",
    "required": [
      "job"
    ],
    "properties": {
      "job": {
        "type": "string",
        "description": "Path to the EM/IR-drop job file (PDN + limits)."
      },
      "out": {
        "type": "string",
        "description": "Write output to FILE instead of stdout."
      }
    }
  },
  "artifacts": [
    {
      "role": "emir_report",
      "field": "report_path"
    }
  ],
  "assertion": {
    "id": "power-integrity-met",
    "field": "pi_met",
    "pass_when": {
      "is_true": true
    }
  },
  "consumes": [
    "pdn",
    "power_report",
    "spef",
    "emgeom"
  ]
}

Given only that, a caller can build the command, validate arguments before spending a run, find the report afterwards, and read a pass/fail verdict — without knowing anything else about power integrity.

Top-level fields

FieldRequiredWhat it is
nameyesStable tool id (drc, sta-si, …). The MCP tool takes this name.
summaryyesOne line, shown as the tool’s description.
invocationyesHow to build the command — see below.
inputsnoJSON Schema for the callable parameters. Defaults to an empty object schema.
artifactsnoThe files the run produces, and how to locate them.
assertionnoHow to derive the engineering verdict. Omitted → the result is unknown.
maturitynoHow far the evidence has been proven. Omitted → discovered, which suppresses the verdict.
provenance_limitationsyesWhat input_hash does not cover, in the engine’s own words.
consumesnoInput artifact roles the engine expects (e.g. ["netlist","liberty","spef"]). Declarative today — published for flow authors, not interpreted by the CLI.

A payload without a usable invocation.args_template is not a descriptor; callers should fall back to passing raw CLI arguments.

invocation — building the command

"invocation": {
  "args_template": ["check", "{gds}", "--rules", "{deck}"],
  "optional": [ { "arg": "top", "flag": "--top" } ],
  "emits_json": true
}
  • args_template — the argument vector. A {name} token is substituted with the caller’s value for name. Tokens are required: a missing one is a caller error, not a default.
  • optional — appended only when supplied. With flag, the pair --top TOP is appended; without one, the bare value is appended.
  • emits_json — when true (the default), callers append --json if it is not already present, so the engine’s machine-readable output comes back.

For the descriptor above, {job: "block.emir", out: "emir.rpt"} builds:

vyges loom em-ir run block.emir -o emir.rpt --json

inputs — the parameter schema

A standard JSON Schema object describing the callable parameters. vyges mcp passes it through as the MCP tool’s inputSchema, which is why an engine’s parameters appear individually named and documented in an AI IDE. Use it to validate a call before running anything.

Keep name and inputs in mind as the compatibility surface: changing either changes how every existing caller must invoke the tool.

artifacts — finding what the run produced

Each entry names a role and says where the path comes from:

"artifacts": [
  { "role": "timing_report", "field": "report_path" },  // path comes from the engine's --json
  { "role": "sdf", "from_arg": "sdf" }             // path is the value of an input argument
]

An entry needs a source. role alone says a file exists but not where to find it, so the registry drops it — the role then has no effect and the file never appears in the result. Pair every role with a field or a from_arg, and expose the output argument in invocation.optional so a caller can ask for it in the first place.

Prefer field for anything the engine writes. A sign-off engine given -o FILE reports the path back as report_path in its --json, so the artifact is located from the result rather than by echoing an input. That also means the payload still arrives on stdout when -o is used — -o writes the report, it does not redirect the machine output — so one call yields both the verdict and the artifact. Use from_arg where the path really is just an input the engine wrote to and does not echo back (sta-si’s --sdf, for instance).

KeyMeaning
roleWhat the file isdrc_report, timing_report, lvs_report, netlist, svg, …
fieldKey in the engine’s --json output holding the path.
from_argInput-argument name whose value is the path (for -o-style outputs).

Roles are not just labels. vyges mcp derives a tool’s read-only vs mutating classification from them — an engine that produces a netlist edits your design, one that produces a drc_report does not — which is what the VYGES_MCP_PROFILE tiers gate on. See read-only vs mutating tools.

assertion — the design verdict

An engine run has two independent outcomes: whether the process succeeded, and what the evidence says about your design. assertion declares how to derive the second from the engine’s own --json output.

"assertion": {
  "id": "drc-clean",          // stable name for the claim
  "field": "clean",           // key in the engine's --json output
  "pass_when": { "is_true": true },
  "summary_field": "summary"  // optional: a human-readable line to surface
}
pass_whenPasses when the field is
{ "is_true": true }boolean true
{ "eq": <value> }equal to that value (numbers compare numerically)
{ "lte": <number> }a number less than or equal to it

Alternatively { "id": "...", "not_applicable": true } declares that the operation makes no engineering claim — a viewer or format converter — which is different from having one and not reaching it.

The derived verdict lands in the loom-result envelope as engineering.status (pass | fail | unknown | not_applicable), separate from the execution status — see the result envelope. The rules are deliberately conservative: no declared assertion, a run that did not complete, or a verdict field that is missing, null, or the wrong type all yield unknown — never pass, and never fail. Nothing is reported as passing on absent evidence, and a tooling defect is never reported as a design defect. See the two status axes.

Declaring one well

The verdict field must cover the whole check. Two traps are worth naming, because both occurred in this suite and both would have overstated the result:

  • A partial verdict. Timing has setup and hold; power integrity has IR drop and electromigration. Asserting on a field that covers only one half passes designs that fail the other. Where an engine reports several partial verdicts, assert on a combined field.
  • A genuinely inconclusive state. Some checks can finish without reaching a conclusion — a layout-vs-schematic MATCH the bounded search could not confirm, or a timing run with no endpoints to analyze. Emit the verdict field as null in those cases rather than forcing a boolean; null resolves to unknown, which is the honest answer.

maturity — how far the evidence has been proven

Separate from what a verdict says is how much it can be relied on. maturity is that second axis, and it is deliberately about the engine, not the design.

LevelMeans
discoveredThe binary resolves and reports a version. Nothing about its output is guaranteed.
structuredIt publishes a versioned operation and a normalized result — a descriptor like this one.
workflow-validatedIts repository additionally carries a pinned design or fixture that the test suite runs end to end and asserts against, so the operation is proven on a real input, not merely described.

It gates the verdict, it is not decoration. At discovered there is no validated result shape behind an assertion, so the claim is not reportedengineering.status stays unknown however well-formed the assertion is. Absent or unrecognized means discovered: an engine has to say more to claim more, the same principle as an absent assertion.

The level also rides along in the generated MCP tool description, so an agent weighing a result sees it without a second call.

Where the Loom engines stand

Published because publishing your own gaps is the point — a ladder nobody is ever on the bottom rung of would say nothing.

LevelEngines
workflow-validatedsta-si · em-ir · lvs · extract · thermal · power
structuredloom · char · drc · cdc · glitch · lec · gds-view · meas · remap · resize · vt-swap · buffer-insert · hold-fix · ant · ifp · mpl · tap · pdn · ppl · pad · fin · dpl

The structured engines are not less correct — several carry substantial unit-test suites, and some are correlated against a reference tool on hundreds of cases. They simply do not yet ship a pinned end-to-end case in-repo, and the ladder reports what is proven there, not what is believed or what is proven elsewhere. dpl, for instance, matches OpenROAD on every comparable case of its own regression suite — but that harness lives outside the repository, so the rung stays structured.

⛔ An invalid level is not a modest claim — it is a discarded result

maturity is a closed enum, and the parser returns None for anything outside it. An unrecognized value therefore lands on discovered, where can_assert() is false and the verdict is suppressed to unknown — however well-formed the assertion is, and however correct the engine.

⚠️ Four engines shipped one at once, each chosen to sound honest about being incomplete: ppl, pad and dpl said partial; pdn said correlated. All four were silently throwing their own verdicts away, and none of them had a test on the field. Fixed 2026-09-02, with a guard added to each.

🔑 The rung describes the shape of the EVIDENCE, not feature completeness. There is no rung for “works, but not all of it” and there should not be: what is unbuilt belongs in provenance_limitations, which is required and can carry the nuance a single word cannot. Reach for that field, never for a new level.

provenance_limitations — the boundary of the hash

Required. Every descriptor must state what input_hash does not cover.

The reason it is required rather than encouraged: the boundary is easy to leave implicit and expensive to discover later. input_hash is taken over the resolved binary identity, version, declared environment and the argument vectornot the content of any file those arguments name, and not anything those files in turn reference. A job file that points at a netlist, a Liberty and a SPEF contributes only its own path to the hash. Edit the netlist in place and the hash does not move.

"provenance_limitations": [
  "The job names the netlist, Liberty and SPEF; input_hash covers the job path and arguments, not their contents, and Liberty `include` files are not enumerated."
]

The declared strings travel with the evidence they qualify — they appear as provenance.limitations in every result envelope, beside the input_hash they are about, so a consumer reads the caveat where it matters rather than having to go looking for it.

Stability

A descriptor carries no version of its own — it is whatever the binary you invoked emits, so pin the engine version if you need a fixed contract (vyges-<engine> --version).

name and inputs form the compatibility surface: treat a change to either as a breaking change for callers. New optional fields may appear over time — ignore what you do not recognize, and never infer a passing verdict from a field you cannot interpret.

Configuration & docs — no undocumented knobs

A hard lesson from OpenLane/LibreLane: a global, flat namespace of hundreds of config variables, set through env/config.json, applied across steps — so you can’t tell which step reads which variable, what its impact is, or when it applies. Loom is designed so that failure mode is structurally impossible. This page is the convention every Loom step and engine follows.

The three failure modes we reject

OpenLane/LibreLaneLoom
Global, flat env-var namespacePer-step, typed config — scoped to the op that reads it
“Which var does this step use?” is unclearConfig is the step’s typed struct — the surface is finite and local
Impact / when-it-applies undocumentedEvery field documents impact + when, and docs are generated from the code

The five rules

  1. No global env vars for behavior. A step’s configuration is a typed struct (Rust + serde), passed as JSON or as typed Flow IR. Environment variables are for environment (paths, credentials) — never behavior knobs.
  2. Every field is documented in place — a doc-comment stating what it does, its impact, when it applies, and its default. No field ships without this.
  3. Every step self-describes. Each step/engine emits its config as a JSON Schema via --describe (the same pattern as vyges-events --schema and vyges metadata). The schema lives next to the struct, so it cannot drift from behavior. One owner, no drift.
  4. Docs are generated from --describe, not hand-written. The mdbook reference page for a step is generated from its schema — name, type, default, description, impact, example. Regenerated in CI; drift fails the build.
  5. There is a config catalog. One generated, searchable page lists every knob across every step — the index OpenLane never had — plus a worked example per step.

How config flows

   typed config struct  ──(--describe)──▶  JSON Schema  ──▶  generated mdbook reference
   (serde + doc-comments)                  (self-published)   + config catalog
          │                                                          ▲
          └──── consumed by the step (and by the flow layer) ────────┘

The typed struct is the single source of truth. It is what the step deserializes at run time, what --describe publishes, and what the flow layer validates against. Documentation is a projection of it — so it is always complete and always current.

Example — the insert-eco-buffers step

Config is a typed struct, LibreLane-compatible in shape:

#![allow(unused)]
fn main() {
/// One entry of `INSERT_ECO_BUFFERS`.
pub struct EcoBuffer {
    /// `"instance/pin"` — the pin to buffer. Impact: a buffer is spliced here.
    /// When: applied once, during the ECO surgery step (before legalization).
    pub target: String,
    /// Buffer master cell name. Impact: which library cell is inserted.
    pub buffer: String,
}
}

Invoked as a step with an explicit, worked example (never a bare env var):

vyges opendb insert-eco-buffers --input in.odb --output out.odb --config eco.json
# eco.json: { "INSERT_ECO_BUFFERS": [ { "target": "inst42/A", "buffer": "sky130_fd_sc_hd__buf_2" } ] }

What this buys a user

  • Discoverability: --describe tells you exactly what a step accepts — no source-diving.
  • Locality: a knob’s impact is the op that owns it, not a global side effect.
  • Currency: docs (defaults, impacts, examples) are generated, so they never rot.
  • Composition: the flow layer carries per-op config validated against the schema, so “what to use when” is answered by the flow’s structure — not env-var archaeology.

Status

The generated half is live. Every engine’s CLI reference page is produced from both --help (the surface: subcommands and flags) and --describe (the contract: typed config, input schemas, maturity, provenance) — neither alone is enough, since an engine’s --describe usually covers one primary operation while its --help covers all of them. Multi-step tools are walked step by step, so each step’s config keys appear with their types and descriptions.

A step that publishes no contract is named on its own page rather than quietly omitted: a gap a reader can see is a gap someone can report. That is the standing convention in force — no step ships an undocumented knob.

Result envelope — loom-result

Every engine invocation made through vyges mcp returns the same JSON object, whatever the engine. One shape to parse, whether you called DRC, timing, or a layout viewer — and a failure comes back in that shape too, never as a crash.

Where the tool descriptor says how to call an engine, this says what comes back.

Note: engines invoked directly on the command line print their own --json payload. The envelope is added by the layer that runs them, and the engine’s own output is carried inside it verbatim as result.

A complete envelope

Real output from a vyges-em-ir run, abridged only where marked:

{
  "schema": "loom-result/1.1",
  "tool": "em-ir",
  "tool_version": "0.1.15",

  "status": "ok",                       // did the PROCESS run?
  "engineering": {                      // what does the evidence say about the DESIGN?
    "status": "pass",
    "assertion": "power-integrity-met",
    "summary": "assertion 'power-integrity-met' held on 'pi_met'"
  },

  "input_hash": "blake3:f81c6c0221446a1296d4541fa732e433517e759b6480a612d6dda769f7c3f3f7",
  "result": { /* the engine's own --json output, verbatim */ },
  "artifacts": [
    { "role": "emir_report", "path": "emir.rpt",
      "hash": "blake3:9f2a4c1e7b3d8056a1c4f9e2b7d05384c6a1f9e2b7d05384c6a1f9e2b7d05384" }
  ],
  "error": null,
  "logs": {
    "count": 1,
    "events": [ /* vyges-events records */ ],
    "summary": { "info_count": 1, "codes": ["EMIR-DONE"] }
  },
  "provenance": {
    "cmd": ["vyges-em-ir", "run", "block.emir", "--json"],
    "duration_ms": 34,
    "env": {},
    "maturity": "workflow-validated",
    "limitations": [
      "The job names the PDN or DEF/LEF and any power and decap maps; input_hash covers the job path and arguments, not their contents."
    ]
  }
}

Fields

FieldAlways presentWhat it is
schemayesEnvelope version — loom-result/1.1.
tool / tool_versionyesWhich engine ran, and its resolved version.
statusyesExecution state: ok or error.
engineeringyesDesign verdict — see below.
input_hashyesBLAKE3 over the resolved binary identity, version, declared environment, and argument vector.
resultyesThe engine’s own --json, passed through unchanged.
artifactsyes (may be [])Produced files: role, path, and a BLAKE3 hash (null if the file is declared but absent). Paths are workspace-relative where they can be (see below). Sign-off engines report the path as report_path in their own output, so asking for a report never costs you the parsed result.
erroryes (null when fine)Populated only when status is error.
logsyes (null when none)Structured events plus a compact summary.
provenanceyescmd, duration_ms, the declared env, plus the engine’s maturity and its limitations — see below.

Parse defensively: new optional fields may appear, so ignore what you do not recognize.

The two status axes

The single most important thing about this envelope: status and engineering.status are independent, and neither implies the other.

statusengineering.status
QuestionCould we invoke and observe the process?What does the evidence support about the design?
Valuesok, errorpass, fail, unknown, not_applicable

A DRC run that exits cleanly having found three violations is a successful execution of a failed check:

{ "status": "ok",
  "engineering": { "status": "fail", "assertion": "drc-clean",
                   "summary": "3 rule violation(s) found" } }

Collapsing these into one field would force every caller to know each engine’s private JSON shape to learn whether the design actually passed — and would leave no way to say “the tool ran, but the evidence is not trustworthy.”

When the verdict is unknown

unknown is not a failure. It means no trustworthy conclusion was reached, and it is the answer in every one of these cases:

  • the engine declares no assertion — silence is never a pass;
  • the execution did not succeed — a crashed, missing, or timed-out tool reached no conclusion about the design, and reporting fail would blame the design for a tooling defect;
  • the verdict field is missing, null, or the wrong type — absence of evidence is not evidence of a defect;
  • the engine’s assertion is malformed and was therefore dropped;
  • the engine’s maturity is discovered — nothing has validated its result shape, so a claim from it is not yet something to rely on.

not_applicable is different again: the operation establishes no engineering claim at all — a viewer, a format converter.

The rule to build on: treat anything other than pass as not proven, and never infer a pass from a field you cannot interpret.

engineering.assertion names the claim (drc-clean, timing-met, …) and is null when none was declared. Which field an engine derives its verdict from is published in its descriptor.

Errors — structured, never a crash

Every failure mode — engine not installed, non-zero exit, unparsable output — comes back as the same envelope with status: "error", so a session, flow, or job stays alive and can retry:

{ "status": "error",
  "engineering": { "status": "unknown", "assertion": null,
                   "summary": "execution did not complete; no engineering conclusion reached" },
  "error": { "code": "engine_nonzero", "message": "vyges-drc exited with code 2",
             "exit_code": 2, "stderr_tail": "…" } }
error.codeMeaning
not_installedThe engine binary is not on PATH — run vyges install <engine>.
exec_failedThe process could not be started.
engine_nonzeroIt ran and exited non-zero; exit_code and stderr_tail are included.

Note the pairing: an error envelope carries engineering.status: "unknown", never "fail".

input_hash — reproducibility and caching

input_hash is a BLAKE3 digest over the resolved binary identity (path or container image), the engine version, the declared environment (PDK_ROOT and friends), and the full argument vector. Pinning a different version, image, or PDK changes the hash.

Use it as a cache key and as a determinism check: the same input_hash must yield the same artifacts hashes. If it does not, something outside the recorded inputs is leaking into the result.

It covers the invocation, not the full transitive input closure — a SPICE deck’s .include chain or a rule deck’s imports are not enumerated. In fact it does not hash file contents at all: a job file that names a netlist contributes only its own path, so editing that netlist in place leaves the hash unchanged. Treat it as an invocation fingerprint, not a content hash of every byte the engine read.

Rather than leave that boundary to be rediscovered, every engine declares its own:

"provenance": {
  "maturity": "workflow-validated",
  "limitations": [
    "The job names the floorplan and per-block power; input_hash covers the job path and arguments, not their contents."
  ]
}

limitations is the engine’s own statement, carried beside the hash it qualifies. maturity says how far that engine’s evidence has been proven — and at discovered a verdict is suppressed to unknown however well-formed the assertion. Both are declared in the tool descriptor.

Artifact paths are workspace-relative

A path is reported relative to the working directory whenever it lies inside it:

"artifacts": [ { "role": "thermal_report", "path": "report.rpt", "hash": "blake3:…" } ]

An absolute path pins evidence to one machine and carries the host’s directory layout into a record meant to be shared, cached and compared. A path outside the workspace is left exactly as given — it has no portable form, and rewriting it would produce something that does not resolve.

The envelope is validated on the way out

Every envelope is checked against the published schema before it is returned. It is easy to validate only what arrives from elsewhere and never what leaves — the strong guarantees then apply to hypothetical third parties and the weak ones to your own evidence. This is the other way round.

A violation is our defect, not yours: it is reported as a MCP-ENVELOPE-INVALID event and the envelope is still returned, because a caller holding a slightly wrong envelope is better off than one holding none.

logs — the causal trail

Engines emit structured events on stderr. Those are collected here as events, with a summary giving counts by severity and the distinct codes seen, so you get a queryable trail plus a small handle without the full blob. logs is null when a run produced no events.

During a long run these same events stream live as MCP notifications/message, so an agent sees progress instead of waiting for a capture-at-end dump.

Stability

loom-result/1.1 added the engineering block; 1.0 had a single status field. Additions are backwards-compatible — parse defensively, ignore unknown fields, and treat a change to an existing field’s meaning as a version bump.