A new infrastructure for Agentic Analog and Mixed-Signal Design. LLM Agents drive Cadence Virtuoso instances — locally or remotely — turning tedious handcrafting into automated design flows.
Control across Schematic, Layout, Maestro, and Spectre. Flexible programming: execute inline SKILL, load .il files, or use Python APIs.
Multi-server, multi-session. Built for modern distributed design clusters with parallel simulations across servers and accounts.
CLI-first, ships with pre-defined agent skills, optimized for high-frequency agent interactions with persistent SSH tunnels.
| Feature | virtuoso-bridge-lite | skillbridge |
|---|---|---|
| Core mechanism | ipcBeginProcess + evalstring | ipcBeginProcess + evalstring |
| Local mode | Yes | Yes |
| Remote execution | SSH tunnel, jump host, auto-reconnect | Not supported |
| Calling style | String-based: execute_skill("...") | Pythonic: ws.db.open_cell_view_by_type(...) |
| Load .il files | client.load_il() | Not supported |
| Layout / schematic API | client.layout.edit() context manager | Raw SKILL only |
| Spectre simulation | Built-in runner + PSF parser | Not supported |
| AI agent support | Skill files, CLI-first, command logging | Not designed for agents |
| Python ↔ SKILL types | String-based | Auto bidirectional mapping |
| IDE tab completion | No (not needed by agents) | Yes (Jupyter, PyCharm stubs) |
Both projects use the same Cadence SKILL IPC facility. The divergence is in what's built on top: skillbridge stays thin — a Pythonic RPC client. virtuoso-bridge-lite adds SSH remote access, high-level APIs, Spectre simulation, and an AI-agent-ready harness.

| Component | Description |
|---|---|
VirtuosoClient | Pure TCP SKILL client. Sends SKILL as JSON, gets results. No SSH awareness. |
SpectreSimulator | Runs Spectre simulations remotely via SSH shell commands, transfers netlists and results via rsync. |
SSHClient | Persistent ControlMaster connection multiplexing TCP port-forwarding, SSH shell, and rsync. Optional — bypassed in local mode. |
Fully decoupled: VirtuosoClient works with any TCP endpoint — SSH tunnel, VPN, direct LAN, or local.
| SSH | ssh my-server must work without a password prompt |
| Virtuoso | A running Virtuoso process on the remote (or local) machine |
| Spectre | (Simulation only) spectre on PATH, or set VB_CADENCE_CSHRC |
# 1. Install
pip install -e .
# 2. Generate config (fills host/user/jump + port in one shot)
virtuoso-bridge init user@host -J user@jump-host
# or `virtuoso-bridge init` for an empty template to edit manually
# 3. (only if step 2 left anything blank) edit ~/.virtuoso-bridge/.env
# VB_REMOTE_HOST=my-server
# VB_REMOTE_USER=username
# VB_REMOTE_PORT=65081 # auto-assigned by hashing VB_REMOTE_USER
# VB_LOCAL_PORT=65082
# 4. Start the bridge
virtuoso-bridge start
# 5. Load SKILL in Virtuoso CIW (path printed by `virtuoso-bridge start`)
# load("/tmp/virtuoso_bridge_<user>/virtuoso_bridge/virtuoso_setup.il")
# 6. Verify
virtuoso-bridge status
# 7. Connect from Python
from virtuoso_bridge import VirtuosoClient
client = VirtuosoClient.from_env()
result = client.execute_skill("1+2")
print(result) # VirtuosoResult(status=SUCCESS, output='3')
# — or skip Python entirely: run SKILL straight from the shell —
# One-liner — full VirtuosoResult JSON on stdout
virtuoso-bridge eval 'getCurrentTime()'
# Multi-line SKILL via heredoc (auto-wrapped in progn; returns the last form)
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
# In .env — VB_REMOTE_HOST is where Virtuoso runs, NOT the jump host
VB_REMOTE_HOST=compute-host
VB_JUMP_HOST=jump-host
# Default (no profile)
VB_REMOTE_HOST=server-a
VB_REMOTE_USER=user1
# Profile "worker1" — used with -p worker1
VB_REMOTE_HOST_worker1=server-b
VB_REMOTE_USER_worker1=user2
virtuoso-bridge start -p worker1
virtuoso-bridge status -p worker1
from virtuoso_bridge import VirtuosoClient
bridge = VirtuosoClient.local(port=65432)
bridge.execute_skill("1+2")
Set VB_REMOTE_HOST=localhost and run virtuoso-bridge start — it skips the SSH tunnel, deploys the bridge files locally, and prints the load(...) line for the CIW.
| Method | Description |
|---|---|
execute_skill(code) | Run any SKILL expression, return result |
load_il(path) | Upload & load a .il file in Virtuoso |
execute_operations(cmds) | Run a batch of SKILL commands |
get_current_design() | Returns (lib, cell, view) of active cellview |
open_window(lib, cell) | Open a cellview in Virtuoso GUI |
save_current_cellview() | Save the active cellview |
test_connection() | Ping the daemon to check connectivity |
from virtuoso_bridge.virtuoso.layout import (
layout_create_rect as rect,
layout_create_path as path,
layout_create_param_inst as inst,
layout_create_via_by_name as via,
)
with client.layout.edit("myLib", "myCell") as lay:
lay.add(rect("M1", "drawing", 0, 0, 1, 0.5))
lay.add(path("M2", "drawing", [(0,0),(1,0)], 0.1))
lay.add(inst("tsmcN28", "nch_ulvt_mac", "layout", "M0", 0, 0, "R0"))
lay.add(via("M1_M2", 0.5, 0.25))
| Method | Description |
|---|---|
lay.add(skill_cmd) | Queue a SKILL command (from ops functions) |
from virtuoso_bridge.virtuoso.schematic import (
schematic_create_inst_by_master_name as inst,
schematic_create_pin as pin,
schematic_label_instance_term as label,
)
with client.schematic.edit("myLib", "myCell") as sch:
sch.add(inst("analogLib", "vdc", "symbol", "V0", 0, 0, "R0"))
sch.add(label("V0", "PLUS", "VDD"))
sch.add(pin("VDD", 0, 1.0, "R0", direction="inputOutput"))
sch.add_net_label_to_transistor("M0",
drain_net="OUT", gate_net="IN",
source_net="VSS", body_net="VSS")
| Method | Description |
|---|---|
sch.add(skill_cmd) | Queue a SKILL command (from ops functions) |
sch.add_net_label_to_transistor(...) | Label MOS D/G/S/B terminals |
from virtuoso_bridge.spectre.runner import SpectreSimulator
sim = SpectreSimulator.from_env(work_dir="./output")
result = sim.run_simulation("tb_inv.scs", {})
print(result.status) # ExecutionStatus.SUCCESS
print(result.data.keys()) # ['time', 'VOUT', 'VIN', ...]
print(result.data["VOUT"][:5]) # first 5 voltage samples
from virtuoso_bridge.virtuoso.maestro import (
snapshot, open_gui_session, close_gui_session,
run_and_wait, read_results, export_waveform,
)
# Snapshot the focused maestro window — no session arg needed.
# Returns dict with raw_sections (list of (skill_probe, raw_output)
# tuples — no Python parsing of SKILL alists).
d = snapshot(client)
# Or also write the full disk dump (raw + filtered XMLs +
# state_from_skill.txt + per-point run files via tar). Files
# pulled are governed by snapshot_filter.yaml — edit to add/drop.
d = snapshot(client, output_root="output/")
# Pin a specific history instead of the auto-pick (mtime-first).
d = snapshot(client, output_root="output/", history="Interactive.160")
# Open / run / collect — GUI mode is required for run_and_wait
session = open_gui_session(client, "myLib", "myTB")
run_and_wait(client, session) # non-blocking, SKILL callback + ssh polling
results = read_results(client, session, lib="myLib", cell="myTB")
export_waveform(client, session, 'dB20(mag(v("/OUT")))', "gain.txt")
close_gui_session(client)
from virtuoso_bridge import SSHClient, VirtuosoClient
# Remote mode
tunnel = SSHClient.from_env()
tunnel.warm()
bridge = VirtuosoClient.from_tunnel(tunnel)
# Local mode
bridge = VirtuosoClient.local(port=65432)
| Method | Description |
|---|---|
upload_file(local, remote) | Upload a file to the remote machine |
download_file(remote, local) | Download a file from the remote machine |
run_shell_command(cmd) | Run a shell command on the remote host via SKILL csh() |
| Command | Description |
|---|---|
virtuoso-bridge init [user@host] [-J user@jump] [--force] | Write ~/.virtuoso-bridge/.env; arguments fill host/user/jump, --force overwrites an existing file |
virtuoso-bridge start | Start SSH tunnel + deploy daemon |
virtuoso-bridge status | Check tunnel, daemon, hostname, Spectre license |
virtuoso-bridge restart | Restart tunnel |
virtuoso-bridge stop | Stop tunnel |
virtuoso-bridge windows | List Virtuoso windows + focused session |
virtuoso-bridge snapshot | Dump focused maestro's SKILL probe sections to stdout |
virtuoso-bridge snapshot -o ROOT [--history NAME] | Full disk dump of the focused session to ROOT/<ts>__<lib>__<cell>/: raw + filtered setup XMLs, state_from_skill.txt, latest-run per-point files. --history pins a specific maestro history (e.g. Interactive.160); otherwise auto-picks by mtime. File whitelist lives in virtuoso/maestro/snapshot_filter.yaml — edit to reshape the dump (no code change). |
virtuoso-bridge screenshot | Capture a Virtuoso window |
virtuoso-bridge load FILE.il NEW | Run a .il file in the live Virtuoso session — uploads the file in SSH mode and emits the full VirtuosoResult as JSON on stdout. Errors keep the original file/line (no temp-wrapper noise), so it pairs cleanly with VS Code tasks.json for one-keystroke SKILL iteration. |
virtuoso-bridge eval 'EXPR' / eval --stdin NEW | One-liner / heredoc companion to load. Multi-statement input is auto-wrapped in progn(...); --stdin sidesteps shell quoting for snippets with embedded quotes/parens. |
virtuoso-bridge dismiss-dialog | X11 rescue path — finds blocking Virtuoso GUI dialogs and presses Enter outside the SKILL channel. The one command that still works when CIW is deadlocked on a modal dialog (SpecifyHistory, "Run dir exists", ASSEMBLER-8127, etc.). |
virtuoso-bridge license | Check Spectre license availability on the remote. |
virtuoso-bridge export-visio LIB CELL -o OUT.vsdx | Windows + pywin32 path: render a Virtuoso schematic into Microsoft Visio. Defaults skip the MOS body pin B; --include-body-pins keeps it, --exclude-net/--exclude-pin repeatable for finer filtering. |
snapshot -o pullsOne command dumps the focused maestro session's setup and the latest run's per-point files to a timestamped folder:
# auto-pick newest history
virtuoso-bridge snapshot -o output
# or pin a specific history (e.g. side-by-side comparison of two runs)
virtuoso-bridge snapshot -o output --history Interactive.160
Layout of the output directory:
output/<YYYYMMDD_HHMMSS>__<lib>__<cell>/
├── maestro.sdb, active.state # raw Cadence files
├── state_from_sdb.xml # sdb filtered (~10× smaller)
├── state_from_active_state.xml # active.state filtered
├── state_from_skill.txt # SKILL-probe setup summary
└── <history>/
├── <history>.{log,rdb,msg.db} # run-level (rdb/msg.db are SQLite)
└── <pt>/<tb>/
├── netlist/ → netlist, input.scs, qpInformation.ils, paramInfo.ils
└── psf/ → spectre.out, logFile, dcOp.dc, *.ac, *.tran, *.noise, ...
Per-point netlist/ keeps only the 4 files that describe the design (main SPICE netlist, testbench top-level, FOM/output-expression definitions, corner label). psf/ keeps stdout + logs + non-binary analysis results.
File selection is governed by virtuoso/maestro/snapshot_filter.yaml — every entry carries a per-file tag ([KEEP], [REDUNDANT], [SCAFFOLDING], [METADATA], [DEBUG-ONLY], [GUI-CONFIG], [RARELY], [NEVER]). Uncomment a line to add a file, comment it to skip. No package reinstall needed; the YAML is reread on every snapshot call.
Deliberately not pulled, regardless of YAML: *.raw binary waveforms, wavedb/, and PDK info dumps (modelParameter.info, primitives.info.*). Read waveforms through reader.runs.read_results instead of copying the binaries.
| Category | Example | Description |
|---|---|---|
| Basic | 00_ciw_output_vs_return.py | Compare CIW print vs SKILL return value |
01_execute_skill.py | Run SKILL expressions (Hello World) | |
02_ciw_print.py | Print to Virtuoso CIW | |
03_load_il.py | Load .il file into Virtuoso | |
04_list_library_cells.py | Enumerate libraries and cells | |
05_multiline_skill.py | Execute multi-line SKILL code | |
06_screenshot.py | Capture window screenshot and download | |
| Layout | 01_create_layout.py | Create layout with NMOS/PMOS/CFMOM instances |
02_add_polygon.py | Add polygon shapes | |
03_add_via.py | Place vias | |
04_multilayer_routing.py | M2-M7 multi-layer routing | |
05_bus_routing.py | 8-bit bus wiring | |
06_read_layout.py | Read back layout geometry | |
| Schematic | 01a_create_rc_stepwise.py | Create RC circuit step by step |
01b_create_rc_load_skill.py | Create RC via .il script injection | |
02_read_connectivity.py | Read nets, pins, instances | |
03_read_instance_params.py | Read instance parameters | |
08_import_cdl_cap_array.py | Import CDL netlist via spiceIn | |
| Maestro | 01_read_focused_maestro.py | snapshot() of focused maestro (in-memory, JSON to stdout) |
02_snapshot_with_metrics.py | Full disk dump (snapshot -o ROOT) of focused maestro | |
03_bg_open_read_close_maestro.py | Background open/close + read (no GUI window) | |
04_gui_open_snapshot_close.py | GUI open → snapshot → close (owns lifecycle) | |
05_gui_session_lifecycle.py | open_gui_session / close_gui_session lifecycle test | |
06a_rc_create.py | Create Maestro test for RC filter (auto-timestamped cell) | |
06b_rc_simulate_and_read.py | Run simulation in background, read results, export waveforms | |
| Spectre | 01_inverter_tran.py | CMOS inverter transient simulation |
01_veriloga_adc_dac.py | 4-bit ADC/DAC with Verilog-A | |
02_cap_dc_ac.py | RC filter DC+AC frequency response | |
04_strongarm_pss_pnoise.py | StrongArm comparator PSS+Pnoise |