Zesty - a pin-accurate, cycle-accurate NES emulator written in Zig
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380//! 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 ANEinline 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), }}/// SAXinline 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 A11inline 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 }; }};