virtuoso-bridge-lite

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.

Deep Virtuoso Integration

Control across Schematic, Layout, Maestro, and Spectre. Flexible programming: execute inline SKILL, load .il files, or use Python APIs.

Scalable Architecture

Multi-server, multi-session. Built for modern distributed design clusters with parallel simulations across servers and accounts.

AI-Native Design

CLI-first, ships with pre-defined agent skills, optimized for high-frequency agent interactions with persistent SSH tunnels.

Comparison with skillbridge

Featurevirtuoso-bridge-liteskillbridge
Core mechanismipcBeginProcess + evalstringipcBeginProcess + evalstring
Local modeYesYes
Remote executionSSH tunnel, jump host, auto-reconnectNot supported
Calling styleString-based: execute_skill("...")Pythonic: ws.db.open_cell_view_by_type(...)
Load .il filesclient.load_il()Not supported
Layout / schematic APIclient.layout.edit() context managerRaw SKILL only
Spectre simulationBuilt-in runner + PSF parserNot supported
AI agent supportSkill files, CLI-first, command loggingNot designed for agents
Python ↔ SKILL typesString-basedAuto bidirectional mapping
IDE tab completionNo (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.

Architecture

Architecture

ComponentDescription
VirtuosoClientPure TCP SKILL client. Sends SKILL as JSON, gets results. No SSH awareness.
SpectreSimulatorRuns Spectre simulations remotely via SSH shell commands, transfers netlists and results via rsync.
SSHClientPersistent 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.

Getting Started

Prerequisites

SSHssh my-server must work without a password prompt
VirtuosoA running Virtuoso process on the remote (or local) machine
Spectre(Simulation only) spectre on PATH, or set VB_CADENCE_CSHRC
Note: Virtuoso and Spectre are independent — you can run Spectre without the SKILL bridge, and use the SKILL bridge without Spectre.

Step-by-step setup

# 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

Jump host setup

# In .env — VB_REMOTE_HOST is where Virtuoso runs, NOT the jump host
VB_REMOTE_HOST=compute-host
VB_JUMP_HOST=jump-host

Multi-profile setup

# 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

Local mode (no SSH)

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.

API Reference

VirtuosoClient — SKILL execution

MethodDescription
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

Layout editing

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))
MethodDescription
lay.add(skill_cmd)Queue a SKILL command (from ops functions)

Schematic editing

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")
MethodDescription
sch.add(skill_cmd)Queue a SKILL command (from ops functions)
sch.add_net_label_to_transistor(...)Label MOS D/G/S/B terminals

Spectre simulation

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

ADE Maestro (Assembler)

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)

SSHClient — independent SSH layer

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)

File transfer and shell

MethodDescription
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()

CLI commands

CommandDescription
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 startStart SSH tunnel + deploy daemon
virtuoso-bridge statusCheck tunnel, daemon, hostname, Spectre license
virtuoso-bridge restartRestart tunnel
virtuoso-bridge stopStop tunnel
virtuoso-bridge windowsList Virtuoso windows + focused session
virtuoso-bridge snapshotDump 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 screenshotCapture a Virtuoso window
virtuoso-bridge load FILE.il NEWRun 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 NEWOne-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-dialogX11 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 licenseCheck Spectre license availability on the remote.
virtuoso-bridge export-visio LIB CELL -o OUT.vsdxWindows + 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.

What snapshot -o pulls

One 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.

Examples

CategoryExampleDescription
Basic00_ciw_output_vs_return.pyCompare CIW print vs SKILL return value
01_execute_skill.pyRun SKILL expressions (Hello World)
02_ciw_print.pyPrint to Virtuoso CIW
03_load_il.pyLoad .il file into Virtuoso
04_list_library_cells.pyEnumerate libraries and cells
05_multiline_skill.pyExecute multi-line SKILL code
06_screenshot.pyCapture window screenshot and download
Layout01_create_layout.pyCreate layout with NMOS/PMOS/CFMOM instances
02_add_polygon.pyAdd polygon shapes
03_add_via.pyPlace vias
04_multilayer_routing.pyM2-M7 multi-layer routing
05_bus_routing.py8-bit bus wiring
06_read_layout.pyRead back layout geometry
Schematic01a_create_rc_stepwise.pyCreate RC circuit step by step
01b_create_rc_load_skill.pyCreate RC via .il script injection
02_read_connectivity.pyRead nets, pins, instances
03_read_instance_params.pyRead instance parameters
08_import_cdl_cap_array.pyImport CDL netlist via spiceIn
Maestro01_read_focused_maestro.pysnapshot() of focused maestro (in-memory, JSON to stdout)
02_snapshot_with_metrics.pyFull disk dump (snapshot -o ROOT) of focused maestro
03_bg_open_read_close_maestro.pyBackground open/close + read (no GUI window)
04_gui_open_snapshot_close.pyGUI open → snapshot → close (owns lifecycle)
05_gui_session_lifecycle.pyopen_gui_session / close_gui_session lifecycle test
06a_rc_create.pyCreate Maestro test for RC filter (auto-timestamped cell)
06b_rc_simulate_and_read.pyRun simulation in background, read results, export waveforms
Spectre01_inverter_tran.pyCMOS inverter transient simulation
01_veriloga_adc_dac.py4-bit ADC/DAC with Verilog-A
02_cap_dc_ac.pyRC filter DC+AC frequency response
04_strongarm_pss_pnoise.pyStrongArm comparator PSS+Pnoise

Citation

@article{zhang2025virtuosobridge, title = {Virtuoso-Bridge: An Agent-Native Bridge for Remote Analog and Mixed-Signal Design Automation}, author = {Zhang, Zhishuai and Li, Xintian and Sun, Nan and Jie, Lu}, year = {2025} }