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

87 lines
3.1 KiB
TypeScript

/**
* Phase 2 — synchronous settle kernel (project/multichip-bus/). Drives the
* delta-cycle settle loop with a real PinManager and listeners standing in for
* chips (a chip = a watcher that, on its input net, drives an output net — the
* same shape busNets+ChipRuntime produce). Covers: multi-hop settle to a fixed
* point, settle-before-read, no deep recursion on long chains, and the
* oscillation cap.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { PinManager } from '../simulation/PinManager';
import { publishNetLevel, resetBusKernel } from '../simulation/customChips/busKernel';
describe('busKernel — delta-cycle settle', () => {
let pm: PinManager;
beforeEach(() => {
resetBusKernel();
pm = new PinManager();
});
afterEach(() => {
resetBusKernel();
vi.restoreAllMocks();
});
it('settles a multi-hop combinational chain to its fixed point', () => {
const A = 1000;
const B = 1001;
const C = 1002;
// "chip" 1: B follows A. "chip" 2: C = NOT B.
pm.onPinChange(A, (_p, v) => publishNetLevel(pm, B, v));
pm.onPinChange(B, (_p, v) => publishNetLevel(pm, C, !v));
publishNetLevel(pm, A, true);
expect(pm.getPinState(A)).toBe(true);
expect(pm.getPinState(B)).toBe(true);
expect(pm.getPinState(C)).toBe(false);
});
it('settle-before-read: a driven net is settled by the time the publish returns', () => {
const ADDR = 2000;
const DATA = 2001;
// "memory": DATA mirrors ADDR (combinational). Models a ROM driving the data
// bus in reaction to the address/strobe within the same bus cycle.
pm.onPinChange(ADDR, (_p, v) => publishNetLevel(pm, DATA, v));
publishNetLevel(pm, ADDR, true);
// A synchronous in-cycle read here (as a CPU chip would do) sees settled data.
expect(pm.getPinState(DATA)).toBe(true);
});
it('handles a very long chain without recursing (no stack overflow)', () => {
const N = 5000;
for (let i = 0; i < N; i++) {
const from = 3000 + i;
const to = 3000 + i + 1;
pm.onPinChange(from, (_p, v) => publishNetLevel(pm, to, v));
}
publishNetLevel(pm, 3000, true);
expect(pm.getPinState(3000 + N)).toBe(true); // value walked the whole chain
});
it('caps a zero-delay oscillation and warns instead of hanging', () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
const OSC = 4000;
// A ring oscillator: every settle flips the net, which re-triggers forever.
pm.onPinChange(OSC, (_p, v) => publishNetLevel(pm, OSC, !v));
publishNetLevel(pm, OSC, true); // must RETURN (cap trips), not hang
expect(warn).toHaveBeenCalledTimes(1);
expect(String(warn.mock.calls[0][0])).toContain('did not converge');
});
it('coalesces multiple drives of one net within a delta to the latest', () => {
const N = 5000;
let fires = 0;
pm.onPinChange(N, () => {
fires++;
});
// Same net published twice before any settle delta applies it: the watcher
// should see exactly one (latest) value, not two.
publishNetLevel(pm, N, true);
expect(pm.getPinState(N)).toBe(true);
expect(fires).toBe(1);
});
});