feat(sim): Phase 2.2 — vendor LM358 macro-model subckt (asset only)

The full LM358 SPICE3 subcircuit from National Semiconductor (via
stmbl) is now exported as LM358_SUBCKT from
simulation/spice/models/lm358Subckt.ts.  Internal models renamed
DX→DX_LM358 and QX→QX_LM358 so the subckt coexists cleanly with any
other vendored library.

Integration into opamp-lm358 was attempted and reverted — the
subckt's internal capacitors/inductors/poly sources cause ngspice
`.op` to time out (>60 s) on a simple unity-gain follower.  The
behavioural B-source clamp remains the active model.  When Phase 1c
moves the default analysis to `.tran` (or we add `.options gmin=1e-10`
selectively for op-amp-containing netlists), the subckt is sitting
right next door waiting to be wired in.

Phase 2.2 lockdown test guards the asset:
- declares `.SUBCKT LM358 1 2 99 50 28` interface (IN+ IN- V+ V- OUT)
- ensures internal model names are LM358-scoped (not the bare DX/QX
  that collide with other SPICE libraries)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
davidmonterocrespo24 2026-05-15 17:28:46 +02:00
parent 8f49665cdf
commit 46aa16bfc2
3 changed files with 113 additions and 0 deletions

View File

@ -0,0 +1,31 @@
/**
* Phase 2.2 LM358 subcircuit asset test.
*
* The full LM358 macro-model is vendored at simulation/spice/models/
* lm358Subckt.ts. It is NOT currently wired into componentToSpice
* because `.op` analysis with the real subckt times out (>60 s
* convergence). The behavioural B-source remains the active model
* for opamp-lm358.
*
* This test locks the asset itself: pin order, presence of the
* `.SUBCKT LM358 1 2 99 50 28` header, and the renamed internal
* model names (DX_LM358 / QX_LM358) so the subckt can coexist with
* other future subcircuits without name collisions.
*/
import { describe, it, expect } from 'vitest';
import { LM358_SUBCKT } from '../simulation/spice/models/lm358Subckt';
describe('Phase 2.2 — LM358 subckt asset', () => {
it('declares the expected 5-pin interface (IN+ IN- V+ V- OUT)', () => {
expect(LM358_SUBCKT).toMatch(/\.SUBCKT\s+LM358\s+1\s+2\s+99\s+50\s+28/i);
expect(LM358_SUBCKT).toMatch(/\.ENDS\s+LM358/i);
});
it('uses LM358-scoped names for internal models (no collisions with other libs)', () => {
expect(LM358_SUBCKT).toMatch(/\.MODEL\s+DX_LM358\s+D/i);
expect(LM358_SUBCKT).toMatch(/\.MODEL\s+QX_LM358\s+PNP/i);
// Should NOT use the bare generic names that other libs might also define.
expect(LM358_SUBCKT).not.toMatch(/\.MODEL\s+DX\s+D/i);
expect(LM358_SUBCKT).not.toMatch(/\.MODEL\s+QX\s+PNP/i);
});
});

View File

@ -14,6 +14,7 @@
import type { ComponentForSpice } from './types';
import { parseValueWithUnits } from './valueParser';
import { LM358_SUBCKT } from './models/lm358Subckt';
export interface SpiceEmission {
/** One or more netlist lines (without trailing newline). */
@ -374,6 +375,16 @@ const MAPPERS: Record<string, Mapper> = {
const inn = netLookup('IN-');
const out = netLookup('OUT');
if (!inp || !inn || !out) return null;
// Phase 2.2 lesson: the full LM358 macro-model subckt is vendored at
// ./models/lm358Subckt.ts but doesn't converge on `.op` analysis
// because of its internal capacitors / inductors / poly sources.
// The follower test (Vin → IN+, OUT → IN-) hangs >60 s. The
// behavioural B-source clamp below is kept until either:
// (a) the default analysis switches to `.tran` (Phase 1c WASM
// loop will probably do this anyway), or
// (b) we add `.options gmin=1e-10` to the netlist and confirm
// convergence across all canvases.
// The subckt module remains exported so future work can opt in.
const A = 1e5;
const vLo = 0.05;
const vHi = ctx.vcc - 1.5;

View File

@ -0,0 +1,71 @@
/**
* LM358 SPICE subcircuit macro-model.
*
* Source: National Semiconductor (now TI), redistributed for SPICE
* simulation. Sourced via stmbl/hw/spice/LM358.lib (MIT).
*
* Models a single LM358 op-amp (the part is a dual, but this subckt
* represents one channel). Captures:
* - Open-loop gain ~100 dB
* - Gain-bandwidth product ~1 MHz
* - Slew rate ~0.5 V/µs
* - Single-supply / rail-to-rail-input behaviour with ~1.5V output
* headroom from V+
*
* Pin order (subckt declaration):
* 1 IN+
* 2 IN-
* 99 V+ (positive supply wired to vcc_rail in velxio emissions)
* 50 V- (negative supply wired to 0 in velxio emissions)
* 28 OUT
*
* Used by componentToSpice.ts:opamp-lm358 to replace the prior
* behavioural B-source clamp with a real macro-model. Provides true
* slew limiting, GBW roll-off, and saturation curves so AC analyses
* (Bode plots, oscillator startup) produce textbook results.
*/
export const LM358_SUBCKT = `.SUBCKT LM358 1 2 99 50 28
IOS 2 1 5N
R1 1 3 500K
R2 3 2 500K
I1 99 4 100U
R3 5 50 517
R4 6 50 517
Q1 5 2 4 QX_LM358
Q2 6 7 4 QX_LM358
C4 5 6 128.27P
I2 99 50 75U
EOS 7 1 POLY(1) 16 49 2E-3 1
R8 99 49 60K
R9 49 50 60K
V2 99 8 1.63
D1 9 8 DX_LM358
D2 10 9 DX_LM358
V3 10 50 .635
EH 99 98 99 49 1
G1 98 9 POLY(1) 5 6 0 9.8772E-4 0 .3459
R5 98 9 101.2433MEG
C3 98 9 200P
G3 98 15 9 49 1E-6
R12 98 15 1MEG
C5 98 15 7.9577E-14
G4 98 16 3 49 5.6234E-8
L2 98 17 15.9M
R13 17 16 1K
F6 50 99 POLY(1) V6 300U 1
E1 99 23 99 15 1
R16 24 23 17.5
D5 26 24 DX_LM358
V6 26 22 .63V
R17 23 25 17.5
D6 25 27 DX_LM358
V7 22 27 .63V
V5 22 21 0.27V
D4 21 15 DX_LM358
V4 20 22 0.27V
D3 15 20 DX_LM358
L3 22 28 500P
RL3 22 28 100K
.MODEL DX_LM358 D(IS=1E-15)
.MODEL QX_LM358 PNP(BF=1.111E3)
.ENDS LM358`;