velxio/test/test_circuit/test/spice_digital.test.js

114 lines
3.4 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import { runNetlist } from '../src/spice/SpiceEngine.js';
/**
* Digital logic via ngspice B-sources (behavioral voltage sources).
* Uses the u() step function: u(x) = 1 if x>0, else 0.
*
* AND: 5 * u(V(a)-2.5) * u(V(b)-2.5)
* OR: 5 * (1 - (1-u(V(a)-2.5)) * (1-u(V(b)-2.5)))
* NAND: 5 * (1 - u(V(a)-2.5) * u(V(b)-2.5))
* XOR: 5 * ((u(V(a)-2.5) + u(V(b)-2.5)) - 2*u(V(a)-2.5)*u(V(b)-2.5))
* NOT: 5 * (1 - u(V(a)-2.5))
*/
describe('ngspice — digital gates via B-sources (u-function)', () => {
it('AND truth table', { timeout: 60_000 }, async () => {
const rows = [
{ a: 0, b: 0, expected: 0 },
{ a: 0, b: 5, expected: 0 },
{ a: 5, b: 0, expected: 0 },
{ a: 5, b: 5, expected: 5 },
];
for (const r of rows) {
const netlist = `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`;
const { dcValue } = await runNetlist(netlist);
expect(dcValue('v(y)')).toBeCloseTo(r.expected, 0);
}
});
it('NAND truth table', { timeout: 60_000 }, async () => {
const rows = [
{ a: 0, b: 0, expected: 5 },
{ a: 0, b: 5, expected: 5 },
{ a: 5, b: 0, expected: 5 },
{ a: 5, b: 5, expected: 0 },
];
for (const r of rows) {
const netlist = `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`;
const { dcValue } = await runNetlist(netlist);
expect(dcValue('v(y)')).toBeCloseTo(r.expected, 0);
}
});
it('XOR truth table', { timeout: 60_000 }, async () => {
const rows = [
{ a: 0, b: 0, expected: 0 },
{ a: 0, b: 5, expected: 5 },
{ a: 5, b: 0, expected: 5 },
{ a: 5, b: 5, expected: 0 },
];
for (const r of rows) {
const netlist = `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`;
const { dcValue } = await runNetlist(netlist);
expect(dcValue('v(y)')).toBeCloseTo(r.expected, 0);
}
});
});
describe('ngspice — logic + analog mixed', () => {
it('AND gate drives an RC low-pass: filter smooths the square wave', { timeout: 60_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 t = vec('time');
const filt = vec('v(filt)');
const y = vec('v(y)');
// After 5τ (50ms wait is overkill — actually 5·10kΩ·1µF = 50ms exactly,
// but 20ms is enough to see settling happen).
const last = filt.slice(-50);
const mean = last.reduce((s, v) => s + v, 0) / last.length;
// Filter ripple should be much smaller than 5 V swing (the raw AND gate output)
const filtMin = Math.min(...last);
const filtMax = Math.max(...last);
const ripple = filtMax - filtMin;
expect(ripple).toBeLessThan(1.5);
// Over the whole run, the raw AND output must have toggled between 0 and 5
const rawMin = Math.min(...y);
const rawMax = Math.max(...y);
expect(rawMin).toBeLessThan(0.5);
expect(rawMax).toBeGreaterThan(4.5);
// Mean of the filtered signal is strictly between 0 and 5 V
expect(mean).toBeGreaterThan(0);
expect(mean).toBeLessThan(5);
});
});