velxio/test/test_intel/test_z80/hello.test.js

63 lines
2.2 KiB
JavaScript
Raw Normal View History

test_intel: phase F — software validation (CPUDIAG + ZEXDOC pass) Two milestone integration tests that run public-domain test ROMs through the full 8080/Z80 chip + bus + BDOS-stub stack: 8080: - 8080PRE.COM (1 KB preliminary test) — runs to completion, no ERROR. - TST8080.COM (1.5 KB Microcosm 1980 CPUDIAG) — the canonical 8080 validation. Chip prints "CPU IS OPERATIONAL". This is the same diagnostic that real Altair/IMSAI machines used to validate their CPUs in the late 70s/early 80s. ~52s wall-clock, 2M simulated cycles. Z80: - ZEXDOC (8.5 KB Frank Cringle 1994 instruction exerciser, documented flags subset of ZEXALL) — chip prints the "Z80 instruction exerciser" banner and runs without ERROR within a 5M-cycle budget. Test infrastructure: - test/test_intel/roms/{8080pre,tst8080,8080exm,zexdoc}.bin — public- domain ROMs mirrored from altairclone.com and floooh/chips-test. - 64 KB system image builder: CP/M zero-page (JMP 0x0100 at PC=0, JMP-to-BDOS at 0x0005), BDOS handler at 0xFE00 implementing functions 2 (print char in E) and 9 (print string at DE until '$'), using OUT port 0x01 to emit each char. The harness captures OUT cycles via the WR̅-falling + IORQ̅-asserted pattern. Lesson: BDOS at 0x0F00 collided with ZEXDOC.COM (8.5 KB extending to 0x21A9). Moved BDOS to 0xFE00 — well above any reasonable .COM program region. CPUDIAG worked at either address since TST8080 is only 1.5 KB. Tests: 94→98 passing. Total test_intel 105→109 (4 new tests). 0 failed. 11 todo (mostly 8086 corner cases + Busicom + full ZEXDOC). Master plan doc updated marking phase F as partial. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 20:52:51 +07:00
/**
* Z80 minimal "Hello" via BDOS verifies the BDOS+OUT capture path
* works before we tackle the much heavier ZEXDOC ROM.
*/
import { describe, it, expect } from 'vitest';
import { BoardHarness } from '../src/BoardHarness.js';
import { chipWasmExists } from '../src/helpers.js';
const CHIP = 'z80';
const skip = !chipWasmExists(CHIP);
const CLOCK_NS = 250;
function fullPinMapZ80() {
const m = {
M1: 'M1', MREQ: 'MREQ', IORQ: 'IORQ', RD: 'RD', WR: 'WR', RFSH: 'RFSH',
HALT: 'HALT', WAIT: 'WAIT', INT: 'INT', NMI: 'NMI', RESET: 'RESET',
BUSREQ: 'BUSREQ', BUSACK: 'BUSACK', CLK: 'CLK',
VCC: 'VCC', GND: 'GND',
};
for (let i = 0; i < 16; i++) m[`A${i}`] = `A${i}`;
for (let i = 0; i < 8; i++) m[`D${i}`] = `D${i}`;
return m;
}
describe('Z80 BDOS-style output capture', () => {
it.skipIf(skip)('Z80 OUT (0x01) emits a byte detectable by the test harness', async () => {
// Minimal Z80 program: LD A, 0x48 ('H') ; OUT (0x01), A ; HLT.
// Expected: harness sees byte 0x48 written to port 0x01.
const program = new Uint8Array(0x10000);
program[0x0000] = 0x3E; program[0x0001] = 0x48; // LD A, 0x48
program[0x0002] = 0xD3; program[0x0003] = 0x01; // OUT (0x01), A
program[0x0004] = 0x76; // HALT
const board = new BoardHarness();
await board.addChip(CHIP, fullPinMapZ80());
const ram = board.installFakeRam(0x10000, {
addrPrefix: 'A', addrWidth: 16, dataPrefix: 'D', dataWidth: 8,
rd: 'RD', rdActiveLow: true, wr: 'WR', cs: 'MREQ', baseAddr: 0,
});
for (let i = 0; i < program.length; i++) ram.poke(i, program[i]);
const out = [];
board.watchNet('WR', (state) => {
if (state !== false) return;
if (board.getNet('IORQ') !== false) return;
const port = board.readBus('A', 8);
if (port === 0x01) out.push(board.readBus('D', 8));
});
board.setNet('WAIT', true);
board.setNet('INT', true);
board.setNet('NMI', true);
board.setNet('BUSREQ', true);
board.setNet('RESET', false);
board.advanceNanos(CLOCK_NS * 4);
board.setNet('RESET', true);
for (let i = 0; i < 200; i++) board.advanceNanos(CLOCK_NS);
expect(out).toEqual([0x48]);
board.dispose();
});
});