| name | SIM-IO |
| description | Build simulation testbenches for IO Ring / mixed-signal designs in Cadence Virtuoso. Automates the full flow: symbol export -> pin redistribution -> testbench creation -> DUT placement -> LLM-driven pin classification -> source/load placement with label-based wiring. Use this skill whenever the user wants to create a simulation testbench, classify IO pins, place stimulus/load on a DUT, or run the sim_flow pipeline for any design. Also use when the user mentions "sim flow", "testbench", "TB setup", "pin classification", "source placement", or "stimulus generation" in the context of Virtuoso simulation.
|
SIM-IO - Simulation Testbench Builder
Master orchestrator for building a complete simulation TB around a DUT cell in Cadence Virtuoso.
Pipeline at a Glance
Source Schematic
|
[symbol_export] symbol export -> pin redistribution -> pin extraction
|
pin_info.json
|
[LLM stop] read pin_info.json + pin_classification.md
-> write pin_classifications.json
|
[testbench_build] create TB schematic -> place DUT -> wire labels
-> place sources/loads -> Maestro setup
|
[Sim] Spectre netlist -> deck build -> run -> check results
Each workflow step is a CLI call. The LLM classification step is a deliberate pause between them.
Entry Points
| Situation | Start here |
|---|
Fresh run: user provides lib + cell | Step 0 -> symbol_export |
symbol_export already done (.latest_run exists, no classification yet) | LLM Classification |
pin_classifications.json already written | testbench_build |
| TB exists, run simulation only | spectre_runner.py --run-dir <path> |
Step 0: Environment Setup
Run once per session before any other step.
SKILL_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/SIM-IO"
PROJECT_ROOT="$(cd "${SKILL_ROOT}" && while [ ! -d .venv ] && [ "$(pwd)" != "/" ]; do cd ..; done; pwd)"
if [ -f "${PROJECT_ROOT}/.venv/Scripts/python.exe" ]; then export AMS_PYTHON="${PROJECT_ROOT}/.venv/Scripts/python.exe"
elif [ -f "${PROJECT_ROOT}/.venv/bin/python" ]; then export AMS_PYTHON="${PROJECT_ROOT}/.venv/bin/python"
elif command -v python3 &>/dev/null; then export AMS_PYTHON="python3"
else echo "ERROR: No Python found."; return 1; fi
echo "AMS_PYTHON=${AMS_PYTHON}"
[ -f "${SKILL_ROOT}/.env" ] && { set -a; . "${SKILL_ROOT}/.env"; set +a; }
All subsequent steps use $AMS_PYTHON.
Ask the user for lib and cell if not provided. Optional: --vdd <volts> (default 1.8).
symbol_export: Symbol Export + Pin Redistribution + Extraction
$AMS_PYTHON ${SKILL_ROOT}/scripts/symbol_export.py <lib> <cell> [--vdd <vdd_value>]
What happens internally:
- TSG export - generates
{lib}/{cell}/symbol from schematic via schSchemToPinList + schPinListToSymbol
- Redistribution - extracts symbol geometry (
extract_symbol_info.il), computes new layout (Python), applies it (apply_layout.il) - pins reorganized left/right
- Pin extraction - reads terminal names, directions, positions from redistributed symbol
Outputs:
output/<timestamp>/pin_info.json - input to LLM classification
output/<timestamp>/dut_context.json - checkpoint for testbench_build
.latest_run - absolute path to the run directory
Exit codes:
0 -> proceed to LLM Classification
1 -> error printed to stderr (common causes: Virtuoso not connected, lib/cell not found, no schematic view)
pin_intent_authoring (Between symbol_export and testbench_build - YOU do this)
symbol_export stops here. You must produce two files before testbench_build can run:
pin_classifications.json and sim_config.json. Both go in the same run directory.
Find the run directory: read SIM-IO/.latest_run or use the path printed by symbol_export.
File 1 - Pin Classifications
- Read
references/pin_classification.md - classification rules, topology tables, domain definitions
- Read
<run_dir>/pin_info.json - pin names, directions, positions, side (left/right)
- Classify every pin according to the rules
- Write
<run_dir>/pin_classifications.json following schema in scripts/pin_classify_schema.json
Key principles (full rules in references/pin_classification.md):
pin_type from pin name prefix + direction
domain (analog / digital / digital_hv) - sets the ground reference
stimulus + stimulus_params for the outer (left/pad) side
inner_stimulus + inner_params for the inner (right/CORE) side
- Non-round values only (e.g.
2.7m not 3m, 1.72 not 1.8)
File 2 - Simulation Config
- Read
references/sim_config_rules.md - IO Ring simulation rules
- From
pin_classifications.json, collect all vpulse stimulus params to compute tstop:
- Gather every
per value across all pin stimulus/inner_stimulus params
tstop = 10 x max(per), clamped to [100n, 10u]
- If no vpulse sources: use
500n
- List every placed device that is NOT
pin_type=ground and NOT pin_type=no_connect:
these are SRC_<pin>, LOAD_<pin>, INNER_<pin> instances
- Write
<run_dir>/sim_config.json
sim_config.json is consumed by testbench_build in two places:
- Maestro setup (Step 4e) - configures analyses and outputs in Virtuoso Maestro
- Spectre deck (Step 5) - controls netlist analyses, save statements, and power expressions
Schema (see scripts/sim_config_schema.json for full spec).
The LLM specifies measurement intent via pin_measurements - the code translates
intent into correct Maestro OCEAN expressions automatically. Never write raw OCEAN
expressions in outputs; use pin_measurements instead.
{
"analyses": [
{"name": "dc", "enabled": true},
{"name": "tran", "enabled": true,
"stop": "<tstop>", "maxstep": "<tstop/1000>", "errpreset": "moderate"}
],
"model_includes": [],
"save_default": "allpub",
"pin_measurements": {
"VDD": {"measures": ["voltage", "current", "power"], "spec": {"i_max": "0.1"}},
"D0": {"measures": ["voltage"], "spec": {"vmax_above": "0.9*VDD", "vmin_below": "0.1*VDD"}},
"GND": {"measures": []}
}
}
One pin_measurements entry per DUT pin. model_includes is always [] - testbench_build injects PDK paths from .env automatically. save_default is always "allpub" - code auto-upgrades to "all" when current/power measurements are detected.
testbench_build: TB Build + Source/Load Placement + Maestro
$AMS_PYTHON ${SKILL_ROOT}/scripts/tb_builder.py [--run-dir <path>]
--run-dir is optional; defaults to path in .latest_run.
What happens internally:
- Create TB cellview - creates
{lib}/{cell}_tb/schematic (fresh, overwrites if exists)
- Place DUT - instantiates
{lib}/{cell}/symbol as DUT at (2.5, 0.0)
- Wire labels - places net name labels on each DUT terminal (label-based wiring - no explicit wires)
- Sources + loads - places stimulus/load devices based on your
pin_classifications.json:
- Outer (left): sources/loads for pad-side signals
- Inner (right): complementary devices for CORE-side signals
- PVSS devices (one per ground pin) + GND_REF bridge to
gnd!
- CDF parameters set via
set_inst_params.il
- Maestro setup - configures Maestro test for GUI simulation
Outputs:
{lib}/{cell}_tb/schematic in Virtuoso
output/<timestamp>/result.json - full run summary
Exit codes:
0 -> TB complete, proceed to simulation or stop
1 -> error printed to stderr
Step 5: Direct Spectre Simulation + Maestro Sync
$AMS_PYTHON ${SKILL_ROOT}/scripts/spectre_runner.py [--intent "<description>"]
Or if the TB is already built, pass --run-dir to an existing run directory.
Internal flow:
- Export a fresh Spectre netlist from the testbench schematic.
- Build
deck.scs from sim_config.json plus site model includes.
- Run Spectre directly through
SpectreSimulator.
- Parse PSF results locally into
measurements.json and SVG plots.
- Sync the same resolved settings into Maestro for GUI inspection.
Maestro is required for setup/GUI state, but this route does not run
simulation through Maestro. Do not use Maestro results as the verification
source for this workflow; use sim_run_result.json, measurements.json,
verify.json, and plots/.
Troubleshooting
| Problem | Solution |
|---|
| symbol_export exit 1 "not found in Virtuoso" | Check lib/cell spelling; verify Virtuoso is connected and cds.lib is loaded |
| symbol_export exit 1 "no schematic view" | Cell exists but has no schematic - open schematic in Virtuoso first |
| Virtuoso not responding | Check SIM_VB_LOCAL_PORT in .env; verify virtuoso-bridge start is running |
| Symbol redistribution wrong layout | Inspect output/<ts>/extract_raw.txt and layout_result.json; check LayoutConfig in sim_io/symbol/layout_engine.py |
| testbench_build: "pin_classifications.json not found" | WARNING only - runs with heuristic fallback. Write the file for accurate placement |
| testbench_build: wrong device placed | Re-check pin_classifications.json; verify pin_type, domain, stimulus, inner_stimulus fields |
| Spectre: license error | Set SIM_LM_LICENSE_FILE and SIM_CDS_LIC_FILE in .env |
| Spectre: no convergence | Check stimulus values - ensure vdc/vpulse params are within PDK operating range |
| Maestro eval error | Known issue - Maestro dialog may require manual confirmation; see memory feedback_sim_io_pipeline.md |
si netlist export hangs | Confirmation dialog opened in Virtuoso GUI - dismiss it manually or set si_batch=yes in site config |
File Guide
| Path | Purpose |
|---|
scripts/symbol_export.py | CLI: Symbol export + pin extraction entry point |
scripts/tb_builder.py | CLI: TB build entry point |
scripts/spectre_runner.py | CLI: direct Spectre simulation + Maestro setup sync |
scripts/maestro_runner.py | Legacy/debug CLI: Maestro setup + optional Maestro simulation |
sim_io/flow.py | Core workflow primitives and DutContext |
sim_io/pin_types.py | PinInfo, PinClassification, heuristic fallback, JSON loader |
sim_io/symbol/layout_engine.py | Pure-Python layout calculator for pin redistribution |
sim_io/bridge/edit_patterns.py | Virtuoso schematic editing API (batch_ops, label_term, create_inst) |
sim_io/sim/viz.py | TranData, plot_tran() - SVG waveform generator |
sim_io/maestro/setup.py | Maestro testbench setup generator |
sim_io/maestro/results.py | parse_maestro_measurements() - Maestro outputs -> measurements.json |
sim_io/maestro/waves.py | plot_maestro_waves() - maestro_waves/*.txt -> SVG |
skill_code/extract_symbol_info.il | SKILL: extract symbol geometry (called in symbol_export Step 2) |
skill_code/set_inst_params.il | SKILL: set CDF parameters on instances (called in testbench_build Step 4) |
references/pin_classification.md | Classification rules + dual-side topology tables - read before classifying |
scripts/pin_classify_schema.json | JSON schema for pin_classifications.json |
scripts/sim_config_schema.json | JSON schema for simulation deck configuration |
.env | Site-specific paths: cds.lib, IC_ROOT, MMSIM_ROOT, license, PDK model paths |
.latest_run | Absolute path to current run directory (written by symbol_export) |
Run Directory Structure
output/<YYYYMMDD_HHMMSS>/
|-- pin_info.json symbol_export output -> LLM input
|-- pin_classifications.json LLM output -> testbench_build (source/load placement)
|-- sim_config.json LLM output -> testbench_build (Maestro setup + Spectre deck)
|-- dut_context.json Symbol export checkpoint (loaded by tb_builder.py / maestro_runner.py)
|-- measurements.json Direct Spectre measurement results
|-- sim_run_result.json Direct Spectre + Maestro sync summary metadata
|-- build/
| |-- extract_raw.txt Raw output from extract_symbol_info.il
| |-- layout_result.json Computed pin layout (debug only)
| |-- apply_layout.il Generated SKILL for redistribution
| `-- skill_code/ Logged copies of all .il files used
|-- spectre/
| |-- netlist.scs Exported Spectre netlist
| |-- deck.scs Complete direct-Spectre deck
| |-- spectre.out Spectre log
| `-- deck.raw/ PSF data from Spectre
`-- plots/ SVG waveforms (DC/AC/TRAN)
Checklist