zx-generation
Version:
A high-fidelity ZX Spectrum emulator in JavaScript — fully generated by a large language model (LLM) to explore the boundaries of AI in systems programming.
208 lines (175 loc) • 5.93 kB
JavaScript
import { sign8 } from '../utils/helpers.js';
/**
* Load Instructions
* Handles all LD operations for 8‐bit and 16‐bit loads, including special
* cases LD A,I and LD A,R with correct flag behaviour (PV = IFF2).
*/
class LoadInstructions {
constructor(registers, memory, io, flags, cpu = null) {
this.registers = registers;
this.memory = memory;
this.io = io;
this.flags = flags;
this.cpu = cpu; // optional, only needed for IFF2 in LD A,I and LD A,R
}
/* LD reg, value */
loadRegImmediate(regName, value) {
this.registers.set(regName, value & 0xff);
return 7;
}
/* LD reg16, value */
loadReg16Immediate(regName, value) {
this.registers.set16(regName, value & 0xffff);
return regName === 'IX' || regName === 'IY' ? 14 : 10;
}
/* LD reg, reg */
loadRegReg(destReg, srcReg) {
this.registers.set(destReg, this.registers.get(srcReg));
return 4;
}
/* LD reg, (HL) */
loadRegFromHL(regName) {
const addr = this.registers.getHL();
this.registers.set(regName, this.memory.readByte(addr));
return 7;
}
/* LD (HL), reg */
loadHLFromReg(regName) {
const addr = this.registers.getHL();
this.memory.writeByte(addr, this.registers.get(regName));
return 7;
}
/* LD (HL), n */
loadHLImmediate(value) {
const addr = this.registers.getHL();
this.memory.writeByte(addr, value & 0xff);
return 10;
}
/* LD A,(BC) */
loadAFromBC() {
const addr = this.registers.getBC();
this.registers.set('A', this.memory.readByte(addr));
return 7;
}
/* LD A,(DE) */
loadAFromDE() {
const addr = this.registers.getDE();
this.registers.set('A', this.memory.readByte(addr));
return 7;
}
/* LD (BC),A */
loadBCFromA() {
const addr = this.registers.getBC();
this.memory.writeByte(addr, this.registers.get('A'));
return 7;
}
/* LD (DE),A */
loadDEFromA() {
const addr = this.registers.getDE();
this.memory.writeByte(addr, this.registers.get('A'));
return 7;
}
/* LD A,(nn) */
loadAFromAddress(address) {
this.registers.set('A', this.memory.readByte(address & 0xffff));
return 13;
}
/* LD (nn),A */
loadAddressFromA(address) {
this.memory.writeByte(address & 0xffff, this.registers.get('A'));
return 13;
}
/* LD HL,(nn) */
loadHLFromAddress(address) {
this.registers.setHL(this.memory.readWord(address & 0xffff));
return 16;
}
/* LD (nn),HL */
loadAddressFromHL(address) {
this.memory.writeWord(address & 0xffff, this.registers.getHL());
return 16;
}
/* LD reg16,(nn) (ED) */
loadReg16FromAddress(regName, address) {
this.registers.set16(regName, this.memory.readWord(address & 0xffff));
return 20;
}
/* LD (nn),reg16 (ED) */
loadAddressFromReg16(address, regName) {
this.memory.writeWord(address & 0xffff, this.registers.get16(regName));
return 20;
}
/* LD SP,HL */
loadSPFromHL() {
this.registers.set16('SP', this.registers.getHL());
return 6;
}
/* LD I,A */
loadIFromA() {
this.registers.set('I', this.registers.get('A'));
return 9;
}
/* LD R,A */
loadRFromA() {
this.registers.set('R', this.registers.get('A'));
return 9;
}
/**
* Helper used by LD A,I and LD A,R to compute flags.
* @param {number} value The value loaded into A (either I or R)
*/
_updateFlagsAfterLoadAIorAR(value) {
let f = this.registers.get('F');
const carryState = this.flags.getFlag(f, this.flags.masks.C); // preserve C
f = this.flags.setFlag(f, this.flags.masks.S, (value & 0x80) !== 0);
f = this.flags.setFlag(f, this.flags.masks.Z, value === 0);
f = this.flags.setFlag(f, this.flags.masks.H, false);
f = this.flags.setFlag(f, this.flags.masks.N, false);
// PV == IFF2 when CPU context is provided
if (this.cpu) {
f = this.flags.setFlag(f, this.flags.masks.PV, !!this.cpu.iff2);
}
// Undocumented flags
f = this.flags.setFlag(f, this.flags.masks.F5, (value & 0x20) !== 0);
f = this.flags.setFlag(f, this.flags.masks.F3, (value & 0x08) !== 0);
// Restore original carry
f = this.flags.setFlag(f, this.flags.masks.C, carryState);
this.registers.set('F', f);
}
/* LD A,I */
loadAFromI() {
const i = this.registers.get('I') & 0xff;
this.registers.set('A', i);
this._updateFlagsAfterLoadAIorAR(i);
return 9;
}
/* LD A,R */
loadAFromR() {
const r = this.registers.get('R') & 0xff;
this.registers.set('A', r);
this._updateFlagsAfterLoadAIorAR(r);
return 9;
}
/* LD reg,(IX/IY+d) */
loadRegFromIndexed(regName, indexReg, displacement) {
const signedDisp = sign8(displacement);
const addr = (this.registers.get16(indexReg) + signedDisp) & 0xffff;
this.registers.set(regName, this.memory.readByte(addr));
return 19;
}
/* LD (IX/IY+d),reg */
loadIndexedFromReg(indexReg, displacement, regName) {
const signedDisp = sign8(displacement);
const addr = (this.registers.get16(indexReg) + signedDisp) & 0xffff;
this.memory.writeByte(addr, this.registers.get(regName));
return 19;
}
/* LD (IX/IY+d),n */
loadIndexedImmediate(indexReg, displacement, value) {
const signedDisp = sign8(displacement);
const addr = (this.registers.get16(indexReg) + signedDisp) & 0xffff;
this.memory.writeByte(addr, value & 0xff);
return 19;
}
}
export { LoadInstructions };