velxio/test/test_intel/test_buses/4002-ram.c

190 lines
6.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Intel 4002 RAM — companion data/IO chip for the 4004/4040.
*
* 16-pin DIP, 80 nibbles of static RAM (4 registers × 20 chars: 16
* main + 4 status), plus 4 dedicated output port lines driven by WMP.
* Like the 4001 ROM, the 4002 uses the multiplexed nibble bus and
* tracks the 4004's 8-phase frame via SYNC + an internal timer.
*
* Source: Intel MCS-4 User's Manual (Feb 1973), §V "4002 Random
* Access Memory" + Fig. 5-15 pin diagram.
*
* Pin contract (we register 14 named pins; some 4002 variants have
* additional power rails we collapse):
* D0..D3 I/O shared multiplexed bus with the 4004
* O0..O3 out dedicated output port (driven by WMP)
* SYNC in cycle marker driven by the 4004
* CL in Φ2 clock — informational
* RESET in asynchronous reset — clears storage
* CM in chip-match strobe (one of CM-RAM0..3)
* VDD, VSS power
*
* Address protocol (the SRC instruction):
* When the 4004 executes SRC Pn, during X2 of that cycle the bus
* carries the chip-select address (high nibble of the register
* pair). During X3 it carries the char address (low nibble). The
* 4002 latches both, but only retains them if the high nibble's
* bits 3..2 match the chip's hardcoded chip-pair number AND the
* strobed CM line is the one this chip is wired to.
*
* Subsequent I/O ops (WRM/RDM/WR0..3/RD0..3) use the latched address.
*
* For the FIRST cut of this chip:
* - Storage exists (80 nibbles + 4 status lines).
* - Pin contract registered.
* - SRC chip-select latching tracked via SYNC + timer + D-bus
* observation during the X2/X3 phases (works only when the 4004
* is modified to actually drive the SRC address — currently the
* 4004 stubs SRC so this chip's storage is never reached
* end-to-end. Tracked as a Phase D follow-up.)
* - WMP write drives the 4 output port pins.
*
* NOT yet implemented:
* - WRR/RDR (these are 4001 ROM-port operations, unrelated to RAM).
* - Status-character (WR0..WR3 / RD0..RD3) handling beyond raw
* storage.
* - Cycle-accurate latch timing across CM strobes.
*/
#include "velxio-chip.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#ifndef RAM4002_CHIP_PAIR
#define RAM4002_CHIP_PAIR 0 /* bits 3..2 of chip-select address */
#endif
#define MAIN_CHARS_PER_REG 16
#define STATUS_PER_REG 4
#define NUM_REGS 4
typedef enum {
S_IDLE = 0,
S_AFTER_SYNC, /* tracking phases since last SYNC */
} state_t;
typedef struct {
vx_pin d[4];
vx_pin o[4];
vx_pin sync;
vx_pin cl;
vx_pin reset_;
vx_pin cm;
vx_pin vdd, vss;
vx_timer phase_timer;
/* 4 registers × 16 main chars + 4 status chars each */
uint8_t main[NUM_REGS][MAIN_CHARS_PER_REG];
uint8_t status[NUM_REGS][STATUS_PER_REG];
uint8_t output_port; /* driven on O0..O3 by WMP */
/* Latched SRC address. Updated when CM strobe + SRC X2/X3 align. */
uint8_t latched_reg; /* 0..3 */
uint8_t latched_char; /* 0..15 */
bool selected; /* this chip's pair matches the latched reg's high bits */
state_t state;
int phase_count; /* phases since last SYNC */
bool driving_d;
} chip_t;
static chip_t G;
/* ─── D-bus helpers ─────────────────────────────────────────────────────── */
static uint8_t read_d_nibble(void) {
uint8_t v = 0;
for (int i = 0; i < 4; i++) if (vx_pin_read(G.d[i])) v |= (1u << i);
return v;
}
static void drive_d_nibble(uint8_t n) {
for (int i = 0; i < 4; i++) {
vx_pin_set_mode(G.d[i], VX_OUTPUT);
vx_pin_write(G.d[i], (n >> i) & 1);
}
G.driving_d = true;
}
static void release_d(void) {
if (!G.driving_d) return;
for (int i = 0; i < 4; i++) vx_pin_set_mode(G.d[i], VX_INPUT);
G.driving_d = false;
}
static void drive_output(uint8_t v) {
G.output_port = v & 0x0F;
for (int i = 0; i < 4; i++) vx_pin_write(G.o[i], (v >> i) & 1);
}
/* ─── Phase tracking ────────────────────────────────────────────────────── */
static void on_phase(void* user_data) {
(void)user_data;
if (G.state != S_AFTER_SYNC) return;
G.phase_count++;
/* A faithful 4002 latches the SRC chip-select bits at X2 (phase 6
counting from A1=0) when CM is asserted. Without explicit X2
opcode tracking from the 4004, we approximate: capture the bus
contents at phase 6 IF CM is high. */
if (G.phase_count == 6 && vx_pin_read(G.cm)) {
uint8_t hi = read_d_nibble(); /* chip# (bits 3..2) | reg# (bits 1..0) */
G.selected = ((hi >> 2) & 3) == RAM4002_CHIP_PAIR;
if (G.selected) {
G.latched_reg = hi & 3;
}
} else if (G.phase_count == 7 && G.selected && vx_pin_read(G.cm)) {
G.latched_char = read_d_nibble() & 0xF;
}
}
static void on_sync(void* user_data, vx_pin pin, int value) {
(void)user_data; (void)pin;
if (value) {
G.state = S_AFTER_SYNC;
G.phase_count = 0;
}
}
static void on_reset(void* user_data, vx_pin pin, int value) {
(void)user_data; (void)pin;
if (value) {
memset(G.main, 0, sizeof G.main);
memset(G.status, 0, sizeof G.status);
drive_output(0);
G.selected = false;
G.latched_reg = 0;
G.latched_char = 0;
release_d();
}
}
void chip_setup(void) {
char name[5];
for (int i = 0; i < 4; i++) {
name[0]='D'; name[1]='0'+i; name[2]=0;
G.d[i] = vx_pin_register(name, VX_INPUT);
}
for (int i = 0; i < 4; i++) {
name[0]='O'; name[1]='0'+i; name[2]=0;
G.o[i] = vx_pin_register(name, VX_OUTPUT_LOW);
}
G.sync = vx_pin_register("SYNC", VX_INPUT);
G.cl = vx_pin_register("CL", VX_INPUT);
G.reset_ = vx_pin_register("RESET", VX_INPUT);
G.cm = vx_pin_register("CM", VX_INPUT);
G.vdd = vx_pin_register("VDD", VX_INPUT);
G.vss = vx_pin_register("VSS", VX_INPUT);
memset(G.main, 0, sizeof G.main);
memset(G.status, 0, sizeof G.status);
G.output_port = 0;
G.state = S_IDLE;
G.phase_count = 0;
G.selected = false;
G.driving_d = false;
vx_pin_watch(G.sync, VX_EDGE_RISING, on_sync, 0);
vx_pin_watch(G.reset_, VX_EDGE_RISING, on_reset, 0);
G.phase_timer = vx_timer_create(on_phase, 0);
vx_timer_start(G.phase_timer, 1351, true);
}