diff --git a/test/test_intel/test_buses/latch-8282.c b/test/test_intel/test_buses/latch-8282.c new file mode 100644 index 00000000..83dc2263 --- /dev/null +++ b/test/test_intel/test_buses/latch-8282.c @@ -0,0 +1,110 @@ +/* + * Intel 8282 octal latch — clean-room implementation as a velxio + * custom chip. + * + * Source: Intel 8282/8283 datasheet (2-page short form, public). + * Used to demultiplex AD0..AD7 (or AD8..AD15) on 8086 minimum-mode + * boards under control of ALE. + * + * Behaviour: + * STB=1, OE̅=0 → transparent: DOn tracks DIn + * STB 1→0 → latch: hold DOn at DIn captured during STB=1 + * OE̅=1 → release DO pins (high-Z; we model as VX_INPUT) + * + * Implementation: pin watches on DI0..7 + STB + OE̅. On any change, + * recompute outputs: + * - If OE̅=1: release DO pins. + * - Else if STB=1: drive DOn = DIn (transparent). + * - Else: drive DOn from the latched register (set at last STB=1). + * + * The 8283 (inverting variant) is NOT implemented here — would just + * be the same logic with DOn = ~DIn. + */ +#include "velxio-chip.h" +#include +#include + +typedef struct { + vx_pin di[8]; + vx_pin dout[8]; + vx_pin stb; + vx_pin oe; + vx_pin vcc, gnd; + uint8_t latched; /* held value when STB is low */ + bool driving; +} chip_t; + +static chip_t G; + +static uint8_t read_di(void) { + uint8_t v = 0; + for (int i = 0; i < 8; i++) if (vx_pin_read(G.di[i])) v |= (1u << i); + return v; +} + +static void drive_do(uint8_t v) { + for (int i = 0; i < 8; i++) { + vx_pin_set_mode(G.dout[i], VX_OUTPUT); + vx_pin_write(G.dout[i], (v >> i) & 1); + } + G.driving = true; +} + +static void release_do(void) { + if (!G.driving) return; + for (int i = 0; i < 8; i++) vx_pin_set_mode(G.dout[i], VX_INPUT); + G.driving = false; +} + +static void update(void) { + int oe_high = vx_pin_read(G.oe); + int stb_high = vx_pin_read(G.stb); + + if (oe_high) { + release_do(); + return; + } + + if (stb_high) { + /* Transparent: latched value tracks DI continuously while STB + is high, AND we drive that value on DO. */ + G.latched = read_di(); + drive_do(G.latched); + } else { + /* Latched: DO holds whatever was last captured. */ + drive_do(G.latched); + } +} + +static void on_pin_change(void* user_data, vx_pin pin, int value) { + (void)user_data; (void)pin; (void)value; + update(); +} + +void chip_setup(void) { + char name[5]; + + for (int i = 0; i < 8; i++) { + name[0]='D'; name[1]='I'; name[2]='0'+i; name[3]=0; + G.di[i] = vx_pin_register(name, VX_INPUT); + } + for (int i = 0; i < 8; i++) { + name[0]='D'; name[1]='O'; name[2]='0'+i; name[3]=0; + G.dout[i] = vx_pin_register(name, VX_INPUT); + } + G.stb = vx_pin_register("STB", VX_INPUT); + G.oe = vx_pin_register("OE", VX_INPUT); + G.vcc = vx_pin_register("VCC", VX_INPUT); + G.gnd = vx_pin_register("GND", VX_INPUT); + + G.latched = 0; + G.driving = false; + + for (int i = 0; i < 8; i++) { + vx_pin_watch(G.di[i], VX_EDGE_BOTH, on_pin_change, 0); + } + vx_pin_watch(G.stb, VX_EDGE_BOTH, on_pin_change, 0); + vx_pin_watch(G.oe, VX_EDGE_BOTH, on_pin_change, 0); + + update(); +} diff --git a/test/test_intel/test_buses/latch-8282.test.js b/test/test_intel/test_buses/latch-8282.test.js new file mode 100644 index 00000000..1809254a --- /dev/null +++ b/test/test_intel/test_buses/latch-8282.test.js @@ -0,0 +1,115 @@ +/** + * 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); + }); + }); +});