/** * Intel 8086 emulator chip — TDD spec. * * The 8086 is the most ambitious chip on this list: * - 16-bit data bus multiplexed with low 16 bits of address (AD0..AD15) * - High 4 address bits multiplexed with status (A16/S3..A19/S6) * - ALE pulse latches the address into an external 8282 each cycle * - 20-bit physical addresses from 16-bit segment + 16-bit offset * - Variable-length instructions (1–6 bytes, ModR/M decode) * - Min mode and Max mode (only Min mode tested here) * * These tests exercise ONLY the bus protocol and a handful of basic * instructions. Full ISA coverage is deferred until the chip * implementation reaches a known-good baseline. */ import { describe, it, expect } from 'vitest'; import { BoardHarness } from '../src/BoardHarness.js'; import { chipWasmExists, hex16 } from '../src/helpers.js'; const CHIP = '8086'; const skip = !chipWasmExists(CHIP); const CLOCK_HZ = 5_000_000; const CLOCK_NS = Math.round(1e9 / CLOCK_HZ); function fullPinMap() { const m = { ALE: 'ALE', RD: 'RD', WR: 'WR', MIO: 'MIO', DTR: 'DTR', DEN: 'DEN', HOLD: 'HOLD', HLDA: 'HLDA', INTR: 'INTR', NMI: 'NMI', INTA: 'INTA', RESET: 'RESET', READY: 'READY', TEST: 'TEST', CLK: 'CLK', MNMX: 'MNMX', // tied high externally for minimum mode BHE: 'BHE', VCC: 'VCC', GND: 'GND', }; // Multiplexed address/data bus (low 16 bits): AD0..AD15. for (let i = 0; i < 16; i++) m[`AD${i}`] = `AD${i}`; // High address bits (also multiplexed with status, but drive A16..A19 // for the test perspective). for (let i = 16; i < 20; i++) m[`A${i}`] = `A${i}`; return m; } describe('Intel 8086 chip (minimum mode)', () => { describe('pin contract', () => { it.skipIf(skip)('registers the 40-pin minimum-mode contract', async () => { const board = new BoardHarness(); await expect(board.addChip(CHIP, fullPinMap())).resolves.toBeDefined(); board.dispose(); }); }); describe('reset', () => { it.skipIf(skip)('first fetch is from physical address 0xFFFF0', async () => { // Real 8086 resets to CS=0xFFFF, IP=0x0000 → physical = 0xFFFF0. const board = new BoardHarness(); await board.addChip(CHIP, fullPinMap()); let firstAddr = null; board.watchNet('ALE', (high) => { if (high && firstAddr === null) { // ALE goes high in T1; capture the address on AD0..AD15 + A16..A19 let lo = 0, hi = 0; for (let i = 0; i < 16; i++) if (board.getNet(`AD${i}`)) lo |= (1 << i); for (let i = 16; i < 20; i++) if (board.getNet(`A${i}`)) hi |= (1 << (i - 16)); firstAddr = (hi << 16) | lo; } }); board.setNet('MNMX', true); board.setNet('READY', true); board.setNet('TEST', true); board.setNet('NMI', false); board.setNet('INTR', false); board.setNet('HOLD', false); board.setNet('RESET', true); board.advanceNanos(CLOCK_NS * 8); board.setNet('RESET', false); board.advanceNanos(CLOCK_NS * 50); expect(firstAddr).toBe(0xFFFF0); board.dispose(); }); }); describe('AD bus multiplexing', () => { it.skipIf(skip)('drives address on AD then switches direction in T2 of a read', async () => { // Conceptual test: during T1, AD0..AD15 are outputs carrying the // low 16 bits of address and ALE is high; during T2..T3 (read), // AD0..AD15 must become inputs. We can verify this by externally // driving AD0..AD15 high during T2 and confirming we see those // values come back into the chip (the chip should sample data, // not contend). // // Implementation deferred — needs a more careful clock-step // harness that knows about T-states. // (skipped intentionally for now) expect(skip).toBeDefined(); }); it.todo('asserts ALE high for one clock during T1 of every bus cycle'); it.todo('does not drive AD0..AD15 during T2 of a read cycle (chip releases bus)'); }); describe('basic instructions', () => { it.todo('MOV reg, imm16 loads 16-bit immediate'); it.todo('MOV [addr], AX writes a 16-bit word with BHE̅+A0 indicating word write'); it.todo('MOV AX, [addr] reads a 16-bit word'); it.todo('JMP near transfers IP within the current segment'); it.todo('CALL pushes return address (CS:IP) onto the stack'); }); describe('segment math', () => { it.todo('physical address = (segment << 4) + offset is wrapped at 1 MB'); it.todo('segment override prefix changes the default segment for one op'); }); describe('integration', () => { it.todo('runs a hand-built "hello world" via memory-mapped UART'); }); });