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

90 lines
2.4 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { runNetlist } from './helpers/testSolver';
/**
* Behavioral logic gates via ngspice B-sources + `u()` step function.
* Validates that digital logic and analog can live in the same netlist.
*/
describe('ngspice — digital gates via B-sources', () => {
it('AND truth table', { timeout: 30_000 }, async () => {
const rows = [
{ a: 0, b: 0, y: 0 },
{ a: 0, b: 5, y: 0 },
{ a: 5, b: 0, y: 0 },
{ a: 5, b: 5, y: 5 },
];
for (const r of rows) {
const { dcValue } = await runNetlist(`AND
Va a 0 DC ${r.a}
Vb b 0 DC ${r.b}
Band y 0 V = 5 * u(V(a)-2.5) * u(V(b)-2.5)
Rload y 0 1k
.op
.end`);
expect(dcValue('v(y)')).toBeCloseTo(r.y, 0);
}
});
it('NAND truth table', { timeout: 30_000 }, async () => {
const rows = [
{ a: 0, b: 0, y: 5 },
{ a: 0, b: 5, y: 5 },
{ a: 5, b: 0, y: 5 },
{ a: 5, b: 5, y: 0 },
];
for (const r of rows) {
const { dcValue } = await runNetlist(`NAND
Va a 0 DC ${r.a}
Vb b 0 DC ${r.b}
Bnand y 0 V = 5 * (1 - u(V(a)-2.5) * u(V(b)-2.5))
Rload y 0 1k
.op
.end`);
expect(dcValue('v(y)')).toBeCloseTo(r.y, 0);
}
});
it('XOR truth table', { timeout: 30_000 }, async () => {
const rows = [
{ a: 0, b: 0, y: 0 },
{ a: 0, b: 5, y: 5 },
{ a: 5, b: 0, y: 5 },
{ a: 5, b: 5, y: 0 },
];
for (const r of rows) {
const { dcValue } = await runNetlist(`XOR
Va a 0 DC ${r.a}
Vb b 0 DC ${r.b}
Bxor y 0 V = 5 * (u(V(a)-2.5) + u(V(b)-2.5) - 2*u(V(a)-2.5)*u(V(b)-2.5))
Rload y 0 1k
.op
.end`);
expect(dcValue('v(y)')).toBeCloseTo(r.y, 0);
}
});
it('AND drives RC low-pass: filter smooths the square wave', { timeout: 30_000 }, async () => {
const netlist = `AND -> RC
Va a 0 PULSE(0 5 0 1n 1n 1m 2m)
Vb b 0 PULSE(0 5 0 1n 1n 0.75m 1.5m)
Band y 0 V = 5 * u(V(a)-2.5) * u(V(b)-2.5)
R1 y filt 10k
C1 filt 0 1u IC=0
.tran 10u 20m
.end`;
const { vec } = await runNetlist(netlist);
const filt = vec('v(filt)') as number[];
const y = vec('v(y)') as number[];
const last = filt.slice(-50);
const mean = last.reduce((s, v) => s + v, 0) / last.length;
const filtMin = Math.min(...last);
const filtMax = Math.max(...last);
expect(filtMax - filtMin).toBeLessThan(1.5);
expect(Math.min(...y)).toBeLessThan(0.5);
expect(Math.max(...y)).toBeGreaterThan(4.5);
expect(mean).toBeGreaterThan(0);
expect(mean).toBeLessThan(5);
});
});