velxio/frontend/src/simulation/parts/LogicGateParts.ts

202 lines
8.1 KiB
TypeScript

/**
* LogicGateParts.ts — Simulation logic for logic gate components.
*
* All gates listen to their input pins via pinManager.onPinChange,
* compute the boolean output, and drive the Y pin accordingly.
*
* 2-input gates: A, B → Y
* NOT gate: A → Y
*/
import { PartSimulationRegistry } from './PartSimulationRegistry';
import type { PartSimulationLogic } from './PartSimulationRegistry';
// ─── Helper ───────────────────────────────────────────────────────────────────
function twoInputGate(
compute: (a: boolean, b: boolean) => boolean,
): PartSimulationLogic {
return {
attachEvents: (element, simulator, getPin) => {
const pinA = getPin('A');
const pinB = getPin('B');
const pinY = getPin('Y');
if (pinA === null || pinB === null || pinY === null) return () => {};
let stateA = false;
let stateB = false;
const update = () => simulator.setPinState(pinY, compute(stateA, stateB));
const unsubA = simulator.pinManager.onPinChange(pinA, (_: number, s: boolean) => {
stateA = s; update();
});
const unsubB = simulator.pinManager.onPinChange(pinB, (_: number, s: boolean) => {
stateB = s; update();
});
update(); // Drive Y immediately with initial LOW state
return () => { unsubA(); unsubB(); };
},
};
}
// ─── AND ──────────────────────────────────────────────────────────────────────
PartSimulationRegistry.register('logic-gate-and', twoInputGate((a, b) => a && b));
// ─── NAND ─────────────────────────────────────────────────────────────────────
PartSimulationRegistry.register('logic-gate-nand', twoInputGate((a, b) => !(a && b)));
// ─── OR ───────────────────────────────────────────────────────────────────────
PartSimulationRegistry.register('logic-gate-or', twoInputGate((a, b) => a || b));
// ─── NOR ──────────────────────────────────────────────────────────────────────
PartSimulationRegistry.register('logic-gate-nor', twoInputGate((a, b) => !(a || b)));
// ─── XOR ──────────────────────────────────────────────────────────────────────
PartSimulationRegistry.register('logic-gate-xor', twoInputGate((a, b) => a !== b));
// ─── XNOR ─────────────────────────────────────────────────────────────────────
PartSimulationRegistry.register('logic-gate-xnor', twoInputGate((a, b) => a === b));
// ─── Multi-input gates (3 / 4 inputs) ─────────────────────────────────────────
function nInputGate(
inputNames: string[],
compute: (inputs: boolean[]) => boolean,
): PartSimulationLogic {
return {
attachEvents: (element, simulator, getPin) => {
const inputPins = inputNames.map(n => getPin(n));
const pinY = getPin('Y');
if (inputPins.some(p => p === null) || pinY === null) return () => {};
const states = inputNames.map(() => false);
const update = () => simulator.setPinState(pinY, compute(states));
const unsubs = inputPins.map((p, i) =>
simulator.pinManager.onPinChange(p!, (_: number, s: boolean) => {
states[i] = s;
update();
}),
);
update();
return () => { unsubs.forEach(u => u()); };
},
};
}
const allTrue = (xs: boolean[]) => xs.every(Boolean);
const anyTrue = (xs: boolean[]) => xs.some(Boolean);
const notAll = (xs: boolean[]) => !allTrue(xs);
const notAny = (xs: boolean[]) => !anyTrue(xs);
// ─── Flip-flops (edge-triggered, digital-sim only) ────────────────────────────
// SPICE mode cannot simulate real edge detection at DC; these components are
// therefore digital-only and do not emit a SPICE mapper.
//
// Each FF samples its data inputs on the rising edge of CLK. Q and Qbar are
// driven synchronously.
function edgeTriggeredFF(
dataPins: string[],
initial: boolean,
sample: (state: boolean, inputs: boolean[]) => boolean,
): PartSimulationLogic {
return {
attachEvents: (element, simulator, getPin) => {
const clkPin = getPin('CLK');
const qPin = getPin('Q');
const qbarPin = getPin('Qbar');
const dataPinIds = dataPins.map(n => getPin(n));
if (clkPin === null || qPin === null || qbarPin === null) return () => {};
if (dataPinIds.some(p => p === null)) return () => {};
let prevClk = false;
let q = initial;
const dataStates = dataPins.map(() => false);
const emit = () => {
simulator.setPinState(qPin, q);
simulator.setPinState(qbarPin, !q);
};
const unsubClk = simulator.pinManager.onPinChange(clkPin, (_: number, s: boolean) => {
if (!prevClk && s) {
// Rising edge
q = sample(q, dataStates);
emit();
}
prevClk = s;
});
const unsubData = dataPinIds.map((p, i) =>
simulator.pinManager.onPinChange(p!, (_: number, s: boolean) => {
dataStates[i] = s;
}),
);
emit(); // Drive initial Q / Qbar
return () => {
unsubClk();
unsubData.forEach(u => u());
};
},
};
}
// D flip-flop: Q ← D on rising CLK
PartSimulationRegistry.register('flip-flop-d',
edgeTriggeredFF(['D'], false, (_q, [d]) => d));
// T flip-flop: Q ← Q ⊕ T on rising CLK (toggle when T=1)
PartSimulationRegistry.register('flip-flop-t',
edgeTriggeredFF(['T'], false, (q, [t]) => (t ? !q : q)));
// JK flip-flop:
// J=0, K=0 → hold
// J=1, K=0 → set (Q=1)
// J=0, K=1 → reset (Q=0)
// J=1, K=1 → toggle
PartSimulationRegistry.register('flip-flop-jk',
edgeTriggeredFF(['J', 'K'], false, (q, [j, k]) => {
if (j && k) return !q;
if (j) return true;
if (k) return false;
return q;
}));
PartSimulationRegistry.register('logic-gate-and-3', nInputGate(['A', 'B', 'C'], allTrue));
PartSimulationRegistry.register('logic-gate-or-3', nInputGate(['A', 'B', 'C'], anyTrue));
PartSimulationRegistry.register('logic-gate-nand-3', nInputGate(['A', 'B', 'C'], notAll));
PartSimulationRegistry.register('logic-gate-nor-3', nInputGate(['A', 'B', 'C'], notAny));
PartSimulationRegistry.register('logic-gate-and-4', nInputGate(['A', 'B', 'C', 'D'], allTrue));
PartSimulationRegistry.register('logic-gate-or-4', nInputGate(['A', 'B', 'C', 'D'], anyTrue));
PartSimulationRegistry.register('logic-gate-nand-4', nInputGate(['A', 'B', 'C', 'D'], notAll));
PartSimulationRegistry.register('logic-gate-nor-4', nInputGate(['A', 'B', 'C', 'D'], notAny));
// ─── NOT (inverter) ───────────────────────────────────────────────────────────
PartSimulationRegistry.register('logic-gate-not', {
attachEvents: (element, simulator, getPin) => {
const pinA = getPin('A');
const pinY = getPin('Y');
if (pinA === null || pinY === null) return () => {};
const unsub = simulator.pinManager.onPinChange(pinA, (_: number, s: boolean) => {
simulator.setPinState(pinY, !s);
});
simulator.setPinState(pinY, true); // NOT LOW = HIGH (initial LOW input → HIGH output)
return unsub;
},
});