From adc99a803555050bfa2ccab14ade1889140282e6 Mon Sep 17 00:00:00 2001 From: David Montero Date: Fri, 1 May 2026 03:07:11 +0200 Subject: [PATCH] =?UTF-8?q?test=5Fintel:=20phase=20D-3=20=E2=80=94=204040?= =?UTF-8?q?=20SRC=20+=20I/O=20bus=20wiring=20(4004=20parity)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the same xact_t pattern from the 4004 (phase D-2) to the 4040, so SRC and the I/O group (WRM/WMP/WRR/WPM/WR0..3/SBM/RDM/RDR/ADM/ RD0..3) drive or sample the multiplexed nibble bus during X2/X3 with CM-RAM (or CM-ROM for ROM-port ops) strobed. The 4040's two CM-ROM lines (selected by rom_bank) and its STP/INT control flow are unchanged — the bus action is staged at M2 and acted on at X2/X3, fitting cleanly inside the existing PHASE_X3 control-flow block. Two new integration tests under "4040 + 4002 RAM integration" mirror the 4004's: SRC + WMP drives the output port, and SRC + WRM/RDM round-trips a nibble through 4002 storage. Total test_intel: 117 passing, 11 todo, 0 failed (was 115). Co-Authored-By: Claude Opus 4.7 (1M context) --- test/test_intel/test_4040/4040.c | 127 +++++++++++++++++++++++-- test/test_intel/test_4040/4040.test.js | 83 ++++++++++++++++ 2 files changed, 201 insertions(+), 9 deletions(-) diff --git a/test/test_intel/test_4040/4040.c b/test/test_intel/test_4040/4040.c index 42f5a2e8..d855f80c 100644 --- a/test/test_intel/test_4040/4040.c +++ b/test/test_intel/test_4040/4040.c @@ -50,6 +50,19 @@ typedef enum { 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]; @@ -94,6 +107,12 @@ typedef struct { 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; @@ -162,6 +181,8 @@ static void reset_state(void) { 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); @@ -336,24 +357,31 @@ static void exec_1byte(uint8_t op) { G.pc_overridden = true; break; case 0xD: G.acc = lo; break; /* LDM d */ - case 0xE: /* I/O / RAM group — stubs (no real RAM/ROM ports yet) */ + 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 */ - uint8_t r = G.acc + 0xF + (G.cy ? 0 : 1); + 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: case 0xA: case 0xC: case 0xD: case 0xE: case 0xF: - G.acc = 0; - break; - case 0xB: { /* ADM (RAM=0) */ - uint8_t r = G.acc + 0 + (G.cy ? 1 : 0); + 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; } - /* 0,1,2,3,4,5,6,7 = WRM/WMP/WRR/WPM/WR0..3 — write stubs */ + 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; @@ -428,6 +456,7 @@ static void on_phase(void* user_data) { 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) { @@ -460,13 +489,93 @@ static void on_phase(void* user_data) { 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) { diff --git a/test/test_intel/test_4040/4040.test.js b/test/test_intel/test_4040/4040.test.js index f199a920..c116b1a0 100644 --- a/test/test_intel/test_4040/4040.test.js +++ b/test/test_intel/test_4040/4040.test.js @@ -236,4 +236,87 @@ describe('Intel 4040 chip', () => { board.dispose(); }); }); + + describe('4040 + 4002 RAM integration', () => { + const RAM = '4002-ram'; + const skipIntegration = skip || !chipWasmExists(RAM); + + it.skipIf(skipIntegration)( + 'SRC + WMP drives the 4002 output port from ACC', + async () => { + // PC=0x00: 0xD3 LDM 3 → ACC=3 + // PC=0x01: 0x21 SRC P0 → drive R0:R1=0:0 → chip-pair=0 + // PC=0x02: 0xE1 WMP → 4002.O0..O3 = 3 + const PROG = new Uint8Array(0x40); + PROG[0] = 0xD3; + PROG[1] = 0x21; + PROG[2] = 0xE1; + + const board = new BoardHarness(); + // Register the 4002 BEFORE the 4040 (same ordering trick as + // 4004/4002 integration). 4040.CMRAM0 → 4002.CM. + await board.addChip(RAM, { + SYNC: 'SYNC', CL: 'CLK1', RESET: 'RESET', CM: 'CMRAM0', + VDD: 'VDD', VSS: 'VSS', + D0: 'D0', D1: 'D1', D2: 'D2', D3: 'D3', + O0: 'O0', O1: 'O1', O2: 'O2', O3: 'O3', + }); + await board.addChip(CHIP, fullPinMap()); + + board.setNet('STP', false); + board.setNet('INT', false); + board.setNet('TEST', false); + board.setNet('RESET', true); + board.advanceNanos(CLOCK_NS * 12); + board.setNet('RESET', false); + + const bus = new Bus4040(board, PROG); + for (let cyc = 0; cyc < 8; cyc++) bus.runCycle(); + + let out = 0; + for (let i = 0; i < 4; i++) if (board.getNet(`O${i}`)) out |= (1 << i); + expect(out, '4002 output port after WMP must equal ACC (= 3)').toBe(3); + board.dispose(); + } + ); + + it.skipIf(skipIntegration)( + 'WRM stores into RAM and RDM reads it back through the bus', + async () => { + // 0xD5 LDM 5 ; 0x21 SRC P0 ; 0xE0 WRM ; 0xF0 CLB + // 0xE9 RDM ; 0xE1 WMP ; 0x00 NOP + const PROG = new Uint8Array(0x40); + PROG[0] = 0xD5; + PROG[1] = 0x21; + PROG[2] = 0xE0; + PROG[3] = 0xF0; + PROG[4] = 0xE9; + PROG[5] = 0xE1; + + const board = new BoardHarness(); + await board.addChip(RAM, { + SYNC: 'SYNC', CL: 'CLK1', RESET: 'RESET', CM: 'CMRAM0', + VDD: 'VDD', VSS: 'VSS', + D0: 'D0', D1: 'D1', D2: 'D2', D3: 'D3', + O0: 'O0', O1: 'O1', O2: 'O2', O3: 'O3', + }); + await board.addChip(CHIP, fullPinMap()); + + board.setNet('STP', false); + board.setNet('INT', false); + board.setNet('TEST', false); + board.setNet('RESET', true); + board.advanceNanos(CLOCK_NS * 12); + board.setNet('RESET', false); + + const bus = new Bus4040(board, PROG); + for (let cyc = 0; cyc < 12; cyc++) bus.runCycle(); + + let out = 0; + for (let i = 0; i < 4; i++) if (board.getNet(`O${i}`)) out |= (1 << i); + expect(out, 'WMP after RDM must surface the mem-stored 5').toBe(5); + board.dispose(); + } + ); + }); });