velxio/test/test_circuit/test/spice_mapped_multi_input_ga...

93 lines
3.5 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import { runNetlist } from '../src/spice/SpiceEngine.js';
/**
* Sanity check for the 3- and 4-input gate mappers (fase 9.5).
* Verifies the exact behavioural expressions componentToSpice.ts emits
* for AND/OR/NAND/NOR with 3 and 4 inputs.
*/
const VCC = 5;
const T = VCC / 2;
function buildCard(variant, inputs) {
const U = inputs.map(n => `u(V(${n})-${T})`);
const NU = inputs.map(n => `(1-u(V(${n})-${T}))`);
switch (variant) {
case 'and': return `${VCC} * ${U.join(' * ')}`;
case 'or': return `${VCC} * (1 - ${NU.join(' * ')})`;
case 'nand': return `${VCC} * (1 - ${U.join(' * ')})`;
case 'nor': return `${VCC} * ${NU.join(' * ')}`;
default: throw new Error(`unknown ${variant}`);
}
}
async function evalGate(variant, values) {
const inputs = Object.keys(values);
const sources = inputs.map(n => `V${n} ${n} 0 DC ${values[n]}`).join('\n');
const expr = buildCard(variant, inputs);
const netlist = `gate ${variant} ${inputs.length}-in
${sources}
B_g y 0 V = ${expr}
R_g_load y 0 1Meg
.op
.end`;
const { dcValue } = await runNetlist(netlist);
return dcValue('v(y)');
}
function hi(n) { return Array.from({ length: n }, () => 5); }
function lo(n) { return Array.from({ length: n }, () => 0); }
function mixed(n, highIdx) {
return Array.from({ length: n }, (_, i) => (i === highIdx ? 5 : 0));
}
function named(values) {
return Object.fromEntries(values.map((v, i) => [String.fromCharCode(65 + i), v]));
}
describe('componentToSpice — 3-input gates', () => {
it('AND-3 truth table (all HIGH → 5, any LOW → 0)', { timeout: 60_000 }, async () => {
expect(await evalGate('and', named(hi(3)))).toBeCloseTo(5, 0);
expect(await evalGate('and', named(mixed(3, 0)))).toBeCloseTo(0, 0);
expect(await evalGate('and', named(lo(3)))).toBeCloseTo(0, 0);
});
it('OR-3 truth table (any HIGH → 5, all LOW → 0)', { timeout: 60_000 }, async () => {
expect(await evalGate('or', named(lo(3)))).toBeCloseTo(0, 0);
expect(await evalGate('or', named(mixed(3, 2)))).toBeCloseTo(5, 0);
expect(await evalGate('or', named(hi(3)))).toBeCloseTo(5, 0);
});
it('NAND-3 is complement of AND-3', { timeout: 60_000 }, async () => {
expect(await evalGate('nand', named(hi(3)))).toBeCloseTo(0, 0);
expect(await evalGate('nand', named(mixed(3, 0)))).toBeCloseTo(5, 0);
});
it('NOR-3 is complement of OR-3', { timeout: 60_000 }, async () => {
expect(await evalGate('nor', named(lo(3)))).toBeCloseTo(5, 0);
expect(await evalGate('nor', named(mixed(3, 2)))).toBeCloseTo(0, 0);
});
});
describe('componentToSpice — 4-input gates', () => {
it('AND-4 only HIGH when all 4 inputs are HIGH', { timeout: 60_000 }, async () => {
expect(await evalGate('and', named(hi(4)))).toBeCloseTo(5, 0);
expect(await evalGate('and', named(mixed(4, 3)))).toBeCloseTo(0, 0);
});
it('OR-4 HIGH when any input is HIGH (even the last one)', { timeout: 60_000 }, async () => {
expect(await evalGate('or', named(lo(4)))).toBeCloseTo(0, 0);
expect(await evalGate('or', named(mixed(4, 3)))).toBeCloseTo(5, 0);
});
it('NAND-4 HIGH unless all 4 inputs are HIGH', { timeout: 60_000 }, async () => {
expect(await evalGate('nand', named(hi(4)))).toBeCloseTo(0, 0);
expect(await evalGate('nand', named(mixed(4, 2)))).toBeCloseTo(5, 0);
});
it('NOR-4 LOW whenever any input is HIGH', { timeout: 60_000 }, async () => {
expect(await evalGate('nor', named(lo(4)))).toBeCloseTo(5, 0);
expect(await evalGate('nor', named(mixed(4, 0)))).toBeCloseTo(0, 0);
});
});