velxio/frontend/src/__tests__/spice-active.test.ts

128 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect } from 'vitest';
import { runNetlist } from './helpers/testSolver';
describe('ngspice — diode', () => {
it('1N4148-style diode forward drop is 0.550.80V @ ~4 mA', { timeout: 30_000 }, async () => {
const netlist = `Diode forward
V1 vcc 0 DC 5
R1 vcc a 1k
D1 a 0 DMOD
.model DMOD D(Is=2.52n N=1.752 Rs=0.568 Ibv=0.1u Bv=100)
.op
.end`;
const { dcValue } = await runNetlist(netlist);
const Va = dcValue('v(a)');
expect(Va).toBeGreaterThan(0.55);
expect(Va).toBeLessThan(0.8);
});
it('full-wave bridge rectifier outputs ~|Vin| 2·Vf', { timeout: 30_000 }, async () => {
const netlist = `Bridge rectifier
V1 a b SIN(0 6 50)
D1 a p DMOD
D2 b p DMOD
D3 n a DMOD
D4 n b DMOD
R1 p n 1k
.model DMOD D(Is=1e-14 N=1)
.tran 0.1m 40m
.end`;
const { vec } = await runNetlist(netlist);
const t = vec('time') as number[];
const vp = vec('v(p)') as number[];
const vn = vec('v(n)') as number[];
let peakOut = -Infinity;
let minOut = Infinity;
for (let i = 0; i < t.length; i++) {
if (t[i] < 20e-3) continue;
const d = vp[i] - vn[i];
if (d > peakOut) peakOut = d;
if (d < minOut) minOut = d;
}
expect(peakOut).toBeGreaterThan(4.2);
expect(peakOut).toBeLessThan(5.4);
expect(minOut).toBeGreaterThan(-0.1);
});
});
describe('ngspice — BJT', () => {
it(
'common-emitter amplifier inverts and amplifies a small signal',
{ timeout: 30_000 },
async () => {
const netlist = `Common-emitter
Vcc vcc 0 DC 12
Vin in 0 SIN(0 0.01 1k)
Cin in b 1u
RB1 vcc b 47k
RB2 b 0 10k
RC vcc c 4.7k
RE e 0 1k
CE e 0 100u
Q1 c b e Q2N2222
Cout c out 1u
Rout out 0 100k
.model Q2N2222 NPN(Is=1e-14 Bf=200 Vaf=75)
.tran 10u 6m
.end`;
const { vec } = await runNetlist(netlist);
const t = vec('time') as number[];
const vin = vec('v(in)') as number[];
const vout = vec('v(out)') as number[];
let maxIn = 0;
let maxOut = 0;
let minOut = Infinity;
for (let i = 0; i < t.length; i++) {
if (t[i] < 3e-3) continue;
if (Math.abs(vin[i]) > maxIn) maxIn = Math.abs(vin[i]);
if (vout[i] > maxOut) maxOut = vout[i];
if (vout[i] < minOut) minOut = vout[i];
}
const gain = (maxOut - minOut) / (2 * maxIn);
expect(gain).toBeGreaterThan(30);
},
);
});
describe('ngspice — MOSFET', () => {
it('N-MOS switch: V_GS > Vth pulls drain to ground', { timeout: 30_000 }, async () => {
const netlist = `N-MOS switch
Vcc vcc 0 DC 5
Vgate gate 0 DC 5
RL vcc drain 1k
M1 drain gate 0 0 NMOS_L1 L=1u W=100u
.model NMOS_L1 NMOS(Level=1 Vto=1.0 Kp=50u Lambda=0.01)
.op
.end`;
const { dcValue } = await runNetlist(netlist);
expect(dcValue('v(drain)')).toBeLessThan(1.0);
});
it('N-MOS switch: V_GS < Vth leaves drain near V_dd', { timeout: 30_000 }, async () => {
const netlist = `N-MOS off
Vcc vcc 0 DC 5
Vgate gate 0 DC 0
RL vcc drain 1k
M1 drain gate 0 0 NMOS_L1 L=1u W=100u
.model NMOS_L1 NMOS(Level=1 Vto=1.0 Kp=50u Lambda=0.01)
.op
.end`;
const { dcValue } = await runNetlist(netlist);
expect(dcValue('v(drain)')).toBeGreaterThan(4.9);
});
});
describe('ngspice — op-amp (behavioral E-source)', () => {
it('inverting amplifier: V_out = (R_f/R_in) · V_in', { timeout: 30_000 }, async () => {
const netlist = `Inverting amp
Vin in 0 DC 0.2
Rin in n 1k
Rf n out 10k
Eopa out 0 0 n 1e6
.op
.end`;
const { dcValue } = await runNetlist(netlist);
expect(dcValue('v(out)')).toBeCloseTo(-2.0, 2);
});
});