fixed Z80 reset when probe/unprobe problem in buildCPU()

This commit is contained in:
Steven Hugg committed 2026-09-12 21:59:24 +02:00
1 parent 408c45481b
commit 801dc79c3a
3 files changed
+136 -9

No files matched your search

+10 -5
View File
@@ -3383,12 +3383,17 @@ export class Z80 implements CPU, InstructionBased, IOBusConnected, SavesState<Z8
retryData : number = -1;
private buildCPU() {
if (this.memBus && this.ioBus) {
// Build the core only once and route its callbacks through the current
// bus fields. BasicHeadlessMachine.rewireCPUBuses() swaps memBus/ioBus
// whenever a probe is attached or detached; rebuilding here would throw
// away the whole register state (i.e. reset the machine). FastZ80 calls
// core.mem_read/... at instruction time, so the closures pick up the swap.
if (this.memBus && this.ioBus && !this.cpu) {
this.cpu = new FastZ80({
mem_read: this.memBus.read.bind(this.memBus),
mem_write: this.memBus.write.bind(this.memBus),
io_read: this.ioBus.read.bind(this.ioBus),
io_write: this.ioBus.write.bind(this.ioBus),
mem_read: (a) => this.memBus.read(a),
mem_write: (a, v) => this.memBus.write(a, v),
io_read: (a) => this.ioBus.read(a),
io_write: (a, v) => this.ioBus.write(a, v),
});
}
}
+4 -4
View File
@@ -238,11 +238,13 @@ export class PacmanMachine extends BasicScanlineMachine {
constructor() {
super();
this.cpu.connectIOBus({
// Go through the base connect* helpers so the probe wrapper is spliced
// in when probing is toggled (and removed when it is not).
this.connectCPUIOBus({
read: (_p) => 0xff,
write: (port, val) => { if ((port & 0xff) === 0) this.interruptVector = val & 0xff; }
});
this.cpu.connectMemoryBus({ read: this.readByte, write: this.writeByte });
this.connectCPUMemoryBus({ read: this.readByte, write: this.writeByte });
this.cpu.retryInterrupts = true;
this.rom = new Uint8Array(this.defaultROMSize);
this.gfx = new PacmanVideo(this.rom, this.vram, this.cram, this.ram);
@@ -319,8 +321,6 @@ export class PacmanMachine extends BasicScanlineMachine {
this.waveforms = hasCustom ? custom : PacmanMachine.WAVEFORMS;
}
advanceCPU() { return this.cpu.advanceInsn(); }
/** Rebuild voice frequency from low-nibble registers. Voice 0 is 20-bit; 1/2 are 16-bit. */
private voiceFreq(v: number): number {
var r = this.soundRegs;
+122
View File
@@ -0,0 +1,122 @@
import assert from "assert";
import { describe, it } from "mocha";
import { Z80 } from "../../src/common/cpu/ZilogZ80";
import { BasicHeadlessMachine } from "../../src/common/devices";
import { ProbeRecorder } from "../../src/common/probe";
import { PacmanMachine } from "../../src/machine/pacman";
function makeBus(mem: Uint8Array) {
return {
read: (a: number) => mem[a & 0xffff],
write: (a: number, v: number) => { mem[a & 0xffff] = v & 0xff; },
};
}
function makeIO() {
return { read: (_a: number) => 0xff, write: (_a: number, _v: number) => { } };
}
describe('Z80 bus reconnection', function () {
it('should preserve CPU state when the buses are reconnected', function () {
const mem = new Uint8Array(0x10000);
// LD A,$42 ; LD B,$13
mem.set([0x3e, 0x42, 0x06, 0x13], 0);
const cpu = new Z80();
cpu.connectMemoryBus(makeBus(mem));
cpu.connectIOBus(makeIO());
cpu.advanceInsn();
cpu.advanceInsn();
const before = cpu.saveState();
assert.strictEqual(before.PC, 4);
assert.strictEqual((before.AF >> 8) & 0xff, 0x42);
// This is what BasicHeadlessMachine.rewireCPUBuses() does when a probe is
// attached or detached.
cpu.connectMemoryBus(makeBus(mem));
cpu.connectIOBus(makeIO());
const after = cpu.saveState();
assert.strictEqual(after.PC, before.PC);
assert.strictEqual(after.AF, before.AF);
assert.strictEqual(after.BC, before.BC);
});
it('should route accesses through the newly connected memory bus', function () {
const memA = new Uint8Array(0x10000);
const memB = new Uint8Array(0x10000);
// LD HL,$0010 ; LD A,(HL)
const prog = [0x21, 0x10, 0x00, 0x7e];
memA.set(prog, 0);
memB.set(prog, 0);
memA[0x10] = 0xaa;
memB[0x10] = 0xbb;
const cpu = new Z80();
cpu.connectMemoryBus(makeBus(memA));
cpu.connectIOBus(makeIO());
cpu.advanceInsn(); // LD HL,$0010
cpu.connectMemoryBus(makeBus(memB)); // swap in a different bus
cpu.advanceInsn(); // LD A,(HL) must read memB
assert.strictEqual((cpu.saveState().AF >> 8) & 0xff, 0xbb);
});
});
class TestZ80Machine extends BasicHeadlessMachine {
cpuFrequency = 1000000;
defaultROMSize = 0x10000;
cpu = new Z80();
ram = new Uint8Array(0x10000);
constructor() {
super();
this.connectCPUMemoryBus(this);
this.connectCPUIOBus(makeIO());
}
read(a: number): number { return this.ram[a & 0xffff]; }
write(a: number, v: number): void { this.ram[a & 0xffff] = v & 0xff; }
}
describe('Probing does not reset Z80 machines', function () {
it('should keep CPU state across connectProbe() on a generic Z80 machine', function () {
const m = new TestZ80Machine();
m.ram.set([0x3e, 0x42, 0x06, 0x13, 0x7e], 0);
m.advanceCPU();
m.advanceCPU();
const before = m.saveState().c;
assert.strictEqual(before.PC, 4);
const rec = new ProbeRecorder(m);
m.connectProbe(rec);
assert.strictEqual(m.saveState().c.PC, before.PC);
assert.strictEqual(m.saveState().c.AF, before.AF);
m.advanceCPU();
assert.ok(rec.idx > 0, 'probe should record data while connected');
m.connectProbe(null);
assert.strictEqual(m.saveState().c.PC, 5);
});
});
describe('Pacman probing', function () {
it('should not reset and should record data when the probe is toggled', function () {
const m = new PacmanMachine();
m.rom.fill(0);
// LD A,$42 ; NOP ; LD B,$13
m.rom.set([0x3e, 0x42, 0x00, 0x06, 0x13], 0);
m.advanceCPU();
m.advanceCPU();
const before = m.saveState().c;
assert.strictEqual(before.PC, 3);
const rec = new ProbeRecorder(m);
m.connectProbe(rec);
assert.strictEqual(m.saveState().c.PC, before.PC, 'probe toggle must not reset Pacman');
m.advanceCPU();
assert.ok(rec.idx > 0, 'Pacman probe should record execute/clocks');
m.connectProbe(null);
assert.strictEqual(m.saveState().c.PC, 5);
});
});