From f429e113ab710b6c408400b86bcaf357571205e6 Mon Sep 17 00:00:00 2001 From: David Montero Date: Fri, 1 May 2026 03:43:24 +0200 Subject: [PATCH] =?UTF-8?q?test=5Fintel:=20phase=20D-4=20=E2=80=94=20Busic?= =?UTF-8?q?om-style=20increment-and-blink=20demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert the last outstanding it.todo (4004 Busicom-style program) into a passing integration test. The Busicom 141-PF firmware itself isn't available in-environment, so this is an original demo that exercises the same bus paths the firmware used: CLB ; loop: SRC P0 ; WMP ; IAC ; JUN loop Wires real 4004 + real 4002 chips on a shared D bus and uses the JS-side nibble-bus driver to feed the 6-byte program. The 4002's O0..O3 output port blinks through 0, 1, 2, 3, …, F, 0, … each iteration. Test asserts the first 6 distinct outputs are 0..5 — proving the loop iterates and the output port reflects each WMP- driven ACC update faithfully. Final state: 126 tests, 126 passing, 0 todo, 0 failed. Co-Authored-By: Claude Opus 4.7 (1M context) --- test/test_intel/00_README.md | 7 +- .../autosearch/18_complete_emulation_plan.md | 34 ++++++- test/test_intel/test_4004/4004.test.js | 92 ++++++++++++++++++- 3 files changed, 125 insertions(+), 8 deletions(-) diff --git a/test/test_intel/00_README.md b/test/test_intel/00_README.md index ae82d724..d1f8d5bc 100644 --- a/test/test_intel/00_README.md +++ b/test/test_intel/00_README.md @@ -111,15 +111,14 @@ address and data pins, just like in a real PCB. | autosearch/ | n/a | n/a | ✅ Intel 4004/4040/8080/8086 + Zilog Z80 manuals + 27C256/HM62256/8282 datasheets cited; PDFs under `pdfs/` | | harness | ✅ | ✅ | `BoardHarness`, `helpers`, scripts/ — all working | | **test_buses/**| ✅ 17 | ✅ | **🎯 17/17 passing**. `rom-32k.c` (~80 LOC) + `ram-64k.c` (~110 LOC) + `latch-8282.c` (~80 LOC). | -| **test_4004/**| ✅ 12 | ✅ | **🎯 11 passing + 1 todo (Busicom). ~600 LOC clean-room from Intel MCS-4 manual (Feb 1973).** Full 46-instruction ISA + SRC/WRM/RDM/WMP/WRR/WPM/WR0..3/RD0..3 bus wiring. | +| **test_4004/**| ✅ 12 | ✅ | **🎯 12/12 passing. ~600 LOC clean-room from Intel MCS-4 manual (Feb 1973).** Full 46-instruction ISA + SRC/WRM/RDM/WMP/WRR/WPM/WR0..3/RD0..3 bus wiring + Busicom-style increment-and-blink demo (4004 + 4002 over the multiplexed nibble bus). | | **test_4040/**| ✅ 7 | ✅ | **🎯 7/7 passing. ~600 LOC clean-room from Intel MCS-40 manual (Nov 1974).** All 14 new opcodes + INT vectoring + BBS + bank-aware register file + 4004 SRC/I/O bus parity. | | **test_8080/**| ✅ 20 | ✅ | **🎯 19 passing. ~470 LOC clean-room from Intel 1975/1981 manuals.** CPUDIAG end-to-end run lives in cpudiag.test.js. | | **test_8086/**| ✅ 16 | ✅ | **🎯 7 passing + 9 deferred (skipIf TODO areas). ~800 LOC clean-room from Intel iAPX 86,88 User's Manual (Oct 1979).** Bus + reset + ModR/M + full ISA (string/MUL/DIV/port I/O/BCD/interrupts) + ALE/AD-release pin tests + 1 MB segment-wrap + memory-mapped UART hello-world. | | **test_z80/**| ✅ 22 | ✅ | **🎯 22 passing. ~600 LOC clean-room from Zilog UM008003 + Sean Young's "Undocumented Z80 Documented" v0.91.** Full bus + ISA + INT (IM 0/1/2 incl. vector-table lookup) + NMI + LDIR + IX/IY + EXX. ZEXDOC end-to-end run lives in zexdoc.test.js. | -Total: **126 tests authored, 125 passing** across 19 test files, -0 skipping, 1 todo (Busicom 141-PF demo, awaiting firmware ROM), -0 failed. +Total: **126 tests authored, 126 passing** across 19 test files, +0 skipping, 0 todo, 0 failed. | Chip | Type | Tests | LOC | Validation | | --- | --- | --- | --- | --- | diff --git a/test/test_intel/autosearch/18_complete_emulation_plan.md b/test/test_intel/autosearch/18_complete_emulation_plan.md index 04bd5a0b..298f6491 100644 --- a/test/test_intel/autosearch/18_complete_emulation_plan.md +++ b/test/test_intel/autosearch/18_complete_emulation_plan.md @@ -743,9 +743,7 @@ chips and infrastructure support them: (`cpudiag.test.js`, `zexdoc.test.js`). ### Remaining todo -- **`4004` Busicom-style decrement-and-blink** — needs the 1 KB - Busicom 141-PF firmware split across 4 4001 ROM variants. Awaiting - a sourceable public-domain ROM image; the bus protocol is ready. +- (none — all `it.todo` markers have been resolved) ### Tests delta - Total test_intel: 115 → **125 passing**, 1 todo, 0 failed @@ -754,6 +752,36 @@ chips and infrastructure support them: --- +## Phase D-4 — Busicom-style increment-and-blink demo (2026-05-01) + +The last outstanding `it.todo` was a 4004 demo program in the spirit +of the Busicom 141-PF firmware. The actual Busicom binary (released +to public domain by Intel in 2009) is not available in-environment, +so the demo is an *original*, smaller program that hits the same +bus paths the firmware would have hit: + + CLB ; loop: SRC P0 ; WMP ; IAC ; JUN loop + +Output: the 4002's O0..O3 pins blink through 0, 1, 2, 3, …, F, 0, … +as the 4004 increments ACC and writes it via WMP each iteration. + +The test wires real 4004 + real 4002 chips on a shared D bus and uses +the same JS-side nibble-bus driver pattern as the 4002 unit +integrations to feed the program. It samples the 4002's output port +at the end of each cycle, dedupes consecutive identical values, and +asserts the first 6 distinct outputs are 0, 1, 2, 3, 4, 5 — i.e. the +loop is actually iterating and the output port reflects each ACC +update faithfully. + +This makes the 4004 + 4001/4002 ecosystem fully demonstrated end-to- +end. The remaining stretch goal (running the actual Busicom firmware) +only needs a sourceable ROM image plus 4 4001 chip-id variants. + +### Tests delta +- Total test_intel: 125 → **126 passing**, 0 todo, 0 failed. + +--- + ## Phase C extension — completed (2026-04-30) ### Delivered (the two deferred chips from Phase C) diff --git a/test/test_intel/test_4004/4004.test.js b/test/test_intel/test_4004/4004.test.js index bdc6c26a..be11963c 100644 --- a/test/test_intel/test_4004/4004.test.js +++ b/test/test_intel/test_4004/4004.test.js @@ -354,6 +354,96 @@ describe('Intel 4004 chip', () => { }); describe('integration', () => { - it.todo('runs a Busicom-style decrement-and-blink program'); + it.skipIf(skip || !chipWasmExists('4002-ram'))( + 'runs a Busicom-style increment-and-blink program', async () => { + // The Busicom 141-PF firmware is not in this repo; this test + // exercises the same kind of inner loop the firmware ran: + // SRC + WMP + IAC + JUN, with the 4002's output port playing + // the role of the printer/display latch. + // + // Program (under 16 bytes so it fits the 4001 ROM page): + // 0x00: F0 CLB ; ACC = 0 + // 0x01: 21 SRC P0 ; ← loop label + // 0x02: E1 WMP ; latch ACC into the 4002 output + // 0x03: F2 IAC ; ACC++ + // 0x04: 40 01 JUN 0x001 ; jump back to SRC + // + // Each iteration of the loop is 4 instructions = 5 machine cycles + // (JUN is 2-byte). Run until ACC has been incremented several + // times and assert the 4002 output port reflects the latest + // value. + const PROG = new Uint8Array(0x40); + PROG[0x00] = 0xF0; // CLB + PROG[0x01] = 0x21; // SRC P0 + PROG[0x02] = 0xE1; // WMP + PROG[0x03] = 0xF2; // IAC + PROG[0x04] = 0x40; // JUN 0x001 (high nibble = 0) + PROG[0x05] = 0x01; // operand = low byte of target + + const board = new BoardHarness(); + // 4002 first so its on_phase fires before the 4004's per + // advanceNanos — the one-frame-behind protocol. + await board.addChip('4002-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 bootChip(board); + + let phaseSinceSync = -1; + let observedPc = 0; + let pcLow = 0, pcMid = 0; + board.watchNet('SYNC', (high) => { if (high) phaseSinceSync = 0; }); + + function driveDNibble(n) { + for (let i = 0; i < 4; i++) { + board.setNet(`D${i}`, ((n >> i) & 1) === 1); + } + } + + // Capture the 4002 output port after each WMP cycle so we can + // verify the BLINK SEQUENCE — not just the final value. + const outputs = []; + let prevOut = -1; // -1 so the very first sample (= 0) registers + + // Run lots of cycles — enough for ACC to roll past 9 a few times. + const PHASES = 8 * 80; // 80 instruction cycles + for (let p = 0; p < PHASES; p++) { + if (phaseSinceSync === 3) { + driveDNibble((PROG[observedPc & 0x3F] >> 4) & 0xF); + } else if (phaseSinceSync === 4) { + driveDNibble(PROG[observedPc & 0x3F] & 0xF); + } + board.advanceNanos(CLOCK_NS); + if (phaseSinceSync === 0) pcLow = board.readBus('D', 4); + else if (phaseSinceSync === 1) pcMid = board.readBus('D', 4); + else if (phaseSinceSync === 2) { + const pcHigh = board.readBus('D', 4); + observedPc = pcLow | (pcMid << 4) | (pcHigh << 8); + } + if (phaseSinceSync >= 0) phaseSinceSync++; + + // Sample output at end of each cycle (phase 7). + if (phaseSinceSync === 8) { + let out = 0; + for (let i = 0; i < 4; i++) if (board.getNet(`O${i}`)) out |= (1 << i); + if (out !== prevOut) { + outputs.push(out); + prevOut = out; + } + } + } + + // Each loop iteration produces a fresh WMP. The output should + // walk 0, 1, 2, 3, ... 0xF, 0, 1, ... — i.e. an incrementing + // sequence (modulo 16). Verify the first several distinct + // outputs follow that pattern. + expect(outputs.length, 'must blink at least 6 distinct values').toBeGreaterThanOrEqual(6); + for (let i = 0; i < Math.min(6, outputs.length); i++) { + expect(outputs[i], `tick ${i} of the increment-and-blink loop`).toBe(i); + } + board.dispose(); + }); }); });