velxio/test/test_custom_chips/sdk/examples/galaksija-ram-display.c

226 lines
11 KiB
C

/*
* galaksija-ram-display - 64 KB SRAM that ALSO renders the Galaksija text
* screen from its own video RAM (Phase 3, project/multichip-bus/).
*
* This is galaksija-ram (64 KB, yields 0x2000-0x203F reads to the keyboard)
* with the display folded in. Crucially it renders from the ACTUAL contents of
* its video RAM (0x2800-0x2BFF) on a ~30 fps timer, NOT from snooped bus writes.
*
* The earlier split (separate galaksija-ram + galaksija-display) made the
* display a passive write-snoop. That loses the picture whenever it misses a
* write: the Galaksija ROM paints the whole screen ONCE at boot then idles
* polling the keyboard, so a snoop that comes up late (the chips load
* asynchronously in the browser) shows stale/blank content forever. Reading the
* memory it already owns makes the display correct regardless of load order -
* exactly how the real machine generates its picture by scanning video RAM.
*
* Pin contract: idealised 64 KB byte-wide SRAM.
* A0..A15 input 16-bit address
* D0..D7 bidirectional 8-bit data (output on read, input on write)
* CE/OE/WE input active-low chip/output/write enables
* VCC, GND power
* Read 0x2000-0x203F is released (the memory-mapped keyboard drives it).
*
* Video RAM: the ROM stores ASCII codes at 0x2800-0x2BFF (32x16). Rendered with
* the public-domain IBM/VGA 8x8 font (font8x8 by Daniel Hepper / Marcel
* Sondaar), green-on-black phosphor; 1 byte/row, bit 0 (LSB) = leftmost pixel.
*/
#include "velxio-chip.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#define RAM_SIZE 0x10000 /* 64 KB */
/* Video RAM as THIS chip sees it. The board wires A0-A12 (A13 selects the chip
via CE), so the CPU's 0x2000-0x3FFF window maps to internal mem[0x0000-0x1FFF]
and the 0x2800 video RAM lands at internal 0x0800. The memory-mapped keyboard
(0x2000-0x203F real -> 0x00-0x3F here) is yielded in update_outputs(). */
#define VRAM_BASE 0x0800
#define COLS 32
#define ROWS 16
#define FB_W (COLS*8)
#define FB_H (ROWS*8)
static const uint8_t font8x8[1024] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x3c,0x3c,0x18,0x18,0x00,0x18,0x00,
0x36,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x36,0x7f,0x36,0x7f,0x36,0x36,0x00,
0x0c,0x3e,0x03,0x1e,0x30,0x1f,0x0c,0x00,0x00,0x63,0x33,0x18,0x0c,0x66,0x63,0x00,
0x1c,0x36,0x1c,0x6e,0x3b,0x33,0x6e,0x00,0x06,0x06,0x03,0x00,0x00,0x00,0x00,0x00,
0x18,0x0c,0x06,0x06,0x06,0x0c,0x18,0x00,0x06,0x0c,0x18,0x18,0x18,0x0c,0x06,0x00,
0x00,0x66,0x3c,0xff,0x3c,0x66,0x00,0x00,0x00,0x0c,0x0c,0x3f,0x0c,0x0c,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x0c,0x0c,0x06,0x00,0x00,0x00,0x3f,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x0c,0x0c,0x00,0x60,0x30,0x18,0x0c,0x06,0x03,0x01,0x00,
0x3e,0x63,0x73,0x7b,0x6f,0x67,0x3e,0x00,0x0c,0x0e,0x0c,0x0c,0x0c,0x0c,0x3f,0x00,
0x1e,0x33,0x30,0x1c,0x06,0x33,0x3f,0x00,0x1e,0x33,0x30,0x1c,0x30,0x33,0x1e,0x00,
0x38,0x3c,0x36,0x33,0x7f,0x30,0x78,0x00,0x3f,0x03,0x1f,0x30,0x30,0x33,0x1e,0x00,
0x1c,0x06,0x03,0x1f,0x33,0x33,0x1e,0x00,0x3f,0x33,0x30,0x18,0x0c,0x0c,0x0c,0x00,
0x1e,0x33,0x33,0x1e,0x33,0x33,0x1e,0x00,0x1e,0x33,0x33,0x3e,0x30,0x18,0x0e,0x00,
0x00,0x0c,0x0c,0x00,0x00,0x0c,0x0c,0x00,0x00,0x0c,0x0c,0x00,0x00,0x0c,0x0c,0x06,
0x18,0x0c,0x06,0x03,0x06,0x0c,0x18,0x00,0x00,0x00,0x3f,0x00,0x00,0x3f,0x00,0x00,
0x06,0x0c,0x18,0x30,0x18,0x0c,0x06,0x00,0x1e,0x33,0x30,0x18,0x0c,0x00,0x0c,0x00,
0x3e,0x63,0x7b,0x7b,0x7b,0x03,0x1e,0x00,0x0c,0x1e,0x33,0x33,0x3f,0x33,0x33,0x00,
0x3f,0x66,0x66,0x3e,0x66,0x66,0x3f,0x00,0x3c,0x66,0x03,0x03,0x03,0x66,0x3c,0x00,
0x1f,0x36,0x66,0x66,0x66,0x36,0x1f,0x00,0x7f,0x46,0x16,0x1e,0x16,0x46,0x7f,0x00,
0x7f,0x46,0x16,0x1e,0x16,0x06,0x0f,0x00,0x3c,0x66,0x03,0x03,0x73,0x66,0x7c,0x00,
0x33,0x33,0x33,0x3f,0x33,0x33,0x33,0x00,0x1e,0x0c,0x0c,0x0c,0x0c,0x0c,0x1e,0x00,
0x78,0x30,0x30,0x30,0x33,0x33,0x1e,0x00,0x67,0x66,0x36,0x1e,0x36,0x66,0x67,0x00,
0x0f,0x06,0x06,0x06,0x46,0x66,0x7f,0x00,0x63,0x77,0x7f,0x7f,0x6b,0x63,0x63,0x00,
0x63,0x67,0x6f,0x7b,0x73,0x63,0x63,0x00,0x1c,0x36,0x63,0x63,0x63,0x36,0x1c,0x00,
0x3f,0x66,0x66,0x3e,0x06,0x06,0x0f,0x00,0x1e,0x33,0x33,0x33,0x3b,0x1e,0x38,0x00,
0x3f,0x66,0x66,0x3e,0x36,0x66,0x67,0x00,0x1e,0x33,0x07,0x0e,0x38,0x33,0x1e,0x00,
0x3f,0x2d,0x0c,0x0c,0x0c,0x0c,0x1e,0x00,0x33,0x33,0x33,0x33,0x33,0x33,0x3f,0x00,
0x33,0x33,0x33,0x33,0x33,0x1e,0x0c,0x00,0x63,0x63,0x63,0x6b,0x7f,0x77,0x63,0x00,
0x63,0x63,0x36,0x1c,0x1c,0x36,0x63,0x00,0x33,0x33,0x33,0x1e,0x0c,0x0c,0x1e,0x00,
0x7f,0x63,0x31,0x18,0x4c,0x66,0x7f,0x00,0x1e,0x06,0x06,0x06,0x06,0x06,0x1e,0x00,
0x03,0x06,0x0c,0x18,0x30,0x60,0x40,0x00,0x1e,0x18,0x18,0x18,0x18,0x18,0x1e,0x00,
0x08,0x1c,0x36,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,
0x0c,0x0c,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1e,0x30,0x3e,0x33,0x6e,0x00,
0x07,0x06,0x06,0x3e,0x66,0x66,0x3b,0x00,0x00,0x00,0x1e,0x33,0x03,0x33,0x1e,0x00,
0x38,0x30,0x30,0x3e,0x33,0x33,0x6e,0x00,0x00,0x00,0x1e,0x33,0x3f,0x03,0x1e,0x00,
0x1c,0x36,0x06,0x0f,0x06,0x06,0x0f,0x00,0x00,0x00,0x6e,0x33,0x33,0x3e,0x30,0x1f,
0x07,0x06,0x36,0x6e,0x66,0x66,0x67,0x00,0x0c,0x00,0x0e,0x0c,0x0c,0x0c,0x1e,0x00,
0x30,0x00,0x30,0x30,0x30,0x33,0x33,0x1e,0x07,0x06,0x66,0x36,0x1e,0x36,0x67,0x00,
0x0e,0x0c,0x0c,0x0c,0x0c,0x0c,0x1e,0x00,0x00,0x00,0x33,0x7f,0x7f,0x6b,0x63,0x00,
0x00,0x00,0x1f,0x33,0x33,0x33,0x33,0x00,0x00,0x00,0x1e,0x33,0x33,0x33,0x1e,0x00,
0x00,0x00,0x3b,0x66,0x66,0x3e,0x06,0x0f,0x00,0x00,0x6e,0x33,0x33,0x3e,0x30,0x78,
0x00,0x00,0x3b,0x6e,0x66,0x06,0x0f,0x00,0x00,0x00,0x3e,0x03,0x1e,0x30,0x1f,0x00,
0x08,0x0c,0x3e,0x0c,0x0c,0x2c,0x18,0x00,0x00,0x00,0x33,0x33,0x33,0x33,0x6e,0x00,
0x00,0x00,0x33,0x33,0x33,0x1e,0x0c,0x00,0x00,0x00,0x63,0x6b,0x7f,0x7f,0x36,0x00,
0x00,0x00,0x63,0x36,0x1c,0x36,0x63,0x00,0x00,0x00,0x33,0x33,0x33,0x3e,0x30,0x1f,
0x00,0x00,0x3f,0x19,0x0c,0x26,0x3f,0x00,0x38,0x0c,0x0c,0x07,0x0c,0x0c,0x38,0x00,
0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x00,0x07,0x0c,0x0c,0x38,0x0c,0x0c,0x07,0x00,
0x6e,0x3b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
typedef struct {
vx_pin a[16];
vx_pin d[8];
vx_pin ce, oe, we, vcc, gnd;
uint8_t* mem;
bool driving;
int we_last;
vx_buffer fb;
vx_timer paint;
} 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 < 16; i++) if (vx_pin_read(G.a[i])) v |= (1u << i);
return v;
}
static uint8_t read_data_bus(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 drive_data(uint8_t v) {
for (int i = 0; i < 8; i++) { vx_pin_set_mode(G.d[i], VX_OUTPUT); vx_pin_write(G.d[i], (v >> i) & 1); }
G.driving = true;
}
static void release_data(void) {
if (!G.driving) return;
for (int i = 0; i < 8; i++) vx_pin_set_mode(G.d[i], VX_INPUT);
G.driving = false;
}
static void update_outputs(void) {
int ce_low = (vx_pin_read(G.ce) == 0);
int oe_low = (vx_pin_read(G.oe) == 0);
int we_low = (vx_pin_read(G.we) == 0);
if (ce_low && oe_low && !we_low) {
uint16_t addr = read_addr();
if (addr < 0x40) { release_data(); return; } /* keyboard owns 0x2000-0x203F */
drive_data(G.mem[addr]);
} else {
release_data();
}
}
static void on_addr_or_ctrl(void* u, vx_pin p, int v) { (void)u;(void)p;(void)v; update_outputs(); }
static void on_we(void* u, vx_pin p, int value) {
(void)u;(void)p;
int ce_low = (vx_pin_read(G.ce) == 0);
if (G.we_last == 0 && value == 1 && ce_low) {
uint16_t addr = read_addr();
G.mem[addr] = read_data_bus();
}
G.we_last = value;
update_outputs();
}
static void put_px(int x, int y, uint8_t lit) {
if ((unsigned)x < FB_W && (unsigned)y < FB_H) {
int o = (y*FB_W + x)*4;
fbpix[o]=lit?0x33:0x00; fbpix[o+1]=lit?0xE0:0x12; fbpix[o+2]=lit?0x33:0x00; fbpix[o+3]=0xFF;
}
}
/* Render the whole 32x16 screen from video RAM, then blit. Reading the memory
we own (rather than snooping writes) keeps the picture correct no matter when
this chip was instantiated relative to the CPU. */
static void on_paint(void* u) {
(void)u;
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLS; col++) {
uint8_t ch = G.mem[VRAM_BASE + row*COLS + col] & 0x7f;
for (int line = 0; line < 8; line++) {
uint8_t bits = font8x8[ch*8 + line];
for (int px = 0; px < 8; px++) put_px(col*8+px, row*8+line, (bits >> px) & 1);
}
}
}
vx_buffer_write(G.fb, 0, fbpix, sizeof(fbpix));
}
void chip_setup(void) {
char name[4];
for (int i = 0; i < 16; 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.ce = vx_pin_register("CE", VX_INPUT);
G.oe = vx_pin_register("OE", VX_INPUT);
G.we = vx_pin_register("WE", VX_INPUT);
G.vcc = vx_pin_register("VCC", VX_INPUT);
G.gnd = vx_pin_register("GND", VX_INPUT);
G.mem = (uint8_t*)calloc(RAM_SIZE, 1);
G.driving = false;
G.we_last = vx_pin_read(G.we);
for (int i = 0; i < 16; i++) vx_pin_watch(G.a[i], VX_EDGE_BOTH, on_addr_or_ctrl, 0);
vx_pin_watch(G.ce, VX_EDGE_BOTH, on_addr_or_ctrl, 0);
vx_pin_watch(G.oe, VX_EDGE_BOTH, on_addr_or_ctrl, 0);
vx_pin_watch(G.we, VX_EDGE_BOTH, on_we, 0);
update_outputs();
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]=0x12; fbpix[i*4+2]=0; fbpix[i*4+3]=0xFF; }
vx_buffer_write(G.fb, 0, fbpix, sizeof(fbpix));
G.paint = vx_timer_create(on_paint, 0);
vx_timer_start(G.paint, 33000000ULL, true); /* ~30 fps */
}