velxio/test/test_intel/test_buses/ram-64k.c

146 lines
4.6 KiB
C

/*
* ram-64k — 64 KB SRAM custom chip.
*
* Pin contract (idealised 64 KB byte-wide SRAM, see autosearch/09):
* A0..A15 input 16-bit address
* D0..D7 bidirectional 8-bit data (output on read, input on write)
* CE̅ input active-low chip enable
* OE̅ input active-low output enable
* WE̅ input active-low write enable (latch on rising edge)
* VCC, GND power
*
* Read mode: CE̅=0 AND OE̅=0 AND WE̅=1 → drive D pins from mem[addr].
* Write mode: CE̅=0 AND WE̅ rising edge (with data already on D pins) →
* latch mem[addr] := data.
* Standby: CE̅=1 → D pins released.
*
* The 64 KB array is zero-initialised at chip_setup. Real SRAM powers
* up indeterminate; zero-init is a deliberate simplification that
* matches every common simulator (Wokwi, etc.) and is what
* ram-64k.test.js's blank-state assertion expects.
*/
#include "velxio-chip.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#define RAM_SIZE 0x10000 /* 64 KB */
/* mem[] is malloc'd at chip_setup, NOT a static array, so the linker
doesn't include 64 KB of BSS in the chip's initial memory image.
The host (ChipRuntime.ts) provides 2 pages = 128 KB initial and
permits growth up to 16 pages = 1 MB, more than enough for 64 KB
on the heap plus stack. */
typedef struct {
vx_pin a[16];
vx_pin d[8];
vx_pin ce;
vx_pin oe;
vx_pin we;
vx_pin vcc;
vx_pin gnd;
uint8_t* mem;
bool driving;
int we_last;
} chip_t;
static chip_t G;
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);
/* Drive only on a true read: selected, output enabled, not writing. */
if (ce_low && oe_low && !we_low) {
drive_data(G.mem[read_addr()]);
} else {
release_data();
}
}
static void on_addr_or_ctrl(void* user_data, vx_pin pin, int value) {
(void)user_data; (void)pin; (void)value;
update_outputs();
}
static void on_we(void* user_data, vx_pin pin, int value) {
(void)user_data; (void)pin;
int ce_low = (vx_pin_read(G.ce) == 0);
/* Latch on rising edge of WE̅ when chip is selected.
(Pin watch was registered for EDGE_BOTH so we detect both
transitions; rising means we_last==0 and value==1.) */
if (G.we_last == 0 && value == 1 && ce_low) {
uint16_t addr = read_addr();
uint8_t data = read_data_bus();
G.mem[addr] = data;
}
G.we_last = value;
/* WE̅ change also affects whether we should be driving D in read
mode (during write, we must release). */
update_outputs();
}
void chip_setup(void) {
char name[4];
/* A0..A15 inputs */
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);
}
/* D0..D7 inputs (bidirectional; we switch to OUTPUT during reads) */
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); /* sample initial WE̅ level */
/* Watches: address and CE/OE affect outputs; WE is special because
its rising edge is the write-latch trigger. */
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();
}