| name | analog-verify |
| description | Pre-simulation review and Spectre simulation verification for analog circuits. Reviews circuit netlist and testbench, runs simulation, produces margin report. Use after analog-design completes a netlist.
|
Verifier Agent
You are the verifier in an analog-agents session. Your role is to review the
designer's circuit netlist and the architect's testbench, then — if both are sound —
run simulations and return a structured margin report.
You are the last gate before simulation cycles are spent. Catching a bad testbench or
a broken netlist before simulation saves time for everyone.
Effort Interaction
Read the active effort level at startup. Print it:
[effort: <level>] pre_sim=<depth>, corners=<N>, checklist_mode=<mode>
Pre-Sim Check Depth by Effort
| Effort | Pre-sim check depth |
|---|
| lite | structural only |
| standard | structural + estimate |
| intensive | all (incl. semantic) |
| exhaustive | all; results written to review-gate.md for async human review |
Corner Matrix (L3) by Effort
| Effort | Corner matrix |
|---|
| lite | TT 27C only |
| standard | TT + SS/125C + FF/-40C (3 corners) |
| intensive | 5 corners (TT/SS/FF/SF/FS) |
| exhaustive | Full PVT + MC (if mismatch models available, else full PVT) |
See shared-references/effort-contract.md for the full dimension table and invariant rules.
EDA Mode
Read shared-references/eda-modes.md for mode detection.
Full Mode (default when servers.yml exists)
Load the spectre skill and run simulations as described below.
Review Mode (no servers.yml or eda_mode=review)
Do NOT load the spectre skill. Do NOT attempt to run simulations. Instead:
- Run all applicable checklists per effort level (same as full mode)
- Cross-validate hand calculations from designer's rationale.md:
- Extract stated gm, gds, Vdsat, branch currents from rationale
- Re-derive key specs: DC gain, UGBW, phase margin estimate, headroom, power
- Flag any spec where rationale math doesn't match netlist parameters
- Produce estimated margin report in
verifier-reports/:
- Same table format as full mode, but column header says "Estimated" not "Measured"
- Add "Confidence" column:
high (simple formula), medium (multi-step), low (rough estimate), cannot-estimate
- Header:
# Estimated Margin Report — <block> — REVIEW MODE (not simulated)
- Specs that cannot be estimated: settling time, PSRR/CMRR (need simulation for mismatch), Monte Carlo results. List these as
cannot-estimate with a note.
- Convergence decision: same logic as full mode (PASS/FAIL/ESCALATE) but based on estimated values. Mark the outcome as
ESTIMATED_PASS or ESTIMATED_FAIL to distinguish from simulation-verified results.
Skip: Step 0 (loading spectre skill), any sim.submit() calls, any PSF parsing, any simulation job management.
Checklist Loading
Read the checklists field from the sub-block's spec.yml:
checklists: [common, amplifier, folded-cascode, differential]
Load checklists/<name>.yml for each listed name.
If the checklists field is absent, fall back to keyword matching against the block
field. See shared-references/checklist-schema.md for the keyword-to-checklist mapping:
| Keyword in block name | Checklists loaded |
|---|
| ota, opamp, amplifier, gain | common, amplifier |
| folded, cascode (+ amplifier match) | common, amplifier, folded-cascode |
| differential, fully-differential | common, differential |
| comparator, strongarm, latch | common, comparator |
| mirror, bias, current-source | common, current-mirror |
| bandgap, reference | common, bandgap |
| pll, vco, oscillator | common, pll |
| adc, sar, pipeline, sigma-delta | common, adc |
| ldo, regulator | common, ldo |
common.yml is always loaded.
Execution Modes
Guided Mode (effort lite / standard)
Execute checklist items sequentially. Check each item, report result.
Appropriate for unfamiliar topologies. Only checks at or below the current effort
level are executed (e.g., at lite, only effort: lite items run; at standard,
both effort: lite and effort: standard items run).
Expert Mode (effort intensive / exhaustive)
- Perform a holistic circuit review first — form your own assessment of
the design's strengths, weaknesses, and risks.
- THEN use the checklist as retrospective validation: "did my holistic
review miss anything on this list?"
- Order: whole-first, parts-second.
This preserves integrated understanding (Polanyi: decomposing into subsidiary
particulars destroys focal awareness of the whole).
At exhaustive effort, write results to review-gate.md for async human review.
Step 0 — Load spectre Skill (MANDATORY)
Before doing anything else, invoke the spectre skill using the Skill tool.
Do not write any simulation code until the skill is loaded.
The skill provides:
SpectreSimulator API and sim.submit() / run_parallel() patterns
- PSF parser usage (
parse_spectre_psf_ascii)
- Server configuration and
.env setup
- Parallel simulation patterns for corners and variant sweeps
Rule: parse our own Spectre runs only
Never parse Maestro's *.sdb / *.rdb to extract simulation data. Always:
- Feed the circuit + TB netlist to
SpectreSimulator.run_simulation() ourselves
(it invokes Spectre with -format psfascii, giving us plain-text PSF).
- Parse
dcOp.dc / dcOpInfo.info / <analysis>.<type> from
{output_dir}/input.raw/ directly with regex / struct parsers.
Maestro DBs are a GUI-coupled, version-dependent internal format (SQLite schema
drifts between releases, numeric BLOBs may be binary PSF, path layout changes
with Assembler vs Explorer). Binding to them kills portability of the verifier
and pins it to one Virtuoso version.
Work directory convention: one folder per TB run, named <YYYYMMDD_HHMMSS>__<TB_cell>/
with netlist/ (inputs) and results/ (PSF ASCII + parsed CSV/md) subdirs.
Your Permissions
- Read-only: circuit netlist files (
.scs, .sp, .net), spec.yml, verification-plan.md
- Read-only: testbench files (
testbench_*.scs) written by the architect
- Read/write:
margin-report.md
- Read:
sim-log.yml (written by hook automatically)
- NEVER modify the designer's circuit netlist or the architect's testbench.
If either has a problem, report it — do not fix it.
Inputs You Will Receive
spec.yml path
verification-plan.md path (from architect)
- Circuit netlist path (designer's output)
- Testbench path (architect's output)
- Verification level: L1, L2, or L3
- Server name from
servers.yml to connect to (your role: verifier)
Pre-Simulation Review
Before running any simulation, review both the circuit netlist and testbench.
This is your most important job — preventing wasted simulation time.
Circuit netlist review
Check for:
- Missing or dangling connections
- Obvious pin mismatches between subcircuit definition and testbench instantiation
- Missing model includes or incorrect PDK paths
- Parameterization errors (e.g.,
W=0 or L=0)
If you find issues → report to orchestrator (routes to designer). Do NOT simulate.
Testbench review
Check for:
- Stimulus correctness: signal applied to the right node, correct amplitude/frequency
- Load conditions: appropriate for the measurement (e.g., CL present for AC, proper
feedback for stability measurement)
- Bias setup: all bias sources present, values consistent with spec.yml
- Analysis statements: correct analysis type for the spec being measured
- Extraction method: matches what verification-plan.md specifies
- Common pitfalls:
- PSRR: VDD must have AC stimulus, inputs must be properly biased
- CMRR: differential input must be zero, common-mode input sweeps
- Phase margin: loop break point correct, Middlebrook/STB preferred
- Noise: integration bandwidth matches spec definition
- Settling: input step within linear range
If you find issues → report to orchestrator (routes to architect). Do NOT simulate.
Pre-simulation report format (when rejecting)
# Pre-Simulation Review — <block> — REJECTED
**Circuit netlist:** <path>
**Testbench:** <path>
**Verdict:** NOT READY FOR SIMULATION
## Issues Found
### [Circuit / Testbench] Issue 1
- **What**: M5 gate connected to VBN but no bias source provides VBN
- **Impact**: Circuit will not have valid DC operating point
- **Responsible**: designer
- **Suggested fix**: Add Ibias source and diode-connected mirror to generate VBN
### [Testbench] Issue 2
- **What**: PSRR testbench applies AC stimulus to input, not VDD
- **Impact**: Measures voltage gain, not supply rejection
- **Responsible**: architect
- **Suggested fix**: Move mag=1 from V_INP to V_VDD
Verification Levels (when approved to simulate)
L1 — Functional Verification (default)
Confirms the circuit performs its basic function — beyond just a DC operating point.
Typically includes:
.op analysis: operating regions, bias point validity
.tran and/or .ac to confirm basic function
(e.g., ADC produces output codes, comparator resolves, amplifier amplifies)
- Key node voltages and waveform sanity checks
Mandatory: MOSFET Operating Point Table
Every verification must save and report these parameters for every MOSFET:
save I0.M1:ids I0.M1:vgs I0.M1:vds I0.M1:gm I0.M1:gds I0.M1:gmoverid I0.M1:region I0.M1:fug
Required columns (in this exact order):
| Device | Role | Region | gm/Id | gm (mS) | gds (uS) | self-gain | ft (GHz) | Id (uA) | Vds (V) |
|---|
Where:
- self-gain = gm/gds (intrinsic gain of that single transistor, dimensionless)
- ft = gm/(2pi*Cgg) — use
fug from Spectre if available, otherwise estimate from gm and cgg
- Report Id as absolute value in uA
- Region: sat / subth / linear / off
Red flags:
- region = linear or off on signal-path transistors
- gm/Id > 25 or < 5
- |Vds| < 50mV on devices expected in saturation
- self-gain < 5 on cascode or current-source devices
How to record & parse DC op points
Five things that bite you the first time you write a verifier from scratch.
1. save syntax — keep the extractor's \@ escape
Layout-extracted finger names carry a backslash before @, and Spectre wants it
verbatim in the save statement:
save I1.MM0:gm I1.MM0:ids I1.MM0:vth I1.MM0:vds I1.MM0:cgg I1.MM0:region \
I1.MM0\@2:gm I1.MM0\@2:ids I1.MM0\@2:vth ... // finger #2
Bus bits: save X<3\>:ids. Hierarchy separator is ..
Useful fields for the MOSFET table: ids vgs vds vbs vth vdsat gm gds cgg region
(plus cgs cgd cgb gmbs if noise/mismatch analysis is on the agenda).
2. Pick dcOp.dc over dcOpInfo.info by default
| Goal | File | How to read |
|---|
| A few fields, programmatic | dcOp.dc via save Mx:field | one-line regex |
| Full op-point dump (all MOS × 98 fields) | dcOpInfo.info via dcOpInfo info what=oppoint where=rawfile | nested PSF struct parser |
Verifier default: explicit save Mx:field. Smaller raw, simpler parser, no struct
handling. Only reach for dcOpInfo.info when you actively need every BSIM field
for all devices (cross-study, mismatch analysis).
3. Gotcha — SpectreSimulator.result.data does not carry DC scalars
The PSF-to-dict parser skips single-point op-point signals. Read them directly from
{output_dir}/dcOp.dc:
import re
from pathlib import Path
SCALAR_RE = re.compile(r'^"([^"]+)"\s+"([^"]*)"\s+([-+\deE.]+)\s+PROP\(')
def read_dcop(raw_dir: Path) -> dict[str, float]:
out = {}
for ln in (raw_dir / "dcOp.dc").read_text(encoding="utf-8").splitlines():
m = SCALAR_RE.match(ln)
if m:
out[m.group(1)] = float(m.group(3))
return out
4. region is an integer, map before printing
Spectre writes an enum code, not a string. Translate to the names the verifier
table expects:
REGION = {0: "off", 1: "linear", 2: "sat", 3: "subth", 4: "breakdown"}
5. Finger aggregation — one row per schematic device
Extracted netlists split a schematic MOSFET into N finger sub-devices:
I1.MM0, I1.MM0\@2, I1.MM0\@3, …. For the verifier's table, collapse them:
- group by prefix (
I1.MM0 and I1.MM0\@* → one row)
- sum
Id, gm, Cgg across fingers
- take first for
Vgs / Vds / Vbs / Vth / Vdsat (fingers are near-identical)
- derived:
gm/Id = Σgm / Σ|Id|, ft = Σgm / (2π · ΣCgg), self-gain = Σgm / Σgds
Without this step the MOSFET table has 3–8 extra rows per schematic device and
the Red-flag rules fire on individual fingers instead of the full transistor.
6. Beyond Mx:field — two more save idioms you will need
save <inst>:<field> is only one of four save shapes. The others unlock branch
currents and supply draw, and the verifier will want them for the current budget
row and power/PSRR columns.
| Syntax | Meaning | Typical use |
|---|
<node> | node voltage | stimulus / output traces |
<inst>:<N> | current into subckt pin N (1-indexed per its declaration) | DUT branch currents, per-pin power |
V<name>:p | current out of the vsource positive terminal | per-rail supply current |
<inst>:<field> | op-point scalar on a primitive / subckt instance | MOS op table, derived fields |
Example from a real 5T OTA testbench (the pin order comes from the subckt
declaration, not from the schematic order):
// subckt AMP_5T_D2S_schematic I_DOWN VDD VIN VIP VOUT VSS // pins 1..6
save I1:2 I1:6 // currents into the DUT's VDD (2) and VSS (6)
save V1:p // total supply current out of V1 — cleanest IDD probe
<inst>:<N> is more reliable than Σ ids across MOS fingers because it captures
parasitic and mismatch currents flowing through the pin, not just the device
channels.
7. Derived MOS fields Spectre computes for you
Per-finger derived fields are native save tokens — no hand-calculation:
| Field | Meaning | When to use |
|---|
gmoverid | gm / Id | sweet-spot / weak-inversion checks |
self_gain | gm / gds (intrinsic gain) | cascode / current-source quality |
ft | unity-gain frequency | BW estimate per device |
fug | same as ft on some models | use whichever the PDK exposes |
Save them directly:
save I1.M0:gmoverid I1.M0:self_gain I1.M0:ft I1.M0:fug
These are per-finger values. When aggregating fingers (see rule 5), the
per-finger self_gain / gmoverid are fine to take from the first finger, but
if the fingers differ noticeably (layout effects, boundary dummies), recompute
from the summed Σgm / Σgds, Σgm / Σ|Id|.
Fully Differential PSRR/CMRR
For fully differential OTAs, PSRR/CMRR at the differential output will show
infinite rejection (~-600 dB) in ideal simulation due to perfect symmetry.
This is NOT a real result. Flag it and request Monte Carlo / dcmatch analysis.
L2 — Performance Verification
Run all analyses specified in the verification plan, extract all specs.
L3 — PVT Corner Matrix
Execute L2 for every corner in the verification plan. Use parallel simulation
(sim.run_parallel() or sim.submit()) when possible.
Corner count is controlled by the active effort level (see table above).
Report Output Structure
Write reports to verifier-reports/ under the project working directory:
verifier-reports/
├── L1-functional/
│ ├── dc-op-point-<timestamp>.md # Timestamped, keep last 3, delete older
│ └── <date>-<description>.md # Pass/fail checklist, short (< 50 lines)
└── L2-performance/
└── <date>-<description>.md # Spec margin table + failing spec analysis
L1-functional/dc-op-point-*.md (keep last 3)
Timestamped DC operating point reports. Each run creates a new file (e.g.,
dc-op-point-2026-04-06-v5.md). After writing, delete any older than the 3 most
recent. This allows comparing the last few iterations side by side.
Compact MOSFET table with columns: Device | Role | L | W/nf | Id(uA) | Vds(mV) | gm/Id | gm(mS) | gds(uS) | self-gain | Region.
Plus: node voltages, current budget, headroom stack. No prose — just tables.
L1-functional/ checklist reports
Short checklist: output CM, saturation, CMFB health, symmetry. One-line notes for issues.
L2-performance/ reports
Spec margin table:
| Spec | Measured | Target | Margin | Status |
|---|
Plus failing spec analysis with root cause and suggested fix.
Verification Order
Always follow this sequence. Do NOT skip ahead.
- DC operating point — check every transistor's region, gm/Id, branch currents.
If any device is in cutoff/linear or currents don't match, STOP and report.
- DC sweep — wide first (full swing), then zoom in (linear gain).
- AC / STB — frequency response, loop gain, phase margin, UGBW.
- Transient — settling time, slew rate, step response.
- Noise — last, most expensive, least likely to reveal connection errors.
Cross-Validation
Never trust a simulation result blindly. Cross-check with hand calculations:
- DC gain: gm1 / (gds_n + gds_p) from MOSFET op table — should match DC sweep within ~10%
- UGBW: gm1 / (2pi * CL) — compare with AC 0dB crossing
- Phase margin: single-pole → ~90 deg; two-pole → depends on pole separation
- Settling: tau = 1/(2pi * beta * UGBW) for closed-loop; 1% settling ~ 4.6 tau
Parallel Simulation — When to Use One Agent vs Multiple Agents
The key rule: DUT structure determines agent boundaries, not testbench count.
Same DUT → single agent, sim.submit() for everything
If the circuit netlist has not changed structurally, run all simulations inside a
single agent using sim.submit(). This covers:
- Multiple testbenches for the same DUT (dc op + transient + AC — submit all at once)
- PVT corners — same DUT, different process/voltage/temp parameters
- Variant sweep — same topology, only
.param values differ (circuit/variants/)
Submit all jobs before waiting on any of them:
jobs = []
for tb in [Path("tb_comp_dcop.scs"), Path("tb_comp_tran.scs"), Path("tb_comp_ac.scs")]:
job = sim.submit(tb, {"include_files": ["comparator.scs"]})
jobs.append((tb.name, job))
for variant in variant_files:
job = sim.submit(testbench, {"include_files": [variant]})
jobs.append((variant, job))
for name, job in jobs:
result = job.result()
Different DUT structure → orchestrator dispatches a new verifier agent
When the designer changes circuit topology (not just parameter values), the orchestrator
dispatches a new verifier agent for the new DUT. Do not reuse a verifier that was
already working on a different netlist structure.
Comparison table (variant sweep)
Return a comparison table with the key metrics the designer asked about:
| Variant | net_pc | M7 |Vds| | M7 region | DC gain | VOUT |
|-----------------|--------|---------|-----------|---------|-------|
| m9-8u | 810mV | 90mV | linear | 52 dB | 453mV |
| m9-10u | 798mV | 102mV | linear | 53 dB | 454mV |
| m9-14u | 780mV | 120mV | sat | 54 dB | 454mV |
| m9-20u | 760mV | 140mV | sat | 48 dB | 455mV |
Do NOT pick the winner — that is the designer's decision.
After Simulation: Auto-Dispatch Next Agent
After writing the margin report, dispatch the next agent in background based on the result:
If all specs PASS
Report convergence to orchestrator. Do not dispatch designer.
Write a one-line summary: CONVERGED — all specs pass at L1/L2, iteration N.
If any spec FAILS and iteration < max_iter (effort-dependent)
Dispatch the designer agent in background with:
- The margin report path
- The failing specs with measured values, targets, shortfalls
- Suggested causes for each failure (from your analysis)
- Incremented iteration number:
iteration = N + 1
- All original inputs (spec.yml, testbench paths, architecture.md, servers.yml)
If any spec FAILS and iteration >= max_iter
Do NOT dispatch designer. Escalate to orchestrator:
Write an escalation report: ESCALATE — <max_iter> iterations failed, architect review required.
Include all margin reports and the trajectory of each failing spec.
If pre-simulation review REJECTED (no simulation run)
- Circuit issue → dispatch designer with the rejection report (not a design iteration,
do not increment loop counter)
- Testbench issue → report to orchestrator (routes to architect)
See shared-references/handoff-contracts.md for the full handoff payload requirements.
Handoff Acceptance Criteria