UNPKG

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.

380 lines (345 loc) 13.9 kB
import { Registers } from './registers.js'; import { Flags } from './flags.js'; import { MemoryInterface } from '../interfaces/memory-interface.js'; import { IOInterface } from '../interfaces/io-interface.js'; import { ArithmeticInstructions } from '../instructions/arithmetic.js'; import { LogicalInstructions } from '../instructions/logical.js'; import { LoadInstructions } from '../instructions/load.js'; import { JumpInstructions } from '../instructions/jump.js'; import { BitInstructions } from '../instructions/bit.js'; import { MiscInstructions } from '../instructions/misc.js'; import { ExtendedInstructions } from '../instructions/extended.js'; import { IndexedInstructions } from '../instructions/indexed.js'; import { InstructionDecoder } from '../decoder/instruction-decoder.js'; /** * Z80 CPU Emulator * Complete and accurate Z80 processor emulation with full instruction set support * * @class Z80 */ class Z80 { /** * Create a Z80 CPU instance * * @constructor * @param {Object} memory - Memory interface for RAM/ROM access * @param {Object} ula - ULA interface for I/O operations */ constructor(memory, ula) { // Initialize interfaces this.memory = new MemoryInterface(memory); this.io = new IOInterface(ula); // Initialize core components this.registers = new Registers(); this.flags = new Flags(); // Initialize instruction handlers this.instructions = { arithmetic: new ArithmeticInstructions(this.registers, this.flags, this.memory), logical: new LogicalInstructions(this.registers, this.flags), load: new LoadInstructions(this.registers, this.memory, this.io, this.flags, this), jump: new JumpInstructions(this.registers, this.flags, this.memory), bit: new BitInstructions(this.registers, this.flags, this.memory), misc: new MiscInstructions(this.registers, this.memory), extended: new ExtendedInstructions(this.registers, this.flags, this.memory, this.io), }; // Initialize indexed instructions with factory pattern to avoid circular dependencies const instructionFactories = { getArithmetic: () => this.instructions.arithmetic, getLogical: () => this.instructions.logical, getLoad: () => this.instructions.load, getBit: () => this.instructions.bit, }; this.instructions.indexed = new IndexedInstructions( this.registers, this.flags, this.memory, instructionFactories ); // Initialize instruction decoder this.decoder = new InstructionDecoder( this.registers, this.flags, this.memory, this.io, this.instructions ); // CPU state this.reset(); } /** * Reset the CPU to initial state * * @returns {void} */ reset() { this.registers.reset(); this.halted = false; this.interruptMode = 1; this.cycles = 0; this.iff1 = true; // Interrupt flip-flop 1 this.iff2 = true; // Interrupt flip-flop 2 this.instructionCount = 0; // For debugging } /** * Execute a single instruction * * @returns {void} */ execute() { if (this.halted) { // HALT instruction continues to consume 4 T-states per M1 cycle // until an interrupt occurs. This is important for accurate timing. this.cycles += 4; this.registers.incrementR(); return 4; } // Log first few instructions for debugging // if (this.instructionCount < 10) { // const pc = this.registers.getPC(); // const opcode = this.memory.readByte ? this.memory.readByte(pc) : this.memory.read(pc); // console.log(`Instruction ${this.instructionCount}: PC=0x${pc.toString(16).padStart(4, '0')}, opcode=0x${opcode.toString(16).padStart(2, '0')}`); // this.instructionCount++; // } const opcode = this.memory.fetchByte(this.registers); this.registers.incrementR(); const instructionCycles = this.decoder.execute(opcode, this); this.cycles += instructionCycles; return instructionCycles; } /** * Trigger a maskable interrupt * * @returns {void} */ interrupt() { if (this.iff1 || this.halted) { this.halted = false; this.iff1 = false; this.iff2 = false; // Handle different interrupt modes switch (this.interruptMode) { case 0: // Mode 0 - Execute instruction on data bus // On most systems, data bus contains 0xFF (RST 38H) this.memory.pushWord(this.registers, this.registers.getPC()); this.registers.setPC(0x0038); this.cycles += 13; break; case 1: // Mode 1 - RST 38h always this.memory.pushWord(this.registers, this.registers.getPC()); this.registers.setPC(0x0038); this.cycles += 13; break; case 2: // Mode 2 - Vectored interrupt { // Vector formed from I register (high byte) and data bus (low byte) const vector = (this.registers.get('I') << 8) | 0xff; // Assuming 0xFF on data bus const addr = this.memory.readWord(vector); this.memory.pushWord(this.registers, this.registers.getPC()); this.registers.setPC(addr); this.cycles += 19; } break; } } } /** * Get complete CPU state with nested structure (DEPRECATED) * @deprecated Use getState() instead - this method will be removed in future versions * @returns {Object} CPU state with nested structure */ getStateNested() { return { registers: this.registers.dump(), flags: { S: this.flags.getFlag(this.registers.get('F'), this.flags.masks.S), Z: this.flags.getFlag(this.registers.get('F'), this.flags.masks.Z), H: this.flags.getFlag(this.registers.get('F'), this.flags.masks.H), PV: this.flags.getFlag(this.registers.get('F'), this.flags.masks.PV), N: this.flags.getFlag(this.registers.get('F'), this.flags.masks.N), C: this.flags.getFlag(this.registers.get('F'), this.flags.masks.C), }, cpu: { halted: this.halted, interruptMode: this.interruptMode, cycles: this.cycles, iff1: this.iff1, iff2: this.iff2, }, }; } /** * Set CPU state from nested structure (DEPRECATED) * @deprecated Use setState() instead - this method will be removed in future versions * @param {Object} state - CPU state with nested structure */ setStateNested(state) { if (state.registers) { Object.keys(state.registers).forEach(reg => { if (reg.length === 1) { this.registers.set(reg, parseInt(state.registers[reg], 16)); } else { this.registers.set16(reg, parseInt(state.registers[reg], 16)); } }); } if (state.cpu) { this.halted = state.cpu.halted || false; this.interruptMode = state.cpu.interruptMode || 1; this.cycles = state.cpu.cycles || 0; this.iff1 = state.cpu.iff1 || false; this.iff2 = state.cpu.iff2 || false; } } // Legacy compatibility methods getBC() { return this.registers.getBC(); } getDE() { return this.registers.getDE(); } getHL() { return this.registers.getHL(); } getAF() { return this.registers.getAF(); } setBC(value) { this.registers.setBC(value); } setDE(value) { this.registers.setDE(value); } setHL(value) { this.registers.setHL(value); } setAF(value) { this.registers.setAF(value); } getFlag(flag) { return this.flags.getFlag(this.registers.get('F'), flag); } setFlag(flag, value) { const newF = this.flags.setFlag(this.registers.get('F'), flag, value); this.registers.set('F', newF); } get flagMasks() { return this.flags.masks; } /** * Get complete CPU state in flat structure * Used for snapshots and state management * * @returns {Object} CPU state with all registers and flags * @returns {number} .pc - Program counter * @returns {number} .sp - Stack pointer * @returns {number} .a - Accumulator * @returns {number} .f - Flags register * @returns {number} .b - B register * @returns {number} .c - C register * @returns {number} .d - D register * @returns {number} .e - E register * @returns {number} .h - H register * @returns {number} .l - L register * @returns {number} .ix - IX index register * @returns {number} .iy - IY index register * @returns {number} .i - Interrupt vector register * @returns {number} .r - Memory refresh register * @returns {number} .im - Interrupt mode * @returns {boolean} .iff1 - Interrupt flip-flop 1 * @returns {boolean} .iff2 - Interrupt flip-flop 2 * @returns {boolean} .halted - HALT state * @returns {number} .cycles - Total cycles executed * * @example * const state = cpu.getState(); * console.log(`PC: ${state.pc.toString(16)}`); * console.log(`SP: ${state.sp.toString(16)}`); */ getState() { return { // 16-bit registers need special handling pc: this.registers.getPC(), sp: this.registers.get16('SP'), // 8-bit registers a: this.registers.get('A'), f: this.registers.get('F'), b: this.registers.get('B'), c: this.registers.get('C'), d: this.registers.get('D'), e: this.registers.get('E'), h: this.registers.get('H'), l: this.registers.get('L'), // 16-bit index registers ix: this.registers.get16('IX'), iy: this.registers.get16('IY'), // Special registers i: this.registers.get('I'), r: this.registers.get('R'), // CPU state im: this.interruptMode, // Fixed: was this.im iff1: this.iff1, iff2: this.iff2, halted: this.halted, // Added missing property cycles: this.cycles }; } /** * Set CPU state from flat structure * * @param {Object} state - CPU state to restore * @param {number} [state.pc] - Program counter * @param {number} [state.sp] - Stack pointer * @param {number} [state.a] - Accumulator * @param {number} [state.f] - Flags register * @param {number} [state.b] - B register * @param {number} [state.c] - C register * @param {number} [state.d] - D register * @param {number} [state.e] - E register * @param {number} [state.h] - H register * @param {number} [state.l] - L register * @param {number} [state.ix] - IX index register * @param {number} [state.iy] - IY index register * @param {number} [state.i] - Interrupt vector register * @param {number} [state.r] - Memory refresh register * @param {number} [state.im] - Interrupt mode * @param {boolean} [state.iff1] - Interrupt flip-flop 1 * @param {boolean} [state.iff2] - Interrupt flip-flop 2 * @param {boolean} [state.halted] - HALT state * @param {number} [state.cycles] - Total cycles executed * @returns {void} * * @example * cpu.setState({ * pc: 0x8000, * sp: 0xFFFF, * a: 0x00 * }); */ setState(state) { // 16-bit registers if (state.pc !== undefined) this.registers.setPC(state.pc); if (state.sp !== undefined) this.registers.set16('SP', state.sp); // 8-bit registers if (state.a !== undefined) this.registers.set('A', state.a); if (state.f !== undefined) this.registers.set('F', state.f); if (state.b !== undefined) this.registers.set('B', state.b); if (state.c !== undefined) this.registers.set('C', state.c); if (state.d !== undefined) this.registers.set('D', state.d); if (state.e !== undefined) this.registers.set('E', state.e); if (state.h !== undefined) this.registers.set('H', state.h); if (state.l !== undefined) this.registers.set('L', state.l); // 16-bit index registers if (state.ix !== undefined) this.registers.set16('IX', state.ix); if (state.iy !== undefined) this.registers.set16('IY', state.iy); // Special registers if (state.i !== undefined) this.registers.set('I', state.i); if (state.r !== undefined) this.registers.set('R', state.r); // CPU state if (state.im !== undefined) this.interruptMode = state.im; if (state.iff1 !== undefined) this.iff1 = state.iff1; if (state.iff2 !== undefined) this.iff2 = state.iff2; if (state.halted !== undefined) this.halted = state.halted; if (state.cycles !== undefined) this.cycles = state.cycles; } } export { Z80 };