//! The NES's 6502-based CPU core. const Cpu = @This(); const std = @import("std"); const zesty = @import("zesty.zig"); const log = std.log.scoped(.cpu); //------------------------------------------ // Registers a: u8 = 0, x: u8 = 0, y: u8 = 0, sp: u8 = 0, pc: u16 = 0x00ff, status: Status = .{}, /// Temporary storage of the target address. /// Used by complex addressing modes. hilo: Addr = .{}, /// The currently executing opcode. opcode: u8 = 0, /// The execution cycle of the opcode. /// At most 7. cycle: u3 = 0, // Not exposed on the 2A03 chip /// Sync pin (new instruction) sync: bool = false, in_reset: bool = false, pub fn tick(self: *Cpu, pins: *zesty.Pins, last_pins: zesty.Pins) void { _ = last_pins; // TODO: emulate M2 duty cycle pins.cpu_m2 = pins.cpu_clk; if (!pins.cpu_clk) return; defer self.cycle +|= 1; // *Most* cycles are reads. pins.cpu_rw = .read; if (pins.cpu_rst) { pins.cpu_rst = false; self.in_reset = true; self.cycle = 0; } if (self.in_reset) { // Exact cycle-by-cycle breakdown: https://www.pagetable.com/?p=410 switch (self.cycle) { // First three cycles do essentially nothing. 0, 1 => { self.sp = 0; self.opcode = 0; }, // Fake stack push; note that SP does not change. 2 => pins.cpu_addr = 0x0100, // Fake stack push 3 => pins.cpu_addr = 0x01ff, 4 => pins.cpu_addr = 0x01fe, 5 => { self.sp = 0xfd; pins.cpu_addr = 0xfffc; }, 6 => { self.hilo.lo = pins.cpu_data; pins.cpu_addr = 0xfffd; }, else => { self.hilo.hi = pins.cpu_data; self.pc = self.hilo.addr(); pins.cpu_addr = self.pc; // IRQ is always disabled after every reset self.status.irq_disabled = true; self.in_reset = false; self.sync = true; }, } return; } if (self.sync) { self.opcode = pins.cpu_data; self.cycle = 0; self.sync = false; } // if (self.sync or pins.cpu_irq or pins.cpu_nmi or pins.cpu_rst) { // // NMI is triggered by a edge detector // if (!last_pins.cpu_nmi and pins.cpu_nmi) { // // TODO: handle NMI // } // // IRQ is detected by a level detector // if (pins.cpu_irq and !self.status.irq_disabled) { // // TODO: handle IRQ // } // } // log.err("BOBER={x}", .{self.opcode}); @setEvalBranchQuota(2000); const v = pins.cpu_data; switch (self.opcode) { 0x00 => self.brk(pins), // BRK 0x01 => if (self.zpXInd(pins)) |_| self.ora(pins, v), // ORA (zp,X) 0x02 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0x03 => if (self.zpXInd(pins)) |c| self.slo(pins, c), // *SLO (zp,X) 0x04 => if (self.zp(pins)) |_| self.fetch(pins), // *NOP zp 0x05 => if (self.zp(pins)) |_| self.ora(pins, v), // ORA zp 0x06 => if (self.zp(pins)) |c| self.asl(pins, c), // ASL zp 0x07 => if (self.zp(pins)) |c| self.slo(pins, c), // *SLO zp 0x08 => self.php(pins), // PHP 0x09 => if (self.imm(pins)) |_| self.ora(pins, v), // ORA # 0x0a => if (self.imp(pins)) |_| self.asla(pins), // ASL A 0x0b => if (self.imm(pins)) |_| self.anc(pins), // *ANC # 0x0c => if (self.abs(pins)) |_| self.fetch(pins), // *NOP abs 0x0d => if (self.abs(pins)) |_| self.ora(pins, v), // ORA abs 0x0e => if (self.abs(pins)) |c| self.asl(pins, c), // ASL abs 0x0f => if (self.abs(pins)) |c| self.slo(pins, c), // *SLO abs 0x10 => if (self.imm(pins)) |_| self.branch(pins, !self.status.negative), // BPL # 0x11 => if (self.zpIndY(pins)) |_| self.ora(pins, v), // ORA (zp),Y 0x12 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0x13 => if (self.zpIndY(pins)) |c| self.slo(pins, c), // *SLO (zp),Y 0x14 => if (self.zpOff(pins, self.x)) |_| self.fetch(pins), // *NOP zp,X 0x15 => if (self.zpOff(pins, self.x)) |_| self.ora(pins, v), // ORA zp,X 0x16 => if (self.zpOff(pins, self.x)) |c| self.asl(pins, c), // ASL zp,X 0x17 => if (self.zpOff(pins, self.x)) |c| self.slo(pins, c), // *SLO zp,X 0x18 => if (self.imp(pins)) |_| self.set(pins, .carry, false), // CLC 0x19 => if (self.absOff(pins, self.y)) |_| self.ora(pins, v), // ORA abs,Y 0x1a => if (self.imp(pins)) |_| self.fetch(pins), // *NOP 0x1b => if (self.absOff(pins, self.y)) |c| self.slo(pins, c), // *SLO abs,Y 0x1c => if (self.absOff(pins, self.x)) |_| self.fetch(pins), // *NOP abs,X 0x1d => if (self.absOff(pins, self.x)) |_| self.ora(pins, v), // ORA abs,X 0x1e => if (self.absOff(pins, self.x)) |c| self.asl(pins, c), // ASL abs,X 0x1f => if (self.absOff(pins, self.x)) |c| self.slo(pins, c), // *SLO abs,X 0x20 => self.jsr(pins), // JSR abs 0x21 => if (self.zpXInd(pins)) |_| self._and(pins, v), // AND (zp,X) 0x22 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0x23 => if (self.zpXInd(pins)) |c| self.rla(pins, c), // *RLA (zp,X) 0x24 => if (self.zp(pins)) |_| self.bit(pins), // BIT zp 0x25 => if (self.zp(pins)) |_| self._and(pins, v), // AND zp 0x26 => if (self.zp(pins)) |c| self.rol(pins, c), // ROL zp 0x27 => if (self.zp(pins)) |c| self.rla(pins, c), // *RLA zp 0x28 => self.plp(pins), // PLP 0x29 => if (self.imm(pins)) |_| self._and(pins, v), // AND # 0x2a => if (self.imp(pins)) |_| self.rola(pins), // ROL A 0x2b => if (self.imm(pins)) |_| self.anc(pins), // *ANC # 0x2c => if (self.abs(pins)) |_| self.bit(pins), // BIT abs 0x2d => if (self.abs(pins)) |_| self._and(pins, v), // AND abs 0x2e => if (self.abs(pins)) |c| self.rol(pins, c), // ROL abs 0x2f => if (self.abs(pins)) |c| self.rla(pins, c), // *RLA abs 0x30 => if (self.imm(pins)) |_| self.branch(pins, self.status.negative), // BMI # 0x31 => if (self.zpIndY(pins)) |_| self._and(pins, v), // AND (zp),Y 0x32 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0x33 => if (self.zpIndY(pins)) |c| self.rla(pins, c), // *RLA (zp),Y 0x34 => if (self.zpOff(pins, self.x)) |_| self.fetch(pins), // *NOP zp,X 0x35 => if (self.zpOff(pins, self.x)) |_| self._and(pins, v), // AND zp,X 0x36 => if (self.zpOff(pins, self.x)) |c| self.rol(pins, c), // ROL zp,X 0x37 => if (self.zpOff(pins, self.x)) |c| self.rla(pins, c), // *RLA zp,X 0x38 => if (self.imp(pins)) |_| self.set(pins, .carry, true), // SEC 0x39 => if (self.absOff(pins, self.y)) |_| self._and(pins, v), // AND abs,Y 0x3a => if (self.imp(pins)) |_| self.fetch(pins), // *NOP 0x3b => if (self.absOff(pins, self.y)) |c| self.rla(pins, c), // *RLA abs,Y 0x3c => if (self.absOff(pins, self.x)) |_| self.fetch(pins), // *NOP abs,X 0x3d => if (self.absOff(pins, self.x)) |_| self._and(pins, v), // AND abs,X 0x3e => if (self.absOff(pins, self.x)) |c| self.rol(pins, c), // ROL abs,X 0x3f => if (self.absOff(pins, self.x)) |c| self.rla(pins, c), // *RLA abs,X 0x40 => self.rti(pins), // RTI 0x41 => if (self.zpXInd(pins)) |_| self.eor(pins, v), // EOR (zp,X) 0x42 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0x43 => if (self.zpXInd(pins)) |c| self.sre(pins, c), // *SRE (zp,X) 0x44 => if (self.zp(pins)) |_| self.fetch(pins), // *NOP zp 0x45 => if (self.zp(pins)) |_| self.eor(pins, v), // EOR zp 0x46 => if (self.zp(pins)) |c| self.lsr(pins, c), // LSR zp 0x47 => if (self.zp(pins)) |c| self.sre(pins, c), // *SRE zp 0x48 => self.pha(pins), // PHA 0x49 => if (self.imm(pins)) |_| self.eor(pins, v), // EOR # 0x4a => if (self.imp(pins)) |_| self.lsra(pins), // LSR A 0x4b => if (self.imm(pins)) |_| self.alr(pins), // *ALR # 0x4c => self.jmp(pins), // JMP abs 0x4d => if (self.abs(pins)) |_| self.eor(pins, v), // EOR abs 0x4e => if (self.abs(pins)) |c| self.lsr(pins, c), // LSR abs 0x4f => if (self.abs(pins)) |c| self.sre(pins, c), // *SRE abs 0x50 => if (self.imp(pins)) |_| self.branch(pins, !self.status.overflow), // BVC # 0x51 => if (self.zpIndY(pins)) |_| self.eor(pins, v), // EOR (zp),Y 0x52 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0x53 => if (self.zpIndY(pins)) |c| self.sre(pins, c), // *SRE (zp),Y 0x54 => if (self.zpOff(pins, self.x)) |_| self.fetch(pins), // *NOP zp,X 0x55 => if (self.zpOff(pins, self.x)) |_| self.eor(pins, v), // EOR zp,X 0x56 => if (self.zpOff(pins, self.x)) |c| self.lsr(pins, c), // LSR zp,X 0x57 => if (self.zpOff(pins, self.x)) |c| self.sre(pins, c), // *SRE zp,X 0x58 => if (self.imp(pins)) |_| self.set(pins, .irq_disabled, false), // CLI 0x59 => if (self.absOff(pins, self.y)) |_| self.eor(pins, v), // EOR abs,Y 0x5a => if (self.imp(pins)) |_| self.fetch(pins), // *NOP 0x5b => if (self.absOff(pins, self.y)) |c| self.sre(pins, c), // *SRE abs,Y 0x5c => if (self.absOff(pins, self.x)) |_| self.fetch(pins), // *NOP abs,X 0x5d => if (self.absOff(pins, self.x)) |_| self.eor(pins, v), // EOR abs,X 0x5e => if (self.absOff(pins, self.x)) |c| self.lsr(pins, c), // LSR abs,X 0x5f => if (self.absOff(pins, self.x)) |c| self.sre(pins, c), // *SRE abs,X 0x60 => self.rts(pins), // RTS 0x61 => if (self.zpXInd(pins)) |_| self.adc(pins, v), // ADC (zp,X) 0x62 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0x63 => if (self.zpXInd(pins)) |c| self.rra(pins, c), // *RRA (zp,X) 0x64 => if (self.zp(pins)) |_| self.fetch(pins), // *NOP zp 0x65 => if (self.zp(pins)) |_| self.adc(pins, v), // ADC zp 0x66 => if (self.zp(pins)) |c| self.ror(pins, c), // ROR zp 0x67 => if (self.zp(pins)) |c| self.rra(pins, c), // *RRA zp 0x68 => self.pla(pins), // PLA 0x69 => if (self.imm(pins)) |_| self.adc(pins, v), // ADC # 0x6a => if (self.imp(pins)) |_| self.rora(pins), // ROR A 0x6b => if (self.imm(pins)) |_| self.arr(pins), // *ARR # 0x6c => self.jmpInd(pins), // JMP (ind) 0x6d => if (self.abs(pins)) |_| self.adc(pins, v), // ADC abs 0x6e => if (self.abs(pins)) |c| self.ror(pins, c), // ROR abs 0x6f => if (self.abs(pins)) |c| self.rra(pins, c), // *RRA abs 0x70 => if (self.imm(pins)) |_| self.branch(pins, self.status.overflow), // BVS # 0x71 => if (self.zpIndY(pins)) |_| self.adc(pins, v), // ADC (zp),Y 0x72 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0x73 => if (self.zpIndY(pins)) |c| self.rra(pins, c), // *RRA (zp),Y 0x74 => if (self.zpOff(pins, self.x)) |_| self.fetch(pins), // *NOP zp,X 0x75 => if (self.zpOff(pins, self.x)) |_| self.adc(pins, v), // ADC zp,X 0x76 => if (self.zpOff(pins, self.x)) |c| self.ror(pins, c), // ROR zp,X 0x77 => if (self.zpOff(pins, self.x)) |c| self.rra(pins, c), // *RRA zp,X 0x78 => if (self.imp(pins)) |_| self.set(pins, .irq_disabled, true), // SEI 0x79 => if (self.absOff(pins, self.y)) |_| self.adc(pins, v), // ADC abs,Y 0x7a => if (self.imp(pins)) |_| self.fetch(pins), // *NOP 0x7b => if (self.absOff(pins, self.y)) |c| self.rra(pins, c), // *RRA abs,Y 0x7c => if (self.absOff(pins, self.x)) |_| self.fetch(pins), // *NOP abs,X 0x7d => if (self.absOff(pins, self.x)) |_| self.adc(pins, v), // ADC abs,X 0x7e => if (self.absOff(pins, self.x)) |c| self.ror(pins, c), // ROR abs,X 0x7f => if (self.absOff(pins, self.x)) |c| self.rra(pins, c), // *RRA abs,X 0x80 => if (self.imm(pins)) |_| self.fetch(pins), // *NOP imm 0x81 => if (self.zpXInd(pins)) |_| self.st(pins, self.y), // STA (zp,X) 0x82 => if (self.imm(pins)) |_| self.fetch(pins), // *NOP imm 0x83 => if (self.zpXInd(pins)) |c| self.sax(pins, c), // *SAX (zp,X) 0x84 => if (self.zp(pins)) |_| self.st(pins, self.y), // STY zp 0x85 => if (self.zp(pins)) |_| self.st(pins, self.a), // STA zp 0x86 => if (self.zp(pins)) |_| self.st(pins, self.x), // STX zp 0x87 => if (self.zp(pins)) |c| self.sax(pins, c), // *SAX zp 0x88 => if (self.imp(pins)) |_| self.dexy(pins, &self.y), // DEY 0x89 => if (self.imm(pins)) |_| self.fetch(pins), // *NOP imm 0x8a => if (self.imp(pins)) |_| self.ld(pins, &self.a, self.x), // TXA 0x8b => if (self.imm(pins)) |_| self.ane(pins), // *ANE # 0x8c => if (self.abs(pins)) |_| self.st(pins, self.y), // STY abs 0x8d => if (self.abs(pins)) |_| self.st(pins, self.a), // STA abs 0x8e => if (self.abs(pins)) |_| self.st(pins, self.x), // STX abs 0x8f => if (self.abs(pins)) |c| self.sax(pins, c), // *SAX abs 0x90 => if (self.imm(pins)) |_| self.branch(pins, !self.status.carry), // BCC # 0x91 => if (self.zpIndY(pins)) |_| self.ld(pins, &self.a, v), // STA (zp),Y 0x92 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0x93 => if (self.zpIndY(pins)) |c| self.a11(pins, self.a & self.x, c), // *SHA (zp,Y) 0x94 => if (self.zpOff(pins, self.x)) |_| self.st(pins, self.y), // STY zp,X 0x95 => if (self.zpOff(pins, self.x)) |_| self.st(pins, self.a), // STA zp,X 0x96 => if (self.zpOff(pins, self.y)) |_| self.st(pins, self.x), // STX zp,Y 0x97 => if (self.zpOff(pins, self.y)) |c| self.sax(pins, c), // *SAX zp,Y 0x99 => if (self.absOff(pins, self.y)) |_| self.st(pins, self.a), // STA abs,Y 0x9a => if (self.imp(pins)) |_| self.ld(pins, &self.sp, self.x), // TXS 0x9b => if (self.absOff(pins, self.y)) |c| self.tas(pins, c), // *TAS abs,Y 0x9c => if (self.absOff(pins, self.x)) |c| self.a11(pins, self.y, c), // *SHY abs,X 0x9d => if (self.absOff(pins, self.x)) |_| self.st(pins, self.a), // STA abs,X 0x9e => if (self.absOff(pins, self.y)) |c| self.a11(pins, self.x, c), // *SHX abs,Y 0x9f => if (self.absOff(pins, self.y)) |c| self.a11(pins, self.a & self.x, c), // *SHA abs,Y 0xa0 => if (self.imm(pins)) |_| self.ld(pins, &self.y, v), // LDY # 0xa1 => if (self.zpXInd(pins)) |_| self.ld(pins, &self.y, v), // LDA (zp,X) 0xa2 => if (self.imm(pins)) |_| self.ld(pins, &self.x, v), // LDX # 0xa3 => if (self.zpXInd(pins)) |c| self.dcp(pins, c), // *DCP (zp,X) 0xa4 => if (self.zp(pins)) |_| self.ld(pins, &self.y, v), // LDY zp 0xa5 => if (self.zp(pins)) |_| self.ld(pins, &self.a, v), // LDA zp 0xa6 => if (self.zp(pins)) |_| self.ld(pins, &self.x, v), // LDX zp 0xa7 => if (self.zp(pins)) |c| self.dcp(pins, c), // *DCP zp 0xa8 => if (self.imp(pins)) |_| self.ld(pins, &self.y, self.a), // TAY 0xa9 => if (self.imm(pins)) |_| self.ld(pins, &self.a, v), // LDA # 0xaa => if (self.imp(pins)) |_| self.ld(pins, &self.x, self.a), // TAX 0xab => if (self.imm(pins)) |_| self.lxa(pins), // *LXA # 0xac => if (self.abs(pins)) |_| self.ld(pins, &self.y, v), // LDY abs 0xad => if (self.abs(pins)) |_| self.ld(pins, &self.a, v), // LDA abs 0xae => if (self.abs(pins)) |_| self.ld(pins, &self.x, v), // LDX abs 0xaf => if (self.abs(pins)) |c| self.dcp(pins, c), // *DCP abs 0xb0 => if (self.imm(pins)) |_| self.branch(pins, self.status.carry), // BCS # 0xb1 => if (self.zpIndY(pins)) |_| self.ld(pins, &self.a, v), // LDA (zp),Y 0xb2 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0xb3 => if (self.zpIndY(pins)) |c| self.dcp(pins, c), // *DCP (zp),Y 0xb4 => if (self.zpOff(pins, self.x)) |_| self.ld(pins, &self.y, v), // LDY zp,X 0xb5 => if (self.zpOff(pins, self.x)) |_| self.ld(pins, &self.a, v), // LDA zp,X 0xb6 => if (self.zpOff(pins, self.y)) |_| self.ld(pins, &self.x, v), // LDX zp,Y 0xb7 => if (self.zpOff(pins, self.y)) |c| self.dcp(pins, c), // *DCP zp,Y 0xb8 => if (self.imp(pins)) |_| self.set(pins, .overflow, true), // SEV 0xb9 => if (self.absOff(pins, self.y)) |_| self.ld(pins, &self.a, v), // LDA abs,Y 0xba => if (self.imp(pins)) |_| self.ld(pins, &self.x, self.sp), // TSX 0xbb => if (self.absOff(pins, self.y)) |_| self.las(pins), // *LAS abs,Y 0xbc => if (self.absOff(pins, self.x)) |_| self.ld(pins, &self.y, v), // LDY abs,X 0xbd => if (self.absOff(pins, self.x)) |_| self.ld(pins, &self.a, v), // LDA abs,X 0xbe => if (self.absOff(pins, self.y)) |_| self.ld(pins, &self.x, v), // LDX abs,Y 0xbf => if (self.absOff(pins, self.y)) |c| self.dcp(pins, c), // *DCP abs,Y 0xc0 => if (self.imm(pins)) |_| self.cmp(pins, self.y), // CPY # 0xc1 => if (self.zpXInd(pins)) |_| self.cmp(pins, self.a), // CMP (zp,X) 0xc2 => if (self.imm(pins)) |_| self.fetch(pins), // *NOP imm 0xc3 => if (self.zpXInd(pins)) |c| self.dcp(pins, c), // *DCP (zp,X) 0xc4 => if (self.zp(pins)) |_| self.cmp(pins, self.y), // CPY zp 0xc5 => if (self.zp(pins)) |_| self.cmp(pins, self.a), // CMP zp 0xc6 => if (self.zp(pins)) |c| self.dec(pins, c), // DEC zp 0xc7 => if (self.zp(pins)) |c| self.dcp(pins, c), // *DCP zp 0xc8 => if (self.imp(pins)) |_| self.inxy(pins, &self.y), // INY 0xc9 => if (self.imm(pins)) |_| self.cmp(pins, self.a), // CMP # 0xca => if (self.imp(pins)) |_| self.dexy(pins, &self.x), // DEX 0xcc => if (self.abs(pins)) |_| self.cmp(pins, self.y), // CPY abs 0xcb => if (self.imm(pins)) |_| self.las(pins), // *SBX # 0xcd => if (self.abs(pins)) |_| self.cmp(pins, self.a), // CMP abs 0xce => if (self.abs(pins)) |c| self.dec(pins, c), // DEC abs 0xcf => if (self.abs(pins)) |c| self.dcp(pins, c), // *DCP abs 0xd0 => if (self.imm(pins)) |_| self.branch(pins, !self.status.zero), // BNE # 0xd1 => if (self.zpIndY(pins)) |_| self.cmp(pins, self.a), // CMP (zp),Y 0xd2 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0xd3 => if (self.zpIndY(pins)) |c| self.dcp(pins, c), // *DCP (zp),Y 0xd4 => if (self.zpOff(pins, self.x)) |_| self.fetch(pins), // *NOP zp,X 0xd5 => if (self.zpOff(pins, self.x)) |_| self.cmp(pins, self.a), // CMP zp,X 0xd6 => if (self.zpOff(pins, self.x)) |c| self.dec(pins, c), // DEC zp,X 0xd7 => if (self.zpOff(pins, self.x)) |c| self.dcp(pins, c), // *DCP zp,X 0xd8 => if (self.imp(pins)) |_| self.set(pins, .decimal, false), // CLD 0xd9 => if (self.absOff(pins, self.y)) |_| self.cmp(pins, self.a), // CMP abs,Y 0xda => if (self.imp(pins)) |_| self.fetch(pins), // *NOP 0xdb => if (self.absOff(pins, self.y)) |c| self.dcp(pins, c), // *DCP abs,Y 0xdc => if (self.absOff(pins, self.x)) |_| self.fetch(pins), // *NOP abs,X 0xdd => if (self.absOff(pins, self.x)) |_| self.cmp(pins, self.a), // CMP abs,X 0xde => if (self.absOff(pins, self.x)) |c| self.dec(pins, c), // DEC abs,X 0xdf => if (self.absOff(pins, self.x)) |c| self.dcp(pins, c), // *DCP abs,X 0xe0 => if (self.imm(pins)) |_| self.cmp(pins, self.x), // CPX # 0xe1 => if (self.zpXInd(pins)) |_| self.sbc(pins, v), // SBC (zp,X) 0xe2 => if (self.imm(pins)) |_| self.fetch(pins), // *NOP imm 0xe3 => if (self.zpXInd(pins)) |c| self.isc(pins, c), // *ISC (zp,X) 0xe4 => if (self.zp(pins)) |_| self.cmp(pins, self.x), // CPX zp 0xe5 => if (self.zp(pins)) |_| self.sbc(pins, v), // SBC zp 0xe6 => if (self.zp(pins)) |c| self.inc(pins, c), // INC zp 0xe7 => if (self.zp(pins)) |c| self.isc(pins, c), // *ISC zp 0xe8 => if (self.imp(pins)) |_| self.inxy(pins, &self.x), // INX 0xe9 => if (self.imm(pins)) |_| self.sbc(pins, v), // SBC # 0xea => if (self.imp(pins)) |_| self.fetch(pins), // NOP 0xeb => if (self.imm(pins)) |_| self.sbc(pins, v), // *SBC # 0xec => if (self.abs(pins)) |_| self.cmp(pins, self.x), // CPX abs 0xed => if (self.abs(pins)) |_| self.sbc(pins, v), // SBC abs 0xee => if (self.abs(pins)) |c| self.inc(pins, c), // INC abs 0xef => if (self.abs(pins)) |c| self.isc(pins, c), // *ISC abs 0xf0 => if (self.imm(pins)) |_| self.branch(pins, self.status.zero), // BEQ # 0xf1 => if (self.zpIndY(pins)) |_| self.sbc(pins, v), // SBC (zp),Y 0xf2 => if (self.imp(pins)) |_| self.hlt(pins), // *HLT 0xf3 => if (self.zpIndY(pins)) |c| self.isc(pins, c), // *ISC (zp),Y 0xf4 => if (self.zpOff(pins, self.x)) |_| self.fetch(pins), // *NOP zp,X 0xf5 => if (self.zpOff(pins, self.x)) |_| self.sbc(pins, v), // SBC zp,X 0xf6 => if (self.zpOff(pins, self.x)) |c| self.inc(pins, c), // INC zp,X 0xf7 => if (self.zpOff(pins, self.x)) |c| self.isc(pins, c), // *ISC zp,X 0xf8 => if (self.imp(pins)) |_| self.set(pins, .decimal, true), // SED 0xf9 => if (self.absOff(pins, self.y)) |_| self.sbc(pins, v), // SBC abs,Y 0xfa => if (self.imp(pins)) |_| self.fetch(pins), // *NOP 0xfb => if (self.absOff(pins, self.y)) |c| self.isc(pins, c), // *ISC abs,Y 0xfc => if (self.absOff(pins, self.x)) |_| self.fetch(pins), // *NOP abs,X 0xfd => if (self.absOff(pins, self.x)) |_| self.sbc(pins, v), // SBC abs,X 0xfe => if (self.absOff(pins, self.x)) |c| self.inc(pins, c), // INC abs,X 0xff => if (self.absOff(pins, self.x)) |c| self.isc(pins, c), // *ISC abs,X else => unreachable, } } inline fn imp(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { pins.cpu_addr = self.pc; }, else => return self.cycle - 1, } return null; } inline fn imm(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, else => return self.cycle - 1, } return null; } inline fn zp(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, 1 => { self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_addr = self.hilo.addr(); }, else => return self.cycle - 2, } return null; } inline fn zpOff(self: *Cpu, pins: *zesty.Pins, v: u8) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, 1 => { self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_addr = self.hilo.addr(); }, 2 => { self.hilo.lo +%= v; pins.cpu_addr = self.hilo.addr(); }, else => return self.cycle - 3, } return null; } inline fn abs(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, 1 => { self.pc +%= 1; pins.cpu_addr = self.pc; self.hilo = .{ .lo = pins.cpu_data }; }, 2 => { self.hilo.hi = pins.cpu_data; pins.cpu_addr = self.hilo.addr(); }, else => return self.cycle - 3, } return null; } inline fn absOff(self: *Cpu, pins: *zesty.Pins, v: u8) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, 1 => { self.pc +%= 1; self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_addr = self.pc; }, 2 => { self.hilo.hi = pins.cpu_data; pins.cpu_addr, const page_crossed = self.hilo.offsetWithPageFaultBehavior(v, false); if (!page_crossed) self.cycle += 1; }, 3 => { pins.cpu_addr = self.hilo.addr(); }, else => return self.cycle - 4, } return null; } inline fn zpXInd(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, 1 => { self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_addr +%= 1; }, 2 => { self.hilo.lo +%= self.x; pins.cpu_addr = self.hilo.addr(); }, 3 => { self.hilo.lo +%= 1; pins.cpu_addr = self.hilo.addr(); self.hilo = .{ .lo = pins.cpu_data }; }, 4 => { self.hilo.hi = pins.cpu_data; pins.cpu_addr = self.hilo.addr(); }, else => return self.cycle - 5, } return null; } inline fn zpIndY(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, 1 => { pins.cpu_addr = pins.cpu_data; }, 2 => { self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_addr +%= 1; }, 3 => { self.hilo.hi = pins.cpu_data; }, 4 => { pins.cpu_addr = self.hilo.addr(); }, else => return self.cycle - 5, } return null; } inline fn fetch(self: *Cpu, pins: *zesty.Pins) void { self.fetchAt(pins, self.pc +% 1); } inline fn fetchAt(self: *Cpu, pins: *zesty.Pins, pc: u16) void { self.pc = pc; pins.cpu_addr = pc; self.sync = true; } //------------------------------------------------------ // Opcodes: Load/Store inline fn ld(self: *Cpu, pins: *zesty.Pins, to: *u8, from: u8) void { to.* = from; self.status.setNZ(to.*); self.fetch(pins); } inline fn st(self: *Cpu, pins: *zesty.Pins, from: u8) void { pins.cpu_data = from; pins.cpu_rw = .write; self.fetch(pins); } inline fn push(self: *Cpu, pins: *zesty.Pins, v: u8) void { switch (self.cycle) { // Dummy read 0 => pins.cpu_addr = self.pc, 1 => { self.hilo = .stack(self.sp); pins.cpu_addr = self.hilo.addr(); pins.cpu_data = v; pins.cpu_rw = .write; self.sp -%= 1; }, else => self.fetch(pins), } } inline fn pull(self: *Cpu, pins: *zesty.Pins) ?u8 { switch (self.cycle) { // Dummy read 0 => pins.cpu_addr = self.pc, 1 => { self.hilo = .stack(self.sp); pins.cpu_addr = self.hilo.addr(); self.sp +%= 1; }, 2 => { self.hilo = .stack(self.sp); pins.cpu_addr = self.hilo.addr(); }, else => { self.fetch(pins); return pins.cpu_data; }, } return null; } inline fn pha(self: *Cpu, pins: *zesty.Pins) void { self.push(pins, self.a); } inline fn php(self: *Cpu, pins: *zesty.Pins) void { var status = self.status; // BRK is always set to true here status.brk = true; self.push(pins, status.toByte()); } inline fn pla(self: *Cpu, pins: *zesty.Pins) void { self.a = self.pull(pins) orelse return; self.status.setNZ(self.a); } inline fn plp(self: *Cpu, pins: *zesty.Pins) void { const status: Status = .from(self.pull(pins) orelse return); self.status = .{ .negative = status.negative, .overflow = status.overflow, .brk = self.status.brk, // Do not inherit BRK. .decimal = status.decimal, .irq_disabled = status.irq_disabled, .zero = status.zero, .carry = status.carry, }; } //------------------------------------------------------ // Opcodes: Arithmetic inline fn _adc(a: u8, b: u8, status: *Status) u8 { var result, var carry = @addWithOverflow(a, @intFromBool(status.carry)); // TODO: implement optional support for decimal mode result, carry = @addWithOverflow(result, b); status.carry = carry > 0; status.setNZ(result); // Overflow bit is set if both inputs have the same sign, // and the output has a different sign status.overflow = ~(a ^ b) & (a ^ result) & 0x80 > 0; return result; } inline fn _sbc(a: u8, b: u8, status: *Status) u8 { return _adc(a, ~b, status); } inline fn _cmp(a: u8, b: u8, status: *Status) u8 { // a CMP is basically a SBC in disguise const result, const carry = @addWithOverflow(a, ~b +% 1); status.carry = carry > 0; status.setNZ(result); return result; } inline fn _asl(a: u8, comptime rotate: bool, status: *Status) u8 { var v, const carry = @shlWithOverflow(a, 1); if (rotate) v |= @intFromBool(status.carry); status.carry = carry > 0; status.setNZ(v); return v; } inline fn _lsr(a: u8, comptime rotate: bool, status: *Status) u8 { // There's no @shrWithOverflow :( var v = a >> 1; if (rotate and status.carry) v |= 0x80; status.carry = a & 1 > 0; status.setNZ(v); return v; } inline fn ora(self: *Cpu, pins: *zesty.Pins, v: u8) void { self.a |= v; self.status.setNZ(self.a); self.fetch(pins); } inline fn _and(self: *Cpu, pins: *zesty.Pins, v: u8) void { self.a &= v; self.status.setNZ(self.a); self.fetch(pins); } inline fn eor(self: *Cpu, pins: *zesty.Pins, v: u8) void { self.a ^= v; self.status.setNZ(self.a); self.fetch(pins); } inline fn asl(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { pins.cpu_data = _asl(pins.cpu_data, false, &self.status); pins.cpu_rw = .write; }, else => self.fetch(pins), } } inline fn rol(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { pins.cpu_data = _asl(pins.cpu_data, true, &self.status); pins.cpu_rw = .write; }, else => self.fetch(pins), } } inline fn lsr(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { pins.cpu_data = _lsr(pins.cpu_data, false, &self.status); self.status.setNZ(pins.cpu_data); pins.cpu_rw = .write; }, else => self.fetch(pins), } } inline fn ror(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { pins.cpu_data = _lsr(pins.cpu_data, true, &self.status); self.status.setNZ(pins.cpu_data); pins.cpu_rw = .write; }, else => self.fetch(pins), } } inline fn asla(self: *Cpu, pins: *zesty.Pins) void { self.a = _asl(self.a, false, &self.status); self.status.setNZ(self.a); self.fetch(pins); } inline fn rola(self: *Cpu, pins: *zesty.Pins) void { self.a = _asl(self.a, true, &self.status); self.status.setNZ(self.a); self.fetch(pins); } inline fn lsra(self: *Cpu, pins: *zesty.Pins) void { self.a = _lsr(self.a, false, &self.status); self.status.setNZ(self.a); self.fetch(pins); } inline fn rora(self: *Cpu, pins: *zesty.Pins) void { self.a = _lsr(self.a, true, &self.status); self.status.setNZ(self.a); self.fetch(pins); } inline fn adc(self: *Cpu, pins: *zesty.Pins, v: u8) void { self.a = _adc(self.a, v, &self.status); self.fetch(pins); } inline fn sbc(self: *Cpu, pins: *zesty.Pins, v: u8) void { self.a = _sbc(self.a, v, &self.status); self.fetch(pins); } inline fn cmp(self: *Cpu, pins: *zesty.Pins, v: u8) void { _ = _cmp(v, pins.cpu_data, &self.status); self.fetch(pins); } inline fn inxy(self: *Cpu, pins: *zesty.Pins, v: *u8) void { v.* +%= 1; self.status.setNZ(v.*); self.fetch(pins); } inline fn dexy(self: *Cpu, pins: *zesty.Pins, v: *u8) void { v.* -%= 1; self.status.setNZ(v.*); self.fetch(pins); } inline fn inc(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { // Dummy write? (Not sure why the processor does this) self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_rw = .write; }, 1 => { self.hilo.lo +%= 1; self.status.setNZ(self.hilo.lo); pins.cpu_data = self.hilo.lo; pins.cpu_rw = .write; }, else => self.fetch(pins), } } inline fn dec(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { // Dummy write? (Not sure why the processor does this) self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_rw = .write; }, 1 => { self.hilo.lo -%= 1; self.status.setNZ(self.hilo.lo); pins.cpu_data = self.hilo.lo; pins.cpu_rw = .write; }, else => self.fetch(pins), } } inline fn bit(self: *Cpu, pins: *zesty.Pins) void { const status: Status = .from(pins.cpu_data); self.status.negative = status.negative; self.status.overflow = status.overflow; self.status.zero = (self.a & pins.cpu_data) == 0; self.fetch(pins); } //------------------------------------------------------ // Opcodes: Control flow /// Branch instructions (BPL, BMI, BVC, BVS, BCC, BCS, BNE, BEQ) inline fn branch(self: *Cpu, pins: *zesty.Pins, cond: bool) void { switch (self.cycle) { 0 => { // TODO: Poll interrupts self.pc +%= 1; pins.cpu_addr = self.pc; }, 1 => { pins.cpu_addr = self.pc; // We're done if (!cond) self.sync = true; }, 2 => { self.hilo = .from(self.pc); const pc, const page_crossed = self.hilo.offsetWithPageFaultBehavior( pins.cpu_data, true, ); pins.cpu_addr = pc; if (!page_crossed) self.fetchAt(pins, pc); }, else => self.fetchAt(pins, self.hilo.addr()), } } /// Set status flags (CLC, SEC, CLI, SEI, CLV, CLD, SED) inline fn set( self: *Cpu, pins: *zesty.Pins, flag: enum { carry, irq_disabled, overflow, decimal }, v: bool, ) void { switch (flag) { .carry => self.status.carry = v, .irq_disabled => self.status.irq_disabled = v, .overflow => self.status.overflow = v, .decimal => self.status.decimal = v, } self.fetch(pins); } /// BRK (break) inline fn brk(self: *Cpu, pins: *zesty.Pins) void { const pc: Addr = .from(self.pc); switch (self.cycle) { 0 => { // Set B flag. // // The B flag isn't really a *real* flag in the status register // on a silicon level, but we're setting it here for easier debugging // visualization, and to also stop most tests from running any further. self.status.brk = true; // Store PC hi self.hilo = .stack(self.sp); pins.cpu_addr = self.hilo.addr(); pins.cpu_data = pc.hi; pins.cpu_rw = .write; self.sp -%= 1; }, 1 => { // Store PC lo self.hilo = .stack(self.sp); pins.cpu_addr = self.hilo.addr(); pins.cpu_data = pc.lo; pins.cpu_rw = .write; self.sp -%= 1; }, 2 => { // Store processor status self.hilo = .stack(self.sp); pins.cpu_addr = self.hilo.addr(); pins.cpu_data = self.status.toByte(); pins.cpu_rw = .write; self.sp -%= 1; }, 3 => { // Fetch 0xFFFE into PC lo pins.cpu_addr = 0xfffe; }, 4 => { // Fetch 0xFFFF into PC hi self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_addr = 0xffff; }, else => { // Break sequence is done. Unset B flag self.status.brk = false; self.hilo.hi = pins.cpu_data; self.fetchAt(pins, self.hilo.addr()); }, } } /// RTI (return from interrupt) inline fn rti(self: *Cpu, pins: *zesty.Pins) void { switch (self.cycle) { 0 => { // Dummy read pins.cpu_addr = self.pc; }, 1 => { const stack: Addr = .stack(self.sp); pins.cpu_addr = stack.addr(); self.sp +%= 1; }, 2 => { // Pop status register const stack: Addr = .stack(self.sp); pins.cpu_addr = stack.addr(); self.sp +%= 1; }, 3 => { self.status = .from(pins.cpu_data); // Pop PC lo const stack: Addr = .stack(self.sp); pins.cpu_addr = stack.addr(); self.sp +%= 1; }, 4 => { self.hilo = .{ .lo = pins.cpu_data }; // Pop PC hi const stack: Addr = .stack(self.sp); pins.cpu_addr = stack.addr(); self.sp +%= 1; }, else => { self.hilo.hi = pins.cpu_data; self.fetchAt(pins, self.hilo.addr()); }, } } /// Jump (JMP) inline fn jmp(self: *Cpu, pins: *zesty.Pins) void { switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, 1 => { self.pc +%= 1; pins.cpu_addr = self.pc; self.hilo = .{ .lo = pins.cpu_data }; }, else => { self.hilo.hi = pins.cpu_data; self.fetchAt(pins, self.hilo.addr()); }, } } /// Jump indirect (JMP) inline fn jmpInd(self: *Cpu, pins: *zesty.Pins) void { switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, 1 => { // Fetch indirect lo self.pc +%= 1; pins.cpu_addr = self.pc; self.hilo = .{ .lo = pins.cpu_data }; }, 2 => { // Fetch indirect hi self.hilo.hi = pins.cpu_data; pins.cpu_addr = self.hilo.addr(); }, 3 => { // Fetch target lo self.hilo.lo +%= 1; pins.cpu_addr = self.hilo.addr(); self.hilo = .{ .lo = pins.cpu_data }; }, else => { // Fetch target hi self.hilo.hi = pins.cpu_data; self.fetchAt(pins, self.hilo.addr()); }, } } /// Jump to subroutine (JSR) inline fn jsr(self: *Cpu, pins: *zesty.Pins) void { const pc: Addr = .from(self.pc); const stack: Addr = .stack(self.sp); switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, 1 => { // Fetch target lo self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_addr = stack.addr(); }, 2 => { // Store PC hi pins.cpu_addr = stack.addr(); pins.cpu_data = pc.hi; pins.cpu_rw = .write; self.sp -%= 1; }, 3 => { // Store PC lo pins.cpu_addr = stack.addr(); pins.cpu_data = pc.lo; pins.cpu_rw = .write; self.sp -%= 1; }, 4 => { // Reposition back to PC self.pc +%= 1; pins.cpu_addr = self.pc; }, else => { // Fetch target hi self.hilo.hi = pins.cpu_data; self.fetchAt(pins, self.hilo.addr()); }, } } /// RTS (return from subroutine) inline fn rts(self: *Cpu, pins: *zesty.Pins) void { const stack: Addr = .stack(self.sp); switch (self.cycle) { 0 => { // Dummy read pins.cpu_addr = self.pc; }, 1 => { // Dummy read pins.cpu_addr = stack.addr(); self.sp +%= 1; }, 2 => { // Dummy read pins.cpu_addr = stack.addr(); self.sp +%= 1; }, 3 => { // Pop PC lo self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_addr = stack.addr(); }, 4 => { // Pop PC hi self.hilo.hi = pins.cpu_data; self.pc = self.hilo.addr(); pins.cpu_addr = self.pc; self.pc +%= 1; }, else => { self.fetch(pins); }, } } //------------------------------------------------------ // Opcodes: Undocumented /// This seems like the most common "magic" value for ANE and LXA. /// I genuinely hope nobody depends on this. /// You shouldn't, anyway, since on real hardware /// this apparently changes depending on temperature?? const undocumented_magic: u8 = 0xee; /// HLT (halt) inline fn hlt(self: *Cpu, pins: *zesty.Pins) void { // Stuck pins.cpu_addr = 0xffff; pins.cpu_data = 0xff; self.cycle -= 1; } /// ALR (AND + LSR) inline fn alr(self: *Cpu, pins: *zesty.Pins) void { self.a &= pins.cpu_data; self.a = _lsr(self.a, false, &self.status); self.fetch(pins); } /// ANC (AND, set C) inline fn anc(self: *Cpu, pins: *zesty.Pins) void { self.a &= pins.cpu_data; self.status.carry = self.a >= 0x80; self.status.setNZ(self.a); self.fetch(pins); } /// ANE/XAA, aka The One Unstable Opcode /// See https://www.nesdev.org/wiki/Visual6502wiki/6502_Opcode_8B_(XAA,_ANE) inline fn ane(self: *Cpu, pins: *zesty.Pins) void { self.a = (self.a | undocumented_magic) & self.x & pins.cpu_data; self.status.setNZ(self.a); self.fetch(pins); } /// ARR (AND + ROR) inline fn arr(self: *Cpu, pins: *zesty.Pins) void { // The behavior of these flags is cursed AF. // Kudos to https://github.com/floooh/chips for expressing // this in emulator-friendly form self.a >>= 1; if (self.status.carry) self.a |= 0x80; self.status.setNZ(self.a); if (self.a & 0x40 > 0) { self.status.overflow = true; self.status.carry = true; } if (self.a & 0x20 > 0) { self.status.overflow = !self.status.overflow; } self.fetch(pins); } /// DCP (DEC + CMP) inline fn dcp(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { // Dummy write self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_rw = .write; }, 1 => { self.hilo.lo -%= 1; pins.cpu_data = self.hilo.lo; pins.cpu_rw = .write; _ = _cmp(self.a, pins.cpu_data, &self.status); }, else => self.fetch(pins), } } /// ISC (INC + SBC) inline fn isc(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { // Dummy write self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_rw = .write; }, 1 => { self.hilo.lo +%= 1; pins.cpu_data = self.hilo.lo; pins.cpu_rw = .write; self.a = _sbc(self.a, self.hilo.lo, &self.status); }, else => self.fetch(pins), } } /// LAS (LDA + TSX) inline fn las(self: *Cpu, pins: *zesty.Pins) void { self.sp &= pins.cpu_data; self.a = self.sp; self.x = self.sp; self.status.setNZ(self.a); self.fetch(pins); } /// LAX (LDA + LDX) inline fn lax(self: *Cpu, pins: *zesty.Pins) void { self.a = pins.cpu_data; self.x = pins.cpu_data; self.status.setNZ(self.a); self.fetch(pins); } /// LXA (LAX immediate) /// Highly magical. See ANE inline fn lxa(self: *Cpu, pins: *zesty.Pins) void { self.a = (self.a | undocumented_magic) & pins.cpu_data; self.x = self.a; self.status.setNZ(self.a); self.fetch(pins); } /// RLA (ROL + AND) inline fn rla(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { // Dummy write self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_rw = .write; }, 1 => { pins.cpu_data = self.hilo.lo; pins.cpu_data, const carry = @shlWithOverflow(pins.cpu_data, 1); pins.cpu_data |= @intFromBool(self.status.carry); pins.cpu_rw = .write; self.a &= self.hilo.lo; self.status.setNZ(self.a); self.status.carry = carry > 0; }, else => self.fetch(pins), } } /// RRA (ROR + AND) inline fn rra(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { // Dummy write self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_rw = .write; }, 1 => { pins.cpu_data = self.hilo.lo; self.status.carry = pins.cpu_data & 1 > 0; pins.cpu_data >>= 1; if (self.status.carry) pins.cpu_data |= 0b1000_0000; pins.cpu_rw = .write; self.a &= pins.cpu_data; self.status.setNZ(self.a); }, else => self.fetch(pins), } } /// SAX inline fn sax(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { pins.cpu_data = self.a & self.x; pins.cpu_rw = .write; }, else => self.fetch(pins), } } /// SBX (CMP, DEX) inline fn sbx(self: *Cpu, pins: *zesty.Pins) void { self.x = _cmp(self.x & self.a, pins.cpu_data, &self.status); self.fetch(pins); } /// SHA, SHX, SHY (A11) /// Even though the high byte may be dropped on real 6502 CPUs, /// here I choose to add them unconditionally. inline fn a11(self: *Cpu, pins: *zesty.Pins, v: u8, c: u3) void { switch (c) { 0 => { const pc: Addr = .from(self.pc); pins.cpu_data = v & (pc.hi +% 1); pins.cpu_rw = .write; }, else => self.fetch(pins), } } /// SLO (ASL + ORA) inline fn slo(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { self.hilo.lo = pins.cpu_data; pins.cpu_rw = .write; }, 1 => { pins.cpu_data = _asl(self.hilo.lo, false, &self.status); self.a |= pins.cpu_data; self.status.setNZ(self.a); pins.cpu_rw = .write; }, else => self.fetch(pins), } } /// SRE (LSR + EOR) inline fn sre(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { self.hilo.lo = pins.cpu_data; pins.cpu_rw = .write; }, 1 => { pins.cpu_data = _lsr(self.hilo.lo, false, &self.status); self.a ^= pins.cpu_data; self.status.setNZ(self.a); pins.cpu_rw = .write; }, else => self.fetch(pins), } } /// TAS /// Unstable. See A11 inline fn tas(self: *Cpu, pins: *zesty.Pins, c: u3) void { switch (c) { 0 => { const pc: Addr = .from(self.pc); const v = self.a & self.x; self.sp = v; pins.cpu_data = v & (pc.hi +% 1); pins.cpu_rw = .write; }, else => self.fetch(pins), } } //------------------------------------------------------ // Helpers pub const Status = packed struct(u8) { carry: bool = false, zero: bool = false, irq_disabled: bool = true, decimal: bool = false, brk: bool = false, _unused: bool = true, overflow: bool = false, negative: bool = false, fn from(v: u8) Status { var new: Status = @bitCast(v); new._unused = true; // BRK is never restored from a read value. new.brk = false; return new; } fn toByte(v: Status) u8 { return @bitCast(v); } inline fn setNZ(self: *Status, v: u8) void { self.zero = v == 0; self.negative = v >= 0x80; } pub fn format( self: Status, writer: *std.Io.Writer, ) std.Io.Writer.Error!void { try writer.writeByte(if (self.negative) 'N' else 'n'); try writer.writeByte(if (self.overflow) 'V' else 'v'); try writer.writeByte('-'); try writer.writeByte(if (self.brk) 'B' else 'b'); try writer.writeByte(if (self.decimal) 'D' else 'd'); try writer.writeByte(if (self.irq_disabled) 'I' else 'i'); try writer.writeByte(if (self.zero) 'Z' else 'z'); try writer.writeByte(if (self.carry) 'C' else 'c'); } }; const Addr = packed struct(u16) { lo: u8 = 0, hi: u8 = 0, fn stack(sp: u8) Addr { return .{ .hi = 0x01, .lo = sp }; } fn from(v: u16) Addr { return @bitCast(v); } fn addr(v: Addr) u16 { return @bitCast(v); } /// The 6502 CPU has a very interesting behavior where if an internal /// address has an offset applied to it, and said offset would make the /// address cross pages, then the CPU would require one extra cycle to /// properly calculate the final address. /// /// This calculation can also be signed, which is used in branch offsets. inline fn offsetWithPageFaultBehavior( self: *Addr, v: u8, comptime signed: bool, ) struct { u16, bool } { const is_negative = signed and v >= 0x80; self.lo, const page_crossed = if (is_negative) // v should NEVER be lower than 0x80, but saturate down to 0 to be safe @subWithOverflow(self.lo, v - 0x80) else @addWithOverflow(self.lo, v); // If we haven't crossed the page boundary, skip over the next cycle. defer { if (is_negative) self.hi -%= page_crossed else self.hi +%= page_crossed; } return .{ self.addr(), page_crossed > 0 }; } };