122 lines
4.6 KiB
JavaScript
122 lines
4.6 KiB
JavaScript
/**
|
||
* 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');
|
||
});
|
||
});
|