velxio/frontend/src/simulation/customChips/busNets.ts

88 lines
3.1 KiB
TypeScript

/**
* Multi-driver bus net registry — Phase 1 of the multi-chip digital bus track
* (project/multichip-bus/). Sits between the chip runtime and the PinManager
* for chip-to-chip BUS net keys (syntheticNetPin) only.
*
* Phase 0 gave every endpoint of a chip-to-chip net one shared PinManager key,
* but PinManager is last-writer-wins — wrong for a bus where several chips can
* drive one data line and the rest release it (Hi-Z). This registry tracks each
* chip pin's (value, strength) contribution per net, resolves the net with the
* 4-valued rules (busLogic.resolveNet), and pushes the RESOLVED level into the
* PinManager so readers see the bus, not the last writer. Contention (two strong
* drivers disagree -> X) is surfaced as a console warning, like Proteus.
*
* Only BUS net keys flow through here; board pins and single-chip-to-component
* synthetic pins keep the legacy direct PinManager path untouched.
*/
import { resolveNet, resolvedToBool, type Drive } from './busLogic';
import { publishNetLevel, resetBusKernel } from './busKernel';
interface PinManagerLike {
triggerPinChange(pin: number, state: boolean, source?: 'mcu' | 'external'): void;
}
// netKey -> (driverId -> Drive). driverId = `${componentId}::${pinName}`.
const nets = new Map<number, Map<string, Drive>>();
// Nets currently flagged as in contention — so we warn once per onset, not per
// re-resolve, and clear when the contention is gone.
const inContention = new Set<number>();
function recompute(pm: PinManagerLike, netKey: number): void {
const drivers = nets.get(netKey);
const resolved = resolveNet(drivers ? drivers.values() : []);
if (resolved.v === 'X') {
if (!inContention.has(netKey)) {
inContention.add(netKey);
console.warn(
`[chipbus] bus contention on net ${netKey}: two strong drivers disagree (resolved X)`,
);
}
} else {
inContention.delete(netKey);
}
// PinManager is boolean; push the projected level (only a driven 1 is high)
// through the settle kernel so the change propagates as a bounded delta-cycle
// pass rather than a recursive cascade.
publishNetLevel(pm, netKey, resolvedToBool(resolved));
}
/** Set (or replace) one chip pin's contribution to a bus net and re-resolve. */
export function setBusDrive(
pm: PinManagerLike,
netKey: number,
driverId: string,
drive: Drive,
): void {
let m = nets.get(netKey);
if (!m) {
m = new Map();
nets.set(netKey, m);
}
m.set(driverId, drive);
recompute(pm, netKey);
}
/** Remove every driver a chip contributes (on dispose) and re-resolve its nets. */
export function clearBusDriversForChip(pm: PinManagerLike, componentId: string): void {
const prefix = `${componentId}::`;
for (const [netKey, m] of nets) {
let changed = false;
for (const id of [...m.keys()]) {
if (id.startsWith(prefix)) {
m.delete(id);
changed = true;
}
}
if (changed) recompute(pm, netKey);
}
}
/** Test seam: wipe all bus-net driver state (and the settle kernel). */
export function resetBusNets(): void {
nets.clear();
inContention.clear();
resetBusKernel();
}