Skip to content

JSON Circuit Description Format

Project Overview | Core Solver Overview | 中文版

Status: maintained reference. The loader and schemas/circuit.schema.json are the source of truth. This document describes the user-facing contract and should be updated with either one.

Purpose

The JSON circuit description separates topology, sizes, bias, and analysis metadata from Python source code. Swap circuits by changing JSON files instead of editing circuitopt/ac_solver.py, circuitopt/noise_solver.py, or circuitopt/transient_solver.py.

Schema file:

schemas/circuit.schema.json

Example files:

examples/single_stage.json
examples/resistor_load_stage.json
examples/afe_explore.json
examples/periodic_rc.json

Minimal Structure

A valid circuit JSON needs at minimum:

{
  "solved": ["OUT"],
  "rails": {
    "VDD": "VDD",
    "GND": 0.0,
    "IN": "VIN"
  },
  "devices": [
    {"name": "M1", "drain": "OUT", "gate": "IN", "source": "VDD", "W": 2000, "L": 80}
  ],
  "bias": {
    "VDD": 40.0,
    "VIN": 25.0
  },
  "outputs": ["OUT"]
}
  • solved — nodes the solver must find voltages for.
  • rails — known-voltage nodes. Values can be numeric constants or keys in bias.
  • devices — list of transistor devices (currently PMOS_TFT; model type is determined by the device_model factory, default "pmos_tft").
  • bias — DC voltages for rail references.
  • outputs — nodes observed for AC/noise/transient results.

Field Reference

name

Optional. Circuit name for display and logging.

"name": "single_stage_pmos_load"

solved

Required. Unknown node list; also defines the MNA/DAE vector ordering.

"solved": ["VOP", "VON", "VFBP", "VFBN", "NET20", "NET2"]
  • At least one node.
  • No duplicate names.
  • Every node in outputs must be in solved.

rails

Required. Known-voltage node map.

"rails": {
  "VDD": "VDD",
  "GND": 0.0,
  "VB": "VB"
}
  • "GND": 0.0 — GND is always 0 V.
  • "VDD": "VDD" — VDD voltage is read from bias["VDD"].
  • Every node referenced by a device port must appear in solved or rails.

devices

Required. Each active device is a three-terminal transistor (drain/gate/source). Model implementation is selected by the device_model factory (default "pmos_tft").

Object form (preferred):

{
  "name": "M7",
  "drain": "VOP",
  "gate": "VCM",
  "source": "NET2",
  "W": 61365,
  "L": 61,
  "NF": 1
}

Array shorthand:

["M7", "VOP", "VCM", "NET2"]

If using array form, W/L must be supplied in sizes.

sizes

Optional. Per-device dimensions, useful for separating topology from sizing.

"sizes": {
  "M7": [61365, 61],
  "M8": [61365, 61]
}
  • If a device object already has W and L, sizes is not needed.
  • If both are present, sizes overrides the embedded W/L.
  • Every device must ultimately have W and L.

nf

Optional. Number of fingers (multiplies drain current).

Global:

"nf": 2

Per-device:

"nf": {
  "M7": 4,
  "M8": 4
}

Device-level NF in the device object is overridden by top-level nf when both exist.

models

Required whenever devices is non-empty. Every MOS must explicitly bind its PDK, model, process section, and geometry bin policy. Missing or partial bindings fail during parsing; there is no default-PDK fallback.

