87 lines
3.1 KiB
TypeScript
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);
|
|
});
|
|
});
|