{
    "name": "spectre",
    "version": "1.0.0",
    "description": "Run Cadence Spectre simulations remotely via virtuoso-bridge: upload netlists, execute, parse PSF results. TRIGGER when the user wants to run a SPICE/Spectre simulation from a netlist file, do transient/AC/PSS/pnoise analysis outside Virtuoso GUI, parse PSF waveform data, run multiple simulations in parallel across one or more servers, check simulation job status, or mentions Spectre APS/AXS modes. Also triggers for sim-jobs, sim-cancel, or parallel/concurrent simulation requests. Use this for standalone netlist-driven simulation — for GUI-based ADE Maestro simulation, use the virtuoso skill instead.",
    "system_prompt": "name spectre description Run Cadence Spectre simulations remotely via virtuoso-bridge: upload netlists, execute, parse PSF results. TRIGGER when the user wants to run a SPICE/Spectre simulation from a netlist file, do transient/AC/PSS/pnoise analysis outside Virtuoso GUI, parse PSF waveform data, run multiple simulations in parallel across one or more servers, check simulation job status, or mentions Spectre APS/AXS modes. Also triggers for sim-jobs, sim-cancel, or parallel/concurrent simulation requests. Use this for standalone netlist-driven simulation — for GUI-based ADE Maestro simulation, use the virtuoso skill instead. Spectre Skill Run a .scs netlist locally or on a remote machine through SSH, then parse PSF results into Python dicts. Independent of VirtuosoClient — no GUI needed. Before you start virtuoso-bridge is a Python CLI — install it in a virtual environment with uv pip install -e virtuoso-bridge-lite . virtuoso-bridge status — check connection, Spectre path, license Check examples/02_spectre/ — use existing examples as a basis spectre must be on PATH , or set VB_CADENCE_CSHRC (project or user .env ) so the runner can source the Cadence environment. This applies to local and SSH execution. Core pattern from virtuoso_bridge.spectre.runner import SpectreSimulator, spectre_mode_args sim = SpectreSimulator.from_env( spectre_args=spectre_mode_args( \"ax\" ), # APS extended (recommended) work_dir= \"./output\" , ) result = sim.run_simulation( \"my_netlist.scs\" , {}) if result.ok: vout = result.data[ \"VOUT\" ] else : print (result.errors) With Verilog-A includes: result = sim.run_simulation( \"tb_adc.scs\" , { \"include_files\" : [ \"adc.va\" , \"dac.va\" ], \"spectre_args\" : [ \"+aps\" ]}, ) include_files and per-run spectre_args have the same meaning in local and SSH mode. They stage include files and extend the command; they do not inject arbitrary circuit parameters. For local-only execution, use SpectreSimulator.local(...) , or configure VB_REMOTE_HOST=localhost and use from_env() . Result object Attribute Content result.ok Whether simulation succeeded result.data Parsed waveforms plus scalar OP values; STRUCT OP entries are flattened as \"instance:parameter\" (for example \"M0:gm\" ) result.errors Error messages (short, classified); check these whenever result.ok is false result.metadata[\"timings\"] Execution and parse durations, plus transfer timing in SSH mode result.metadata[\"output_dir\"] Local path to .raw directory Treat result.ok as the execution contract. A nonzero exit, explicit fatal Spectre output, netlist read-in error, or explicit convergence failure returns FAILURE / PARTIAL even if the raw directory contains incomplete files. Do not use a non-empty result.data as proof that the simulation succeeded. Strict PSF accessors Use the strict helpers when a downstream calculation must fail loudly instead of silently accepting the wrong file, signal spelling, or malformed data: from pathlib import Path from virtuoso_bridge.spectre.psf import ( frequency_hz, read_psf_ascii, result_file, scalar, vector, ) raw = Path(result.metadata[ \"output_dir\" ]) ac = read_psf_ascii(result_file(raw, \"ac.ac\" )) freq = frequency_hz(ac) # exact \"freq\" key; finite, increasing vout = vector(ac, r\"VOUT\" ) # exact raw PSF key; finite, non-empty dc = read_psf_ascii(result_file(raw, \"dcOp.dc\" )) gm = scalar(dc, r\"M0:gm\" ) # exact raw PSF key; one finite real result_file() requires exactly one matching file below the explicit raw PSF root. The value helpers never normalize names or guess aliases: pass the exact key emitted by the parser, including Spectre's \\< / \\> escapes. scalar() accepts one finite real number, vector() accepts a non-empty finite numeric vector, and frequency_hz() additionally requires real, strictly increasing samples. Gotchas (Spectre 21.1 + IC618 lab cluster) These are silent or near-silent foot-guns from real lab runs: -param X=Y CLI flag is BROKEN. Spectre 21.1 parses the value as a second input netlist → SPECTRE-132: input file has been re-specified as 'X=Y' . Workaround : bake parameters into the netlist (regenerate the master per sweep point with txt.replace(\"parameters X=0\", f\"parameters X={val}\") ). parameters X=Y re-declaration after include \"header.scs\" does not update DEPENDENT expressions. E.g., header has parameters N=64 t_end=((N+N_extra)/Fs) , then later parameters N=256 — N updates but t_end stays at 276 ns (eagerly bound from the first declaration). Symptom: tran stops far too early. Fix : copy header locally and edit the parameters line in place. Default timeout=600 s is too short for noised long-tran . With tranNoise=yes + N≥256 or 6+-way parallel contention, a single run can exceed 600 s wall while spectre is still progressing — bridge reports \"Remote command timed out\" but spectre.out actually shows clean completion. Fix : SpectreSimulator.from_env(timeout=3600, ...) . PSF parser keeps \\<> escape chars in signal names. Saved signal DOUT\\<0\\> parses as dict key r\"DOUT\\<0\\>\" , not \"DOUT<0>\" . Symptom: KeyError: 'DOUT<0>' even though save list looks right. strobeoutput=all in psfascii outputs only the continuous tran. Despite the docs implying \"both continuous + strobed\", Spectre 21.1's psfascii emitter writes just the continuous stream into tran.tran.tran . You'll get ~140k samples per signal instead of N strobed values. Fix : either Python-strobe yourself with np.searchsorted(t, k/Fs + offset) , or use strobeoutput=strobeonly (which DOES work and shrinks the PSF ~1500×). Parallel simulation For a fixed batch, use run_parallel() . It creates a scoped executor for that call and releases it automatically, so concurrency settings never leak between batches: results = sim.run_parallel([ (Path( \"tb_comp.scs\" ), {}), (Path( \"tb_dac.scs\" ), {}), ], max_workers= 4 ) For incremental asynchronous submission, use an explicitly owned pool: with sim.parallel_pool(max_workers= 4 ) as pool: t1 = pool.submit(Path( \"tb_comp.scs\" )) t2 = pool.submit(Path( \"tb_dac.scs\" )) result = t1.result() results = pool.wait_all([t1, t2]) Each task gets a unique <netlist-stem>__<run-id>/ directory below work_dir , plus its own remote directory when applicable, so even repeated submissions of the same deck do not overwrite PSF data or auxiliary files. For full API and multi-server setup, read references/parallel.md . Simulation modes Precision ordering (measured on an 11-bit sub-radix-2 SAR ADC tran, N=128 coherent FFT, ax baseline ≈ 220 s): arg preset speed ENOB Δ vs aps use for \"spectre\" (none) slowest reference least license demand, basic direct \"aps\" +preset=aps 1.0× (gold) 0.000 sign-off accuracy reference \"cx\" +preset=cx 1.2× −0.03 sign-off for designs with mixed-signal stiff loops (cmp metastability) \"ax\" +preset=ax 2.0× −0.03 (within noise) default for daily work \"mx\" +preset=mx 3.8× −0.29 design exploration, corner sweeps where 0.3 ENOB is acceptable \"lx\" +preset=lx 5.9× −2.8 ( unusable for SAR ) small-signal AC / linear DC sweeps; not for circuits with cmp/regen \"vx\" +preset=vx 8.8× −8.5 ( totally fails ) verification-style connectivity / DC convergence only — never for transient signal fidelity spectre_mode_args( \"ax\" ) # default for daily transient work spectre_mode_args( \"aps\" ) # reference / sign-off spectre_mode_args( \"mx\" ) # fast iteration if ENOB ≤ 0.3 loss is OK Critical: SAR / latched-comparator circuits and any topology with metastable regeneration depend on tight reltol (1e-4 or better) to resolve LSB-scale differential inputs. lx relaxes reltol to ~1e-3 and drops ENOB by ~3 bits on such circuits; vx disables LTE bounding entirely and produces garbage. Reserve those two for non-signal-fidelity work (DC, connectivity, link-test). If a Maestro config you inherit specifies +preset=lx or +preset=vx for a transient performance sim, that's almost always a bug. When (and when not) to replace cells with Verilog-A for speedup Verilog-A behavioral replacement of cells is a tempting acceleration lever, but the speedup is non-monotonic in cell size — replacing big cells helps, replacing small cells hurts . Measured on a 11-bit SAR ADC tran (ax mode, N=64, baseline 132s): Cell replaced Transistor count Wall-time change Result Output capture DFFs (1-pin behavior, 12 instances × 1 D-FF each) 12 × ~10 MOS 0% (neutral) ✓ Easy, no gain — skip unless cleaning the netlist Per-bit SAR latch with feedback (12 × ~12 MOS + 4 std cells) ~200 MOS total −13% (slower) ✗ transition() event-queue overhead × 11 concurrent instances exceeds the BSIM equation savings StrongARM comparator (47 MOS) 47 MOS, 1 instance +9-17% ✓ Big cell, single instance — clear win Rule of thumb : VA replacement helps when the cell is large (≥ 40 MOS) and instantiated once or twice . It hurts when the cell is small (< 20 MOS) and many instances share the same input event source — each @(cross()) adds to the spectre event queue; with N concurrent instances watching the same node, queue overhead grows ~N× while the BSIM savings stay linear in N. Self-timed feedback loops are extra-fragile : replacing one element of an async chain (e.g., a SAR daisy-chain latch with feedback to CMPCK) requires matching not just the steady-state truth table but the propagation delay and edge timing to within a few ps. Standalone unit-test the VA before integrating into the chain; if the unit-test passes but the chain breaks, suspect transition() td interacting with multiple concurrent listeners. The actually-effective SAR speed levers (measured, not from VA): Lever Mechanism Typical speedup ENOB cost Cut FFT N (e.g., 128 → 64) Tran stop time scales linearly ~40% 0 (within meas noise) strobeoutput=strobeonly + lean save Cuts download + parse overhead; file size 1000× smaller ~5-10% wall, 1500× disk 0 Replace 1-2 big cells (cmp / opamp) with VA Skip BSIM equations for ~50+ MOS ~10-20% depends on VA fidelity Drop LPE std-cell models for schematic-spi Remove per-cell wire parasitics ~20% minor timing shift Increase maxstep Fewer solver iterations ~20% per 2× depends on circuit, risky for cmp metastability Spectre mode ax → mx Looser solver tolerance ~50% −0.3 ENOB on SAR The first four stack without ENOB cost. The last two trade accuracy for speed. Output size control: save list, strobing, format By default the .scs netlist's tran tran ... directive saves at every solver timestep for every signal — a clocked SAR-style transient at maxstep=5p over hundreds of ns produces 100+ MB of PSF ASCII per signal group. Three knobs: 1. saveOptions options save=<mode> + explicit save list save CLKS RSTP I_SAR.VTOPP DOUT\\<11\\> ... DOUT\\<0\\> saveOptions options save=selected save=allpub — every public node + every terminal current (huge default). save=selected — only the nodes/terminals in the explicit save line. save=lvlpub — pub down to a given hierarchy level. For production runs of large mixed-signal designs, always use save=selected with a curated 10-20 signal list. save=allpub is the most common cause of runaway PSF size on lab-cluster sims. 2. strobeoutput=<mode> (gotcha: \"all\" is bigger, not smaller) The tran tran ... directive accepts strobeperiod and strobeoutput : tran tran stop=t_end maxstep=5p \\ strobeperiod=1/Fs strobeoutput=strobeonly ... Mode What gets saved Use for strobeoutput=all Every solver timestep PLUS strobed samples (biggest file) Debugging — need waveform shape between samples strobeoutput=strobeonly Only strobed samples (1 sample per strobeperiod ) ENOB / SNDR / corner sweeps where you only need per-cycle values The name \"all\" misleads — it means \"both continuous and strobed views,\" not \"all signals.\" Switching to strobeonly typically cuts file size 500×-1500× on N=64..256 sims. For ENOB-only runs of a clocked ADC , strobeonly is the right default. 3. output_format — PSF ASCII vs binary The bridge currently uses output_format=\"psfascii\" by default, parsed via parse_spectre_psf_ascii . output_format=\"psfbin\" is NOT supported by the in-tree parser ( virtuoso_bridge/spectre/parsers.py has no parse_spectre_psf_bin ). Passing it will produce a .raw directory the local side cannot read. If you need 10× smaller PSF files: add a binary parser (e.g., wrap psf_utils — pure Python, pip install). Until then, the size lever is save=selected + strobeoutput=strobeonly , not the format. Transient noise ( tranNoise=yes ) tran tran is deterministic by default — no thermal / 1/f noise injected. Most BSIM models have noise params but they only fire during noise analysis or when tranNoise=yes is on the tran line: tran tran stop=t_end maxstep=5p \\ tranNoise=yes noisefmax=50G noiseseed=1 noisetmin=1 binnum=16 noiseruns=1 \\ write=\"spectre.ic\" writefinal=\"spectre.fc\" annotate=status Param Meaning Default-ish value tranNoise=yes Enable the noise injection at all off noisefmax=<f> Max frequency for noise integration 5×Fclock or 1× signal BW (smaller = faster) noiseseed=<n> RNG seed for one run 1 noisetmin=<t> Earliest time when noise becomes active 0 (or 1×Ts to skip startup) binnum=<n> Frequency-bin discretization (Wiener model) 16",
    "model_config": {
        "provider": "deepseek",
        "model": "deepseek-chat",
        "temperature": 0.7,
        "max_tokens": 4096,
        "top_p": 0.9
    },
    "trigger_words": [],
    "source": "DeepseekModel",
    "source_url": "https://deepseekmodel.com/skill?id=arcadia-1-virtuoso-bridge-lite-skills-spectre-skill-md"
}