"models": {
  "M1": {"pdk": "sky130", "model": "nmos",
         "section": "inherit", "bin": "auto", "extract_w": 24.0},
  "M3": {"pdk": "sky130", "model": "pmos",
         "section": "inherit", "bin": "auto", "vb": 1.8, "extract_w": 12.0}
}
  • pdk and model select the registered process and device model.
  • section is a fixed card section such as tt/ss, or inherit when a PVT run is allowed to select the section.
  • bin is an exact model/bin name, or auto for unique geometry-based selection. An exact selector must match the resolved card bin.
  • type is obsolete and rejected.
  • Remaining keys are forwarded to the device constructor. For SKY130 devices: vb (bulk bias, volts; default 0), extract_w (µm — select a bundled reference-width card while the native BSIM instance uses the actual W), temperature (kelvin; default 300.15), NF (int).
  • bulk_rail optionally names the physical rail that supplies vb. It is useful when several rails share the same DC voltage; source-power reporting otherwise resolves a source-tied bulk or conventional GND/VSS/VDD rail.
  • FreePDK45 ("freepdk45.nmos" / "freepdk45.pmos", plus the low-threshold flavors "freepdk45.nmos_vtl" / "freepdk45.pmos_vtl" binding the NMOS_VTL/PMOS_VTL cards — vth0 0.322 V vs 0.4106 V at nominal, the standard choice for comparator input pairs near the VTG threshold) directly parses the flat BSIM4 level-54 cards and evaluates them with the in-process Berkeley BSIM4.5 backend. The cards declare version=4.0; this metadata field does not select a separate equation path in the bundled kernel, and native device/5T OTA results are regression-checked against ngspice. Device keys include vb (0 for NMOS, normally 1.0 V for PMOS), corner (nom/tt/ss/ff/sf/fs; default nom), temperature (kelvin), NF, M, and supported numeric BSIM4 instance parameters. The backend supplies full terminal current, conductance, charge, capacitance, and correlated noise to DC, AC, noise, transient, PSS, PAC, and PNoise. extract_w is accepted as a legacy hint but native devices always use their actual geometry. The optional freepdk45_ngspice.nmos / .pmos aliases retain the old cached-grid evaluator, while the full-circuit ngspice helpers provide an external oracle. Cards live under PDK_ROOT/freepdk45/; see examples/freepdk45_5t_ota.json (simple) and examples/freepdk45_fd_ota.json (the fully differential OTA design case, docs/freepdk45_fd_ota_design.md).
  • TSMC28HPC+ ("tsmc28hpcp.nmos" / "tsmc28hpcp.pmos") binds the 0.9 V nch_mac / pch_mac core wrappers from the licensed 1d8 HSPICE deck. Use vb=0.9 for a PMOS bulk tied to the core supply. Supported corners are tt/ss/ff/sf/fs (nom aliases tt); temperature is in kelvin and NF is passed natively to the foundry macro. The portable default model entry is PDK/tsmc28hpcp/models/hspice/cln28hpcp_1d8_elk_v1d0_2p2.l; overrides are TSMC28_MODEL_DIR, then TSMC28_PDK_ROOT. The default model types use the internal HSPICE parser plus native Berkeley BSIM4.5 backend for DC, AC, noise, transient, PSS, PAC, and PNoise; they do not launch ngspice. Explicit tsmc28hpcp_ngspice.nmos / .pmos aliases retain the complete-circuit ngspice oracle for regression comparisons. See TSMC28HPC+ Local Adapter.
  • A mixed circuit (some devices OTFT, some silicon) is valid — e.g. a complementary silicon OTA binds NMOS/PMOS devices independently. See examples/sky130_5t_ota.json.
  • SKY130 normal simulation uses bundled resolved cards and the native C BSIM4 backend. FreePDK45 and TSMC28HPC+ use their local model files with the same native backend. A C compiler is needed for the first backend build; ngspice is an optional oracle/card-generation tool. Missing prerequisites raise a clear error. See PDK Support Matrix.

bias

Optional but usually needed. Supplies numeric values for rails string references.

"bias": {
  "VDD": 40.0,
  "VCM": 30.65,
  "VB": 9.84,
  "VC": 16.0
}

If a rail references a key not in bias, the solve will fail.

adc

Optional closed-loop SAR workflow configuration. The circuit itself still uses the ordinary devices/capacitors/vsources fields. Differential bit_inputs and bit_inputs_bar name CDAC PWL keys from MSB to LSB; decisions are read from the physical transient comparator node. Run it with circuit-opt adc --vin, --sweep, or --sine. See examples/freepdk45_sar3.json (static 5T comparator) and examples/freepdk45_sar6.json (6-bit, static three-stage comparator: two diode-loaded differential preamps into a 5T mirror stage; its nominal ramp is the ideal 64-code staircase) for complete configurations.

adc.clock

Optional comparator strobe for a clocked dynamic (StrongARM) comparator. When present, sar_input_waveforms generates the named waveform key: it rests at low and pulses to high around every bit's decision_time, so a dynamic latch precharges while the CDAC settles and evaluates at the decision instant. Drive the clocked tail / output-reset devices from that key via transient_inputs. Omitting the block reproduces the static-comparator behaviour (no clock waveform emitted, so examples/freepdk45_sar3.json renders a byte-identical netlist).

"clock": {"input": "clk", "bar_input": "clkb", "high": 1.0, "low": 0.0,
          "eval_before": 3e-9, "reset_hold": 1e-9}
  • input — required transient waveform key for the strobe.
  • bar_input — optional key for the complemented strobe (high + low - clock), for latches whose reset devices need the opposite phase (double-tail second stages, PMOS-tail latches).
  • high / low — asserted (evaluate) / deasserted (reset) levels [V]; default high = adc.vref, low = 0.
  • eval_before — seconds before each decision_time that the strobe rises (default 0.3 * bit_period); must be < bit_period/2 - edge_time so the tested CDAC bit has already switched when the latch samples.
  • reset_hold — seconds after each decision_time before the strobe resets (default 0.1 * bit_period).

adc.mismatch

Optional per-instance mismatch Monte-Carlo config for the FreePDK45 SAR path, consumed by circuitopt.sar_mismatch_mc. Every sigma defaults to 0.0, so omitting the block (or leaving sigmas zero) reproduces the nominal conversion.

"mismatch": {
  "sigma_vth0": 5e-3, "w0": 1.0, "l0": 0.05,
  "sigma_cu": 0.01, "c_unit": 1e-14,
  "dnl_threshold": 0.5, "inl_threshold": 0.5
}
  • sigma_vth0 — transistor threshold-voltage sigma [V] at the reference area w0*l0; per device it scales as sigma_vth0 / sqrt(W*L / (w0*l0)) (Pelgrom area law) and is injected as the BSIM4 instance parameter delvto. sigma_vth0_nmos / sigma_vth0_pmos override it per polarity.
  • sigma_cu — CDAC unit-capacitor relative sigma at c_unit; a cap of value C gets relative sigma sigma_cu / sqrt(C / c_unit) (binary-weighted caps are paralleled units and match better).
  • dnl_threshold / inl_threshold — |DNL|/|INL| yield limits in LSB (default 0.5).
  • sweep_points — optional per-trial sweep subsample (default: the full 2**n_bits code centers). Below full density, transition DNL/INL and missing codes are not measurable (rows report NaN) and the yield gates on code_err_threshold instead — the screening mode for 12-bit-class resolutions, where a full ramp is 4096 conversions per trial. The CLI flag --sweep-points overrides it.
  • code_err_threshold — |code error| yield limit in LSB for subsampled sweeps (default 0.5, i.e. every sampled center must read its own code).

outputs

Optional but needed for AC/noise/transient. Supports single-ended or differential.

Single-ended:

"outputs": ["OUT"]

Differential:

"outputs": ["VOP", "VON"]

Differential output is computed as the first node minus the second (VOP - VON).

input_drives

Optional. AC small-signal gate drive, keyed by device name.

"input_drives": {
  "M7": 0.5,
  "M8": -0.5
}
  • Only meaningful for devices whose gates are on rails.
  • Unlisted gates are treated as small-signal ground.
  • For differential input, use +0.5/-0.5 for a unit differential amplitude.

load_caps

Optional. Fixed load capacitors, stamped into AC/noise/transient.

Array form:

"load_caps": [
  ["VOP", "GND", 5e-12],
  ["VON", "GND", 5e-12]
]

Object form:

"load_caps": [
  {"a": "OUT", "b": "GND", "C": 2e-12}
]

resistors

Optional. Two-terminal resistors between nodes a and b, resistance R (ohms, must be positive). DC adds branch current (Va-Vb)/R. AC/noise stamps conductance 1/R. Transient stamps conductance. Thermal noise PSD 4kT/R is included in dev_psd, keyed by resistor name.

"resistors": [
  {"name": "RL", "a": "OUT", "b": "GND", "R": 4e6}
]

Array form: ["RL", "OUT", "GND", 4e6].

capacitors

Optional. Two-terminal capacitors between nodes a and b, value C (farads, must be positive). DC is open-circuit. AC stamps admittance jωC. Transient uses backward Euler companion model. Equivalent to load_caps; the difference is that capacitors entries have names and follow netlist convention.

"capacitors": [
  {"name": "CL", "a": "OUT", "b": "GND", "C": 2e-12}
]

Array form: ["CL", "OUT", "GND", 2e-12].

current_sources

Optional. Ideal DC current sources. Current I (amps, can be negative) flows from nplus to nminus inside the source — i.e. pulls I from nplus, injects I into nminus. DC enters KCL. Open-circuit (noiseless) in small-signal AC/noise. Constant current in transient.

"current_sources": [
  {"name": "IB", "nplus": "VDD", "nminus": "OUT", "I": 1e-6}
]

Array form: ["IB", "VDD", "OUT", 1e-6].

vccs

Optional. Voltage‑controlled current sources. Output current flows p → q: I = gm * (Vctrl_p - Vctrl_n). DC enters KCL; AC stamps into G matrix; noiseless (ideal); instantaneous in transient with full Jacobian contribution.

"vccs": [
  {"name": "G1", "p": "OUT", "q": "GND",
   "ctrl_p": "IN", "ctrl_n": "GND", "gm": 1e-4}
]

Array form: ["G1", "OUT", "GND", "IN", "GND", 1e-4].

vsources

Optional. Ideal voltage sources, solved with true MNA: each source adds one branch‑current unknown and one constraint row V_p − V_q = value, so the system grows from n nodes to n_aug = n + m. value is a constant EMF (number) or a transient input‑waveform key (string) for a time‑varying E(t).

"vsources": [
  {"name": "V1", "p": "IN", "q": "GND", "value": 2.0}
]

Array form: ["V1", "IN", "GND", 2.0]. At least one of p, q must be a solved node (a source between two rails is rejected).

  • DC pins the node voltage exactly (the node stays in the solved set); ac_solve reports the source currents under branch_currents (sign: p → q through the source).
  • AC / Noise treat a DC source as a short (AC ground); the ideal source carries no thermal noise. If the source name appears in ac_drives, it acts as an AC stimulus.
  • Transient supports constant or waveform‑keyed E(t). The compiled Rust fixed‑grid kernel handles the augmented n_aug = n + m system directly — a circuit with vsource (or VCVS/CCVS) branch‑current unknowns still reports result["rust_grid_solver"] is True. Native BSIM adaptive Gear2 also handles the augmented system; its LTE norm controls dynamic node voltages and deliberately excludes algebraic ideal-source branch currents. The generic OTFT adaptive path still requires n_aug == n. There is no Python numeric fallback in production; the retired numba_grid_solver result key no longer exists (tests assert its absence).
  • PSS / PAC / PNoise are supported too: the shooting monodromy and the harmonic‑balance matrices are bordered with the branch‑current unknowns (PNoise forces its dense path when a source is present).

vcvs

Optional. Voltage‑controlled voltage sources. Output voltage V_p − V_q = mu * (V_cp − V_cn). Each VCVS adds a branch‑current unknown (like an ideal voltage source) and a constraint row with entries for the control nodes. Ideal / noiseless.

"vcvs": [
  {"name": "E1", "p": "OUT", "q": "GND",
   "cp": "INP", "cn": "INN", "mu": 100.0}
]

Array form: ["E1", "OUT", "GND", "INP", "INN", 100.0]. At least one of p, q must be a solved node.

cccs

Optional. Current‑controlled current sources. Output current I_out = beta * I_ctrl flows p → q. The control current I_ctrl is the branch current of a voltage source (vsource / VCVS / CCVS) named by ctrl_name. Ideal / noiseless. Does NOT add a new branch‑current unknown — it references an existing one.

"cccs": [
  {"name": "F1", "p": "OUT", "q": "GND",
   "ctrl_name": "V1", "beta": 2.0}
]

Array form: ["F1", "OUT", "GND", "V1", 2.0]. ctrl_name must reference a vsource, VCVS, or CCVS in the same topology.

ccvs

Optional. Current‑controlled voltage sources. Output voltage V_p − V_q = gamma * I_ctrl. The control current I_ctrl is the branch current of a voltage source named by ctrl_name. Each CCVS adds a branch‑current unknown. Ideal / noiseless.

"ccvs": [
  {"name": "H1", "p": "OUT", "q": "GND",
   "ctrl_name": "V1", "gamma": 100.0}
]

Array form: ["H1", "OUT", "GND", "V1", 100.0]. At least one of p, q must be a solved node. ctrl_name must reference a vsource, VCVS, or CCVS (which has a branch current). CCCS and CCVS can cascade: a CCCS can control on a CCVS's branch current.

dc_guesses

Optional. DC initial guesses. Each entry can specify some or all solved nodes.

"dc_guesses": [
  {"OUT": 20.0},
  {"OUT": 5.0},
  {"OUT": 35.0}
]

Provide multiple physically reasonable guesses for circuits with multistability or positive feedback.

aliases

Optional. Adds aliases to the DC operating point for compatibility with older code or report fields.

"aliases": {
  "vfb": "VFBP",
  "net2": "NET2"
}

The returned dc_op includes both the original solved nodes and the aliases.

transient_inputs

Optional. Maps transient input waveform keys to device gates.

"transient_inputs": {
  "M7": "vip",
  "M8": "vin"
}

Call transient with:

tran = transient(sizes, bias, t, topo=topology,
                 inputs={"vip": vip_waveform, "vin": vin_waveform})

ac_drives

Optional. Like input_drives, but drives a node instead of a device gate. Used for testbench front-ends where the AC stimulus enters through a passive network rather than directly at a transistor gate.

"ac_drives": {
  "VINP": 0.5,
  "VINN": -0.5
}

periodic

Optional. Default large-signal periodic excitation for PSS/PAC/PNoise and periodic transient dispatch.

"periodic": {
  "frequency": 1000.0,
  "n_points": 101,
  "inputs": {
    "vin": {"type": "constant", "value": "VIN"},
    "clk": {"type": "pulse", "low": 0.0, "high": "VDD", "duty": 0.5,
            "rise": 20e-6, "fall": 20e-6}
  },
  "node_inputs": {"VIN": "vin", "CLK": "clk"},
  "current_inputs": [{"p": "VDD", "q": "OUT", "input": "iqinj"}],
  "signed_devices": ["SW1", "SW2"]
}

Supported waveform forms:

  • Number or bias key: constant waveform, e.g. "VIN".
  • constant / dc: constant waveform.
  • sine / sin / cosine / cos: fields include dc, amplitude, phase, frequency, or harmonic.
  • square: ideal square wave with low, high, duty, and delay.
  • pulse: finite-edge periodic pulse with optional rise and fall.
  • pwl: periodic piecewise-linear waveform with times and values.

analyses

Optional. Unified analysis-dispatch configuration. Calling circuitopt.analysis_dispatch.run_analysis_suite(spec) runs configured analyses in the fixed order ac -> noise -> transient -> pss -> pac -> pnoise; PAC/PNoise automatically reuse or create the required PSS result.

The authoritative option registry for transient / pss / pac / pnoise lives in circuitopt.analysis_options. analysis_dispatch.py derives forwarded solver kwargs and defaults from that registry, and the JSON schema is regression tested against the same registry so new solver options do not silently drift. Unknown keys in an analyses block are rejected with an error (a typo such as max_sidebands for max_sideband is not silently ignored).

"analyses": {
  "pss": {
    "corner": "slow",
    "integration_method": "gear2",
    "adaptive": true,
    "tstab_periods": 10,
    "final_n_points": 3201,
    "residual_tol": 1e-12,
    "max_shooting_iters": 2,
    "jacobian_reuse": true,
    "analytic_jacobian": true
  },
  "pac": {
    "freqs": [100.0, 1000.0],
    "input_drive": {"vin": 1.0},
    "analytic": true,
    "max_sideband": 10,
    "n_period_samples": 384,
    "time_domain": false,
    "td_integration": "gear2",
    "td_n_period_samples": 768,
    "lti_fast_path": true,
    "cache_linearization": true,
    "cache_forcing": true
  },
  "pnoise": {
    "freqs": [100.0, 1000.0],
    "input_drive": {"vin": 1.0},
    "max_sideband": 0,
    "n_period_samples": 32,
    "lti_fast_path": true,
    "cache_linearization": true,
    "band": [100.0, 1000.0]
  }
}

Grid and stimulus

Key Analyses Meaning
freqs ac, noise, pac, pnoise An explicit list of frequencies, or an object such as {"start": 1.0, "stop": 1e4, "num": 41, "scale": "log"}.
input_drive pac, pnoise Small-signal complex amplitude per input. A complex value may be a number, [real, imag], or {"real": ..., "imag": ...}.
corner all "typical", "slow", "fast", or an explicit model-shift map.

PAC and PNoise reuse a PSS orbit, so all three must run at the same corner. When pss sets no corner, dispatch inherits the unique PAC/PNoise corner. A dependent analysis that requests a corner different from an already-built PSS is an error.

PSS

Key Default Meaning
analytic_jacobian true Build the monodromy matrix in one orbit pass from the small-signal G(t)/C(t) stamps. false selects the original path, which needs n_state finite-difference period runs.
jacobian_reuse true Reuse the Jacobian across shooting iterations with a Broyden update. Set false for difficult convergence or tight reference comparisons.
jacobian_rebuild_interval Rebuild the Jacobian every N iterations, e.g. 2, instead of relying on the Broyden update alone.
final_n_points Point count of the deterministic final grid; see Reproducible conversion.

