From bf5f3b00059a4da3eb8476b4f2e23d4e191297ea Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Fri, 12 Sep 2025 00:28:40 +0200 Subject: [PATCH] cpu: finish ALU impl Change-Id: nktwyztsqxqrzkymtwmwkyoqmqmpysqs --- src/Cpu.zig | 404 +++++++++++++++++++++------------ src/tests/cpu/alu.zig | 279 +++++++++++++++++++++++ src/tests/cpu/assembler.zig | 148 +++++++++++- src/tests/cpu/control_flow.zig | 97 +++++++- 4 files changed, 779 insertions(+), 149 deletions(-) diff --git a/src/Cpu.zig b/src/Cpu.zig index f3b9a25..4ad1f1e 100644 --- a/src/Cpu.zig +++ b/src/Cpu.zig @@ -109,145 +109,187 @@ pub fn tick(self: *Cpu, pins: *zesty.Pins, last_pins: zesty.Pins) void { 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) - 0x05 => if (self.zp(pins)) self.ora(pins, v), // ORA zp - 0x06 => if (self.zp(pins)) self.asl(pins, .mem), // ASL zp - 0x09 => if (self.imm(pins)) self.ora(pins, v), // ORA # - 0x0a => if (self.imm(pins)) self.asl(pins, .acc), // ASL A - 0x0d => if (self.abs(pins)) self.ora(pins, v), // ORA abs - 0x0e => if (self.abs(pins)) self.asl(pins, .mem), // ASL 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 - 0x15 => if (self.zpOff(pins, self.x)) self.ora(pins, v), // ORA zp,X - 0x16 => if (self.zpOff(pins, self.x)) self.asl(pins, .mem), // ASL zp,X + 0x01 => if (self.zpXInd(pins)) |_| self.ora(pins, v), // ORA (zp,X) + 0x05 => if (self.zp(pins)) |_| self.ora(pins, v), // ORA zp + 0x06 => if (self.zp(pins)) |_| self.asl(pins, .mem), // ASL zp + 0x09 => if (self.imm(pins)) |_| self.ora(pins, v), // ORA # + 0x0a => if (self.imm(pins)) |_| self.asl(pins, .acc), // ASL A + 0x0d => if (self.abs(pins)) |_| self.ora(pins, v), // ORA abs + 0x0e => if (self.abs(pins)) |_| self.asl(pins, .mem), // ASL 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 + 0x15 => if (self.zpOff(pins, self.x)) |_| self.ora(pins, v), // ORA zp,X + 0x16 => if (self.zpOff(pins, self.x)) |_| self.asl(pins, .mem), // ASL zp,X 0x18 => self.set(pins, .carry, false), // CLC - 0x19 => if (self.absOff(pins, self.y)) self.ora(pins, v), // ORA abs,Y - 0x1d => if (self.absOff(pins, self.x)) self.ora(pins, v), // ORA abs,X - 0x1e => if (self.absOff(pins, self.x)) self.asl(pins, .mem), // ASL abs,X - - 0x21 => if (self.zpXInd(pins)) self._and(pins, v), // AND (zp,X) - 0x25 => if (self.zp(pins)) self._and(pins, v), // AND zp - 0x26 => if (self.zp(pins)) self.rol(pins, .mem), // ROL zp - 0x29 => if (self.imm(pins)) self._and(pins, v), // AND # - 0x2a => if (self.imm(pins)) self.rol(pins, .acc), // ROL A - 0x2d => if (self.abs(pins)) self._and(pins, v), // AND abs - 0x2e => if (self.abs(pins)) self.rol(pins, .mem), // ROL 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 - 0x35 => if (self.zpOff(pins, self.x)) self._and(pins, v), // AND zp,X - 0x36 => if (self.zpOff(pins, self.x)) self.rol(pins, .mem), // ROL zp,X + 0x19 => if (self.absOff(pins, self.y)) |_| self.ora(pins, v), // ORA abs,Y + 0x1d => if (self.absOff(pins, self.x)) |_| self.ora(pins, v), // ORA abs,X + 0x1e => if (self.absOff(pins, self.x)) |_| self.asl(pins, .mem), // ASL abs,X + + 0x20 => self.jsr(pins), // JSR abs + 0x21 => if (self.zpXInd(pins)) |_| self._and(pins, v), // AND (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)) |_| self.rol(pins, .mem), // ROL zp + 0x29 => if (self.imm(pins)) |_| self._and(pins, v), // AND # + 0x2a => if (self.imm(pins)) |_| self.rol(pins, .acc), // ROL A + 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)) |_| self.rol(pins, .mem), // ROL 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 + 0x35 => if (self.zpOff(pins, self.x)) |_| self._and(pins, v), // AND zp,X + 0x36 => if (self.zpOff(pins, self.x)) |_| self.rol(pins, .mem), // ROL zp,X 0x38 => self.set(pins, .carry, true), // SEC - 0x39 => if (self.absOff(pins, self.y)) self._and(pins, v), // AND abs,Y - 0x3d => if (self.absOff(pins, self.x)) self._and(pins, v), // AND abs,X - 0x3e => if (self.absOff(pins, self.x)) self.rol(pins, .mem), // ROL abs,X + 0x39 => if (self.absOff(pins, self.y)) |_| self._and(pins, v), // AND abs,Y + 0x3d => if (self.absOff(pins, self.x)) |_| self._and(pins, v), // AND abs,X + 0x3e => if (self.absOff(pins, self.x)) |_| self.rol(pins, .mem), // ROL abs,X 0x40 => self.rti(pins), // RTI - 0x41 => if (self.zpXInd(pins)) self.eor(pins, v), // EOR (zp,X) - 0x45 => if (self.zp(pins)) self.eor(pins, v), // EOR zp - 0x46 => if (self.zp(pins)) self.lsr(pins, .mem), // LSR zp - 0x49 => if (self.imm(pins)) self.eor(pins, v), // EOR # - 0x4a => if (self.imm(pins)) self.lsr(pins, .acc), // LSR A - 0x4d => if (self.abs(pins)) self.eor(pins, v), // EOR abs - 0x4e => if (self.abs(pins)) self.lsr(pins, .mem), // LSR abs - - 0x50 => if (self.imm(pins)) self.branch(pins, !self.status.overflow), // BVC - 0x51 => if (self.zpIndY(pins)) self.eor(pins, v), // EOR (zp),Y - 0x55 => if (self.zpOff(pins, self.x)) self.eor(pins, v), // EOR zp,X - 0x56 => if (self.zpOff(pins, self.x)) self.lsr(pins, .mem), // LSR zp,X + 0x41 => if (self.zpXInd(pins)) |_| self.eor(pins, v), // EOR (zp,X) + 0x45 => if (self.zp(pins)) |_| self.eor(pins, v), // EOR zp + 0x46 => if (self.zp(pins)) |_| self.lsr(pins, .mem), // LSR zp + 0x49 => if (self.imm(pins)) |_| self.eor(pins, v), // EOR # + 0x4a => if (self.imm(pins)) |_| self.lsr(pins, .acc), // LSR A + 0x4c => self.jmp(pins), // JMP abs + 0x4d => if (self.abs(pins)) |_| self.eor(pins, v), // EOR abs + 0x4e => if (self.abs(pins)) |_| self.lsr(pins, .mem), // LSR abs + + 0x50 => if (self.imm(pins)) |_| self.branch(pins, !self.status.overflow), // BVC + 0x51 => if (self.zpIndY(pins)) |_| self.eor(pins, v), // EOR (zp),Y + 0x55 => if (self.zpOff(pins, self.x)) |_| self.eor(pins, v), // EOR zp,X + 0x56 => if (self.zpOff(pins, self.x)) |_| self.lsr(pins, .mem), // LSR zp,X 0x58 => self.set(pins, .irq_disabled, false), // CLI - 0x59 => if (self.absOff(pins, self.y)) self.eor(pins, v), // EOR abs,Y - 0x5d => if (self.absOff(pins, self.x)) self.eor(pins, v), // EOR abs,X - 0x5e => if (self.absOff(pins, self.x)) self.lsr(pins, .mem), // LSR abs,X - - // 0x61 => if (self.zpXInd(pins)) self.adc(pins, v), // ADC (zp,X) - // 0x65 => if (self.zp(pins)) self.adc(pins, v), // ADC zp - 0x66 => if (self.zp(pins)) self.ror(pins, .mem), // ROR zp - // 0x69 => if (self.imm(pins)) self.adc(pins, v), // ADC # - 0x6a => if (self.imm(pins)) self.ror(pins, .acc), // ROR A - // 0x6d => if (self.abs(pins)) self.adc(pins, v), // ADC abs - 0x6e => if (self.abs(pins)) self.ror(pins, .mem), // ROR 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 - // 0x75 => if (self.zpOff(pins, self.x)) self.adc(pins, v), // ADC zp,X - 0x76 => if (self.zpOff(pins, self.x)) self.ror(pins, .mem), // ROR zp,X + 0x59 => if (self.absOff(pins, self.y)) |_| self.eor(pins, v), // EOR abs,Y + 0x5d => if (self.absOff(pins, self.x)) |_| self.eor(pins, v), // EOR abs,X + 0x5e => if (self.absOff(pins, self.x)) |_| self.lsr(pins, .mem), // LSR abs,X + + 0x60 => self.rts(pins), // RTS + 0x61 => if (self.zpXInd(pins)) |_| self.adc(pins, v), // ADC (zp,X) + 0x65 => if (self.zp(pins)) |_| self.adc(pins, v), // ADC zp + 0x66 => if (self.zp(pins)) |_| self.ror(pins, .mem), // ROR zp + 0x69 => if (self.imm(pins)) |_| self.adc(pins, v), // ADC # + 0x6a => if (self.imm(pins)) |_| self.ror(pins, .acc), // ROR A + 0x6c => self.jmpInd(pins), // JMP (ind) + 0x6d => if (self.abs(pins)) |_| self.adc(pins, v), // ADC abs + 0x6e => if (self.abs(pins)) |_| self.ror(pins, .mem), // ROR 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 + 0x75 => if (self.zpOff(pins, self.x)) |_| self.adc(pins, v), // ADC zp,X + 0x76 => if (self.zpOff(pins, self.x)) |_| self.ror(pins, .mem), // ROR zp,X 0x78 => self.set(pins, .irq_disabled, true), // SEI - // 0x79 => if (self.absOff(pins, self.y)) self.adc(pins, v), // ADC abs,Y - // 0x7d => if (self.absOff(pins, self.x)) self.adc(pins, v), // ADC abs,X - 0x7e => if (self.absOff(pins, self.x)) self.ror(pins, .mem), // ROR abs,X - - 0x81 => if (self.zpXInd(pins)) self.st(pins, self.y), // STA (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 - 0x88 => if (self.imm(pins)) self.dec(pins, &self.y), // DEY - 0x8a => if (self.imm(pins)) self.ld(pins, &self.a, self.x), // TXA - 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 - - 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 - 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 - 0x99 => if (self.absOff(pins, self.y)) self.st(pins, self.a), // STA abs,Y - 0x9a => if (self.imm(pins)) self.ld(pins, &self.sp, self.x), // TXS - 0x9c => if (self.absOff(pins, self.x)) self.st(pins, self.y), // STY abs,X - 0x9d => if (self.absOff(pins, self.x)) self.st(pins, self.a), // STA abs,X - 0x9e => if (self.absOff(pins, self.y)) self.st(pins, self.x), // STX 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 # - 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 - 0xa8 => if (self.imm(pins)) self.ld(pins, &self.y, self.a), // TAY - 0xa9 => if (self.imm(pins)) self.ld(pins, &self.a, v), // LDA # - 0xaa => if (self.imm(pins)) self.ld(pins, &self.x, self.a), // TAX - 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 - - 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 - 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 + 0x79 => if (self.absOff(pins, self.y)) |_| self.adc(pins, v), // ADC abs,Y + 0x7d => if (self.absOff(pins, self.x)) |_| self.adc(pins, v), // ADC abs,X + 0x7e => if (self.absOff(pins, self.x)) |_| self.ror(pins, .mem), // ROR abs,X + + 0x81 => if (self.zpXInd(pins)) |_| self.st(pins, self.y), // STA (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 + 0x88 => if (self.imm(pins)) |_| self.dexy(pins, &self.y), // DEY + 0x8a => if (self.imm(pins)) |_| self.ld(pins, &self.a, self.x), // TXA + 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 + + 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 + 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 + 0x99 => if (self.absOff(pins, self.y)) |_| self.st(pins, self.a), // STA abs,Y + 0x9a => if (self.imm(pins)) |_| self.ld(pins, &self.sp, self.x), // TXS + 0x9c => if (self.absOff(pins, self.x)) |_| self.st(pins, self.y), // STY abs,X + 0x9d => if (self.absOff(pins, self.x)) |_| self.st(pins, self.a), // STA abs,X + 0x9e => if (self.absOff(pins, self.y)) |_| self.st(pins, self.x), // STX 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 # + 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 + 0xa8 => if (self.imm(pins)) |_| self.ld(pins, &self.y, self.a), // TAY + 0xa9 => if (self.imm(pins)) |_| self.ld(pins, &self.a, v), // LDA # + 0xaa => if (self.imm(pins)) |_| self.ld(pins, &self.x, self.a), // TAX + 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 + + 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 + 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 0xb8 => 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.imm(pins)) self.ld(pins, &self.x, self.sp), // TSX - 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 - - 0xd0 => if (self.imm(pins)) self.branch(pins, !self.status.zero), // BNE + 0xb9 => if (self.absOff(pins, self.y)) |_| self.ld(pins, &self.a, v), // LDA abs,Y + 0xba => if (self.imm(pins)) |_| self.ld(pins, &self.x, self.sp), // TSX + 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 + + 0xc0 => if (self.imm(pins)) |_| self.cmp(pins, self.y), // CPY # + 0xc1 => if (self.zpXInd(pins)) |_| self.cmp(pins, self.a), // CMP (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 + 0xc8 => if (self.imm(pins)) |_| self.inxy(pins, &self.y), // INY + 0xc9 => if (self.imm(pins)) |_| self.cmp(pins, self.a), // CMP # + 0xca => if (self.imm(pins)) |_| self.dexy(pins, &self.x), // DEY + 0xcc => if (self.abs(pins)) |_| self.cmp(pins, self.y), // CPY abs + 0xcd => if (self.abs(pins)) |_| self.cmp(pins, self.a), // CMP abs + 0xce => if (self.abs(pins)) |c| self.dec(pins, c), // DEC 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 + 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 0xd8 => self.set(pins, .decimal, false), // CLD - - 0xea => if (self.imm(pins)) self.fetch(pins), // NOP - - 0xf0 => if (self.imm(pins)) self.branch(pins, self.status.zero), // BEQ + 0xd9 => if (self.absOff(pins, self.y)) |_| self.cmp(pins, self.a), // CMP abs,Y + 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 + + 0xe0 => if (self.imm(pins)) |_| self.cmp(pins, self.x), // CPX # + 0xe1 => if (self.zpXInd(pins)) |_| self.sbc(pins, v), // SBC (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 + 0xe8 => if (self.imm(pins)) |_| self.inxy(pins, &self.x), // INX + 0xe9 => if (self.imm(pins)) |_| self.sbc(pins, v), // SBC # + 0xea => if (self.imm(pins)) |_| self.fetch(pins), // NOP + 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 + + 0xf0 => if (self.imm(pins)) |_| self.branch(pins, self.status.zero), // BEQ + 0xf1 => if (self.zpIndY(pins)) |_| self.sbc(pins, v), // SBC (zp),Y + 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 0xf8 => self.set(pins, .decimal, true), // SED + 0xf9 => if (self.absOff(pins, self.y)) |_| self.sbc(pins, v), // SBC abs,Y + 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 - else => log.err("UNHANDLED OP={X}", .{self.opcode}), + else => { + log.err("UNHANDLED OP={X}", .{self.opcode}); + self.fetch(pins); + }, } } -inline fn imm(self: *Cpu, pins: *zesty.Pins) bool { +inline fn imm(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; pins.cpu_addr = self.pc; }, - else => return true, + else => return self.cycle - 1, } - return false; + return null; } -inline fn zp(self: *Cpu, pins: *zesty.Pins) bool { +inline fn zp(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; @@ -257,11 +299,11 @@ inline fn zp(self: *Cpu, pins: *zesty.Pins) bool { self.hilo = .{ .lo = pins.cpu_data }; pins.cpu_addr = self.hilo.addr(); }, - else => return true, + else => return self.cycle - 2, } - return false; + return null; } -inline fn zpOff(self: *Cpu, pins: *zesty.Pins, v: u8) bool { +inline fn zpOff(self: *Cpu, pins: *zesty.Pins, v: u8) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; @@ -275,11 +317,11 @@ inline fn zpOff(self: *Cpu, pins: *zesty.Pins, v: u8) bool { self.hilo.lo +%= v; pins.cpu_addr = self.hilo.addr(); }, - else => return true, + else => return self.cycle - 3, } - return false; + return null; } -inline fn abs(self: *Cpu, pins: *zesty.Pins) bool { +inline fn abs(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; @@ -294,11 +336,11 @@ inline fn abs(self: *Cpu, pins: *zesty.Pins) bool { self.hilo.hi = pins.cpu_data; pins.cpu_addr = self.hilo.addr(); }, - else => return true, + else => return self.cycle - 3, } - return false; + return null; } -inline fn absOff(self: *Cpu, pins: *zesty.Pins, v: u8) bool { +inline fn absOff(self: *Cpu, pins: *zesty.Pins, v: u8) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; @@ -317,11 +359,11 @@ inline fn absOff(self: *Cpu, pins: *zesty.Pins, v: u8) bool { 3 => { pins.cpu_addr = self.hilo.addr(); }, - else => return true, + else => return self.cycle - 4, } - return false; + return null; } -inline fn zpXInd(self: *Cpu, pins: *zesty.Pins) bool { +inline fn zpXInd(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; @@ -344,11 +386,11 @@ inline fn zpXInd(self: *Cpu, pins: *zesty.Pins) bool { self.hilo.hi = pins.cpu_data; pins.cpu_addr = self.hilo.addr(); }, - else => return true, + else => return self.cycle - 5, } - return false; + return null; } -inline fn zpIndY(self: *Cpu, pins: *zesty.Pins) bool { +inline fn zpIndY(self: *Cpu, pins: *zesty.Pins) ?u3 { switch (self.cycle) { 0 => { self.pc +%= 1; @@ -367,9 +409,9 @@ inline fn zpIndY(self: *Cpu, pins: *zesty.Pins) bool { 4 => { pins.cpu_addr = self.hilo.addr(); }, - else => return true, + else => return self.cycle - 5, } - return false; + return null; } inline fn fetch(self: *Cpu, pins: *zesty.Pins) void { @@ -483,11 +525,79 @@ inline fn ror(self: *Cpu, pins: *zesty.Pins, comptime dst: Dst) void { self.setNZ(v); self.fetch(pins); } -inline fn dec(self: *Cpu, pins: *zesty.Pins, v: *u8) void { +inline fn adc(self: *Cpu, pins: *zesty.Pins, v: u8) void { + var result, var carry = @addWithOverflow(self.a, @intFromBool(self.status.carry)); + // TODO: implement optional support for decimal mode + result, carry = @addWithOverflow(result, v); + + self.status.carry = carry > 0; + self.setNZ(result); + // Overflow bit is set if both inputs have the same sign, + // and the output has a different sign + self.status.overflow = ~(self.a ^ v) & (self.a ^ result) & 0x80 > 0; + self.a = result; + self.fetch(pins); +} +inline fn sbc(self: *Cpu, pins: *zesty.Pins, v: u8) void { + self.adc(pins, ~v); +} +inline fn cmp(self: *Cpu, pins: *zesty.Pins, v: u8) void { + // a CMP is basically a SBC in disguise + const result, const carry = @addWithOverflow(v, ~pins.cpu_data +% 1); + self.status.carry = carry > 0; + self.setNZ(result); + self.fetch(pins); +} +inline fn inxy(self: *Cpu, pins: *zesty.Pins, v: *u8) void { + v.* +%= 1; + self.setNZ(v.*); + self.fetch(pins); +} + +inline fn dexy(self: *Cpu, pins: *zesty.Pins, v: *u8) void { v.* -%= 1; self.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.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.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 @@ -511,7 +621,7 @@ inline fn branch(self: *Cpu, pins: *zesty.Pins, cond: bool) void { pins.cpu_data, true, ); - pins.cpu_addr = self.pc; + pins.cpu_addr = pc; if (!page_crossed) self.fetchAt(pins, pc); }, else => self.fetchAt(pins, self.hilo.addr()), @@ -697,16 +807,22 @@ const Addr = packed struct(u16) { inline fn offsetWithPageFaultBehavior( self: *Addr, v: u8, - signed: bool, + comptime signed: bool, ) struct { u16, bool } { - self.lo, const page_crossed = if (!signed and v < 0x80) - @addWithOverflow(self.lo, v) - else + 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); + @subWithOverflow(self.lo, v - 0x80) + else + @addWithOverflow(self.lo, v); // If we haven't crossed the page boundary, skip over the next cycle. - defer self.hi +%= page_crossed; + defer { + if (is_negative) + self.hi -%= page_crossed + else + self.hi +%= page_crossed; + } return .{ self.addr(), page_crossed > 0 }; } }; diff --git a/src/tests/cpu/alu.zig b/src/tests/cpu/alu.zig index c35e298..aa20cf6 100644 --- a/src/tests/cpu/alu.zig +++ b/src/tests/cpu/alu.zig @@ -131,3 +131,282 @@ test "ROR" { try std.testing.expect(!z.cpu.status.zero); try std.testing.expect(z.cpu.status.carry); } + +test "ADC" { + var z = cpu.testZes(.{ + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\LDA #%10000101 + \\SEC + \\ADC #%10000101 + ), + }, + }, + }); + cpu.run(&z); // Setup A register + cpu.run(&z); // Set carry + cpu.run(&z); // Perform addition + + try std.testing.expectEqual(0x8005, z.cpu.pc); + try std.testing.expectEqual(0b00001011, z.cpu.a); + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); + try std.testing.expect(z.cpu.status.carry); + try std.testing.expect(z.cpu.status.overflow); +} + +test "SBC" { + var z = cpu.testZes(.{ + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\LDA #%10000101 + \\SEC + \\SBC #%01111010 + ), + }, + }, + }); + cpu.run(&z); // Setup A register + cpu.run(&z); // Set carry + cpu.run(&z); // Perform addition + + try std.testing.expectEqual(0x8005, z.cpu.pc); + try std.testing.expectEqual(0b00001011, z.cpu.a); + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); + try std.testing.expect(z.cpu.status.carry); + try std.testing.expect(z.cpu.status.overflow); +} + +test "DEC" { + var z = cpu.testZes(.{ + .ram = &.{ + .{ 0x42, &.{0x69} }, + }, + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\DEC <$42 + ), + }, + }, + }); + cpu.run(&z); + try std.testing.expectEqual(0x8002, z.cpu.pc); + try std.testing.expectEqual(0x68, z.ram[0x42]); + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); +} + +test "DEX" { + var z = cpu.testZes(.{ + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\DEX + ), + }, + }, + }); + cpu.run(&z); + try std.testing.expectEqual(0x8002, z.cpu.pc); + try std.testing.expectEqual(0b11111111, z.cpu.x); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); +} + +test "DEY" { + var z = cpu.testZes(.{ + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\DEY + ), + }, + }, + }); + cpu.run(&z); + try std.testing.expectEqual(0x8002, z.cpu.pc); + try std.testing.expectEqual(0b11111111, z.cpu.y); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); +} + +test "INC" { + var z = cpu.testZes(.{ + .ram = &.{ + .{ 0x42, &.{0x69} }, + }, + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\INC <$42 + ), + }, + }, + }); + cpu.run(&z); + try std.testing.expectEqual(0x8002, z.cpu.pc); + try std.testing.expectEqual(0x6a, z.ram[0x42]); + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); +} + +test "INX" { + var z = cpu.testZes(.{ + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\INX + ), + }, + }, + }); + cpu.run(&z); + try std.testing.expectEqual(0x8002, z.cpu.pc); + try std.testing.expectEqual(0b00000001, z.cpu.x); + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); +} + +test "INY" { + var z = cpu.testZes(.{ + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\INY + ), + }, + }, + }); + cpu.run(&z); + try std.testing.expectEqual(0x8002, z.cpu.pc); + try std.testing.expectEqual(0b00000001, z.cpu.y); + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); +} + +test "CMP" { + var z = cpu.testZes(.{ + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\LDA #$dd + \\CMP #$42 + \\CMP #$dd + \\CMP #$ee + ), + }, + }, + }); + cpu.run(&z); + + cpu.run(&z); + try std.testing.expectEqual(0x8004, z.cpu.pc); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); + try std.testing.expect(z.cpu.status.carry); + + cpu.run(&z); + try std.testing.expectEqual(0x8006, z.cpu.pc); + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(z.cpu.status.zero); + try std.testing.expect(z.cpu.status.carry); + + cpu.run(&z); + try std.testing.expectEqual(0x8008, z.cpu.pc); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); + try std.testing.expect(!z.cpu.status.carry); +} + +test "CPX" { + var z = cpu.testZes(.{ + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\LDX #$dd + \\CPX #$42 + \\CPX #$dd + \\CPX #$ee + ), + }, + }, + }); + cpu.run(&z); + + cpu.run(&z); + try std.testing.expectEqual(0x8004, z.cpu.pc); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); + try std.testing.expect(z.cpu.status.carry); + + cpu.run(&z); + try std.testing.expectEqual(0x8006, z.cpu.pc); + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(z.cpu.status.zero); + try std.testing.expect(z.cpu.status.carry); + + cpu.run(&z); + try std.testing.expectEqual(0x8008, z.cpu.pc); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); + try std.testing.expect(!z.cpu.status.carry); +} +test "CPY" { + var z = cpu.testZes(.{ + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\LDY #$dd + \\CPY #$42 + \\CPY #$dd + \\CPY #$ee + ), + }, + }, + }); + cpu.run(&z); + + cpu.run(&z); + try std.testing.expectEqual(0x8004, z.cpu.pc); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); + try std.testing.expect(z.cpu.status.carry); + + cpu.run(&z); + try std.testing.expectEqual(0x8006, z.cpu.pc); + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(z.cpu.status.zero); + try std.testing.expect(z.cpu.status.carry); + + cpu.run(&z); + try std.testing.expectEqual(0x8008, z.cpu.pc); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); + try std.testing.expect(!z.cpu.status.carry); +} +test "BIT" { + var z = cpu.testZes(.{ + .ram = &.{ + .{ 0x42, &.{0b11000000} }, + }, + .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\LDA #%00000101 + \\BIT <$42 + ), + }, + }, + }); + cpu.run(&z); + + cpu.run(&z); + try std.testing.expectEqual(0x8004, z.cpu.pc); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(z.cpu.status.overflow); + try std.testing.expect(z.cpu.status.zero); +} diff --git a/src/tests/cpu/assembler.zig b/src/tests/cpu/assembler.zig index 4435eae..9ee6110 100644 --- a/src/tests/cpu/assembler.zig +++ b/src/tests/cpu/assembler.zig @@ -50,12 +50,11 @@ pub fn assemble( pub fn assembleComptime(comptime in: []const u8) []const u8 { // Heuristically defined - @setEvalBranchQuota(in.len * 100); + @setEvalBranchQuota(in.len * 500); return comptime v: { var count_w: std.Io.Writer.Discarding = .init(&.{}); - assemble(in, &count_w.writer) catch |err| std.debug.panic("error: {}", .{err}); - + assemble(in, &count_w.writer) catch |err| @compileError(std.fmt.comptimePrint("error: {}", .{err})); var buf: [count_w.fullCount()]u8 = undefined; var w: std.Io.Writer = .fixed(&buf); assemble(in, &w) catch unreachable; @@ -77,10 +76,22 @@ pub const Opcode = enum { ora, @"and", eor, + adc, + sbc, asl, lsr, rol, ror, + inc, + dec, + inx, + dex, + iny, + dey, + cmp, + cpx, + cpy, + bit, clc, sec, @@ -90,6 +101,20 @@ pub const Opcode = enum { cld, sed, + bpl, + bmi, + bvc, + bvs, + bcc, + bcs, + bne, + beq, + jmp, + jsr, + rts, + + nop, + fn encode(op: Opcode, opr: Operand) ?u8 { return switch (op) { .brk => switch (opr) { @@ -180,6 +205,28 @@ pub const Opcode = enum { .indirect_indexed => 0x51, else => null, }, + .adc => switch (opr) { + .zeropage => 0x65, + .zeropage_x => 0x75, + .absolute => 0x6d, + .absolute_x => 0x7d, + .absolute_y => 0x79, + .immediate => 0x69, + .indexed_indirect => 0x61, + .indirect_indexed => 0x71, + else => null, + }, + .sbc => switch (opr) { + .zeropage => 0xe5, + .zeropage_x => 0xf5, + .absolute => 0xed, + .absolute_x => 0xfd, + .absolute_y => 0xf9, + .immediate => 0xe9, + .indexed_indirect => 0xe1, + .indirect_indexed => 0xf1, + else => null, + }, .asl => switch (opr) { .zeropage => 0x06, .zeropage_x => 0x16, @@ -212,6 +259,64 @@ pub const Opcode = enum { .implied => 0x6a, else => null, }, + .inc => switch (opr) { + .zeropage => 0xe6, + .zeropage_x => 0xf6, + .absolute => 0xee, + .absolute_x => 0xfe, + else => null, + }, + .dec => switch (opr) { + .zeropage => 0xc6, + .zeropage_x => 0xd6, + .absolute => 0xce, + .absolute_x => 0xde, + else => null, + }, + .inx => switch (opr) { + .implied => 0xe8, + else => null, + }, + .dex => switch (opr) { + .implied => 0xca, + else => null, + }, + .iny => switch (opr) { + .implied => 0xc8, + else => null, + }, + .dey => switch (opr) { + .implied => 0x88, + else => null, + }, + .cmp => switch (opr) { + .zeropage => 0xc5, + .zeropage_x => 0xd5, + .absolute => 0xcd, + .absolute_x => 0xdd, + .absolute_y => 0xd9, + .immediate => 0xc9, + .indexed_indirect => 0xc1, + .indirect_indexed => 0xd1, + else => null, + }, + .cpx => switch (opr) { + .immediate => 0xe0, + .zeropage => 0xe4, + .absolute => 0xec, + else => null, + }, + .cpy => switch (opr) { + .immediate => 0xc0, + .zeropage => 0xc4, + .absolute => 0xcc, + else => null, + }, + .bit => switch (opr) { + .zeropage => 0x24, + .absolute => 0x2c, + else => null, + }, .clc => switch (opr) { .implied => 0x18, else => null, @@ -240,6 +345,43 @@ pub const Opcode = enum { .implied => 0xf8, else => null, }, + + .bpl => switch (opr) { + .immediate => 0x10, + else => null, + }, + .bmi => switch (opr) { + .immediate => 0x30, + else => null, + }, + .bvc => switch (opr) { + .immediate => 0x50, + else => null, + }, + .bvs => switch (opr) { + .immediate => 0x70, + else => null, + }, + .bcc => switch (opr) { + .immediate => 0x90, + else => null, + }, + .bcs => switch (opr) { + .immediate => 0xb0, + else => null, + }, + .bne => switch (opr) { + .immediate => 0xd0, + else => null, + }, + .beq => switch (opr) { + .immediate => 0xf0, + else => null, + }, + .nop => switch (opr) { + .implied => 0xea, + else => null, + }, }; } }; diff --git a/src/tests/cpu/control_flow.zig b/src/tests/cpu/control_flow.zig index 9c0d9dd..30d2038 100644 --- a/src/tests/cpu/control_flow.zig +++ b/src/tests/cpu/control_flow.zig @@ -15,12 +15,12 @@ test "reset" { try std.testing.expectEqual(0x69, z.pins.cpu_data); } -test "BRK and RTI" { +test "BRK" { var z = cpu.testZes(.{ // Set a recognizable IRQ offset .irq = 0x8964, .rom = &.{ - .{ 0x8000, cpu.assemble("brk") }, // BRK + .{ 0x8000, cpu.assemble("brk") }, }, }); @@ -67,3 +67,96 @@ test "BRK and RTI" { try std.testing.expectEqual(0x00, z.ram[0x1fc]); try std.testing.expectEqual(status, z.ram[0x1fb]); } + +test "set and clear" { + // TODO: Test CLV as well + var z = cpu.testZes(.{ .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\sec + \\cli + \\sed + \\clc + \\sei + \\cld + ), + }, + } }); + + // Initial state + try std.testing.expect(!z.cpu.status.carry); + try std.testing.expect(z.cpu.status.irq_disabled); + try std.testing.expect(!z.cpu.status.decimal); + + cpu.run(&z); // SEC + try std.testing.expectEqual(0x8001, z.cpu.pc); + try std.testing.expect(z.cpu.status.carry); + + cpu.run(&z); // CLI + try std.testing.expectEqual(0x8002, z.cpu.pc); + try std.testing.expect(!z.cpu.status.irq_disabled); + + cpu.run(&z); // SED + try std.testing.expectEqual(0x8003, z.cpu.pc); + try std.testing.expect(z.cpu.status.decimal); + + cpu.run(&z); // CLC + try std.testing.expectEqual(0x8004, z.cpu.pc); + try std.testing.expect(!z.cpu.status.carry); + + cpu.run(&z); // SEI + try std.testing.expectEqual(0x8005, z.cpu.pc); + try std.testing.expect(z.cpu.status.irq_disabled); + + cpu.run(&z); // CLD + try std.testing.expectEqual(0x8006, z.cpu.pc); + try std.testing.expect(!z.cpu.status.decimal); +} + +test "branches" { + // TODO: Test overflow as well + var z = cpu.testZes(.{ .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\sec + \\bcs #5 + \\ + \\lda #%11000000 + \\bmi #5 + \\ + \\lda #0 + \\beq #$87 + \\ + \\bit $8004 + \\bvs #2 + \\brk + \\nop + ), + }, + } }); + + cpu.run(&z); // SEC + try std.testing.expectEqual(0x8001, z.cpu.pc); + try std.testing.expect(z.cpu.status.carry); + cpu.run(&z); // BCS + try std.testing.expectEqual(0x8007, z.cpu.pc); + // LDA #80 should be skipped here. + try std.testing.expect(!z.cpu.status.negative); + + cpu.run(&z); // LDA #0 + try std.testing.expectEqual(0x8009, z.cpu.pc); + try std.testing.expect(z.cpu.status.zero); + cpu.run(&z); // BEQ (jump backwards) + try std.testing.expectEqual(0x8003, z.cpu.pc); + + cpu.run(&z); // LDA #%11000000 + try std.testing.expectEqual(0xa9, z.cpu.opcode); + try std.testing.expectEqual(0x8005, z.cpu.pc); + try std.testing.expect(z.cpu.status.negative); + + // TODO: investigate why the CPU run helper doesn't stop after branching + z.stepCpu(); // BMI + z.stepCpu(); // BMI + z.stepCpu(); // BMI + try std.testing.expectEqual(0x800c, z.cpu.pc); +} -- 2.43.0