::spice::ac_grid
Frequency grid for .ac
FLOOXS » TclLib » Spice
55 documented proc(s) in TclLib/Spice/.
::spice::ac_grid · ::spice::diff · ::spice::element_refs_for · ::spice::emit_ac · ::spice::emit_all · ::spice::emit_bjt · ::spice::emit_capacitor · ::spice::emit_cccs · ::spice::emit_dc · ::spice::emit_diode · ::spice::emit_inductor · ::spice::emit_isource · ::spice::emit_iswitch · ::spice::emit_jfet · ::spice::emit_k_coupling · ::spice::emit_mosfet · ::spice::emit_op · ::spice::emit_resistor · ::spice::emit_tran · ::spice::emit_vccs · ::spice::emit_vcvs · ::spice::emit_vsource · ::spice::emit_vswitch · ::spice::eqnref · ::spice::expand_subckt_instance · ::spice::finalize_inductors · ::spice::fromto · ::spice::fromto_args · ::spice::harvest_subckts · ::spice::is_ground · ::spice::node_count_for · ::spice::normalize_lines · ::spice::numeric · ::spice::p · ::spice::parse_directive · ::spice::parse_error · ::spice::parse_ic_card · ::spice::parse_kv · ::spice::parse_model_card · ::spice::parse_param_card · ::spice::parse_plot_card · ::spice::parse_subckt_header · ::spice::picard_transient · ::spice::push_emit · ::spice::read_deck_recursive · ::spice::rewrite_body_line · ::spice::safe_exp · ::spice::solve_until_settled · ::spice::transient_expr · ::spice::valueof · bjt_level_1 · bjt_level_2 · jfet_level_1 · jfet_level_2 · switch_level_1
Frequency grid for .ac
Build "(node1 - node2)" with ground-aware simplification: ground becomes the literal 0. Inside a Tcl-substituted equation string this is just arithmetic.
Some element kinds reference *another element* (not a node) at a specific
position. F/H/W reference a V-source name; K references two L names.
Returns a list of {position kind} pairs (1-indexed positions).
`.ac dec|oct|lin npts fstart fstop` Now that ddt() inside eq= handles the jω scaling automatically (via TimeTerm::SetImag at AC assembly time), the sweep is just a Tcl loop over frequencies calling `device freq=$f` -- no element re-emission needed. Results land in the Tcl array ::spice::output(ac), one entry per sweep point: {f {node1_mag node1_phase node2_mag ...}}.
Stitch together the four passes. Pass 1 is in spice_parser.tcl (::spice::parse_file). Pass 2/3/4 are below: directive harvest happened during parse_file; element emission walks ::spice::elements; analysis emission walks ::spice::analyses.
Q<name> Nc Nb Ne model [param=val ...]
C<name> N+ N- value i_C = C * d(V_C)/dt -- one current element whose equation is the time derivative of stored charge. ddt() handles DC (zero), transient (finite-difference) and AC (jω scaling) inside the C++ assembler.
F<name>/H<name> CCCS/CCVS require a controlling V-element branch current. FLOOXS exposes branch currents as `_flux_<vname>`, so we can emit directly.
`.dc srcname start stop step` Emits a Tcl for-loop that mutates the swept source's supply node via `circuit supply` and re-solves with `device` at each step. Results are captured into ::spice::output(dc:<srcname>) as a list of {sweep_val {node_dict}} entries.
D<name> A K <model>
L<name> N+ N- value v_L = L * di_L/dt -- voltage element. In FLOOXS, `_flux_<elem with from=A>` is the current flowing INTO A via the element (i.e., opposite the physical from→to direction). So the inductor's MNA constraint in this convention is V+ - V- + L * d(_flux)/dt = 0 (the + sign on the ddt term, not -). The `flux` keyword inside CktElem resolves to this element's `_flux_<name>` branch variable.
I<name> N+ N- value (SPICE: current flows from N+ through source to N-) KCL convention in CktElem: 'from' has +eq added to its row, 'to' has -eq added. SPICE convention: current leaves N+ via the external circuit, i.e. the source injects -I into N+ and +I into N-. We swap from/to to match. Same supply-node pattern as V: emit settable supply scalar, eq references it.
W<name> N+ N- Vctrl model -- current-controlled switch. Control variable is the branch current of the named V source.
J<name> Nd Ng Ns model [param=val ...]
K<name> Lref1 Lref2 coupling -- mutual inductance. Just records the coupling; finalize_inductors does the work.
M<name> D G S B model [W=... L=... param=val ...]
`.op` → Newton solve at DC.
R<name> N+ N- value
`.tran tstep tstop [tstart [tmax]] [UIC]` FLOOXS transient driver uses `device time=<stop> t.init=<step> userstep=<step>`.
G<name> n+ n- nc+ nc- gm VCCS (current element)
E<name> n+ n- nc+ nc- gain VCVS (voltage element)
V<name> N+ N- [DC] value [AC mag [phase]] [PULSE(...)|SIN(...)|PWL(...)|EXP(...)|SFFM(...)] Emits a settable supply node plus a `circuit add` whose equation references it. .dc sweeps and AC stimulus mutate the supply node via the same primitive the user invokes directly. Transient specs (PULSE/SIN/PWL/EXP/SFFM) bypass the supply node and emit a time-dependent eq= using the `time` global.
S<name> N+ N- NC+ NC- model -- voltage-controlled switch. Control variable is the voltage across (NC+, NC-).
Format a node reference inside an equation. Ground becomes the literal "0".
Expand an X instance line into a list of located rewritten body lines. Handles nested X instances by recursing. Each body line preserves its definition-site loc so emit-time errors point at the .subckt body line, not at the X instance line.
Called by emit_all after all elements are buffered but before analyses run. Emits each inductor's `circuit add` line with the basic L*ddt(flux) term plus M*ddt(_flux_<other>) addends for every K touching it.
Build a "from=A to=B" pair for the circuit add line, dropping to= when the second node is ground. If the FIRST node is ground, swap so ground becomes the implicit terminal -- callers handle the resulting sign flip themselves.
List form for callers that invoke `circuit add` directly (not via string concatenation). Returns {from sign to_args_list} where to_args_list is `{to=X}` or `{}` depending on whether b is ground.
Extract all .subckt blocks from a located line list. Returns {remaining_lines subckts_dict} where remaining_lines has the .subckt/.ends blocks removed, and subckts_dict maps name -> {formals body} where body is itself a list of located {loc content} pairs.
SPICE node "0" is ground. FLOOXS has no GND-pinning mechanism — it uses an implicit ground via the absence of a to= clause: when only `from=` is given, the KCL stamp lands on that node and the conjugate side is dropped (treated as a grounded sink). Our emitters call these helpers to do the right thing regardless of which terminal happens to be ground.
How many positional nodes does an element of this kind have? Tokens are passed in for kinds that need to count (X variable arity).
Pass-1 normalization. Strips * full-line comments and ; trailing comments, folds + continuations into the previous line, lowercases content outside double-quoted strings. Input: list of raw located triples {file lineno source_text}. Output: list of normalized located pairs {loc content} where loc = {file lineno source_text} carries the first-line attribution for the (possibly continuation-joined) block.
Convert a SPICE numeric literal (e.g. "1k", "2.5meg", "1u", "1e-6") into a decimal number string. Engineering suffixes per SPICE 3F5 / ngspice manual.
Convert mF to F (Farads), W=10u to 10e-6 m, etc. -- arguments coming from spice_elements have already been through `valueof`, so they're plain decimals. This helper just defends against bare missing parameters.
Single directive line (starts with '.'). Routes to the corresponding harvester. Output directives are silently dropped per v1 scope. Each analysis card is stored with its `loc` so the emitter can attribute emit-time errors back to the deck line.
Emit a translator error with source-line attribution. Reads ::spice::current_loc unless an explicit `loc` 3-tuple is passed. Falls back to a plain "spice: <msg>" prefix when no location is available (e.g. errors raised before any deck line is in flight).
`.ic V(node)=val V(node)=val ...` (transient initial condition)
`.nodeset V(node)=val ...` (DC warm-start)
Stored into ::spice::initial_conditions or ::spice::nodesets as a dict
of {node -> value}. Emit pass later issues `circuit supply` (for .ic
with SetPrev semantics) or `circuit warmstart` (for .nodeset) lines
before the first analysis runs.
Parse a list like {file=foo.sp out=bar.tcl} into a Tcl dict keyed by name.
`.model <name> <type> [LEVEL=N] [param=val ...]`
`.param name=val [name=val ...]`
`.plot <analysis> <signal> <signal> ...` -- record signals to capture during the given analysis. Stored into ::spice::plot_requests($analysis). v4 unifies .plot and .print: both populate the same capture array; users post-process the output.
Detect a `.subckt name n1 n2 ...` header. Returns {name {formals}} on match,
empty list otherwise.
picard_transient.tcl -- Picard outer-loop transient for coupled device+circuit systems (v12 of the TCAD-MOSFET capstone). v12 fixed three underlying FLOOXS issues that this proc depends on: * Matrix::AddElement / SubElement skip stamps past NumberNode (stale CktExpr/MNA references from a prior larger Symbolic). * Solution::ClearEqnNum resets the eqnCurFlux IntBlocks so a re-Symbolic doesn't carry stale flux equation row numbers. * CktSol::NumberEqn cascade-pins orphan voltage-element MNA branches: when every variable in a VOLTAGE element's equation is a supply CktNode, the branch's MNA Newton row would be empty. CktSol::NumberEqn marks the MNA branch as supply too, with SetDC(0). The matrix structure correctly reflects the reduced Newton unknown set -- no orphan rows ever exist. Motivation: v11's monolithic device->ckt KCL coupling diverges under adaptive BDF small-dt because the coupled flux+KCL chain is a DAE Index-2 problem (TR-BDF2 was designed for parabolic PDEs, not for DAE-with-circuit). The community-standard remedy is to alternate per timestep: device PDE with circuit frozen as Dirichlet BCs, then circuit KCL with device fluxes frozen. Each half is well-posed on its own; BDF stays in its lane (bulk PDE), and the algebraic KCL is a small Newton on its own. Implementation: pure Tcl orchestration using existing FLOOXS primitives. The v9 fix made `contact ... voltage supply=X` / `contact ... network` mode flips safe (mutate Bound::ctype in place, no CktNode recreation), so we can freeze NETWORK contacts during the device half and unfreeze during the circuit half. The v12 C++ gate (`circuit cflux on|off`, src/device/CktElem.cc) disables v11's monolithic stamps during the device half so we don't double-count the device current. Usage (Tcl positional args, with optional trailing kv overrides): ::spice::picard_transient <tstop> <dt> <contacts_list> ?<tol>? ?<maxiter>? where: tstop total simulation time to advance (seconds, from current device time) dt per-Picard timestep (seconds). Single big BDF step per Picard inner iteration -- adaptive BDF sub-stepping below dt is disabled by setting t.init = dt. contacts_list Tcl list of contact names currently in NETWORK mode that should be frozen/thawed each iteration. Must be braced as `{gate drain}` so Tcl groups it into a single arg with spaces. tol (optional, default 1e-4) max-abs CktNode delta below which the inner Picard loop terminates. maxiter (optional, default 20) inner-loop iteration limit. Pre-conditions: - The TCAD device is built, an initial DC solve has been completed, and the named contacts are in NETWORK mode with current CktNode values seeded by a prior monolithic `device` run. - SPICE periphery has been loaded (Vdd, Vin, Rload/PMOS, etc). - v11 coupling is currently enabled (default). Post-condition: device time has advanced by `time` seconds. Final state has bulk PDE state + CktNode values consistent within `tol`. Limitations (v12 compromise; v13 follow-on): - The "circuit half" of each Picard iteration is `device init; device` (DC re-solve). This perturbs the bulk state slightly because the DC solver re-equilibrates everything. The Picard fixed point absorbs this drift but it's not strict. A true circuit-only Newton would fix this in v13. - Single big BDF step per Picard iter -- no adaptive sub-stepping within a timestep. If the user wants finer dt, they call picard_transient with smaller dt. - The `device init` calls between halves rebuild the matrix from scratch each iteration; expensive but correct. v13 could cache the device-side Jacobian across Picard halves.
Push a Tcl statement onto the emit list. Used by mosfet_level_* handlers (which are top-level procs, not inside namespace ::spice) to append safely.
Read `path`, expand any `.include "child.sp"` lines recursively, and
normalise the result. `include_stack` carries the chain of currently-open
file paths so we can detect cycles.
Returns a list of located lines [{loc content} ...] where loc =
{file lineno source_text}. `file` is the basename of the deck file
containing the line, `lineno` is the 1-indexed original line number
(first line of a continuation block), `source_text` is the raw line.
Nested calls return raw located triples ({file lineno source_text});
only the top-level call normalizes — so continuation folding works
across an .include boundary.
Rewrite one body line for an X instance. Returns the rewritten line. Rules: - The element's own name (first token) is prefixed: R1 -> X1_R1 - Node positions are substituted: formal -> actual via map; "0" stays "0"; otherwise local -> X1_<local> - Element-reference positions (F/H/W's Vctrl, K's L names) are prefixed: Vctrl -> X1_Vctrl - Other positions (values, model names, DC/AC keywords, transient specs) are passed through unchanged
Linearised exp -- past `limit`, replace exp(arg) with exp(limit)*(1+arg-limit). Standard SPICE technique for stabilising Newton on diode/BJT junctions where exp(Vbe/Vt) would otherwise blow up beyond floating-point. Smooth at the limit (continuous value and derivative). 25 is the conventional threshold: exp(25) ~ 7e10, large enough for any physical current but tame enough that Newton can iterate without 1e57 overshoots.
Tcl-side Newton wrapper. Runs `device` repeatedly until every circuit node's value stops changing within tolerance. Needed for elements where the C++ Newton driver's RhsNorm-based convergence test fires prematurely (e.g. BJT operating points). Returns iteration count on success or errors on non-convergence. Use sparingly -- prefer the plain `device` call for circuits where it already converges (the Newton driver does correctly handle UpdNorm for circuit nodes after v2). This helper exists for the residual cases where RhsNorm tracking would also need to be circuit-aware.
Transient-spec compiler. Each kind returns an Alagator expression that
evaluates to the source value at the current `time`.
PULSE(v1 v2 td tr tf pw per)
v(t) starts at v1, ramps to v2 over (td, td+tr], holds v2 over
(td+tr, td+tr+pw], ramps back to v1 over (td+tr+pw, td+tr+pw+tf],
holds v1 for the remainder of `per`, repeats.
SIN(vo va freq td theta)
v(t) = vo + va * sin(2*pi*freq*(t-td)) * exp(-theta*(t-td)) for t>=td
= vo otherwise
PWL(t1 v1 t2 v2 ...)
Linear interpolation between (ti, vi) pairs; flat outside the range.
EXP(v1 v2 td1 tau1 td2 tau2)
v(t) = v1 + (v2-v1)*(1-exp(-(t-td1)/tau1)) for td1<=t<td2
+ (v1-v2)*(1-exp(-(t-td2)/tau2)) for t>=td2
SFFM(vo va fc mdi fs)
v(t) = vo + va * sin(2*pi*fc*t + mdi*sin(2*pi*fs*t))
step(x) is encoded as (1+sign(x))/2 since Alagator has sign() but not step().
Param helper: resolve a token through ::spice::params, then through the engineering-suffix decoder.
Ebers-Moll Level 1 -- Berkeley SPICE2 Q model. NPN convention (terminals C, B, E): Vbe = Vb - Ve forward-biased when positive Vbc = Vb - Vc reverse-biased when negative under normal op Ifor = IS · (exp(Vbe/(NF·Vt)) - 1) forward injection Irev = IS · (exp(Vbc/(NR·Vt)) - 1) reverse injection Ic = Ifor - (1 + 1/BR) · Irev collector current Ib = Ifor/BF + Irev/BR base current Ie = -(Ic + Ib) emitter current (out of E) Early effect (VAF) modulates Ic by (1 + Vbc/VAF). VAR is the reverse Early voltage; rarely matters at typical operating points. PNP: flip Vbe -> -Vbe, Vbc -> -Vbc; the same equations apply, and the resulting currents have the opposite physical direction (KCL stamps carry the polarity). Each terminal current goes into its KCL row as a CURRENT element. To keep the matrix well-conditioned at the all-zero initial guess we add a small GMIN shunt between each terminal pair.
Gummel-Poon Level 2 -- Berkeley SPICE2 §3.3. Extends Ebers-Moll with: * High-injection rolloff via knee currents IKF, IKR * Base-width modulation (Early effect) via VAF, VAR * Base recombination components via ISE/NE (B-E) and ISC/NC (B-C) NPN convention: Ibf = IS · (safe_exp(Vbe/(NF·Vt), 40) - 1) forward injection Ibr = IS · (safe_exp(Vbc/(NR·Vt), 40) - 1) reverse injection Ile = ISE · (safe_exp(Vbe/(NE·Vt), 40) - 1) B-E recombination Ilc = ISC · (safe_exp(Vbc/(NC·Vt), 40) - 1) B-C recombination q1 = 1 / (1 - Vbc/VAF - Vbe/VAR) Early-effect factor q2 = Ibf/IKF + Ibr/IKR high-injection qb = q1/2 · (1 + sqrt(1 + 4·q2)) normalised base charge Ict = (Ibf - Ibr) / qb transport current Ic = Ict - Ibr/BR - Ilc Ib = Ibf/BF + Ile + Ibr/BR + Ilc Ie = -(Ic + Ib) Defaults: IKF=IKR=0 disables high-injection (q2=0, qb=q1); ISE=ISC=0 disables recombination; VAF=VAR=0 disables Early effect (q1=1). These special cases are folded into the emitted expression at translate time so the Alagator string stays tight. PNP flips Vbe/Vbc and the stamped current direction via the $sgn factor.
Schichman-Hodges JFET (Level 1). NJF (n-channel) conventions: VTO < 0, depletion-mode device. Vgst = Vgs - VTO (positive when channel exists, since VTO<0) Cutoff (Vgst <= 0): Id = 0 Triode (Vds < Vgst): Id = BETA * Vds * (2*Vgst - Vds) * (1 + LAMBDA*Vds) Saturation : Id = BETA * Vgst^2 * (1 + LAMBDA*Vds) PJF mirrors with sign flips on Vgs, Vds. Region selection uses the same abs-based smooth-max trick as the MOSFET L1: vgst_eff = (vgst + |vgst|)/2 = max(vgst, 0) vds_eff = (vds + vgst_eff - |vds - vgst_eff|)/2 = min(vds, vgst_eff) The combined Id expression collapses to triode or saturation depending on which side of the boundary vds sits.
Parker-Skellern JFET (Level 2).
Extends Schichman-Hodges with:
* Short-channel threshold modulation: SIGMA shifts VTO with Vds so the
effective overdrive is Vgst_eff = Vgs - VTO + SIGMA·Vds. SIGMA=0
disables and the model collapses to L1.
* Vdsat smoothing: Vdsat = Vgst / (1 + Vgst/VGSMAX). With VGSMAX large
this is essentially Vgst and the triode/saturation merging stays the
same as L1. Smaller VGSMAX softens the knee for short channels.
* Optional Schottky gate leakage: when IS > 0, the gate carries
Igs = IS·(exp(Vgs/Vt) - 1) (plus Igd from drain side); the gate
terminal current is no longer pinned at zero.
Triode/saturation use the same abs-based smooth-max merge as L1, applied
to the smoothed Vgst and Vdsat. Channel-length modulation rides on top
via (1 + LAMBDA·Vds).
NJF convention; PJF flips Vgs/Vds and the stamped current sign.
Smoothed-step conductance switch. The `control` arg is the Alagator expression that drives the switch: S: a voltage difference "(NC+ - NC-)" W: a branch current "_flux_<Vname>" The `threshold` and `hyst` come from the model (VT/VH for S, IT/IH for W).