velxio/test/test_intel/test_8086/8086.test.js

122 lines
4.6 KiB
JavaScript
Raw Normal View History

/**
* 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 (16 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');
});
});