FLOOXS » TclLib » Spice

Spice — TclLib Procs

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

::spice::ac_grid

file: TclLib/Spice/spice_analyses.tcl

Frequency grid for .ac

::spice::diff

file: TclLib/Spice/spice_elements.tcl

Build "(node1 - node2)" with ground-aware simplification: ground becomes the
literal 0. Inside a Tcl-substituted equation string this is just arithmetic.

::spice::element_refs_for

file: TclLib/Spice/spice_subckt.tcl

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).

::spice::emit_ac

file: TclLib/Spice/spice_analyses.tcl

`.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 ...}}.

::spice::emit_all

file: TclLib/Spice/spice.tcl

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.

::spice::emit_bjt

file: TclLib/Spice/spice_elements.tcl

Q<name> Nc Nb Ne model [param=val ...]

::spice::emit_capacitor

file: TclLib/Spice/spice_elements.tcl

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.

::spice::emit_cccs

file: TclLib/Spice/spice_elements.tcl

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.

::spice::emit_dc

file: TclLib/Spice/spice_analyses.tcl

`.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.

::spice::emit_diode

file: TclLib/Spice/spice_elements.tcl

D<name> A K <model>

::spice::emit_inductor

file: TclLib/Spice/spice_elements.tcl

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.

::spice::emit_isource

file: TclLib/Spice/spice_elements.tcl

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.

::spice::emit_iswitch

file: TclLib/Spice/spice_elements.tcl

W<name> N+ N- Vctrl model -- current-controlled switch.
Control variable is the branch current of the named V source.

::spice::emit_jfet

file: TclLib/Spice/spice_elements.tcl

J<name> Nd Ng Ns model [param=val ...]

::spice::emit_k_coupling

file: TclLib/Spice/spice_elements.tcl

K<name> Lref1 Lref2 coupling -- mutual inductance.
Just records the coupling; finalize_inductors does the work.

::spice::emit_mosfet

file: TclLib/Spice/spice_elements.tcl

M<name> D G S B model [W=... L=... param=val ...]

::spice::emit_op

file: TclLib/Spice/spice_analyses.tcl

`.op`  → Newton solve at DC.

::spice::emit_resistor

file: TclLib/Spice/spice_elements.tcl

R<name> N+ N- value

::spice::emit_tran

file: TclLib/Spice/spice_analyses.tcl

`.tran tstep tstop [tstart [tmax]] [UIC]`
FLOOXS transient driver uses `device time=<stop> t.init=<step> userstep=<step>`.

::spice::emit_vccs

file: TclLib/Spice/spice_elements.tcl

G<name> n+ n- nc+ nc- gm                   VCCS (current element)

::spice::emit_vcvs

file: TclLib/Spice/spice_elements.tcl

E<name> n+ n- nc+ nc- gain                 VCVS (voltage element)

::spice::emit_vsource

file: TclLib/Spice/spice_elements.tcl

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.

::spice::emit_vswitch

file: TclLib/Spice/spice_elements.tcl

S<name> N+ N- NC+ NC- model -- voltage-controlled switch.
Control variable is the voltage across (NC+, NC-).

::spice::eqnref

file: TclLib/Spice/spice_elements.tcl

Format a node reference inside an equation. Ground becomes the literal "0".

::spice::expand_subckt_instance

file: TclLib/Spice/spice_subckt.tcl

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.

::spice::finalize_inductors

file: TclLib/Spice/spice_elements.tcl

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.

::spice::fromto

file: TclLib/Spice/spice_elements.tcl

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.

::spice::fromto_args

file: TclLib/Spice/spice_elements.tcl

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.

::spice::harvest_subckts

file: TclLib/Spice/spice_subckt.tcl

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::is_ground

file: TclLib/Spice/spice_elements.tcl

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.

::spice::node_count_for

file: TclLib/Spice/spice_subckt.tcl

How many positional nodes does an element of this kind have?
Tokens are passed in for kinds that need to count (X variable arity).

::spice::normalize_lines

file: TclLib/Spice/spice_parser.tcl

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.

::spice::numeric

file: TclLib/Spice/spice_parser.tcl

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.

::spice::p

file: TclLib/Spice/spice_mosfet.tcl

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.

::spice::parse_directive

file: TclLib/Spice/spice_parser.tcl

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.

::spice::parse_error

file: TclLib/Spice/spice.tcl

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).

::spice::parse_ic_card

file: TclLib/Spice/spice_parser.tcl

`.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.

::spice::parse_kv

file: TclLib/Spice/spice.tcl

Parse a list like {file=foo.sp out=bar.tcl} into a Tcl dict keyed by name.

::spice::parse_model_card

file: TclLib/Spice/spice_parser.tcl

`.model <name> <type> [LEVEL=N] [param=val ...]`

::spice::parse_param_card

file: TclLib/Spice/spice_parser.tcl

`.param name=val [name=val ...]`

::spice::parse_plot_card

file: TclLib/Spice/spice_parser.tcl

`.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.

::spice::parse_subckt_header

file: TclLib/Spice/spice_subckt.tcl

Detect a `.subckt name n1 n2 ...` header. Returns {name {formals}} on match,
empty list otherwise.

::spice::picard_transient

file: TclLib/Spice/picard_transient.tcl

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.

::spice::push_emit

file: TclLib/Spice/spice_mosfet.tcl

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.

::spice::read_deck_recursive

file: TclLib/Spice/spice_parser.tcl

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.

::spice::rewrite_body_line

file: TclLib/Spice/spice_subckt.tcl

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

::spice::safe_exp

file: TclLib/Spice/spice_elements.tcl

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.

::spice::solve_until_settled

file: TclLib/Spice/spice_analyses.tcl

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.

::spice::transient_expr

file: TclLib/Spice/spice_elements.tcl

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().

::spice::valueof

file: TclLib/Spice/spice_elements.tcl

Param helper: resolve a token through ::spice::params, then through the
engineering-suffix decoder.

bjt_level_1

file: TclLib/Spice/spice_bjt.tcl

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.

bjt_level_2

file: TclLib/Spice/spice_bjt.tcl

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.

jfet_level_1

file: TclLib/Spice/spice_jfet.tcl

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.

jfet_level_2

file: TclLib/Spice/spice_jfet.tcl

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.

switch_level_1

file: TclLib/Spice/spice_switch.tcl

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).