Advertisement
Guest User

Untitled

a guest
Oct 18th, 2019
99
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.29 KB | None | 0 0
  1. //
  2. // CPU.swift
  3. //
  4.  
  5. import UIKit
  6.  
  7. class CPU {
  8.  
  9. // Calls specified method on every screen refresh (much more fine detail than a NSTimer)
  10. var displayTimer: CADisplayLink?
  11.  
  12. lazy private var pongData = NSDataAsset(name: "Pong")
  13. lazy private var testData = NSDataAsset(name: "BC_test")
  14.  
  15. var totalCPUCycles = 0
  16.  
  17. // 60hz frame timer
  18. var frameTimer = Timer()
  19.  
  20. // This is always initialized with this value in Chip8 since the actual program data begins here in memory
  21. var programCounter = 80
  22.  
  23. var currentOpCode: UInt16 = 0x0
  24. var indexRegister: Int = 0
  25.  
  26. var stack: [UInt16] = [0x0]
  27. var stackPointer = 0
  28.  
  29. // Both of these timers, when above 0, should decrement by 1 per cycle at 60hz.
  30. // when sound timer reaches 0, buzzer should sound.
  31. var soundTimer = 0
  32. var delayTimer = 0
  33.  
  34. var screenPixels: Screen.DataRepresentation = [[]]
  35.  
  36. // 16 registers, 0->F
  37. var vRegisters: [UInt8] = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
  38. 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]
  39.  
  40. /* MEMORY MAP
  41. 0x000-0x1FF - Chip 8 interpreter (contains font set in emu)
  42. 0x050-0x0A0 - Used for the built in 4x5 pixel font set (0-F)
  43. 0x200-0xFFF - Program ROM and work RAM
  44. */
  45. var memory: [UInt16] = []
  46.  
  47. // HEX -> BINARY
  48. // 0xF0 -> 11110000
  49. // 0x09 -> 00001001
  50. enum Constants {
  51. static let fontSet: [UInt16] = [0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
  52. 0x20, 0x60, 0x20, 0x20, 0x70, // 1
  53. 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
  54. 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
  55. 0x90, 0x90, 0xF0, 0x10, 0x10, // 4
  56. 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
  57. 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
  58. 0xF0, 0x10, 0x20, 0x40, 0x40, // 7
  59. 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
  60. 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
  61. 0xF0, 0x90, 0xF0, 0x90, 0x90, // A
  62. 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
  63. 0xF0, 0x80, 0x80, 0x80, 0xF0, // C
  64. 0xE0, 0x90, 0x90, 0x90, 0xE0, // D
  65. 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
  66. 0xF0, 0x80, 0xF0, 0x80, 0x80] // F
  67. }
  68.  
  69. init() {
  70. memory = Constants.fontSet
  71.  
  72. // Update all 2048 screen pixels to off
  73. for yIndex in 0..<32 {
  74. self.screenPixels.insert([], at: yIndex)
  75. for xIndex in 0..<64 {
  76. let isACorner = (xIndex == 0 && yIndex == 0) ||
  77. (xIndex == 63 && yIndex == 31) ||
  78. (xIndex == 0 && yIndex == 31) ||
  79. (xIndex == 63 && yIndex == 0)
  80.  
  81. self.screenPixels[yIndex].insert(isACorner, at: xIndex)
  82. }
  83. }
  84. }
  85.  
  86. typealias CycleHandler = (Screen.DataRepresentation) -> Void
  87. var cycleEndedHandler: CycleHandler?
  88. func start(cycleEndedHandler: @escaping CycleHandler) {
  89. loadRomIntoMemory(completionHandler: { [weak self] in
  90. guard let self = self else { return }
  91. self.cycleEndedHandler = cycleEndedHandler
  92. let displayLink = CADisplayLink(target: self, selector: #selector(cpuCycleStart))
  93. displayLink.preferredFramesPerSecond = 60
  94. displayLink.add(to: .current, forMode: .common)
  95. self.displayTimer = displayLink
  96. })
  97. }
  98.  
  99. /// This should be called approx 60 times a second (60hz)
  100. @objc private func cpuCycleStart() {
  101. emulateCycle()
  102. cycleEndedHandler?(self.screenPixels)
  103. }
  104.  
  105. /// Gets the opcode at current index, and the opcode at index+1,
  106. /// combines them into memory and continues until reaching the end of the rom.
  107. private func loadRomIntoMemory(completionHandler: () -> Void) {
  108. var romOpcodeIndex = 0
  109. if let romData = self.testData?.data {
  110. var reachedEndOfRom = romOpcodeIndex >= romData.count && (romOpcodeIndex + 1) >= romData.count
  111.  
  112. while !reachedEndOfRom {
  113. // combining the partial opcodes 8bit and 8bit = 16bit opcode
  114. let opcodeA = UInt16(romData[romOpcodeIndex])
  115. let opcodeB = UInt16(romData[romOpcodeIndex + 1])
  116. let mergedOpcode = (opcodeA << 8) | opcodeB
  117.  
  118. self.memory.append(mergedOpcode)
  119.  
  120. // Binary Digit Print: String(someNum, radix: 2)
  121. print("OpCode: \(String(format: "%X", mergedOpcode))")
  122.  
  123. romOpcodeIndex += 2
  124. reachedEndOfRom = romOpcodeIndex >= romData.count && (romOpcodeIndex + 1) >= romData.count
  125. }
  126.  
  127. completionHandler()
  128. }
  129. }
  130.  
  131. private func emulateCycle() {
  132.  
  133. // Decode & execute opcode
  134. if self.programCounter < self.memory.count {
  135. self.currentOpCode = self.memory[self.programCounter]
  136. self.updateStack(opCode: currentOpCode)
  137. self.process(opCode: currentOpCode)
  138. } else {
  139. print("🛑 ERROR: Reached end of rom")
  140. print("🔄 Total CPU Cycles: \(self.totalCPUCycles)")
  141. self.displayTimer?.invalidate()
  142. }
  143.  
  144. // Update timers
  145. self.delayTimer = self.delayTimer > 0 ? (self.delayTimer - 1) : 0
  146. self.soundTimer = self.soundTimer > 0 ? (self.soundTimer - 1) : 0
  147. self.programCounter += 2
  148. self.totalCPUCycles += 1
  149. }
  150.  
  151. private func updateStack(opCode: UInt16) {
  152. var updatableStack = stack
  153. self.stackPointer = self.stackPointer > 0 ? self.stackPointer + 1 : 0
  154. updatableStack[self.stackPointer] = opCode
  155. self.stack = updatableStack
  156. }
  157.  
  158. private func process(opCode: UInt16) {
  159. print("Processing OpCode: \(String(opCode, radix: 16))")
  160. switch (opCode & 0xF000) {
  161. case 0x6000:
  162. load(value: UInt8(opCode >> 8), toRegister: UInt8(opCode & 0x00FF))
  163. case 0xA000:
  164. updateIndexRegister(value: Int(opCode & 0x0FFF))
  165. case 0xD000:
  166. draw(nBytes: (opCode & 0x000F), vX: (opCode & 0x0F00), vy: (opCode & 0x00F8))
  167. default:
  168. instructionIgnored()
  169. return
  170. }
  171. }
  172. }
  173.  
  174. // MARK: CPU OpCode Handling
  175. extension CPU {
  176.  
  177. private func instructionIgnored() {
  178. print("⚠ Got unhandled instruction: \(String(currentOpCode, radix: 16))")
  179. }
  180.  
  181. private func instructionFailed() {
  182. print("⚠ Failed instruction: \(String(currentOpCode, radix: 16))")
  183. }
  184.  
  185. /// # Opcode 6xkk
  186. /// * Set Vregister[x] = kk
  187. func load(value: UInt8, toRegister: UInt8) {
  188. if toRegister < self.vRegisters.count {
  189. self.vRegisters[Int(toRegister)] = value
  190. } else {
  191. // Attempt to insert into invalid register
  192. instructionFailed()
  193. }
  194. }
  195.  
  196. /// # Opcode Annn
  197. /// * Set the indexRegister = nnn
  198. func updateIndexRegister(value: Int) {
  199. self.indexRegister = value
  200. }
  201.  
  202. /// # Opcode Dxyn
  203. /// * Dxyn - DRW Vx, Vy, nibble
  204. /// * Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
  205. /// * The interpreter reads n bytes from memory, starting at the address stored in I. These bytes are then displayed as sprites on screen at coordinates (Vx, Vy). Sprites are XORed onto the existing screen. If this causes any pixels to be erased, VF is set to 1, otherwise it is set to 0. If the sprite is positioned so part of it is outside the coordinates of the display, it wraps around to the opposite side of the screen. See instruction 8xy3 for more information on XOR, and section 2.4, Display, for more information on the Chip-8 screen and sprites.
  206. func draw(nBytes: UInt16, vX: UInt16, vy: UInt16) {
  207. var sprite: [UInt16] = []
  208. let endByte = self.indexRegister + Int(nBytes)
  209. for byteIndex in self.indexRegister..<endByte {
  210. sprite.append(self.memory[Int(byteIndex)])
  211. }
  212.  
  213. print("")
  214. }
  215. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement