velxio/frontend/src/__tests__/chipbus-busnets.test.ts

95 lines
3.5 KiB
TypeScript

/**
* Phase 1 — multi-driver bus net registry (project/multichip-bus/). Drives the
* busNets registry against a real PinManager: tri-state release, pull-up
* fallback, contention warning, and per-chip driver teardown.
*/
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { PinManager } from '../simulation/PinManager';
import {
setBusDrive,
clearBusDriversForChip,
resetBusNets,
} from '../simulation/customChips/busNets';
import { Strength, HIGHZ_DRIVE, type Drive } from '../simulation/customChips/busLogic';
const NET = 100500; // a stand-in syntheticNetPin key
const strong = (v: 0 | 1): Drive => ({ value: v, strength: Strength.STRONG });
const pull = (v: 0 | 1): Drive => ({ value: v, strength: Strength.PULL });
describe('busNets — multi-driver resolution into PinManager', () => {
let pm: PinManager;
beforeEach(() => {
resetBusNets();
pm = new PinManager();
});
afterEach(() => {
resetBusNets();
vi.restoreAllMocks();
});
it('a single strong driver sets the net level', () => {
setBusDrive(pm, NET, 'cpu::D0', strong(1));
expect(pm.getPinState(NET)).toBe(true);
setBusDrive(pm, NET, 'cpu::D0', strong(0));
expect(pm.getPinState(NET)).toBe(false);
});
it('tri-state hand-off: the releasing driver yields to the active one', () => {
setBusDrive(pm, NET, 'rom::D0', strong(1)); // ROM drives 1
setBusDrive(pm, NET, 'ram::D0', HIGHZ_DRIVE); // RAM released
expect(pm.getPinState(NET)).toBe(true);
// ROM releases, RAM drives 0 -> bus follows RAM.
setBusDrive(pm, NET, 'rom::D0', HIGHZ_DRIVE);
setBusDrive(pm, NET, 'ram::D0', strong(0));
expect(pm.getPinState(NET)).toBe(false);
});
it('a pull-up holds the bus high only while every driver is released', () => {
setBusDrive(pm, NET, 'pullup::D0', pull(1));
setBusDrive(pm, NET, 'drv::D0', HIGHZ_DRIVE);
expect(pm.getPinState(NET)).toBe(true); // floats up to the pull-up
setBusDrive(pm, NET, 'drv::D0', strong(0)); // a strong driver beats the pull
expect(pm.getPinState(NET)).toBe(false);
setBusDrive(pm, NET, 'drv::D0', HIGHZ_DRIVE); // released again -> pull-up wins
expect(pm.getPinState(NET)).toBe(true);
});
it('contention (two strong, opposite) warns once and resolves low (X)', () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
setBusDrive(pm, NET, 'a::D0', strong(0));
setBusDrive(pm, NET, 'b::D0', strong(1)); // now in contention
expect(warn).toHaveBeenCalledTimes(1);
expect(pm.getPinState(NET)).toBe(false); // X projects to low
// Re-resolving while still in contention does not warn again.
setBusDrive(pm, NET, 'a::D0', strong(0));
expect(warn).toHaveBeenCalledTimes(1);
// Clearing the contention and re-entering it warns afresh.
setBusDrive(pm, NET, 'b::D0', HIGHZ_DRIVE);
setBusDrive(pm, NET, 'b::D0', strong(1));
expect(warn).toHaveBeenCalledTimes(2);
});
it('clearBusDriversForChip releases that chip across every net', () => {
setBusDrive(pm, NET, 'rom::D0', strong(1));
setBusDrive(pm, NET, 'ram::D0', HIGHZ_DRIVE);
expect(pm.getPinState(NET)).toBe(true);
clearBusDriversForChip(pm, 'rom'); // ROM removed -> only RAM's Hi-Z left
expect(pm.getPinState(NET)).toBe(false); // floats (Z) -> low
});
it('a reader notified via onPinChange sees the resolved level synchronously', () => {
let seen: boolean | null = null;
pm.onPinChange(NET, (_p, s) => {
seen = s;
});
setBusDrive(pm, NET, 'drv::D0', strong(1));
expect(seen).toBe(true);
});
});