211 lines
7.4 KiB
TypeScript
211 lines
7.4 KiB
TypeScript
/**
|
|
* ChipParts.ts — Simulation logic for complex IC chips
|
|
*
|
|
* Implements:
|
|
* - 74HC595 8-bit Serial-to-Parallel Shift Register
|
|
* - wokwi-7segment display (driven by 74HC595 outputs)
|
|
*/
|
|
|
|
import { PartSimulationRegistry } from './PartSimulationRegistry';
|
|
import { useSimulatorStore } from '../../store/useSimulatorStore';
|
|
|
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Given a 74HC595 component ID and a pin name (e.g. 'Q0'), find the DOM element
|
|
* of whatever component is connected on the other side of that wire, plus the
|
|
* pin name on that component.
|
|
*/
|
|
function getConnectedToPin(
|
|
componentId: string,
|
|
pinName: string,
|
|
): { element: HTMLElement; pinName: string } | null {
|
|
const { wires } = useSimulatorStore.getState();
|
|
for (const wire of wires) {
|
|
let otherCompId: string | null = null;
|
|
let otherPin: string | null = null;
|
|
|
|
if (wire.start.componentId === componentId && wire.start.pinName === pinName) {
|
|
otherCompId = wire.end.componentId;
|
|
otherPin = wire.end.pinName;
|
|
} else if (wire.end.componentId === componentId && wire.end.pinName === pinName) {
|
|
otherCompId = wire.start.componentId;
|
|
otherPin = wire.start.pinName;
|
|
}
|
|
|
|
if (otherCompId && otherPin) {
|
|
const el = document.getElementById(otherCompId);
|
|
if (el) return { element: el as HTMLElement, pinName: otherPin };
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Update a 7-segment display element when pin states change.
|
|
* pinName is the segment identifier (A, B, C, D, E, F, G, DP).
|
|
* state is whether the segment is lit (HIGH = lit for common-cathode).
|
|
*/
|
|
function set7SegPin(element: HTMLElement, pinName: string, state: boolean) {
|
|
const segmentIndex: Record<string, number> = {
|
|
A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, DP: 7,
|
|
};
|
|
const idx = segmentIndex[pinName.toUpperCase()];
|
|
if (idx === undefined) return;
|
|
|
|
const el = element as any;
|
|
const current: number[] = Array.isArray(el.values) ? [...el.values] : [0, 0, 0, 0, 0, 0, 0, 0];
|
|
current[idx] = state ? 1 : 0;
|
|
el.values = current;
|
|
}
|
|
|
|
// ─── 74HC595 simulation ───────────────────────────────────────────────────────
|
|
|
|
PartSimulationRegistry.register('74hc595', {
|
|
attachEvents: (element, simulator, getArduinoPinHelper) => {
|
|
const pinManager = (simulator as any).pinManager;
|
|
if (!pinManager) return () => {};
|
|
|
|
// Internal state
|
|
let shiftReg = 0; // 8-bit shift register
|
|
let storageReg = 0; // 8-bit storage register (output)
|
|
let oeActive = false; // output enable (active low)
|
|
let mrActive = true; // master reset (active low — HIGH = not reset)
|
|
|
|
let prevShcp = false;
|
|
let prevStcp = false;
|
|
|
|
// Resolve connected Arduino pins
|
|
const pinDS = getArduinoPinHelper('DS');
|
|
const pinSHCP = getArduinoPinHelper('SHCP');
|
|
const pinSTCP = getArduinoPinHelper('STCP');
|
|
const pinMR = getArduinoPinHelper('MR');
|
|
const pinOE = getArduinoPinHelper('OE');
|
|
|
|
const unsubscribers: (() => void)[] = [];
|
|
|
|
// Helper: propagate current storage reg outputs to connected components
|
|
const propagateOutputs = () => {
|
|
if (!oeActive) return; // outputs disabled (OE high = disabled)
|
|
const compId = element.id;
|
|
|
|
// Q0-Q7 maps to bits 0-7 of storageReg
|
|
const outputPins = ['Q0', 'Q1', 'Q2', 'Q3', 'Q4', 'Q5', 'Q6', 'Q7'];
|
|
for (let i = 0; i < 8; i++) {
|
|
const state = ((storageReg >> i) & 1) === 1;
|
|
const connected = getConnectedToPin(compId, outputPins[i]);
|
|
if (connected) {
|
|
// Update 7-segment or LED or any other component
|
|
const tagName = connected.element.tagName.toLowerCase();
|
|
if (tagName === 'wokwi-7segment') {
|
|
set7SegPin(connected.element, connected.pinName, state);
|
|
} else if (tagName === 'wokwi-led') {
|
|
(connected.element as any).value = state ? 1 : 0;
|
|
}
|
|
// Update 74HC595 chained via Q7S
|
|
if (tagName === 'wokwi-74hc595' && outputPins[i] === 'Q7S') {
|
|
// Q7S is serial out — drives DS of next chip (handled via wire logic)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update this element's visual (Q0-Q7 output dots)
|
|
const el = element as any;
|
|
el.values = outputPins.map((_, i) => ((storageReg >> i) & 1));
|
|
|
|
// Also propagate Q7S (serial output = bit 7 of shift register, not storage)
|
|
const q7sConn = getConnectedToPin(element.id, 'Q7S');
|
|
if (q7sConn) {
|
|
// Q7S is used to chain to the DS pin of next 74HC595 — this is handled
|
|
// by the DS monitoring of the downstream chip
|
|
}
|
|
};
|
|
|
|
// OE (active low — LOW enables outputs)
|
|
if (pinOE !== null) {
|
|
pinManager.triggerPinChange(pinOE, true); // default HIGH = disabled
|
|
unsubscribers.push(pinManager.onPinChange(pinOE, (_: number, state: boolean) => {
|
|
oeActive = !state; // OE low = active
|
|
propagateOutputs();
|
|
}));
|
|
} else {
|
|
oeActive = true; // assume OE tied to GND (always enabled)
|
|
}
|
|
|
|
// MR (active low — LOW resets shift register)
|
|
if (pinMR !== null) {
|
|
unsubscribers.push(pinManager.onPinChange(pinMR, (_: number, state: boolean) => {
|
|
mrActive = state; // MR high = no reset, low = reset
|
|
if (!mrActive) {
|
|
shiftReg = 0;
|
|
}
|
|
}));
|
|
} else {
|
|
mrActive = true; // assume MR tied high
|
|
}
|
|
|
|
// DS — latched on SHCP rising edge; just track current value
|
|
let dsState = false;
|
|
if (pinDS !== null) {
|
|
unsubscribers.push(pinManager.onPinChange(pinDS, (_: number, state: boolean) => {
|
|
dsState = state;
|
|
}));
|
|
}
|
|
|
|
// SHCP — rising edge shifts DS into shift register
|
|
if (pinSHCP !== null) {
|
|
unsubscribers.push(pinManager.onPinChange(pinSHCP, (_: number, state: boolean) => {
|
|
if (state && !prevShcp) {
|
|
// Rising edge
|
|
if (mrActive) {
|
|
// Shift: MSB shifts out via Q7S, DS enters at LSB
|
|
shiftReg = ((shiftReg << 1) | (dsState ? 1 : 0)) & 0xFF;
|
|
}
|
|
}
|
|
prevShcp = state;
|
|
}));
|
|
}
|
|
|
|
// STCP — rising edge latches shift register to storage register
|
|
if (pinSTCP !== null) {
|
|
unsubscribers.push(pinManager.onPinChange(pinSTCP, (_: number, state: boolean) => {
|
|
if (state && !prevStcp) {
|
|
// Rising edge — latch
|
|
storageReg = shiftReg;
|
|
propagateOutputs();
|
|
}
|
|
prevStcp = state;
|
|
}));
|
|
}
|
|
|
|
// Initial state propagation
|
|
propagateOutputs();
|
|
|
|
return () => unsubscribers.forEach(u => u());
|
|
},
|
|
});
|
|
|
|
// ─── 7-segment display (direct-drive, when connected directly to Arduino) ────
|
|
|
|
PartSimulationRegistry.register('7segment', {
|
|
attachEvents: (element, simulator, getArduinoPinHelper) => {
|
|
const pinManager = (simulator as any).pinManager;
|
|
if (!pinManager) return () => {};
|
|
|
|
const unsubscribers: (() => void)[] = [];
|
|
|
|
const segments = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'DP'];
|
|
for (let i = 0; i < segments.length; i++) {
|
|
const seg = segments[i];
|
|
const arduinoPin = getArduinoPinHelper(seg);
|
|
if (arduinoPin !== null) {
|
|
unsubscribers.push(pinManager.onPinChange(arduinoPin, (_: number, state: boolean) => {
|
|
set7SegPin(element, seg, state);
|
|
}));
|
|
}
|
|
}
|
|
|
|
return () => unsubscribers.forEach(u => u());
|
|
},
|
|
});
|