Adaptive timestepping

Applies to gear2 transient and PSS. Set "adaptive": true to enable LTE-controlled adaptive timestepping; dispatch then forwards the options below. Pulse and square periodic inputs get edge breakpoints inserted before the adaptive run.

Key Default Meaning
adaptive_reltol see adaptive_config.py Relative local-error tolerance.
adaptive_vabstol see adaptive_config.py Absolute voltage error tolerance.
adaptive_iabstol see adaptive_config.py Absolute current error tolerance.
adaptive_max_steps see adaptive_config.py Step-count ceiling.
adaptive_h0 Initial step size.
cap_mode "charge" Capacitance operator. Only "charge" (id 0) and "average" (id 1) and their documented aliases are accepted.

Native BSIM transient

Both options below default to zero, and both should be chosen by A/B against a run with much tighter step tolerances rather than by inspection.

Key Constraint Meaning
bsim_model_bypass_tolerance 0 ≤ x ≤ newton_vtol, in volts A positive value enables the compact model's standard device bypass, and only inside the exclusive transient Newton loop. Validate with a circuit-level trajectory and signoff A/B before setting it.
newton_error_fraction 0 ≤ x ≤ 1, adaptive only (a fixed grid rejects it) Each node's Newton update converges below this fraction of its own step-error budget adaptive_reltol*abs(V) + adaptive_vabstol, instead of the single absolute newton_vtol that holds a rail node and a node at zero to the same number. Nodes near zero are therefore held tighter; large ones stop once their remaining error is negligible against the error the step controller already accepts.

Reproducible PAC/PNoise conversion

Set final_n_points together with "adaptive": true and at least one stabilization period. Adaptive gear2 then supplies only the warm start and its accepted grid is not frozen; dispatch builds a deterministic final grid carrying the same pulse/square edge breakpoints. Shooting, monodromy, profiling, and the returned orbit all use that grid.

PAC

Key Default Meaning
analytic true Analytic-adjoint harmonic balance: one adjoint linear solve per frequency on the orbit conversion matrix, with no extra transient runs. Set false only for the original finite-difference shooting path.
max_sideband Harmonic-balance resolution.
n_period_samples Harmonic-balance resolution.
time_domain false Try the accelerated time-domain Floquet path first. Intended for rail-driven chopper-like circuits; unsupported topologies fall back to harmonic balance when analytic is true.
td_integration BDF setting of the time-domain path.
td_n_period_samples Grid setting of the time-domain path.

PAC/PNoise caches

Key Default Meaning
lti_fast_path true Static-orbit LTI fast path.
cache_linearization true Reuse linearization attached to the PSS result.
cache_forcing true Reuse forcing attached to the PSS result.

Set any of them to false to force fresh finite-difference or harmonic-balance work. With the caches on, PNoise reuses the sampled G(t)/C(t), the HB blocks, and identical-frequency adjoint solves from pss_result.

PNoise

Key Default Meaning
hb_solver "auto" "sparse" forces a sparse direct solve, "iterative" forces block-Jacobi preconditioned GMRES. "auto" keeps small matrices dense and switches only when the HB matrix is large and very sparse.
compute_condition false PAC boundary-matrix condition diagnostics. Off by default because they require an SVD at every frequency; "profile": true or "debug": true also enable them.

The JSON dispatch pnoise entry is the generic HB path. The chopper helper pmos_chopper_pnoise(...) defaults to the TD-adjoint PNoise path for Cadence alignment. Use that wrapper, or call circuitopt.pnoise_solver.pnoise_solve(..., time_domain=True) directly, when the truncation-free chopper PNoise path is required.

signoff

Optional. Defines measurements and acceptance limits explicitly. This block is the only path that produces phase margin, settling time, integrated-noise, and saturation signoff measurements. They are not inferred from an arbitrary AC response, the last transient sample, the simulated noise span, or every MOS in the DUT.

