test_intel: phase D-4 — Busicom-style increment-and-blink demo
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) <noreply@anthropic.com>
This commit is contained in:
parent
1aa9fb872c
commit
f429e113ab
|
|
@ -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 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue