velxio/test/test_intel/test_buses/4001-rom.test.js

94 lines
3.6 KiB
JavaScript

/**
* Intel 4001 ROM — integration test.
*
* The interesting part of the 4001 is that it cooperates with a real
* 4004 over the 8-phase nibble-multiplexed bus. So this test wires
* BOTH chips together and verifies the 4004 actually fetches and
* executes opcodes from the 4001 — a true end-to-end integration that
* couldn't be tested by either chip in isolation.
*
* Setup:
* - 4001 baked with a known 16-byte program at offsets 0..15.
* Byte 0 = 0x00 (NOP). The chip's rom_image only has bytes 0..15
* set; rest is zero.
* - 4004 wired to the 4001 (D bus, SYNC, RESET, CM-ROM).
* - We register the 4001 BEFORE the 4004 so its timer fires first
* per advanceNanos — the 4001 drives D before the 4004 reads.
* - Run a few cycles, observe that the 4004 advances PC normally
* (NOPs walk through addresses 0,1,2,...).
*
* The default chip-id (compile-time ROM4001_CHIP_ID = 0) means the
* 4001 responds when address bits 11..8 are 0 — i.e. for the first
* 256 bytes of program memory.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { BoardHarness } from '../src/BoardHarness.js';
import { chipWasmExists } from '../src/helpers.js';
const ROM = '4001-rom';
const CPU = '4004';
const skip = !chipWasmExists(ROM) || !chipWasmExists(CPU);
const CLOCK_NS = 1351;
describe('4001 ROM + 4004 integration', () => {
let board;
beforeEach(() => { board = new BoardHarness(); });
afterEach(() => { board.dispose(); });
it.skipIf(skip)('4001 responds to chip-id 0 and the 4004 fetches its bytes', async () => {
// Register the 4001 FIRST so its timer fires before the 4004's
// timer in each advanceNanos call.
await board.addChip(ROM, {
VDD: 'VDD', VSS: 'VSS', SYNC: 'SYNC', CL: 'CLK1',
RESET: 'RESET', CM: 'CMROM',
D0: 'D0', D1: 'D1', D2: 'D2', D3: 'D3',
IO0: 'I0', IO1: 'I1', IO2: 'I2', IO3: 'I3',
});
await board.addChip(CPU, {
SYNC: 'SYNC', RESET: 'RESET', TEST: 'TEST',
CMROM: 'CMROM',
CMRAM0: 'CMRAM0', CMRAM1: 'CMRAM1', CMRAM2: 'CMRAM2', CMRAM3: 'CMRAM3',
CLK1: 'CLK1', CLK2: 'CLK2',
VDD: 'VDD', VSS: 'VSS',
D0: 'D0', D1: 'D1', D2: 'D2', D3: 'D3',
});
// Quiet inputs.
board.setNet('TEST', false);
// Pulse RESET high then low.
board.setNet('RESET', true);
board.advanceNanos(CLOCK_NS * 12);
board.setNet('RESET', false);
// Watch SYNC + capture the 4-bit data on D pins right after each
// SYNC rising pulse — this is what the 4001 drives during M1/M2.
// Specifically we want to confirm that during M1 of cycle 0, the
// 4001 drove the high nibble of rom[0] = 0x00, and that the chip
// advances PC normally.
const fetchedPCs = [];
let phaseSinceSync = -1;
board.watchNet('SYNC', (high) => { if (high) phaseSinceSync = 0; });
// Run 3 cycles (24 phases). At each A3 (phase 2 since SYNC), read
// the 12-bit PC the 4004 drove on D0..D3 across A1/A2/A3.
let pcLow = 0, pcMid = 0, pcHigh = 0;
for (let i = 0; i < 24; i++) {
board.advanceNanos(CLOCK_NS);
if (phaseSinceSync === 0) pcLow = board.readBus('D', 4);
else if (phaseSinceSync === 1) pcMid = board.readBus('D', 4);
else if (phaseSinceSync === 2) {
pcHigh = board.readBus('D', 4);
fetchedPCs.push(pcLow | (pcMid << 4) | (pcHigh << 8));
}
if (phaseSinceSync >= 0) phaseSinceSync++;
}
// After 3 cycles: PC should walk 0, 1, 2 (NOP advances by 1).
// Note: the 4001's ROM image has byte 0 = 0x00 (NOP), so the 4004
// reads NOP and increments PC.
expect(fetchedPCs.slice(0, 3)).toEqual([0x000, 0x001, 0x002]);
});
});