144 lines
5.2 KiB
JavaScript
144 lines
5.2 KiB
JavaScript
/**
|
|
* rom-32k chip — 32 KB read-only memory with 16-bit address and 8-bit data.
|
|
*
|
|
* Pin contract (28-pin DIP — modelled after the 27C256 EPROM):
|
|
* A0..A14 — input (15-bit address; high bit ignored when chip
|
|
* is mapped above 0x8000 internally)
|
|
* D0..D7 — output (data, only driven when CE̅=0 AND OE̅=0)
|
|
* CE̅ — input, active low (chip enable)
|
|
* OE̅ — input, active low (output enable; tristates D0..D7)
|
|
* VCC, GND — power
|
|
*
|
|
* Test image: when the .c source is compiled with
|
|
* #define ROM_TEST_IMAGE 1
|
|
* it embeds a known 16-byte program at offset 0:
|
|
* 00: 0x12 0x34 0x56 0x78 0x9A 0xBC 0xDE 0xF0
|
|
* 08: 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88
|
|
* everything else is 0xFF.
|
|
*
|
|
* Tests assume that test image; the C source must honor it.
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { BoardHarness } from '../src/BoardHarness.js';
|
|
import { chipWasmExists, hex8, hex16 } from '../src/helpers.js';
|
|
|
|
const CHIP = 'rom-32k';
|
|
const skip = !chipWasmExists(CHIP);
|
|
|
|
const TEST_IMAGE = [
|
|
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
|
|
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
|
|
];
|
|
|
|
function setAddr(board, addr) {
|
|
for (let i = 0; i < 15; i++) {
|
|
board.setNet(`A${i}`, ((addr >> 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;
|
|
}
|
|
|
|
describe(`${CHIP} chip`, () => {
|
|
let board;
|
|
beforeEach(() => { board = new BoardHarness(); });
|
|
afterEach(() => { board.dispose(); });
|
|
|
|
describe('pin contract', () => {
|
|
it.skipIf(skip)('registers all 25 logical pins (A0-A14, D0-D7, CE̅, OE̅)', async () => {
|
|
const pinMap = {
|
|
VCC: 'VCC', GND: 'GND', 'CE': 'CE', 'OE': 'OE',
|
|
};
|
|
for (let i = 0; i < 15; i++) pinMap[`A${i}`] = `A${i}`;
|
|
for (let i = 0; i < 8; i++) pinMap[`D${i}`] = `D${i}`;
|
|
// If chip_setup throws (missing pin) ChipInstance.create rejects.
|
|
await expect(board.addChip(CHIP, pinMap)).resolves.toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('read protocol', () => {
|
|
it.skipIf(skip)('drives D0..D7 with the byte at the asserted address', async () => {
|
|
const pinMap = { VCC: 'VCC', GND: 'GND', CE: 'CE', OE: 'OE' };
|
|
for (let i = 0; i < 15; i++) pinMap[`A${i}`] = `A${i}`;
|
|
for (let i = 0; i < 8; i++) pinMap[`D${i}`] = `D${i}`;
|
|
await board.addChip(CHIP, pinMap);
|
|
|
|
board.setNet('CE', false); // CE̅ asserted
|
|
board.setNet('OE', false); // OE̅ asserted
|
|
|
|
for (let addr = 0; addr < TEST_IMAGE.length; addr++) {
|
|
setAddr(board, addr);
|
|
board.advanceNanos(50);
|
|
expect(readData(board), `addr=${hex16(addr)}`).toBe(TEST_IMAGE[addr]);
|
|
}
|
|
});
|
|
|
|
it.skipIf(skip)('returns 0xFF for unprogrammed addresses', async () => {
|
|
const pinMap = { VCC: 'VCC', GND: 'GND', CE: 'CE', OE: 'OE' };
|
|
for (let i = 0; i < 15; i++) pinMap[`A${i}`] = `A${i}`;
|
|
for (let i = 0; i < 8; i++) pinMap[`D${i}`] = `D${i}`;
|
|
await board.addChip(CHIP, pinMap);
|
|
|
|
board.setNet('CE', false);
|
|
board.setNet('OE', false);
|
|
setAddr(board, 0x4000); // beyond TEST_IMAGE
|
|
board.advanceNanos(50);
|
|
expect(readData(board)).toBe(0xff);
|
|
});
|
|
|
|
it.skipIf(skip)('tristates D0..D7 when OE̅ is high', async () => {
|
|
const pinMap = { VCC: 'VCC', GND: 'GND', CE: 'CE', OE: 'OE' };
|
|
for (let i = 0; i < 15; i++) pinMap[`A${i}`] = `A${i}`;
|
|
for (let i = 0; i < 8; i++) pinMap[`D${i}`] = `D${i}`;
|
|
await board.addChip(CHIP, pinMap);
|
|
|
|
board.setNet('CE', false);
|
|
board.setNet('OE', true); // OE̅ deasserted
|
|
setAddr(board, 0);
|
|
board.advanceNanos(50);
|
|
// With OE̅ high, the chip must NOT drive its data pins. We check
|
|
// by externally driving D0..D7 high and verifying the chip
|
|
// doesn't fight us.
|
|
for (let i = 0; i < 8; i++) board.setNet(`D${i}`, true);
|
|
board.advanceNanos(50);
|
|
expect(readData(board)).toBe(0xff);
|
|
});
|
|
|
|
it.skipIf(skip)('does not drive D0..D7 when CE̅ is high', async () => {
|
|
const pinMap = { VCC: 'VCC', GND: 'GND', CE: 'CE', OE: 'OE' };
|
|
for (let i = 0; i < 15; i++) pinMap[`A${i}`] = `A${i}`;
|
|
for (let i = 0; i < 8; i++) pinMap[`D${i}`] = `D${i}`;
|
|
await board.addChip(CHIP, pinMap);
|
|
|
|
board.setNet('CE', true); // CE̅ deasserted
|
|
board.setNet('OE', false);
|
|
setAddr(board, 0);
|
|
board.advanceNanos(50);
|
|
for (let i = 0; i < 8; i++) board.setNet(`D${i}`, true);
|
|
board.advanceNanos(50);
|
|
expect(readData(board)).toBe(0xff);
|
|
});
|
|
});
|
|
|
|
describe('timing', () => {
|
|
it.skipIf(skip)('updates data within one clock period of an address change', async () => {
|
|
const pinMap = { VCC: 'VCC', GND: 'GND', CE: 'CE', OE: 'OE' };
|
|
for (let i = 0; i < 15; i++) pinMap[`A${i}`] = `A${i}`;
|
|
for (let i = 0; i < 8; i++) pinMap[`D${i}`] = `D${i}`;
|
|
await board.addChip(CHIP, pinMap);
|
|
|
|
board.setNet('CE', false);
|
|
board.setNet('OE', false);
|
|
|
|
setAddr(board, 0); board.advanceNanos(50);
|
|
expect(readData(board)).toBe(TEST_IMAGE[0]);
|
|
|
|
setAddr(board, 5); board.advanceNanos(50);
|
|
expect(readData(board)).toBe(TEST_IMAGE[5]);
|
|
});
|
|
});
|
|
});
|