"ac_drives": {"Vinj": 1.0},
"signoff": {
  "measurements": {
    "phase_margin": {
      "analysis": "ac",
      "injection_source": "Vinj",
      "return_signal": {"RETURN_P": 1.0, "RETURN_N": -1.0},
      "polarity": -1,
      "return_scale": 0.5
    },
    "settling_time": {
      "analysis": "transient",
      "signal": {"OUTP": 1.0, "OUTN": -1.0},
      "target": 0.45,
      "start_time": 5e-9,
      "end_time": 10e-9,
      "tolerance": {"relative": 0.001, "reference": 0.9}
    },
    "noise": {
      "analysis": "noise",
      "band": [1e3, 50e6],
      "references": ["input", "output"]
    },
    "saturation": {
      "analysis": "ac",
      "devices": ["M1", "M2", "M3", "M4"],
      "minimum_headroom": 0.02
    }
  },
  "constraints": {
    "phase_margin": {"min": 60.0},
    "settling_time": {"max": 5e-9},
    "integrated_input_noise": {"max": 40e-6},
    "saturation": {"equals": true}
  }
}

For phase margin, injection_source must be a constant 0 V source breaking between two solved nodes and must be the sole non-zero voltage-source entry in ac_drives; return_signal is an explicit weighted sum of solved nodes. The loop gain is the declared return signal divided by the injection source's positive-terminal node voltage, with polarity and optional positive return_scale. The reference signal is therefore fixed by the physical loop break, so an ordinary amplifier transfer function cannot be mistaken for loop gain.

Settling requires a fixed target and window. tolerance is either {"absolute": <volts>} or {"relative": <ratio>, "reference": <volts>}; relative tolerance never silently uses the target or final sample as its reference. The signal is settled at the earliest sample after start_time that remains inside tolerance through end_time.

Noise band must lie inside the simulated noise frequency grid. Saturation checks only the listed MOS devices and applies minimum_headroom in volts. For a transient saturation check, set "analysis": "transient" and declare named checkpoints, for example [{"name": "static", "time": 0.0}, {"name": "settled", "time": 5e-9}]. The runner interpolates every solved node at each declared time and re-evaluates the exact PDK-bound MOS operating regions; it does not reuse the initial AC operating point or infer a checkpoint from the last sample. Unknown nodes/devices, missing analyses, unsupported operating regions, and ambiguous loop injection are signoff_configuration invalid results.

The unified signoff result is:

{
  "status": "pass",
  "measurements": {"phase_margin": {"value": 72.1, "unit": "deg", "status": "valid"}},
  "constraints": {"phase_margin": {"observed": {}, "checks": {}, "passed": true}},
  "passed": true,
  "worst_case": {"measurement": "phase_margin", "passed": true, "normalized_margin": 0.201}
}

When no signoff block is present, the result has status: "not_configured", passed: null, and still reports non-signoff gain, UGF, and source-power measurements when AC was run.

For multi-testbench PVT aggregation, see Signoff Campaigns. Campaign manifests have their own strict schema at schemas/signoff_campaign.schema.json.

explore

Optional. Design-space exploration configuration — variables to sweep with ranges, feasibility constraints (gain, BW, IRN, power, area), and optimization objectives. Consumed by circuitopt.explore (sample → evaluate → constrain → Pareto-select), circuitopt.dataset (sample → evaluate every candidate, no filtering — a labeled training set), and circuitopt.optimize (screen a trained surrogate → verify the shortlist on the solver).

"explore": {
  "variables": {
    "in_pair_W": {"min": 40000, "max": 90000, "targets": ["M7.W", "M8.W"]},
    "VCM":       {"min": 28.0,  "max": 33.0}
  },
  "constraints": {"gain_dB": {"min": 20}, "bw_Hz": {"min": 100},
                  "irn_uV": {"max": 44.5}},
  "objectives":  {"area": "min", "power_uW": "min"},
  "band":  [0.05, 100.0],
  "freqs": {"start": -2, "stop": 3, "num": 81}
}
  • variables — each entry needs numeric min/max. targets lets one variable drive several keys at once (matched/symmetric device pairs); it defaults to [<variable name>]. round (decimals) snaps sampled values to a grid; int rounds to a whole number (handy for W/L/NF).
  • constraints — each metric needs a min and/or max bound. Known metrics: gain_dB, gain_peak_dB, bw_Hz, irn_uV, power_uW, area.
  • objectives{metric: "min" | "max"}; at least one is required.
  • band[f_lo, f_hi] (Hz) for the band-integrated irn_uV metric.
  • freqs — the AC/noise analysis grid: {"start": <log10 Hz>, "stop": <log10 Hz>, "num": <points>} (logarithmic).

Target syntax — beyond "DEV.W" / "DEV.L" / "DEV.NF" (device sizes) and a bare bias key, targets supports structural design axes (each rebuilds the circuit per candidate; consumed by circuitopt.dataset/circuitopt.optimize, not by circuitopt.explore):

Target Axis Notes
"<CapName>.C" a named capacitor's value (F) the capacitors entry needs a "name"
"<ResName>.R" a named resistor's value (Ω) the resistors entry needs a "name"
"periodic.frequency" the periodic stimulus clock frequency needs a periodic block
"pvt0" / "pbeta0" continuous global process shift routes into evaluate(corner=...); sampling this turns a discrete corner sweep into continuous-PVT training data

See the models field above for binding a variable's target device to a non-default PDK (e.g. sweeping a SKY130 device's W); the explore block itself doesn't change — models and explore.variables compose freely.

Complete Examples

examples/single_stage.json        # Single-transistor common-source (PMOS_TFT)
examples/resistor_load_stage.json # PMOS + resistive load + output cap + current source
examples/voltage_divider.json     # Ideal voltage source (true MNA) — resistor divider
examples/vcvs_amplifier.json      # VCVS amplifier — linear gain 100×
examples/sc_lpf.json              # Switched-capacitor LPF (2-phase, PMOS switches + vsource clocks)
examples/afe_explore.json         # 10-transistor AFE with explore config
examples/periodic_rc.json         # Passive RC with PSS/PAC/PNoise dispatch
examples/sky130_5t_ota.json       # Silicon SKY130 complementary 5T OTA — `models` block + explore/dataset/optimize
examples/freepdk45_sar3.json      # FreePDK45 differential 3-bit SAR — full-charge .tran + ADC metrics

Load and run:

import numpy as np

from circuitopt.circuit_loader import load_circuit_json
from circuitopt.ac_solver import ac_solve
from circuitopt.noise_solver import noise_analysis
from circuitopt.transient_solver import transient

spec = load_circuit_json("examples/single_stage.json")
freqs = np.logspace(0, 4, 121)

ac = ac_solve(spec.sizes, spec.bias, freqs, topo=spec.topology, nf=spec.nf)
noise = noise_analysis(spec.sizes, spec.bias, freqs, topo=spec.topology, nf=spec.nf)

t = np.linspace(0, 1e-3, 100)
vin = np.full_like(t, spec.bias["VIN"])
tran = transient(spec.sizes, spec.bias, t, topo=spec.topology,
                 nf=spec.nf, inputs={"vin": vin})

Or run the analyses configured inside the JSON:

from circuitopt.analysis_dispatch import run_analysis_suite
from circuitopt.circuit_loader import load_circuit_json

spec = load_circuit_json("examples/periodic_rc.json")
results = run_analysis_suite(spec)
pac_gain = results["pac"]["gains"]
pnoise_irn = results["pnoise"]["irn_uV_band"]

Current Limitations

The JSON format is a local-solver circuit description, not a full SPICE netlist.

Supported:

  • Three-terminal transistor devices (PMOS_TFT via TransistorModel interface).
  • Resistors, capacitors, ideal DC current sources, VCCS (voltage‑controlled current sources), VCVS (voltage‑controlled voltage sources), CCCS (current‑controlled current sources), CCVS (current‑controlled voltage sources), ideal voltage sources (true MNA).
  • DC/AC/noise/transient shared topology (resistors include thermal noise; controlled sources and ideal voltage sources are ideal/noiseless).
  • Single-ended or differential outputs.
  • Fixed load capacitance.
  • AC gate drive and node drive.
  • Transient gate waveforms and node waveforms.
  • Periodic PSS/PAC/PNoise dispatch from JSON.
  • DC initial guesses.

Supported (model abstraction):

  • Device model registry (circuitopt/device_model.py) — TransistorModel ABC + factory. New model types can be added without modifying solver code.
  • NMOS and PMOS across AT4000TG (PMOS-only), SKY130, FreePDK45, and TSMC28HPC+.
  • Per-device model binding via the models field — mixed OTFT/silicon circuits with an explicit PDK, model, section, and geometry-bin selector for every MOS. Missing or partial bindings fail during parsing.
  • Silicon DC/AC/noise/transient; SKY130, FreePDK45, and TSMC28HPC+ use the internal native BSIM4 backend.

Not yet supported:

  • ADC transient noise, layout parasitic extraction, and transistor-level SAR digital control.
  • Multi-output simultaneous analysis.
  • Hierarchical subcircuits.
  • Arbitrary user-circuit SPICE netlist import. The internal HSPICE parser is currently scoped to supported local model-library elaboration.