144 lines
4.6 KiB
JavaScript
144 lines
4.6 KiB
JavaScript
/**
|
|
* ram-64k chip — 64 KB RAM with 16-bit address bus, 8-bit bidirectional
|
|
* data bus. Modelled after the HM62256 / 6264 family.
|
|
*
|
|
* Pin contract (28-pin DIP):
|
|
* A0..A15 — input (16-bit address)
|
|
* D0..D7 — bidirectional (input on write, output on read)
|
|
* CE̅ — input, active low
|
|
* OE̅ — input, active low (output enable for reads)
|
|
* WE̅ — input, active low (write enable; pulse low to write)
|
|
* VCC, GND
|
|
*
|
|
* RAM is volatile — starts zero-initialized at chip_setup, no embedded
|
|
* image, no persistence between simulation runs.
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { BoardHarness } from '../src/BoardHarness.js';
|
|
import { chipWasmExists, hex16 } from '../src/helpers.js';
|
|
|
|
const CHIP = 'ram-64k';
|
|
const skip = !chipWasmExists(CHIP);
|
|
|
|
function pinMap() {
|
|
const m = { VCC: 'VCC', GND: 'GND', CE: 'CE', OE: 'OE', WE: 'WE' };
|
|
for (let i = 0; i < 16; i++) m[`A${i}`] = `A${i}`;
|
|
for (let i = 0; i < 8; i++) m[`D${i}`] = `D${i}`;
|
|
return m;
|
|
}
|
|
|
|
function setAddr(board, addr) {
|
|
for (let i = 0; i < 16; i++) board.setNet(`A${i}`, ((addr >> i) & 1) === 1);
|
|
}
|
|
|
|
function setData(board, byte) {
|
|
for (let i = 0; i < 8; i++) board.setNet(`D${i}`, ((byte >> i) & 1) === 1);
|
|
}
|
|
|
|
function readData(board) {
|
|
let v = 0;
|
|
for (let i = 0; i < 8; i++) if (board.getNet(`D${i}`)) v |= (1 << i);
|
|
return v;
|
|
}
|
|
|
|
async function setupRam(board) {
|
|
await board.addChip(CHIP, pinMap());
|
|
board.setNet('CE', false);
|
|
board.setNet('OE', true); // start with OE̅ deasserted
|
|
board.setNet('WE', true); // start with WE̅ deasserted
|
|
}
|
|
|
|
/** Drive a write cycle: address + data on bus, pulse WE̅ low then high. */
|
|
function writeCycle(board, addr, data) {
|
|
setAddr(board, addr);
|
|
setData(board, data);
|
|
board.advanceNanos(20);
|
|
board.setNet('WE', false);
|
|
board.advanceNanos(20);
|
|
board.setNet('WE', true); // rising edge latches
|
|
board.advanceNanos(20);
|
|
}
|
|
|
|
/** Drive a read cycle: address on bus, OE̅ low, sample. */
|
|
function readCycle(board, addr) {
|
|
setAddr(board, addr);
|
|
board.setNet('OE', false);
|
|
board.advanceNanos(50);
|
|
const v = readData(board);
|
|
board.setNet('OE', true);
|
|
return v;
|
|
}
|
|
|
|
describe(`${CHIP} chip`, () => {
|
|
let board;
|
|
beforeEach(() => { board = new BoardHarness(); });
|
|
afterEach(() => { board.dispose(); });
|
|
|
|
describe('pin contract', () => {
|
|
it.skipIf(skip)('registers all logical pins', async () => {
|
|
await expect(board.addChip(CHIP, pinMap())).resolves.toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('write then read', () => {
|
|
it.skipIf(skip)('round-trips a single byte at low address', async () => {
|
|
await setupRam(board);
|
|
writeCycle(board, 0x0000, 0xA5);
|
|
expect(readCycle(board, 0x0000)).toBe(0xA5);
|
|
});
|
|
|
|
it.skipIf(skip)('round-trips bytes at scattered addresses', async () => {
|
|
await setupRam(board);
|
|
const samples = [
|
|
[0x0000, 0x11], [0x00FF, 0x22], [0x0100, 0x33], [0x1234, 0x44],
|
|
[0x8000, 0x55], [0xFFFE, 0x66], [0xFFFF, 0x77],
|
|
];
|
|
for (const [a, d] of samples) writeCycle(board, a, d);
|
|
for (const [a, d] of samples) {
|
|
expect(readCycle(board, a), `addr=${hex16(a)}`).toBe(d);
|
|
}
|
|
});
|
|
|
|
it.skipIf(skip)('writes to one address do not corrupt neighbours', async () => {
|
|
await setupRam(board);
|
|
writeCycle(board, 0x4000, 0xAB);
|
|
writeCycle(board, 0x4001, 0xCD);
|
|
writeCycle(board, 0x4002, 0xEF);
|
|
writeCycle(board, 0x4001, 0x99);
|
|
expect(readCycle(board, 0x4000)).toBe(0xAB);
|
|
expect(readCycle(board, 0x4001)).toBe(0x99);
|
|
expect(readCycle(board, 0x4002)).toBe(0xEF);
|
|
});
|
|
});
|
|
|
|
describe('blank state', () => {
|
|
it.skipIf(skip)('reads 0x00 from never-written addresses', async () => {
|
|
await setupRam(board);
|
|
expect(readCycle(board, 0x1234)).toBe(0x00);
|
|
});
|
|
});
|
|
|
|
describe('control signals', () => {
|
|
it.skipIf(skip)('does not write when CE̅ is high', async () => {
|
|
await setupRam(board);
|
|
// Try to write with CE̅ high
|
|
board.setNet('CE', true);
|
|
writeCycle(board, 0x0000, 0xFF);
|
|
board.setNet('CE', false);
|
|
expect(readCycle(board, 0x0000)).toBe(0x00);
|
|
});
|
|
|
|
it.skipIf(skip)('does not drive data when OE̅ is high', async () => {
|
|
await setupRam(board);
|
|
writeCycle(board, 0x0000, 0xA5);
|
|
// Read with OE̅ high — chip should leave bus alone
|
|
setAddr(board, 0x0000);
|
|
board.setNet('OE', true);
|
|
// Externally drive bus high; chip must not contend
|
|
for (let i = 0; i < 8; i++) board.setNet(`D${i}`, true);
|
|
board.advanceNanos(50);
|
|
expect(readData(board)).toBe(0xff);
|
|
});
|
|
});
|
|
});
|