Imported from Arcadia-1/virtuoso-bridge-lite (
skills/virtuoso/SKILL.md). Install upstream withnpx skills add Arcadia-1/virtuoso-bridge-lite --skill virtuoso. Copyright stays with the author.
Virtuoso Skill
CRITICAL: Do NOT invent SKILL code or API calls from memory. SKILL function names, signatures, and PDK device parameters are not reliably included in LLM training data — and they differ between Virtuoso versions (e.g. IC618 vs IC231). Before writing any SKILL expression, calling any SKILL function, or using any library cell:
- Quick check (repo): search
references/andexamples/for the function name or operation — fastest, no round-trip.- REQUIRED — verify against the installed Cadence docs (the ground truth for this Virtuoso version):
- SKILL function →
virtuoso-bridge skill-find <name>(syntax + description), thenvirtuoso-bridge skill-info <name>(full documentation page)- Concept / topic →
virtuoso-bridge doc-search "<query>"(searches all installed doc sets)- New host/profile → run
virtuoso-bridge doc-infoonce per session to identify the active Virtuoso version and documentation layout before interpreting any doc results- Library cells: confirm existence (
ddGetLibList/ddGetObj) and read the actual cell (symbol pins, CDF params) before instantiating or setting parameters — never assume pin names or parameter names.If
skill-finddoes not find a function, trydoc-searchwith related terms before concluding anything. A miss in repo references is NOT evidence that a function does not exist. Never guess parameter names — verify first. Full protocol + command reference:references/local-docs.md.
Mental Model
You control a remote Cadence Virtuoso through virtuoso-bridge. Python runs locally; SKILL executes remotely in the Virtuoso CIW. SSH tunneling is automatic.
Local (Python) Remote (Virtuoso)
┌──────────────────┐ SSH tunnel ┌──────────────────┐
│ VirtuosoClient │ ────────────► │ CIW (SKILL) │
│ │ │ │
│ • schematic.* │ │ • dbCreateInst │
│ • layout.* │ │ • schCreateWire │
│ • execute_skill │ │ • mae* │
│ • load_il │ │ • dbOpenCellView │
└──────────────────┘ └──────────────────┘
Three abstraction levels
| Level | When to use | Example |
|---|---|---|
| Python API | Schematic/layout editing — structured, safe | client.schematic.create(lib, cell) |
| Inline SKILL | Maestro, CDF params, anything the API doesn't cover | client.execute_skill('maeRunSimulation()') |
| SKILL file | Bulk operations, complex loops | client.load_il("my_script.il") |
Always use the highest level that works. Drop to a lower level only when needed.
Never guess function names. If the function isn't in the examples below, verify it against the installed Cadence documentation (see "Documentation protocol") before writing the call. Fabricating a wrong name wastes time debugging in CIW.
Documentation protocol (mandatory)
The SKILL language, its per-version function set, and PDK device parameters are the part of this stack most likely to be wrong from memory. Verify against the docs installed on the target Virtuoso — not from general knowledge — before:
- writing any SKILL expression that calls a function you haven't used before in this session,
- instantiating a library cell or setting CDF parameters on one,
- relying on version-dependent behavior (a function that exists in IC231 may be absent or renamed in IC618, and vice versa).
The four verification commands
| Goal | Command | What you get |
|---|---|---|
| Identify the active version + doc layout (once per host) | virtuoso-bridge doc-info |
Virtuoso version (from install metadata), doc root, doc-set count, SKILL Finder status, More-Info index status, skdfref style (chapter vs per-function) |
| Find a SKILL function by name | virtuoso-bridge skill-find <name> [--mode fuzzy|prefix|suffix|exact|regex] |
Best-matching functions with exact syntax and one-line description from the installed .fnd database |
| Read the full docs for a SKILL function | virtuoso-bridge skill-info <name> |
The installed More-Info page as plain text: signature, argument descriptions, return values, examples |
| Search all installed documentation | virtuoso-bridge doc-search "<query>" [-n N] |
Ranked matches (path + title + snippet) across every installed doc set — user guides, API references, FAQs |
All four support --json (for parsing), -p PROFILE (multi-profile setups), and
--env FILE. doc-search additionally supports --doc-root (explicit local
roots, no bridge needed), --rebuild-index, and --cache-dir. Python equivalents:
client.doc_info(), client.find_skill(query, mode=...),
client.get_skill_more_info(func), client.search_docs(query).
Standard verification flow
# 1. Once per session / per host: which Virtuoso, what docs are installed?
virtuoso-bridge doc-info
# 2. Is the function there, and what is its EXACT syntax?
virtuoso-bridge skill-find dbOpenCellViewByType
# → dbOpenCellViewByType( { gt_lib | nil } t_cellName lt_viewName [ t_viewTypeName [ t_mode [ d_contextCellView ] ] ] ) => d_cellView / nil
# 3. Full argument descriptions, return values, examples:
virtuoso-bridge skill-info dbOpenCellViewByType
# 4. Concept-level questions (e.g. "how to create inherited net expression"):
virtuoso-bridge doc-search "net expression label"
Interpret results through the doc-info output: skdfref style tells you whether
function pages are individual files (per-function, e.g. IC231) or one large
chapter file with anchors (chapter, e.g. IC618) — skill-info handles both
transparently, but it matters when reading doc paths by hand.
Library-cell verification
Before instantiating a cell or setting parameters on it:
- Existence — confirm the lib/cell/view triple is real:
client.execute_skill('ddGetObj("lib" "cell")')(iterateddGetLibList()if you only know the cell name). - Pins/parameters — read them from the live cellview, never from memory:
open the symbol view and list terms, or use
schGetParams/dbGetqon an instance. CDF parameter names are PDK-specific (e.g.fingersis editable whilenfis read-only on many PDKs — see "Create a schematic" below). - Semantics — for PDK device behavior (terminal order, multi-finger
semantics, corner behavior), search the PDK/user documentation:
virtuoso-bridge doc-search "<device> <parameter>".
Five domains
| Domain | What it does | Python package | API docs |
|---|---|---|---|
| Schematic | Create/edit schematics, wire instances, add pins | client.schematic.* |
references/schematic-python-api.md, references/schematic-skill-api.md |
| Symbol | Generate, edit, and read symbol views | client.symbol.* |
references/symbol-python-api.md |
| Layout | Create/edit layout, add shapes/vias/instances | client.layout.* |
references/layout-python-api.md, references/layout-skill-api.md |
| Maestro | Read/write ADE Assembler config, run simulations | client.maestro.* |
references/maestro-python-api.md, references/maestro-skill-api.md |
| Library | Read/create/rename/delete libraries, bind technology | client.library.* |
references/library-python-api.md |
| Netlist (si) | Batch netlist generation without Maestro | simInitEnvWithArgs + si CLI |
See "Batch Netlist (si)" section below |
| SKILL Finder | Search SKILL function names and get detailed docs | client.find_skill(), client.get_skill_more_info() |
references/skill-finder-python-api.md |
| General | File transfer, screenshots, raw SKILL, .il loading | client.* |
See below |
Before you start
Environment setup
virtuoso-bridgeis a Python CLI. Useuv+ virtual environment — never install into the global Python.
uv venv .venv && source .venv/bin/activate # Windows: source .venv/Scripts/activate
uv pip install -e virtuoso-bridge-lite
All virtuoso-bridge CLI commands and Python scripts must run inside the activated venv.
Connection sequence (follow in order)
- Check
.env— the bridge looks up.envin this order:--env FILE(CLI flag) → first parent.envthat looks like a Virtuoso Bridge config (anyVB_*_HOSTrole orVB_LOCAL_PORT) →~/.virtuoso-bridge/.env(user-level). If any of these exists, skipinit. Only runvirtuoso-bridge initwhen none exist — it creates~/.virtuoso-bridge/.env(user-level, shared across projects). If the user already told you their SSH target, prefervirtuoso-bridge init user@host [-J user@jump]to fill the one-host model + ports in one step; otherwise plainvirtuoso-bridge initwrites a template. For split installations, verifyVB_GUI_HOSTowns CIW/X11,VB_DEPLOY_HOSTreceives generated files,VB_DAEMON_HOSTis the tunnel endpoint,VB_SPECTRE_HOSTruns standalone jobs, andVB_REMOTE_SCRATCH_ROOTis visible to every role that consumes deployed files. Unset roles fall back toVB_REMOTE_HOST. virtuoso-bridge start— starts the local bridge service and SSH tunnel.- If status is
degraded— load the exact setup line printed bystartin Virtuoso CIW. As an opt-in alternative, runvirtuoso-bridge list-windows --top-level --json, select one explicit CIW, then runvirtuoso-bridge bootstrap --window WINDOW_ID; bootstrap refuses non-CIW windows and accepts no arbitrary SKILL. virtuoso-bridge status— verify everything ishealthybefore proceeding.virtuoso-bridge windows— list all open Virtuoso windows (num + name).virtuoso-bridge eval 'EXPR'— run a one-line SKILL expression from the shell and print the fullVirtuosoResultJSON.virtuoso-bridge eval --stdin— run multi-line SKILL from stdin; the CLI auto-wraps multiple forms inprogn(...)and returns the last form.virtuoso-bridge load FILE.il— run a.ilfile in the live Virtuoso session; uploads the file automatically in SSH mode.virtuoso-bridge screenshot [ciw|current|N] [-o DIR|FILE]— screenshot a window. Default target is CIW; default output is the user artifact screenshots directory.virtuoso-bridge snapshot -o <dir>— dump the currently-focused maestro window to<dir>/<YYYYMMDD_HHMMSS>__<lib>__<cell>/(state XMLs, SKILL probe output, per-point netlist + PSF results,.rdb). This is the default way to capture Maestro state — no Python required. Use the Python API (below) only inside a multi-step pipeline.
Then
- Check examples first:
examples/01_virtuoso/— don't reinvent from scratch. - Run
virtuoso-bridge doc-infoonce to pin the active Virtuoso version and doc layout (see "Documentation protocol (mandatory)"). - Open the window:
client.open_window(lib, cell, view="layout")so the user sees what you're doing.
Client basics
Direct CLI SKILL execution
For quick checks and one-off SKILL files, prefer the CLI over writing a Python wrapper. It uses the same bridge connection and avoids shell/Python/SKILL triple-quoting problems.
# One-line expression -- full VirtuosoResult JSON on stdout
virtuoso-bridge eval 'getCurrentTime()'
# Multi-line SKILL -- auto-wrapped in progn when needed
virtuoso-bridge eval --stdin <<'EOF'
let((libs)
libs = mapcar(lambda((l) l~>name) ddGetLibList())
printf("found %d libraries\n" length(libs))
libs)
EOF
# Whole .il file -- uploaded automatically in SSH mode
virtuoso-bridge load my_script.il
Use Python only when the SKILL call is one step in a larger scripted workflow or when you need structured high-level APIs such as schematic/layout editors.
Python client
from virtuoso_bridge import VirtuosoClient
client = VirtuosoClient.from_env()
client.execute_skill('...') # run SKILL expression
client.fetch(expr, fields) # batch ~>slot extract (see below)
client.fetch_one(expr, fields) # single-object ~>slot extract
client.load_il("my_script.il") # upload + load .il file
client.upload_file(local_path, remote_path) # local → remote
client.download_file(remote_path, local_path) # remote → local
client.open_window(lib, cell, view="layout") # open GUI window
client.run_shell_command("ls /tmp/") # run shell on remote
client.list_windows() # list all open windows
client.screenshot(target="ciw") # screenshot to the user artifact directory
client.screenshot(output="output", target="ciw") # explicit repo-local output
Batch attribute fetch: fetch() / fetch_one()
execute_skill() is a raw-string in, raw-string out channel. For DFII
objects it returns an opaque handle ("db:0x2800ccbe") that's useless
by itself — to get attributes you'd have to send another SKILL call
per attribute, which is both verbose and slow (~100 ms per
round-trip).
fetch(expr, fields) does the right thing in one round-trip:
sends mapcar(lambda((o) list(o~>f1 o~>f2 ...)) <expr>), parses the
SKILL s-expression response, and returns a list of Python dicts.
# List of selected schematic objects in one call
objs = client.fetch("geGetSelSet()", ["objType", "cellName", "name"])
# [{"objType": "inst", "cellName": "nch_mac", "name": "M1"},
# {"objType": "inst", "cellName": "pch_mac", "name": "M2"}, ...]
print(objs[0]["name"]) # → 'M1'
# All instances in the current schematic — 1 call, not N×fields
insts = client.fetch(
"geGetEditCellView()~>instances",
["name", "cellName", "libName", "viewName"],
)
fetch_one(expr, fields) is the single-object variant — wraps in
list(...) and returns one dict:
cv = client.fetch_one("geGetEditCellView()",
["libName", "cellName", "viewName"])
# {"libName": "PLAYGROUND", "cellName": "AMP", "viewName": "schematic"}
Value decoding (both methods): strings unquoted, nil →
None, t → True, nested SKILL lists → nested Python lists,
bare atoms (numbers / symbols) returned as strings so the caller can
coerce (int(d["fingers"])).
Why not a client["fn"]() lazy-proxy style (à la skillbridge)?
Lazy proxies look nicer syntactically but trigger one round-trip per
attribute access — 100 selected objects × 3 fields = 300 ssh hops
(~30 s). fetch does it all in one hop (~200 ms). If you need the
REPL-style ergonomics, use skillbridge alongside this bridge —
they coexist fine on the same Virtuoso session.
CIW output vs return value
execute_skill() returns the result to Python but does not print anything in the CIW window. This is by design — the bridge is a programmatic API, not an interactive REPL.
# Return value only — CIW stays silent
r = client.execute_skill("1+2") # Python gets 3, CIW shows nothing
# To also display in CIW, use printf explicitly
r = client.execute_skill(r'let((v) v=1+2 printf("1+2 = %d\n" v) v)')
# Python gets 3, CIW shows "1+2 = 3"
Full example: examples/01_virtuoso/basic/00_ciw_output_vs_return.py
Printing multi-line text to CIW
Sending multiple printf in a single execute_skill() loses newlines — the CIW concatenates everything on one line. To print multi-line text, write it as a Python multiline string and send one execute_skill() per line:
text = """\
========================================
Title goes here
========================================
First paragraph line one.
First paragraph line two.
Second paragraph.
========================================"""
for line in text.splitlines():
client.execute_skill('printf("' + line + '\\n")')
Constraints:
- ASCII only — emojis and CJK characters cause a JSON encoding error on the remote SKILL interpreter
- No unescaped SKILL special chars in the text — if the line may contain
"or%, escape them (\\",%%) or useload_il()instead (see03_load_il.py)
IMPORTANT: Always write
.pyfiles, never usepython -c.python -c "..."has three layers of quoting (shell + Python + SKILL).\\neasily becomes\\\\n, causingprintfto silently produce no output. Always write code to a.pyfile and runpython script.py-- only two quoting layers (Python + SKILL), matching the examples.
Full example: examples/01_virtuoso/basic/02_ciw_print.py
References
Load on demand — each contains detailed API docs and edge-case guidance:
| File | Contents |
|---|---|
references/schematic-skill-api.md |
Schematic SKILL API, terminal-aware helpers, CDF params |
references/schematic-python-api.md |
SchematicEditor, SchematicOps, netlist import/export, low-level builders |
references/layout-skill-api.md |
Layout SKILL API, read/query, mosaic, layer control |
references/layout-python-api.md |
LayoutEditor, LayoutOps, shape/via/instance creation |
references/library-python-api.md |
Library CRUD, technology binding, return/error contracts |
references/maestro-skill-api.md |
mae* SKILL functions, OCEAN, corners, known blockers |
references/maestro-python-api.md |
snapshot() (raw SKILL sections) + filter_*_xml + writer functions; read_results (per-point × per-output CSV), export_waveform (OCEAN), and waveform viewer lifecycle |
references/simulation-flow.md |
Standard simulation flow — ordered lifecycle, pitfalls, optimization loops |
references/netlist.md |
CDL/Spectre netlist formats, spiceIn import |
references/troubleshooting.md |
Known gotchas, GUI blocking, CDF quirks, connection issues |
references/cellview-on-disk-layout.md |
What's inside each view on disk (sch.oa, data.dm binary format, maestro.sdb/active.state XML skeleton, lock files, SOS markers); which files are text-editable vs must go through DFII API |
references/schematic-recreation.md |
Recreate schematic from existing design (grid layout, diff pair conventions) |
references/batch-netlist-si.md |
Generate netlists without Maestro using si batch translator |
references/skill-finder-python-api.md |
skill-find (search SKILL by name) and skill-info (More Info docs) |
references/local-docs.md |
Local-documentation protocol — verify SKILL code + library cells against the installed Cadence docs: doc-info / skill-find / skill-info / doc-search, doc-root anatomy (IC618 vs IC231), version identification, caching, troubleshooting |
Examples
Always check these before writing new code.
examples/01_virtuoso/basic/
00_ciw_output_vs_return.py— CIW output vs Python return value (when CIW prints, when it doesn't)01_execute_skill.py— run arbitrary SKILL expressions02_ciw_print.py— print messages to CIW (oneexecute_skillper line)03_load_il.py— upload and load .il files04_list_library_cells.py— list libraries and cells05_multiline_skill.py— multi-line SKILL with comments, loops, procedures06_screenshot.py— capture layout/schematic screenshots07_sanitize_on_download.py— download remote text while sanitizing unsafe path fragments08_library_management.py— inspect a library, technology binding, categories, and members
examples/01_virtuoso/schematic/
01a_create_rc_stepwise.py— create RC schematic via operations01b_create_rc_load_skill.py— create RC schematic via .il script02_read_connectivity.py— read instance connections and nets03_read_instance_params.py— read CDF instance parameters04_test_set_instance_params_analoglib.py— update analogLib instance parameters05_rename_instance.py— rename schematic instances06_delete_instance.py— delete instances07_delete_cell.py— delete cells from library08_import_cdl_cap_array.py— import CDL netlist via spiceIn (SSH)09_create_pins.py— create schematic pins10_create_wire.py— draw wires between pins11_read_schematic_unified.py— read instances, nets, pins, geometry, and parameters12_plan_differential_pair.py— deterministically plan, create, read back, and verify a constrained differential-pair schematic
examples/01_virtuoso/layout/
01_create_layout.py— create layout with rects, paths, instances02_add_polygon.py— add polygons03_add_via.py— add vias04_multilayer_routing.py— multi-layer routing05_bus_routing.py— bus routing06_read_layout.py— read layout shapes07–10— delete/clear operations11_read_summary.py— read a compact layout summary12_layer_visibility.py— inspect and change layer visibility13_select_and_delete.py— select and delete matching figures14_mosaic_and_nets.py— create mosaics and assign nets15_export_gds.py— stream out GDS with staged inputs and fail-fast log polling
examples/01_virtuoso/symbol/
01_rc_create_with_symbol.py— native schematic-to-symbol generation02_bus10_create_with_symbol.py— native generation with 20 pins03_manual_symbol_semantics.py— manual drawing with native pin-name, instance/logical labels, selection box, and readback verification
examples/01_virtuoso/maestro/
01_read_focused_maestro.py— in-memory snapshot of the focused maestro (config + env + results + outputs + corners + variables)02_snapshot_with_metrics.py— snapshot the focused maestro to a timestamped directory (disk artifacts)03_bg_open_read_close_maestro.py— background open → read config → close (no GUI window)04_gui_open_snapshot_close.py— GUI open → snapshot artifacts → close (owns lifecycle)05_gui_session_lifecycle.py— GUI session lifecycle integration test (open/close edge cases)06a_rc_create.py— create RC schematic + Maestro setup (cell name auto-timestamped)06b_rc_simulate_and_read.py— run simulation in GUI mode, bind the exact history, export waveforms07_ensure_maestro_view.py— bootstrap a missing maestro cellview (maeOpenSetup+maeSaveSetup) beforeopen_gui_session08_set_simulator_mode.py— switch between APS / Spectre X (LX/MX/AX/VX/CX) / Spectre FX viaasiSetHighPerformanceOptionVal09_export_sweep_subpoints.py— pull per-sweep-point waveforms via OCEANopenResults(<abs path>)(works aroundmaeOpenResultsrejectingInteractive.N/M)
examples/01_virtuoso/veriloga/
import_veriloga.py— turn a local.vafile into a Cadence Verilog-A cellview via the 5-step IC618 path: placeholder schematic → symbol → veriloga skeleton → upload .va → reparse. This example covers the file/cellview interface only — the.vacontents are out of scope;sample.vais a trivial placeholder.
examples/01_virtuoso/diagnostics/
sniff_cdslck.py— walk a library tree and report.cdslcklock-file owners. Authoritative when SKILL-side session enumeration disagrees with on-disk reality.
examples/01_virtuoso/digital_import/
Hand off Genus/Innovus P&R products into a Virtuoso library. All three scripts wrap standalone Cadence batch tools (strmin / ihdl) via SKILL system() — no GUI forms, no manual bootstrap. See that folder's README.md for prerequisites, PDK-portability notes, and full CLI reference.
import_gds.py— routed layout viastrminimport_verilog.py— schematic + symbol viaihdlbatch (the official CLI entry point for Verilog Import)add_power_labels.py— drop VDD/VSS labels on a routed layout by reflectively reading std-cell pin geometry (no--ref-cellneeded, auto-discovers)
Common workflows
Find which library contains a cell
ddGetObj(cellName) with a single argument returns nil — must iterate ddGetLibList():
r = client.execute_skill(f'''
let((result)
result = nil
foreach(lib ddGetLibList()
when(ddGetObj(lib~>name "{CELL}")
result = cons(lib~>name result)))
result)
''')
# r.output e.g. '("2025_FIA")'
No need for a separate script — inline in any workflow that needs to locate a cell before operating on it.
Create a schematic
from virtuoso_bridge.virtuoso.schematic import (
schematic_create_inst_by_master_name as inst,
schematic_create_pin as pin,
)
with client.schematic.create(LIB, CELL) as sch:
# 1. Place instances — sch.add() queues SKILL commands
sch.add(inst("tsmcN28", "pch_mac", "symbol", "MP0", 0, 1.5, "R0"))
sch.add(inst("tsmcN28", "nch_mac", "symbol", "MN0", 0, 0, "R0"))
# 2. Label MOS terminals with stubs — NOT manual add_wire
sch.add_net_label_to_transistor("MP0",
drain_net="OUT", gate_net="IN", source_net="VDD", body_net="VDD")
sch.add_net_label_to_transistor("MN0",
drain_net="OUT", gate_net="IN", source_net="VSS", body_net="VSS")
# 3. Add pins at circuit EDGE, not on terminals
sch.add(pin("IN", -1.0, 0.75, "R0", direction="input"))
sch.add(pin("OUT", -1.0, 0.25, "R0", direction="output"))
# schCheck + dbSave happen automatically on context exit
Key rules:
-
Use
add_net_label_to_transistorfor MOS D/G/S/B — it auto-detects stub direction. Never manuallyadd_wirebetween terminals. -
Pins go at the circuit edge, not on instance terminals. They connect via matching net names.
-
Delete before recreate — if the cell already exists,
add_instanceaccumulates on top of old instances:client.execute_skill(f'ddDeleteObj(ddGetObj("{LIB}" "{CELL}"))') -
CDF parameters — two-step process:
Step 1: Set values with
schHiReplace(Edit > Replace). Do NOT useparam~>value =ordbSetq— they don't update display or derived params.client.execute_skill( 'schHiReplace(?replaceAll t ?propName "cellName" ?condOp "==" ' '?propValue "pch_mac" ?newPropName "w" ?newPropValue "500n")')Step 2: Trigger CDF callbacks with
CCSinvokeCdfCallbacksto update derived parameters (finger_width, display annotations, etc.). Use?orderto run only the changed params — running all callbacks may fail on PDK-specific variables likemdlDir.# Must load CCSinvokeCdfCallbacks.il first (one-time) client.upload_file("reference/CCSinvokeCdfCallbacks.il", "/tmp/CCSinvokeCdfCallbacks.il") client.execute_skill('load("/tmp/CCSinvokeCdfCallbacks.il")') # Trigger only the callbacks you need client.execute_skill('CCSinvokeCdfCallbacks(geGetEditCellView() ?order list("fingers"))')Critical: PDK devices have
nfas read-only. Usefingersinstead:# ✅ "fingers" is editable, "nf" is not client.execute_skill( 'schHiReplace(?replaceAll t ?propName "cellName" ?condOp "==" ' '?propValue "pch_mac" ?newPropName "fingers" ?newPropValue "4")') # ❌ schHiReplace(...?newPropName "nf" ...) → SCH-1725 "not editable"Why two steps:
schHiReplacechanges the stored property but does NOT trigger CDF callbacks. Without callbacks, derived params (finger_width, m_ov_nf annotations) stay stale.CCSinvokeCdfCallbacks(?order ...)triggers only the specified callbacks, avoiding PDK errors from unrelated callbacks.Or use the Python wrapper which handles both steps:
from virtuoso_bridge.virtuoso.schematic.params import set_instance_params set_instance_params(client, "MP0", w="500n", l="30n", nf="4", m="2")
Read a design (schematic + maestro + netlist)
Always use the Python API functions below. Do NOT hand-write SKILL for reading.
from virtuoso_bridge import VirtuosoClient, decode_skill_output
client = VirtuosoClient.from_env()
LIB, CELL = "myLib", "myCell"
# 1. Schematic — default: topology only (no positions/geometry)
data = client.schematic.read(LIB, CELL, include_positions=False)
# data = {
# "instances": [{"name", "lib", "cell", "numInst", "view",
# "params": {...}, "terms": {...}}, ...],
# "nets": {"VN1": {"connections": ["M0.D", ...], "numBits": 1,
# "sigType": "signal", "isGlobal": false}, ...},
# "pins": {"VINP": {"direction": "input", "numBits": 1}, ...},
# "notes": [{"text": "...", ...}, ...]
# }
# With positions (only when you need xy/bBox, e.g. for layout-aware editing):
data_with_pos = client.schematic.read(LIB, CELL, include_positions=True)
# No CDF param filtering (return all 200+ PDK params):
raw = client.schematic.read(LIB, CELL, include_positions=False, param_filters=None)
# 2. Maestro — snapshot the focused window
#
# PREFER THE CLI for one-shot captures. The CLI handles venv + client
# construction, and on-disk output is everything you need for
# analysis (state XMLs, SKILL probe text, per-point psf/* results,
# .rdb, netlist/). Python for this case is pure boilerplate.
#
# $ virtuoso-bridge snapshot -o output/
#
# Use the Python API only when snapshot is one step in a larger
# same-connection pipeline (e.g. client.maestro.open_session →
# client.maestro.snapshot → client.maestro.run_simulation →
# client.maestro.close_session, or a loop over many cells):
d = client.maestro.snapshot() # SKILL-only, ~150ms, 1 round-trip
# d["raw_sections"] = [(probe_skill_text, raw_output), ...]
# Each label IS the actual SKILL string we ran (e.g.
# 'maeGetAnalysis("test" "ac" ?session "fnxSession18")');
# value is the verbatim SKILL alist — no Python parsing.
# d also has session / lib / cell / view / mode / unsaved.
# Full disk dump (raw + YAML-filtered XMLs + 16 SKILL probes + per-point
# inputs + spectre results + .rdb):
d = client.maestro.snapshot(output_root="output/") # → d["output_dir"]
# IMPORTANT: snapshot() always uses the CURRENTLY FOCUSED maestro window.
# Click the desired ADE Assembler first, or use client.maestro.open_session() to bring it up.
# Rule of thumb: one-shot inspection → CLI; multi-step pipeline → Python.
# 3. Netlist — generate from maestro session, download via SSH
session = client.maestro.open_session(LIB, CELL)
test = decode_skill_output(
client.execute_skill(f'car(maeGetSetup(?session "{session}"))').output)
client.execute_skill(
f'maeCreateNetlistForCorner("{test}" "Nominal" "/tmp/nl_{CELL}" ?session "{session}")')
client.download_file(f"/tmp/nl_{CELL}/netlist/input.scs", "output/netlist.scs")
client.maestro.close_session(session)
Run a simulation
Follow this sequence exactly. Do not skip steps.
session = "fnxSession33" # from find_open_session() or maeGetSessions()
# 1. Set variables
client.execute_skill(f'maeSetVar("CL" "1p" ?session "{session}")')
# 2. Save before running — REQUIRED, skipping causes stale state
client.execute_skill(
f'maeSaveSetup(?lib "{LIB}" ?cell "{CELL}" ?view "maestro" ?session "{session}")')
# 3. Start + wait without blocking the SKILL channel. The timeout is one
# end-to-end budget for Maestro to accept the run and for completion polling.
history, status = client.maestro.run_and_wait(session=session, timeout=300)
history = history.strip('"')
# 4. Read every point/output from the exact history returned above.
results = client.maestro.read_results(
session, lib=LIB, cell=CELL, history=history,
)
# 5. If run_and_wait times out while a modal is visible, recover outside the
# blocked SKILL channel, then diagnose before deciding whether to rerun:
# $ virtuoso-bridge dismiss-dialog
# $ virtuoso-bridge list-windows --top-level --json
# $ virtuoso-bridge dismiss-window WINDOW_ID --action enter
Output read/export guardrails (collision-safe)
Apply these rules whenever you read or export any maestro output (scalar or waveform):
-
History binding is mandatory
- Always use the exact
historyreturned bymaeRunSimulation(). - Pass that
historyexplicitly to result readers/exporters (for example,read_results(..., history=history)andexport_waveform(..., history=history)). - Do not rely on "latest" history inference when reproducibility matters.
- Always use the exact
-
Remote filename must be unique per export
- Never use a fixed
/tmp/vb_wave_xxx.txtpath. - Use unique naming such as
/tmp/vb_wave_<history>_<timestamp>_<nonce>.txt. - This avoids collisions with stale files from previous runs or other users.
- Never use a fixed
-
Bind results directory to the same history before ocnPrint
- After
maeOpenResults(?history ...), verify the resolvedresultsDircontains/<history>/. - If mismatch is detected, stop and raise an error instead of exporting the wrong waveform.
- After
In optimization loops: add maeSaveSetup and dialog-recovery in every iteration. GUI dialogs ("Specify history name", "No analyses enabled") block the entire SKILL channel — all subsequent execute_skill calls will timeout until the dialog is dismissed.
Debug with screenshots: if simulation appears stuck or results are unexpected, use the X11/CLI path so capture does not depend on a responsive SKILL channel:
virtuoso-bridge list-windows --top-level --json
virtuoso-bridge screenshot maestro -o output/debug_maestro.png
This reveals dialog boxes, error messages, or unexpected variable values that are invisible through the SKILL channel alone.
Root Cause: Why maeGetOutputValue returns nil for computed expressions
Symptom: maeGetOutputValue("bandwidth(...)" testName) returns nil, but maeGetOutputValue("Noise_rms_out" testName) returns a value.
Root Cause: The PSF directory contains no actual waveform data files. Check with:
# SSH to remote and check PSF directory
ssh user@compute-host "ls /path/to/results/Interactive.N/psf/<test>/psf/"
# Expected: .raw, simdata, spectre.log files
# Actual: only spectre.out, variables_file (NO waveform data!)
Why this happens:
- Maestro saves only pre-computed scalar outputs (like
Noise_rms_out) to the RDB - Raw waveforms (VOUT, VSIN signals) are NOT saved to PSF unless "save=all" is enabled
- Computed expressions (bandwidth, dB20, value) need the waveform data to calculate — returns nil
Check the RDB directly:
ssh user@compute-host "sqlite3 /path/to/Interactive.N.rdb 'SELECT * FROM resultValue'"
# Returns rows like:
# 1|7|0.000469 -> Noise_rms_out scalar (saved)
# 1|8|wave -> VF(/VOUT)/VF(/VSIN) is a waveform reference, not saved!
Solution: Enable "save all" option before running simulation:
client.execute_skill(f'maeSetEnvOption("{test}" ?option "save" ?value "all")')
client.execute_skill('maeSaveSetup()')
Reliable Result Reading: Parse the Log File
When Maestro OCEAN functions fail (due to missing PSF waveform data), parse the .log file:
def read_maestro_results_from_log(client, LIB, CELL, history):
"""Read simulation results from the log file - most reliable method."""
# Resolve the OA library path via SKILL — works on any setup,
# no hardcoded ``/home/USER/...`` assumption.
r = client.execute_skill(f'ddGetObj("{LIB}")~>readPath')
lib_path = (r.output or "").strip().strip('"')
log_path = f"{lib_path}/{CELL}/maestro/results/maestro/{history}.log"
client.download_file(log_path, "/tmp/sim.log")
# Parse tab-separated format: "expression\t\tvalue"
results = {}
with open("/tmp/sim.log") as f:
for line in f:
if "\t\t" in line:
parts = line.rstrip().split("\t\t")
if len(parts) >= 2:
name = parts[0].strip()
value = parts[1].strip()
# Skip header lines
if name and value and "corner" not in name.lower():
results[name] = value
return results
# Full workflow:
from virtuoso_bridge import VirtuosoClient
client = VirtuosoClient.from_env()
LIB, CELL = "PLAYGROUND_AMP", "TB_AMP_5T_D2S_DC_AC"
session = client.maestro.open_gui_session(LIB, CELL) # GUI mode required for results
history, _ = client.maestro.run_and_wait(session=session, timeout=300)
h = history.strip('"')
results = read_maestro_results_from_log(client, LIB, CELL, h)
print(results)
# {'bandwidth(...)': '1.64M', 'dB20(...)': '10.93', ...}
client.maestro.close_gui_session(session, save=False)
Log format in the file:
bandwidth(abs((VF("/VOUT") / VF("/VSIN"))) 3 "low") 1.64M
dB20(value(abs((VF("/VOUT") / VF("/VSIN"))) 10000)) 10.93
value(abs((VF("/VOUT") / VF("/VSIN"))) 10000) 3.519
Noise_rms_out 469u
SKILL channel timeout — diagnosis and recovery
When execute_skill() times out, possible causes:
| Cause | Symptom | Fix |
|---|---|---|
| Modal dialog | GUI popup blocking CIW | virtuoso-bridge dismiss-dialog |
| Auto dialog finder missed a modal | GUI popup visible, SKILL channel blocked | virtuoso-bridge list-windows --top-level --json, then virtuoso-bridge dismiss-window WINDOW_ID --action enter |
| Long operation | Simulation or netlist running | Wait, or use ?waitUntilDone nil |
| CIW input prompt | CIW waiting for typed input | dismiss-dialog (sends Enter) |
| Bridge disconnected | All calls fail immediately | virtuoso-bridge restart |
Dialog recovery (bypasses SKILL, uses X11 directly):
# Find and dismiss all blocking Virtuoso dialogs
virtuoso-bridge dismiss-dialog
# Inspect X11 windows and dismiss one explicitly
virtuoso-bridge list-windows --top-level --json
virtuoso-bridge dismiss-window 0x4203583 --action enter
# From Python
client.dismiss_dialog()
Uses xwininfo to find virtuoso-owned dialog windows and XTestFakeKeyEvent to send the requested key action. Works even when the SKILL channel is completely stuck.
Prevention: Always dbSave(cv) before hiCloseWindow(win). Never use ?waitUntilDone t in simulation calls. Add dialog-recovery in simulation loops (see "Run a simulation" section).
Related skills
- spectre — standalone netlist-driven Spectre simulation (no Virtuoso GUI). Use when the user has a
.scsnetlist and wants to run it directly.