94 lines
3.6 KiB
JavaScript
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]);
|
|
});
|
|
});
|