# Emulation strategy: mapping a CPU onto a velxio custom chip The velxio runtime is **reactive** — there is no `loop()` hook. Every chip is woken by either a pin edge or a timer fire. This document describes how to fit an instruction-level CPU emulator into that model. ## Two viable execution models ### A. Timer-driven step (recommended starting point) ``` chip_setup() { register all pins reset_cpu_state() timer = vx_timer_create() vx_timer_start(timer, INSTR_PERIOD_NS, /* repeating */ true, on_clock_tick, NULL) } on_clock_tick() { step_one_instruction() // or step_one_machine_cycle() drive_bus_pins_for_phase() // RD/WR/MREQ/ALE/etc. } ``` - One timer fires at a chosen period (e.g. 250 ns for a 4 MHz Z80, or coarser if we step a whole instruction per tick). - The callback runs CPU work, sets the appropriate output pins for the bus phase, and returns. - Memory and I/O reads happen in two passes: in callback `N`, the CPU drives the address pins and asserts `RD`; in callback `N+1`, the CPU reads the data pins (which the memory chip on the canvas has had a full tick to settle). - This is **not cycle-accurate** with respect to the real silicon unless the period equals one T-state. For most retro-computing demos that's fine. **Pro:** simple, deterministic, easy to reason about, easy to slow down for visualization. **Con:** can't react instantly to a wait state — has to wait for the next tick. Mitigated by adding a pin watch on `WAIT`/`READY` that stalls the timer-driven step. ### B. Pin-edge-driven step ``` chip_setup() { register pins vx_pin_watch(CLK_PIN, VX_RISING, on_clk_rising, NULL) } on_clk_rising() { advance_one_t_state() } ``` - The chip itself does **not** generate the clock; an external clock chip on the canvas drives `CLK` and the CPU advances one T-state per rising edge. - Closer to how the real silicon behaves — the user can plug in a slow manual clock and single-step. - More expensive per second (one host callback per clock edge) but matches user expectations and lets wait-state logic just be more pin watches. **Recommendation:** ship model A first for each chip (proves the ISA implementation), then optionally upgrade to model B for chips where single-stepping is part of the demo (Z80, 8080). ## Bus-cycle implementation For separate-bus CPUs (8080, Z80) one machine read cycle looks like: ``` T1: drive A0..A15 with addr; assert MREQ; assert RD; release D0..D7 T2: (wait one tick for memory to respond) T3: sample D0..D7 → byte = vx_pin_read_byte(D0..D7) T4: deassert RD, MREQ ``` In the timer-driven model we expose this as a small state machine inside `step_one_instruction()`. We do **not** need the runtime to know anything about it — to the runtime we are just toggling pins. For multiplexed-bus CPUs (4004, 8086) the chip drives a richer phase sequence: ``` 8086 T1: drive AD0..AD15 with low addr, A16..A19 with high addr, assert ALE high then low (latches address into external 8282) 8086 T2: switch AD0..AD15 to data direction (input for read, output for write), assert RD or WR 8086 T3: sample/drive data 8086 T4: deassert RD/WR ``` The chip does not need to *implement* the 8282 latch — it just needs to drive `ALE` faithfully. A separate "address latch" custom chip on the canvas (or a built-in primitive — see open question Q3) consumes `ALE`+`AD0..15` and produces stable demultiplexed `A0..15`. ## Memory and I/O on the canvas Each CPU needs at minimum a ROM holding the program. Three options, in increasing order of effort: 1. **Bake the ROM into the CPU chip itself.** Simplest, allows zero external wiring for a "hello world" demo. Bad for the long-term pedagogical goal (the user can't see the bus working). 2. **Author a separate `rom-32k.c` custom chip** with 16 address pins, 8 data pins, `OE̅`, `CS̅`. Reads the ROM image from a chip-config blob. Reusable across CPUs. 3. **Author a separate `ram-64k.c` custom chip** with the same pinout plus `WE̅`. Even more reusable. The right call is to do (2) and (3) once, in a sibling `test_buses/` folder, and reuse them across all five CPUs. Tracked as Q1 in `05_open_questions.md`. ## Decoder representation For the 8-bit CPUs, the conventional approach is a 256-entry `dispatch[]` table whose entries are small functions or compact microcode steps. Andre Weissflog's `chips/z80.h` uses a flat table-driven decoder generated by an offline script — porting that script into our build pipeline is feasible but non-trivial. **Pragmatic call:** for the first cut, use a giant `switch (opcode)` in C. WASI-SDK's clang compiles big switches into reasonable jump tables. Premature optimization is the enemy here. ## Timing and the simulated clock `vx_sim_now_nanos()` is monotonic *in simulated time*. The host advances simulated time as fast as it can, throttled by frame rendering. This means: - A 4 MHz Z80 emulator running 1 instruction per 1 µs of simulated time will *appear* to run at whatever speed the host can sustain in wall time — could be slower than 4 MHz, could be faster. - Don't use `vx_sim_now_nanos` for "realistic" wall-clock pacing. Use it for ordering events. - For pedagogy, expose a "clock rate" config knob in the chip JSON so users can set their Z80 to 1 Hz and watch instructions execute one at a time. ## Determinism All the CPUs in scope are deterministic given the same inputs (no async errata of consequence). Two important invariants: 1. Avoid host-side wall-clock dependence. Only `vx_sim_now_nanos` is safe; never call out to anything that observes real time. 2. Avoid floating-point in the CPU core unless we explicitly want it (none of these chips have hardware FP). Stick to integer `uint8_t` / `uint16_t` / `uint32_t`. ## Suggested implementation order 1. **8080 first.** Cleanest bus, smallest ISA per pin, MIT-licensed reference emulator (`superzazu/8080`) available for porting. 2. **Z80 second.** Same bus shape as 8080 + extensions; reuse what we learned. Reference emulator: `floooh/chips/z80.h`. 3. **4004 third.** Different in shape (4-bit, multiplexed) — proves the multiplexed-bus pattern that 8086 will need. 4. **4040 fourth.** Increment on 4004. 5. **8086 last.** Most complex; benefits from everything learned above. This ordering also matches what reference material we already trust.