const types = @import("types.zig"); const Model = types.Model; const Registers = types.Registers; const Instruction = @import("instruction.zig"); const AddressingMode = Instruction.AddressingMode; const Alu = @import("alu.zig"); const Addressing = @import("addressing.zig"); const utils = @import("utils.zig"); pub const disassembler = @import("disassembler.zig"); pub const mos6510 = @import("mos6510.zig"); pub const Vector = enum(u16) { nmi = 0xfffa, reset = 0xfffc, irq = 0xfffe, }; const CpuState = enum { running, waiting, stopped, jammed, }; pub const Accuracy = enum { instruction, cycle, }; pub const CpuSnapshot = struct { registers: Registers, cycles: u64, state: CpuState, nmi_line: bool, nmi_latch: bool, nmi_pending: bool, irq_asserted: bool, }; pub fn Cpu( comptime Bus: type, comptime model: Model, comptime accuracy: Accuracy, ) type { return CpuWithHook(Bus, model, accuracy, void); } pub fn CpuWithHook( comptime Bus: type, comptime model: Model, comptime accuracy: Accuracy, comptime Hook: type, ) type { comptime { utils.validateBus(Bus); if (accuracy == .cycle and !@hasDecl(Bus, "tick")) { @compileError( "Cycle-accurate CPU mode requires Bus.tick(self: *Bus) void", ); } } return struct { pub const cpu_model = model; pub const cpu_accuracy = accuracy; pub const features = model.features(); pub const instruction_table = model.instructionTable(); pub const address_mask = model.addressMask(); bus: *Bus, registers: Registers = .{}, cycles: u64 = 0, state: CpuState = .running, nmi_line: bool = false, nmi_latch: bool = false, nmi_pending: bool = false, irq_asserted: bool = false, const Self = @This(); // Public API & Lifecycle pub fn init(bus: *Bus) Self { return .{ .bus = bus, }; } pub fn saveState(self: *const Self) CpuSnapshot { return .{ .registers = self.registers, .cycles = self.cycles, .state = self.state, .nmi_line = self.nmi_line, .nmi_latch = self.nmi_latch, .nmi_pending = self.nmi_pending, .irq_asserted = self.irq_asserted, }; } pub fn loadState(self: *Self, snapshot: CpuSnapshot) void { self.registers = snapshot.registers; self.cycles = snapshot.cycles; self.state = snapshot.state; self.nmi_line = snapshot.nmi_line; self.nmi_latch = snapshot.nmi_latch; self.nmi_pending = snapshot.nmi_pending; self.irq_asserted = snapshot.irq_asserted; } pub fn setNmi(self: *Self, active: bool) void { if (self.nmi_line and !active) { self.nmi_latch = true; } self.nmi_line = active; } pub fn reset(self: *Self) void { self.state = .running; self.nmi_line = false; self.nmi_latch = false; self.nmi_pending = false; self.irq_asserted = false; self.cycles = 0; self.registers.status.interrupt_disable = true; if (features.cmos_instructions) { self.registers.status.decimal = false; } self.registers.sp = 0x00; _ = self.readCycle(self.registers.pc); _ = self.readCycle(self.registers.pc); for (0..3) |_| { _ = self.readCycle( 0x0100 | @as(u16, self.registers.sp), ); self.registers.sp -%= 1; } const low = self.readCycle(@intFromEnum(Vector.reset)); const high = self.readCycle(@intFromEnum(Vector.reset) + 1); self.registers.pc = utils.makeWord(low, high); } pub fn step(self: *Self) ?u8 { if (self.state == .stopped or self.state == .jammed) { return null; } const start = self.cycles; if (self.nmi_latch or self.nmi_pending) { self.nmi_latch = false; self.nmi_pending = false; self.wakeFromWait(); self.handleInterrupt(@intFromEnum(Vector.nmi)); return @intCast(self.cycles -% start); } if (self.irq_asserted) { self.wakeFromWait(); if (!self.registers.status.interrupt_disable) { self.handleInterrupt(@intFromEnum(Vector.irq)); return @intCast(self.cycles -% start); } } if (self.state == .waiting) return null; const pc_before = self.registers.pc; const opcode = self.fetchByte(); const instruction = instruction_table[opcode]; if (Hook != void) { Hook.onStep(self, pc_before, opcode, instruction); } self.execute(instruction); return @intCast(self.cycles -% start); } fn wakeFromWait(self: *Self) void { if (self.state == .waiting) self.state = .running; } // Execution Dispatch fn execute(self: *Self, instruction: Instruction) void { switch (instruction.operation) { // Load & Store Instructions .lda => self.executeRead(instruction.mode, Self.lda), .ldx => self.executeRead(instruction.mode, Self.ldx), .ldy => self.executeRead(instruction.mode, Self.ldy), .sta => self.executeWrite(instruction.mode, self.registers.a), .stx => self.executeWrite(instruction.mode, self.registers.x), .sty => self.executeWrite(instruction.mode, self.registers.y), .stz => self.executeWrite(instruction.mode, 0), // Register Transfers & Stack Operations .tax => self.executeImplied(Self.tax), .txa => self.executeImplied(Self.txa), .tay => self.executeImplied(Self.tay), .tya => self.executeImplied(Self.tya), .tsx => self.executeImplied(Self.tsx), .txs => self.executeImplied(Self.txs), .pha => self.pha(), .php => self.php(), .pla => self.pla(), .plp => self.plp(), .phx => self.phx(), .phy => self.phy(), .plx => self.plx(), .ply => self.ply(), // Arithmetic & Logical Instructions .adc => self.executeRead(instruction.mode, Self.adc), .sbc => self.executeRead(instruction.mode, Self.sbc), .@"and" => self.executeRead(instruction.mode, Self.@"and"), .ora => self.executeRead(instruction.mode, Self.ora), .eor => self.executeRead(instruction.mode, Self.eor), .bit => self.executeBit(instruction.mode), .cmp => self.executeCompare(instruction.mode, self.registers.a), .cpx => self.executeCompare(instruction.mode, self.registers.x), .cpy => self.executeCompare(instruction.mode, self.registers.y), .trb => self.executeModify(instruction.mode, Self.trb), .tsb => self.executeModify(instruction.mode, Self.tsb), // Increments, Decrements & Shifts .inc => self.executeIncDec(instruction.mode, Self.inc), .dec => self.executeIncDec(instruction.mode, Self.dec), .inx => self.executeImplied(Self.inx), .dex => self.executeImplied(Self.dex), .iny => self.executeImplied(Self.iny), .dey => self.executeImplied(Self.dey), .asl => self.executeShift(instruction.mode, Self.asl), .lsr => self.executeShift(instruction.mode, Self.lsr), .rol => self.executeShift(instruction.mode, Self.rol), .ror => self.executeShift(instruction.mode, Self.ror), // Branch & Jump Instructions .bcc => self.branch(!self.registers.status.carry), .bcs => self.branch(self.registers.status.carry), .beq => self.branch(self.registers.status.zero), .bne => self.branch(!self.registers.status.zero), .bmi => self.branch(self.registers.status.negative), .bpl => self.branch(!self.registers.status.negative), .bvc => self.branch(!self.registers.status.overflow), .bvs => self.branch(self.registers.status.overflow), .bra => self.branch(true), .bbr0, .bbr1, .bbr2, .bbr3, .bbr4, .bbr5, .bbr6, .bbr7 => self.bbr(bitNumber(instruction.operation, .bbr0)), .bbs0, .bbs1, .bbs2, .bbs3, .bbs4, .bbs5, .bbs6, .bbs7 => self.bbs(bitNumber(instruction.operation, .bbs0)), .jmp => self.jmp(instruction.mode), .jsr => self.jsr(), .rti => self.rti(), .rts => self.rts(), // Flags & System Control .clc => self.executeImplied(Self.clc), .sec => self.executeImplied(Self.sec), .cli => self.executeImplied(Self.cli), .sei => self.executeImplied(Self.sei), .cld => self.executeImplied(Self.cld), .sed => self.executeImplied(Self.sed), .clv => self.executeImplied(Self.clv), .nop => self.nop(instruction.mode), .brk => self.brk(), .rmb0, .rmb1, .rmb2, .rmb3, .rmb4, .rmb5, .rmb6, .rmb7 => self.rmb(bitNumber(instruction.operation, .rmb0)), .smb0, .smb1, .smb2, .smb3, .smb4, .smb5, .smb6, .smb7 => self.smb(bitNumber(instruction.operation, .smb0)), .wai => self.wai(), .stp => self.stp(), .kil => self.kil(), // Undocumented NMOS 6502 Opcodes .ahx => self.ahx(instruction.mode), .alr => self.executeRead(instruction.mode, Self.alr), .anc => self.executeRead(instruction.mode, Self.anc), .arr => self.executeRead(instruction.mode, Self.arr), .axs => self.executeRead(instruction.mode, Self.axs), .dcp => self.executeModify(instruction.mode, Self.dcp), .isc => self.executeModify(instruction.mode, Self.isc), .las => self.executeRead(instruction.mode, Self.las), .lax => self.executeRead(instruction.mode, Self.lax), .rla => self.executeModify(instruction.mode, Self.rla), .rra => self.executeModify(instruction.mode, Self.rra), .sax => self.executeWrite( instruction.mode, self.registers.a & self.registers.x, ), .shx => self.shx(instruction.mode), .shy => self.shy(instruction.mode), .slo => self.executeModify(instruction.mode, Self.slo), .sre => self.executeModify(instruction.mode, Self.sre), .tas => self.tas(instruction.mode), .xaa => self.executeRead(instruction.mode, Self.xaa), } } // Execution Families fn executeRead( self: *Self, mode: AddressingMode, comptime operation: fn (*Self, u8) void, ) void { const value = self.readOperand(mode); operation(self, value); } fn executeWrite( self: *Self, mode: AddressingMode, value: u8, ) void { self.writeOperand(mode, value); } fn executeModify( self: *Self, mode: AddressingMode, comptime operation: fn (*Self, u8) u8, ) void { switch (mode) { .zero_page => { const address = self.fetchByte(); self.modifyAddress(address, operation); }, .zero_page_x => { const base = self.fetchByte(); _ = self.readCycle(base); self.modifyAddress( base +% self.registers.x, operation, ); }, .absolute => { const address = self.fetchWord(); self.modifyAddress(address, operation); }, .absolute_x => self.modifyAbsoluteIndexed( self.registers.x, operation, ), // Used by undocumented RMW instructions. .absolute_y => self.modifyAbsoluteIndexed( self.registers.y, operation, ), .indexed_indirect_x => self.modifyIndexedIndirectX( operation, ), .indirect_indexed_y => self.modifyIndirectIndexedY( operation, ), else => unreachable, } } fn executeShift( self: *Self, mode: AddressingMode, comptime operation: fn (*Self, u8) u8, ) void { if (mode == .accumulator) { self.executeAccumulator(operation); } else { self.executeModify(mode, operation); } } fn executeAccumulator( self: *Self, comptime operation: fn (*Self, u8) u8, ) void { _ = self.readCycle(self.registers.pc); self.registers.a = operation(self, self.registers.a); } fn executeIncDec( self: *Self, mode: AddressingMode, comptime operation: fn (*Self, u8) u8, ) void { if (mode == .accumulator) { self.executeAccumulator(operation); } else { self.executeModify(mode, operation); } } fn executeImplied( self: *Self, comptime operation: fn (*Self) void, ) void { _ = self.readCycle(self.registers.pc); operation(self); } fn executeCompare(self: *Self, mode: AddressingMode, register: u8) void { const value = self.readOperand(mode); self.compare(register, value); } fn executeBit(self: *Self, mode: AddressingMode) void { const value = self.readOperand(mode); self.registers.status.zero = (self.registers.a & value) == 0; // 65C02 BIT #imm only affects Z. if (mode != .immediate) { self.registers.status.negative = (value & 0x80) != 0; self.registers.status.overflow = (value & 0x40) != 0; } } // Bus Access & Fetch pub fn tick(self: *Self) void { self.cycles +%= 1; if (comptime @hasDecl(Bus, "tick")) { self.bus.tick(); } } pub fn readCycle(self: *Self, address: u16) u8 { const value = self.bus.read(address & address_mask); self.tick(); return value; } pub fn writeCycle(self: *Self, address: u16, value: u8) void { self.bus.write(address & address_mask, value); self.tick(); } pub fn fetchByte(self: *Self) u8 { const value = self.readCycle(self.registers.pc); self.registers.pc +%= 1; return value; } pub fn fetchWord(self: *Self) u16 { const low = self.fetchByte(); const high = self.fetchByte(); return utils.makeWord(low, high); } // Addressing Operations fn readOperand(self: *Self, mode: AddressingMode) u8 { return Addressing.readOperand(self, mode); } fn writeOperand(self: *Self, mode: AddressingMode, value: u8) void { Addressing.writeOperand(self, mode, value); } fn modifyAddress(self: *Self, address: u16, comptime operation: fn (*Self, u8) u8) void { Addressing.modifyAddress(self, address, operation); } fn modifyAbsoluteIndexed(self: *Self, index: u8, comptime operation: fn (*Self, u8) u8) void { Addressing.modifyAbsoluteIndexed(self, index, operation); } fn modifyIndexedIndirectX(self: *Self, comptime operation: fn (*Self, u8) u8) void { Addressing.modifyIndexedIndirectX(self, operation); } fn modifyIndirectIndexedY(self: *Self, comptime operation: fn (*Self, u8) u8) void { Addressing.modifyIndirectIndexedY(self, operation); } fn bitNumber(operation: Instruction.Operation, first: Instruction.Operation) u3 { return Addressing.bitNumber(operation, first); } fn addRelative(base: u16, offset: i8) u16 { return Addressing.addRelative(base, offset); } fn pageCrossed(base: u16, address: u16) bool { return Addressing.pageCrossed(base, address); } fn branch(self: *Self, condition: bool) void { const offset: i8 = @bitCast(self.fetchByte()); if (!condition) return; const old_pc = self.registers.pc; const new_pc = addRelative(old_pc, offset); _ = self.readCycle(old_pc); if (pageCrossed(old_pc, new_pc)) { const dummy = (old_pc & 0xff00) | (new_pc & 0x00ff); _ = self.readCycle(dummy); } self.registers.pc = new_pc; } // Stack & Interrupt Primitives fn push(self: *Self, value: u8) void { self.writeCycle( 0x0100 | @as(u16, self.registers.sp), value, ); self.registers.sp -%= 1; } fn pop(self: *Self) u8 { self.registers.sp +%= 1; return self.readCycle( 0x0100 | @as(u16, self.registers.sp), ); } fn push16(self: *Self, value: u16) void { self.push(@truncate(value >> 8)); self.push(@truncate(value)); } fn pop16(self: *Self) u16 { const low = self.pop(); const high = self.pop(); return utils.makeWord(low, high); } fn handleInterrupt( self: *Self, vector: u16, ) void { _ = self.readCycle(self.registers.pc); _ = self.readCycle(self.registers.pc); self.push16(self.registers.pc); self.push(self.registers.status.toByte(false)); self.registers.status.interrupt_disable = true; if (features.cmos_instructions) { self.registers.status.decimal = false; } const low = self.readCycle(vector); const high = self.readCycle(vector +% 1); self.registers.pc = utils.makeWord(low, high); } // Control Flow & Stack Instructions fn brk(self: *Self) void { // Opcode fetch already incremented PC once. // BRK performs another read and advances PC again, // so the stacked return address is BRK + 2. _ = self.fetchByte(); self.push16(self.registers.pc); // BRK pushes P with the B bit set. self.push(self.registers.status.toByte(true)); self.registers.status.interrupt_disable = true; // 65C02 clears decimal mode on interrupt entry. if (features.cmos_instructions) { self.registers.status.decimal = false; } const low = self.readCycle(@intFromEnum(Vector.irq)); const high = self.readCycle(@intFromEnum(Vector.irq) + 1); self.registers.pc = utils.makeWord(low, high); } fn jmp(self: *Self, mode: AddressingMode) void { switch (mode) { .absolute => { self.registers.pc = self.fetchWord(); }, .absolute_indexed_indirect => { const base = self.fetchWord(); // Extra indexing cycle. _ = self.readCycle(base); const pointer = base +% self.registers.x; const low = self.readCycle(pointer); const high = self.readCycle(pointer +% 1); self.registers.pc = @as(u16, low) | (@as(u16, high) << 8); }, .indirect => { const pointer = self.fetchWord(); const low = self.readCycle(pointer); const high_address = if (features.fixed_indirect_jump) pointer +% 1 else (pointer & 0xff00) | ((pointer +% 1) & 0x00ff); const high = self.readCycle(high_address); self.registers.pc = @as(u16, low) | (@as(u16, high) << 8); }, else => unreachable, } } fn jsr(self: *Self) void { const target_low = self.fetchByte(); _ = self.readCycle(0x0100 | @as(u16, self.registers.sp)); // PC currently points at the high byte of the JSR operand. // This is the return address pushed by the 6502. self.push16(self.registers.pc); const target_high = self.fetchByte(); self.registers.pc = utils.makeWord(target_low, target_high); } fn nop(self: *Self, mode: AddressingMode) void { switch (mode) { .implied, .accumulator, => { _ = self.readCycle(self.registers.pc); }, .immediate, .zero_page, .zero_page_x, .zero_page_y, .absolute, .absolute_x, .absolute_y, .indirect, .indexed_indirect_x, .indirect_indexed_y, .zero_page_indirect, .absolute_indexed_indirect, => { _ = self.readOperand(mode); }, .relative => { _ = self.fetchByte(); }, .zero_page_relative => { _ = self.fetchByte(); _ = self.fetchByte(); }, } } fn pha(self: *Self) void { _ = self.readCycle(self.registers.pc); self.push(self.registers.a); } fn php(self: *Self) void { _ = self.readCycle(self.registers.pc); // PHP pushes status with B = 1. self.push(self.registers.status.toByte(true)); } fn pla(self: *Self) void { _ = self.readCycle(self.registers.pc); _ = self.readCycle( 0x0100 | @as(u16, self.registers.sp), ); self.registers.a = self.pop(); self.updateNZ(self.registers.a); } fn plp(self: *Self) void { _ = self.readCycle(self.registers.pc); _ = self.readCycle( 0x0100 | @as(u16, self.registers.sp), ); const value = self.pop(); self.loadStatus(value); } fn rti(self: *Self) void { _ = self.readCycle(self.registers.pc); _ = self.readCycle( 0x0100 | @as(u16, self.registers.sp), ); self.loadStatus(self.pop()); self.registers.pc = self.pop16(); } fn rts(self: *Self) void { _ = self.readCycle(self.registers.pc); _ = self.readCycle( 0x0100 | @as(u16, self.registers.sp), ); self.registers.pc = self.pop16(); _ = self.readCycle(self.registers.pc); // JSR stores the address of its final operand byte, // so RTS resumes at address + 1. self.registers.pc +%= 1; } // Status & Comparison Helpers fn updateNZ(self: *Self, value: u8) void { self.registers.status.zero = value == 0; self.registers.status.negative = value & 0x80 != 0; } fn loadStatus(self: *Self, value: u8) void { self.registers.status.carry = (value & 0x01) != 0; self.registers.status.zero = (value & 0x02) != 0; self.registers.status.interrupt_disable = (value & 0x04) != 0; self.registers.status.decimal = (value & 0x08) != 0; self.registers.status.overflow = (value & 0x40) != 0; self.registers.status.negative = (value & 0x80) != 0; } fn compare(self: *Self, register: u8, value: u8) void { const result = register -% value; self.registers.status.carry = register >= value; self.updateNZ(result); } // Documented Arithmetic & Logic fn adc(self: *Self, value: u8) void { const accumulator = self.registers.a; const carry: u16 = @intFromBool(self.registers.status.carry); const sum = @as(u16, accumulator) + @as(u16, value) + carry; const binary_result: u8 = @truncate(sum); const overflow = ((accumulator ^ binary_result) & (value ^ binary_result) & 0x80) != 0; const decimal = features.decimal_arithmetic and self.registers.status.decimal; if (decimal and features.cmos_instructions) self.tick(); const out: struct { value: u8, carry: bool } = if (decimal) decimal: { const low_sum = @as(u16, accumulator & 0x0f) + @as(u16, value & 0x0f) + carry; var adjusted = sum; if (low_sum > 9) adjusted += 6; const carry_out = adjusted > 0x99; if (carry_out) adjusted += 0x60; break :decimal .{ .value = @truncate(adjusted), .carry = carry_out, }; } else .{ .value = binary_result, .carry = sum > 0xff, }; self.registers.status.carry = out.carry; self.registers.status.overflow = overflow; self.registers.a = out.value; self.updateNZ(out.value); } fn @"and"(self: *Self, value: u8) void { self.registers.a &= value; self.updateNZ(self.registers.a); } fn eor(self: *Self, value: u8) void { self.registers.a ^= value; self.updateNZ(self.registers.a); } fn ora(self: *Self, value: u8) void { self.registers.a |= value; self.updateNZ(self.registers.a); } fn sbc(self: *Self, value: u8) void { const accumulator = self.registers.a; // SBC subtracts M + (1 - C). const borrow: u16 = if (self.registers.status.carry) 0 else 1; const diff: i16 = @as(i16, accumulator) - @as(i16, value) - @as(i16, @intCast(borrow)); const binary_result: u8 = @truncate(@as(u16, @bitCast(diff))); const overflow = ((accumulator ^ binary_result) & (accumulator ^ value) & 0x80) != 0; const no_borrow = diff >= 0; const decimal = features.decimal_arithmetic and self.registers.status.decimal; if (decimal and features.cmos_instructions) { self.tick(); } const result: u8 = if (decimal) decimal: { var low: i16 = @as(i16, accumulator & 0x0f) - @as(i16, value & 0x0f) - @as(i16, @intCast(borrow)); var high: i16 = @as(i16, accumulator >> 4) - @as(i16, value >> 4); if (low < 0) { low -= 6; high -= 1; } if (high < 0) high -= 6; break :decimal (@as(u8, @intCast(high & 0x0f)) << 4) | @as(u8, @intCast(low & 0x0f)); } else binary_result; self.registers.status.carry = no_borrow; self.registers.status.overflow = overflow; self.registers.a = result; self.updateNZ(result); } // Documented Shifts & Memory Updates fn asl(self: *Self, value: u8) u8 { self.registers.status.carry = value & 0x80 != 0; const result = value << 1; self.updateNZ(result); return result; } fn lsr(self: *Self, value: u8) u8 { self.registers.status.carry = value & 1 != 0; const result = value >> 1; self.updateNZ(result); return result; } fn rol(self: *Self, value: u8) u8 { const carry: u8 = @intFromBool(self.registers.status.carry); self.registers.status.carry = (value & 0x80) != 0; const result = (value << 1) | carry; self.updateNZ(result); return result; } fn ror(self: *Self, value: u8) u8 { const carry: u8 = if (self.registers.status.carry) 0x80 else 0; self.registers.status.carry = value & 1 != 0; const result = (value >> 1) | carry; self.updateNZ(result); return result; } fn dec(self: *Self, value: u8) u8 { const result = value -% 1; self.updateNZ(result); return result; } fn inc(self: *Self, value: u8) u8 { const result = value +% 1; self.updateNZ(result); return result; } // Documented Register Updates fn dex(self: *Self) void { self.registers.x -%= 1; self.updateNZ(self.registers.x); } fn dey(self: *Self) void { self.registers.y -%= 1; self.updateNZ(self.registers.y); } fn inx(self: *Self) void { self.registers.x +%= 1; self.updateNZ(self.registers.x); } fn iny(self: *Self) void { self.registers.y +%= 1; self.updateNZ(self.registers.y); } fn lda(self: *Self, value: u8) void { self.registers.a = value; self.updateNZ(value); } fn ldx(self: *Self, value: u8) void { self.registers.x = value; self.updateNZ(value); } fn ldy(self: *Self, value: u8) void { self.registers.y = value; self.updateNZ(value); } fn tax(self: *Self) void { self.registers.x = self.registers.a; self.updateNZ(self.registers.x); } fn tay(self: *Self) void { self.registers.y = self.registers.a; self.updateNZ(self.registers.y); } fn tsx(self: *Self) void { self.registers.x = self.registers.sp; self.updateNZ(self.registers.x); } fn txa(self: *Self) void { self.registers.a = self.registers.x; self.updateNZ(self.registers.a); } fn txs(self: *Self) void { self.registers.sp = self.registers.x; } fn tya(self: *Self) void { self.registers.a = self.registers.y; self.updateNZ(self.registers.a); } // Documented Flag Operations fn clc(self: *Self) void { self.registers.status.carry = false; } fn cld(self: *Self) void { self.registers.status.decimal = false; } fn cli(self: *Self) void { self.registers.status.interrupt_disable = false; } fn clv(self: *Self) void { self.registers.status.overflow = false; } fn sec(self: *Self) void { self.registers.status.carry = true; } fn sed(self: *Self) void { self.registers.status.decimal = true; } fn sei(self: *Self) void { self.registers.status.interrupt_disable = true; } // Undocumented Read/ALU Operations fn alr(self: *Self, value: u8) void { const result = self.registers.a & value; self.registers.status.carry = (result & 0x01) != 0; self.registers.a = result >> 1; self.updateNZ(self.registers.a); } fn anc(self: *Self, value: u8) void { self.registers.a &= value; self.updateNZ(self.registers.a); self.registers.status.carry = self.registers.status.negative; } fn arr(self: *Self, value: u8) void { const anded = self.registers.a & value; const carry_in: u8 = @as(u8, @intFromBool(self.registers.status.carry)) << 7; const result = (anded >> 1) | carry_in; self.registers.a = result; self.updateNZ(result); self.registers.status.carry = (result & 0x40) != 0; self.registers.status.overflow = ((result >> 6) ^ (result >> 5)) & 1 != 0; } fn axs(self: *Self, value: u8) void { const source = self.registers.a & self.registers.x; const result = source -% value; self.registers.status.carry = source >= value; self.registers.x = result; self.updateNZ(result); } fn las(self: *Self, value: u8) void { const result = value & self.registers.sp; self.registers.a = result; self.registers.x = result; self.registers.sp = result; self.updateNZ(result); } fn lax(self: *Self, value: u8) void { self.registers.a = value; self.registers.x = value; self.updateNZ(value); } fn xaa(self: *Self, value: u8) void { self.registers.a = self.registers.x & value; self.updateNZ(self.registers.a); } // Undocumented RMW Operations fn dcp(self: *Self, value: u8) u8 { const result = value -% 1; self.compare( self.registers.a, result, ); return result; } fn isc(self: *Self, value: u8) u8 { const result = value +% 1; self.sbc(result); return result; } fn rla(self: *Self, value: u8) u8 { const carry_in: u8 = @intFromBool(self.registers.status.carry); self.registers.status.carry = (value & 0x80) != 0; const result = (value << 1) | carry_in; self.registers.a &= result; self.updateNZ(self.registers.a); return result; } fn rra(self: *Self, value: u8) u8 { const carry_in: u8 = @as(u8, @intFromBool(self.registers.status.carry)) << 7; const carry_out = (value & 0x01) != 0; const result = (value >> 1) | carry_in; // ADC uses the carry generated by ROR. self.registers.status.carry = carry_out; self.adc(result); return result; } fn slo(self: *Self, value: u8) u8 { self.registers.status.carry = (value & 0x80) != 0; const result = value << 1; self.registers.a |= result; self.updateNZ(self.registers.a); return result; } fn sre(self: *Self, value: u8) u8 { self.registers.status.carry = (value & 0x01) != 0; const result = value >> 1; self.registers.a ^= result; self.updateNZ(self.registers.a); return result; } // Undocumented Store Operations fn ahx(self: *Self, mode: AddressingMode) void { self.writeHighMasked( mode, self.registers.a & self.registers.x, ); } fn shx(self: *Self, mode: AddressingMode) void { self.writeHighMasked( mode, self.registers.x, ); } fn shy(self: *Self, mode: AddressingMode) void { self.writeHighMasked( mode, self.registers.y, ); } fn tas(self: *Self, mode: AddressingMode) void { self.registers.sp = self.registers.a & self.registers.x; self.writeHighMasked( mode, self.registers.sp, ); } fn writeHighMasked( self: *Self, mode: AddressingMode, source: u8, ) void { switch (mode) { .absolute_x => self.writeHighMaskedAbsolute( self.registers.x, source, ), .absolute_y => self.writeHighMaskedAbsolute( self.registers.y, source, ), .indirect_indexed_y => self.writeHighMaskedIndirectY( source, ), else => unreachable, } } fn writeHighMaskedAbsolute( self: *Self, index: u8, source: u8, ) void { const low = self.fetchByte(); const high = self.fetchByte(); const base = @as(u16, low) | (@as(u16, high) << 8); const address = base +% index; const dummy = (base & 0xff00) | (address & 0x00ff); _ = self.readCycle(dummy); const value = source & (high +% 1); const write_address = if (pageCrossed(base, address)) (@as(u16, value) << 8) | (address & 0x00ff) else address; self.writeCycle( write_address, value, ); } fn writeHighMaskedIndirectY( self: *Self, source: u8, ) void { const pointer = self.fetchByte(); const low = self.readCycle(pointer); const high = self.readCycle(pointer +% 1); const base = @as(u16, low) | (@as(u16, high) << 8); const address = base +% self.registers.y; const dummy = (base & 0xff00) | (address & 0x00ff); _ = self.readCycle(dummy); const value = source & (high +% 1); const write_address = if (pageCrossed(base, address)) (@as(u16, value) << 8) | (address & 0x00ff) else address; self.writeCycle( write_address, value, ); } // Jammed State fn kil(self: *Self) void { // Enter the jammed state after the opcode fetch. _ = self.readCycle(self.registers.pc); self.state = .jammed; } fn rmb(self: *Self, bit: u3) void { self.modifyMemoryBit(bit, false); } fn smb(self: *Self, bit: u3) void { self.modifyMemoryBit(bit, true); } fn bitBranch(self: *Self, bit: u3, expected_bit: bool) void { const address = self.fetchByte(); const value = self.readCycle(address); const offset: i8 = @bitCast(self.fetchByte()); const bit_is_set = (value & (@as(u8, 1) << bit)) != 0; if (bit_is_set == expected_bit) { const old_pc = self.registers.pc; const new_pc = addRelative(old_pc, offset); _ = self.readCycle(old_pc); if (pageCrossed(old_pc, new_pc)) { const dummy = (old_pc & 0xff00) | (new_pc & 0x00ff); _ = self.readCycle(dummy); } self.registers.pc = new_pc; } } fn bbr(self: *Self, bit: u3) void { self.bitBranch(bit, false); } fn bbs(self: *Self, bit: u3) void { self.bitBranch(bit, true); } fn modifyMemoryBit( self: *Self, bit: u3, set: bool, ) void { const address = self.fetchByte(); const old = self.readCycle(address); // W65C02 RMW uses a second read rather than // the NMOS dummy write. _ = self.readCycle(address); const mask: u8 = @as(u8, 1) << bit; const new = if (set) old | mask else old & ~mask; self.writeCycle(address, new); } fn phx(self: *Self) void { _ = self.readCycle(self.registers.pc); self.push(self.registers.x); } fn phy(self: *Self) void { _ = self.readCycle(self.registers.pc); self.push(self.registers.y); } fn plx(self: *Self) void { _ = self.readCycle(self.registers.pc); _ = self.readCycle( 0x0100 | @as(u16, self.registers.sp), ); self.registers.x = self.pop(); self.updateNZ(self.registers.x); } fn ply(self: *Self) void { _ = self.readCycle(self.registers.pc); _ = self.readCycle( 0x0100 | @as(u16, self.registers.sp), ); self.registers.y = self.pop(); self.updateNZ(self.registers.y); } fn trb(self: *Self, value: u8) u8 { self.registers.status.zero = (self.registers.a & value) == 0; return value & ~self.registers.a; } fn tsb(self: *Self, value: u8) u8 { self.registers.status.zero = (self.registers.a & value) == 0; return value | self.registers.a; } fn wai(self: *Self) void { _ = self.readCycle(self.registers.pc); _ = self.readCycle(self.registers.pc); if (features.wait_and_stop) { self.state = .waiting; } } fn stp(self: *Self) void { _ = self.readCycle(self.registers.pc); _ = self.readCycle(self.registers.pc); if (features.wait_and_stop) { self.state = .stopped; } } // Draft tracing helper for debugging initial execution loop pub fn debugTraceStep(self: *Self) void { const std = @import("std"); std.debug.print("CPU Step: PC={X:04} A={X:02} X={X:02} Y={X:02}\n", .{ self.registers.pc, self.registers.a, self.registers.x, self.registers.y, }); } }; }