diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..ae693b8 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swiftsdl2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ctreffs/SwiftSDL2.git", + "state" : { + "revision" : "30a2886bd68e43fc19ba29b63ffe230ac0e4db7a", + "version" : "1.4.1" + } + } + ], + "version" : 2 +} diff --git a/Package.swift b/Package.swift index 2921c5d..1be4441 100644 --- a/Package.swift +++ b/Package.swift @@ -5,11 +5,18 @@ import PackageDescription let package = Package( name: "SwiftNES", + platforms: [.macOS("11.0")], + dependencies: [ + .package(url: "https://github.com/ctreffs/SwiftSDL2.git", from: "1.4.0") + ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .executableTarget( - name: "SwiftNES"), + name: "SwiftNES", + dependencies: [ + .product(name: "SDL", package: "SwiftSDL2") + ]), .testTarget(name: "SwiftNESTests", dependencies: ["SwiftNES"]) ] ) diff --git a/Sources/CPU.swift b/Sources/CPU.swift index 97fed05..741432b 100644 --- a/Sources/CPU.swift +++ b/Sources/CPU.swift @@ -1,5 +1,3 @@ - - enum AddressingMode { case Immediate case ZeroPage @@ -13,12 +11,28 @@ enum AddressingMode { case NoneAddressing } +struct CPUFlags: OptionSet { + var rawValue: UInt8 + + static let carry = CPUFlags(rawValue: 0b00000001) + static let zero = CPUFlags(rawValue: 0b00000010) + static let interruptDisable = CPUFlags(rawValue: 0b00000100) + static let decimalMode = CPUFlags(rawValue: 0b00001000) + static let break1 = CPUFlags(rawValue: 0b00010000) + static let break2 = CPUFlags(rawValue: 0b00100000) + static let overflow = CPUFlags(rawValue: 0b01000000) + static let negative = CPUFlags(rawValue: 0b10000000) +} + +let STACK: UInt16 = 0x0100 +let STACK_RESET: UInt8 = 0xfd + class CPU { var register_a: UInt8 = 0 var register_x: UInt8 = 0 var register_y: UInt8 = 0 - - var status: UInt8 = 0 + var stackPointer: UInt8 = STACK_RESET + var status: CPUFlags = [.interruptDisable, .break2] var programCounter: UInt16 = 0 private var memory = [UInt8](repeating: 0, count: 0xFFFF) @@ -65,32 +79,12 @@ class CPU { } } - func memRead(_ addr: UInt16) -> UInt8 { - memory[Int(addr)] - } - - func memReadU16(_ addr: UInt16) -> UInt16 { - let lo = UInt16(memRead(addr)) - let hi = UInt16(memRead(addr + 1)) - return (hi << 8) | lo - } - - func memWriteU16(addr: UInt16, data: UInt16) { - let hi = UInt8(data >> 8) - let lo = UInt8(data & 0xff) - self.memWrite(addr: addr, data: lo) - self.memWrite(addr: addr + 1, data: hi) - } - - func memWrite(addr: UInt16, data: UInt8) { - memory[Int(addr)] = data - } - func reset() { register_a = 0 register_x = 0 register_y = 0 - status = 0 + stackPointer = STACK_RESET + status = [.interruptDisable, .break2] programCounter = self.memReadU16(0xFFFC) } @@ -102,14 +96,21 @@ class CPU { } func load(_ program: [UInt8]) { - memory[0x8000 ..< (0x8000 + program.count)] = program[0.. ()) { let opcodes = OPCODES_MAP while true { + callback() let code = memRead(programCounter) programCounter += 1 @@ -123,11 +124,151 @@ class CPU { /// STA case 0x85, 0x95, 0x8d, 0x9d, 0x99, 0x81, 0x91: sta(opcode.mode) + case 0xd8: + status.remove(.decimalMode) + case 0x58: + status.remove(.interruptDisable) + case 0xb8: + status.remove(.overflow) + case 0x18: + clearCarryFlag() + case 0x38: + setCarryFlag() + case 0x78: + status.insert(.interruptDisable) + case 0xf8: + status.insert(.decimalMode) + case 0x48: + stackPush(register_a) + case 0x68: + pla() + case 0x08: + php() + case 0x28: + plp() + case 0x69, 0x65, 0x75, 0x6d, 0x7d, 0x79, 0x61, 0x71: + adc(opcode.mode) + case 0xe9, 0xe5, 0xf5, 0xed, 0xfd, 0xf9, 0xe1, 0xf1: + sbc(opcode.mode) + case 0x29, 0x25, 0x35, 0x2d, 0x3d, 0x39, 0x21, 0x31: + and(opcode.mode) + case 0x49, 0x45, 0x55, 0x4d, 0x5d, 0x59, 0x41, 0x51: + eor(opcode.mode) + case 0x09, 0x05, 0x15, 0x0d, 0x1d, 0x19, 0x01, 0x11: + ora(opcode.mode) + case 0x4a: + lsrAccumulator() + case 0x46, 0x56, 0x4e, 0x5e: + _ = lsr(opcode.mode) + case 0x0a: + aslAccumulator() + case 0x06, 0x16, 0x0e, 0x1e: + _ = asl(opcode.mode) + case 0x2a: + rolAccumulator() + case 0x26, 0x36, 0x2e, 0x3e: + _ = rol(opcode.mode) + case 0x6a: + rorAccumulator() + case 0x66, 0x76, 0x6e, 0x7e: + _ = ror(opcode.mode) + case 0xe6, 0xf6, 0xee, 0xfe: + _ = inc(opcode.mode) + case 0xc8: + iny() + case 0xc6, 0xd6, 0xce, 0xde: + _ = dec(opcode.mode) + case 0xca: + dex() + case 0x88: + dey() + case 0xc9, 0xc5, 0xd5, 0xcd, 0xdd, 0xd9, 0xc1, 0xd1: + compare(mode: opcode.mode, compare_with: register_a) + case 0xc0, 0xc4, 0xcc: + compare(mode: opcode.mode, compare_with: register_y) + case 0xe0, 0xe4, 0xec: + compare(mode: opcode.mode, compare_with: register_x) + case 0x4c: + let memAddr = memReadU16(programCounter) + programCounter = memAddr + case 0x6c: + let memAddr = memReadU16(programCounter) + //6502 bug mode with with page boundary: + // if address $3000 contains $40, $30FF contains $80, and $3100 contains $50, + // the result of JMP ($30FF) will be a transfer of control to $4080 rather than $5080 as you intended + // i.e. the 6502 took the low byte of the address from $30FF and the high byte from $3000 + let indirectRef: UInt16 + if memAddr & 0x00ff == 0x00ff { + let lo = memRead(memAddr) + let hi = memRead(memAddr & 0x00ff) + indirectRef = UInt16(hi) << 8 | UInt16(lo) + } else { + indirectRef = memReadU16(memAddr) + } + + programCounter = indirectRef + + case 0x20: + stackPushU16(programCounter + 2 - 1) + let targetAddr = memReadU16(programCounter) + programCounter = targetAddr + case 0x60: + programCounter = stackPopU16() + 1 + case 0x40: + status.rawValue = stackPop() + status.remove(.break1) + status.remove(.break2) + + programCounter = stackPopU16() + case 0xd0: + branch(!status.contains(.zero)) + case 0x70: + branch(status.contains(.overflow)) + case 0x50: + branch(!status.contains(.overflow)) + case 0x10: + branch(!status.contains(.negative)) + case 0x30: + branch(status.contains(.negative)) + case 0xf0: + branch(status.contains(.zero)) + case 0xb0: + branch(status.contains(.carry)) + case 0x90: + branch(!status.contains(.carry)) + case 0x24, 0x2c: + bit(opcode.mode) + case 0x86, 0x96, 0x8e: + let addr = getOpperandAddress(opcode.mode) + memWrite(addr, data: register_x) + case 0x84, 0x94, 0x8c: + let addr = getOpperandAddress(opcode.mode) + memWrite(addr, data: register_y) + case 0xa2, 0xa6, 0xb6, 0xae, 0xbe: + ldx(opcode.mode) + case 0xa0, 0xa4, 0xb4, 0xac, 0xbc: + ldy(opcode.mode) + case 0xea: + continue + case 0xa8: + register_y = register_x + updateZeroAndNegativeFlags(register_y) + case 0xba: + register_x = stackPointer + updateZeroAndNegativeFlags(register_x) + case 0x8a: + register_a = register_x + updateZeroAndNegativeFlags(register_a) + case 0x9a: + stackPointer = register_x + case 0x98: + register_a = register_y + updateZeroAndNegativeFlags(register_a) /// TAX - case 0xAA: + case 0xaa: tax() /// INX - case 0xE8: + case 0xe8: inx() /// BRK case 0x00: @@ -143,40 +284,136 @@ class CPU { func updateZeroAndNegativeFlags(_ result: UInt8) { if result == 0 { - status = status | 0b0000_0010 + status.insert(.zero) } else { - status = status & 0b1111_1101 + status.remove(.zero) } if result & 0b1000_0000 != 0 { - status = status | 0b1000_0000 + status.insert(.negative) } else { - status = status & 0b0111_1111 + status.remove(.negative) } } - func lda(_ mode: AddressingMode) { - let addr = getOpperandAddress(mode) - let value = memRead(addr) - + func setRegisterA(_ value: UInt8) { register_a = value updateZeroAndNegativeFlags(register_a) } - func tax() { - register_x = register_a - updateZeroAndNegativeFlags(register_x) + func setCarryFlag() { + status.insert(.carry) } - func inx() { - register_x = register_x &+ 1 - updateZeroAndNegativeFlags(register_x) + func clearCarryFlag() { + status.remove(.carry) } - func sta(_ mode: AddressingMode) { + /// note: ignoring decimal mode + /// http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html + func addToRegisterA(_ data: UInt8) { + let shouldCarry = status.contains(.carry) ? 1 : 0 + let sum = UInt16(register_a) + UInt16(data) + UInt16(shouldCarry) + + let carry = sum > 0xff + + if carry { + status.insert(.carry) + } else { + status.remove(.carry) + } + + let result = UInt8(sum) + + if (data ^ result) & (result ^ register_a) & 0x80 != 0 { + status.insert(.overflow) + } else { + status.remove(.overflow) + } + + setRegisterA(result) + } + + func stackPop() -> UInt8 { + stackPointer = stackPointer &+ 1 + return memRead(STACK + UInt16(stackPointer)) + } + + func stackPush(_ data: UInt8) { + memWrite(STACK + UInt16(stackPointer), data: data) + stackPointer = stackPointer &- 1 + } + + func stackPopU16() -> UInt16 { + let lo = UInt16(stackPop()) + let hi = UInt16(stackPop()) + + return hi << 8 | lo + } + + func stackPushU16(_ data: UInt16) { + let hi = UInt8(data >> 8) + let lo = UInt8(data & 0xff) + stackPush(hi) + stackPush(lo) + } + + func compare(mode: AddressingMode, compare_with: UInt8) { let addr = getOpperandAddress(mode) - memWrite(addr: addr, data: register_a) + let data = memRead(addr) + if data <= compare_with { + status.insert(.carry) + } else { + status.remove(.carry) + } + + updateZeroAndNegativeFlags(compare_with &- data) } + func branch(_ condition: Bool) { + if condition { + let addr = memRead(programCounter) + // print(addr) + let jump: Int8 = Int8(bitPattern: addr) + let jump_addr = programCounter &+ 1 &+ UInt16(bitPattern: Int16(jump)) + programCounter = jump_addr + } + } +} + +extension CPU: Memory { + func memRead(_ addr: UInt16) -> UInt8 { + memory[Int(addr)] + } + + func memWrite(_ addr: UInt16, data: UInt8) { + memory[Int(addr)] = data + } +} + + +protocol Memory { + func memRead(_ addr: UInt16) -> UInt8 + + func memWrite(_ addr: UInt16, data: UInt8) + + func memReadU16(_ addr: UInt16) -> UInt16 + + func memWriteU16(_ addr: UInt16, data: UInt16) +} + +extension Memory { + func memReadU16(_ addr: UInt16) -> UInt16 { + let lo = UInt16(memRead(addr)) + let hi = UInt16(memRead(addr + 1)) + return (hi << 8) | lo + } + + func memWriteU16(_ addr: UInt16, data: UInt16) { + let hi = UInt8(data >> 8) + let lo = UInt8(data & 0xff) + self.memWrite(addr, data: lo) + self.memWrite(addr + 1, data: hi) + } } diff --git a/Sources/CPU_Instructions.swift b/Sources/CPU_Instructions.swift new file mode 100644 index 0000000..f9a5187 --- /dev/null +++ b/Sources/CPU_Instructions.swift @@ -0,0 +1,262 @@ +extension CPU { + func lda(_ mode: AddressingMode) { + let addr = getOpperandAddress(mode) + let value = memRead(addr) + + setRegisterA(value) + } + + func ldy(_ mode: AddressingMode) { + let addr = getOpperandAddress(mode) + let data = memRead(addr) + register_y = data + updateZeroAndNegativeFlags(register_y) + } + + func ldx(_ mode: AddressingMode) { + let addr = getOpperandAddress(mode) + let data = memRead(addr) + register_x = data + updateZeroAndNegativeFlags(register_x) + } + + func sta(_ mode: AddressingMode) { + let addr = getOpperandAddress(mode) + memWrite(addr, data: register_a) + } + + func and(_ mode: AddressingMode) { + let addr = getOpperandAddress(mode) + let data = memRead(addr) + self.setRegisterA(data & register_a) + } + + func eor(_ mode: AddressingMode) { + let addr = getOpperandAddress(mode) + let data = memRead(addr) + setRegisterA(data ^ register_a) + } + + func ora(_ mode: AddressingMode) { + let addr = getOpperandAddress(mode) + let data = memRead(addr) + setRegisterA(data | register_a) + } + + func tax() { + register_x = register_a + updateZeroAndNegativeFlags(register_x) + } + + func inx() { + register_x = register_x &+ 1 + updateZeroAndNegativeFlags(register_x) + } + + func iny() { + register_y = register_y &+ 1 + updateZeroAndNegativeFlags(register_y) + } + + func sbc(_ mode: AddressingMode) { + let addr = getOpperandAddress(mode) + let data = memRead(addr) + let res = Int8(data) &* -1 + addToRegisterA(UInt8(res &- 1)) + } + + func adc(_ mode: AddressingMode) { + let addr = getOpperandAddress(mode) + let data = memRead(addr) + addToRegisterA(data) + } + + func aslAccumulator() { + var data = register_a + if data >> 7 == 1 { + setCarryFlag() + } else { + clearCarryFlag() + } + data = data << 1 + setRegisterA(data) + } + + func asl(_ mode: AddressingMode) -> UInt8 { + let addr = getOpperandAddress(mode) + var data = memRead(addr) + if data >> 7 == 1 { + setCarryFlag() + } else { + clearCarryFlag() + } + data = data << 1 + memWrite(addr, data: data) + updateZeroAndNegativeFlags(data) + return data + } + + func lsrAccumulator() { + var data = register_a + if data & 1 == 1 { + setCarryFlag() + } else { + clearCarryFlag() + } + data = data >> 1 + setRegisterA(data) + } + + func lsr(_ mode: AddressingMode) -> UInt8 { + let addr = getOpperandAddress(mode) + var data = memRead(addr) + if data & 1 == 1 { + setCarryFlag() + } else { + clearCarryFlag() + } + data = data >> 1 + memWrite(addr, data: data) + updateZeroAndNegativeFlags(data) + return data + } + + func rolAccumulator() { + var data = register_a + let oldCarry = status.contains(.carry) + + if data >> 7 == 1 { + setCarryFlag() + } else { + clearCarryFlag() + } + data = data << 1 + if oldCarry { + data = data | 1 + } + setRegisterA(data) + } + + func rol(_ mode: AddressingMode) -> UInt8 { + let addr = getOpperandAddress(mode) + var data = memRead(addr) + let oldCarry = status.contains(.carry) + + if data >> 7 == 1 { + setCarryFlag() + } else { + clearCarryFlag() + } + + data = data << 1 + if oldCarry { + data = data | 1 + } + memWrite(addr, data: data) + updateZeroAndNegativeFlags(data) + return data + } + + func rorAccumulator() { + var data = register_a + let oldCarry = status.contains(.carry) + + if data & 1 == 1 { + setCarryFlag() + } else { + clearCarryFlag() + } + data = data >> 1 + if oldCarry { + data = data | 0b10000000 + } + setRegisterA(data) + } + + func ror(_ mode: AddressingMode) -> UInt8 { + let addr = getOpperandAddress(mode) + var data = memRead(addr) + let oldCarry = status.contains(.carry) + if data & 1 == 1 { + setCarryFlag() + } else { + clearCarryFlag() + } + data = data >> 1 + if oldCarry { + data = data | 0b10000000 + } + memWrite(addr, data: data) + updateZeroAndNegativeFlags(data) + return data + } + + func inc(_ mode: AddressingMode) -> UInt8 { + let addr = getOpperandAddress(mode) + var data = memRead(addr) + data = data &+ 1 + memWrite(addr, data: data) + updateZeroAndNegativeFlags(data) + return data + } + + func dey() { + register_y = register_y &- 1 + updateZeroAndNegativeFlags(register_y) + } + + func dex() { + register_x = register_x &- 1 + updateZeroAndNegativeFlags(register_x) + } + + func dec(_ mode: AddressingMode) -> UInt8 { + let addr = getOpperandAddress(mode) + var data = memRead(addr) + data = data &- 1 + memWrite(addr, data: data) + updateZeroAndNegativeFlags(data) + return data + } + + func pla() { + let data = stackPop() + setRegisterA(data) + } + + func plp() { + status.rawValue = stackPop() + status.remove(.break1) + status.remove(.break2) + } + + func php() { + var flags = status + flags.insert(.break1) + flags.insert(.break2) + stackPush(flags.rawValue) + } + + func bit(_ mode: AddressingMode) { + let addr = getOpperandAddress(mode) + let data = memRead(addr) + let and = register_a & data + if and == 0 { + status.insert(.zero) + } else { + status.remove(.zero) + } + + if data & 0b10000000 > 0 { + status.insert(.negative) + } else { + status.remove(.negative) + } + + if data & 0b01000000 > 0 { + status.insert(.overflow) + } else { + status.remove(.overflow) + } + } +} diff --git a/Sources/Util.swift b/Sources/Util.swift new file mode 100644 index 0000000..e69de29 diff --git a/Sources/main.swift b/Sources/main.swift index 7ede841..dbfb28f 100644 --- a/Sources/main.swift +++ b/Sources/main.swift @@ -1,7 +1,59 @@ // The Swift Programming Language // https://docs.swift.org/swift-book -let cpu = CPU() -let program: [UInt8] = [0xa9, 0xc0, 0xaa, 0xe8, 0x00] -cpu.loadAndRun(program) -print(cpu.status) +import SDL + +guard SDL_Init(SDL_INIT_VIDEO) == 0 else { + fatalError("SDL could not initialize! SDL_Error: \(String(cString: SDL_GetError()))") +} + +let window = SDL_CreateWindow( + "Snake Game", + Int32(SDL_WINDOWPOS_CENTERED_MASK), Int32(SDL_WINDOWPOS_CENTERED_MASK), + Int32(32.0 * 10.0), Int32(32.0 * 10.0), + SDL_WINDOW_SHOWN.rawValue) + +let canvas = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC.rawValue) + +SDL_RenderSetScale(canvas, 10.0, 10.0) + + +let gameCode: [UInt8] = [ + 0x20, 0x06, 0x06, 0x20, 0x38, 0x06, 0x20, 0x0d, 0x06, 0x20, 0x2a, 0x06, 0x60, 0xa9, 0x02, 0x85, + 0x02, 0xa9, 0x04, 0x85, 0x03, 0xa9, 0x11, 0x85, 0x10, 0xa9, 0x10, 0x85, 0x12, 0xa9, 0x0f, 0x85, + 0x14, 0xa9, 0x04, 0x85, 0x11, 0x85, 0x13, 0x85, 0x15, 0x60, 0xa5, 0xfe, 0x85, 0x00, 0xa5, 0xfe, + 0x29, 0x03, 0x18, 0x69, 0x02, 0x85, 0x01, 0x60, 0x20, 0x4d, 0x06, 0x20, 0x8d, 0x06, 0x20, 0xc3, + 0x06, 0x20, 0x19, 0x07, 0x20, 0x20, 0x07, 0x20, 0x2d, 0x07, 0x4c, 0x38, 0x06, 0xa5, 0xff, 0xc9, + 0x77, 0xf0, 0x0d, 0xc9, 0x64, 0xf0, 0x14, 0xc9, 0x73, 0xf0, 0x1b, 0xc9, 0x61, 0xf0, 0x22, 0x60, + 0xa9, 0x04, 0x24, 0x02, 0xd0, 0x26, 0xa9, 0x01, 0x85, 0x02, 0x60, 0xa9, 0x08, 0x24, 0x02, 0xd0, + 0x1b, 0xa9, 0x02, 0x85, 0x02, 0x60, 0xa9, 0x01, 0x24, 0x02, 0xd0, 0x10, 0xa9, 0x04, 0x85, 0x02, + 0x60, 0xa9, 0x02, 0x24, 0x02, 0xd0, 0x05, 0xa9, 0x08, 0x85, 0x02, 0x60, 0x60, 0x20, 0x94, 0x06, + 0x20, 0xa8, 0x06, 0x60, 0xa5, 0x00, 0xc5, 0x10, 0xd0, 0x0d, 0xa5, 0x01, 0xc5, 0x11, 0xd0, 0x07, + 0xe6, 0x03, 0xe6, 0x03, 0x20, 0x2a, 0x06, 0x60, 0xa2, 0x02, 0xb5, 0x10, 0xc5, 0x10, 0xd0, 0x06, + 0xb5, 0x11, 0xc5, 0x11, 0xf0, 0x09, 0xe8, 0xe8, 0xe4, 0x03, 0xf0, 0x06, 0x4c, 0xaa, 0x06, 0x4c, + 0x35, 0x07, 0x60, 0xa6, 0x03, 0xca, 0x8a, 0xb5, 0x10, 0x95, 0x12, 0xca, 0x10, 0xf9, 0xa5, 0x02, + 0x4a, 0xb0, 0x09, 0x4a, 0xb0, 0x19, 0x4a, 0xb0, 0x1f, 0x4a, 0xb0, 0x2f, 0xa5, 0x10, 0x38, 0xe9, + 0x20, 0x85, 0x10, 0x90, 0x01, 0x60, 0xc6, 0x11, 0xa9, 0x01, 0xc5, 0x11, 0xf0, 0x28, 0x60, 0xe6, + 0x10, 0xa9, 0x1f, 0x24, 0x10, 0xf0, 0x1f, 0x60, 0xa5, 0x10, 0x18, 0x69, 0x20, 0x85, 0x10, 0xb0, + 0x01, 0x60, 0xe6, 0x11, 0xa9, 0x06, 0xc5, 0x11, 0xf0, 0x0c, 0x60, 0xc6, 0x10, 0xa5, 0x10, 0x29, + 0x1f, 0xc9, 0x1f, 0xf0, 0x01, 0x60, 0x4c, 0x35, 0x07, 0xa0, 0x00, 0xa5, 0xfe, 0x91, 0x00, 0x60, + 0xa6, 0x03, 0xa9, 0x00, 0x81, 0x10, 0xa2, 0x00, 0xa9, 0x01, 0x81, 0x10, 0x60, 0xa2, 0x00, 0xea, + 0xea, 0xca, 0xd0, 0xfb, 0x60 +] + +print(gameCode[32]) + + +var cpu = CPU() +cpu.load(gameCode) +cpu.reset() + +cpu.run { + // TODO: + // read user input and write it to mem[0xFF] + // update mem[0xFE] with new Random Number + // read mem mapped screen state + // render screen state + + +} diff --git a/Sources/opcodes.swift b/Sources/opcodes.swift index 5abb3f8..ced18c5 100644 --- a/Sources/opcodes.swift +++ b/Sources/opcodes.swift @@ -8,9 +8,133 @@ struct OpCode { let CPU_OP_CODES: [OpCode] = [ OpCode(code: 0x00, mnemonic: "BRK", len: 1, cycles: 7, mode: .NoneAddressing), - OpCode(code: 0xaa, mnemonic: "TAX", len: 1, cycles: 2, mode: .NoneAddressing), - OpCode(code: 0xe8, mnemonic: "INX", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xea, mnemonic: "NOP", len: 1, cycles: 2, mode: .NoneAddressing), + /// Arithmetic + OpCode(code: 0x69, mnemonic: "ADC", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0x65, mnemonic: "ADC", len: 2, cycles: 3, mode: .ZeroPage), + 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: 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), + + OpCode(code: 0xe9, mnemonic: "SBC", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0xe5, mnemonic: "SBC", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0xf5, mnemonic: "SBC", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0xed, mnemonic: "SBC", len: 3, cycles: 4, mode: .Absolute), + OpCode(code: 0xfd, mnemonic: "SBC", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_X), + OpCode(code: 0xf9, mnemonic: "SBC", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_Y), + OpCode(code: 0xe1, mnemonic: "SBC", len: 2, cycles: 6, mode: .Indirect_X), + OpCode(code: 0xf1, mnemonic: "SBC", len: 2, cycles: 5 /* +1 if page crossed */, mode: .Indirect_Y), + + OpCode(code: 0x29, mnemonic: "AND", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0x25, mnemonic: "AND", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0x35, mnemonic: "AND", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0x2d, mnemonic: "AND", len: 3, cycles: 4, mode: .Absolute), + OpCode(code: 0x3d, mnemonic: "AND", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_X), + OpCode(code: 0x39, mnemonic: "AND", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_Y), + OpCode(code: 0x21, mnemonic: "AND", len: 2, cycles: 6, mode: .Indirect_X), + OpCode(code: 0x31, mnemonic: "AND", len: 2, cycles: 5 /* +1 if page crossed */, mode: .Indirect_Y), + + OpCode(code: 0x49, mnemonic: "EOR", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0x45, mnemonic: "EOR", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0x55, mnemonic: "EOR", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0x4d, mnemonic: "EOR", len: 3, cycles: 4, mode: .Absolute), + OpCode(code: 0x5d, mnemonic: "EOR", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_X), + OpCode(code: 0x59, mnemonic: "EOR", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_Y), + OpCode(code: 0x41, mnemonic: "EOR", len: 2, cycles: 6, mode: .Indirect_X), + OpCode(code: 0x51, mnemonic: "EOR", len: 2, cycles: 5 /* +1 if page crossed */, mode: .Indirect_Y), + + OpCode(code: 0x09, mnemonic: "ORA", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0x05, mnemonic: "ORA", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0x15, mnemonic: "ORA", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0x0d, mnemonic: "ORA", len: 3, cycles: 4, mode: .Absolute), + OpCode(code: 0x1d, mnemonic: "ORA", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_X), + OpCode(code: 0x19, mnemonic: "ORA", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_Y), + OpCode(code: 0x01, mnemonic: "ORA", len: 2, cycles: 6, mode: .Indirect_X), + OpCode(code: 0x11, mnemonic: "ORA", len: 2, cycles: 5 /* +1 if page crossed */, mode: .Indirect_Y), + + /// Shifts + OpCode(code: 0x0a, mnemonic: "ASL", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x06, mnemonic: "ASL", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0x16, mnemonic: "ASL", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0x0e, mnemonic: "ASL", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0x1e, mnemonic: "ASL", len: 3, cycles: 7, mode: .Absolute_X), + + OpCode(code: 0x4a, mnemonic: "LSR", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x46, mnemonic: "LSR", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0x56, mnemonic: "LSR", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0x4e, mnemonic: "LSR", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0x5e, mnemonic: "LSR", len: 3, cycles: 7, mode: .Absolute_X), + + OpCode(code: 0x2a, mnemonic: "ROL", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x26, mnemonic: "ROL", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0x36, mnemonic: "ROL", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0x2e, mnemonic: "ROL", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0x3e, mnemonic: "ROL", len: 3, cycles: 7, mode: .Absolute_X), + + OpCode(code: 0x6a, mnemonic: "ROR", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x66, mnemonic: "ROR", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0x76, mnemonic: "ROR", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0x6e, mnemonic: "ROR", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0x7e, mnemonic: "ROR", len: 3, cycles: 7, mode: .Absolute_X), + + OpCode(code: 0xe6, mnemonic: "INC", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0xf6, mnemonic: "INC", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0xee, mnemonic: "INC", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0xfe, mnemonic: "INC", len: 3, cycles: 7, mode: .Absolute_X), + + OpCode(code: 0xe8, mnemonic: "INX", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xc8, mnemonic: "INY", len: 1, cycles: 2, mode: .NoneAddressing), + + OpCode(code: 0xc6, mnemonic: "DEC", len: 2, cycles: 5, mode: .ZeroPage), + OpCode(code: 0xd6, mnemonic: "DEC", len: 2, cycles: 6, mode: .ZeroPage_X), + OpCode(code: 0xce, mnemonic: "DEC", len: 3, cycles: 6, mode: .Absolute), + OpCode(code: 0xde, mnemonic: "DEC", len: 3, cycles: 7, mode: .Absolute_X), + + OpCode(code: 0xca, mnemonic: "DEX", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x88, mnemonic: "DEY", len: 1, cycles: 2, mode: .NoneAddressing), + + OpCode(code: 0xc9, mnemonic: "CMP", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0xc5, mnemonic: "CMP", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0xd5, mnemonic: "CMP", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0xcd, mnemonic: "CMP", len: 3, cycles: 4, mode: .Absolute), + OpCode(code: 0xdd, mnemonic: "CMP", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_X), + OpCode(code: 0xd9, mnemonic: "CMP", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_Y), + OpCode(code: 0xc1, mnemonic: "CMP", len: 2, cycles: 6, mode: .Indirect_X), + OpCode(code: 0xd1, mnemonic: "CMP", len: 2, cycles: 5 /* +1 if page crossed */, mode: .Indirect_Y), + + OpCode(code: 0xc0, mnemonic: "CPY", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0xc4, mnemonic: "CPY", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0xcc, mnemonic: "CPY", len: 3, cycles: 4, mode: .Absolute), + + OpCode(code: 0xe0, mnemonic: "CPX", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0xe4, mnemonic: "CPX", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0xec, mnemonic: "CPX", len: 3, cycles: 4, mode: .Absolute), + + /// Branching + OpCode(code: 0x4c, mnemonic: "JMP", len: 3, cycles: 3, mode: .NoneAddressing), // AddressingMode that acts as Immidiate + OpCode(code: 0x6c, mnemonic: "JMP", len: 3, cycles: 5, mode: .NoneAddressing), // AddressingMode.Indirect with 6502 bug + + OpCode(code: 0x20, mnemonic: "JSR", len: 3, cycles: 6, mode: .NoneAddressing), + OpCode(code: 0x60, mnemonic: "RTS", len: 1, cycles: 6, mode: .NoneAddressing), + OpCode(code: 0x40, mnemonic: "RTI", len: 1, cycles: 6, mode: .NoneAddressing), + + OpCode(code: 0xd0, mnemonic: "BNE", len: 2, cycles: 2 /* +1 if branch succeeds +2 of to a new page */ , mode: .NoneAddressing), + OpCode(code: 0x70, mnemonic: "BVS", len: 2, cycles: 2 /* +1 if branch succeeds +2 of to a new page */ , mode: .NoneAddressing), + OpCode(code: 0x50, mnemonic: "BVC", len: 2, cycles: 2 /* +1 if branch succeeds +2 of to a new page */ , mode: .NoneAddressing), + OpCode(code: 0x30, mnemonic: "BMI", len: 2, cycles: 2 /* +1 if branch succeeds +2 of to a new page */ , mode: .NoneAddressing), + OpCode(code: 0xf0, mnemonic: "BEQ", len: 2, cycles: 2 /* +1 if branch succeeds +2 of to a new page */ , mode: .NoneAddressing), + OpCode(code: 0xb0, mnemonic: "BCS", len: 2, cycles: 2 /* +1 if branch succeeds +2 of to a new page */ , mode: .NoneAddressing), + OpCode(code: 0x90, mnemonic: "BCC", len: 2, cycles: 2 /* +1 if branch succeeds +2 of to a new page */ , mode: .NoneAddressing), + OpCode(code: 0x10, mnemonic: "BPL", len: 2, cycles: 2 /* +1 if branch succeeds +2 of to a new page */ , mode: .NoneAddressing), + + OpCode(code: 0x24, mnemonic: "BIT", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0x2c, mnemonic: "BIT", len: 3, cycles: 4, mode: .Absolute), + + /// Stores, Loads OpCode(code: 0xa9, mnemonic: "LDA", len: 2, cycles: 2, mode: .Immediate), OpCode(code: 0xa5, mnemonic: "LDA", len: 2, cycles: 3, mode: .ZeroPage), OpCode(code: 0xb5, mnemonic: "LDA", len: 2, cycles: 4, mode: .ZeroPage_X), @@ -20,18 +144,61 @@ let CPU_OP_CODES: [OpCode] = [ OpCode(code: 0xa1, mnemonic: "LDA", len: 2, cycles: 6, mode: .Indirect_X), OpCode(code: 0xb1, mnemonic: "LDA", len: 2, cycles: 5 /* +1 if page crossed */, mode: .Indirect_Y), - OpCode(code: 0xa5, mnemonic: "STA", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0xa2, mnemonic: "LDX", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0xa6, mnemonic: "LDX", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0xb6, mnemonic: "LDX", len: 2, cycles: 4, mode: .ZeroPage_Y), + OpCode(code: 0xae, mnemonic: "LDX", len: 3, cycles: 4, mode: .Absolute), + OpCode(code: 0xbe, mnemonic: "LDX", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_Y), + + OpCode(code: 0xa0, mnemonic: "LDY", len: 2, cycles: 2, mode: .Immediate), + OpCode(code: 0xa4, mnemonic: "LDY", len: 2, cycles: 3, mode: .ZeroPage), + OpCode(code: 0xb4, mnemonic: "LDY", len: 2, cycles: 4, mode: .ZeroPage_X), + OpCode(code: 0xac, mnemonic: "LDY", len: 3, cycles: 4, mode: .Absolute), + OpCode(code: 0xbc, mnemonic: "LDY", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_X), + + OpCode(code: 0x85, mnemonic: "STA", len: 2, cycles: 3, mode: .ZeroPage), OpCode(code: 0x95, mnemonic: "STA", len: 2, cycles: 4, mode: .ZeroPage_X), OpCode(code: 0x8d, mnemonic: "STA", len: 3, cycles: 4, mode: .Absolute), OpCode(code: 0x9d, mnemonic: "STA", len: 3, cycles: 5, mode: .Absolute_X), OpCode(code: 0x99, mnemonic: "STA", len: 3, cycles: 5, mode: .Absolute_Y), OpCode(code: 0x81, mnemonic: "STA", len: 2, cycles: 6, mode: .Indirect_X), 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: 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: 0x8c, mnemonic: "STY", len: 3, cycles: 4, mode: .Absolute), + + /// Flag Clears + OpCode(code: 0xd8, mnemonic: "CLD", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x58, mnemonic: "CLI", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xb8, mnemonic: "CLV", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x18, mnemonic: "CLC", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x38, mnemonic: "SEC", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x78, mnemonic: "SEI", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xf8, mnemonic: "SED", len: 1, cycles: 2, mode: .NoneAddressing), + + OpCode(code: 0xaa, mnemonic: "TAX", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xa8, mnemonic: "TAY", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0xba, mnemonic: "TSX", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x8a, mnemonic: "TXA", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x9a, mnemonic: "TXS", len: 1, cycles: 2, mode: .NoneAddressing), + OpCode(code: 0x98, mnemonic: "TYA", len: 1, cycles: 2, mode: .NoneAddressing), + + /// Stack + OpCode(code: 0x48, mnemonic: "PHA", len: 1, cycles: 3, mode: .NoneAddressing), + 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), ] let OPCODES_MAP: [UInt8: OpCode] = { var map: [UInt8:OpCode] = [:] for cpuop in CPU_OP_CODES { + guard map[cpuop.code] == nil else {fatalError("Duplicate opcode \(cpuop) and \(map[cpuop.code]!)")} map[cpuop.code] = cpuop } return map diff --git a/Tests/CPU.swift b/Tests/CPU.swift index 6da2b42..27cb284 100644 --- a/Tests/CPU.swift +++ b/Tests/CPU.swift @@ -7,13 +7,13 @@ class CPUTests: XCTestCase { cpu.loadAndRun([0xa9, 0x05, 0x00]) XCTAssertEqual(cpu.register_a, 0x05) - XCTAssert(cpu.status & 0b0000_0010 == 0b00) - XCTAssert(cpu.status & 0b1000_0000 == 0) + XCTAssert(cpu.status.rawValue & 0b0000_0010 == 0b00) + XCTAssert(cpu.status.rawValue & 0b1000_0000 == 0) } func test_lda_from_memory() { let cpu = CPU() - cpu.memWrite(addr: 0x10, data: 0x55) + cpu.memWrite(0x10, data: 0x55) cpu.loadAndRun([0xa5, 0x10, 0x00]) @@ -23,13 +23,13 @@ class CPUTests: XCTestCase { func test_0xa9_lda_zero_flag() { let cpu = CPU() cpu.loadAndRun([0xa9, 0x00, 0x00]) - XCTAssert(cpu.status & 0b0000_0010 == 0b10) + XCTAssert(cpu.status.rawValue & 0b0000_0010 == 0b10) } func test_0xa9_lda_neg_flag() { let cpu = CPU() cpu.loadAndRun([0xa9, 0xFF, 0x00]) - XCTAssert(cpu.status & 0b1000_0000 == 0b1000_0000) + XCTAssert(cpu.status.rawValue & 0b1000_0000 == 0b1000_0000) } func test_0xaa_tax_move_a_to_x() {