class Bus { var cpuVram: [UInt8] = .init(repeating: 0, count: 2048) var prgRom: [UInt8] let ppu: NesPPU let joypad1: Joypad var cycles: Int = 0 var gameloopCallback: (NesPPU) -> () fileprivate let RAM : UInt16 = 0x0000 fileprivate let RAM_MIRRORS_END: UInt16 = 0x1FFF fileprivate let PPU_REGISTERS: UInt16 = 0x2000 fileprivate let PPU_REGISTERS_MIRRORS_END: UInt16 = 0x3FFF fileprivate let ROM_ADDRESS_START: UInt16 = 0x8000 fileprivate let ROM_ADDRESS_END: UInt16 = 0xFFFF init(rom: Rom, joypad1: Joypad, gameloopCallback: @escaping (NesPPU) -> ()) { ppu = NesPPU(rom.character, rom.screenMirror) self.prgRom = rom.program self.gameloopCallback = gameloopCallback self.joypad1 = joypad1 } func tick(_ cycles: UInt8) { self.cycles += Int(cycles) let newFrame = self.ppu.tick(cycles * 3) if newFrame { gameloopCallback(ppu) } } func pollNMI() -> UInt8? { ppu.pollNMI() } } extension Bus: Memory { func memRead(_ addr: UInt16) -> UInt8 { switch addr { case RAM...RAM_MIRRORS_END: let mirrorDownAddr = addr & 0b00000111_11111111 return self.cpuVram[Int(mirrorDownAddr)] case 0x2000, 0x2001, 0x2003, 0x2005, 0x2006, 0x4014: //fatalError("Attempt to read from write-only PPU address \(addr)") return 0 case 0x2002: return ppu.readStatus() case 0x2004: return ppu.readOamData() case 0x2007: return ppu.readData() case 0x4000...0x4015: return 0 // Ignore APU case 0x4016: return joypad1.read() case 0x4017: return 0 // Ignore Joy 2 case 0x2008...PPU_REGISTERS_MIRRORS_END: let mirrorDownAddr = addr & 0b00100000_00000111; return self.memRead(mirrorDownAddr) case ROM_ADDRESS_START...ROM_ADDRESS_END: return readProgramRom(addr) default: print("Ignoring mem access at \(addr)") return 0 } } func memWrite(_ addr: UInt16, data: UInt8) { switch addr { case RAM...RAM_MIRRORS_END: let mirrorDownAddr = addr & 0b11111111111 self.cpuVram[Int(mirrorDownAddr)] = data case 0x2000: ppu.writeToCtrl(data) case 0x2001: ppu.writeToMask(data) case 0x2002: fatalError("Attempt to write to PPU status register") case 0x2003: ppu.writeToOamAddr(data) case 0x2004: ppu.writeToOamData(data) case 0x2005: ppu.writeToScroll(data) case 0x2006: ppu.writeToPPUAddr(data) case 0x2007: ppu.writeToData(data) case 0x2008...PPU_REGISTERS_MIRRORS_END: let mirrorDownAddr = addr & 0b00100000_00000111 memWrite(mirrorDownAddr, data: data) case 0x4000...0x4013, 0x4015: return // Ignore APU case 0x4016: joypad1.write(data) case 0x4017: return // Ignore Joy 2 case 0x4014: var buffer = [UInt8](repeating: 0, count: 256) let hi = UInt16(data) << 8 for i in 0..<256 { buffer[i] = memRead(hi + UInt16(i)) } ppu.writeOamDma(buffer) case ROM_ADDRESS_START...ROM_ADDRESS_END: fatalError("Attempt to write to Cartridge ROM space: \(addr)") default: print("Ignorming mem-write at \(addr)") } } func readProgramRom(_ addr: UInt16) -> UInt8 { var addr = addr - 0x8000 if prgRom.count == 0x4000 && addr >= 0x4000 { // rom mirroring addr = addr % 0x4000 } return prgRom[Int(addr)] } }