226 lines
11 KiB
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 */
|
|
}
|