116 lines
3.6 KiB
JavaScript
116 lines
3.6 KiB
JavaScript
/**
|
|
* Intel 8282 octal latch — TDD spec.
|
|
*
|
|
* The 8282 is the canonical address latch used on 8086 minimum-mode
|
|
* boards to demultiplex AD0..AD15 → A0..A15. ALE from the 8086 drives
|
|
* STB; when ALE pulses high the latch becomes transparent, when ALE
|
|
* falls the address is latched and held while AD becomes the data bus.
|
|
*
|
|
* 20-pin DIP behaviour (per Intel 8282/8283 datasheet):
|
|
* STB=1, OE̅=0 → DOn = DIn (transparent)
|
|
* STB falling, OE̅=0 → DOn = DIn at the moment STB went 0→1→0
|
|
* (latched, held while STB=0)
|
|
* OE̅=1 → DO pins float (we model by switching to VX_INPUT)
|
|
*
|
|
* The 8283 is the inverting variant (DOn = ~DIn). We implement the
|
|
* non-inverting 8282 only.
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { BoardHarness } from '../src/BoardHarness.js';
|
|
import { chipWasmExists } from '../src/helpers.js';
|
|
|
|
const CHIP = 'latch-8282';
|
|
const skip = !chipWasmExists(CHIP);
|
|
|
|
function pinMap() {
|
|
const m = { STB: 'STB', OE: 'OE', VCC: 'VCC', GND: 'GND' };
|
|
for (let i = 0; i < 8; i++) {
|
|
m[`DI${i}`] = `DI${i}`;
|
|
m[`DO${i}`] = `DO${i}`;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
function setDI(board, byte) {
|
|
for (let i = 0; i < 8; i++) board.setNet(`DI${i}`, ((byte >> i) & 1) === 1);
|
|
}
|
|
|
|
function readDO(board) {
|
|
let v = 0;
|
|
for (let i = 0; i < 8; i++) if (board.getNet(`DO${i}`)) v |= (1 << i);
|
|
return v;
|
|
}
|
|
|
|
describe(`${CHIP} chip`, () => {
|
|
let board;
|
|
beforeEach(() => { board = new BoardHarness(); });
|
|
afterEach(() => { board.dispose(); });
|
|
|
|
describe('pin contract', () => {
|
|
it.skipIf(skip)('registers all 20 logical pins', async () => {
|
|
await expect(board.addChip(CHIP, pinMap())).resolves.toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('transparent mode', () => {
|
|
it.skipIf(skip)('DO follows DI while STB=1 and OE̅=0', async () => {
|
|
await board.addChip(CHIP, pinMap());
|
|
board.setNet('OE', false);
|
|
board.setNet('STB', true);
|
|
|
|
setDI(board, 0xA5);
|
|
board.advanceNanos(20);
|
|
expect(readDO(board)).toBe(0xA5);
|
|
|
|
setDI(board, 0x3C);
|
|
board.advanceNanos(20);
|
|
expect(readDO(board)).toBe(0x3C);
|
|
});
|
|
});
|
|
|
|
describe('latch mode', () => {
|
|
it.skipIf(skip)('holds DI value at the moment STB falls', async () => {
|
|
// Real 8282 is "transparent while STB=1, latched when STB=0".
|
|
// The latched value is whatever DI was at the falling edge.
|
|
await board.addChip(CHIP, pinMap());
|
|
board.setNet('OE', false);
|
|
board.setNet('STB', true);
|
|
|
|
setDI(board, 0x77);
|
|
board.advanceNanos(20);
|
|
// Drop STB → freeze
|
|
board.setNet('STB', false);
|
|
board.advanceNanos(20);
|
|
expect(readDO(board)).toBe(0x77);
|
|
|
|
// Change DI; DO should NOT change.
|
|
setDI(board, 0xFF);
|
|
board.advanceNanos(20);
|
|
expect(readDO(board)).toBe(0x77);
|
|
|
|
// Raise STB → transparent again
|
|
board.setNet('STB', true);
|
|
board.advanceNanos(20);
|
|
expect(readDO(board)).toBe(0xFF);
|
|
});
|
|
});
|
|
|
|
describe('output enable', () => {
|
|
it.skipIf(skip)('does not drive DO pins when OE̅ is high', async () => {
|
|
await board.addChip(CHIP, pinMap());
|
|
board.setNet('STB', true);
|
|
setDI(board, 0xAA);
|
|
board.advanceNanos(20);
|
|
expect(readDO(board)).toBe(0xAA);
|
|
|
|
// OE̅ high → 8282 should release outputs. Externally drive DO
|
|
// pins high; 8282 must not pull them back down.
|
|
board.setNet('OE', true);
|
|
board.advanceNanos(20);
|
|
for (let i = 0; i < 8; i++) board.setNet(`DO${i}`, true);
|
|
board.advanceNanos(20);
|
|
expect(readDO(board)).toBe(0xff);
|
|
});
|
|
});
|
|
});
|