95 lines
3.5 KiB
TypeScript
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);
|
|
});
|
|
});
|