@fugloy/emu 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +27 -0
- package/README.md +119 -2
- package/package.json +30 -3
- package/src/cpu/z80cpu.js +3343 -0
- package/src/debug/debugger.js +777 -0
- package/src/debug/instr.js +90 -0
- package/src/debug/label-file.js +423 -0
- package/src/debug/state.js +214 -0
- package/src/devices/cassette-tape.js +254 -0
- package/src/devices/cassette.js +259 -0
- package/src/devices/disk-image.js +578 -0
- package/src/devices/fdc-wd1793.js +466 -0
- package/src/devices/key-queue.js +261 -0
- package/src/devices/keyboard.js +176 -0
- package/src/devices/printer.js +44 -0
- package/src/devices/sound.js +53 -0
- package/src/devices/video.js +66 -0
- package/src/index.js +22 -0
- package/src/loaders/bas.js +107 -0
- package/src/loaders/cas-format.js +213 -0
- package/src/loaders/cmd-format.js +88 -0
- package/src/loaders/handoff.js +29 -0
- package/src/loaders/load.js +135 -0
- package/src/machine/io.js +290 -0
- package/src/machine/machine.js +295 -0
- package/src/machine/memory.js +207 -0
- package/src/machine/rom-hashes.js +14 -0
- package/src/machine/sha256.js +74 -0
- package/src/machine/trs80-system.js +232 -0
|
@@ -0,0 +1,3343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Z80 CPU Emulator Core
|
|
3
|
+
* Implements COMPLETE Z80 instruction set with cycle-accurate timing
|
|
4
|
+
*
|
|
5
|
+
* This is a FULL Z80 implementation matching the real TRS-80 Model III.
|
|
6
|
+
* ALL instructions must be implemented:
|
|
7
|
+
* - Base opcodes: 0x00-0xFF (252 instructions)
|
|
8
|
+
* - CB prefix: 0xCB00-0xCBFF (bit operations, rotates, shifts)
|
|
9
|
+
* - ED prefix: 0xED00-0xEDFF (extended instructions, block operations)
|
|
10
|
+
* - DD prefix: 0xDD00-0xDDFF (IX register operations)
|
|
11
|
+
* - FD prefix: 0xFD00-0xFDFF (IY register operations)
|
|
12
|
+
*
|
|
13
|
+
* Reference: docs/cschweda-z80-assembler-8a5edab282632443.txt (opcodes.js)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Flag bit positions
|
|
17
|
+
const FLAG_C = 0; // Carry
|
|
18
|
+
const FLAG_N = 1; // Add/Subtract
|
|
19
|
+
const FLAG_PV = 2; // Parity/Overflow
|
|
20
|
+
const FLAG_H = 4; // Half Carry
|
|
21
|
+
const FLAG_Z = 6; // Zero
|
|
22
|
+
const FLAG_S = 7; // Sign
|
|
23
|
+
|
|
24
|
+
// The 8-bit registers auto-mask writes to 8 bits via accessor properties
|
|
25
|
+
// over a closure-private backing store. Accessors, not a Proxy: register
|
|
26
|
+
// access is the hottest path in the emulator, and V8 inline-caches plain
|
|
27
|
+
// accessors where every Proxy trap costs a handler call plus a key
|
|
28
|
+
// lookup — swapping the Proxy for accessors measured 3.25x faster
|
|
29
|
+
// overall emulation. The backing store is only reachable through the
|
|
30
|
+
// setters, so its values are always pre-masked and reads need no mask.
|
|
31
|
+
const EIGHT_BIT_REGS = [
|
|
32
|
+
"A", "F", "B", "C", "D", "E", "H", "L",
|
|
33
|
+
"A_", "F_", "B_", "C_", "D_", "E_", "H_", "L_",
|
|
34
|
+
"IXH", "IXL", "IYH", "IYL",
|
|
35
|
+
"I", "R",
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// DD/FD CB register field (low 3 bits) -> destination for the
|
|
39
|
+
// undocumented result copy; index 6 is the documented memory-only form.
|
|
40
|
+
const INDEXED_CB_COPY_REGS = ["B", "C", "D", "E", "H", "L", null, "A"];
|
|
41
|
+
|
|
42
|
+
function makeRegisterFile() {
|
|
43
|
+
const raw = {};
|
|
44
|
+
for (const name of EIGHT_BIT_REGS) {
|
|
45
|
+
raw[name] = 0x00;
|
|
46
|
+
}
|
|
47
|
+
// SP/PC stay plain 16-bit data properties; every write site masks them.
|
|
48
|
+
const registers = {
|
|
49
|
+
SP: 0xffff,
|
|
50
|
+
PC: 0x0000,
|
|
51
|
+
};
|
|
52
|
+
for (const name of EIGHT_BIT_REGS) {
|
|
53
|
+
Object.defineProperty(registers, name, {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
get() {
|
|
56
|
+
return raw[name];
|
|
57
|
+
},
|
|
58
|
+
set(value) {
|
|
59
|
+
raw[name] = value & 0xff;
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return registers;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class Z80CPU {
|
|
67
|
+
constructor() {
|
|
68
|
+
this.registers = makeRegisterFile();
|
|
69
|
+
|
|
70
|
+
// Interrupt system
|
|
71
|
+
this.IFF1 = false;
|
|
72
|
+
this.IFF2 = false;
|
|
73
|
+
this.interruptMode = 0;
|
|
74
|
+
this.halted = false;
|
|
75
|
+
// EI enables interrupts only after the instruction that follows it;
|
|
76
|
+
// counts down at the end of each executed instruction (2 -> 1 -> enable).
|
|
77
|
+
this.pendingEI = 0;
|
|
78
|
+
this.afterPrefix = false; // a DD or FD the next byte made a no-op; no maskable interrupt until it runs
|
|
79
|
+
|
|
80
|
+
// When true, unimplemented opcodes throw instead of executing as NOP.
|
|
81
|
+
// Test harnesses use this so instruction-set gaps can't hide.
|
|
82
|
+
this.strictMode = false;
|
|
83
|
+
// Where warnings go (unimplemented opcodes); a no-op unless the host sets one.
|
|
84
|
+
this.log = () => {};
|
|
85
|
+
|
|
86
|
+
// Execution state
|
|
87
|
+
this.cycles = 0;
|
|
88
|
+
this.lastOpCycles = 0;
|
|
89
|
+
this.wz = 0x0000; // MEMPTR (internal address latch)
|
|
90
|
+
|
|
91
|
+
// External interfaces (connected by system)
|
|
92
|
+
this.readMemory = (address) => 0x00;
|
|
93
|
+
this.writeMemory = (address, value) => {};
|
|
94
|
+
this.readPort = (port) => 0x00;
|
|
95
|
+
this.writePort = (port, value) => {};
|
|
96
|
+
|
|
97
|
+
// Unimplemented opcode tracking (for debugging)
|
|
98
|
+
this.unimplementedOpcodes = new Set();
|
|
99
|
+
|
|
100
|
+
// Setup opcode handlers
|
|
101
|
+
this.setupOpcodeHandlers();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Register pair getters/setters
|
|
105
|
+
getRegisterPair(high, low) {
|
|
106
|
+
return (this.registers[high] << 8) | this.registers[low];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
setRegisterPair(high, low, value) {
|
|
110
|
+
this.registers[high] = (value >> 8) & 0xff;
|
|
111
|
+
this.registers[low] = value & 0xff;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Ensure 8-bit registers wrap correctly
|
|
115
|
+
setRegister(reg, value) {
|
|
116
|
+
this.registers[reg] = value & 0xff;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get BC() {
|
|
120
|
+
return this.getRegisterPair("B", "C");
|
|
121
|
+
}
|
|
122
|
+
set BC(value) {
|
|
123
|
+
this.setRegisterPair("B", "C", value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
get DE() {
|
|
127
|
+
return this.getRegisterPair("D", "E");
|
|
128
|
+
}
|
|
129
|
+
set DE(value) {
|
|
130
|
+
this.setRegisterPair("D", "E", value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get HL() {
|
|
134
|
+
return this.getRegisterPair("H", "L");
|
|
135
|
+
}
|
|
136
|
+
set HL(value) {
|
|
137
|
+
this.setRegisterPair("H", "L", value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get IX() {
|
|
141
|
+
return this.getRegisterPair("IXH", "IXL");
|
|
142
|
+
}
|
|
143
|
+
set IX(value) {
|
|
144
|
+
this.setRegisterPair("IXH", "IXL", value);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
get IY() {
|
|
148
|
+
return this.getRegisterPair("IYH", "IYL");
|
|
149
|
+
}
|
|
150
|
+
set IY(value) {
|
|
151
|
+
this.setRegisterPair("IYH", "IYL", value);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Flag operations
|
|
155
|
+
getFlag(flag) {
|
|
156
|
+
return (this.registers.F >> flag) & 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Undocumented X/Y flags (F bits 3 and 5): every flag-writing
|
|
161
|
+
* instruction copies bits 3/5 of some instruction-specific value —
|
|
162
|
+
* usually the result, but CP uses its operand, BIT n,(HL) uses
|
|
163
|
+
* MEMPTR's high byte, and the block ops use a synthetic n (see the
|
|
164
|
+
* call sites). ZEXALL-class fidelity lives or dies here.
|
|
165
|
+
*/
|
|
166
|
+
_xy(value) {
|
|
167
|
+
this.registers.F = (this.registers.F & ~0x28) | (value & 0x28);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Block-op X/Y mapping: Y = bit 1 of n, X = bit 3 of n. */
|
|
171
|
+
_xyBlock(n) {
|
|
172
|
+
this.registers.F =
|
|
173
|
+
(this.registers.F & ~0x28) | ((n & 0x02) << 4) | (n & 0x08);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
setFlag(flag, value) {
|
|
177
|
+
if (value) {
|
|
178
|
+
this.registers.F |= 1 << flag;
|
|
179
|
+
} else {
|
|
180
|
+
this.registers.F &= ~(1 << flag);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
get flagC() {
|
|
185
|
+
return this.getFlag(FLAG_C);
|
|
186
|
+
}
|
|
187
|
+
set flagC(v) {
|
|
188
|
+
this.setFlag(FLAG_C, v);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
get flagN() {
|
|
192
|
+
return this.getFlag(FLAG_N);
|
|
193
|
+
}
|
|
194
|
+
set flagN(v) {
|
|
195
|
+
this.setFlag(FLAG_N, v);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get flagPV() {
|
|
199
|
+
return this.getFlag(FLAG_PV);
|
|
200
|
+
}
|
|
201
|
+
set flagPV(v) {
|
|
202
|
+
this.setFlag(FLAG_PV, v);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
get flagH() {
|
|
206
|
+
return this.getFlag(FLAG_H);
|
|
207
|
+
}
|
|
208
|
+
set flagH(v) {
|
|
209
|
+
this.setFlag(FLAG_H, v);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
get flagZ() {
|
|
213
|
+
return this.getFlag(FLAG_Z);
|
|
214
|
+
}
|
|
215
|
+
set flagZ(v) {
|
|
216
|
+
this.setFlag(FLAG_Z, v);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
get flagS() {
|
|
220
|
+
return this.getFlag(FLAG_S);
|
|
221
|
+
}
|
|
222
|
+
set flagS(v) {
|
|
223
|
+
this.setFlag(FLAG_S, v);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Reset CPU
|
|
227
|
+
reset() {
|
|
228
|
+
this.registers.PC = 0x0000;
|
|
229
|
+
this.registers.SP = 0xffff;
|
|
230
|
+
this.registers.I = 0x00;
|
|
231
|
+
this.registers.R = 0x00;
|
|
232
|
+
this.wz = 0x0000; // MEMPTR — feeds BIT n,(HL)'s X/Y flags
|
|
233
|
+
this.halted = false;
|
|
234
|
+
this.IFF1 = false;
|
|
235
|
+
this.IFF2 = false;
|
|
236
|
+
this.interruptMode = 0;
|
|
237
|
+
this.pendingEI = 0;
|
|
238
|
+
this.afterPrefix = false; // a DD or FD the next byte made a no-op; no maskable interrupt until it runs
|
|
239
|
+
this.cycles = 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Execute single instruction
|
|
243
|
+
executeInstruction() {
|
|
244
|
+
this.afterPrefix = false;
|
|
245
|
+
if (this.halted) {
|
|
246
|
+
this.lastOpCycles = 4;
|
|
247
|
+
this.cycles += 4;
|
|
248
|
+
return 4;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const opcode = this.readMemory(this.registers.PC);
|
|
252
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
253
|
+
|
|
254
|
+
this.incrementR();
|
|
255
|
+
|
|
256
|
+
const cycles = this.decodeAndExecute(opcode);
|
|
257
|
+
|
|
258
|
+
// Promote a pending EI once the instruction after EI has completed
|
|
259
|
+
if (this.pendingEI > 0) {
|
|
260
|
+
this.pendingEI--;
|
|
261
|
+
if (this.pendingEI === 0) {
|
|
262
|
+
this.IFF1 = true;
|
|
263
|
+
this.IFF2 = true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
this.lastOpCycles = cycles;
|
|
268
|
+
this.cycles += cycles;
|
|
269
|
+
|
|
270
|
+
return cycles;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// R increments once per opcode fetch (so twice for prefixed
|
|
274
|
+
// instructions); bit 7 is only ever set by LD R,A and must be preserved.
|
|
275
|
+
incrementR() {
|
|
276
|
+
this.registers.R =
|
|
277
|
+
(this.registers.R & 0x80) | ((this.registers.R + 1) & 0x7f);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Accept a maskable interrupt (the Model III raises these at 30 Hz).
|
|
282
|
+
* Returns true if the interrupt was taken. In IM 2 the dataBus byte
|
|
283
|
+
* selects the vector-table entry; the Model III bus presents 0xFF,
|
|
284
|
+
* which in IM 0/1 behaves as RST 38.
|
|
285
|
+
*/
|
|
286
|
+
interrupt(dataBus = 0xff) {
|
|
287
|
+
if (!this.IFF1 || this.pendingEI > 0 || this.afterPrefix) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this.halted = false;
|
|
292
|
+
this.IFF1 = false;
|
|
293
|
+
this.IFF2 = false;
|
|
294
|
+
this.incrementR(); // acknowledge is an M1 cycle
|
|
295
|
+
|
|
296
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
297
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
298
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
299
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
300
|
+
|
|
301
|
+
if (this.interruptMode === 2) {
|
|
302
|
+
const vecAddr = ((this.registers.I << 8) | (dataBus & 0xfe)) & 0xffff;
|
|
303
|
+
const low = this.readMemory(vecAddr);
|
|
304
|
+
const high = this.readMemory((vecAddr + 1) & 0xffff);
|
|
305
|
+
this.registers.PC = (high << 8) | low;
|
|
306
|
+
this.cycles += 19;
|
|
307
|
+
} else {
|
|
308
|
+
// IM 0 with 0xFF on the bus is RST 38 — identical to IM 1 here
|
|
309
|
+
this.registers.PC = 0x0038;
|
|
310
|
+
this.cycles += 13;
|
|
311
|
+
}
|
|
312
|
+
this.wz = this.registers.PC; // MEMPTR, as for CALL
|
|
313
|
+
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Non-maskable interrupt: always taken, saves IFF1 into IFF2 so RETN
|
|
319
|
+
* can restore it, jumps to 0x0066.
|
|
320
|
+
*/
|
|
321
|
+
nmi() {
|
|
322
|
+
this.halted = false;
|
|
323
|
+
this.IFF2 = this.IFF1;
|
|
324
|
+
this.IFF1 = false;
|
|
325
|
+
this.incrementR(); // acknowledge is an M1 cycle
|
|
326
|
+
|
|
327
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
328
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
329
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
330
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
331
|
+
|
|
332
|
+
this.registers.PC = 0x0066;
|
|
333
|
+
this.wz = 0x0066; // MEMPTR, as for CALL
|
|
334
|
+
this.cycles += 11;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Decode and execute opcode
|
|
338
|
+
decodeAndExecute(opcode) {
|
|
339
|
+
// Extended instructions (second opcode fetch bumps R again)
|
|
340
|
+
if (opcode === 0xcb) {
|
|
341
|
+
this.incrementR();
|
|
342
|
+
return this.executeCB();
|
|
343
|
+
}
|
|
344
|
+
if (opcode === 0xed) {
|
|
345
|
+
this.incrementR();
|
|
346
|
+
return this.executeED();
|
|
347
|
+
}
|
|
348
|
+
if (opcode === 0xdd) {
|
|
349
|
+
return this.executeDD();
|
|
350
|
+
}
|
|
351
|
+
if (opcode === 0xfd) {
|
|
352
|
+
return this.executeFD();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Standard instructions
|
|
356
|
+
const handler = this.opcodeHandlers[opcode];
|
|
357
|
+
if (handler) {
|
|
358
|
+
return handler.call(this);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return this.reportUnimplemented(opcode, "");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Shared unimplemented-opcode path: throw in strict mode so gaps
|
|
365
|
+
// surface in tests; warn once and NOP otherwise (browser behavior).
|
|
366
|
+
reportUnimplemented(opcode, prefix) {
|
|
367
|
+
const label = `${prefix}0x${opcode
|
|
368
|
+
.toString(16)
|
|
369
|
+
.toUpperCase()
|
|
370
|
+
.padStart(2, "0")}`;
|
|
371
|
+
if (this.strictMode) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`Unimplemented opcode ${label} at PC=0x${(this.registers.PC - 1)
|
|
374
|
+
.toString(16)
|
|
375
|
+
.padStart(4, "0")}`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
const key = prefix ? `${prefix}${opcode}` : opcode;
|
|
379
|
+
if (!this.unimplementedOpcodes.has(key)) {
|
|
380
|
+
this.unimplementedOpcodes.add(key);
|
|
381
|
+
this.log("warn",
|
|
382
|
+
`Unimplemented opcode: ${label} at PC=0x${(this.registers.PC - 1)
|
|
383
|
+
.toString(16)
|
|
384
|
+
.padStart(4, "0")}`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return 4;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Setup opcode handlers
|
|
391
|
+
setupOpcodeHandlers() {
|
|
392
|
+
this.opcodeHandlers = {};
|
|
393
|
+
|
|
394
|
+
// NOP
|
|
395
|
+
this.opcodeHandlers[0x00] = () => 4;
|
|
396
|
+
|
|
397
|
+
// HALT
|
|
398
|
+
this.opcodeHandlers[0x76] = () => {
|
|
399
|
+
this.halted = true;
|
|
400
|
+
return 4;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// LD r, n (immediate loads)
|
|
404
|
+
const ldRegImm = (reg) => {
|
|
405
|
+
this.registers[reg] = this.readMemory(this.registers.PC);
|
|
406
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
407
|
+
return 7;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
this.opcodeHandlers[0x3e] = () => ldRegImm("A");
|
|
411
|
+
this.opcodeHandlers[0x06] = () => ldRegImm("B");
|
|
412
|
+
this.opcodeHandlers[0x0e] = () => ldRegImm("C");
|
|
413
|
+
this.opcodeHandlers[0x16] = () => ldRegImm("D");
|
|
414
|
+
this.opcodeHandlers[0x1e] = () => ldRegImm("E");
|
|
415
|
+
this.opcodeHandlers[0x26] = () => ldRegImm("H");
|
|
416
|
+
this.opcodeHandlers[0x2e] = () => ldRegImm("L");
|
|
417
|
+
|
|
418
|
+
// ADD A, r
|
|
419
|
+
this.opcodeHandlers[0x87] = () => this.addA(this.registers.A);
|
|
420
|
+
this.opcodeHandlers[0x80] = () => this.addA(this.registers.B);
|
|
421
|
+
this.opcodeHandlers[0x81] = () => this.addA(this.registers.C);
|
|
422
|
+
this.opcodeHandlers[0x82] = () => this.addA(this.registers.D);
|
|
423
|
+
this.opcodeHandlers[0x83] = () => this.addA(this.registers.E);
|
|
424
|
+
this.opcodeHandlers[0x84] = () => this.addA(this.registers.H);
|
|
425
|
+
this.opcodeHandlers[0x85] = () => this.addA(this.registers.L);
|
|
426
|
+
|
|
427
|
+
// SUB r
|
|
428
|
+
this.opcodeHandlers[0x97] = () => this.subA(this.registers.A);
|
|
429
|
+
this.opcodeHandlers[0x90] = () => this.subA(this.registers.B);
|
|
430
|
+
this.opcodeHandlers[0x91] = () => this.subA(this.registers.C);
|
|
431
|
+
this.opcodeHandlers[0x92] = () => this.subA(this.registers.D);
|
|
432
|
+
this.opcodeHandlers[0x93] = () => this.subA(this.registers.E);
|
|
433
|
+
this.opcodeHandlers[0x94] = () => this.subA(this.registers.H);
|
|
434
|
+
this.opcodeHandlers[0x95] = () => this.subA(this.registers.L);
|
|
435
|
+
|
|
436
|
+
// JP nn - unconditional jump
|
|
437
|
+
this.opcodeHandlers[0xc3] = () => {
|
|
438
|
+
const low = this.readMemory(this.registers.PC);
|
|
439
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
440
|
+
this.wz = (high << 8) | low;
|
|
441
|
+
this.registers.PC = (high << 8) | low;
|
|
442
|
+
return 10;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// JP Z, nn - conditional jump
|
|
446
|
+
this.opcodeHandlers[0xca] = () => {
|
|
447
|
+
const low = this.readMemory(this.registers.PC);
|
|
448
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
449
|
+
this.wz = (high << 8) | low;
|
|
450
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
451
|
+
|
|
452
|
+
if (this.flagZ) {
|
|
453
|
+
this.registers.PC = (high << 8) | low;
|
|
454
|
+
}
|
|
455
|
+
return 10;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// JP NZ, nn
|
|
459
|
+
this.opcodeHandlers[0xc2] = () => {
|
|
460
|
+
const low = this.readMemory(this.registers.PC);
|
|
461
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
462
|
+
this.wz = (high << 8) | low;
|
|
463
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
464
|
+
|
|
465
|
+
if (!this.flagZ) {
|
|
466
|
+
this.registers.PC = (high << 8) | low;
|
|
467
|
+
}
|
|
468
|
+
return 10;
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// CP n - compare immediate
|
|
472
|
+
this.opcodeHandlers[0xfe] = () => {
|
|
473
|
+
const value = this.readMemory(this.registers.PC);
|
|
474
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
475
|
+
this.subA(value, true);
|
|
476
|
+
return 7;
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
// RET - return from subroutine
|
|
480
|
+
this.opcodeHandlers[0xc9] = () => {
|
|
481
|
+
const low = this.readMemory(this.registers.SP);
|
|
482
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
483
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
484
|
+
this.registers.PC = (high << 8) | low;
|
|
485
|
+
this.wz = this.registers.PC;
|
|
486
|
+
return 10;
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// CALL nn - call subroutine
|
|
490
|
+
this.opcodeHandlers[0xcd] = () => {
|
|
491
|
+
const low = this.readMemory(this.registers.PC);
|
|
492
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
493
|
+
const targetAddr = (high << 8) | low;
|
|
494
|
+
this.wz = targetAddr;
|
|
495
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
496
|
+
|
|
497
|
+
// Push return address (little-endian)
|
|
498
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
499
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
500
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
501
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
502
|
+
|
|
503
|
+
this.registers.PC = targetAddr;
|
|
504
|
+
return 17;
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// PUSH BC
|
|
508
|
+
// Stack grows downward. Push low byte (C) first, then high byte (B)
|
|
509
|
+
this.opcodeHandlers[0xc5] = () => {
|
|
510
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
511
|
+
this.writeMemory(this.registers.SP, this.registers.B); // High byte
|
|
512
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
513
|
+
this.writeMemory(this.registers.SP, this.registers.C); // Low byte
|
|
514
|
+
return 11;
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// POP BC
|
|
518
|
+
this.opcodeHandlers[0xc1] = () => {
|
|
519
|
+
this.registers.C = this.readMemory(this.registers.SP);
|
|
520
|
+
this.registers.SP = (this.registers.SP + 1) & 0xffff;
|
|
521
|
+
this.registers.B = this.readMemory(this.registers.SP);
|
|
522
|
+
this.registers.SP = (this.registers.SP + 1) & 0xffff;
|
|
523
|
+
return 10;
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// PUSH DE
|
|
527
|
+
this.opcodeHandlers[0xd5] = () => {
|
|
528
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
529
|
+
this.writeMemory(this.registers.SP, this.registers.D);
|
|
530
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
531
|
+
this.writeMemory(this.registers.SP, this.registers.E);
|
|
532
|
+
return 11;
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// POP DE
|
|
536
|
+
this.opcodeHandlers[0xd1] = () => {
|
|
537
|
+
this.registers.E = this.readMemory(this.registers.SP);
|
|
538
|
+
this.registers.SP = (this.registers.SP + 1) & 0xffff;
|
|
539
|
+
this.registers.D = this.readMemory(this.registers.SP);
|
|
540
|
+
this.registers.SP = (this.registers.SP + 1) & 0xffff;
|
|
541
|
+
return 10;
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// PUSH HL
|
|
545
|
+
this.opcodeHandlers[0xe5] = () => {
|
|
546
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
547
|
+
this.writeMemory(this.registers.SP, this.registers.H);
|
|
548
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
549
|
+
this.writeMemory(this.registers.SP, this.registers.L);
|
|
550
|
+
return 11;
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// POP HL
|
|
554
|
+
this.opcodeHandlers[0xe1] = () => {
|
|
555
|
+
this.registers.L = this.readMemory(this.registers.SP);
|
|
556
|
+
this.registers.SP = (this.registers.SP + 1) & 0xffff;
|
|
557
|
+
this.registers.H = this.readMemory(this.registers.SP);
|
|
558
|
+
this.registers.SP = (this.registers.SP + 1) & 0xffff;
|
|
559
|
+
return 10;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// LD (HL), n
|
|
563
|
+
this.opcodeHandlers[0x36] = () => {
|
|
564
|
+
const value = this.readMemory(this.registers.PC);
|
|
565
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
566
|
+
this.writeMemory(this.HL, value);
|
|
567
|
+
return 10;
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// LD A, (HL)
|
|
571
|
+
this.opcodeHandlers[0x7e] = () => {
|
|
572
|
+
this.registers.A = this.readMemory(this.HL);
|
|
573
|
+
return 7;
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// INC r
|
|
577
|
+
this.opcodeHandlers[0x3c] = () => this.incReg("A");
|
|
578
|
+
this.opcodeHandlers[0x04] = () => this.incReg("B");
|
|
579
|
+
this.opcodeHandlers[0x0c] = () => this.incReg("C");
|
|
580
|
+
this.opcodeHandlers[0x14] = () => this.incReg("D");
|
|
581
|
+
this.opcodeHandlers[0x1c] = () => this.incReg("E");
|
|
582
|
+
this.opcodeHandlers[0x24] = () => this.incReg("H");
|
|
583
|
+
this.opcodeHandlers[0x2c] = () => this.incReg("L");
|
|
584
|
+
|
|
585
|
+
// DEC r
|
|
586
|
+
this.opcodeHandlers[0x3d] = () => this.decReg("A");
|
|
587
|
+
this.opcodeHandlers[0x05] = () => this.decReg("B");
|
|
588
|
+
this.opcodeHandlers[0x0d] = () => this.decReg("C");
|
|
589
|
+
this.opcodeHandlers[0x15] = () => this.decReg("D");
|
|
590
|
+
this.opcodeHandlers[0x1d] = () => this.decReg("E");
|
|
591
|
+
this.opcodeHandlers[0x25] = () => this.decReg("H");
|
|
592
|
+
this.opcodeHandlers[0x2d] = () => this.decReg("L");
|
|
593
|
+
|
|
594
|
+
// LD BC, nn (0x01)
|
|
595
|
+
this.opcodeHandlers[0x01] = () => {
|
|
596
|
+
const low = this.readMemory(this.registers.PC);
|
|
597
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
598
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
599
|
+
this.BC = (high << 8) | low;
|
|
600
|
+
return 10;
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// LD DE, nn (0x11)
|
|
604
|
+
this.opcodeHandlers[0x11] = () => {
|
|
605
|
+
const low = this.readMemory(this.registers.PC);
|
|
606
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
607
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
608
|
+
this.DE = (high << 8) | low;
|
|
609
|
+
return 10;
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// LD HL, nn
|
|
613
|
+
this.opcodeHandlers[0x21] = () => {
|
|
614
|
+
const low = this.readMemory(this.registers.PC);
|
|
615
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
616
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
617
|
+
this.HL = (high << 8) | low;
|
|
618
|
+
return 10;
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// LD SP, nn (0x31)
|
|
622
|
+
this.opcodeHandlers[0x31] = () => {
|
|
623
|
+
const low = this.readMemory(this.registers.PC);
|
|
624
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
625
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
626
|
+
this.registers.SP = (high << 8) | low;
|
|
627
|
+
return 10;
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
// LD (nn), HL (0x22)
|
|
631
|
+
this.opcodeHandlers[0x22] = () => {
|
|
632
|
+
const low = this.readMemory(this.registers.PC);
|
|
633
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
634
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
635
|
+
const addr = (high << 8) | low;
|
|
636
|
+
this.wz = (addr + 1) & 0xffff;
|
|
637
|
+
this.writeMemory(addr, this.registers.L);
|
|
638
|
+
this.writeMemory((addr + 1) & 0xffff, this.registers.H);
|
|
639
|
+
return 16;
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// LD HL, (nn) (0x2A)
|
|
643
|
+
this.opcodeHandlers[0x2a] = () => {
|
|
644
|
+
const low = this.readMemory(this.registers.PC);
|
|
645
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
646
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
647
|
+
const addr = (high << 8) | low;
|
|
648
|
+
this.wz = (addr + 1) & 0xffff;
|
|
649
|
+
this.registers.L = this.readMemory(addr);
|
|
650
|
+
this.registers.H = this.readMemory((addr + 1) & 0xffff);
|
|
651
|
+
return 16;
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// LD (nn), A (0x32)
|
|
655
|
+
this.opcodeHandlers[0x32] = () => {
|
|
656
|
+
const low = this.readMemory(this.registers.PC);
|
|
657
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
658
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
659
|
+
const addr = (high << 8) | low;
|
|
660
|
+
this.writeMemory(addr, this.registers.A);
|
|
661
|
+
this.wz = ((addr + 1) & 0xff) | (this.registers.A << 8);
|
|
662
|
+
return 13;
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// LD A, (nn) (0x3A)
|
|
666
|
+
this.opcodeHandlers[0x3a] = () => {
|
|
667
|
+
const low = this.readMemory(this.registers.PC);
|
|
668
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
669
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
670
|
+
const addr = (high << 8) | low;
|
|
671
|
+
this.registers.A = this.readMemory(addr);
|
|
672
|
+
this.wz = (addr + 1) & 0xffff;
|
|
673
|
+
return 13;
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
// LD A, (BC) (0x0A)
|
|
677
|
+
this.opcodeHandlers[0x0a] = () => {
|
|
678
|
+
this.registers.A = this.readMemory(this.BC);
|
|
679
|
+
this.wz = (this.BC + 1) & 0xffff;
|
|
680
|
+
return 7;
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
// LD A, (DE) (0x1A)
|
|
684
|
+
this.opcodeHandlers[0x1a] = () => {
|
|
685
|
+
this.registers.A = this.readMemory(this.DE);
|
|
686
|
+
this.wz = (this.DE + 1) & 0xffff;
|
|
687
|
+
return 7;
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
// LD (BC), A (0x02)
|
|
691
|
+
this.opcodeHandlers[0x02] = () => {
|
|
692
|
+
this.writeMemory(this.BC, this.registers.A);
|
|
693
|
+
this.wz = ((this.BC + 1) & 0xff) | (this.registers.A << 8);
|
|
694
|
+
return 7;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// LD (DE), A (0x12)
|
|
698
|
+
this.opcodeHandlers[0x12] = () => {
|
|
699
|
+
this.writeMemory(this.DE, this.registers.A);
|
|
700
|
+
this.wz = ((this.DE + 1) & 0xff) | (this.registers.A << 8);
|
|
701
|
+
return 7;
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// OUT (n), A
|
|
705
|
+
this.opcodeHandlers[0xd3] = () => {
|
|
706
|
+
const port = this.readMemory(this.registers.PC);
|
|
707
|
+
this.wz = (this.registers.A << 8) | ((port + 1) & 0xff);
|
|
708
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
709
|
+
this.writePort(port, this.registers.A);
|
|
710
|
+
return 11;
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// IN A, (n)
|
|
714
|
+
this.opcodeHandlers[0xdb] = () => {
|
|
715
|
+
const port = this.readMemory(this.registers.PC);
|
|
716
|
+
this.wz = (((this.registers.A << 8) | port) + 1) & 0xffff;
|
|
717
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
718
|
+
this.registers.A = this.readPort(port);
|
|
719
|
+
return 11;
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
// ADD A, (HL) (0x86)
|
|
723
|
+
this.opcodeHandlers[0x86] = () => {
|
|
724
|
+
this.addA(this.readMemory(this.HL));
|
|
725
|
+
return 7;
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
// ADC A, r (0x8F-0x8E)
|
|
729
|
+
this.opcodeHandlers[0x8f] = () => this.adcA(this.registers.A);
|
|
730
|
+
this.opcodeHandlers[0x88] = () => this.adcA(this.registers.B);
|
|
731
|
+
this.opcodeHandlers[0x89] = () => this.adcA(this.registers.C);
|
|
732
|
+
this.opcodeHandlers[0x8a] = () => this.adcA(this.registers.D);
|
|
733
|
+
this.opcodeHandlers[0x8b] = () => this.adcA(this.registers.E);
|
|
734
|
+
this.opcodeHandlers[0x8c] = () => this.adcA(this.registers.H);
|
|
735
|
+
this.opcodeHandlers[0x8d] = () => this.adcA(this.registers.L);
|
|
736
|
+
this.opcodeHandlers[0x8e] = () => {
|
|
737
|
+
this.adcA(this.readMemory(this.HL));
|
|
738
|
+
return 7;
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
// SBC A, r (0x9F-0x9E)
|
|
742
|
+
this.opcodeHandlers[0x9f] = () => this.sbcA(this.registers.A);
|
|
743
|
+
this.opcodeHandlers[0x98] = () => this.sbcA(this.registers.B);
|
|
744
|
+
this.opcodeHandlers[0x99] = () => this.sbcA(this.registers.C);
|
|
745
|
+
this.opcodeHandlers[0x9a] = () => this.sbcA(this.registers.D);
|
|
746
|
+
this.opcodeHandlers[0x9b] = () => this.sbcA(this.registers.E);
|
|
747
|
+
this.opcodeHandlers[0x9c] = () => this.sbcA(this.registers.H);
|
|
748
|
+
this.opcodeHandlers[0x9d] = () => this.sbcA(this.registers.L);
|
|
749
|
+
this.opcodeHandlers[0x9e] = () => {
|
|
750
|
+
this.sbcA(this.readMemory(this.HL));
|
|
751
|
+
return 7;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
// SUB A, (HL) (0x96)
|
|
755
|
+
this.opcodeHandlers[0x96] = () => {
|
|
756
|
+
this.subA(this.readMemory(this.HL));
|
|
757
|
+
return 7;
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
// AND r (0xA0-0xA7)
|
|
761
|
+
this.opcodeHandlers[0xa7] = () => this.andA(this.registers.A);
|
|
762
|
+
this.opcodeHandlers[0xa0] = () => this.andA(this.registers.B);
|
|
763
|
+
this.opcodeHandlers[0xa1] = () => this.andA(this.registers.C);
|
|
764
|
+
this.opcodeHandlers[0xa2] = () => this.andA(this.registers.D);
|
|
765
|
+
this.opcodeHandlers[0xa3] = () => this.andA(this.registers.E);
|
|
766
|
+
this.opcodeHandlers[0xa4] = () => this.andA(this.registers.H);
|
|
767
|
+
this.opcodeHandlers[0xa5] = () => this.andA(this.registers.L);
|
|
768
|
+
this.opcodeHandlers[0xa6] = () => {
|
|
769
|
+
this.andA(this.readMemory(this.HL));
|
|
770
|
+
return 7;
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
// XOR r (0xA8-0xAF)
|
|
774
|
+
this.opcodeHandlers[0xaf] = () => this.xorA(this.registers.A);
|
|
775
|
+
this.opcodeHandlers[0xa8] = () => this.xorA(this.registers.B);
|
|
776
|
+
this.opcodeHandlers[0xa9] = () => this.xorA(this.registers.C);
|
|
777
|
+
this.opcodeHandlers[0xaa] = () => this.xorA(this.registers.D);
|
|
778
|
+
this.opcodeHandlers[0xab] = () => this.xorA(this.registers.E);
|
|
779
|
+
this.opcodeHandlers[0xac] = () => this.xorA(this.registers.H);
|
|
780
|
+
this.opcodeHandlers[0xad] = () => this.xorA(this.registers.L);
|
|
781
|
+
this.opcodeHandlers[0xae] = () => {
|
|
782
|
+
this.xorA(this.readMemory(this.HL));
|
|
783
|
+
return 7;
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
// OR r (0xB0-0xB7)
|
|
787
|
+
this.opcodeHandlers[0xb7] = () => this.orA(this.registers.A);
|
|
788
|
+
this.opcodeHandlers[0xb0] = () => this.orA(this.registers.B);
|
|
789
|
+
this.opcodeHandlers[0xb1] = () => this.orA(this.registers.C);
|
|
790
|
+
this.opcodeHandlers[0xb2] = () => this.orA(this.registers.D);
|
|
791
|
+
this.opcodeHandlers[0xb3] = () => this.orA(this.registers.E);
|
|
792
|
+
this.opcodeHandlers[0xb4] = () => this.orA(this.registers.H);
|
|
793
|
+
this.opcodeHandlers[0xb5] = () => this.orA(this.registers.L);
|
|
794
|
+
this.opcodeHandlers[0xb6] = () => {
|
|
795
|
+
this.orA(this.readMemory(this.HL));
|
|
796
|
+
return 7;
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// LD r, r (0x40-0x7F) - register-to-register transfers
|
|
800
|
+
// Pattern: 0x40 | (dest << 3) | src
|
|
801
|
+
const regs = ["B", "C", "D", "E", "H", "L", null, "A"];
|
|
802
|
+
for (let dest = 0; dest < 8; dest++) {
|
|
803
|
+
for (let src = 0; src < 8; src++) {
|
|
804
|
+
if (dest === 6 || src === 6) continue; // Skip (HL) cases
|
|
805
|
+
const opcode = 0x40 | (dest << 3) | src;
|
|
806
|
+
const destReg = regs[dest];
|
|
807
|
+
const srcReg = regs[src];
|
|
808
|
+
this.opcodeHandlers[opcode] = () => {
|
|
809
|
+
this.registers[destReg] = this.registers[srcReg];
|
|
810
|
+
return 4;
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// LD (HL), r (0x70-0x75)
|
|
816
|
+
this.opcodeHandlers[0x70] = () => {
|
|
817
|
+
this.writeMemory(this.HL, this.registers.B);
|
|
818
|
+
return 7;
|
|
819
|
+
};
|
|
820
|
+
this.opcodeHandlers[0x71] = () => {
|
|
821
|
+
this.writeMemory(this.HL, this.registers.C);
|
|
822
|
+
return 7;
|
|
823
|
+
};
|
|
824
|
+
this.opcodeHandlers[0x72] = () => {
|
|
825
|
+
this.writeMemory(this.HL, this.registers.D);
|
|
826
|
+
return 7;
|
|
827
|
+
};
|
|
828
|
+
this.opcodeHandlers[0x73] = () => {
|
|
829
|
+
this.writeMemory(this.HL, this.registers.E);
|
|
830
|
+
return 7;
|
|
831
|
+
};
|
|
832
|
+
this.opcodeHandlers[0x74] = () => {
|
|
833
|
+
this.writeMemory(this.HL, this.registers.H);
|
|
834
|
+
return 7;
|
|
835
|
+
};
|
|
836
|
+
this.opcodeHandlers[0x75] = () => {
|
|
837
|
+
this.writeMemory(this.HL, this.registers.L);
|
|
838
|
+
return 7;
|
|
839
|
+
};
|
|
840
|
+
this.opcodeHandlers[0x77] = () => {
|
|
841
|
+
this.writeMemory(this.HL, this.registers.A);
|
|
842
|
+
return 7;
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
// LD r, (HL) (0x46, 0x4E, 0x56, 0x5E, 0x66, 0x6E)
|
|
846
|
+
this.opcodeHandlers[0x46] = () => {
|
|
847
|
+
this.registers.B = this.readMemory(this.HL);
|
|
848
|
+
return 7;
|
|
849
|
+
};
|
|
850
|
+
this.opcodeHandlers[0x4e] = () => {
|
|
851
|
+
this.registers.C = this.readMemory(this.HL);
|
|
852
|
+
return 7;
|
|
853
|
+
};
|
|
854
|
+
this.opcodeHandlers[0x56] = () => {
|
|
855
|
+
this.registers.D = this.readMemory(this.HL);
|
|
856
|
+
return 7;
|
|
857
|
+
};
|
|
858
|
+
this.opcodeHandlers[0x5e] = () => {
|
|
859
|
+
this.registers.E = this.readMemory(this.HL);
|
|
860
|
+
return 7;
|
|
861
|
+
};
|
|
862
|
+
this.opcodeHandlers[0x66] = () => {
|
|
863
|
+
this.registers.H = this.readMemory(this.HL);
|
|
864
|
+
return 7;
|
|
865
|
+
};
|
|
866
|
+
this.opcodeHandlers[0x6e] = () => {
|
|
867
|
+
this.registers.L = this.readMemory(this.HL);
|
|
868
|
+
return 7;
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
// ADD A, n (0xC6)
|
|
872
|
+
this.opcodeHandlers[0xc6] = () => {
|
|
873
|
+
const value = this.readMemory(this.registers.PC);
|
|
874
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
875
|
+
this.addA(value);
|
|
876
|
+
return 7;
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
// ADC A, n (0xCE)
|
|
880
|
+
this.opcodeHandlers[0xce] = () => {
|
|
881
|
+
const value = this.readMemory(this.registers.PC);
|
|
882
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
883
|
+
this.adcA(value);
|
|
884
|
+
return 7;
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
// SUB A, n (0xD6)
|
|
888
|
+
this.opcodeHandlers[0xd6] = () => {
|
|
889
|
+
const value = this.readMemory(this.registers.PC);
|
|
890
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
891
|
+
this.subA(value);
|
|
892
|
+
return 7;
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
// SBC A, n (0xDE)
|
|
896
|
+
this.opcodeHandlers[0xde] = () => {
|
|
897
|
+
const value = this.readMemory(this.registers.PC);
|
|
898
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
899
|
+
this.sbcA(value);
|
|
900
|
+
return 7;
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// AND n (0xE6)
|
|
904
|
+
this.opcodeHandlers[0xe6] = () => {
|
|
905
|
+
const value = this.readMemory(this.registers.PC);
|
|
906
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
907
|
+
this.andA(value);
|
|
908
|
+
return 7;
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
// XOR n (0xEE)
|
|
912
|
+
this.opcodeHandlers[0xee] = () => {
|
|
913
|
+
const value = this.readMemory(this.registers.PC);
|
|
914
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
915
|
+
this.xorA(value);
|
|
916
|
+
return 7;
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// OR n (0xF6)
|
|
920
|
+
this.opcodeHandlers[0xf6] = () => {
|
|
921
|
+
const value = this.readMemory(this.registers.PC);
|
|
922
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
923
|
+
this.orA(value);
|
|
924
|
+
return 7;
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
// CP r (0xBF-0xBE)
|
|
928
|
+
this.opcodeHandlers[0xbf] = () => this.subA(this.registers.A, true);
|
|
929
|
+
this.opcodeHandlers[0xb8] = () => this.subA(this.registers.B, true);
|
|
930
|
+
this.opcodeHandlers[0xb9] = () => this.subA(this.registers.C, true);
|
|
931
|
+
this.opcodeHandlers[0xba] = () => this.subA(this.registers.D, true);
|
|
932
|
+
this.opcodeHandlers[0xbb] = () => this.subA(this.registers.E, true);
|
|
933
|
+
this.opcodeHandlers[0xbc] = () => this.subA(this.registers.H, true);
|
|
934
|
+
this.opcodeHandlers[0xbd] = () => this.subA(this.registers.L, true);
|
|
935
|
+
this.opcodeHandlers[0xbe] = () => {
|
|
936
|
+
this.subA(this.readMemory(this.HL), true);
|
|
937
|
+
return 7;
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
// INC (HL) (0x34)
|
|
941
|
+
this.opcodeHandlers[0x34] = () => {
|
|
942
|
+
const value = this.readMemory(this.HL);
|
|
943
|
+
const result = (value + 1) & 0xff;
|
|
944
|
+
this.writeMemory(this.HL, result);
|
|
945
|
+
this.flagZ = result === 0;
|
|
946
|
+
this.flagS = (result & 0x80) !== 0;
|
|
947
|
+
this.flagH = (value & 0x0f) === 0x0f;
|
|
948
|
+
this.flagPV = value === 0x7f;
|
|
949
|
+
this.flagN = 0;
|
|
950
|
+
this._xy(result);
|
|
951
|
+
return 11;
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
// DEC (HL) (0x35)
|
|
955
|
+
this.opcodeHandlers[0x35] = () => {
|
|
956
|
+
const value = this.readMemory(this.HL);
|
|
957
|
+
const result = (value - 1) & 0xff;
|
|
958
|
+
this.writeMemory(this.HL, result);
|
|
959
|
+
this.flagZ = result === 0;
|
|
960
|
+
this.flagS = (result & 0x80) !== 0;
|
|
961
|
+
this.flagH = (value & 0x0f) === 0x00;
|
|
962
|
+
this.flagPV = value === 0x80;
|
|
963
|
+
this.flagN = 1;
|
|
964
|
+
this._xy(result);
|
|
965
|
+
return 11;
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
// JP C, nn (0xDA)
|
|
969
|
+
this.opcodeHandlers[0xda] = () => {
|
|
970
|
+
const low = this.readMemory(this.registers.PC);
|
|
971
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
972
|
+
this.wz = (high << 8) | low;
|
|
973
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
974
|
+
if (this.flagC) {
|
|
975
|
+
this.registers.PC = (high << 8) | low;
|
|
976
|
+
}
|
|
977
|
+
return 10;
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
// JP NC, nn (0xD2)
|
|
981
|
+
this.opcodeHandlers[0xd2] = () => {
|
|
982
|
+
const low = this.readMemory(this.registers.PC);
|
|
983
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
984
|
+
this.wz = (high << 8) | low;
|
|
985
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
986
|
+
if (!this.flagC) {
|
|
987
|
+
this.registers.PC = (high << 8) | low;
|
|
988
|
+
}
|
|
989
|
+
return 10;
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
// JP P, nn (0xF2)
|
|
993
|
+
this.opcodeHandlers[0xf2] = () => {
|
|
994
|
+
const low = this.readMemory(this.registers.PC);
|
|
995
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
996
|
+
this.wz = (high << 8) | low;
|
|
997
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
998
|
+
if (!this.flagS) {
|
|
999
|
+
this.registers.PC = (high << 8) | low;
|
|
1000
|
+
}
|
|
1001
|
+
return 10;
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
// JP M, nn (0xFA)
|
|
1005
|
+
this.opcodeHandlers[0xfa] = () => {
|
|
1006
|
+
const low = this.readMemory(this.registers.PC);
|
|
1007
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1008
|
+
this.wz = (high << 8) | low;
|
|
1009
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1010
|
+
if (this.flagS) {
|
|
1011
|
+
this.registers.PC = (high << 8) | low;
|
|
1012
|
+
}
|
|
1013
|
+
return 10;
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
// JP PO, nn (0xE2)
|
|
1017
|
+
this.opcodeHandlers[0xe2] = () => {
|
|
1018
|
+
const low = this.readMemory(this.registers.PC);
|
|
1019
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1020
|
+
this.wz = (high << 8) | low;
|
|
1021
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1022
|
+
if (!this.flagPV) {
|
|
1023
|
+
this.registers.PC = (high << 8) | low;
|
|
1024
|
+
}
|
|
1025
|
+
return 10;
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
// JP PE, nn (0xEA)
|
|
1029
|
+
this.opcodeHandlers[0xea] = () => {
|
|
1030
|
+
const low = this.readMemory(this.registers.PC);
|
|
1031
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1032
|
+
this.wz = (high << 8) | low;
|
|
1033
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1034
|
+
if (this.flagPV) {
|
|
1035
|
+
this.registers.PC = (high << 8) | low;
|
|
1036
|
+
}
|
|
1037
|
+
return 10;
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
// JP (HL) (0xE9)
|
|
1041
|
+
this.opcodeHandlers[0xe9] = () => {
|
|
1042
|
+
this.registers.PC = this.HL;
|
|
1043
|
+
return 4;
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
// JR e (0x18) - relative jump
|
|
1047
|
+
this.opcodeHandlers[0x18] = () => {
|
|
1048
|
+
const offset = this.readMemory(this.registers.PC);
|
|
1049
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1050
|
+
const signedOffset = offset & 0x80 ? offset - 256 : offset;
|
|
1051
|
+
this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
|
|
1052
|
+
this.wz = this.registers.PC;
|
|
1053
|
+
return 12;
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
// JR Z, e (0x28)
|
|
1057
|
+
this.opcodeHandlers[0x28] = () => {
|
|
1058
|
+
const offset = this.readMemory(this.registers.PC);
|
|
1059
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1060
|
+
if (this.flagZ) {
|
|
1061
|
+
const signedOffset = offset & 0x80 ? offset - 256 : offset;
|
|
1062
|
+
this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
|
|
1063
|
+
this.wz = this.registers.PC;
|
|
1064
|
+
return 12;
|
|
1065
|
+
}
|
|
1066
|
+
return 7;
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
// JR NZ, e (0x20)
|
|
1070
|
+
this.opcodeHandlers[0x20] = () => {
|
|
1071
|
+
const offset = this.readMemory(this.registers.PC);
|
|
1072
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1073
|
+
if (!this.flagZ) {
|
|
1074
|
+
const signedOffset = offset & 0x80 ? offset - 256 : offset;
|
|
1075
|
+
this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
|
|
1076
|
+
this.wz = this.registers.PC;
|
|
1077
|
+
return 12;
|
|
1078
|
+
}
|
|
1079
|
+
return 7;
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
// JR C, e (0x38)
|
|
1083
|
+
this.opcodeHandlers[0x38] = () => {
|
|
1084
|
+
const offset = this.readMemory(this.registers.PC);
|
|
1085
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1086
|
+
if (this.flagC) {
|
|
1087
|
+
const signedOffset = offset & 0x80 ? offset - 256 : offset;
|
|
1088
|
+
this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
|
|
1089
|
+
this.wz = this.registers.PC;
|
|
1090
|
+
return 12;
|
|
1091
|
+
}
|
|
1092
|
+
return 7;
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
// JR NC, e (0x30)
|
|
1096
|
+
this.opcodeHandlers[0x30] = () => {
|
|
1097
|
+
const offset = this.readMemory(this.registers.PC);
|
|
1098
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1099
|
+
if (!this.flagC) {
|
|
1100
|
+
const signedOffset = offset & 0x80 ? offset - 256 : offset;
|
|
1101
|
+
this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
|
|
1102
|
+
this.wz = this.registers.PC;
|
|
1103
|
+
return 12;
|
|
1104
|
+
}
|
|
1105
|
+
return 7;
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
// DJNZ e (0x10)
|
|
1109
|
+
this.opcodeHandlers[0x10] = () => {
|
|
1110
|
+
const offset = this.readMemory(this.registers.PC);
|
|
1111
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1112
|
+
this.registers.B = (this.registers.B - 1) & 0xff;
|
|
1113
|
+
if (this.registers.B !== 0) {
|
|
1114
|
+
const signedOffset = offset & 0x80 ? offset - 256 : offset;
|
|
1115
|
+
this.registers.PC = (this.registers.PC + signedOffset) & 0xffff;
|
|
1116
|
+
this.wz = this.registers.PC;
|
|
1117
|
+
return 13;
|
|
1118
|
+
}
|
|
1119
|
+
return 8;
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
// RET NZ (0xC0)
|
|
1123
|
+
this.opcodeHandlers[0xc0] = () => {
|
|
1124
|
+
if (!this.flagZ) {
|
|
1125
|
+
const low = this.readMemory(this.registers.SP);
|
|
1126
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
1127
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
1128
|
+
this.registers.PC = (high << 8) | low;
|
|
1129
|
+
this.wz = this.registers.PC;
|
|
1130
|
+
return 11;
|
|
1131
|
+
}
|
|
1132
|
+
return 5;
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
// RET Z (0xC8)
|
|
1136
|
+
this.opcodeHandlers[0xc8] = () => {
|
|
1137
|
+
if (this.flagZ) {
|
|
1138
|
+
const low = this.readMemory(this.registers.SP);
|
|
1139
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
1140
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
1141
|
+
this.registers.PC = (high << 8) | low;
|
|
1142
|
+
this.wz = this.registers.PC;
|
|
1143
|
+
return 11;
|
|
1144
|
+
}
|
|
1145
|
+
return 5;
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
// RET C (0xD8)
|
|
1149
|
+
this.opcodeHandlers[0xd8] = () => {
|
|
1150
|
+
if (this.flagC) {
|
|
1151
|
+
const low = this.readMemory(this.registers.SP);
|
|
1152
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
1153
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
1154
|
+
this.registers.PC = (high << 8) | low;
|
|
1155
|
+
this.wz = this.registers.PC;
|
|
1156
|
+
return 11;
|
|
1157
|
+
}
|
|
1158
|
+
return 5;
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
// RET NC (0xD0)
|
|
1162
|
+
this.opcodeHandlers[0xd0] = () => {
|
|
1163
|
+
if (!this.flagC) {
|
|
1164
|
+
const low = this.readMemory(this.registers.SP);
|
|
1165
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
1166
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
1167
|
+
this.registers.PC = (high << 8) | low;
|
|
1168
|
+
this.wz = this.registers.PC;
|
|
1169
|
+
return 11;
|
|
1170
|
+
}
|
|
1171
|
+
return 5;
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
// RET P (0xF0)
|
|
1175
|
+
this.opcodeHandlers[0xf0] = () => {
|
|
1176
|
+
if (!this.flagS) {
|
|
1177
|
+
const low = this.readMemory(this.registers.SP);
|
|
1178
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
1179
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
1180
|
+
this.registers.PC = (high << 8) | low;
|
|
1181
|
+
this.wz = this.registers.PC;
|
|
1182
|
+
return 11;
|
|
1183
|
+
}
|
|
1184
|
+
return 5;
|
|
1185
|
+
};
|
|
1186
|
+
|
|
1187
|
+
// RET M (0xF8)
|
|
1188
|
+
this.opcodeHandlers[0xf8] = () => {
|
|
1189
|
+
if (this.flagS) {
|
|
1190
|
+
const low = this.readMemory(this.registers.SP);
|
|
1191
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
1192
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
1193
|
+
this.registers.PC = (high << 8) | low;
|
|
1194
|
+
this.wz = this.registers.PC;
|
|
1195
|
+
return 11;
|
|
1196
|
+
}
|
|
1197
|
+
return 5;
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
// RET PO (0xE0)
|
|
1201
|
+
this.opcodeHandlers[0xe0] = () => {
|
|
1202
|
+
if (!this.flagPV) {
|
|
1203
|
+
const low = this.readMemory(this.registers.SP);
|
|
1204
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
1205
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
1206
|
+
this.registers.PC = (high << 8) | low;
|
|
1207
|
+
this.wz = this.registers.PC;
|
|
1208
|
+
return 11;
|
|
1209
|
+
}
|
|
1210
|
+
return 5;
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
// RET PE (0xE8)
|
|
1214
|
+
this.opcodeHandlers[0xe8] = () => {
|
|
1215
|
+
if (this.flagPV) {
|
|
1216
|
+
const low = this.readMemory(this.registers.SP);
|
|
1217
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
1218
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
1219
|
+
this.registers.PC = (high << 8) | low;
|
|
1220
|
+
this.wz = this.registers.PC;
|
|
1221
|
+
return 11;
|
|
1222
|
+
}
|
|
1223
|
+
return 5;
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
// CALL Z, nn (0xCC)
|
|
1227
|
+
this.opcodeHandlers[0xcc] = () => {
|
|
1228
|
+
const low = this.readMemory(this.registers.PC);
|
|
1229
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1230
|
+
this.wz = (high << 8) | low;
|
|
1231
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1232
|
+
if (this.flagZ) {
|
|
1233
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1234
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
1235
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1236
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
1237
|
+
this.registers.PC = (high << 8) | low;
|
|
1238
|
+
return 17;
|
|
1239
|
+
}
|
|
1240
|
+
return 10;
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
// CALL NZ, nn (0xC4)
|
|
1244
|
+
this.opcodeHandlers[0xc4] = () => {
|
|
1245
|
+
const low = this.readMemory(this.registers.PC);
|
|
1246
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1247
|
+
this.wz = (high << 8) | low;
|
|
1248
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1249
|
+
if (!this.flagZ) {
|
|
1250
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1251
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
1252
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1253
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
1254
|
+
this.registers.PC = (high << 8) | low;
|
|
1255
|
+
return 17;
|
|
1256
|
+
}
|
|
1257
|
+
return 10;
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1260
|
+
// CALL C, nn (0xDC)
|
|
1261
|
+
this.opcodeHandlers[0xdc] = () => {
|
|
1262
|
+
const low = this.readMemory(this.registers.PC);
|
|
1263
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1264
|
+
this.wz = (high << 8) | low;
|
|
1265
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1266
|
+
if (this.flagC) {
|
|
1267
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1268
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
1269
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1270
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
1271
|
+
this.registers.PC = (high << 8) | low;
|
|
1272
|
+
return 17;
|
|
1273
|
+
}
|
|
1274
|
+
return 10;
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
// CALL NC, nn (0xD4)
|
|
1278
|
+
this.opcodeHandlers[0xd4] = () => {
|
|
1279
|
+
const low = this.readMemory(this.registers.PC);
|
|
1280
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1281
|
+
this.wz = (high << 8) | low;
|
|
1282
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1283
|
+
if (!this.flagC) {
|
|
1284
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1285
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
1286
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1287
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
1288
|
+
this.registers.PC = (high << 8) | low;
|
|
1289
|
+
return 17;
|
|
1290
|
+
}
|
|
1291
|
+
return 10;
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
// CALL P, nn (0xF4)
|
|
1295
|
+
this.opcodeHandlers[0xf4] = () => {
|
|
1296
|
+
const low = this.readMemory(this.registers.PC);
|
|
1297
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1298
|
+
this.wz = (high << 8) | low;
|
|
1299
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1300
|
+
if (!this.flagS) {
|
|
1301
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1302
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
1303
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1304
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
1305
|
+
this.registers.PC = (high << 8) | low;
|
|
1306
|
+
return 17;
|
|
1307
|
+
}
|
|
1308
|
+
return 10;
|
|
1309
|
+
};
|
|
1310
|
+
|
|
1311
|
+
// CALL M, nn (0xFC)
|
|
1312
|
+
this.opcodeHandlers[0xfc] = () => {
|
|
1313
|
+
const low = this.readMemory(this.registers.PC);
|
|
1314
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1315
|
+
this.wz = (high << 8) | low;
|
|
1316
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1317
|
+
if (this.flagS) {
|
|
1318
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1319
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
1320
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1321
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
1322
|
+
this.registers.PC = (high << 8) | low;
|
|
1323
|
+
return 17;
|
|
1324
|
+
}
|
|
1325
|
+
return 10;
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
// CALL PO, nn (0xE4)
|
|
1329
|
+
this.opcodeHandlers[0xe4] = () => {
|
|
1330
|
+
const low = this.readMemory(this.registers.PC);
|
|
1331
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1332
|
+
this.wz = (high << 8) | low;
|
|
1333
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1334
|
+
if (!this.flagPV) {
|
|
1335
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1336
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
1337
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1338
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
1339
|
+
this.registers.PC = (high << 8) | low;
|
|
1340
|
+
return 17;
|
|
1341
|
+
}
|
|
1342
|
+
return 10;
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
// CALL PE, nn (0xEC)
|
|
1346
|
+
this.opcodeHandlers[0xec] = () => {
|
|
1347
|
+
const low = this.readMemory(this.registers.PC);
|
|
1348
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
1349
|
+
this.wz = (high << 8) | low;
|
|
1350
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
1351
|
+
if (this.flagPV) {
|
|
1352
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1353
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
1354
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1355
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
1356
|
+
this.registers.PC = (high << 8) | low;
|
|
1357
|
+
return 17;
|
|
1358
|
+
}
|
|
1359
|
+
return 10;
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
// RST p (0xC7-0xFF, step 8)
|
|
1363
|
+
for (let i = 0; i < 8; i++) {
|
|
1364
|
+
const opcode = 0xc7 | (i << 3);
|
|
1365
|
+
const addr = i * 8;
|
|
1366
|
+
this.opcodeHandlers[opcode] = () => {
|
|
1367
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1368
|
+
this.writeMemory(this.registers.SP, (this.registers.PC >> 8) & 0xff);
|
|
1369
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1370
|
+
this.writeMemory(this.registers.SP, this.registers.PC & 0xff);
|
|
1371
|
+
this.registers.PC = addr;
|
|
1372
|
+
this.wz = addr;
|
|
1373
|
+
return 11;
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// DI (0xF3)
|
|
1378
|
+
this.opcodeHandlers[0xf3] = () => {
|
|
1379
|
+
this.IFF1 = false;
|
|
1380
|
+
this.IFF2 = false;
|
|
1381
|
+
this.pendingEI = 0; // a DI right after EI wins
|
|
1382
|
+
return 4;
|
|
1383
|
+
};
|
|
1384
|
+
|
|
1385
|
+
// EI (0xFB) - takes effect after the NEXT instruction completes, so
|
|
1386
|
+
// an EI;RETI pair can't be interrupted between the two instructions.
|
|
1387
|
+
this.opcodeHandlers[0xfb] = () => {
|
|
1388
|
+
this.pendingEI = 2;
|
|
1389
|
+
return 4;
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
// SCF (0x37) - Set Carry Flag
|
|
1393
|
+
this.opcodeHandlers[0x37] = () => {
|
|
1394
|
+
this.flagC = true;
|
|
1395
|
+
this.flagH = false;
|
|
1396
|
+
this.flagN = false;
|
|
1397
|
+
this._xy(this.registers.A);
|
|
1398
|
+
return 4;
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
// CCF (0x3F) - Complement Carry Flag; H receives the previous carry
|
|
1402
|
+
this.opcodeHandlers[0x3f] = () => {
|
|
1403
|
+
const oldCarry = this.flagC;
|
|
1404
|
+
this.flagC = !oldCarry;
|
|
1405
|
+
this.flagH = oldCarry;
|
|
1406
|
+
this.flagN = false;
|
|
1407
|
+
this._xy(this.registers.A);
|
|
1408
|
+
return 4;
|
|
1409
|
+
};
|
|
1410
|
+
|
|
1411
|
+
// CPL (0x2F) - Complement Accumulator
|
|
1412
|
+
this.opcodeHandlers[0x2f] = () => {
|
|
1413
|
+
this.registers.A = ~this.registers.A & 0xff;
|
|
1414
|
+
this.flagH = true;
|
|
1415
|
+
this.flagN = true;
|
|
1416
|
+
this._xy(this.registers.A);
|
|
1417
|
+
return 4;
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
// DAA (0x27) - Decimal Adjust Accumulator (canonical N/H/C correction)
|
|
1421
|
+
this.opcodeHandlers[0x27] = () => {
|
|
1422
|
+
const before = this.registers.A;
|
|
1423
|
+
let correction = 0;
|
|
1424
|
+
let carry = this.flagC ? 1 : 0;
|
|
1425
|
+
|
|
1426
|
+
if (this.flagH || (before & 0x0f) > 9) {
|
|
1427
|
+
correction = 0x06;
|
|
1428
|
+
}
|
|
1429
|
+
if (carry || before > 0x99) {
|
|
1430
|
+
correction |= 0x60;
|
|
1431
|
+
carry = 1;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
const a = this.flagN
|
|
1435
|
+
? (before - correction) & 0xff
|
|
1436
|
+
: (before + correction) & 0xff;
|
|
1437
|
+
|
|
1438
|
+
// H after DAA: for additions, carry out of bit 3 of the correction
|
|
1439
|
+
// step; for subtractions, only when the low nibble borrows.
|
|
1440
|
+
this.flagH = this.flagN
|
|
1441
|
+
? this.flagH && (before & 0x0f) < 0x06
|
|
1442
|
+
: (before & 0x0f) + (correction & 0x0f) > 0x0f;
|
|
1443
|
+
this.flagC = carry;
|
|
1444
|
+
this.registers.A = a;
|
|
1445
|
+
this.flagZ = a === 0;
|
|
1446
|
+
this.flagS = (a & 0x80) !== 0;
|
|
1447
|
+
this.flagPV = this.parity(a);
|
|
1448
|
+
this._xy(a);
|
|
1449
|
+
return 4;
|
|
1450
|
+
};
|
|
1451
|
+
|
|
1452
|
+
// RLCA (0x07)
|
|
1453
|
+
this.opcodeHandlers[0x07] = () => {
|
|
1454
|
+
const a = this.registers.A;
|
|
1455
|
+
this.flagC = (a & 0x80) !== 0;
|
|
1456
|
+
this.registers.A = ((a << 1) | (a >> 7)) & 0xff;
|
|
1457
|
+
this.flagH = false;
|
|
1458
|
+
this.flagN = false;
|
|
1459
|
+
this._xy(this.registers.A);
|
|
1460
|
+
return 4;
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
// RLA (0x17)
|
|
1464
|
+
this.opcodeHandlers[0x17] = () => {
|
|
1465
|
+
const a = this.registers.A;
|
|
1466
|
+
const carry = this.flagC ? 1 : 0;
|
|
1467
|
+
this.flagC = (a & 0x80) !== 0;
|
|
1468
|
+
this.registers.A = ((a << 1) | carry) & 0xff;
|
|
1469
|
+
this.flagH = false;
|
|
1470
|
+
this.flagN = false;
|
|
1471
|
+
this._xy(this.registers.A);
|
|
1472
|
+
return 4;
|
|
1473
|
+
};
|
|
1474
|
+
|
|
1475
|
+
// RRCA (0x0F)
|
|
1476
|
+
this.opcodeHandlers[0x0f] = () => {
|
|
1477
|
+
const a = this.registers.A;
|
|
1478
|
+
this.flagC = (a & 0x01) !== 0;
|
|
1479
|
+
this.registers.A = ((a >> 1) | (a << 7)) & 0xff;
|
|
1480
|
+
this.flagH = false;
|
|
1481
|
+
this.flagN = false;
|
|
1482
|
+
this._xy(this.registers.A);
|
|
1483
|
+
return 4;
|
|
1484
|
+
};
|
|
1485
|
+
|
|
1486
|
+
// RRA (0x1F)
|
|
1487
|
+
this.opcodeHandlers[0x1f] = () => {
|
|
1488
|
+
const a = this.registers.A;
|
|
1489
|
+
const carry = this.flagC ? 0x80 : 0;
|
|
1490
|
+
this.flagC = (a & 0x01) !== 0;
|
|
1491
|
+
this.registers.A = ((a >> 1) | carry) & 0xff;
|
|
1492
|
+
this.flagH = false;
|
|
1493
|
+
this.flagN = false;
|
|
1494
|
+
this._xy(this.registers.A);
|
|
1495
|
+
return 4;
|
|
1496
|
+
};
|
|
1497
|
+
|
|
1498
|
+
// EX DE, HL (0xEB)
|
|
1499
|
+
this.opcodeHandlers[0xeb] = () => {
|
|
1500
|
+
const temp = this.DE;
|
|
1501
|
+
this.DE = this.HL;
|
|
1502
|
+
this.HL = temp;
|
|
1503
|
+
return 4;
|
|
1504
|
+
};
|
|
1505
|
+
|
|
1506
|
+
// EX AF, AF' (0x08)
|
|
1507
|
+
this.opcodeHandlers[0x08] = () => {
|
|
1508
|
+
const tempA = this.registers.A;
|
|
1509
|
+
const tempF = this.registers.F;
|
|
1510
|
+
this.registers.A = this.registers.A_;
|
|
1511
|
+
this.registers.F = this.registers.F_;
|
|
1512
|
+
this.registers.A_ = tempA;
|
|
1513
|
+
this.registers.F_ = tempF;
|
|
1514
|
+
return 4;
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
// EX (SP), HL (0xE3)
|
|
1518
|
+
this.opcodeHandlers[0xe3] = () => {
|
|
1519
|
+
const low = this.readMemory(this.registers.SP);
|
|
1520
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
1521
|
+
const temp = (high << 8) | low;
|
|
1522
|
+
this.writeMemory(this.registers.SP, this.registers.L);
|
|
1523
|
+
this.writeMemory((this.registers.SP + 1) & 0xffff, this.registers.H);
|
|
1524
|
+
this.HL = temp;
|
|
1525
|
+
this.wz = temp;
|
|
1526
|
+
return 19;
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1529
|
+
// EXX (0xD9)
|
|
1530
|
+
this.opcodeHandlers[0xd9] = () => {
|
|
1531
|
+
const tempB = this.registers.B;
|
|
1532
|
+
const tempC = this.registers.C;
|
|
1533
|
+
const tempD = this.registers.D;
|
|
1534
|
+
const tempE = this.registers.E;
|
|
1535
|
+
const tempH = this.registers.H;
|
|
1536
|
+
const tempL = this.registers.L;
|
|
1537
|
+
this.registers.B = this.registers.B_;
|
|
1538
|
+
this.registers.C = this.registers.C_;
|
|
1539
|
+
this.registers.D = this.registers.D_;
|
|
1540
|
+
this.registers.E = this.registers.E_;
|
|
1541
|
+
this.registers.H = this.registers.H_;
|
|
1542
|
+
this.registers.L = this.registers.L_;
|
|
1543
|
+
this.registers.B_ = tempB;
|
|
1544
|
+
this.registers.C_ = tempC;
|
|
1545
|
+
this.registers.D_ = tempD;
|
|
1546
|
+
this.registers.E_ = tempE;
|
|
1547
|
+
this.registers.H_ = tempH;
|
|
1548
|
+
this.registers.L_ = tempL;
|
|
1549
|
+
return 4;
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
// ADD HL, BC (0x09)
|
|
1553
|
+
this.opcodeHandlers[0x09] = () => {
|
|
1554
|
+
this.addHL(this.BC);
|
|
1555
|
+
return 11;
|
|
1556
|
+
};
|
|
1557
|
+
|
|
1558
|
+
// ADD HL, DE (0x19)
|
|
1559
|
+
this.opcodeHandlers[0x19] = () => {
|
|
1560
|
+
this.addHL(this.DE);
|
|
1561
|
+
return 11;
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
// ADD HL, HL (0x29)
|
|
1565
|
+
this.opcodeHandlers[0x29] = () => {
|
|
1566
|
+
this.addHL(this.HL);
|
|
1567
|
+
return 11;
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
// ADD HL, SP (0x39)
|
|
1571
|
+
this.opcodeHandlers[0x39] = () => {
|
|
1572
|
+
this.addHL(this.registers.SP);
|
|
1573
|
+
return 11;
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
// PUSH AF (0xF5)
|
|
1577
|
+
this.opcodeHandlers[0xf5] = () => {
|
|
1578
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1579
|
+
this.writeMemory(this.registers.SP, this.registers.A);
|
|
1580
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1581
|
+
this.writeMemory(this.registers.SP, this.registers.F);
|
|
1582
|
+
return 11;
|
|
1583
|
+
};
|
|
1584
|
+
|
|
1585
|
+
// POP AF (0xF1)
|
|
1586
|
+
this.opcodeHandlers[0xf1] = () => {
|
|
1587
|
+
this.registers.F = this.readMemory(this.registers.SP);
|
|
1588
|
+
this.registers.SP = (this.registers.SP + 1) & 0xffff;
|
|
1589
|
+
this.registers.A = this.readMemory(this.registers.SP);
|
|
1590
|
+
this.registers.SP = (this.registers.SP + 1) & 0xffff;
|
|
1591
|
+
return 10;
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
// INC BC (0x03)
|
|
1595
|
+
this.opcodeHandlers[0x03] = () => {
|
|
1596
|
+
this.BC = (this.BC + 1) & 0xffff;
|
|
1597
|
+
return 6;
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
// INC DE (0x13)
|
|
1601
|
+
this.opcodeHandlers[0x13] = () => {
|
|
1602
|
+
this.DE = (this.DE + 1) & 0xffff;
|
|
1603
|
+
return 6;
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
// INC HL (0x23)
|
|
1607
|
+
this.opcodeHandlers[0x23] = () => {
|
|
1608
|
+
this.HL = (this.HL + 1) & 0xffff;
|
|
1609
|
+
return 6;
|
|
1610
|
+
};
|
|
1611
|
+
|
|
1612
|
+
// INC SP (0x33)
|
|
1613
|
+
this.opcodeHandlers[0x33] = () => {
|
|
1614
|
+
this.registers.SP = (this.registers.SP + 1) & 0xffff;
|
|
1615
|
+
return 6;
|
|
1616
|
+
};
|
|
1617
|
+
|
|
1618
|
+
// DEC BC (0x0B)
|
|
1619
|
+
this.opcodeHandlers[0x0b] = () => {
|
|
1620
|
+
this.BC = (this.BC - 1) & 0xffff;
|
|
1621
|
+
return 6;
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
// DEC DE (0x1B)
|
|
1625
|
+
this.opcodeHandlers[0x1b] = () => {
|
|
1626
|
+
this.DE = (this.DE - 1) & 0xffff;
|
|
1627
|
+
return 6;
|
|
1628
|
+
};
|
|
1629
|
+
|
|
1630
|
+
// DEC HL (0x2B)
|
|
1631
|
+
this.opcodeHandlers[0x2b] = () => {
|
|
1632
|
+
this.HL = (this.HL - 1) & 0xffff;
|
|
1633
|
+
return 6;
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
// DEC SP (0x3B)
|
|
1637
|
+
this.opcodeHandlers[0x3b] = () => {
|
|
1638
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
1639
|
+
return 6;
|
|
1640
|
+
};
|
|
1641
|
+
|
|
1642
|
+
// LD SP, HL (0xF9)
|
|
1643
|
+
this.opcodeHandlers[0xf9] = () => {
|
|
1644
|
+
this.registers.SP = this.HL;
|
|
1645
|
+
return 6;
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// Extended instruction handlers - ALL must be fully implemented
|
|
1650
|
+
executeCB() {
|
|
1651
|
+
const opcode = this.readMemory(this.registers.PC);
|
|
1652
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1653
|
+
|
|
1654
|
+
// CB instructions (bit operations, rotates, shifts)
|
|
1655
|
+
// Pattern: opcode = base | (register << 3) | register
|
|
1656
|
+
// Register mapping: 0=B, 1=C, 2=D, 3=E, 4=H, 5=L, 6=(HL), 7=A
|
|
1657
|
+
|
|
1658
|
+
const regs = ["B", "C", "D", "E", "H", "L", null, "A"];
|
|
1659
|
+
const reg = opcode & 0x07;
|
|
1660
|
+
const base = opcode & 0xf8;
|
|
1661
|
+
|
|
1662
|
+
if (reg === 6) {
|
|
1663
|
+
// (HL) operations - 15 cycles
|
|
1664
|
+
switch (base) {
|
|
1665
|
+
case 0x00: // RLC (HL)
|
|
1666
|
+
return this.rlcMem(this.HL);
|
|
1667
|
+
case 0x08: // RRC (HL)
|
|
1668
|
+
return this.rrcMem(this.HL);
|
|
1669
|
+
case 0x10: // RL (HL)
|
|
1670
|
+
return this.rlMem(this.HL);
|
|
1671
|
+
case 0x18: // RR (HL)
|
|
1672
|
+
return this.rrMem(this.HL);
|
|
1673
|
+
case 0x20: // SLA (HL)
|
|
1674
|
+
return this.slaMem(this.HL);
|
|
1675
|
+
case 0x28: // SRA (HL)
|
|
1676
|
+
return this.sraMem(this.HL);
|
|
1677
|
+
case 0x30: // SLL (HL) - undocumented
|
|
1678
|
+
return this.sllMem(this.HL);
|
|
1679
|
+
case 0x38: // SRL (HL)
|
|
1680
|
+
return this.srlMem(this.HL);
|
|
1681
|
+
case 0x40:
|
|
1682
|
+
case 0x48:
|
|
1683
|
+
case 0x50:
|
|
1684
|
+
case 0x58:
|
|
1685
|
+
case 0x60:
|
|
1686
|
+
case 0x68:
|
|
1687
|
+
case 0x70:
|
|
1688
|
+
case 0x78: // BIT n, (HL)
|
|
1689
|
+
return this.bitMem(this.HL, (base >> 3) & 0x07);
|
|
1690
|
+
case 0x80:
|
|
1691
|
+
case 0x88:
|
|
1692
|
+
case 0x90:
|
|
1693
|
+
case 0x98:
|
|
1694
|
+
case 0xa0:
|
|
1695
|
+
case 0xa8:
|
|
1696
|
+
case 0xb0:
|
|
1697
|
+
case 0xb8: // RES n, (HL)
|
|
1698
|
+
return this.resMem(this.HL, (base >> 3) & 0x07);
|
|
1699
|
+
case 0xc0:
|
|
1700
|
+
case 0xc8:
|
|
1701
|
+
case 0xd0:
|
|
1702
|
+
case 0xd8:
|
|
1703
|
+
case 0xe0:
|
|
1704
|
+
case 0xe8:
|
|
1705
|
+
case 0xf0:
|
|
1706
|
+
case 0xf8: // SET n, (HL)
|
|
1707
|
+
return this.setMem(this.HL, (base >> 3) & 0x07);
|
|
1708
|
+
default:
|
|
1709
|
+
if (!this.unimplementedOpcodes.has(0xcb00 | opcode)) {
|
|
1710
|
+
this.unimplementedOpcodes.add(0xcb00 | opcode);
|
|
1711
|
+
this.log("warn",
|
|
1712
|
+
`Unimplemented CB opcode: 0xCB 0x${opcode
|
|
1713
|
+
.toString(16)
|
|
1714
|
+
.toUpperCase()
|
|
1715
|
+
.padStart(2, "0")}`
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
return 8;
|
|
1719
|
+
}
|
|
1720
|
+
} else {
|
|
1721
|
+
// Register operations - 8 cycles
|
|
1722
|
+
const regName = regs[reg];
|
|
1723
|
+
switch (base) {
|
|
1724
|
+
case 0x00: // RLC r
|
|
1725
|
+
return this.rlcReg(regName);
|
|
1726
|
+
case 0x08: // RRC r
|
|
1727
|
+
return this.rrcReg(regName);
|
|
1728
|
+
case 0x10: // RL r
|
|
1729
|
+
return this.rlReg(regName);
|
|
1730
|
+
case 0x18: // RR r
|
|
1731
|
+
return this.rrReg(regName);
|
|
1732
|
+
case 0x20: // SLA r
|
|
1733
|
+
return this.slaReg(regName);
|
|
1734
|
+
case 0x28: // SRA r
|
|
1735
|
+
return this.sraReg(regName);
|
|
1736
|
+
case 0x30: // SLL r - undocumented
|
|
1737
|
+
return this.sllReg(regName);
|
|
1738
|
+
case 0x38: // SRL r
|
|
1739
|
+
return this.srlReg(regName);
|
|
1740
|
+
case 0x40:
|
|
1741
|
+
case 0x48:
|
|
1742
|
+
case 0x50:
|
|
1743
|
+
case 0x58:
|
|
1744
|
+
case 0x60:
|
|
1745
|
+
case 0x68:
|
|
1746
|
+
case 0x70:
|
|
1747
|
+
case 0x78: // BIT n, r
|
|
1748
|
+
return this.bitReg(regName, (base >> 3) & 0x07);
|
|
1749
|
+
case 0x80:
|
|
1750
|
+
case 0x88:
|
|
1751
|
+
case 0x90:
|
|
1752
|
+
case 0x98:
|
|
1753
|
+
case 0xa0:
|
|
1754
|
+
case 0xa8:
|
|
1755
|
+
case 0xb0:
|
|
1756
|
+
case 0xb8: // RES n, r
|
|
1757
|
+
return this.resReg(regName, (base >> 3) & 0x07);
|
|
1758
|
+
case 0xc0:
|
|
1759
|
+
case 0xc8:
|
|
1760
|
+
case 0xd0:
|
|
1761
|
+
case 0xd8:
|
|
1762
|
+
case 0xe0:
|
|
1763
|
+
case 0xe8:
|
|
1764
|
+
case 0xf0:
|
|
1765
|
+
case 0xf8: // SET n, r
|
|
1766
|
+
return this.setReg(regName, (base >> 3) & 0x07);
|
|
1767
|
+
default:
|
|
1768
|
+
if (!this.unimplementedOpcodes.has(0xcb00 | opcode)) {
|
|
1769
|
+
this.unimplementedOpcodes.add(0xcb00 | opcode);
|
|
1770
|
+
this.log("warn",
|
|
1771
|
+
`Unimplemented CB opcode: 0xCB 0x${opcode
|
|
1772
|
+
.toString(16)
|
|
1773
|
+
.toUpperCase()
|
|
1774
|
+
.padStart(2, "0")}`
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
return 8;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
executeED() {
|
|
1783
|
+
const opcode = this.readMemory(this.registers.PC);
|
|
1784
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1785
|
+
|
|
1786
|
+
// ED instructions (extended instructions, block operations, I/O)
|
|
1787
|
+
switch (opcode) {
|
|
1788
|
+
// Block Transfer Instructions
|
|
1789
|
+
case 0xa0: // LDI
|
|
1790
|
+
return this.ldi();
|
|
1791
|
+
case 0xb0: // LDIR
|
|
1792
|
+
return this.ldir();
|
|
1793
|
+
case 0xa8: // LDD
|
|
1794
|
+
return this.ldd();
|
|
1795
|
+
case 0xb8: // LDDR
|
|
1796
|
+
return this.lddr();
|
|
1797
|
+
|
|
1798
|
+
// Nibble Rotation Instructions
|
|
1799
|
+
case 0x6f: // RLD
|
|
1800
|
+
return this.rld();
|
|
1801
|
+
case 0x67: // RRD
|
|
1802
|
+
return this.rrd();
|
|
1803
|
+
|
|
1804
|
+
// Block Compare Instructions
|
|
1805
|
+
case 0xa1: // CPI
|
|
1806
|
+
return this.cpi();
|
|
1807
|
+
case 0xb1: // CPIR
|
|
1808
|
+
return this.cpir();
|
|
1809
|
+
case 0xa9: // CPD
|
|
1810
|
+
return this.cpd();
|
|
1811
|
+
case 0xb9: // CPDR
|
|
1812
|
+
return this.cpdr();
|
|
1813
|
+
|
|
1814
|
+
// Block Input Instructions
|
|
1815
|
+
case 0xa2: // INI
|
|
1816
|
+
return this.ini();
|
|
1817
|
+
case 0xb2: // INIR
|
|
1818
|
+
return this.inir();
|
|
1819
|
+
case 0xaa: // IND
|
|
1820
|
+
return this.ind();
|
|
1821
|
+
case 0xba: // INDR
|
|
1822
|
+
return this.indr();
|
|
1823
|
+
|
|
1824
|
+
// Block Output Instructions
|
|
1825
|
+
case 0xa3: // OUTI
|
|
1826
|
+
return this.outi();
|
|
1827
|
+
case 0xb3: // OTIR
|
|
1828
|
+
return this.otir();
|
|
1829
|
+
case 0xab: // OUTD
|
|
1830
|
+
return this.outd();
|
|
1831
|
+
case 0xbb: // OTDR
|
|
1832
|
+
return this.otdr();
|
|
1833
|
+
|
|
1834
|
+
// Extended Load Instructions
|
|
1835
|
+
case 0x47: // LD I, A
|
|
1836
|
+
this.registers.I = this.registers.A;
|
|
1837
|
+
return 9;
|
|
1838
|
+
case 0x4f: // LD R, A
|
|
1839
|
+
this.registers.R = this.registers.A;
|
|
1840
|
+
return 9;
|
|
1841
|
+
case 0x57: // LD A, I
|
|
1842
|
+
this.registers.A = this.registers.I;
|
|
1843
|
+
this.flagZ = this.registers.A === 0;
|
|
1844
|
+
this.flagS = (this.registers.A & 0x80) !== 0;
|
|
1845
|
+
this.flagH = false;
|
|
1846
|
+
this.flagPV = this.IFF2;
|
|
1847
|
+
this.flagN = false;
|
|
1848
|
+
this._xy(this.registers.A);
|
|
1849
|
+
return 9;
|
|
1850
|
+
case 0x5f: // LD A, R
|
|
1851
|
+
this.registers.A = this.registers.R;
|
|
1852
|
+
this.flagZ = this.registers.A === 0;
|
|
1853
|
+
this.flagS = (this.registers.A & 0x80) !== 0;
|
|
1854
|
+
this.flagH = false;
|
|
1855
|
+
this.flagPV = this.IFF2;
|
|
1856
|
+
this.flagN = false;
|
|
1857
|
+
this._xy(this.registers.A);
|
|
1858
|
+
return 9;
|
|
1859
|
+
|
|
1860
|
+
// 16-bit Load Instructions
|
|
1861
|
+
case 0x4b: // LD BC, (nn)
|
|
1862
|
+
return this.ldBCnn();
|
|
1863
|
+
case 0x5b: // LD DE, (nn)
|
|
1864
|
+
return this.ldDEnn();
|
|
1865
|
+
case 0x6b: // LD HL, (nn) - alternative encoding
|
|
1866
|
+
return this.ldHLnn();
|
|
1867
|
+
case 0x7b: // LD SP, (nn)
|
|
1868
|
+
return this.ldSPnn();
|
|
1869
|
+
case 0x43: // LD (nn), BC
|
|
1870
|
+
return this.ldnnBC();
|
|
1871
|
+
case 0x53: // LD (nn), DE
|
|
1872
|
+
return this.ldnnDE();
|
|
1873
|
+
case 0x63: // LD (nn), HL - alternative encoding
|
|
1874
|
+
return this.ldnnHL();
|
|
1875
|
+
case 0x73: // LD (nn), SP
|
|
1876
|
+
return this.ldnnSP();
|
|
1877
|
+
|
|
1878
|
+
// Extended Arithmetic
|
|
1879
|
+
case 0x4a: // ADC HL, BC
|
|
1880
|
+
return this.adcHL(this.BC);
|
|
1881
|
+
case 0x5a: // ADC HL, DE
|
|
1882
|
+
return this.adcHL(this.DE);
|
|
1883
|
+
case 0x6a: // ADC HL, HL
|
|
1884
|
+
return this.adcHL(this.HL);
|
|
1885
|
+
case 0x7a: // ADC HL, SP
|
|
1886
|
+
return this.adcHL(this.registers.SP);
|
|
1887
|
+
case 0x42: // SBC HL, BC
|
|
1888
|
+
return this.sbcHL(this.BC);
|
|
1889
|
+
case 0x52: // SBC HL, DE
|
|
1890
|
+
return this.sbcHL(this.DE);
|
|
1891
|
+
case 0x62: // SBC HL, HL
|
|
1892
|
+
return this.sbcHL(this.HL);
|
|
1893
|
+
case 0x72: // SBC HL, SP
|
|
1894
|
+
return this.sbcHL(this.registers.SP);
|
|
1895
|
+
|
|
1896
|
+
// NEG and its duplicates
|
|
1897
|
+
case 0x44:
|
|
1898
|
+
case 0x4c:
|
|
1899
|
+
case 0x54:
|
|
1900
|
+
case 0x5c:
|
|
1901
|
+
case 0x64:
|
|
1902
|
+
case 0x6c:
|
|
1903
|
+
case 0x74:
|
|
1904
|
+
case 0x7c:
|
|
1905
|
+
return this.neg();
|
|
1906
|
+
|
|
1907
|
+
// I/O Instructions
|
|
1908
|
+
case 0x40:
|
|
1909
|
+
case 0x48:
|
|
1910
|
+
case 0x50:
|
|
1911
|
+
case 0x58:
|
|
1912
|
+
case 0x60:
|
|
1913
|
+
case 0x68:
|
|
1914
|
+
case 0x70:
|
|
1915
|
+
case 0x78: // IN r, (C)
|
|
1916
|
+
return this.inrC((opcode >> 3) & 0x07);
|
|
1917
|
+
case 0x41:
|
|
1918
|
+
case 0x49:
|
|
1919
|
+
case 0x51:
|
|
1920
|
+
case 0x59:
|
|
1921
|
+
case 0x61:
|
|
1922
|
+
case 0x69:
|
|
1923
|
+
case 0x71:
|
|
1924
|
+
case 0x79: // OUT (C), r
|
|
1925
|
+
return this.outCr((opcode >> 3) & 0x07);
|
|
1926
|
+
|
|
1927
|
+
// Return Instructions
|
|
1928
|
+
case 0x4d: // RETI
|
|
1929
|
+
return this.reti();
|
|
1930
|
+
case 0x55:
|
|
1931
|
+
case 0x5d:
|
|
1932
|
+
case 0x65:
|
|
1933
|
+
case 0x6d:
|
|
1934
|
+
case 0x75:
|
|
1935
|
+
case 0x7d:
|
|
1936
|
+
case 0x45: // RETN
|
|
1937
|
+
return this.retn();
|
|
1938
|
+
|
|
1939
|
+
// Interrupt Mode
|
|
1940
|
+
case 0x4e:
|
|
1941
|
+
case 0x66:
|
|
1942
|
+
case 0x6e:
|
|
1943
|
+
case 0x46: // IM 0
|
|
1944
|
+
this.interruptMode = 0;
|
|
1945
|
+
return 8;
|
|
1946
|
+
case 0x76:
|
|
1947
|
+
case 0x56: // IM 1
|
|
1948
|
+
this.interruptMode = 1;
|
|
1949
|
+
return 8;
|
|
1950
|
+
case 0x7e:
|
|
1951
|
+
case 0x5e: // IM 2
|
|
1952
|
+
this.interruptMode = 2;
|
|
1953
|
+
return 8;
|
|
1954
|
+
|
|
1955
|
+
// Every other ED opcode does nothing for 8 T-states (Young §3.3)
|
|
1956
|
+
default:
|
|
1957
|
+
return 8;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
executeDD() {
|
|
1962
|
+
const opcode = this.readMemory(this.registers.PC);
|
|
1963
|
+
// Before another prefix or ED, DD is a 4-T-state no-op: leave PC on that byte, which starts
|
|
1964
|
+
// the next step, and take no maskable interrupt before it (Young §3.6).
|
|
1965
|
+
if (opcode === 0xdd || opcode === 0xfd || opcode === 0xed) {
|
|
1966
|
+
this.afterPrefix = true;
|
|
1967
|
+
return 4;
|
|
1968
|
+
}
|
|
1969
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1970
|
+
this.incrementR(); // the second opcode fetch
|
|
1971
|
+
|
|
1972
|
+
// DD instructions (IX register operations)
|
|
1973
|
+
// Most DD instructions mirror HL instructions but use IX
|
|
1974
|
+
// Special cases: DD CB d opcode - indexed CB operations
|
|
1975
|
+
if (opcode === 0xcb) {
|
|
1976
|
+
// DD CB d opcode - indexed CB operation
|
|
1977
|
+
const disp = this.readMemory(this.registers.PC);
|
|
1978
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1979
|
+
const cbOpcode = this.readMemory(this.registers.PC);
|
|
1980
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1981
|
+
const addr = (this.IX + (disp & 0x80 ? disp - 256 : disp)) & 0xffff;
|
|
1982
|
+
return this.executeIndexedCB(addr, cbOpcode);
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// Standard DD instructions - delegate to helper
|
|
1986
|
+
return this.executeIndexReg(opcode, "IX");
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
executeFD() {
|
|
1990
|
+
const opcode = this.readMemory(this.registers.PC);
|
|
1991
|
+
// Before another prefix or ED, FD is a 4-T-state no-op: leave PC on that byte, which starts
|
|
1992
|
+
// the next step, and take no maskable interrupt before it (Young §3.6).
|
|
1993
|
+
if (opcode === 0xdd || opcode === 0xfd || opcode === 0xed) {
|
|
1994
|
+
this.afterPrefix = true;
|
|
1995
|
+
return 4;
|
|
1996
|
+
}
|
|
1997
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
1998
|
+
this.incrementR(); // the second opcode fetch
|
|
1999
|
+
|
|
2000
|
+
// FD instructions (IY register operations)
|
|
2001
|
+
// Most FD instructions mirror HL instructions but use IY
|
|
2002
|
+
// Special cases: FD CB d opcode - indexed CB operations
|
|
2003
|
+
if (opcode === 0xcb) {
|
|
2004
|
+
// FD CB d opcode - indexed CB operation
|
|
2005
|
+
const disp = this.readMemory(this.registers.PC);
|
|
2006
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
2007
|
+
const cbOpcode = this.readMemory(this.registers.PC);
|
|
2008
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
2009
|
+
const addr = (this.IY + (disp & 0x80 ? disp - 256 : disp)) & 0xffff;
|
|
2010
|
+
return this.executeIndexedCB(addr, cbOpcode);
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// Standard FD instructions - delegate to helper
|
|
2014
|
+
return this.executeIndexReg(opcode, "IY");
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// Arithmetic methods
|
|
2018
|
+
/**
|
|
2019
|
+
* Add a value to the accumulator (A register) with flag updates
|
|
2020
|
+
*/
|
|
2021
|
+
addA(value, withCarry = false) {
|
|
2022
|
+
const a = this.registers.A;
|
|
2023
|
+
const carry = withCarry && this.flagC ? 1 : 0;
|
|
2024
|
+
const result = a + value + carry;
|
|
2025
|
+
|
|
2026
|
+
this.flagC = result > 0xff;
|
|
2027
|
+
// Half-carry: carry out of bit 3 in the nibble sum
|
|
2028
|
+
this.flagH = (a & 0x0f) + (value & 0x0f) + carry > 0x0f;
|
|
2029
|
+
this.flagN = 0;
|
|
2030
|
+
this.registers.A = result & 0xff;
|
|
2031
|
+
this.flagZ = this.registers.A === 0;
|
|
2032
|
+
this.flagS = (this.registers.A & 0x80) !== 0;
|
|
2033
|
+
// Parity/Overflow: signed overflow
|
|
2034
|
+
const signedA = a & 0x80 ? a - 256 : a;
|
|
2035
|
+
const signedValue = value & 0x80 ? value - 256 : value;
|
|
2036
|
+
const signedResult = signedA + signedValue + carry;
|
|
2037
|
+
this.flagPV = signedResult < -128 || signedResult > 127;
|
|
2038
|
+
this._xy(this.registers.A);
|
|
2039
|
+
|
|
2040
|
+
return 4;
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
/**
|
|
2044
|
+
* Subtract a value from the accumulator with flag updates
|
|
2045
|
+
*/
|
|
2046
|
+
subA(value, compare = false) {
|
|
2047
|
+
const a = this.registers.A;
|
|
2048
|
+
const result = a - value;
|
|
2049
|
+
|
|
2050
|
+
this.flagC = result < 0;
|
|
2051
|
+
this.flagH = (a & 0x0f) - (value & 0x0f) < 0;
|
|
2052
|
+
this.flagN = 1;
|
|
2053
|
+
|
|
2054
|
+
if (!compare) {
|
|
2055
|
+
this.registers.A = result & 0xff;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
const finalResult = result & 0xff;
|
|
2059
|
+
this.flagZ = finalResult === 0;
|
|
2060
|
+
this.flagS = (finalResult & 0x80) !== 0;
|
|
2061
|
+
this.flagPV = ((a ^ value) & (a ^ finalResult) & 0x80) !== 0;
|
|
2062
|
+
// CP is the X/Y exception: bits 3/5 come from the operand
|
|
2063
|
+
this._xy(compare ? value : finalResult);
|
|
2064
|
+
|
|
2065
|
+
return 4;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
/**
|
|
2069
|
+
* Increment a register with flag updates (except carry)
|
|
2070
|
+
*/
|
|
2071
|
+
incReg(reg) {
|
|
2072
|
+
const value = this.registers[reg];
|
|
2073
|
+
const result = (value + 1) & 0xff;
|
|
2074
|
+
|
|
2075
|
+
this.registers[reg] = result;
|
|
2076
|
+
this.flagZ = result === 0;
|
|
2077
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2078
|
+
this.flagH = (value & 0x0f) === 0x0f;
|
|
2079
|
+
this.flagPV = value === 0x7f;
|
|
2080
|
+
this.flagN = 0;
|
|
2081
|
+
this._xy(result);
|
|
2082
|
+
|
|
2083
|
+
return 4;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
/**
|
|
2087
|
+
* Decrement a register with flag updates (except carry)
|
|
2088
|
+
*/
|
|
2089
|
+
decReg(reg) {
|
|
2090
|
+
const value = this.registers[reg];
|
|
2091
|
+
const result = (value - 1) & 0xff;
|
|
2092
|
+
|
|
2093
|
+
this.registers[reg] = result;
|
|
2094
|
+
this.flagZ = result === 0;
|
|
2095
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2096
|
+
this.flagH = (value & 0x0f) === 0x00;
|
|
2097
|
+
this.flagPV = value === 0x80;
|
|
2098
|
+
this.flagN = 1;
|
|
2099
|
+
this._xy(result);
|
|
2100
|
+
|
|
2101
|
+
return 4;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
/**
|
|
2105
|
+
* Add with carry (ADC)
|
|
2106
|
+
*/
|
|
2107
|
+
adcA(value) {
|
|
2108
|
+
return this.addA(value, true);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
/**
|
|
2112
|
+
* Subtract with carry (SBC)
|
|
2113
|
+
*/
|
|
2114
|
+
sbcA(value) {
|
|
2115
|
+
const a = this.registers.A;
|
|
2116
|
+
const carry = this.flagC ? 1 : 0;
|
|
2117
|
+
const result = a - value - carry;
|
|
2118
|
+
|
|
2119
|
+
this.flagC = result < 0;
|
|
2120
|
+
this.flagH = (a & 0x0f) - (value & 0x0f) - carry < 0;
|
|
2121
|
+
this.flagN = 1;
|
|
2122
|
+
|
|
2123
|
+
this.registers.A = result & 0xff;
|
|
2124
|
+
const finalResult = result & 0xff;
|
|
2125
|
+
this.flagZ = finalResult === 0;
|
|
2126
|
+
this.flagS = (finalResult & 0x80) !== 0;
|
|
2127
|
+
this.flagPV = ((a ^ value) & (a ^ finalResult) & 0x80) !== 0;
|
|
2128
|
+
this._xy(finalResult);
|
|
2129
|
+
|
|
2130
|
+
return 4;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/**
|
|
2134
|
+
* AND operation
|
|
2135
|
+
*/
|
|
2136
|
+
andA(value) {
|
|
2137
|
+
this.registers.A = this.registers.A & value;
|
|
2138
|
+
this.flagZ = this.registers.A === 0;
|
|
2139
|
+
this.flagS = (this.registers.A & 0x80) !== 0;
|
|
2140
|
+
this.flagH = true;
|
|
2141
|
+
this.flagPV = this.parity(this.registers.A);
|
|
2142
|
+
this.flagN = 0;
|
|
2143
|
+
this.flagC = false;
|
|
2144
|
+
this._xy(this.registers.A);
|
|
2145
|
+
return 4;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
/**
|
|
2149
|
+
* OR operation
|
|
2150
|
+
*/
|
|
2151
|
+
orA(value) {
|
|
2152
|
+
this.registers.A = this.registers.A | value;
|
|
2153
|
+
this.flagZ = this.registers.A === 0;
|
|
2154
|
+
this.flagS = (this.registers.A & 0x80) !== 0;
|
|
2155
|
+
this.flagH = false;
|
|
2156
|
+
this.flagPV = this.parity(this.registers.A);
|
|
2157
|
+
this.flagN = 0;
|
|
2158
|
+
this.flagC = false;
|
|
2159
|
+
this._xy(this.registers.A);
|
|
2160
|
+
return 4;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
/**
|
|
2164
|
+
* XOR operation
|
|
2165
|
+
*/
|
|
2166
|
+
xorA(value) {
|
|
2167
|
+
this.registers.A = this.registers.A ^ value;
|
|
2168
|
+
this.flagZ = this.registers.A === 0;
|
|
2169
|
+
this.flagS = (this.registers.A & 0x80) !== 0;
|
|
2170
|
+
this.flagH = false;
|
|
2171
|
+
this.flagPV = this.parity(this.registers.A);
|
|
2172
|
+
this.flagN = 0;
|
|
2173
|
+
this.flagC = false;
|
|
2174
|
+
this._xy(this.registers.A);
|
|
2175
|
+
return 4;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
/**
|
|
2179
|
+
* Calculate parity (even parity)
|
|
2180
|
+
*/
|
|
2181
|
+
parity(value) {
|
|
2182
|
+
let p = 0;
|
|
2183
|
+
for (let i = 0; i < 8; i++) {
|
|
2184
|
+
if (value & (1 << i)) p++;
|
|
2185
|
+
}
|
|
2186
|
+
return (p & 1) === 0 ? 1 : 0;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
/**
|
|
2190
|
+
* Add 16-bit value to HL
|
|
2191
|
+
*/
|
|
2192
|
+
addHL(value) {
|
|
2193
|
+
const hl = this.HL;
|
|
2194
|
+
const result = hl + value;
|
|
2195
|
+
this.flagC = result > 0xffff;
|
|
2196
|
+
this.flagH = (hl & 0x0fff) + (value & 0x0fff) > 0x0fff;
|
|
2197
|
+
this.flagN = false;
|
|
2198
|
+
this.HL = result & 0xffff;
|
|
2199
|
+
this.wz = (hl + 1) & 0xffff;
|
|
2200
|
+
this._xy(this.HL >> 8); // 16-bit ops take X/Y from the high byte
|
|
2201
|
+
return 4;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
// CB prefix helper methods
|
|
2205
|
+
rlcReg(reg) {
|
|
2206
|
+
const value = this.registers[reg];
|
|
2207
|
+
this.flagC = (value & 0x80) !== 0;
|
|
2208
|
+
this.registers[reg] = ((value << 1) | (value >> 7)) & 0xff;
|
|
2209
|
+
this.flagZ = this.registers[reg] === 0;
|
|
2210
|
+
this.flagS = (this.registers[reg] & 0x80) !== 0;
|
|
2211
|
+
this.flagPV = this.parity(this.registers[reg]);
|
|
2212
|
+
this.flagH = false;
|
|
2213
|
+
this.flagN = false;
|
|
2214
|
+
this._xy(this.registers[reg]);
|
|
2215
|
+
return 8;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
rlcMem(addr) {
|
|
2219
|
+
const value = this.readMemory(addr);
|
|
2220
|
+
this.flagC = (value & 0x80) !== 0;
|
|
2221
|
+
const result = ((value << 1) | (value >> 7)) & 0xff;
|
|
2222
|
+
this.writeMemory(addr, result);
|
|
2223
|
+
this.flagZ = result === 0;
|
|
2224
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2225
|
+
this.flagPV = this.parity(result);
|
|
2226
|
+
this.flagH = false;
|
|
2227
|
+
this.flagN = false;
|
|
2228
|
+
this._xy(result);
|
|
2229
|
+
return 15;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
rrcReg(reg) {
|
|
2233
|
+
const value = this.registers[reg];
|
|
2234
|
+
this.flagC = (value & 0x01) !== 0;
|
|
2235
|
+
this.registers[reg] = ((value >> 1) | (value << 7)) & 0xff;
|
|
2236
|
+
this.flagZ = this.registers[reg] === 0;
|
|
2237
|
+
this.flagS = (this.registers[reg] & 0x80) !== 0;
|
|
2238
|
+
this.flagPV = this.parity(this.registers[reg]);
|
|
2239
|
+
this.flagH = false;
|
|
2240
|
+
this.flagN = false;
|
|
2241
|
+
this._xy(this.registers[reg]);
|
|
2242
|
+
return 8;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
rrcMem(addr) {
|
|
2246
|
+
const value = this.readMemory(addr);
|
|
2247
|
+
this.flagC = (value & 0x01) !== 0;
|
|
2248
|
+
const result = ((value >> 1) | (value << 7)) & 0xff;
|
|
2249
|
+
this.writeMemory(addr, result);
|
|
2250
|
+
this.flagZ = result === 0;
|
|
2251
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2252
|
+
this.flagPV = this.parity(result);
|
|
2253
|
+
this.flagH = false;
|
|
2254
|
+
this.flagN = false;
|
|
2255
|
+
this._xy(result);
|
|
2256
|
+
return 15;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
rlReg(reg) {
|
|
2260
|
+
const value = this.registers[reg];
|
|
2261
|
+
const carry = this.flagC ? 1 : 0;
|
|
2262
|
+
this.flagC = (value & 0x80) !== 0;
|
|
2263
|
+
this.registers[reg] = ((value << 1) | carry) & 0xff;
|
|
2264
|
+
this.flagZ = this.registers[reg] === 0;
|
|
2265
|
+
this.flagS = (this.registers[reg] & 0x80) !== 0;
|
|
2266
|
+
this.flagPV = this.parity(this.registers[reg]);
|
|
2267
|
+
this.flagH = false;
|
|
2268
|
+
this.flagN = false;
|
|
2269
|
+
this._xy(this.registers[reg]);
|
|
2270
|
+
return 8;
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
rlMem(addr) {
|
|
2274
|
+
const value = this.readMemory(addr);
|
|
2275
|
+
const carry = this.flagC ? 1 : 0;
|
|
2276
|
+
this.flagC = (value & 0x80) !== 0;
|
|
2277
|
+
const result = ((value << 1) | carry) & 0xff;
|
|
2278
|
+
this.writeMemory(addr, result);
|
|
2279
|
+
this.flagZ = result === 0;
|
|
2280
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2281
|
+
this.flagPV = this.parity(result);
|
|
2282
|
+
this.flagH = false;
|
|
2283
|
+
this.flagN = false;
|
|
2284
|
+
this._xy(result);
|
|
2285
|
+
return 15;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
rrReg(reg) {
|
|
2289
|
+
const value = this.registers[reg];
|
|
2290
|
+
const carry = this.flagC ? 0x80 : 0;
|
|
2291
|
+
this.flagC = (value & 0x01) !== 0;
|
|
2292
|
+
this.registers[reg] = ((value >> 1) | carry) & 0xff;
|
|
2293
|
+
this.flagZ = this.registers[reg] === 0;
|
|
2294
|
+
this.flagS = (this.registers[reg] & 0x80) !== 0;
|
|
2295
|
+
this.flagPV = this.parity(this.registers[reg]);
|
|
2296
|
+
this.flagH = false;
|
|
2297
|
+
this.flagN = false;
|
|
2298
|
+
this._xy(this.registers[reg]);
|
|
2299
|
+
return 8;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
rrMem(addr) {
|
|
2303
|
+
const value = this.readMemory(addr);
|
|
2304
|
+
const carry = this.flagC ? 0x80 : 0;
|
|
2305
|
+
this.flagC = (value & 0x01) !== 0;
|
|
2306
|
+
const result = ((value >> 1) | carry) & 0xff;
|
|
2307
|
+
this.writeMemory(addr, result);
|
|
2308
|
+
this.flagZ = result === 0;
|
|
2309
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2310
|
+
this.flagPV = this.parity(result);
|
|
2311
|
+
this.flagH = false;
|
|
2312
|
+
this.flagN = false;
|
|
2313
|
+
this._xy(result);
|
|
2314
|
+
return 15;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
slaReg(reg) {
|
|
2318
|
+
const value = this.registers[reg];
|
|
2319
|
+
this.flagC = (value & 0x80) !== 0;
|
|
2320
|
+
this.registers[reg] = (value << 1) & 0xff;
|
|
2321
|
+
this.flagZ = this.registers[reg] === 0;
|
|
2322
|
+
this.flagS = (this.registers[reg] & 0x80) !== 0;
|
|
2323
|
+
this.flagPV = this.parity(this.registers[reg]);
|
|
2324
|
+
this.flagH = false;
|
|
2325
|
+
this.flagN = false;
|
|
2326
|
+
this._xy(this.registers[reg]);
|
|
2327
|
+
return 8;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
slaMem(addr) {
|
|
2331
|
+
const value = this.readMemory(addr);
|
|
2332
|
+
this.flagC = (value & 0x80) !== 0;
|
|
2333
|
+
const result = (value << 1) & 0xff;
|
|
2334
|
+
this.writeMemory(addr, result);
|
|
2335
|
+
this.flagZ = result === 0;
|
|
2336
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2337
|
+
this.flagPV = this.parity(result);
|
|
2338
|
+
this.flagH = false;
|
|
2339
|
+
this.flagN = false;
|
|
2340
|
+
this._xy(result);
|
|
2341
|
+
return 15;
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
sraReg(reg) {
|
|
2345
|
+
const value = this.registers[reg];
|
|
2346
|
+
this.flagC = (value & 0x01) !== 0;
|
|
2347
|
+
this.registers[reg] = ((value >> 1) | (value & 0x80)) & 0xff;
|
|
2348
|
+
this.flagZ = this.registers[reg] === 0;
|
|
2349
|
+
this.flagS = (this.registers[reg] & 0x80) !== 0;
|
|
2350
|
+
this.flagPV = this.parity(this.registers[reg]);
|
|
2351
|
+
this.flagH = false;
|
|
2352
|
+
this.flagN = false;
|
|
2353
|
+
this._xy(this.registers[reg]);
|
|
2354
|
+
return 8;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
sraMem(addr) {
|
|
2358
|
+
const value = this.readMemory(addr);
|
|
2359
|
+
this.flagC = (value & 0x01) !== 0;
|
|
2360
|
+
const result = ((value >> 1) | (value & 0x80)) & 0xff;
|
|
2361
|
+
this.writeMemory(addr, result);
|
|
2362
|
+
this.flagZ = result === 0;
|
|
2363
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2364
|
+
this.flagPV = this.parity(result);
|
|
2365
|
+
this.flagH = false;
|
|
2366
|
+
this.flagN = false;
|
|
2367
|
+
this._xy(result);
|
|
2368
|
+
return 15;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
sllReg(reg) {
|
|
2372
|
+
const value = this.registers[reg];
|
|
2373
|
+
this.flagC = (value & 0x80) !== 0;
|
|
2374
|
+
this.registers[reg] = ((value << 1) | 1) & 0xff;
|
|
2375
|
+
this.flagZ = this.registers[reg] === 0;
|
|
2376
|
+
this.flagS = (this.registers[reg] & 0x80) !== 0;
|
|
2377
|
+
this.flagPV = this.parity(this.registers[reg]);
|
|
2378
|
+
this.flagH = false;
|
|
2379
|
+
this.flagN = false;
|
|
2380
|
+
this._xy(this.registers[reg]);
|
|
2381
|
+
return 8;
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
sllMem(addr) {
|
|
2385
|
+
const value = this.readMemory(addr);
|
|
2386
|
+
this.flagC = (value & 0x80) !== 0;
|
|
2387
|
+
const result = ((value << 1) | 1) & 0xff;
|
|
2388
|
+
this.writeMemory(addr, result);
|
|
2389
|
+
this.flagZ = result === 0;
|
|
2390
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2391
|
+
this.flagPV = this.parity(result);
|
|
2392
|
+
this.flagH = false;
|
|
2393
|
+
this.flagN = false;
|
|
2394
|
+
this._xy(result);
|
|
2395
|
+
return 15;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
srlReg(reg) {
|
|
2399
|
+
const value = this.registers[reg];
|
|
2400
|
+
this.flagC = (value & 0x01) !== 0;
|
|
2401
|
+
this.registers[reg] = (value >> 1) & 0xff;
|
|
2402
|
+
this.flagZ = this.registers[reg] === 0;
|
|
2403
|
+
this.flagS = false;
|
|
2404
|
+
this.flagPV = this.parity(this.registers[reg]);
|
|
2405
|
+
this.flagH = false;
|
|
2406
|
+
this.flagN = false;
|
|
2407
|
+
this._xy(this.registers[reg]);
|
|
2408
|
+
return 8;
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
srlMem(addr) {
|
|
2412
|
+
const value = this.readMemory(addr);
|
|
2413
|
+
this.flagC = (value & 0x01) !== 0;
|
|
2414
|
+
const result = (value >> 1) & 0xff;
|
|
2415
|
+
this.writeMemory(addr, result);
|
|
2416
|
+
this.flagZ = result === 0;
|
|
2417
|
+
this.flagS = false;
|
|
2418
|
+
this.flagPV = this.parity(result);
|
|
2419
|
+
this.flagH = false;
|
|
2420
|
+
this.flagN = false;
|
|
2421
|
+
this._xy(result);
|
|
2422
|
+
return 15;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
bitReg(reg, bit) {
|
|
2426
|
+
const value = this.registers[reg];
|
|
2427
|
+
const bitValue = (value >> bit) & 1;
|
|
2428
|
+
this.flagZ = bitValue === 0;
|
|
2429
|
+
this.flagS = bit === 7 ? bitValue : false;
|
|
2430
|
+
this.flagH = true;
|
|
2431
|
+
this.flagPV = bitValue === 0;
|
|
2432
|
+
this.flagN = false;
|
|
2433
|
+
this._xy(value);
|
|
2434
|
+
return 8;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
bitMem(addr, bit) {
|
|
2438
|
+
const value = this.readMemory(addr);
|
|
2439
|
+
const bitValue = (value >> bit) & 1;
|
|
2440
|
+
this.flagZ = bitValue === 0;
|
|
2441
|
+
this.flagS = bit === 7 ? bitValue : false;
|
|
2442
|
+
this.flagH = true;
|
|
2443
|
+
this.flagPV = bitValue === 0;
|
|
2444
|
+
this.flagN = false;
|
|
2445
|
+
// BIT n,(HL) exposes MEMPTR's high byte; the DD/FD CB caller sets
|
|
2446
|
+
// wz = IX+d first, which makes the indexed form come out right too.
|
|
2447
|
+
this._xy(this.wz >> 8);
|
|
2448
|
+
return 12;
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
resReg(reg, bit) {
|
|
2452
|
+
this.registers[reg] = this.registers[reg] & ~(1 << bit);
|
|
2453
|
+
return 8;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
resMem(addr, bit) {
|
|
2457
|
+
const value = this.readMemory(addr);
|
|
2458
|
+
this.writeMemory(addr, value & ~(1 << bit));
|
|
2459
|
+
return 15;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
setReg(reg, bit) {
|
|
2463
|
+
this.registers[reg] = this.registers[reg] | (1 << bit);
|
|
2464
|
+
return 8;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
setMem(addr, bit) {
|
|
2468
|
+
const value = this.readMemory(addr);
|
|
2469
|
+
this.writeMemory(addr, value | (1 << bit));
|
|
2470
|
+
return 15;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// DD/FD prefix helper methods
|
|
2474
|
+
executeIndexReg(opcode, reg) {
|
|
2475
|
+
// Most DD/FD instructions are identical to HL instructions but use IX/IY
|
|
2476
|
+
// Special handling for indexed operations
|
|
2477
|
+
const isIX = reg === "IX";
|
|
2478
|
+
const indexReg = isIX ? this.IX : this.IY;
|
|
2479
|
+
|
|
2480
|
+
switch (opcode) {
|
|
2481
|
+
// LD IX/IY, nn (0x21)
|
|
2482
|
+
case 0x21: {
|
|
2483
|
+
const low = this.readMemory(this.registers.PC);
|
|
2484
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
2485
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
2486
|
+
if (isIX) {
|
|
2487
|
+
this.IX = (high << 8) | low;
|
|
2488
|
+
} else {
|
|
2489
|
+
this.IY = (high << 8) | low;
|
|
2490
|
+
}
|
|
2491
|
+
return 14;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
// ADD IX/IY, BC (0x09)
|
|
2495
|
+
case 0x09:
|
|
2496
|
+
return this.addIndexReg(indexReg, this.BC, isIX);
|
|
2497
|
+
// ADD IX/IY, DE (0x19)
|
|
2498
|
+
case 0x19:
|
|
2499
|
+
return this.addIndexReg(indexReg, this.DE, isIX);
|
|
2500
|
+
// ADD IX/IY, IX/IY (0x29)
|
|
2501
|
+
case 0x29:
|
|
2502
|
+
return this.addIndexReg(indexReg, indexReg, isIX);
|
|
2503
|
+
// ADD IX/IY, SP (0x39)
|
|
2504
|
+
case 0x39:
|
|
2505
|
+
return this.addIndexReg(indexReg, this.registers.SP, isIX);
|
|
2506
|
+
|
|
2507
|
+
// INC IX/IY (0x23)
|
|
2508
|
+
case 0x23:
|
|
2509
|
+
if (isIX) {
|
|
2510
|
+
this.IX = (this.IX + 1) & 0xffff;
|
|
2511
|
+
} else {
|
|
2512
|
+
this.IY = (this.IY + 1) & 0xffff;
|
|
2513
|
+
}
|
|
2514
|
+
return 10;
|
|
2515
|
+
|
|
2516
|
+
// DEC IX/IY (0x2B)
|
|
2517
|
+
case 0x2b:
|
|
2518
|
+
if (isIX) {
|
|
2519
|
+
this.IX = (this.IX - 1) & 0xffff;
|
|
2520
|
+
} else {
|
|
2521
|
+
this.IY = (this.IY - 1) & 0xffff;
|
|
2522
|
+
}
|
|
2523
|
+
return 10;
|
|
2524
|
+
|
|
2525
|
+
// PUSH IX/IY (0xE5)
|
|
2526
|
+
case 0xe5: {
|
|
2527
|
+
const high = isIX ? this.registers.IXH : this.registers.IYH;
|
|
2528
|
+
const low = isIX ? this.registers.IXL : this.registers.IYL;
|
|
2529
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
2530
|
+
this.writeMemory(this.registers.SP, high);
|
|
2531
|
+
this.registers.SP = (this.registers.SP - 1) & 0xffff;
|
|
2532
|
+
this.writeMemory(this.registers.SP, low);
|
|
2533
|
+
return 15;
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// POP IX/IY (0xE1)
|
|
2537
|
+
case 0xe1: {
|
|
2538
|
+
const low = this.readMemory(this.registers.SP);
|
|
2539
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
2540
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
2541
|
+
if (isIX) {
|
|
2542
|
+
this.registers.IXL = low;
|
|
2543
|
+
this.registers.IXH = high;
|
|
2544
|
+
} else {
|
|
2545
|
+
this.registers.IYL = low;
|
|
2546
|
+
this.registers.IYH = high;
|
|
2547
|
+
}
|
|
2548
|
+
return 14;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// LD (nn), IX/IY (0x22)
|
|
2552
|
+
case 0x22: {
|
|
2553
|
+
const low = this.readMemory(this.registers.PC);
|
|
2554
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
2555
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
2556
|
+
const addr = (high << 8) | low;
|
|
2557
|
+
this.wz = (addr + 1) & 0xffff;
|
|
2558
|
+
const regLow = isIX ? this.registers.IXL : this.registers.IYL;
|
|
2559
|
+
const regHigh = isIX ? this.registers.IXH : this.registers.IYH;
|
|
2560
|
+
this.writeMemory(addr, regLow);
|
|
2561
|
+
this.writeMemory((addr + 1) & 0xffff, regHigh);
|
|
2562
|
+
return 20;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// LD IX/IY, (nn) (0x2A)
|
|
2566
|
+
case 0x2a: {
|
|
2567
|
+
const low = this.readMemory(this.registers.PC);
|
|
2568
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
2569
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
2570
|
+
const addr = (high << 8) | low;
|
|
2571
|
+
this.wz = (addr + 1) & 0xffff;
|
|
2572
|
+
const regLow = this.readMemory(addr);
|
|
2573
|
+
const regHigh = this.readMemory((addr + 1) & 0xffff);
|
|
2574
|
+
if (isIX) {
|
|
2575
|
+
this.registers.IXL = regLow;
|
|
2576
|
+
this.registers.IXH = regHigh;
|
|
2577
|
+
} else {
|
|
2578
|
+
this.registers.IYL = regLow;
|
|
2579
|
+
this.registers.IYH = regHigh;
|
|
2580
|
+
}
|
|
2581
|
+
return 20;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// JP (IX/IY) (0xE9)
|
|
2585
|
+
case 0xe9:
|
|
2586
|
+
this.registers.PC = indexReg;
|
|
2587
|
+
return 8;
|
|
2588
|
+
|
|
2589
|
+
// LD SP, IX/IY (0xF9)
|
|
2590
|
+
case 0xf9:
|
|
2591
|
+
this.registers.SP = indexReg;
|
|
2592
|
+
return 10;
|
|
2593
|
+
|
|
2594
|
+
default: {
|
|
2595
|
+
// Opcodes whose (HL) operand becomes (IX+d)/(IY+d). Unlike the HL
|
|
2596
|
+
// forms, these consume a signed displacement byte, and register
|
|
2597
|
+
// names in the opcode keep meaning the REAL registers (H stays H).
|
|
2598
|
+
const indexedAddr = () => {
|
|
2599
|
+
const disp = this.readMemory(this.registers.PC);
|
|
2600
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
2601
|
+
return (this.wz = (indexReg + (disp & 0x80 ? disp - 256 : disp)) & 0xffff);
|
|
2602
|
+
};
|
|
2603
|
+
const regNames = ["B", "C", "D", "E", "H", "L", null, "A"];
|
|
2604
|
+
|
|
2605
|
+
// LD r,(IX+d): 01 rrr 110, excluding HALT's slot
|
|
2606
|
+
if ((opcode & 0xc7) === 0x46 && opcode !== 0x76) {
|
|
2607
|
+
const dest = regNames[(opcode >> 3) & 0x07];
|
|
2608
|
+
this.registers[dest] = this.readMemory(indexedAddr());
|
|
2609
|
+
return 19;
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
// LD (IX+d),r: 01 110 rrr, excluding HALT
|
|
2613
|
+
if ((opcode & 0xf8) === 0x70 && opcode !== 0x76) {
|
|
2614
|
+
const src = regNames[opcode & 0x07];
|
|
2615
|
+
this.writeMemory(indexedAddr(), this.registers[src]);
|
|
2616
|
+
return 19;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
switch (opcode) {
|
|
2620
|
+
case 0x34: {
|
|
2621
|
+
// INC (IX+d)
|
|
2622
|
+
const addr = indexedAddr();
|
|
2623
|
+
const value = this.readMemory(addr);
|
|
2624
|
+
const result = (value + 1) & 0xff;
|
|
2625
|
+
this.writeMemory(addr, result);
|
|
2626
|
+
this.flagZ = result === 0;
|
|
2627
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2628
|
+
this.flagH = (value & 0x0f) === 0x0f;
|
|
2629
|
+
this.flagPV = value === 0x7f;
|
|
2630
|
+
this.flagN = 0;
|
|
2631
|
+
this._xy(result);
|
|
2632
|
+
return 23;
|
|
2633
|
+
}
|
|
2634
|
+
case 0x35: {
|
|
2635
|
+
// DEC (IX+d)
|
|
2636
|
+
const addr = indexedAddr();
|
|
2637
|
+
const value = this.readMemory(addr);
|
|
2638
|
+
const result = (value - 1) & 0xff;
|
|
2639
|
+
this.writeMemory(addr, result);
|
|
2640
|
+
this.flagZ = result === 0;
|
|
2641
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2642
|
+
this.flagH = (value & 0x0f) === 0x00;
|
|
2643
|
+
this.flagPV = value === 0x80;
|
|
2644
|
+
this.flagN = 1;
|
|
2645
|
+
this._xy(result);
|
|
2646
|
+
return 23;
|
|
2647
|
+
}
|
|
2648
|
+
case 0x36: {
|
|
2649
|
+
// LD (IX+d),n — displacement byte precedes the immediate
|
|
2650
|
+
const addr = indexedAddr();
|
|
2651
|
+
const value = this.readMemory(this.registers.PC);
|
|
2652
|
+
this.registers.PC = (this.registers.PC + 1) & 0xffff;
|
|
2653
|
+
this.writeMemory(addr, value);
|
|
2654
|
+
return 19;
|
|
2655
|
+
}
|
|
2656
|
+
case 0x86:
|
|
2657
|
+
this.addA(this.readMemory(indexedAddr()));
|
|
2658
|
+
return 19;
|
|
2659
|
+
case 0x8e:
|
|
2660
|
+
this.adcA(this.readMemory(indexedAddr()));
|
|
2661
|
+
return 19;
|
|
2662
|
+
case 0x96:
|
|
2663
|
+
this.subA(this.readMemory(indexedAddr()));
|
|
2664
|
+
return 19;
|
|
2665
|
+
case 0x9e:
|
|
2666
|
+
this.sbcA(this.readMemory(indexedAddr()));
|
|
2667
|
+
return 19;
|
|
2668
|
+
case 0xa6:
|
|
2669
|
+
this.andA(this.readMemory(indexedAddr()));
|
|
2670
|
+
return 19;
|
|
2671
|
+
case 0xae:
|
|
2672
|
+
this.xorA(this.readMemory(indexedAddr()));
|
|
2673
|
+
return 19;
|
|
2674
|
+
case 0xb6:
|
|
2675
|
+
this.orA(this.readMemory(indexedAddr()));
|
|
2676
|
+
return 19;
|
|
2677
|
+
case 0xbe:
|
|
2678
|
+
this.subA(this.readMemory(indexedAddr()), true);
|
|
2679
|
+
return 19;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// Remaining DD/FD opcodes mirror their HL forms with IX/IY
|
|
2683
|
+
// substituted and take no displacement (INC IXH, ADD A,IXL, ...):
|
|
2684
|
+
// the HL-swap delegation is faithful for these.
|
|
2685
|
+
if (this.opcodeHandlers[opcode]) {
|
|
2686
|
+
// EX DE,HL and EXX ignore the prefix
|
|
2687
|
+
if (opcode === 0xeb || opcode === 0xd9) {
|
|
2688
|
+
return this.opcodeHandlers[opcode].call(this) + 4;
|
|
2689
|
+
}
|
|
2690
|
+
const tempHL = this.HL;
|
|
2691
|
+
this.HL = indexReg;
|
|
2692
|
+
const cycles = this.opcodeHandlers[opcode].call(this);
|
|
2693
|
+
if (isIX) {
|
|
2694
|
+
this.IX = this.HL;
|
|
2695
|
+
} else {
|
|
2696
|
+
this.IY = this.HL;
|
|
2697
|
+
}
|
|
2698
|
+
this.HL = tempHL;
|
|
2699
|
+
return cycles + 4;
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
return this.reportUnimplemented(opcode, isIX ? "0xDD " : "0xFD ");
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
addIndexReg(indexReg, value, isIX) {
|
|
2708
|
+
const result = indexReg + value;
|
|
2709
|
+
this.flagC = result > 0xffff;
|
|
2710
|
+
this.flagH = (indexReg & 0x0fff) + (value & 0x0fff) > 0x0fff;
|
|
2711
|
+
this.flagN = false;
|
|
2712
|
+
if (isIX) {
|
|
2713
|
+
this.IX = result & 0xffff;
|
|
2714
|
+
} else {
|
|
2715
|
+
this.IY = result & 0xffff;
|
|
2716
|
+
}
|
|
2717
|
+
this.wz = (indexReg + 1) & 0xffff;
|
|
2718
|
+
this._xy((result & 0xffff) >> 8);
|
|
2719
|
+
return 15;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
executeIndexedCB(addr, cbOpcode) {
|
|
2723
|
+
const reg = cbOpcode & 0x07;
|
|
2724
|
+
const base = cbOpcode & 0xf8;
|
|
2725
|
+
this.wz = addr & 0xffff; // MEMPTR = IX+d (feeds BIT's X/Y)
|
|
2726
|
+
|
|
2727
|
+
// BIT only reads: 20 T-states, never touches a register
|
|
2728
|
+
if (base >= 0x40 && base <= 0x78) {
|
|
2729
|
+
this.bitMem(addr, (base >> 3) & 0x07);
|
|
2730
|
+
return 20;
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
switch (base) {
|
|
2734
|
+
case 0x00:
|
|
2735
|
+
this.rlcMem(addr);
|
|
2736
|
+
break;
|
|
2737
|
+
case 0x08:
|
|
2738
|
+
this.rrcMem(addr);
|
|
2739
|
+
break;
|
|
2740
|
+
case 0x10:
|
|
2741
|
+
this.rlMem(addr);
|
|
2742
|
+
break;
|
|
2743
|
+
case 0x18:
|
|
2744
|
+
this.rrMem(addr);
|
|
2745
|
+
break;
|
|
2746
|
+
case 0x20:
|
|
2747
|
+
this.slaMem(addr);
|
|
2748
|
+
break;
|
|
2749
|
+
case 0x28:
|
|
2750
|
+
this.sraMem(addr);
|
|
2751
|
+
break;
|
|
2752
|
+
case 0x30:
|
|
2753
|
+
this.sllMem(addr);
|
|
2754
|
+
break;
|
|
2755
|
+
case 0x38:
|
|
2756
|
+
this.srlMem(addr);
|
|
2757
|
+
break;
|
|
2758
|
+
default:
|
|
2759
|
+
// 0x80-0xF8: RES/SET
|
|
2760
|
+
if (base & 0x40) {
|
|
2761
|
+
this.setMem(addr, (base >> 3) & 0x07);
|
|
2762
|
+
} else {
|
|
2763
|
+
this.resMem(addr, (base >> 3) & 0x07);
|
|
2764
|
+
}
|
|
2765
|
+
break;
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
// Undocumented: every writing DD/FD CB form also copies the result
|
|
2769
|
+
// into the register named by the low 3 bits (110 = memory only).
|
|
2770
|
+
if (reg !== 6) {
|
|
2771
|
+
this.registers[INDEXED_CB_COPY_REGS[reg]] = this.readMemory(addr);
|
|
2772
|
+
}
|
|
2773
|
+
return 23;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
// Block Transfer Instructions
|
|
2777
|
+
/**
|
|
2778
|
+
* LDI - Load and Increment
|
|
2779
|
+
* Copies byte from (HL) to (DE), increments HL and DE, decrements BC
|
|
2780
|
+
* Flags: H=0, N=0, PV=1 if BC-1 != 0, else 0
|
|
2781
|
+
*/
|
|
2782
|
+
ldi() {
|
|
2783
|
+
const value = this.readMemory(this.HL);
|
|
2784
|
+
this.writeMemory(this.DE, value);
|
|
2785
|
+
this.HL = (this.HL + 1) & 0xffff;
|
|
2786
|
+
this.DE = (this.DE + 1) & 0xffff;
|
|
2787
|
+
this.BC = (this.BC - 1) & 0xffff;
|
|
2788
|
+
|
|
2789
|
+
// Flag updates
|
|
2790
|
+
this.flagH = false;
|
|
2791
|
+
this.flagN = false;
|
|
2792
|
+
this.flagPV = this.BC !== 0;
|
|
2793
|
+
this._xyBlock((value + this.registers.A) & 0xff);
|
|
2794
|
+
|
|
2795
|
+
return 16;
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
/**
|
|
2799
|
+
* LDIR - Load, Increment, Repeat
|
|
2800
|
+
* Repeats LDI until BC = 0
|
|
2801
|
+
*/
|
|
2802
|
+
ldir() {
|
|
2803
|
+
const cycles = this.ldi();
|
|
2804
|
+
if (this.BC !== 0) {
|
|
2805
|
+
// Repeat: decrement PC by 2 to re-execute instruction
|
|
2806
|
+
this.registers.PC = (this.registers.PC - 2) & 0xffff;
|
|
2807
|
+
this.wz = (this.registers.PC + 1) & 0xffff;
|
|
2808
|
+
return cycles + 5; // Extra cycles for repeat
|
|
2809
|
+
}
|
|
2810
|
+
return cycles;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
/**
|
|
2814
|
+
* LDD - Load and Decrement
|
|
2815
|
+
* Copies byte from (HL) to (DE), decrements HL and DE, decrements BC
|
|
2816
|
+
*/
|
|
2817
|
+
ldd() {
|
|
2818
|
+
const value = this.readMemory(this.HL);
|
|
2819
|
+
this.writeMemory(this.DE, value);
|
|
2820
|
+
this.HL = (this.HL - 1) & 0xffff;
|
|
2821
|
+
this.DE = (this.DE - 1) & 0xffff;
|
|
2822
|
+
this.BC = (this.BC - 1) & 0xffff;
|
|
2823
|
+
|
|
2824
|
+
// Flag updates
|
|
2825
|
+
this.flagH = false;
|
|
2826
|
+
this.flagN = false;
|
|
2827
|
+
this.flagPV = this.BC !== 0;
|
|
2828
|
+
this._xyBlock((value + this.registers.A) & 0xff);
|
|
2829
|
+
|
|
2830
|
+
return 16;
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
/**
|
|
2834
|
+
* LDDR - Load, Decrement, Repeat
|
|
2835
|
+
* Repeats LDD until BC = 0
|
|
2836
|
+
*/
|
|
2837
|
+
lddr() {
|
|
2838
|
+
const cycles = this.ldd();
|
|
2839
|
+
if (this.BC !== 0) {
|
|
2840
|
+
this.registers.PC = (this.registers.PC - 2) & 0xffff;
|
|
2841
|
+
this.wz = (this.registers.PC + 1) & 0xffff;
|
|
2842
|
+
return cycles + 5;
|
|
2843
|
+
}
|
|
2844
|
+
return cycles;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
// Block Compare Instructions
|
|
2848
|
+
/**
|
|
2849
|
+
* CPI - Compare and Increment
|
|
2850
|
+
* Compares A with (HL), increments HL, decrements BC
|
|
2851
|
+
* Flags: Z=1 if A==(HL), H from subtraction, N=1, PV=1 if BC-1 != 0
|
|
2852
|
+
*/
|
|
2853
|
+
cpi() {
|
|
2854
|
+
const value = this.readMemory(this.HL);
|
|
2855
|
+
const a = this.registers.A;
|
|
2856
|
+
const result = (a - value) & 0xff;
|
|
2857
|
+
|
|
2858
|
+
this.HL = (this.HL + 1) & 0xffff;
|
|
2859
|
+
this.BC = (this.BC - 1) & 0xffff;
|
|
2860
|
+
this.wz = (this.wz + 1) & 0xffff;
|
|
2861
|
+
|
|
2862
|
+
// Flag updates
|
|
2863
|
+
this.flagZ = result === 0;
|
|
2864
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2865
|
+
this.flagH = (a & 0x0f) - (value & 0x0f) < 0;
|
|
2866
|
+
this.flagPV = this.BC !== 0;
|
|
2867
|
+
this.flagN = 1;
|
|
2868
|
+
this._xyBlock((result - (this.flagH ? 1 : 0)) & 0xff);
|
|
2869
|
+
|
|
2870
|
+
return 16;
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
/**
|
|
2874
|
+
* CPIR - Compare, Increment, Repeat
|
|
2875
|
+
* Repeats CPI until BC = 0 or match found (Z=1)
|
|
2876
|
+
*/
|
|
2877
|
+
cpir() {
|
|
2878
|
+
const cycles = this.cpi();
|
|
2879
|
+
if (this.BC !== 0 && !this.flagZ) {
|
|
2880
|
+
this.registers.PC = (this.registers.PC - 2) & 0xffff;
|
|
2881
|
+
this.wz = (this.registers.PC + 1) & 0xffff;
|
|
2882
|
+
return cycles + 5;
|
|
2883
|
+
}
|
|
2884
|
+
return cycles;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
/**
|
|
2888
|
+
* CPD - Compare and Decrement
|
|
2889
|
+
* Compares A with (HL), decrements HL, decrements BC
|
|
2890
|
+
*/
|
|
2891
|
+
cpd() {
|
|
2892
|
+
const value = this.readMemory(this.HL);
|
|
2893
|
+
const a = this.registers.A;
|
|
2894
|
+
const result = (a - value) & 0xff;
|
|
2895
|
+
|
|
2896
|
+
this.HL = (this.HL - 1) & 0xffff;
|
|
2897
|
+
this.BC = (this.BC - 1) & 0xffff;
|
|
2898
|
+
this.wz = (this.wz - 1) & 0xffff;
|
|
2899
|
+
|
|
2900
|
+
// Flag updates
|
|
2901
|
+
this.flagZ = result === 0;
|
|
2902
|
+
this.flagS = (result & 0x80) !== 0;
|
|
2903
|
+
this.flagH = (a & 0x0f) - (value & 0x0f) < 0;
|
|
2904
|
+
this.flagPV = this.BC !== 0;
|
|
2905
|
+
this.flagN = 1;
|
|
2906
|
+
this._xyBlock((result - (this.flagH ? 1 : 0)) & 0xff);
|
|
2907
|
+
|
|
2908
|
+
return 16;
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
/**
|
|
2912
|
+
* CPDR - Compare, Decrement, Repeat
|
|
2913
|
+
* Repeats CPD until BC = 0 or match found (Z=1)
|
|
2914
|
+
*/
|
|
2915
|
+
cpdr() {
|
|
2916
|
+
const cycles = this.cpd();
|
|
2917
|
+
if (this.BC !== 0 && !this.flagZ) {
|
|
2918
|
+
this.registers.PC = (this.registers.PC - 2) & 0xffff;
|
|
2919
|
+
this.wz = (this.registers.PC + 1) & 0xffff;
|
|
2920
|
+
return cycles + 5;
|
|
2921
|
+
}
|
|
2922
|
+
return cycles;
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
// Extended Load Instructions
|
|
2926
|
+
/**
|
|
2927
|
+
* LD BC, (nn) - Load BC from memory address
|
|
2928
|
+
*/
|
|
2929
|
+
ldBCnn() {
|
|
2930
|
+
const low = this.readMemory(this.registers.PC);
|
|
2931
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
2932
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
2933
|
+
const addr = (high << 8) | low;
|
|
2934
|
+
this.wz = (addr + 1) & 0xffff;
|
|
2935
|
+
const valueLow = this.readMemory(addr);
|
|
2936
|
+
const valueHigh = this.readMemory((addr + 1) & 0xffff);
|
|
2937
|
+
this.BC = (valueHigh << 8) | valueLow;
|
|
2938
|
+
return 20;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
/**
|
|
2942
|
+
* LD DE, (nn) - Load DE from memory address
|
|
2943
|
+
*/
|
|
2944
|
+
ldDEnn() {
|
|
2945
|
+
const low = this.readMemory(this.registers.PC);
|
|
2946
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
2947
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
2948
|
+
const addr = (high << 8) | low;
|
|
2949
|
+
this.wz = (addr + 1) & 0xffff;
|
|
2950
|
+
const valueLow = this.readMemory(addr);
|
|
2951
|
+
const valueHigh = this.readMemory((addr + 1) & 0xffff);
|
|
2952
|
+
this.DE = (valueHigh << 8) | valueLow;
|
|
2953
|
+
return 20;
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
/**
|
|
2957
|
+
* LD HL, (nn) - Load HL from memory address
|
|
2958
|
+
*/
|
|
2959
|
+
ldHLnn() {
|
|
2960
|
+
const low = this.readMemory(this.registers.PC);
|
|
2961
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
2962
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
2963
|
+
const addr = (high << 8) | low;
|
|
2964
|
+
this.wz = (addr + 1) & 0xffff;
|
|
2965
|
+
const valueLow = this.readMemory(addr);
|
|
2966
|
+
const valueHigh = this.readMemory((addr + 1) & 0xffff);
|
|
2967
|
+
this.HL = (valueHigh << 8) | valueLow;
|
|
2968
|
+
return 20;
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
/**
|
|
2972
|
+
* LD SP, (nn) - Load SP from memory address
|
|
2973
|
+
*/
|
|
2974
|
+
ldSPnn() {
|
|
2975
|
+
const low = this.readMemory(this.registers.PC);
|
|
2976
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
2977
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
2978
|
+
const addr = (high << 8) | low;
|
|
2979
|
+
this.wz = (addr + 1) & 0xffff;
|
|
2980
|
+
const valueLow = this.readMemory(addr);
|
|
2981
|
+
const valueHigh = this.readMemory((addr + 1) & 0xffff);
|
|
2982
|
+
this.registers.SP = (valueHigh << 8) | valueLow;
|
|
2983
|
+
return 20;
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
/**
|
|
2987
|
+
* LD (nn), BC - Store BC to memory address
|
|
2988
|
+
*/
|
|
2989
|
+
ldnnBC() {
|
|
2990
|
+
const low = this.readMemory(this.registers.PC);
|
|
2991
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
2992
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
2993
|
+
const addr = (high << 8) | low;
|
|
2994
|
+
this.wz = (addr + 1) & 0xffff;
|
|
2995
|
+
this.writeMemory(addr, this.registers.C);
|
|
2996
|
+
this.writeMemory((addr + 1) & 0xffff, this.registers.B);
|
|
2997
|
+
return 20;
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
/**
|
|
3001
|
+
* LD (nn), DE - Store DE to memory address
|
|
3002
|
+
*/
|
|
3003
|
+
ldnnDE() {
|
|
3004
|
+
const low = this.readMemory(this.registers.PC);
|
|
3005
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
3006
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
3007
|
+
const addr = (high << 8) | low;
|
|
3008
|
+
this.wz = (addr + 1) & 0xffff;
|
|
3009
|
+
this.writeMemory(addr, this.registers.E);
|
|
3010
|
+
this.writeMemory((addr + 1) & 0xffff, this.registers.D);
|
|
3011
|
+
return 20;
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
/**
|
|
3015
|
+
* LD (nn), HL - Store HL to memory address
|
|
3016
|
+
*/
|
|
3017
|
+
ldnnHL() {
|
|
3018
|
+
const low = this.readMemory(this.registers.PC);
|
|
3019
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
3020
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
3021
|
+
const addr = (high << 8) | low;
|
|
3022
|
+
this.wz = (addr + 1) & 0xffff;
|
|
3023
|
+
this.writeMemory(addr, this.registers.L);
|
|
3024
|
+
this.writeMemory((addr + 1) & 0xffff, this.registers.H);
|
|
3025
|
+
return 20;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
/**
|
|
3029
|
+
* LD (nn), SP - Store SP to memory address
|
|
3030
|
+
*/
|
|
3031
|
+
ldnnSP() {
|
|
3032
|
+
const low = this.readMemory(this.registers.PC);
|
|
3033
|
+
const high = this.readMemory((this.registers.PC + 1) & 0xffff);
|
|
3034
|
+
this.registers.PC = (this.registers.PC + 2) & 0xffff;
|
|
3035
|
+
const addr = (high << 8) | low;
|
|
3036
|
+
this.wz = (addr + 1) & 0xffff;
|
|
3037
|
+
this.writeMemory(addr, this.registers.SP & 0xff);
|
|
3038
|
+
this.writeMemory((addr + 1) & 0xffff, (this.registers.SP >> 8) & 0xff);
|
|
3039
|
+
return 20;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
// Extended Arithmetic Instructions
|
|
3043
|
+
/**
|
|
3044
|
+
* ADC HL, rr - Add with carry to HL
|
|
3045
|
+
*/
|
|
3046
|
+
adcHL(value) {
|
|
3047
|
+
const hl = this.HL;
|
|
3048
|
+
const carry = this.flagC ? 1 : 0;
|
|
3049
|
+
const result = hl + value + carry;
|
|
3050
|
+
|
|
3051
|
+
this.flagC = result > 0xffff;
|
|
3052
|
+
this.flagH = (hl & 0x0fff) + (value & 0x0fff) + carry > 0x0fff;
|
|
3053
|
+
this.flagN = false;
|
|
3054
|
+
this.flagPV = ((hl ^ value ^ 0xffff) & (hl ^ result) & 0x8000) !== 0;
|
|
3055
|
+
this.HL = result & 0xffff;
|
|
3056
|
+
this.flagS = (this.HL & 0x8000) !== 0;
|
|
3057
|
+
this.flagZ = this.HL === 0;
|
|
3058
|
+
this.wz = (hl + 1) & 0xffff;
|
|
3059
|
+
this._xy(this.HL >> 8);
|
|
3060
|
+
|
|
3061
|
+
return 15;
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
/**
|
|
3065
|
+
* SBC HL, rr - Subtract with borrow from HL
|
|
3066
|
+
*/
|
|
3067
|
+
sbcHL(value) {
|
|
3068
|
+
const hl = this.HL;
|
|
3069
|
+
const borrow = this.flagC ? 1 : 0;
|
|
3070
|
+
const result = hl - value - borrow;
|
|
3071
|
+
|
|
3072
|
+
this.flagC = result < 0;
|
|
3073
|
+
this.flagH = (hl & 0x0fff) - (value & 0x0fff) - borrow < 0;
|
|
3074
|
+
this.flagN = true;
|
|
3075
|
+
this.flagPV = ((hl ^ value) & (hl ^ result) & 0x8000) !== 0;
|
|
3076
|
+
this.HL = result & 0xffff;
|
|
3077
|
+
this.flagS = (this.HL & 0x8000) !== 0;
|
|
3078
|
+
this.flagZ = this.HL === 0;
|
|
3079
|
+
this.wz = (hl + 1) & 0xffff;
|
|
3080
|
+
this._xy(this.HL >> 8);
|
|
3081
|
+
|
|
3082
|
+
return 15;
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
/**
|
|
3086
|
+
* NEG - Negate accumulator (two's complement)
|
|
3087
|
+
*/
|
|
3088
|
+
neg() {
|
|
3089
|
+
const a = this.registers.A;
|
|
3090
|
+
const result = (0 - a) & 0xff;
|
|
3091
|
+
|
|
3092
|
+
this.registers.A = result;
|
|
3093
|
+
this.flagC = a !== 0;
|
|
3094
|
+
this.flagH = (a & 0x0f) !== 0;
|
|
3095
|
+
this.flagN = true;
|
|
3096
|
+
this.flagPV = a === 0x80;
|
|
3097
|
+
this.flagZ = result === 0;
|
|
3098
|
+
this.flagS = (result & 0x80) !== 0;
|
|
3099
|
+
this._xy(result);
|
|
3100
|
+
|
|
3101
|
+
return 8;
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
// Extended I/O Instructions
|
|
3105
|
+
/**
|
|
3106
|
+
* IN r, (C) - Input from port C to register
|
|
3107
|
+
* Register mapping: 0=B, 1=C, 2=D, 3=E, 4=H, 5=L, 6=(HL), 7=A
|
|
3108
|
+
*/
|
|
3109
|
+
inrC(regIndex) {
|
|
3110
|
+
this.wz = (this.BC + 1) & 0xffff;
|
|
3111
|
+
const port = this.registers.C;
|
|
3112
|
+
const value = this.readPort(port);
|
|
3113
|
+
|
|
3114
|
+
const regs = ["B", "C", "D", "E", "H", "L", null, "A"];
|
|
3115
|
+
if (regIndex !== 6) this.registers[regs[regIndex]] = value; // 6 is IN F,(C): flags only
|
|
3116
|
+
|
|
3117
|
+
// Flag updates
|
|
3118
|
+
this.flagZ = value === 0;
|
|
3119
|
+
this.flagS = (value & 0x80) !== 0;
|
|
3120
|
+
this.flagH = false;
|
|
3121
|
+
this.flagPV = this.parity(value);
|
|
3122
|
+
this.flagN = false;
|
|
3123
|
+
this._xy(value);
|
|
3124
|
+
|
|
3125
|
+
return 12;
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
/**
|
|
3129
|
+
* OUT (C), r - Output register to port C
|
|
3130
|
+
*/
|
|
3131
|
+
outCr(regIndex) {
|
|
3132
|
+
this.wz = (this.BC + 1) & 0xffff;
|
|
3133
|
+
const port = this.registers.C;
|
|
3134
|
+
const regs = ["B", "C", "D", "E", "H", "L", null, "A"];
|
|
3135
|
+
// 6 is OUT (C),0; an NMOS Z80, as in the Model I and III, sends 0 (CMOS parts send FFH)
|
|
3136
|
+
const value = regIndex === 6 ? 0 : this.registers[regs[regIndex]];
|
|
3137
|
+
this.writePort(port, value);
|
|
3138
|
+
return 12;
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
/**
|
|
3142
|
+
* Block I/O flags (Young §4.3): S, Z, 5, 3 from B after it counts down; N from bit 7 of the
|
|
3143
|
+
* byte moved; H and C from the carry out of k; P/V the parity of (k AND 7) XOR B.
|
|
3144
|
+
*/
|
|
3145
|
+
_blockIOFlags(value, k) {
|
|
3146
|
+
const b = this.registers.B;
|
|
3147
|
+
this.flagS = (b & 0x80) !== 0;
|
|
3148
|
+
this.flagZ = b === 0;
|
|
3149
|
+
this._xy(b);
|
|
3150
|
+
this.flagN = (value & 0x80) !== 0;
|
|
3151
|
+
this.flagH = k > 0xff;
|
|
3152
|
+
this.flagC = k > 0xff;
|
|
3153
|
+
this.flagPV = this.parity((k & 7) ^ b);
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
/**
|
|
3157
|
+
* INI - Input and Increment
|
|
3158
|
+
* Inputs from port C to (HL), increments HL, decrements B
|
|
3159
|
+
*/
|
|
3160
|
+
ini() {
|
|
3161
|
+
this.wz = (this.BC + 1) & 0xffff;
|
|
3162
|
+
const port = this.registers.C;
|
|
3163
|
+
const value = this.readPort(port);
|
|
3164
|
+
this.writeMemory(this.HL, value);
|
|
3165
|
+
this.HL = (this.HL + 1) & 0xffff;
|
|
3166
|
+
this.registers.B = (this.registers.B - 1) & 0xff;
|
|
3167
|
+
|
|
3168
|
+
// Flag updates
|
|
3169
|
+
this._blockIOFlags(value, value + ((port + 1) & 0xff));
|
|
3170
|
+
|
|
3171
|
+
return 16;
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
/**
|
|
3175
|
+
* INIR - Input, Increment, Repeat
|
|
3176
|
+
* Repeats INI until B = 0
|
|
3177
|
+
*/
|
|
3178
|
+
inir() {
|
|
3179
|
+
const cycles = this.ini();
|
|
3180
|
+
if (this.registers.B !== 0) {
|
|
3181
|
+
this.registers.PC = (this.registers.PC - 2) & 0xffff;
|
|
3182
|
+
return cycles + 5;
|
|
3183
|
+
}
|
|
3184
|
+
return cycles;
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
/**
|
|
3188
|
+
* IND - Input and Decrement
|
|
3189
|
+
*/
|
|
3190
|
+
ind() {
|
|
3191
|
+
this.wz = (this.BC - 1) & 0xffff;
|
|
3192
|
+
const port = this.registers.C;
|
|
3193
|
+
const value = this.readPort(port);
|
|
3194
|
+
this.writeMemory(this.HL, value);
|
|
3195
|
+
this.HL = (this.HL - 1) & 0xffff;
|
|
3196
|
+
this.registers.B = (this.registers.B - 1) & 0xff;
|
|
3197
|
+
|
|
3198
|
+
this._blockIOFlags(value, value + ((port - 1) & 0xff));
|
|
3199
|
+
|
|
3200
|
+
return 16;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
/**
|
|
3204
|
+
* INDR - Input, Decrement, Repeat
|
|
3205
|
+
*/
|
|
3206
|
+
indr() {
|
|
3207
|
+
const cycles = this.ind();
|
|
3208
|
+
if (this.registers.B !== 0) {
|
|
3209
|
+
this.registers.PC = (this.registers.PC - 2) & 0xffff;
|
|
3210
|
+
return cycles + 5;
|
|
3211
|
+
}
|
|
3212
|
+
return cycles;
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
/**
|
|
3216
|
+
* OUTI - Output and Increment
|
|
3217
|
+
*/
|
|
3218
|
+
outi() {
|
|
3219
|
+
const port = this.registers.C;
|
|
3220
|
+
const value = this.readMemory(this.HL);
|
|
3221
|
+
this.writePort(port, value);
|
|
3222
|
+
this.HL = (this.HL + 1) & 0xffff;
|
|
3223
|
+
this.registers.B = (this.registers.B - 1) & 0xff;
|
|
3224
|
+
this.wz = (this.BC + 1) & 0xffff;
|
|
3225
|
+
|
|
3226
|
+
this._blockIOFlags(value, value + this.registers.L);
|
|
3227
|
+
|
|
3228
|
+
return 16;
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
/**
|
|
3232
|
+
* OTIR - Output, Increment, Repeat
|
|
3233
|
+
*/
|
|
3234
|
+
otir() {
|
|
3235
|
+
const cycles = this.outi();
|
|
3236
|
+
if (this.registers.B !== 0) {
|
|
3237
|
+
this.registers.PC = (this.registers.PC - 2) & 0xffff;
|
|
3238
|
+
return cycles + 5;
|
|
3239
|
+
}
|
|
3240
|
+
return cycles;
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
/**
|
|
3244
|
+
* OUTD - Output and Decrement
|
|
3245
|
+
*/
|
|
3246
|
+
outd() {
|
|
3247
|
+
const port = this.registers.C;
|
|
3248
|
+
const value = this.readMemory(this.HL);
|
|
3249
|
+
this.writePort(port, value);
|
|
3250
|
+
this.HL = (this.HL - 1) & 0xffff;
|
|
3251
|
+
this.registers.B = (this.registers.B - 1) & 0xff;
|
|
3252
|
+
this.wz = (this.BC - 1) & 0xffff;
|
|
3253
|
+
|
|
3254
|
+
this._blockIOFlags(value, value + this.registers.L);
|
|
3255
|
+
|
|
3256
|
+
return 16;
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
/**
|
|
3260
|
+
* OTDR - Output, Decrement, Repeat
|
|
3261
|
+
*/
|
|
3262
|
+
otdr() {
|
|
3263
|
+
const cycles = this.outd();
|
|
3264
|
+
if (this.registers.B !== 0) {
|
|
3265
|
+
this.registers.PC = (this.registers.PC - 2) & 0xffff;
|
|
3266
|
+
return cycles + 5;
|
|
3267
|
+
}
|
|
3268
|
+
return cycles;
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
// Interrupt Handling Instructions
|
|
3272
|
+
/**
|
|
3273
|
+
* RETI - Return from Interrupt
|
|
3274
|
+
* Same as RET but signals interrupt controller
|
|
3275
|
+
*/
|
|
3276
|
+
reti() {
|
|
3277
|
+
const low = this.readMemory(this.registers.SP);
|
|
3278
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
3279
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
3280
|
+
this.registers.PC = (high << 8) | low;
|
|
3281
|
+
this.wz = this.registers.PC;
|
|
3282
|
+
// IFF2 is copied to IFF1 on RETI
|
|
3283
|
+
this.IFF1 = this.IFF2;
|
|
3284
|
+
return 14;
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
/**
|
|
3288
|
+
* RETN - Return from Non-Maskable Interrupt
|
|
3289
|
+
* Same as RET but restores IFF1 from IFF2
|
|
3290
|
+
*/
|
|
3291
|
+
retn() {
|
|
3292
|
+
const low = this.readMemory(this.registers.SP);
|
|
3293
|
+
const high = this.readMemory((this.registers.SP + 1) & 0xffff);
|
|
3294
|
+
this.registers.SP = (this.registers.SP + 2) & 0xffff;
|
|
3295
|
+
this.registers.PC = (high << 8) | low;
|
|
3296
|
+
this.wz = this.registers.PC;
|
|
3297
|
+
// IFF2 is copied to IFF1 on RETN
|
|
3298
|
+
this.IFF1 = this.IFF2;
|
|
3299
|
+
return 14;
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
/**
|
|
3303
|
+
* RLD - Rotate the three low nibbles left: A.low -> (HL).low,
|
|
3304
|
+
* (HL).low -> (HL).high, (HL).high -> A.low. C is unaffected.
|
|
3305
|
+
*/
|
|
3306
|
+
rld() {
|
|
3307
|
+
const value = this.readMemory(this.HL);
|
|
3308
|
+
this.writeMemory(
|
|
3309
|
+
this.HL,
|
|
3310
|
+
((value << 4) & 0xf0) | (this.registers.A & 0x0f)
|
|
3311
|
+
);
|
|
3312
|
+
this.registers.A = (this.registers.A & 0xf0) | (value >> 4);
|
|
3313
|
+
this.wz = (this.HL + 1) & 0xffff;
|
|
3314
|
+
this._nibbleRotateFlags();
|
|
3315
|
+
return 18;
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
/**
|
|
3319
|
+
* RRD - Rotate the three low nibbles right: A.low -> (HL).high,
|
|
3320
|
+
* (HL).high -> (HL).low, (HL).low -> A.low. C is unaffected.
|
|
3321
|
+
*/
|
|
3322
|
+
rrd() {
|
|
3323
|
+
const value = this.readMemory(this.HL);
|
|
3324
|
+
this.writeMemory(
|
|
3325
|
+
this.HL,
|
|
3326
|
+
((this.registers.A & 0x0f) << 4) | (value >> 4)
|
|
3327
|
+
);
|
|
3328
|
+
this.registers.A = (this.registers.A & 0xf0) | (value & 0x0f);
|
|
3329
|
+
this.wz = (this.HL + 1) & 0xffff;
|
|
3330
|
+
this._nibbleRotateFlags();
|
|
3331
|
+
return 18;
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
_nibbleRotateFlags() {
|
|
3335
|
+
const a = this.registers.A;
|
|
3336
|
+
this.flagS = (a & 0x80) !== 0;
|
|
3337
|
+
this.flagZ = a === 0;
|
|
3338
|
+
this.flagH = false;
|
|
3339
|
+
this.flagPV = this.parity(a);
|
|
3340
|
+
this.flagN = false;
|
|
3341
|
+
this._xy(a);
|
|
3342
|
+
}
|
|
3343
|
+
}
|