diff --git a/src/bus.js b/src/bus.js index 846b9fd..de3873b 100644 --- a/src/bus.js +++ b/src/bus.js @@ -1,6 +1,6 @@ import { hexbyte, hexword } from "./utils"; -class Bus { +export class Bus { #ram = new Uint8Array(0x2000); #cartridgeRam = new Uint8Array(0x8000); #romBanks = []; @@ -248,23 +248,3 @@ class Bus { } } } - -export const bus = new Bus(); - -// Convenience re-exports so callers that import bare functions (z80.js, -// z80_ops.js, z80_dis.js, debug.js) only need to change their import path. -export function readbyte(a) { - return bus.readbyte(a); -} -export function writebyte(a, v) { - bus.writebyte(a, v); -} -export function readport(a) { - return bus.readport(a); -} -export function writeport(a, v) { - bus.writeport(a, v); -} -export function virtualAddress(a) { - return bus.virtualAddress(a); -} diff --git a/src/debug.js b/src/debug.js index b42622d..22c6438 100644 --- a/src/debug.js +++ b/src/debug.js @@ -1,20 +1,30 @@ import { hexbyte, hexword } from "./utils"; -import { bus, readbyte, virtualAddress } from "./bus"; -import { - clearBreakpoint, - audio_enable, - cycleCallback, - start, - z80_do_opcodes, -} from "./miracle"; -import { z80 } from "./z80/z80.js"; -import { disassemble } from "./z80/z80_dis"; -import { vdp } from "./vdp"; +import { makeDisassembler } from "./z80/z80_dis"; + +// Module-level refs — set during debug_init from the SMS instance + callbacks. +let z80, bus, vdp; +let sms; +let disassemble; +let _audioEnable, _cycleCallback, _start; let debugSerial = 0; let annotations = null; -export function debug_init(romName) { +export function debug_init(romName, smsInstance, callbacks) { + sms = smsInstance; + z80 = sms.getZ80(); + bus = sms.getBus(); + vdp = sms.getVdp(); + _audioEnable = callbacks.audioEnable; + _cycleCallback = callbacks.cycleCallback; + _start = callbacks.start; + + const { disassemble: dis } = makeDisassembler( + (a) => bus.readbyte(a), + addressHtml, + ); + disassemble = dis; + debugSerial = (bus.romBanks[1][0x3ffc] << 8) | bus.romBanks[1][0x3ffd]; if (!localStorage[debugSerial]) { @@ -44,7 +54,7 @@ export function debug_init(romName) { // } function addressName(addr) { - const virtual = virtualAddress(addr); + const virtual = bus.virtualAddress(addr); if (annotations.labels[virtual]) { return ( ' el.addEventListener("click", () => showAbout())); z80_init(); - miracle_init(); + miracle_init(sms); miracle_reset(); const parsedQuery = parseQuery(); if (parsedQuery["b64sms"]) { const name = parsedQuery["load"] || "uploaded.sms"; - bus.loadRom(name, atob(parsedQuery["b64sms"]), debug_init); + sms.loadRom(name, atob(parsedQuery["b64sms"]), onRomLoaded); updateUrl({ b64sms: parsedQuery["b64sms"], load: name }); } else if (parsedQuery["load"]) { const name = sanitizeRomName(parsedQuery["load"]); if (name) { - bus.loadRom(name, loadRomData(name), debug_init); + sms.loadRom(name, loadRomData(name), onRomLoaded); updateUrl({ load: name }); } else { // Unknown/invalid ROM name — fall through to default const defaultRom = getDefaultRom(); - bus.loadRom(defaultRom, loadRomData(defaultRom), debug_init); + sms.loadRom(defaultRom, loadRomData(defaultRom), onRomLoaded); } } else { const defaultRom = getDefaultRom(); - bus.loadRom(defaultRom, loadRomData(defaultRom), debug_init); + sms.loadRom(defaultRom, loadRomData(defaultRom), onRomLoaded); } start(); diff --git a/src/miracle.js b/src/miracle.js index af30624..99aa3a5 100644 --- a/src/miracle.js +++ b/src/miracle.js @@ -1,53 +1,36 @@ -import { vdp } from "./vdp"; import { SoundChip } from "./soundchip"; -import { z80, z80_reset, z80_set_irq, z80_nmi } from "./z80/z80.js"; -import { makeZ80Runner } from "./z80/z80_ops"; - -export const { z80_do_opcodes } = makeZ80Runner(z80); +import { SMS } from "./sms"; import { showDebug, debugKeyPress } from "./debug"; -import { bus } from "./bus"; -let breakpointHit = false; +let sms; + let running = false; -export let canvas; +let canvas; let ctx; let imageData; let fb8; -export let fb32; +let fb32; let soundChip; -const framesPerSecond = 50; -const scanLinesPerFrame = 313; // 313 lines in PAL TODO: unify all this -const scanLinesPerSecond = scanLinesPerFrame * framesPerSecond; -const cpuHz = 3.58 * 1000 * 1000; // According to Sega docs. -const tstatesPerHblank = Math.ceil(cpuHz / scanLinesPerSecond) | 0; - -export function clearBreakpoint() { - breakpointHit = false; -} +const targetTimeout = 1000 / SMS.FRAMES_PER_SECOND; +let adjustedTimeout = targetTimeout; +let lastFrame = null; export function cycleCallback(tstates) { soundChip.polltime(tstates); } function line() { - z80.eventNextEvent = tstatesPerHblank; - z80.tstates -= tstatesPerHblank; - z80_do_opcodes(cycleCallback); - const vdp_status = vdp.hblank(); - z80_set_irq(!!(vdp_status & 3)); - if (breakpointHit) { + if (sms.runLine(cycleCallback)) { running = false; - showDebug(z80.pc); - } else if (vdp_status & 4) { - paintScreen(); + showDebug(sms.pc); } } export function start() { - breakpointHit = false; + sms.clearBreakpoint(); if (running) return; running = true; document.getElementById("menu").className = "running"; @@ -56,13 +39,9 @@ export function start() { run(); } -const targetTimeout = 1000 / framesPerSecond; -let adjustedTimeout = targetTimeout; -let lastFrame = null; - function run() { if (!running) { - showDebug(z80.pc); + showDebug(sms.pc); return; } const now = Date.now(); @@ -85,7 +64,7 @@ function run() { setTimeout(run, adjustedTimeout); try { - for (let i = 0; i < scanLinesPerFrame && running; i++) line(); + for (let i = 0; i < SMS.SCAN_LINES_PER_FRAME && running; i++) line(); } catch (e) { running = false; audio_enable(true); @@ -135,18 +114,20 @@ function audio_init() { if (!AudioCtx) { // No Web Audio API at all. - soundChip = new SoundChip(10000, cpuHz); + soundChip = new SoundChip(10000, SMS.CPU_HZ); return; } audioContext = new AudioCtx(); // Create soundChip immediately so audio_reset() works synchronously. - soundChip = new SoundChip(audioContext.sampleRate, cpuHz); + soundChip = new SoundChip(audioContext.sampleRate, SMS.CPU_HZ); // Use floor so we never request more samples than the soundchip has actually // advanced (ceil would synthesise a phantom extra sample on non-integer rates // and cause long-term pitch drift). A fractional accumulator would be ideal // for exact rate matching but floor is safe and correct in practice. - _samplesPerFrame = Math.floor(audioContext.sampleRate / framesPerSecond); + _samplesPerFrame = Math.floor( + audioContext.sampleRate / SMS.FRAMES_PER_SECOND, + ); if (!audioContext.audioWorklet) { // AudioWorklet unavailable (non-secure context, old browser, etc.) @@ -212,11 +193,8 @@ export function audio_enable(enable) { if (enable && audioContext) audioContext.resume(); } -function audio_reset() { - soundChip.reset(); -} - -export function miracle_init() { +export function miracle_init(smsInstance) { + sms = smsInstance; canvas = document.getElementById("screen"); ctx = canvas.getContext("2d"); if (ctx.getImageData) { @@ -228,9 +206,8 @@ export function miracle_init() { // Unsupported.... } - vdp.init(canvas, fb32, paintScreen, breakpoint); audio_init(); - bus.connect(vdp, soundChip); + sms.init(canvas, fb32, paintScreen, soundChip); miracle_reset(); // Scale the canvas to fill its container while maintaining the native aspect ratio. @@ -264,11 +241,8 @@ export function miracle_init() { } export function miracle_reset() { - bus.reset(); //inputMode = 7; - z80_reset(); - vdp.reset(); - audio_reset(); + sms.reset(); } const keys = { @@ -298,7 +272,7 @@ function keyDown(evt) { if (!running) return; const key = keys[keyCode(evt)]; if (key) { - bus.joystick &= ~key; + sms.joystick &= ~key; if (!evt.metaKey) { evt.preventDefault(); return; @@ -306,7 +280,7 @@ function keyDown(evt) { } switch (evt.keyCode) { case 80: // 'P' for pause - z80_nmi(); + sms.nmi(); break; case 8: // 'Backspace' is debug breakpoint(); @@ -319,7 +293,7 @@ function keyUp(evt) { if (!running) return; const key = keys[keyCode(evt)]; if (key) { - bus.joystick |= key; + sms.joystick |= key; if (!evt.metaKey) { evt.preventDefault(); } @@ -335,12 +309,11 @@ function keyPress(evt) { } } -export function paintScreen() { +function paintScreen() { ctx.putImageData(imageData, 0, 0); } -export function breakpoint() { - z80.eventNextEvent = 0; - breakpointHit = true; +function breakpoint() { + sms.triggerBreakpoint(); audio_enable(false); } diff --git a/src/sms.js b/src/sms.js new file mode 100644 index 0000000..55e76e8 --- /dev/null +++ b/src/sms.js @@ -0,0 +1,110 @@ +import { Bus } from "./bus"; +import { Z80 } from "./z80/z80.js"; +import { VDP } from "./vdp"; +import { makeZ80Runner } from "./z80/z80_ops"; + +export class SMS { + static FRAMES_PER_SECOND = 50; + static SCAN_LINES_PER_FRAME = 313; + static CPU_HZ = 3.58e6; + + #z80; + #bus; + #vdp; + #soundChip = null; + #z80_do_opcodes; + #breakpointHit = false; + #paintScreen = null; + #tstatesPerHblank; + #initialized = false; + + constructor() { + this.#bus = new Bus(); + this.#z80 = new Z80(); + this.#vdp = new VDP(); + this.#z80.bus = this.#bus; + const { z80_do_opcodes } = makeZ80Runner(this.#z80); + this.#z80_do_opcodes = z80_do_opcodes; + const linesPerSecond = SMS.SCAN_LINES_PER_FRAME * SMS.FRAMES_PER_SECOND; + this.#tstatesPerHblank = Math.ceil(SMS.CPU_HZ / linesPerSecond) | 0; + } + + init(canvas, fb32, paintScreen, soundChip) { + this.#paintScreen = paintScreen; + this.#soundChip = soundChip; + this.#vdp.init( + canvas, + fb32, + paintScreen, + () => this.triggerBreakpoint(), + (asserted) => this.#z80.setIrq(asserted), + ); + this.#bus.connect(this.#vdp, soundChip); + this.#initialized = true; + } + + reset() { + this.#bus.reset(); + this.#z80.reset(); + this.#vdp.reset(); + this.#soundChip?.reset(); + } + + loadRom(name, data, onLoaded) { + this.#bus.loadRom(name, data, onLoaded); + } + + // Run one scanline. Returns true if a breakpoint was hit. + runLine(cycleCallback) { + if (!this.#initialized) return false; + this.#z80.eventNextEvent = this.#tstatesPerHblank; + this.#z80.tstates -= this.#tstatesPerHblank; + this.#z80_do_opcodes(cycleCallback); + const vdpStatus = this.#vdp.hblank(); + this.#z80.setIrq(!!(vdpStatus & 3)); + if (vdpStatus & 4 && !this.#breakpointHit) this.#paintScreen(); + return this.#breakpointHit; + } + + triggerBreakpoint() { + this.#z80.eventNextEvent = 0; + this.#breakpointHit = true; + } + + clearBreakpoint() { + this.#breakpointHit = false; + } + + nmi() { + if (!this.#initialized) return; + this.#z80.nmi(); + } + + get joystick() { + return this.#bus.joystick; + } + set joystick(val) { + this.#bus.joystick = val; + } + + get pc() { + return this.#z80.pc; + } + + // --- Debug access --- + + getZ80() { + return this.#z80; + } + getBus() { + return this.#bus; + } + getVdp() { + return this.#vdp; + } + + execOpcodes(cycleCallback) { + if (!this.#initialized) return; + this.#z80_do_opcodes(cycleCallback); + } +} diff --git a/src/vdp.js b/src/vdp.js index 3eed22c..7408591 100644 --- a/src/vdp.js +++ b/src/vdp.js @@ -1,21 +1,21 @@ import { hexbyte, hexword } from "./utils"; -import { z80_set_irq } from "./z80/z80.js"; export class VDP { // Exposed for debug.js (read-only intent). - vdp_regs; + vdp_regs = new Uint8Array(16); #canvas; #fb32; #paintScreen; #breakpoint; - #vram; - #vramUntwiddled; - #palette; - #paletteR; - #paletteG; - #paletteB; - #paletteRGB; + #setIrq; + #vram = new Uint8Array(0x4000); + #vramUntwiddled = new Uint8Array(0x8000); + #palette = new Uint8Array(32); + #paletteR = new Uint8Array(32); + #paletteG = new Uint8Array(32); + #paletteB = new Uint8Array(32); + #paletteRGB = new Uint32Array(32); #vdp_addr_state = 0; #vdp_mode_select = 0; #vdp_addr_latch = 0; @@ -33,19 +33,12 @@ export class VDP { // Initialisation // ------------------------------------------------------------------------- - init(canvas, fb32, paintScreen, breakpoint) { + init(canvas, fb32, paintScreen, breakpoint, setIrq) { this.#canvas = canvas; this.#fb32 = fb32; this.#paintScreen = paintScreen; this.#breakpoint = breakpoint; - this.#vram = new Uint8Array(0x4000); - this.#vramUntwiddled = new Uint8Array(0x8000); - this.#palette = new Uint8Array(32); - this.#paletteR = new Uint8Array(32); - this.#paletteG = new Uint8Array(32); - this.#paletteB = new Uint8Array(32); - this.#paletteRGB = new Uint32Array(32); - this.vdp_regs = new Uint8Array(16); + this.#setIrq = setIrq; this.reset(); } @@ -218,7 +211,7 @@ export class VDP { // Clear top three here. this.#vdp_status &= 0x1f; this.#vdp_pending_hblank = false; - z80_set_irq(false); + this.#setIrq(false); this.#vdp_addr_state = 0; return res; } @@ -601,5 +594,3 @@ export class VDP { return needIrq; } } - -export const vdp = new VDP(); diff --git a/src/z80/z80.js b/src/z80/z80.js index 1f84c18..ff82a46 100644 --- a/src/z80/z80.js +++ b/src/z80/z80.js @@ -1,6 +1,5 @@ // Z80 state, flag tables, lifecycle functions, and micro-op methods. -import { bus } from "../bus"; import { FLAG_C, FLAG_N, @@ -879,20 +878,83 @@ class Z80 { otdr() { this._otxr(-1); } + + // ------------------------------------------------------------------------- + // Lifecycle methods + // ------------------------------------------------------------------------- + + reset() { + this.a = this.f = this.b = this.c = this.d = this.e = this.h = this.l = 0; + this.a_ = + this.f_ = + this.b_ = + this.c_ = + this.d_ = + this.e_ = + this.h_ = + this.l_ = + 0; + this.ixh = this.ixl = this.iyh = this.iyl = 0; + this.i = this.r = this.r7 = 0; + this.sp = this.pc = 0; + this.iff1 = this.iff2 = this.im = 0; + this.halted = false; + this.irq_pending = false; + this.irq_suppress = true; + } + + setIrq(asserted) { + this.irq_pending = asserted; + if (this.irq_pending && this.iff1) this.interrupt(); + } + + interrupt() { + if (this.iff1) { + if (this.halted) { + this.pc = (this.pc + 1) & 0xffff; + this.halted = false; + } + this.iff1 = this.iff2 = 0; + this.push16(this.pc); + this.r = (this.r + 1) & 0x7f; + switch (this.im) { + case 0: + this.pc = 0x0038; + this.tstates += 12; + break; + case 1: + this.pc = 0x0038; + this.tstates += 13; + break; + case 2: { + const inttemp = 0x100 * this.i + 0xff; + this.pc = + this.bus.readbyte(inttemp) | + (this.bus.readbyte((inttemp + 1) & 0xffff) << 8); + this.tstates += 19; + break; + } + } + } + } + + instructionHook() {} + + nmi() { + this.iff1 = 0; + this.push16(this.pc); + this.tstates += 11; + this.pc = 0x0066; + } } -export const z80 = new Z80(); -z80.bus = bus; +export { Z80 }; // --------------------------------------------------------------------------- -// Lifecycle functions (standalone exports; external API unchanged) +// Self-initialise lookup tables at module load time // --------------------------------------------------------------------------- -export function z80_init() { - z80_init_tables(); -} - -function z80_init_tables() { +(() => { for (let i = 0; i < 0x100; i++) { sz53_table[i] = i & (FLAG_3 | FLAG_5 | FLAG_S); let j = i; @@ -906,60 +968,7 @@ function z80_init_tables() { } sz53_table[0] |= FLAG_Z; sz53p_table[0] |= FLAG_Z; -} - -export function z80_reset() { - z80.a = z80.f = z80.b = z80.c = z80.d = z80.e = z80.h = z80.l = 0; - z80.a_ = z80.f_ = z80.b_ = z80.c_ = z80.d_ = z80.e_ = z80.h_ = z80.l_ = 0; - z80.ixh = z80.ixl = z80.iyh = z80.iyl = 0; - z80.i = z80.r = z80.r7 = 0; - z80.sp = z80.pc = 0; - z80.iff1 = z80.iff2 = z80.im = 0; - z80.halted = false; - z80.irq_pending = false; - z80.irq_suppress = true; -} - -export function z80_set_irq(asserted) { - z80.irq_pending = asserted; - if (z80.irq_pending && z80.iff1) z80_interrupt(); -} +})(); -export function z80_interrupt() { - if (z80.iff1) { - if (z80.halted) { - z80.pc = (z80.pc + 1) & 0xffff; - z80.halted = false; - } - z80.iff1 = z80.iff2 = 0; - z80.push16(z80.pc); - z80.r = (z80.r + 1) & 0x7f; - switch (z80.im) { - case 0: - z80.pc = 0x0038; - z80.tstates += 12; - break; - case 1: - z80.pc = 0x0038; - z80.tstates += 13; - break; - case 2: { - const inttemp = 0x100 * z80.i + 0xff; - z80.pc = - z80.bus.readbyte(inttemp) | - (z80.bus.readbyte((inttemp + 1) & 0xffff) << 8); - z80.tstates += 19; - break; - } - } - } -} - -export function z80_instruction_hook() {} - -export function z80_nmi() { - z80.iff1 = 0; - z80.push16(z80.pc); - z80.tstates += 11; - z80.pc = 0x0066; -} +// Backward-compatible no-op (tables are now self-initialised above). +export function z80_init() {} diff --git a/src/z80/z80_dis.js b/src/z80/z80_dis.js index e507e9d..8626472 100644 --- a/src/z80/z80_dis.js +++ b/src/z80/z80_dis.js @@ -1,71 +1,73 @@ /* eslint-disable */ import { hexbyte } from "../utils"; -import { readbyte } from "../bus"; -import { addressHtml } from "../debug"; import { sign_extend } from "./z80_ops.js"; -// Runtime register-name variable for DD/FD disassembly. -// Set to "IX" or "IY" before entering the relevant switch. -var dis_REGISTER; +export function makeDisassembler(readbyte, addressHtml) { + // Runtime register-name variable for DD/FD disassembly. + // Set to "IX" or "IY" before entering the relevant switch. + var dis_REGISTER; -export function disassemble(address) { - const opcode = readbyte(address); - address++; - var res = "??"; - switch (opcode) { - /* @z80-dis-generate opcodes_base.dat */ + function disassemble(address) { + const opcode = readbyte(address); + address++; + var res = "??"; + switch (opcode) { + /* @z80-dis-generate opcodes_base.dat */ + } + return [res, address]; } - return [res, address]; -} -function disassemble_CB(address) { - const opcode = readbyte(address); - address++; - var res = "??"; - switch (opcode) { - /* @z80-dis-generate opcodes_cb.dat */ + function disassemble_CB(address) { + const opcode = readbyte(address); + address++; + var res = "??"; + switch (opcode) { + /* @z80-dis-generate opcodes_cb.dat */ + } + return [res, address]; } - return [res, address]; -} -function disassemble_ED(address) { - const opcode = readbyte(address); - address++; - var res = "??"; - switch (opcode) { - /* @z80-dis-generate opcodes_ed.dat */ + function disassemble_ED(address) { + const opcode = readbyte(address); + address++; + var res = "??"; + switch (opcode) { + /* @z80-dis-generate opcodes_ed.dat */ + } + return [res, address]; } - return [res, address]; -} -function disassemble_DD(address) { - const opcode = readbyte(address); - address++; - var res = "??"; - dis_REGISTER = "IX"; - switch (opcode) { - /* @z80-dis-generate opcodes_ddfd.dat */ + function disassemble_DD(address) { + const opcode = readbyte(address); + address++; + var res = "??"; + dis_REGISTER = "IX"; + switch (opcode) { + /* @z80-dis-generate opcodes_ddfd.dat */ + } + return [res, address]; } - return [res, address]; -} -function disassemble_FD(address) { - const opcode = readbyte(address); - address++; - var res = "??"; - dis_REGISTER = "IY"; - switch (opcode) { - /* @z80-dis-generate opcodes_ddfd.dat */ + function disassemble_FD(address) { + const opcode = readbyte(address); + address++; + var res = "??"; + dis_REGISTER = "IY"; + switch (opcode) { + /* @z80-dis-generate opcodes_ddfd.dat */ + } + return [res, address]; } - return [res, address]; -} -function disassemble_DDFDCB(address) { - const opcode = readbyte(address); - address++; - var res = "??"; - switch (opcode) { - /* @z80-dis-generate opcodes_ddfdcb.dat */ + function disassemble_DDFDCB(address) { + const opcode = readbyte(address); + address++; + var res = "??"; + switch (opcode) { + /* @z80-dis-generate opcodes_ddfdcb.dat */ + } + return [res, address]; } - return [res, address]; + + return { disassemble }; } diff --git a/src/z80/z80_ops.js b/src/z80/z80_ops.js index 1a9ecfc..fc6e07f 100644 --- a/src/z80/z80_ops.js +++ b/src/z80/z80_ops.js @@ -2,9 +2,6 @@ // referenced from the opcode code inlined by the @z80-generate transform. /* eslint-disable no-unused-vars */ import { - z80_instruction_hook, - z80_set_irq, - z80_interrupt, halfcarry_add_table, halfcarry_sub_table, overflow_add_table, @@ -121,7 +118,7 @@ export function makeZ80Runner(z80) { z80.irq_suppress = false; } else { z80.irq_suppress = true; - z80_interrupt(); + z80.interrupt(); } } @@ -129,7 +126,7 @@ export function makeZ80Runner(z80) { addTstates(4); z80.r = (z80.r + 1) & 0x7f; const opcode = readbyte(z80.pc); - z80_instruction_hook(z80.pc, opcode); + z80.instructionHook(z80.pc, opcode); z80.pc = (z80.pc + 1) & 0xffff; z80BaseOps[opcode](); diff --git a/tests/disassemble.test.js b/tests/disassemble.test.js index 0f73b65..89bceb2 100644 --- a/tests/disassemble.test.js +++ b/tests/disassemble.test.js @@ -13,35 +13,17 @@ import { vi, describe, it, expect, beforeEach } from "vitest"; // --------------------------------------------------------------------------- const mem = new Uint8Array(0x10000); -vi.mock("../src/bus", () => { - const busObj = { - readbyte: (addr) => mem[addr & 0xffff], - writebyte: (addr, val) => { - mem[addr & 0xffff] = val & 0xff; - }, - writeport: () => {}, - readport: () => 0xff, - }; - return { - bus: busObj, - readbyte: busObj.readbyte, - writebyte: busObj.writebyte, - writeport: busObj.writeport, - readport: busObj.readport, - }; -}); - vi.mock("../src/utils", () => ({ hexbyte: (v) => v.toString(16).padStart(2, "0"), })); -vi.mock("../src/debug", () => ({ - // Return a plain hex address string (no HTML) for predictable assertions. - addressHtml: (addr) => `0x${addr.toString(16).padStart(4, "0")}`, -})); +// Create a disassembler wired to the mock memory — no bus/debug mocks needed. +import { makeDisassembler } from "../src/z80/z80_dis.js"; -// These imports must come *after* vi.mock (vitest hoists vi.mock calls). -import { disassemble } from "../src/z80/z80_dis.js"; +const { disassemble } = makeDisassembler( + (addr) => mem[addr & 0xffff], + (addr) => `0x${addr.toString(16).padStart(4, "0")}`, +); // --------------------------------------------------------------------------- // Helper — write a sequence of bytes into the memory mock diff --git a/tests/fuse.test.js b/tests/fuse.test.js index 04f51d3..dc28215 100644 --- a/tests/fuse.test.js +++ b/tests/fuse.test.js @@ -49,7 +49,7 @@ const KNOWN_FAILURES = new Set([ "edbb_1", ]); -import { vi, describe, it, expect, beforeAll, beforeEach } from "vitest"; +import { describe, it, expect, beforeAll, beforeEach } from "vitest"; import { readFileSync } from "fs"; import { fileURLToPath } from "url"; import path from "path"; @@ -57,33 +57,24 @@ import path from "path"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // --------------------------------------------------------------------------- -// Memory mock — a flat 64K array used by z80.js and z80_ops.js -// via the ../miracle module. We mutate it between tests; never replace it. +// Memory mock — a flat 64K array; wired directly to a fresh Z80 instance. // --------------------------------------------------------------------------- const mem = new Uint8Array(0x10000); -vi.mock("../src/bus", () => { - const busObj = { - readbyte: (addr) => mem[addr & 0xffff], - writebyte: (addr, val) => { - mem[addr & 0xffff] = val & 0xff; - }, - readport: (port) => (port >> 8) & 0xff, // FUSE convention: port returns high byte of address - writeport: () => {}, - }; - return { - bus: busObj, - readbyte: busObj.readbyte, - writebyte: busObj.writebyte, - readport: busObj.readport, - writeport: busObj.writeport, - }; -}); +const mockBus = { + readbyte: (addr) => mem[addr & 0xffff], + writebyte: (addr, val) => { + mem[addr & 0xffff] = val & 0xff; + }, + readport: (port) => (port >> 8) & 0xff, // FUSE convention: port returns high byte of address + writeport: () => {}, +}; -// These imports must come *after* vi.mock (vitest hoists vi.mock automatically) -import { z80, z80_init } from "../src/z80/z80.js"; +import { Z80, z80_init } from "../src/z80/z80.js"; import { makeZ80Runner } from "../src/z80/z80_ops.js"; +const z80 = new Z80(); +z80.bus = mockBus; const { z80_do_opcodes } = makeZ80Runner(z80); // ---------------------------------------------------------------------------