From 5f770589c87eb6493867b09a9f8d859ae844d25f Mon Sep 17 00:00:00 2001 From: Andrew Glaze Date: Sun, 18 Aug 2024 15:55:39 -0400 Subject: [PATCH] implement undocumented opcodes --- .gitignore | 1 + Sources/CPU.swift | 144 +++++++++++++++++++++++++++------ Sources/CPU_Instructions.swift | 2 +- Sources/Trace.swift | 107 ++++++++++++++++++++++++ Sources/main.swift | 12 +-- Sources/opcodes.swift | 135 ++++++++++++++++++++++++++++++- Tests/CPU.swift | 2 + Tests/TestRom.swift | 71 ++++++++++++++++ 8 files changed, 439 insertions(+), 35 deletions(-) create mode 100644 Sources/Trace.swift create mode 100644 Tests/TestRom.swift diff --git a/.gitignore b/.gitignore index 2f5d282..ff1aad2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ DerivedData/ .netrc *.nes +*.log diff --git a/Sources/CPU.swift b/Sources/CPU.swift index b7c6e62..c7cf332 100644 --- a/Sources/CPU.swift +++ b/Sources/CPU.swift @@ -37,46 +37,57 @@ class CPU { var stackPointer: UInt8 = STACK_RESET var status: CPUFlags = [.interruptDisable, .break2] var programCounter: UInt16 = 0 - var bus: Bus? + var bus: Bus + + init(bus: Bus) { + self.bus = bus + } func getOpperandAddress(_ mode: AddressingMode) -> UInt16 { switch mode { case .Immediate: return programCounter + default: + return getAbsoluteAddress(mode, addr: programCounter) + } + } + + func getAbsoluteAddress(_ mode: AddressingMode, addr: UInt16) -> UInt16 { + switch mode { case .ZeroPage: - return UInt16(memRead(programCounter)) + return UInt16(memRead(addr)) case .Absolute: - return memReadU16(programCounter) + return memReadU16(addr) case .ZeroPage_X: - let pos = memRead(programCounter) + let pos = memRead(addr) let addr = pos &+ register_x return UInt16(addr) case .ZeroPage_Y: - let pos = memRead(programCounter) + let pos = memRead(addr) let addr = pos &+ register_y return UInt16(addr) case .Absolute_X: - let base = memReadU16(programCounter) + let base = memReadU16(addr) return base &+ UInt16(register_x) case .Absolute_Y: - let base = memReadU16(programCounter) + let base = memReadU16(addr) return base &+ UInt16(register_y) case .Indirect_X: - let base = memRead(programCounter) + let base = memRead(addr) let ptr = UInt8(base) &+ register_x let lo = memRead(UInt16(ptr)) let hi = memRead(UInt16(ptr &+ 1)) return UInt16(hi) << 8 | UInt16(lo) case .Indirect_Y: - let base = memRead(programCounter) + let base = memRead(addr) let lo = memRead(UInt16(base)) let hi = memRead(UInt16(base &+ 1)) let deref_base = UInt16(hi) << 8 | UInt16(lo) let deref = deref_base &+ UInt16(register_y) return deref - case .NoneAddressing: + default: fatalError("mode \(mode) is not implemented") } } @@ -91,17 +102,21 @@ class CPU { programCounter = self.memReadU16(0xFFFC) } - func loadAndRun(_ program: [UInt8]) { - load(program) - reset() - run() - } + //func loadAndRun(_ program: [UInt8]) { + //load(program) + //reset() + //run() + //} - func load(_ program: [UInt8]) { + //func load(_ program: [UInt8]) { + //bus = Bus(try! Rom(program)) + //for i in 0.. UInt8 { stackPointer = stackPointer &+ 1 return memRead(STACK + UInt16(stackPointer)) @@ -391,19 +483,19 @@ class CPU { extension CPU: Memory { func memRead(_ addr: UInt16) -> UInt8 { - return bus!.memRead(addr) + return bus.memRead(addr) } func memWrite(_ addr: UInt16, data: UInt8) { - bus!.memWrite(addr, data: data) + bus.memWrite(addr, data: data) } func memReadU16(_ addr: UInt16) -> UInt16 { - return bus!.memReadU16(addr) + return bus.memReadU16(addr) } func memWriteU16(_ addr: UInt16, data: UInt16) { - bus!.memWriteU16(addr, data: data) + bus.memWriteU16(addr, data: data) } } diff --git a/Sources/CPU_Instructions.swift b/Sources/CPU_Instructions.swift index 71358aa..9b36b4b 100644 --- a/Sources/CPU_Instructions.swift +++ b/Sources/CPU_Instructions.swift @@ -227,7 +227,7 @@ extension CPU { func plp() { status.rawValue = stackPop() status.remove(.break1) - status.remove(.break2) + status.insert(.break2) } func php() { diff --git a/Sources/Trace.swift b/Sources/Trace.swift new file mode 100644 index 0000000..102c65c --- /dev/null +++ b/Sources/Trace.swift @@ -0,0 +1,107 @@ +func dumpCpuState(_ cpu: CPU) -> String { + guard let opcode = OPCODES_MAP[cpu.memRead(cpu.programCounter)] else { fatalError("Not an opcode: \(cpu.memRead(cpu.programCounter))") } + var hexOpcode = [String(format: "%02X", cpu.memRead(cpu.programCounter))] + + let (memAddr, storedVal) = switch opcode.mode { + case .Immediate, .NoneAddressing: + (UInt16(0), UInt8(0)) + default: + { + let addr = cpu.getAbsoluteAddress(opcode.mode, addr: cpu.programCounter + 1) + + return (addr, cpu.memRead(addr)) + }() + } + + let operand = switch opcode.len { + case 1: + { + return switch opcode.code { + case 0x0a, 0x4a, 0x2a, 0x6a: + "A " + default: + "" + } + }() + case 2: + { + let address = cpu.memRead(cpu.programCounter + 1) + hexOpcode.append(String(format: "%02X", address)) + + return switch opcode.mode { + case .Immediate: + String(format: "#$%02X", address) + case .ZeroPage: + String(format: "$%02X = %02X", memAddr, storedVal) + case .ZeroPage_X: + String(format: "$%02X,X @ %02X = %02X", address, memAddr, storedVal) + case .ZeroPage_Y: + String(format: "$%02X,Y @ %02X = %02X", address, memAddr, storedVal) + case .Indirect_X: + String(format: "($%02X,X) @ %02X = %04X = %02X", address, address &+ cpu.register_x, memAddr, storedVal) + case .Indirect_Y: + String(format: "($%02X),Y = %04X @ %04X = %02X", address, memAddr &- UInt16(cpu.register_y), memAddr, storedVal) + case .NoneAddressing: + String(format: "$%04X", Int(cpu.programCounter) + 2 &+ Int(Int8(bitPattern: address))) + default: + fatalError("Unexpected addressing mode \(opcode.mode) has ops-length of 2. code: \(opcode.mnemonic)") + } + }() + case 3: + { + let addressLo = cpu.memRead(cpu.programCounter + 1) + let addressHi = cpu.memRead(cpu.programCounter + 2) + hexOpcode.append(String(format: "%02X", addressLo)) + hexOpcode.append(String(format: "%02X", addressHi)) + + let address = cpu.memReadU16(cpu.programCounter + 1) + + return switch opcode.mode { + case .NoneAddressing: + { + // jmp indirect + if (opcode.code == 0x6c) { + let jmpAddr = if address & 0x00FF == 0x00FF { + { + let lo = cpu.memRead(address) + let hi = cpu.memRead(address & 0xFF00) + return UInt16(hi) << 8 | UInt16(lo) + }() + } else { + cpu.memReadU16(address) + } + + return String(format: "($%04X) = %04X", address, jmpAddr) + } else { + return String(format: "$%04X", address) + } + }() + case .Absolute: + String(format: "$%04X = %02X", memAddr, storedVal) + case .Absolute_X: + String(format: "$%04X,X @ %04X = %02X", address, memAddr, storedVal) + case .Absolute_Y: + String(format: "$%04X,Y @ %04X = %02X", address, memAddr, storedVal) + default: + fatalError("Unexpected addressing mode \(opcode.mode) has ops-length of 3. code: \(opcode.mnemonic)") + } + }() + default: + "" + } + + let hexString = hexOpcode.joined(separator: " ").padding(toLength: 8, withPad: " ", startingAt: 0) + let asm = "\(String(format: "%04X", cpu.programCounter)) \(hexString) \(opcode.mnemonic.leftPadding(toLength: 4, withPad: " ")) \(operand)".padding(toLength: 47, withPad: " ", startingAt: 0) + return String(format: "\(asm) A:%02X X:%02X Y:%02X P:%02X SP:%02X", cpu.register_a, cpu.register_x, cpu.register_y, cpu.status.rawValue, cpu.stackPointer) +} + +extension String { + func leftPadding(toLength: Int, withPad character: Character) -> String { + let stringLength = self.count + if stringLength < toLength { + return String(repeatElement(character, count: toLength - stringLength)) + self + } else { + return String(self.suffix(toLength)) + } + } +} diff --git a/Sources/main.swift b/Sources/main.swift index d899e0e..883413a 100644 --- a/Sources/main.swift +++ b/Sources/main.swift @@ -94,21 +94,23 @@ func readScreenState(_ cpu: CPU, frame: inout [UInt8]) -> Bool { return update } -guard let rom = NSData(contentsOfFile: "snake.nes") else { fatalError("Rom not found") } +guard let rom = NSData(contentsOfFile: "nestest.nes") else { fatalError("Rom not found") } var gameCode = [UInt8](repeating: 0, count: rom.length) rom.getBytes(&gameCode, length: rom.length) +let bus = Bus(try! Rom(gameCode)) -var cpu = CPU() -cpu.load(gameCode) +var cpu = CPU(bus: bus) +//cpu.load(gameCode) cpu.reset() +cpu.programCounter = 0xC000 var screenState = [UInt8](repeating: 0, count: 32 * 3 * 32) var rng = SystemRandomNumberGenerator() - cpu.run(onCycle: { + print(dumpCpuState(cpu)) handleUserInput(cpu, event: &event) - cpu.memWrite(0xfe, data: UInt8.random(in: 1...16, using: &rng)) + //cpu.memWrite(0xfe, data: UInt8.random(in: 1...16, using: &rng)) if readScreenState(cpu, frame: &screenState) { SDL_UpdateTexture(texture, nil, screenState, 32 * 3) diff --git a/Sources/opcodes.swift b/Sources/opcodes.swift index ced18c5..2620e02 100644 --- a/Sources/opcodes.swift +++ b/Sources/opcodes.swift @@ -16,7 +16,7 @@ let CPU_OP_CODES: [OpCode] = [ OpCode(code: 0x75, mnemonic: "ADC", len: 2, cycles: 4, mode: .ZeroPage_X), OpCode(code: 0x6d, mnemonic: "ADC", len: 3, cycles: 4, mode: .Absolute), OpCode(code: 0x7d, mnemonic: "ADC", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_X), - OpCode(code: 0x79, mnemonic: "ADC", len: 2, cycles: 5 /* +1 if page crossed */, mode: .Absolute_Y), + OpCode(code: 0x79, mnemonic: "ADC", len: 3, cycles: 5 /* +1 if page crossed */, mode: .Absolute_Y), OpCode(code: 0x61, mnemonic: "ADC", len: 2, cycles: 5, mode: .Indirect_X), OpCode(code: 0x71, mnemonic: "ADC", len: 2, cycles: 5 /* +1 if page crossed */, mode: .Indirect_Y), @@ -165,11 +165,11 @@ let CPU_OP_CODES: [OpCode] = [ OpCode(code: 0x91, mnemonic: "STA", len: 2, cycles: 6, mode: .Indirect_Y), OpCode(code: 0x86, mnemonic: "STX", len: 2, cycles: 3, mode: .ZeroPage), - OpCode(code: 0x96, mnemonic: "STX", len: 2, cycles: 4, mode: .Absolute_Y), + OpCode(code: 0x96, mnemonic: "STX", len: 2, cycles: 4, mode: .ZeroPage_Y), OpCode(code: 0x8e, mnemonic: "STX", len: 3, cycles: 4, mode: .Absolute), OpCode(code: 0x84, mnemonic: "STY", len: 2, cycles: 3, mode: .ZeroPage), - OpCode(code: 0x94, mnemonic: "STY", len: 2, cycles: 4, mode: .Absolute_X), + OpCode(code: 0x94, mnemonic: "STY", len: 2, cycles: 4, mode: .ZeroPage_X), OpCode(code: 0x8c, mnemonic: "STY", len: 3, cycles: 4, mode: .Absolute), /// Flag Clears @@ -193,6 +193,135 @@ let CPU_OP_CODES: [OpCode] = [ OpCode(code: 0x68, mnemonic: "PLA", len: 1, cycles: 4, mode: .NoneAddressing), OpCode(code: 0x08, mnemonic: "PHP", len: 1, cycles: 3, mode: .NoneAddressing), OpCode(code: 0x28, mnemonic: "PLP", len: 1, cycles: 4, mode: .NoneAddressing), + + /// Undocumented + + OpCode(code: 0xc7, mnemonic: "*DCP", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0xd7, mnemonic: "*DCP", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0xCF, mnemonic: "*DCP", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0xdF, mnemonic: "*DCP", len: 3, cycles: 7, mode: .Absolute_X), + OpCode(code: 0xdb, mnemonic: "*DCP", len: 3, cycles: 7, mode: .Absolute_Y), + OpCode(code: 0xd3, mnemonic: "*DCP", len: 2, cycles: 8, mode: .Indirect_Y), + OpCode(code: 0xc3, mnemonic: "*DCP", len: 2, cycles: 8, mode: .Indirect_X), + + + OpCode(code: 0x27, mnemonic: "*RLA", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0x37, mnemonic: "*RLA", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0x2F, mnemonic: "*RLA", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0x3F, mnemonic: "*RLA", len: 3, cycles: 7, mode: .Absolute_X), + OpCode(code: 0x3b, mnemonic: "*RLA", len: 3, cycles: 7, mode: .Absolute_Y), + OpCode(code: 0x33, mnemonic: "*RLA", len: 2, cycles: 8, mode: .Indirect_Y), + OpCode(code: 0x23, mnemonic: "*RLA", len: 2, cycles: 8, mode: .Indirect_X), + + OpCode(code: 0x07, mnemonic: "*SLO", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0x17, mnemonic: "*SLO", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0x0F, mnemonic: "*SLO", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0x1f, mnemonic: "*SLO", len: 3, cycles: 7, mode: .Absolute_X), + OpCode(code: 0x1b, mnemonic: "*SLO", len: 3, cycles: 7, mode: .Absolute_Y), + OpCode(code: 0x03, mnemonic: "*SLO", len: 2, cycles: 8, mode: .Indirect_X), + OpCode(code: 0x13, mnemonic: "*SLO", len: 2, cycles: 8, mode: .Indirect_Y), + + OpCode(code: 0x47, mnemonic: "*SRE", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0x57, mnemonic: "*SRE", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0x4F, mnemonic: "*SRE", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0x5f, mnemonic: "*SRE", len: 3, cycles: 7, mode: .Absolute_X), + OpCode(code: 0x5b, mnemonic: "*SRE", len: 3, cycles: 7, mode: .Absolute_Y), + OpCode(code: 0x43, mnemonic: "*SRE", len: 2, cycles: 8, mode: .Indirect_X), + OpCode(code: 0x53, mnemonic: "*SRE", len: 2, cycles: 8, mode: .Indirect_Y), + + + OpCode(code: 0x80, mnemonic: "*NOP", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0x82, mnemonic: "*NOP", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0x89, mnemonic: "*NOP", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0xc2, mnemonic: "*NOP", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0xe2, mnemonic: "*NOP", len: 2, cycles: 2, mode: .Immediate), + + + OpCode(code: 0xCB, mnemonic: "*AXS", len: 2, cycles: 2, mode: .Immediate), + + OpCode(code: 0x6B, mnemonic: "*ARR", len: 2, cycles: 2, mode: .Immediate), + + OpCode(code: 0xeb, mnemonic: "*SBC", len: 2, cycles: 2, mode: .Immediate), + + OpCode(code: 0x0b, mnemonic: "*ANC", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0x2b, mnemonic: "*ANC", len: 2, cycles: 2, mode: .Immediate), + + OpCode(code: 0x4b, mnemonic: "*ALR", len: 2, cycles: 2, mode: .Immediate), + + OpCode(code: 0x04, mnemonic: "*NOP", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0x44, mnemonic: "*NOP", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0x64, mnemonic: "*NOP", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0x14, mnemonic: "*NOP", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0x34, mnemonic: "*NOP", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0x54, mnemonic: "*NOP", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0x74, mnemonic: "*NOP", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0xd4, mnemonic: "*NOP", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0xf4, mnemonic: "*NOP", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0x0c, mnemonic: "*NOP", len: 3, cycles: 4, mode: .Absolute), + OpCode(code: 0x1c, mnemonic: "*NOP", len: 3, cycles: 4, mode: .Absolute_X), + OpCode(code: 0x3c, mnemonic: "*NOP", len: 3, cycles: 4, mode: .Absolute_X), + OpCode(code: 0x5c, mnemonic: "*NOP", len: 3, cycles: 4, mode: .Absolute_X), + OpCode(code: 0x7c, mnemonic: "*NOP", len: 3, cycles: 4, mode: .Absolute_X), + OpCode(code: 0xdc, mnemonic: "*NOP", len: 3, cycles: 4, mode: .Absolute_X), + OpCode(code: 0xfc, mnemonic: "*NOP", len: 3, cycles: 4, mode: .Absolute_X), + + OpCode(code: 0x67, mnemonic: "*RRA", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0x77, mnemonic: "*RRA", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0x6f, mnemonic: "*RRA", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0x7f, mnemonic: "*RRA", len: 3, cycles: 7, mode: .Absolute_X), + OpCode(code: 0x7b, mnemonic: "*RRA", len: 3, cycles: 7, mode: .Absolute_Y), + OpCode(code: 0x63, mnemonic: "*RRA", len: 2, cycles: 8, mode: .Indirect_X), + OpCode(code: 0x73, mnemonic: "*RRA", len: 2, cycles: 8, mode: .Indirect_Y), + + + OpCode(code: 0xe7, mnemonic: "*ISB", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0xf7, mnemonic: "*ISB", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0xef, mnemonic: "*ISB", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0xff, mnemonic: "*ISB", len: 3, cycles: 7, mode: .Absolute_X), + OpCode(code: 0xfb, mnemonic: "*ISB", len: 3, cycles: 7, mode: .Absolute_Y), + OpCode(code: 0xe3, mnemonic: "*ISB", len: 2, cycles: 8, mode: .Indirect_X), + OpCode(code: 0xf3, mnemonic: "*ISB", len: 2, cycles: 8, mode: .Indirect_Y), + + OpCode(code: 0x02, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x12, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x22, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x32, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x42, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x52, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x62, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x72, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x92, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xb2, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xd2, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xf2, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + + OpCode(code: 0x1a, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x3a, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x5a, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x7a, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xda, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xfa, mnemonic: "*NOP", len: 1, cycles: 2, mode: .NoneAddressing), + + OpCode(code: 0xab, mnemonic: "*LXA", len: 2, cycles: 3, mode: .Immediate), //todo: highly unstable and not used + OpCode(code: 0x8b, mnemonic: "*XAA", len: 2, cycles: 3, mode: .Immediate), //todo: highly unstable and not used + OpCode(code: 0xbb, mnemonic: "*LAS", len: 3, cycles: 2, mode: .Absolute_Y), //todo: highly unstable and not used + OpCode(code: 0x9b, mnemonic: "*TAS", len: 3, cycles: 2, mode: .Absolute_Y), //todo: highly unstable and not used + OpCode(code: 0x93, mnemonic: "*AHX", len: 2, cycles: 8, mode: .Indirect_Y), //todo: highly unstable and not used + OpCode(code: 0x9f, mnemonic: "*AHX", len: 3, cycles: 4, mode: .Absolute_Y), //todo: highly unstable and not used + OpCode(code: 0x9e, mnemonic: "*SHX", len: 3, cycles: 4, mode: .Absolute_Y), //todo: highly unstable and not used + OpCode(code: 0x9c, mnemonic: "*SHY", len: 3, cycles: 4, mode: .Absolute_X), //todo: highly unstable and not used + + OpCode(code: 0xa7, mnemonic: "*LAX", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0xb7, mnemonic: "*LAX", len: 2, cycles: 4, mode: .ZeroPage_Y), + OpCode(code: 0xaf, mnemonic: "*LAX", len: 3, cycles: 4, mode: .Absolute), + OpCode(code: 0xbf, mnemonic: "*LAX", len: 3, cycles: 4, mode: .Absolute_Y), + OpCode(code: 0xa3, mnemonic: "*LAX", len: 2, cycles: 6, mode: .Indirect_X), + OpCode(code: 0xb3, mnemonic: "*LAX", len: 2, cycles: 5, mode: .Indirect_Y), + + OpCode(code: 0x87, mnemonic: "*SAX", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0x97, mnemonic: "*SAX", len: 2, cycles: 4, mode: .ZeroPage_Y), + OpCode(code: 0x8f, mnemonic: "*SAX", len: 3, cycles: 4, mode: .Absolute), + OpCode(code: 0x83, mnemonic: "*SAX", len: 2, cycles: 6, mode: .Indirect_X), ] let OPCODES_MAP: [UInt8: OpCode] = { diff --git a/Tests/CPU.swift b/Tests/CPU.swift index d1b9e95..b2e6095 100644 --- a/Tests/CPU.swift +++ b/Tests/CPU.swift @@ -1,6 +1,7 @@ import XCTest @testable import SwiftNES +/* class CPUTests: XCTestCase { func test_lda() { let cpu = CPU() @@ -364,3 +365,4 @@ extension CPU { expect.wait(sender) } } + */ diff --git a/Tests/TestRom.swift b/Tests/TestRom.swift new file mode 100644 index 0000000..7baa34e --- /dev/null +++ b/Tests/TestRom.swift @@ -0,0 +1,71 @@ +import XCTest +@testable import SwiftNES + +class TestRom: XCTestCase { + func testFormatTrace() { + let expect = XCTestExpectation() + let bus = Bus(try! Rom(getRom())) + bus.memWrite(100, data: 0xa2) + bus.memWrite(101, data: 0x01) + bus.memWrite(102, data: 0xca) + bus.memWrite(103, data: 0x88) + bus.memWrite(104, data: 0x00) + let cpu = CPU(bus: bus) + cpu.programCounter = 0x64 + cpu.register_a = 1 + cpu.register_x = 2 + cpu.register_y = 3 + + var result: [String] = [] + cpu.run( + onCycle: { result.append(dumpCpuState(cpu)) }, + onComplete: { expect.fulfill() } + ) + wait(for: [expect], timeout: 0.5) + XCTAssertEqual( + "0064 A2 01 LDX #$01 A:01 X:02 Y:03 P:24 SP:FD", + result[0] + ) + XCTAssertEqual( + "0066 CA DEX A:01 X:01 Y:03 P:24 SP:FD", + result[1] + ) + XCTAssertEqual( + "0067 88 DEY A:01 X:00 Y:03 P:26 SP:FD", + result[2] + ) + } + + func testFormatMemAccess() { + let expect = XCTestExpectation() + let bus = Bus(try! Rom(getRom())) + bus.memWrite(100, data: 0x11) + bus.memWrite(101, data: 0x33 + ) + bus.memWrite(0x33, data: 00) + bus.memWrite(0x34, data: 04) + + bus.memWrite(0x400, data: 0xAA) + let cpu = CPU(bus: bus) + cpu.programCounter = 0x64 + cpu.register_y = 0 + + var result: [String] = [] + cpu.run( + onCycle: { result.append(dumpCpuState(cpu)) }, + onComplete: { expect.fulfill() } + ) + wait(for: [expect], timeout: 0.5) + XCTAssertEqual( + "0064 11 33 ORA ($33),Y = 0400 @ 0400 = AA A:00 X:00 Y:00 P:24 SP:FD", + result[0] + ) + } + + func getRom() -> [UInt8] { + guard let rom = NSData(contentsOfFile: "nestest.nes") else { fatalError("Rom not found") } + var gameCode = [UInt8](repeating: 0, count: rom.length) + rom.getBytes(&gameCode, length: rom.length) + return gameCode; + } +}