feat(chipbus): Galaksija video display chip - full computer renders READY
galaksija-display.c: a 32x16 text video chip that renders the Galaksija video RAM. It is a passive bus snoop -- watches WR + address + data, and on a write into the 0x2800 video region stores the character and renders that cell into a 256x128 framebuffer using the public-domain CHRGEN font (code*8, bit 0 = lit). It never drives the bus. The host blits the framebuffer to the chip canvas (vx_framebuffer_init / vx_buffer_write). Two tests: - chipbus-galaksija-display: snoop+render smoke test (a write of 'R' to 0x2802 lights its cell; unwritten cells stay blank). - chipbus-galaksija-computer: the COMPLETE machine over the chip-to-chip bus (Z80 + galaksija-rom + ram-64k + inverter decode + galaksija-display) boots the public-domain ROM and renders the monitor's "READY" prompt on screen. 40 chipbus tests across 10 files pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8441d370d3
commit
e3d21cd6fc
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Phase 3 — the COMPLETE Galaksija computer renders "READY" on screen
|
||||
* (project/multichip-bus/). The full machine over the chip-to-chip bus:
|
||||
* Z80 + galaksija-rom (0x0000-0x1FFF) + ram-64k (0x2000-0x3FFF) +
|
||||
* inverter (address decode) + galaksija-display (snoops video RAM 0x2800).
|
||||
*
|
||||
* Boot the public-domain ROM; the monitor clears the 32x16 screen and writes
|
||||
* its "READY" prompt to video RAM at 0x2802. The display chip snoops those bus
|
||||
* writes and renders them with the CHRGEN font. We assert the on-screen "READY"
|
||||
* cells (row 0, cols 2..6) light up — i.e. a real 1983 home computer boots AND
|
||||
* draws its prompt, fully through the simulated bus.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { PinManager } from '../simulation/PinManager';
|
||||
import { ChipInstance } from '../simulation/customChips/ChipRuntime';
|
||||
import {
|
||||
resolveChipNetKey, setChipBusEnabledForTest, resetChipNetIndexForTest, type ChipNetState,
|
||||
} from '../simulation/customChips/chipNets';
|
||||
import { syntheticChipPin } from '../simulation/customChips/syntheticPins';
|
||||
import { resetBusNets } from '../simulation/customChips/busNets';
|
||||
|
||||
const f = (n: string) => fileURLToPath(new URL(`./fixtures/chipbus/${n}`, import.meta.url));
|
||||
const paths = { z80: f('z80.wasm'), rom: f('galaksija-rom.wasm'), ram: f('ram-64k.wasm'), inv: f('inverter.wasm'), disp: f('galaksija-display.wasm') };
|
||||
const have = Object.values(paths).every(existsSync);
|
||||
const range = (n: number) => Array.from({ length: n }, (_, i) => i);
|
||||
|
||||
const Z80_PINS = [...range(16).map((i) => `A${i}`), ...range(8).map((i) => `D${i}`),
|
||||
'M1', 'MREQ', 'IORQ', 'RD', 'WR', 'RFSH', 'HALT', 'WAIT', 'INT', 'NMI', 'RESET', 'BUSREQ', 'BUSACK', 'CLK', 'VCC', 'GND'];
|
||||
const ROM_PINS = [...range(13).map((i) => `A${i}`), ...range(8).map((i) => `D${i}`), 'CE', 'OE'];
|
||||
const RAM_PINS = [...range(16).map((i) => `A${i}`), ...range(8).map((i) => `D${i}`), 'CE', 'OE', 'WE', 'VCC', 'GND'];
|
||||
const INV_PINS = ['IN', 'OUT'];
|
||||
const DISP_PINS = [...range(14).map((i) => `A${i}`), ...range(8).map((i) => `D${i}`), 'WR'];
|
||||
|
||||
const W: ChipNetState['wires'] = [];
|
||||
const wire = (a: string, ap: string, b: string, bp: string) =>
|
||||
(W as { start: { componentId: string; pinName: string }; end: { componentId: string; pinName: string } }[]).push(
|
||||
{ start: { componentId: a, pinName: ap }, end: { componentId: b, pinName: bp } });
|
||||
for (const i of range(13)) { wire('z80', `A${i}`, 'rom', `A${i}`); wire('z80', `A${i}`, 'ram', `A${i}`); wire('z80', `A${i}`, 'disp', `A${i}`); }
|
||||
wire('z80', 'A13', 'rom', 'CE'); wire('z80', 'A13', 'inv', 'IN'); wire('z80', 'A13', 'disp', 'A13');
|
||||
wire('inv', 'OUT', 'ram', 'CE');
|
||||
for (const i of range(8)) { wire('z80', `D${i}`, 'rom', `D${i}`); wire('z80', `D${i}`, 'ram', `D${i}`); wire('z80', `D${i}`, 'disp', `D${i}`); }
|
||||
wire('z80', 'RD', 'rom', 'OE'); wire('z80', 'RD', 'ram', 'OE');
|
||||
wire('z80', 'WR', 'ram', 'WE'); wire('z80', 'WR', 'disp', 'WR');
|
||||
|
||||
const STATE: ChipNetState = {
|
||||
wires: W,
|
||||
components: ['z80', 'rom', 'ram', 'inv', 'disp'].map((id) => ({ id, metadataId: 'custom-chip' })),
|
||||
boards: [],
|
||||
};
|
||||
const pinKey = (c: string, p: string): number => resolveChipNetKey(STATE, c, p) ?? syntheticChipPin(c, p);
|
||||
const wiresFor = (c: string, pins: string[]) => new Map(pins.map((p) => [p, pinKey(c, p)] as [string, number]));
|
||||
|
||||
describe.skipIf(!have)('chipbus Phase 3 — full Galaksija computer renders READY', () => {
|
||||
beforeEach(() => { setChipBusEnabledForTest(true); resetChipNetIndexForTest(); resetBusNets(); });
|
||||
afterEach(() => { setChipBusEnabledForTest(null); resetChipNetIndexForTest(); resetBusNets(); });
|
||||
|
||||
it('boots the ROM and renders the READY prompt on the video display', async () => {
|
||||
const pm = new PinManager();
|
||||
const mk = async (k: keyof typeof paths, id: string, pins: string[], display?: { width: number; height: number }) =>
|
||||
ChipInstance.create({ wasm: new Uint8Array(readFileSync(paths[k])), componentId: id, pinManager: pm, wires: wiresFor(id, pins), display });
|
||||
const z80 = await mk('z80', 'z80', Z80_PINS); z80.start();
|
||||
const rom = await mk('rom', 'rom', ROM_PINS); rom.start();
|
||||
const ram = await mk('ram', 'ram', RAM_PINS); ram.start();
|
||||
const inv = await mk('inv', 'inv', INV_PINS); inv.start();
|
||||
const disp = await mk('disp', 'disp', DISP_PINS, { width: 256, height: 128 });
|
||||
let fb: Uint8Array | null = null;
|
||||
disp.onFramebufferUpdate((rgba) => { fb = rgba as Uint8Array; });
|
||||
disp.start();
|
||||
|
||||
for (const p of ['WAIT', 'INT', 'NMI', 'BUSREQ']) pm.triggerPinChange(pinKey('z80', p), true);
|
||||
pm.triggerPinChange(pinKey('z80', 'RESET'), false);
|
||||
pm.triggerPinChange(pinKey('z80', 'RESET'), true);
|
||||
|
||||
z80.tickTimers(BigInt(120000 * 250)); // enough for clear + banner
|
||||
|
||||
expect(fb, 'display produced a framebuffer').not.toBeNull();
|
||||
// "READY" lives at video offset 2 (0x2802) -> row 0, cols 2..6. Count lit
|
||||
// pixels across those five character cells (x 16..55, y 0..7).
|
||||
let litReady = 0;
|
||||
for (let y = 0; y < 8; y++) for (let x = 16; x < 56; x++) if (fb![(y * 256 + x) * 4] === 0xff) litReady++;
|
||||
expect(litReady, 'the READY prompt is rendered on screen').toBeGreaterThan(20);
|
||||
|
||||
z80.dispose(); rom.dispose(); ram.dispose(); inv.dispose(); disp.dispose();
|
||||
}, 30_000);
|
||||
});
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Phase 3 — Galaksija video display chip smoke test (project/multichip-bus/).
|
||||
* Verifies the snoop + CHRGEN render path mechanically: a write of 'R' to the
|
||||
* video address 0x2802 makes the display chip render lit pixels into that cell's
|
||||
* framebuffer region. Exact glyph fidelity is verified visually in the browser.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { PinManager } from '../simulation/PinManager';
|
||||
import { ChipInstance } from '../simulation/customChips/ChipRuntime';
|
||||
import { resetBusNets } from '../simulation/customChips/busNets';
|
||||
|
||||
const dispPath = fileURLToPath(new URL('./fixtures/chipbus/galaksija-display.wasm', import.meta.url));
|
||||
const have = existsSync(dispPath);
|
||||
const range = (n: number) => Array.from({ length: n }, (_, i) => i);
|
||||
|
||||
describe.skipIf(!have)('chipbus Phase 3 — Galaksija display renders a character', () => {
|
||||
beforeEach(() => resetBusNets());
|
||||
afterEach(() => resetBusNets());
|
||||
|
||||
it('a write of "R" to 0x2802 lights pixels in that cell', async () => {
|
||||
const pm = new PinManager();
|
||||
// Explicit pin keys (isolated; no net resolution needed for this snoop test).
|
||||
const aKey = (i: number) => 1 + i; // A0..A13 -> 1..14
|
||||
const dKey = (i: number) => 21 + i; // D0..D7 -> 21..28
|
||||
const WR = 30;
|
||||
const wires = new Map<string, number>([
|
||||
...range(14).map((i) => [`A${i}`, aKey(i)] as [string, number]),
|
||||
...range(8).map((i) => [`D${i}`, dKey(i)] as [string, number]),
|
||||
['WR', WR],
|
||||
]);
|
||||
|
||||
const disp = await ChipInstance.create({
|
||||
wasm: new Uint8Array(readFileSync(dispPath)),
|
||||
componentId: 'disp',
|
||||
pinManager: pm,
|
||||
wires,
|
||||
display: { width: 256, height: 128 },
|
||||
});
|
||||
let fb: Uint8Array | null = null;
|
||||
disp.onFramebufferUpdate((rgba) => { fb = rgba as Uint8Array; });
|
||||
disp.start();
|
||||
|
||||
// Drive address 0x2802 (A1,A11,A13), data 'R'=0x52 (D1,D4,D6), then pulse WR.
|
||||
const setAddr = (addr: number) => { for (let i = 0; i < 14; i++) pm.triggerPinChange(aKey(i), ((addr >> i) & 1) === 1); };
|
||||
const setData = (d: number) => { for (let i = 0; i < 8; i++) pm.triggerPinChange(dKey(i), ((d >> i) & 1) === 1); };
|
||||
setAddr(0x2802);
|
||||
setData(0x52);
|
||||
pm.triggerPinChange(WR, false);
|
||||
pm.triggerPinChange(WR, true); // rising edge -> latch + render
|
||||
|
||||
expect(fb, 'framebuffer produced').not.toBeNull();
|
||||
// Cell (col 2, row 0) spans x 16..23, y 0..7. Count lit (white) pixels there.
|
||||
let lit = 0;
|
||||
for (let y = 0; y < 8; y++) {
|
||||
for (let x = 16; x < 24; x++) {
|
||||
const o = (y * 256 + x) * 4;
|
||||
if (fb![o] === 0xff) lit++;
|
||||
}
|
||||
}
|
||||
expect(lit, 'the R glyph lit some pixels in its cell').toBeGreaterThan(3);
|
||||
// A blank cell elsewhere (col 0) stays dark.
|
||||
let litBlank = 0;
|
||||
for (let y = 0; y < 8; y++) for (let x = 0; x < 8; x++) if (fb![(y * 256 + x) * 4] === 0xff) litBlank++;
|
||||
expect(litBlank, 'an unwritten cell stays blank').toBe(0);
|
||||
|
||||
disp.dispose();
|
||||
});
|
||||
});
|
||||
Binary file not shown.
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* galaksija-display <EFBFBD> Galaksija (1983) 32x16 text video, rendered from its
|
||||
* memory-mapped video RAM. Phase 3 of the multi-chip bus track
|
||||
* (project/multichip-bus/). Public-domain character generator (CHRGEN) by the
|
||||
* Galaksija project. The chip is a passive BUS SNOOP: it watches WR + the
|
||||
* address + data bus and, on a write into the 0x2800-0x2BFF video region,
|
||||
* stores the character and renders that cell into its framebuffer using CHRGEN.
|
||||
* It never drives the bus. The framebuffer (256x128 = 32x16 chars of 8x8) is
|
||||
* blitted to the chip canvas by the host (vx_framebuffer_init / vx_buffer_write).
|
||||
*
|
||||
* CHRGEN: 256 chars x 8 rows, 1 byte per row, bit = 0 -> lit pixel (inverted).
|
||||
*/
|
||||
#include "velxio-chip.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define COLS 32
|
||||
#define ROWS 16
|
||||
#define CW 8
|
||||
#define CH 8
|
||||
#define FB_W (COLS*CW)
|
||||
#define FB_H (ROWS*CH)
|
||||
|
||||
static const uint8_t chrgen[2048] = {
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xaf,0xbf,0xaf,0xaf,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdf,0xdf,0xdf,0xdf,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0x03,0xcf,0x83,0x87,0x83,0x03,0x03,0x87,0x7b,0xc7,0x03,0x7b,0xfb,0x7b,0x7b,0x87,
|
||||
0x83,0x87,0x83,0x87,0x03,0x7b,0x7b,0x7b,0x7b,0x7b,0x03,0x87,0x87,0x03,0x87,0xff,
|
||||
0xff,0xef,0xd7,0xb7,0xdf,0xff,0xe7,0xf8,0xdf,0xef,0xff,0xff,0xff,0xff,0xff,0xbf,
|
||||
0x87,0xdf,0x87,0x87,0xbf,0x03,0x87,0x03,0x87,0x87,0xff,0xff,0xff,0xff,0xff,0x87,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,0xff,0xf1,0x1f,0x11,
|
||||
0xfb,0xb7,0x77,0x7b,0x77,0xfb,0xfb,0x7b,0x7b,0xef,0x7f,0xbb,0xfb,0x33,0x7b,0x7b,
|
||||
0x7b,0x7b,0x7b,0x7b,0xef,0x7b,0x7b,0x7b,0x7b,0x7b,0x7f,0x7b,0x7b,0x7f,0x7b,0xff,
|
||||
0xff,0xef,0xd7,0xb7,0x07,0x73,0xdb,0xfb,0xef,0xdf,0xef,0xef,0xff,0xff,0xff,0xbf,
|
||||
0x7b,0xcf,0x7b,0x7b,0x9f,0xfb,0x7b,0x7b,0x7b,0x7b,0xe7,0xe7,0xbf,0xff,0xf7,0x7b,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xbb,0x7b,0x77,0xfb,0x77,0xfb,0xfb,0xfb,0x7b,0xef,0x7f,0xdb,0xfb,0x33,0x7b,0x7b,
|
||||
0x7b,0x7b,0x7b,0xfb,0xef,0x7b,0x7b,0x7b,0x7b,0x7b,0xbf,0xfb,0xfb,0xbf,0xfb,0xff,
|
||||
0xff,0xef,0xd7,0xb7,0xdb,0x73,0xdb,0xfb,0xf7,0xbf,0xab,0xef,0xff,0xff,0xff,0xdf,
|
||||
0x7b,0xdf,0x7f,0x7f,0xaf,0xfb,0xfb,0x7f,0x7b,0x7b,0xe7,0xe7,0xdf,0xff,0xef,0x7b,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xfb,0x7b,0x77,0xfb,0x77,0xfb,0xfb,0xfb,0x7b,0xef,0x7f,0xeb,0xfb,0x4b,0x7b,0x7b,
|
||||
0x7b,0x7b,0x7b,0xfb,0xef,0x7b,0xb7,0x7b,0xb7,0x7b,0xdf,0xfb,0xfb,0xdf,0xfb,0xff,
|
||||
0xff,0xef,0xff,0x03,0xdb,0xbf,0xeb,0xfb,0xf7,0xbf,0xc7,0xef,0xff,0xff,0xff,0xdf,
|
||||
0x3b,0xdf,0x7f,0x7f,0xb7,0x83,0x83,0xbf,0x7b,0x7b,0xff,0xff,0xef,0x03,0xdf,0x7f,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xeb,0x7b,0x87,0xfb,0x77,0x83,0x83,0x1b,0x03,0xef,0x7f,0xf3,0xfb,0x4b,0x73,0x7b,
|
||||
0x83,0x7b,0x83,0x87,0xef,0x7b,0xb7,0x7b,0xcf,0x07,0xef,0xfb,0xfb,0xef,0x87,0xff,
|
||||
0xff,0xef,0xff,0xb7,0x87,0xdf,0xf7,0xfb,0xf7,0xbf,0x83,0x83,0xff,0x03,0xff,0xef,
|
||||
0x5b,0xdf,0xbf,0x9f,0xbb,0x7f,0x7b,0xdf,0x87,0x7b,0xff,0xff,0xf7,0xff,0xbf,0x9f,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xfb,0x03,0x77,0xfb,0x77,0xfb,0xfb,0x7b,0x7b,0xef,0x7f,0xeb,0xfb,0x7b,0x6b,0x7b,
|
||||
0xfb,0x7b,0xeb,0x7f,0xef,0x7b,0xb7,0x4b,0xcf,0x7f,0xf7,0xfb,0xfb,0xf7,0x7f,0xff,
|
||||
0xff,0xef,0xff,0xb7,0x6f,0xef,0xeb,0xfb,0xf7,0xbf,0xc7,0xef,0xff,0xff,0xff,0xef,
|
||||
0x6b,0xdf,0xc7,0x7f,0xbb,0x7f,0x7b,0xef,0x7b,0x07,0xff,0xff,0xef,0x03,0xdf,0xef,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xf1,0xf1,0xf1,0xf1,0x1f,0x1f,0x1f,0x1f,0x11,0x11,0x11,0x11,
|
||||
0xab,0x7b,0x77,0xfb,0x77,0xfb,0xfb,0x7b,0x7b,0xef,0x7b,0xdb,0xfb,0x7b,0x5b,0x7b,
|
||||
0xfb,0x5b,0xdb,0x7f,0xef,0x7b,0xcf,0x4b,0xb7,0x7f,0xfb,0xfb,0xfb,0xfb,0x7f,0xff,
|
||||
0xff,0xef,0xff,0x03,0x6f,0xf7,0x5b,0xfa,0xf7,0xbf,0xab,0xef,0xe7,0xff,0xff,0xf7,
|
||||
0x73,0xdf,0xfb,0x7f,0x03,0x7f,0x7b,0xf7,0x7b,0x7f,0xe7,0xe7,0xdf,0xff,0xef,0xef,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xfb,0x7b,0x77,0x7b,0x77,0xfb,0xfb,0x7b,0x7b,0xef,0x7b,0xbb,0xfb,0x7b,0x3b,0x7b,
|
||||
0xfb,0xbb,0xbb,0x7b,0xef,0x7b,0xcf,0x33,0x7b,0x7b,0xfb,0x7b,0x7b,0xfb,0x7b,0xff,
|
||||
0xff,0xff,0xff,0xb7,0x83,0x3b,0xbb,0xfb,0xef,0xdf,0xef,0xef,0xe7,0xff,0xe7,0xf7,
|
||||
0x7b,0xdf,0xfb,0x7b,0xbf,0x7b,0x7b,0xf7,0x7b,0x7b,0xe7,0xe7,0xbf,0xff,0xf7,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,
|
||||
0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
|
||||
0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
|
||||
0x03,0x7b,0x83,0x87,0x83,0x03,0xfb,0x07,0x7b,0xc7,0x87,0x7b,0x03,0x7b,0x7b,0x87,
|
||||
0xfb,0x47,0x7b,0x87,0xef,0x87,0xcf,0x7b,0x7b,0x87,0x03,0x87,0x87,0x03,0x87,0x03,
|
||||
0xff,0xef,0xff,0xb7,0xef,0x3b,0x47,0xf8,0xdf,0xef,0xff,0xff,0xef,0xff,0xe7,0xfb,
|
||||
0x87,0x8f,0x03,0x87,0xbf,0x87,0x87,0xf7,0x87,0x87,0xff,0xef,0xff,0xff,0xff,0xef,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,
|
||||
0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
|
||||
0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf7,0xff,0xff,0xfb,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf7,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,
|
||||
0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
|
||||
0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,
|
||||
0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
|
||||
0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
};
|
||||
|
||||
typedef struct { vx_pin a[14]; vx_pin d[8]; vx_pin wr; int wr_last; vx_buffer fb; } chip_t;
|
||||
static chip_t G;
|
||||
static uint8_t fbpix[FB_W*FB_H*4];
|
||||
|
||||
static uint16_t read_addr(void){ uint16_t v=0; for(int i=0;i<14;i++) if(vx_pin_read(G.a[i])) v|=(1u<<i); return v; }
|
||||
static uint8_t read_data(void){ uint8_t v=0; for(int i=0;i<8;i++) if(vx_pin_read(G.d[i])) v|=(1u<<i); return v; }
|
||||
|
||||
static void put_px(int x,int y,uint8_t lit){
|
||||
if(x<0||y<0||x>=FB_W||y>=FB_H) return;
|
||||
int o=(y*FB_W+x)*4; uint8_t v=lit?0xFF:0x00;
|
||||
fbpix[o]=v; fbpix[o+1]=v; fbpix[o+2]=v; fbpix[o+3]=0xFF;
|
||||
}
|
||||
static void render_cell(int col,int row,uint8_t ch){
|
||||
for(int line=0; line<CH; line++){
|
||||
uint8_t bits=chrgen[((int)ch*8 + line) & 2047];
|
||||
for(int px=0; px<CW; px++){
|
||||
uint8_t lit = ((bits>>(7-px))&1) ? 0 : 1; /* 0 bit = lit */
|
||||
put_px(col*CW+px, row*CH+line, lit);
|
||||
}
|
||||
}
|
||||
vx_buffer_write(G.fb, 0, fbpix, sizeof(fbpix));
|
||||
}
|
||||
|
||||
static void on_wr(void* u, vx_pin p, int value){
|
||||
(void)u;(void)p;
|
||||
/* latch on WR rising edge (data + address valid through the write) */
|
||||
if(G.wr_last==0 && value==1){
|
||||
uint16_t addr=read_addr();
|
||||
/* video region 0x2800-0x2BFF: A13=1,A12=0,A11=1,A10=0 */
|
||||
if((addr & 0x3C00) == 0x2800){
|
||||
uint16_t off = addr & 0x3FF; /* 0..1023 */
|
||||
int row=off/COLS, col=off%COLS;
|
||||
if(row<ROWS) render_cell(col,row,read_data());
|
||||
}
|
||||
}
|
||||
G.wr_last=value;
|
||||
}
|
||||
|
||||
void chip_setup(void){
|
||||
char name[4];
|
||||
for(int i=0;i<14;i++){ name[0]='A'; if(i<10){name[1]='0'+i;name[2]=0;} else {name[1]='1';name[2]='0'+(i-10);name[3]=0;} G.a[i]=vx_pin_register(name,VX_INPUT);}
|
||||
for(int i=0;i<8;i++){ name[0]='D';name[1]='0'+i;name[2]=0; G.d[i]=vx_pin_register(name,VX_INPUT);}
|
||||
G.wr=vx_pin_register("WR",VX_INPUT); G.wr_last=vx_pin_read(G.wr);
|
||||
uint32_t w,h; G.fb=vx_framebuffer_init(&w,&h);
|
||||
for(int i=0;i<FB_W*FB_H;i++){ fbpix[i*4]=0;fbpix[i*4+1]=0;fbpix[i*4+2]=0;fbpix[i*4+3]=0xFF; }
|
||||
vx_buffer_write(G.fb,0,fbpix,sizeof(fbpix));
|
||||
vx_pin_watch(G.wr,VX_EDGE_BOTH,on_wr,0);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"schema": "velxio-chip/v1",
|
||||
"name": "Galaksija Display (32x16)",
|
||||
"author": "Velxio (CHRGEN font: Galaksija project, public domain)",
|
||||
"license": "MIT (chip) / public-domain (char generator)",
|
||||
"description": "Galaksija 32x16 text video display. Passive bus snoop: watches WR + address + data and renders writes to the 0x2800 video RAM with the public-domain CHRGEN font into a 256x128 framebuffer. Phase 3 chip-to-chip bus proof.",
|
||||
"display": { "width": 256, "height": 128 },
|
||||
"pins": ["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "A11", "A12", "A13", "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "WR"]
|
||||
}
|
||||
Loading…
Reference in New Issue