Skip to content

Commit 969721a

Browse files
committed
fix: Q latch semantics
1 parent 3e22ea6 commit 969721a

File tree

1 file changed

+32
-6
lines changed

1 file changed

+32
-6
lines changed

src/cpu.zig

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub const Z80 = struct {
3535

3636
// Q latch used for undocumented flag behavior (SCF/CCF).
3737
q: u8 = 0,
38+
q_needs_update: bool = true,
3839

3940
// "WZ" register.
4041
// https://www.grimware.org/lib/exe/fetch.php/documentations/devices/z80/z80.memptr.eng.txt
@@ -125,6 +126,7 @@ pub const Z80 = struct {
125126
self.r = 0;
126127
self.im = 0;
127128
self.q = 0;
129+
self.q_needs_update = true;
128130
self.iff1 = false;
129131
self.iff2 = false;
130132
self.cycles = 0;
@@ -1437,16 +1439,22 @@ pub const Z80 = struct {
14371439

14381440
// Execute a single instruction
14391441
pub fn execute(self: *Z80) void {
1440-
// Snapshot flags before executing an instruction to approximate Q latch
1441-
// semantics: instructions that change F copy new F to Q, others clear Q.
1442+
// Snapshot flags before executing the instruction so we can approximate
1443+
// Q latch semantics: instructions that change F copy new F to Q,
1444+
// instructions that don't change F leave Q unchanged, and some
1445+
// instructions (EX AF,AF', POP AF, etc.) explicitly clear Q.
14421446
const old_f: u8 = self.getF();
14431447
// std.debug.print("opcode = 0x{X:0>2}\n", .{self.peekByte()});
14441448
self.executeOpcode(self.fetchOpcode());
1445-
const new_f: u8 = self.getF();
1446-
if (new_f != old_f) {
1447-
self.q = new_f;
1449+
if (self.q_needs_update) {
1450+
const new_f: u8 = self.getF();
1451+
if (new_f != old_f) {
1452+
self.q = new_f;
1453+
}
1454+
// If F is unchanged, Q stays as-is.
14481455
} else {
1449-
self.q = 0;
1456+
// Instruction explicitly managed Q; re-enable automatic updates.
1457+
self.q_needs_update = true;
14501458
}
14511459
}
14521460

@@ -1508,6 +1516,9 @@ pub const Z80 = struct {
15081516
const tmp = self.af;
15091517
self.af = self.af_;
15101518
self.af_ = tmp;
1519+
// EX AF,AF' does not affect Q latch.
1520+
self.q = 0;
1521+
self.q_needs_update = false;
15111522
self.cycles += 4;
15121523
},
15131524
0x09 => {
@@ -2559,6 +2570,9 @@ pub const Z80 = struct {
25592570
self.incSP();
25602571
self.setB(self.memory[self.sp]);
25612572
self.incSP();
2573+
// POP BC does not affect Q latch.
2574+
self.q = 0;
2575+
self.q_needs_update = false;
25622576
self.cycles += 10;
25632577
},
25642578
0xC2 => {
@@ -2710,6 +2724,9 @@ pub const Z80 = struct {
27102724
self.incSP();
27112725
self.setD(self.memory[self.sp]);
27122726
self.incSP();
2727+
// POP DE does not affect Q latch.
2728+
self.q = 0;
2729+
self.q_needs_update = false;
27132730
self.cycles += 10;
27142731
},
27152732
0xD2 => {
@@ -2859,6 +2876,9 @@ pub const Z80 = struct {
28592876
self.incSP();
28602877
self.setH(self.memory[self.sp]);
28612878
self.incSP();
2879+
// POP HL does not affect Q latch.
2880+
self.q = 0;
2881+
self.q_needs_update = false;
28622882
self.cycles += 10;
28632883
},
28642884
0xE2 => {
@@ -2984,6 +3004,9 @@ pub const Z80 = struct {
29843004
self.incSP();
29853005
self.setA(self.memory[self.sp]);
29863006
self.incSP();
3007+
// POP AF does not affect Q latch.
3008+
self.q = 0;
3009+
self.q_needs_update = false;
29873010
self.cycles += 10;
29883011
},
29893012
0xF2 => {
@@ -3574,6 +3597,9 @@ pub const Z80 = struct {
35743597
const nn = self.fetchWord();
35753598
self.sp = self.readWord(nn);
35763599
self.wz = nn +% 1;
3600+
// LD SP,(nn) does not affect flags; ensure Q is cleared.
3601+
self.q = 0;
3602+
self.q_needs_update = false;
35773603
self.cycles += 20;
35783604
},
35793605

0 commit comments

Comments
 (0)