667 lines
23 KiB
C
667 lines
23 KiB
C
/*
|
|
* Intel 4040 emulator — clean-room implementation as a velxio custom chip.
|
|
*
|
|
* Source (in autosearch/pdfs/):
|
|
* [M40] Intel MCS-40 User's Manual (Nov 1974). Page numbers 1-x are
|
|
* the printed Ch. 1 footers.
|
|
* [M4] Intel MCS-4 User's Manual (Feb 1973) — for the 46 base
|
|
* opcodes inherited from 4004.
|
|
* See autosearch/13_4040_authoritative_spec.md for citations.
|
|
*
|
|
* The 4040 is a binary-compatible superset of the 4004. The 46 4004
|
|
* opcodes execute identically; 14 new opcodes occupy OPR=0000
|
|
* OPA=0x01..0x0E (NOP=0x00 is preserved).
|
|
*
|
|
* New 4040 instructions (all 1-byte, [M40] p. 1-22):
|
|
* HLT 0x01 — halt
|
|
* BBS 0x02 — branch-back from interrupt subroutine (pop PC + clear INTA)
|
|
* LCR 0x03 — ACC ← Command Register
|
|
* OR4 0x04 — ACC ← ACC OR R4
|
|
* OR5 0x05 — ACC ← ACC OR R5
|
|
* AN6 0x06 — ACC ← ACC AND R6
|
|
* AN7 0x07 — ACC ← ACC AND R7
|
|
* DB0 0x08 — designate ROM bank 0 (CMROM0); takes effect 3 cycles later
|
|
* DB1 0x09 — designate ROM bank 1 (CMROM1)
|
|
* SB0 0x0A — select index-register bank 0 (R0..R7 = reg[0..7])
|
|
* SB1 0x0B — select index-register bank 1 (R0..R7 = reg[16..23])
|
|
* EIN 0x0C — enable interrupt (set IFF)
|
|
* DIN 0x0D — disable interrupt
|
|
* RPM 0x0E — read program memory (4289 stub)
|
|
*
|
|
* Index register bank model ([M40] p. 1-11):
|
|
* Physical reg[0..7] = bank 0's R0..R7
|
|
* Physical reg[8..15] = shared upper R8..R15
|
|
* Physical reg[16..23] = bank 1's R0..R7
|
|
* Bank flag selects which physical slice R0..R7 maps to.
|
|
*/
|
|
#include "velxio-chip.h"
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
typedef enum {
|
|
PHASE_A1 = 0, PHASE_A2, PHASE_A3,
|
|
PHASE_M1, PHASE_M2,
|
|
PHASE_X1, PHASE_X2, PHASE_X3,
|
|
} phase_t;
|
|
|
|
typedef enum {
|
|
FETCH_OPCODE = 0,
|
|
FETCH_OPERAND,
|
|
} fetch_t;
|
|
|
|
/* X2/X3 bus action selected at end of M2 from the decoded opcode.
|
|
Same set as the 4004 (the 4040 inherits MCS-4 I/O semantics). */
|
|
typedef enum {
|
|
XACT_NONE = 0,
|
|
XACT_SRC, /* drive pair_hi at X2, pair_lo at X3, CM-RAM strobe */
|
|
XACT_WRM_WMP, /* drive ACC at X2, CM-RAM (or CM-ROM for WRR/WPM) */
|
|
XACT_RDM, /* release D at X2, sample → io_data_in (4002 drives) */
|
|
XACT_RDS, /* RDR — release D at X2, sample (CM-ROM strobed) */
|
|
XACT_ADM_SBM, /* like RDM but feeds ADD/SUB */
|
|
XACT_WR_STATUS, /* WR0..WR3 — drive ACC at X2 */
|
|
XACT_RD_STATUS, /* RD0..RD3 — release at X2, sample */
|
|
} xact_t;
|
|
|
|
typedef struct {
|
|
/* Pin handles — names from [M40] pp. 1-5/1-6 */
|
|
vx_pin dpin[4];
|
|
vx_pin sync;
|
|
vx_pin reset;
|
|
vx_pin test;
|
|
vx_pin cmrom[2];
|
|
vx_pin cmram[4];
|
|
vx_pin clk1, clk2;
|
|
vx_pin stp, stpa;
|
|
vx_pin intn, inta;
|
|
vx_pin cy_pin;
|
|
vx_pin vdd, vdd1, vdd2, vss;
|
|
|
|
vx_timer cycle_timer;
|
|
|
|
/* CPU state */
|
|
uint16_t pc;
|
|
uint8_t acc;
|
|
bool cy;
|
|
uint8_t reg[24]; /* bank0:[0..7] shared:[8..15] bank1:[16..23] */
|
|
uint8_t bank; /* 0 (SB0) or 1 (SB1) */
|
|
uint16_t stack[7]; /* 7-deep PC stack */
|
|
uint8_t sp;
|
|
uint8_t cmram_select;
|
|
uint8_t rom_bank; /* 0 or 1 — set by DB0/DB1 */
|
|
bool iff_enable;
|
|
uint8_t cmd_reg; /* command register accessible via LCR */
|
|
|
|
/* Bus / fetch state */
|
|
int phase;
|
|
uint8_t opcode;
|
|
uint8_t operand;
|
|
fetch_t fetch_state;
|
|
bool reset_active;
|
|
bool driving_d;
|
|
bool pc_overridden;
|
|
|
|
/* Latched control inputs */
|
|
bool stp_latched;
|
|
bool int_latched;
|
|
bool stop_ff;
|
|
bool halt_ff;
|
|
bool inta_ff;
|
|
|
|
/* X2/X3 staging — populated at M2 from the decoded opcode. */
|
|
xact_t xact;
|
|
uint8_t xact_pair;
|
|
uint8_t xact_status_idx;
|
|
uint8_t io_data_in;
|
|
} cpu_t;
|
|
|
|
static cpu_t G;
|
|
|
|
/* ─── Bank-aware register access ────────────────────────────────────────── */
|
|
static uint8_t* regp(uint8_t n) {
|
|
n &= 0xF;
|
|
if (n >= 8) return &G.reg[n]; /* R8..R15 are shared */
|
|
return G.bank ? &G.reg[n + 16] : &G.reg[n]; /* R0..R7 follow SB0/SB1 */
|
|
}
|
|
|
|
/* Register-pair: Pn = (2n, 2n+1), n=0..7 */
|
|
static uint8_t pair_read(uint8_t p) {
|
|
uint8_t hi_idx = (p << 1) & 0xE;
|
|
uint8_t lo_idx = hi_idx + 1;
|
|
return (*regp(hi_idx) << 4) | *regp(lo_idx);
|
|
}
|
|
static void pair_write(uint8_t p, uint8_t v) {
|
|
uint8_t hi_idx = (p << 1) & 0xE;
|
|
uint8_t lo_idx = hi_idx + 1;
|
|
*regp(hi_idx) = (v >> 4) & 0xF;
|
|
*regp(lo_idx) = v & 0xF;
|
|
}
|
|
|
|
/* ─── D-bus helpers ─────────────────────────────────────────────────────── */
|
|
static void drive_d(uint8_t nibble) {
|
|
for (int i = 0; i < 4; i++) {
|
|
vx_pin_set_mode(G.dpin[i], VX_OUTPUT);
|
|
vx_pin_write(G.dpin[i], (nibble >> 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.dpin[i], VX_INPUT);
|
|
G.driving_d = false;
|
|
}
|
|
static uint8_t read_d(void) {
|
|
uint8_t v = 0;
|
|
for (int i = 0; i < 4; i++) if (vx_pin_read(G.dpin[i])) v |= (1u << i);
|
|
return v;
|
|
}
|
|
|
|
static vx_pin active_cmrom(void) { return G.cmrom[G.rom_bank & 1]; }
|
|
|
|
/* ─── Reset ─────────────────────────────────────────────────────────────── */
|
|
static void reset_state(void) {
|
|
G.pc = 0;
|
|
G.acc = 0;
|
|
G.cy = false;
|
|
memset(G.reg, 0, sizeof G.reg);
|
|
memset(G.stack, 0, sizeof G.stack);
|
|
G.sp = 0;
|
|
G.cmram_select = 0;
|
|
G.rom_bank = 0;
|
|
G.bank = 0;
|
|
G.iff_enable = false;
|
|
G.cmd_reg = 0;
|
|
G.phase = 0;
|
|
G.opcode = 0;
|
|
G.operand = 0;
|
|
G.fetch_state = FETCH_OPCODE;
|
|
G.pc_overridden = false;
|
|
G.stp_latched = false;
|
|
G.int_latched = false;
|
|
G.stop_ff = false;
|
|
G.halt_ff = false;
|
|
G.inta_ff = false;
|
|
G.xact = XACT_NONE;
|
|
G.io_data_in = 0;
|
|
|
|
vx_pin_write(G.sync, 0);
|
|
vx_pin_write(G.cmrom[0], 0);
|
|
vx_pin_write(G.cmrom[1], 0);
|
|
for (int i = 0; i < 4; i++) vx_pin_write(G.cmram[i], 0);
|
|
vx_pin_write(G.stpa, 0);
|
|
vx_pin_write(G.inta, 0);
|
|
vx_pin_write(G.cy_pin, 0);
|
|
release_d();
|
|
}
|
|
|
|
/* ─── ALU helpers ────────────────────────────────────────────────────────── */
|
|
static bool is_two_byte(uint8_t op) {
|
|
uint8_t hi = (op >> 4) & 0xF;
|
|
if (hi == 0x1) return true;
|
|
if (hi == 0x2) return (op & 1) == 0;
|
|
if (hi == 0x4) return true;
|
|
if (hi == 0x5) return true;
|
|
if (hi == 0x7) return true;
|
|
return false;
|
|
}
|
|
|
|
static bool jcn_condition(uint8_t opa) {
|
|
uint8_t c1 = (opa >> 3) & 1;
|
|
uint8_t c2 = (opa >> 2) & 1;
|
|
uint8_t c3 = (opa >> 1) & 1;
|
|
uint8_t c4 = (opa >> 0) & 1;
|
|
int test_pin = vx_pin_read(G.test) ? 1 : 0;
|
|
bool any = (c2 && (G.acc == 0))
|
|
|| (c3 && G.cy)
|
|
|| (c4 && (test_pin == 0));
|
|
return c1 ? !any : any;
|
|
}
|
|
|
|
/* 7-deep stack ([M40] p. 1-12) — overflow drops oldest. */
|
|
static void stack_push(uint16_t value) {
|
|
for (int i = 6; i > 0; i--) G.stack[i] = G.stack[i-1];
|
|
G.stack[0] = value;
|
|
if (G.sp < 7) G.sp++;
|
|
}
|
|
static uint16_t stack_pop(void) {
|
|
uint16_t v = G.stack[0];
|
|
for (int i = 0; i < 6; i++) G.stack[i] = G.stack[i+1];
|
|
G.stack[6] = 0;
|
|
if (G.sp > 0) G.sp--;
|
|
return v;
|
|
}
|
|
|
|
static void daa(void) {
|
|
if (G.acc > 9 || G.cy) {
|
|
uint8_t r = G.acc + 6;
|
|
if (r > 0xF) G.cy = true;
|
|
G.acc = r & 0xF;
|
|
}
|
|
}
|
|
|
|
static void kbp(void) {
|
|
static const uint8_t kbp_lut[16] = {
|
|
0x0, 0x1, 0x2, 0xF,
|
|
0x3, 0xF, 0xF, 0xF,
|
|
0x4, 0xF, 0xF, 0xF,
|
|
0xF, 0xF, 0xF, 0xF,
|
|
};
|
|
G.acc = kbp_lut[G.acc & 0xF];
|
|
}
|
|
|
|
/* ─── Execute 1-byte instruction ────────────────────────────────────────── */
|
|
static void exec_1byte(uint8_t op) {
|
|
uint8_t hi = (op >> 4) & 0xF;
|
|
uint8_t lo = op & 0xF;
|
|
|
|
if (hi == 0x0) {
|
|
/* The 4040 places its 14 new instructions here, plus NOP at 0x00. */
|
|
switch (lo) {
|
|
case 0x0: /* NOP */ break;
|
|
case 0x1: /* HLT */
|
|
G.halt_ff = true;
|
|
G.stop_ff = true;
|
|
vx_pin_write(G.stpa, 1);
|
|
break;
|
|
case 0x2: /* BBS — branch back from interrupt subroutine */
|
|
G.pc = stack_pop() & 0xFFF;
|
|
G.inta_ff = false;
|
|
vx_pin_write(G.inta, 0);
|
|
G.pc_overridden = true;
|
|
/* SRC + bank FF restoration happens here on real silicon;
|
|
we don't fully model the SRC re-emit yet. */
|
|
break;
|
|
case 0x3: /* LCR — ACC ← Command Register */
|
|
G.acc = G.cmd_reg & 0xF;
|
|
break;
|
|
case 0x4: /* OR4 — ACC ← ACC OR R4 */
|
|
G.acc = (G.acc | *regp(4)) & 0xF;
|
|
break;
|
|
case 0x5: /* OR5 */
|
|
G.acc = (G.acc | *regp(5)) & 0xF;
|
|
break;
|
|
case 0x6: /* AN6 — ACC ← ACC AND R6 */
|
|
G.acc = (G.acc & *regp(6)) & 0xF;
|
|
break;
|
|
case 0x7: /* AN7 */
|
|
G.acc = (G.acc & *regp(7)) & 0xF;
|
|
break;
|
|
case 0x8: /* DB0 — designate ROM bank 0 */
|
|
G.rom_bank = 0;
|
|
break;
|
|
case 0x9: /* DB1 — designate ROM bank 1 */
|
|
G.rom_bank = 1;
|
|
break;
|
|
case 0xA: /* SB0 — select index-register bank 0 */
|
|
G.bank = 0;
|
|
break;
|
|
case 0xB: /* SB1 — select index-register bank 1 */
|
|
G.bank = 1;
|
|
break;
|
|
case 0xC: /* EIN — enable interrupt */
|
|
G.iff_enable = true;
|
|
break;
|
|
case 0xD: /* DIN — disable interrupt */
|
|
G.iff_enable = false;
|
|
break;
|
|
case 0xE: /* RPM — read program memory (4289 stub) */
|
|
G.acc = 0;
|
|
break;
|
|
/* 0xF unused */
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* The remaining 1-byte opcodes are inherited from the 4004. */
|
|
switch (hi) {
|
|
case 0x2: { /* SRC Pn (odd opcodes only) */
|
|
(void)pair_read(lo >> 1);
|
|
break;
|
|
}
|
|
case 0x3: {
|
|
uint8_t pair_idx = lo >> 1;
|
|
if ((lo & 1) == 0) {
|
|
/* FIN Pn — stub */
|
|
(void)pair_idx;
|
|
} else {
|
|
G.pc = (G.pc & 0xF00) | pair_read(pair_idx);
|
|
G.pc_overridden = true;
|
|
}
|
|
break;
|
|
}
|
|
case 0x6: /* INC Rn */
|
|
*regp(lo) = (*regp(lo) + 1) & 0xF;
|
|
break;
|
|
case 0x8: { /* ADD Rn */
|
|
uint8_t r = G.acc + *regp(lo) + (G.cy ? 1 : 0);
|
|
G.cy = (r > 0xF);
|
|
G.acc = r & 0xF;
|
|
break;
|
|
}
|
|
case 0x9: { /* SUB Rn */
|
|
uint8_t r = G.acc + ((~*regp(lo)) & 0xF) + (G.cy ? 0 : 1);
|
|
G.cy = (r > 0xF);
|
|
G.acc = r & 0xF;
|
|
break;
|
|
}
|
|
case 0xA: G.acc = *regp(lo); break; /* LD Rn */
|
|
case 0xB: { /* XCH Rn */
|
|
uint8_t t = G.acc;
|
|
G.acc = *regp(lo);
|
|
*regp(lo) = t;
|
|
break;
|
|
}
|
|
case 0xC: /* BBL d */
|
|
G.pc = stack_pop() & 0xFFF;
|
|
G.acc = lo;
|
|
G.pc_overridden = true;
|
|
break;
|
|
case 0xD: G.acc = lo; break; /* LDM d */
|
|
case 0xE: /* I/O / RAM group — bus heavy lifting happened during
|
|
X2/X3; here we only update ACC/flags from io_data_in
|
|
for read ops. Writes have no further effect on CPU
|
|
state (output side of the 4002/4001 was driven by
|
|
the X2 bus action). */
|
|
switch (lo) {
|
|
case 0x8: { /* SBM — A ← A + ~RAM + ~CY */
|
|
uint8_t r = G.acc + ((~G.io_data_in) & 0xF) + (G.cy ? 0 : 1);
|
|
G.cy = (r > 0xF);
|
|
G.acc = r & 0xF;
|
|
break;
|
|
}
|
|
case 0x9: G.acc = G.io_data_in; break; /* RDM */
|
|
case 0xA: G.acc = G.io_data_in; break; /* RDR */
|
|
case 0xB: { /* ADM — A ← A + RAM + CY */
|
|
uint8_t r = G.acc + G.io_data_in + (G.cy ? 1 : 0);
|
|
G.cy = (r > 0xF);
|
|
G.acc = r & 0xF;
|
|
break;
|
|
}
|
|
case 0xC: case 0xD: case 0xE: case 0xF: /* RD0..RD3 */
|
|
G.acc = G.io_data_in;
|
|
break;
|
|
/* 0,1,2,3,4,5,6,7 = WRM/WMP/WRR/WPM/WR0..3 — bus drives
|
|
ACC at X2; nothing more for the CPU side. */
|
|
default: break;
|
|
}
|
|
break;
|
|
case 0xF: /* ACC group */
|
|
switch (lo) {
|
|
case 0x0: G.acc = 0; G.cy = false; break;
|
|
case 0x1: G.cy = false; break;
|
|
case 0x2: { uint8_t r = G.acc + 1; G.cy = (r > 0xF); G.acc = r & 0xF; break; }
|
|
case 0x3: G.cy = !G.cy; break;
|
|
case 0x4: G.acc = (~G.acc) & 0xF; break;
|
|
case 0x5: { uint8_t b3 = (G.acc >> 3) & 1;
|
|
G.acc = ((G.acc << 1) | (G.cy ? 1 : 0)) & 0xF;
|
|
G.cy = b3 != 0; break; }
|
|
case 0x6: { uint8_t b0 = G.acc & 1;
|
|
G.acc = ((G.acc >> 1) | ((G.cy ? 1 : 0) << 3)) & 0xF;
|
|
G.cy = b0 != 0; break; }
|
|
case 0x7: G.acc = G.cy ? 1 : 0; G.cy = false; break;
|
|
case 0x8: { uint8_t r = G.acc + 0xF; G.cy = (r > 0xF); G.acc = r & 0xF; break; }
|
|
case 0x9: G.acc = G.cy ? 0xA : 0x9; G.cy = false; break;
|
|
case 0xA: G.cy = true; break;
|
|
case 0xB: daa(); break;
|
|
case 0xC: kbp(); break;
|
|
case 0xD: G.cmram_select = G.acc & 7; G.cmd_reg = G.acc & 7; break;
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
/* ─── Execute 2-byte instruction ────────────────────────────────────────── */
|
|
static void exec_2byte(uint8_t op, uint8_t operand) {
|
|
uint8_t hi = (op >> 4) & 0xF;
|
|
uint8_t lo = op & 0xF;
|
|
|
|
switch (hi) {
|
|
case 0x1:
|
|
if (jcn_condition(lo)) {
|
|
G.pc = (G.pc & 0xF00) | operand;
|
|
G.pc_overridden = true;
|
|
}
|
|
break;
|
|
case 0x2:
|
|
pair_write(lo >> 1, operand);
|
|
break;
|
|
case 0x4:
|
|
G.pc = (((uint16_t)lo) << 8) | operand;
|
|
G.pc_overridden = true;
|
|
break;
|
|
case 0x5:
|
|
stack_push(G.pc & 0xFFF);
|
|
G.pc = (((uint16_t)lo) << 8) | operand;
|
|
G.pc_overridden = true;
|
|
break;
|
|
case 0x7: {
|
|
uint8_t v = (*regp(lo) + 1) & 0xF;
|
|
*regp(lo) = v;
|
|
if (v != 0) {
|
|
G.pc = (G.pc & 0xF00) | operand;
|
|
G.pc_overridden = true;
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
/* ─── Per-phase action ───────────────────────────────────────────────────── */
|
|
static void on_phase(void* user_data) {
|
|
(void)user_data;
|
|
if (G.reset_active) return;
|
|
|
|
if (G.phase == PHASE_A1) {
|
|
vx_pin_write(G.cmrom[0], 0);
|
|
vx_pin_write(G.cmrom[1], 0);
|
|
for (int i = 0; i < 4; i++) vx_pin_write(G.cmram[i], 0);
|
|
}
|
|
|
|
switch (G.phase) {
|
|
case PHASE_A1:
|
|
drive_d(G.pc & 0xF);
|
|
vx_pin_write(G.sync, 1);
|
|
break;
|
|
case PHASE_A2:
|
|
vx_pin_write(G.sync, 0);
|
|
drive_d((G.pc >> 4) & 0xF);
|
|
break;
|
|
case PHASE_A3:
|
|
drive_d((G.pc >> 8) & 0xF);
|
|
break;
|
|
case PHASE_M1:
|
|
release_d();
|
|
vx_pin_write(active_cmrom(), 1);
|
|
if (G.fetch_state == FETCH_OPCODE) {
|
|
G.opcode = (read_d() & 0xF) << 4;
|
|
} else {
|
|
G.operand = (read_d() & 0xF) << 4;
|
|
}
|
|
break;
|
|
case PHASE_M2:
|
|
if (G.fetch_state == FETCH_OPCODE) {
|
|
G.opcode |= read_d() & 0xF;
|
|
} else {
|
|
G.operand |= read_d() & 0xF;
|
|
}
|
|
G.stp_latched = vx_pin_read(G.stp) ? true : false;
|
|
G.int_latched = (G.iff_enable && !G.stp_latched && !G.inta_ff
|
|
&& vx_pin_read(G.intn)) ? true : false;
|
|
/* Decode opcode → set up X2/X3 bus action (mirrors 4004). */
|
|
G.xact = XACT_NONE;
|
|
if (G.fetch_state == FETCH_OPCODE) {
|
|
uint8_t op = G.opcode;
|
|
if ((op & 0xF1) == 0x21) {
|
|
G.xact = XACT_SRC;
|
|
G.xact_pair = (op >> 1) & 7;
|
|
} else if ((op & 0xF0) == 0xE0) {
|
|
uint8_t lo = op & 0xF;
|
|
switch (lo) {
|
|
case 0x0: case 0x1:
|
|
case 0x2: case 0x3:
|
|
G.xact = XACT_WRM_WMP; break;
|
|
case 0x4: case 0x5: case 0x6: case 0x7:
|
|
G.xact = XACT_WR_STATUS;
|
|
G.xact_status_idx = lo - 4;
|
|
break;
|
|
case 0x8: case 0xB: G.xact = XACT_ADM_SBM; break;
|
|
case 0x9: G.xact = XACT_RDM; break;
|
|
case 0xA: G.xact = XACT_RDS; break;
|
|
case 0xC: case 0xD: case 0xE: case 0xF:
|
|
G.xact = XACT_RD_STATUS;
|
|
G.xact_status_idx = lo - 0xC;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case PHASE_X1:
|
|
vx_pin_write(G.cy_pin, G.cy ? 1 : 0);
|
|
break;
|
|
case PHASE_X2:
|
|
switch (G.xact) {
|
|
case XACT_SRC:
|
|
drive_d((pair_read(G.xact_pair) >> 4) & 0xF);
|
|
vx_pin_write(G.cmram[G.cmram_select & 3], 1);
|
|
break;
|
|
case XACT_WRM_WMP: {
|
|
drive_d(G.acc & 0xF);
|
|
uint8_t lo = G.opcode & 0xF;
|
|
if (lo == 0x2 || lo == 0x3) {
|
|
vx_pin_write(active_cmrom(), 1);
|
|
} else {
|
|
vx_pin_write(G.cmram[G.cmram_select & 3], 1);
|
|
}
|
|
break;
|
|
}
|
|
case XACT_WR_STATUS:
|
|
drive_d(G.acc & 0xF);
|
|
vx_pin_write(G.cmram[G.cmram_select & 3], 1);
|
|
break;
|
|
case XACT_RDM:
|
|
case XACT_ADM_SBM:
|
|
case XACT_RD_STATUS:
|
|
release_d();
|
|
vx_pin_write(G.cmram[G.cmram_select & 3], 1);
|
|
G.io_data_in = read_d() & 0xF;
|
|
break;
|
|
case XACT_RDS:
|
|
release_d();
|
|
vx_pin_write(active_cmrom(), 1);
|
|
G.io_data_in = read_d() & 0xF;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case PHASE_X3: {
|
|
switch (G.xact) {
|
|
case XACT_SRC:
|
|
drive_d(pair_read(G.xact_pair) & 0xF);
|
|
break;
|
|
case XACT_WRM_WMP:
|
|
case XACT_WR_STATUS:
|
|
/* drive held from X2 */
|
|
break;
|
|
case XACT_RDM:
|
|
case XACT_ADM_SBM:
|
|
case XACT_RD_STATUS:
|
|
case XACT_RDS:
|
|
vx_pin_write(G.cmram[G.cmram_select & 3], 0);
|
|
vx_pin_write(active_cmrom(), 0);
|
|
release_d();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
G.pc_overridden = false;
|
|
|
|
if (G.stp_latched) {
|
|
G.stop_ff = true;
|
|
vx_pin_write(G.stpa, 1);
|
|
} else if (!G.halt_ff) {
|
|
G.stop_ff = false;
|
|
vx_pin_write(G.stpa, 0);
|
|
}
|
|
|
|
if (G.int_latched && !G.stop_ff) {
|
|
stack_push(G.pc & 0xFFF);
|
|
G.pc = 0x003;
|
|
G.iff_enable = false;
|
|
G.inta_ff = true;
|
|
vx_pin_write(G.inta, 1);
|
|
} else if (!G.stop_ff && !G.halt_ff) {
|
|
if (G.fetch_state == FETCH_OPCODE) {
|
|
if (is_two_byte(G.opcode)) {
|
|
G.pc = (G.pc + 1) & 0xFFF;
|
|
G.fetch_state = FETCH_OPERAND;
|
|
} else {
|
|
exec_1byte(G.opcode);
|
|
if (!G.pc_overridden) G.pc = (G.pc + 1) & 0xFFF;
|
|
}
|
|
} else {
|
|
G.pc = (G.pc + 1) & 0xFFF;
|
|
exec_2byte(G.opcode, G.operand);
|
|
G.fetch_state = FETCH_OPCODE;
|
|
}
|
|
}
|
|
|
|
G.stp_latched = false;
|
|
G.int_latched = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
G.phase = (G.phase + 1) & 7;
|
|
}
|
|
|
|
/* ─── RESET pin watch ────────────────────────────────────────────────────── */
|
|
static void on_reset(void* user_data, vx_pin pin, int value) {
|
|
(void)user_data; (void)pin;
|
|
if (value) {
|
|
G.reset_active = true;
|
|
reset_state();
|
|
} else {
|
|
G.reset_active = false;
|
|
}
|
|
}
|
|
|
|
void chip_setup(void) {
|
|
char name[6];
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
name[0]='D'; name[1]='0'+i; name[2]=0;
|
|
G.dpin[i] = vx_pin_register(name, VX_INPUT);
|
|
}
|
|
G.sync = vx_pin_register("SYNC", VX_OUTPUT_LOW);
|
|
G.reset = vx_pin_register("RESET", VX_INPUT);
|
|
G.test = vx_pin_register("TEST", VX_INPUT);
|
|
G.cmrom[0] = vx_pin_register("CMROM0", VX_OUTPUT_LOW);
|
|
G.cmrom[1] = vx_pin_register("CMROM1", VX_OUTPUT_LOW);
|
|
G.cmram[0] = vx_pin_register("CMRAM0", VX_OUTPUT_LOW);
|
|
G.cmram[1] = vx_pin_register("CMRAM1", VX_OUTPUT_LOW);
|
|
G.cmram[2] = vx_pin_register("CMRAM2", VX_OUTPUT_LOW);
|
|
G.cmram[3] = vx_pin_register("CMRAM3", VX_OUTPUT_LOW);
|
|
G.clk1 = vx_pin_register("CLK1", VX_INPUT);
|
|
G.clk2 = vx_pin_register("CLK2", VX_INPUT);
|
|
G.stp = vx_pin_register("STP", VX_INPUT);
|
|
G.stpa = vx_pin_register("STPA", VX_OUTPUT_LOW);
|
|
G.intn = vx_pin_register("INT", VX_INPUT);
|
|
G.inta = vx_pin_register("INTA", VX_OUTPUT_LOW);
|
|
G.cy_pin = vx_pin_register("CY", VX_OUTPUT_LOW);
|
|
G.vdd = vx_pin_register("VDD", VX_INPUT);
|
|
G.vdd1 = vx_pin_register("VDD1", VX_INPUT);
|
|
G.vdd2 = vx_pin_register("VDD2", VX_INPUT);
|
|
G.vss = vx_pin_register("VSS", VX_INPUT);
|
|
|
|
reset_state();
|
|
G.reset_active = false;
|
|
|
|
vx_pin_watch(G.reset, VX_EDGE_BOTH, on_reset, 0);
|
|
|
|
G.cycle_timer = vx_timer_create(on_phase, 0);
|
|
vx_timer_start(G.cycle_timer, 1351, true);
|
|
}
|