velxio/test/test_circuit/test/spice_mapped_power.test.js

179 lines
5.1 KiB
JavaScript
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 '../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);
});
});