diff --git a/src/cpu/m6502/root.zig b/src/cpu/m6502/root.zig new file mode 100644 index 0000000..0bd4fcf --- /dev/null +++ b/src/cpu/m6502/root.zig @@ -0,0 +1,1490 @@ +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, + }); + } + }; +} + + + diff --git a/src/main.zig b/src/main.zig index 8491c0b..bbb7b6e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1 @@ -const std = @import("std"); - -pub fn main() !void { - std.debug.print("6soz 6502 emulator\n", .{}); -} +pub fn main() !void {}