Files
6soz/src/cpu/m6502/root.zig
T

1491 lines
44 KiB
Zig

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,
});
}
};
}