test_intel: Intel 8282 octal latch
Companion chip for 8086 minimum-mode boards that demultiplexes AD0..AD15 → A0..A15 under control of ALE. ~80 LOC clean-room from the public Intel 8282/8283 datasheet. Pin contract (20-pin DIP): DI0..7 in, DO0..7 out, STB strobe, OE̅ output enable, VCC, GND. Behaviour: STB=1, OE̅=0 → DO follows DI (transparent) STB falling → latch held while STB=0 OE̅=1 → DO pins released (modelled as VX_INPUT) Tests: 4/4 passing (pin contract, transparent mode, latch hold, output enable). Brings test_intel total to 47 passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ef812c3d9b
commit
4a2fa68e51
|
|
@ -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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue