179 lines
5.1 KiB
JavaScript
179 lines
5.1 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
||
import { runNetlist } from '../src/spice/SpiceEngine.js';
|
||
|
||
/**
|
||
* Sanity check for power-supply mappers (fase 9.4):
|
||
* - reg-7805 / -7812 / -7905 / -lm317
|
||
* - battery-9v / -aa / -coin-cell
|
||
* - signal-generator (sine / square / dc)
|
||
*
|
||
* Each test builds the literal card string the mapper emits and verifies
|
||
* ngspice returns the expected steady-state voltage / waveform.
|
||
*/
|
||
|
||
describe('componentToSpice — fixed 78xx / 79xx regulators', () => {
|
||
it('7805 regulates to 5V when V_in > 7V', { timeout: 30_000 }, async () => {
|
||
for (const vin of [7.5, 9, 12, 15]) {
|
||
const netlist = `7805 healthy V_in=${vin}
|
||
Vin vin 0 DC ${vin}
|
||
B_u1 vout 0 V = min(V(vin)-V(0)-2, 5)
|
||
R_u1_out vout 0 10Meg
|
||
Rload vout 0 1k
|
||
.op
|
||
.end`;
|
||
const { dcValue } = await runNetlist(netlist);
|
||
expect(dcValue('v(vout)')).toBeCloseTo(5, 2);
|
||
}
|
||
});
|
||
|
||
it('7805 drops out when V_in < 7V: V_out = V_in − 2', { timeout: 30_000 }, async () => {
|
||
const netlist = `7805 dropout
|
||
Vin vin 0 DC 4
|
||
B_u1 vout 0 V = min(V(vin)-V(0)-2, 5)
|
||
R_u1_out vout 0 10Meg
|
||
Rload vout 0 1k
|
||
.op
|
||
.end`;
|
||
const { dcValue } = await runNetlist(netlist);
|
||
expect(dcValue('v(vout)')).toBeCloseTo(2, 1);
|
||
});
|
||
|
||
it('7812 regulates to 12V when V_in > 14V', { timeout: 30_000 }, async () => {
|
||
const netlist = `7812 healthy
|
||
Vin vin 0 DC 18
|
||
B_u1 vout 0 V = min(V(vin)-V(0)-2, 12)
|
||
R_u1_out vout 0 10Meg
|
||
Rload vout 0 1k
|
||
.op
|
||
.end`;
|
||
const { dcValue } = await runNetlist(netlist);
|
||
expect(dcValue('v(vout)')).toBeCloseTo(12, 2);
|
||
});
|
||
|
||
it('7905 delivers −5V relative to GND', { timeout: 30_000 }, async () => {
|
||
const netlist = `7905 negative
|
||
Vin vin 0 DC -9
|
||
B_u1 vout 0 V = max(V(vin)-V(0)+2, -5)
|
||
R_u1_out vout 0 10Meg
|
||
Rload vout 0 1k
|
||
.op
|
||
.end`;
|
||
const { dcValue } = await runNetlist(netlist);
|
||
expect(dcValue('v(vout)')).toBeCloseTo(-5, 2);
|
||
});
|
||
});
|
||
|
||
describe('componentToSpice — LM317 adjustable regulator', () => {
|
||
it('LM317 with R1=240Ω, R2=720Ω yields V_out ≈ 5V (ideal ratio)', { timeout: 30_000 }, async () => {
|
||
// V_out = 1.25·(1 + R2/R1) = 1.25·(1 + 720/240) = 1.25·4 = 5.0
|
||
const netlist = `LM317 5V
|
||
Vin vin 0 DC 12
|
||
B_u1 vout 0 V = V(adj) + min(V(vin)-V(adj)-2, 1.25)
|
||
R_u1_out vout 0 10Meg
|
||
R1 vout adj 240
|
||
R2 adj 0 720
|
||
Rload vout 0 10k
|
||
.op
|
||
.end`;
|
||
const { dcValue } = await runNetlist(netlist);
|
||
expect(dcValue('v(vout)')).toBeCloseTo(5, 1);
|
||
});
|
||
|
||
it('LM317 with R1=240Ω, R2=1920Ω yields V_out ≈ 11.25V', { timeout: 30_000 }, async () => {
|
||
// V_out = 1.25·(1 + 1920/240) = 1.25·9 = 11.25
|
||
const netlist = `LM317 11.25V
|
||
Vin vin 0 DC 15
|
||
B_u1 vout 0 V = V(adj) + min(V(vin)-V(adj)-2, 1.25)
|
||
R_u1_out vout 0 10Meg
|
||
R1 vout adj 240
|
||
R2 adj 0 1920
|
||
Rload vout 0 10k
|
||
.op
|
||
.end`;
|
||
const { dcValue } = await runNetlist(netlist);
|
||
expect(dcValue('v(vout)')).toBeCloseTo(11.25, 1);
|
||
});
|
||
});
|
||
|
||
describe('componentToSpice — batteries', () => {
|
||
it('9V battery no-load: +/− = 9V', { timeout: 30_000 }, async () => {
|
||
const netlist = `9V no load
|
||
V_b1 pos b1_int DC 9
|
||
R_b1_esr b1_int 0 1.5
|
||
R_meter pos 0 10Meg
|
||
.op
|
||
.end`;
|
||
const { dcValue } = await runNetlist(netlist);
|
||
expect(dcValue('v(pos)')).toBeCloseTo(9, 2);
|
||
});
|
||
|
||
it('9V battery sags under 100Ω load: V ≈ 9·100/(100+1.5) ≈ 8.87V', { timeout: 30_000 }, async () => {
|
||
const netlist = `9V loaded
|
||
V_b1 pos b1_int DC 9
|
||
R_b1_esr b1_int 0 1.5
|
||
Rload pos 0 100
|
||
.op
|
||
.end`;
|
||
const { dcValue } = await runNetlist(netlist);
|
||
const expected = 9 * 100 / (100 + 1.5);
|
||
expect(dcValue('v(pos)')).toBeCloseTo(expected, 2);
|
||
});
|
||
|
||
it('coin cell high ESR limits current to ~300 mA into short', { timeout: 30_000 }, async () => {
|
||
const netlist = `coin cell short
|
||
V_b1 pos b1_int DC 3
|
||
R_b1_esr b1_int 0 10
|
||
Rshort pos 0 0.001
|
||
.op
|
||
.end`;
|
||
const { vec } = await runNetlist(netlist);
|
||
// I through short ≈ 3V / 10Ω = 300 mA
|
||
const iVb1 = vec('i(v_b1)')[0];
|
||
expect(Math.abs(iVb1)).toBeGreaterThan(0.25);
|
||
expect(Math.abs(iVb1)).toBeLessThan(0.35);
|
||
});
|
||
});
|
||
|
||
describe('componentToSpice — signal generator', () => {
|
||
it('sine 1kHz / 1V amplitude / 0 offset has correct peak-to-peak', { timeout: 30_000 }, async () => {
|
||
const netlist = `sine
|
||
V_sg sig 0 SIN(0 1 1000)
|
||
Rload sig 0 1k
|
||
.tran 10u 3m
|
||
.end`;
|
||
const { vec } = await runNetlist(netlist);
|
||
const t = vec('time');
|
||
const vs = vec('v(sig)');
|
||
let mx = -Infinity, mn = Infinity;
|
||
for (let i = 0; i < t.length; i++) {
|
||
if (t[i] < 0.5e-3) continue;
|
||
if (vs[i] > mx) mx = vs[i];
|
||
if (vs[i] < mn) mn = vs[i];
|
||
}
|
||
expect(mx - mn).toBeCloseTo(2, 1);
|
||
});
|
||
|
||
it('square 500Hz toggles between −1V and +1V', { timeout: 30_000 }, async () => {
|
||
const netlist = `square
|
||
V_sg sig 0 PULSE(-1 1 0 1n 1n 1m 2m)
|
||
Rload sig 0 1k
|
||
.tran 20u 5m
|
||
.end`;
|
||
const { vec } = await runNetlist(netlist);
|
||
const vs = vec('v(sig)');
|
||
const hi = vs.some(v => v > 0.9);
|
||
const lo = vs.some(v => v < -0.9);
|
||
expect(hi && lo).toBe(true);
|
||
});
|
||
|
||
it('DC mode: offset=3, amplitude ignored → V=3', { timeout: 30_000 }, async () => {
|
||
const netlist = `dc source
|
||
V_sg sig 0 DC 3
|
||
Rload sig 0 1k
|
||
.op
|
||
.end`;
|
||
const { dcValue } = await runNetlist(netlist);
|
||
expect(dcValue('v(sig)')).toBeCloseTo(3, 2);
|
||
});
|
||
});
|