mirror of
https://github.com/Candygoblen123/SwiftNES.git
synced 2024-11-21 17:46:23 -06:00
PPU: Implement registers
This commit is contained in:
parent
9c5cb22223
commit
05927d8e91
@ -1,6 +1,7 @@
|
|||||||
class Bus {
|
class Bus {
|
||||||
var cpuVram: [UInt8] = .init(repeating: 0, count: 2048)
|
var cpuVram: [UInt8] = .init(repeating: 0, count: 2048)
|
||||||
var rom: Rom
|
var prgRom: [UInt8]
|
||||||
|
var ppu: NesPPU
|
||||||
|
|
||||||
fileprivate let RAM : UInt16 = 0x0000
|
fileprivate let RAM : UInt16 = 0x0000
|
||||||
fileprivate let RAM_MIRRORS_END: UInt16 = 0x1FFF
|
fileprivate let RAM_MIRRORS_END: UInt16 = 0x1FFF
|
||||||
@ -10,7 +11,8 @@ class Bus {
|
|||||||
fileprivate let ROM_ADDRESS_END: UInt16 = 0xFFFF
|
fileprivate let ROM_ADDRESS_END: UInt16 = 0xFFFF
|
||||||
|
|
||||||
init(_ rom: Rom) {
|
init(_ rom: Rom) {
|
||||||
self.rom = rom
|
ppu = NesPPU(rom.character, rom.screenMirror)
|
||||||
|
self.prgRom = rom.program
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,9 +23,17 @@ extension Bus: Memory {
|
|||||||
case RAM...RAM_MIRRORS_END:
|
case RAM...RAM_MIRRORS_END:
|
||||||
let mirrorDownAddr = addr & 0b00000111_11111111
|
let mirrorDownAddr = addr & 0b00000111_11111111
|
||||||
return self.cpuVram[Int(mirrorDownAddr)]
|
return self.cpuVram[Int(mirrorDownAddr)]
|
||||||
case PPU_REGISTERS...PPU_REGISTERS_MIRRORS_END:
|
case 0x2000, 0x2001, 0x2003, 0x2005, 0x2006, 0x4014:
|
||||||
|
fatalError("Attempt to read from write-only PPU address \(addr)")
|
||||||
|
case 0x2002:
|
||||||
|
return ppu.readStatus()
|
||||||
|
case 0x2004:
|
||||||
|
return ppu.readOamData()
|
||||||
|
case 0x2007:
|
||||||
|
return ppu.readData()
|
||||||
|
case 0x2008...PPU_REGISTERS_MIRRORS_END:
|
||||||
let mirrorDownAddr = addr & 0b00100000_00000111;
|
let mirrorDownAddr = addr & 0b00100000_00000111;
|
||||||
fatalError("PPU not implemented yet")
|
return self.memRead(mirrorDownAddr)
|
||||||
case ROM_ADDRESS_START...ROM_ADDRESS_END:
|
case ROM_ADDRESS_START...ROM_ADDRESS_END:
|
||||||
return readProgramRom(addr)
|
return readProgramRom(addr)
|
||||||
default:
|
default:
|
||||||
@ -37,9 +47,27 @@ extension Bus: Memory {
|
|||||||
case RAM...RAM_MIRRORS_END:
|
case RAM...RAM_MIRRORS_END:
|
||||||
let mirrorDownAddr = addr & 0b11111111111
|
let mirrorDownAddr = addr & 0b11111111111
|
||||||
self.cpuVram[Int(mirrorDownAddr)] = data
|
self.cpuVram[Int(mirrorDownAddr)] = data
|
||||||
case PPU_REGISTERS...PPU_REGISTERS_MIRRORS_END:
|
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
|
let mirrorDownAddr = addr & 0b00100000_00000111
|
||||||
fatalError("PPU is not implemented yet!")
|
memWrite(mirrorDownAddr, data: data)
|
||||||
|
case ROM_ADDRESS_START...ROM_ADDRESS_END:
|
||||||
|
fatalError("Attempt to write to Cartridge ROM space: \(addr)")
|
||||||
default:
|
default:
|
||||||
print("Ignorming mem-write at \(addr)")
|
print("Ignorming mem-write at \(addr)")
|
||||||
}
|
}
|
||||||
@ -47,10 +75,10 @@ extension Bus: Memory {
|
|||||||
|
|
||||||
func readProgramRom(_ addr: UInt16) -> UInt8 {
|
func readProgramRom(_ addr: UInt16) -> UInt8 {
|
||||||
var addr = addr - 0x8000
|
var addr = addr - 0x8000
|
||||||
if rom.program.count == 0x4000 && addr >= 0x4000 {
|
if prgRom.count == 0x4000 && addr >= 0x4000 {
|
||||||
// rom mirroring
|
// rom mirroring
|
||||||
addr = addr % 0x4000
|
addr = addr % 0x4000
|
||||||
}
|
}
|
||||||
return rom.program[Int(addr)]
|
return prgRom[Int(addr)]
|
||||||
}
|
}
|
||||||
}
|
}
|
107
Sources/PPU/NesPPU.swift
Normal file
107
Sources/PPU/NesPPU.swift
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
class NesPPU {
|
||||||
|
public var paletteTable = [UInt8](repeating: 0, count: 32)
|
||||||
|
public var vram = [UInt8](repeating: 0, count: 2048)
|
||||||
|
|
||||||
|
public let mirroring: Mirroring
|
||||||
|
public var chrRom: [UInt8]
|
||||||
|
|
||||||
|
private let addr: AddrRegister = AddrRegister()
|
||||||
|
private var readBuff: UInt8 = 0
|
||||||
|
var ctrl = ControlRegister()
|
||||||
|
let status = StatusRegister()
|
||||||
|
let scroll = ScrollRegister()
|
||||||
|
let mask = MaskRegister()
|
||||||
|
|
||||||
|
var oamAddr: UInt8 = 0
|
||||||
|
var oamData = [UInt8](repeating: 0, count: 64 * 4)
|
||||||
|
|
||||||
|
init(_ chrRom: [UInt8], _ mirroring: Mirroring) {
|
||||||
|
self.chrRom = chrRom
|
||||||
|
self.mirroring = mirroring
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeToPPUAddr(_ value: UInt8) {
|
||||||
|
addr.update(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeToCtrl(_ value: UInt8) {
|
||||||
|
ctrl.rawValue = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func incrememtVramAddr() {
|
||||||
|
addr.increment(ctrl.vramAddrIncrement())
|
||||||
|
}
|
||||||
|
|
||||||
|
func readData() -> UInt8 {
|
||||||
|
let addr = addr.get()
|
||||||
|
incrememtVramAddr()
|
||||||
|
|
||||||
|
switch addr {
|
||||||
|
case 0...0x1fff:
|
||||||
|
let res = readBuff
|
||||||
|
readBuff = chrRom[Int(addr)]
|
||||||
|
return res
|
||||||
|
case 0x2000...0x2fff:
|
||||||
|
let res = readBuff
|
||||||
|
readBuff = vram[Int(mirrorVramAddr(addr))]
|
||||||
|
return res
|
||||||
|
case 0x3000...0x3eff:
|
||||||
|
fatalError("addr space 0x3000..0x3eff is not expected to be used, requested = \(addr)")
|
||||||
|
case 0x3f00...0x3fff:
|
||||||
|
return self.paletteTable[Int(addr - 0x3f00)]
|
||||||
|
default:
|
||||||
|
fatalError("Unexpected access to mirrored space \(addr)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mirrorVramAddr(_ addr: UInt16) -> UInt16 {
|
||||||
|
let mirroredVram = addr & 0b10111111111111 // mirror down 0x3000-0x3eff to 0x2000 - 0x2eff
|
||||||
|
let vramIndex = mirroredVram - 0x2000 // to vram array index
|
||||||
|
let nameTable = vramIndex / 0x400 // to the name index table
|
||||||
|
return switch (mirroring, nameTable) {
|
||||||
|
case (.vertical, 2), (.vertical, 3):
|
||||||
|
vramIndex - 0x800
|
||||||
|
case (.horizontal, 2):
|
||||||
|
vramIndex - 0x400
|
||||||
|
case (.horizontal, 1):
|
||||||
|
vramIndex - 0x400
|
||||||
|
case (.horizontal, 3):
|
||||||
|
vramIndex - 0x800
|
||||||
|
default:
|
||||||
|
vramIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeToData(_ data: UInt8) {
|
||||||
|
fatalError("Not Implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func readStatus() -> UInt8 {
|
||||||
|
let data = status.snapshot()
|
||||||
|
status.resetVblankStatus()
|
||||||
|
addr.resetLatch()
|
||||||
|
scroll.resetLatch()
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeToScroll(_ value: UInt8) {
|
||||||
|
scroll.write(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeToOamAddr(_ value: UInt8) {
|
||||||
|
oamAddr = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeToOamData(_ value: UInt8) {
|
||||||
|
oamData[Int(oamAddr)] = value
|
||||||
|
oamAddr = oamAddr &+ 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func readOamData() -> UInt8 {
|
||||||
|
oamData[Int(oamAddr)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeToMask(_ value: UInt8) {
|
||||||
|
mask.update(value)
|
||||||
|
}
|
||||||
|
}
|
42
Sources/PPU/Registers/AddrRegister.swift
Normal file
42
Sources/PPU/Registers/AddrRegister.swift
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
class AddrRegister {
|
||||||
|
private var value: (UInt8, UInt8) = (0, 0) // hi byte first, lo byte second
|
||||||
|
private var hiPointer = true
|
||||||
|
|
||||||
|
func set(_ data: UInt16) {
|
||||||
|
value.0 = UInt8(data >> 8)
|
||||||
|
value.1 = UInt8(data & 0xff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(_ data: UInt8) {
|
||||||
|
if hiPointer {
|
||||||
|
value.0 = data
|
||||||
|
} else {
|
||||||
|
value.1 = data
|
||||||
|
}
|
||||||
|
|
||||||
|
if get() > 0x3fff {
|
||||||
|
set(get() & 0b11111111111111)
|
||||||
|
}
|
||||||
|
hiPointer.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func increment(_ inc: UInt8) {
|
||||||
|
let lo = value.1
|
||||||
|
value.1 = value.1 &+ inc
|
||||||
|
|
||||||
|
if lo > value.1 {
|
||||||
|
value.0 = value.0 &+ 1
|
||||||
|
}
|
||||||
|
if get() > 0x3fff {
|
||||||
|
set(get() & 0b11111111111111)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetLatch() {
|
||||||
|
hiPointer = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func get() -> UInt16 {
|
||||||
|
UInt16(value.0) << 8 | UInt16(value.1)
|
||||||
|
}
|
||||||
|
}
|
37
Sources/PPU/Registers/ControlRegister.swift
Normal file
37
Sources/PPU/Registers/ControlRegister.swift
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// 7 bit 0
|
||||||
|
// ---- ----
|
||||||
|
// VPHB SINN
|
||||||
|
// |||| ||||
|
||||||
|
// |||| ||++- Base nametable address
|
||||||
|
// |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
|
||||||
|
// |||| |+--- VRAM address increment per CPU read/write of PPUDATA
|
||||||
|
// |||| | (0: add 1, going across; 1: add 32, going down)
|
||||||
|
// |||| +---- Sprite pattern table address for 8x8 sprites
|
||||||
|
// |||| (0: $0000; 1: $1000; ignored in 8x16 mode)
|
||||||
|
// |||+------ Background pattern table address (0: $0000; 1: $1000)
|
||||||
|
// ||+------- Sprite size (0: 8x8 pixels; 1: 8x16 pixels)
|
||||||
|
// |+-------- PPU master/slave select
|
||||||
|
// | (0: read backdrop from EXT pins; 1: output color on EXT pins)
|
||||||
|
// +--------- Generate an NMI at the start of the
|
||||||
|
// vertical blanking interval (0: off; 1: on)
|
||||||
|
|
||||||
|
struct ControlRegister: OptionSet {
|
||||||
|
var rawValue: UInt8
|
||||||
|
|
||||||
|
static let NAMETABLE1 = ControlRegister(rawValue: 0b00000001)
|
||||||
|
static let NAMETABLE2 = ControlRegister(rawValue: 0b00000010)
|
||||||
|
static let VRAM_ADD_INCREMENT = ControlRegister(rawValue: 0b00000100)
|
||||||
|
static let SPRITE_PATTERN_ADDR = ControlRegister(rawValue: 0b00001000)
|
||||||
|
static let BACKROUND_PATTERN_ADDR = ControlRegister(rawValue: 0b00010000)
|
||||||
|
static let SPRITE_SIZE = ControlRegister(rawValue: 0b00100000)
|
||||||
|
static let MASTER_SLAVE_SELECT = ControlRegister(rawValue: 0b01000000)
|
||||||
|
static let GENERATE_NMI = ControlRegister(rawValue: 0b10000000)
|
||||||
|
|
||||||
|
func vramAddrIncrement() -> UInt8 {
|
||||||
|
if self.contains(.VRAM_ADD_INCREMENT) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
Sources/PPU/Registers/MaskRegister.swift
Normal file
58
Sources/PPU/Registers/MaskRegister.swift
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
private struct MaskRegisterInternal: OptionSet {
|
||||||
|
var rawValue: UInt8
|
||||||
|
|
||||||
|
static let GREYSCALE = MaskRegisterInternal(rawValue: 0b00000001)
|
||||||
|
static let LEFTMOST_8PXL_BACKGROUND = MaskRegisterInternal(rawValue: 0b00000010)
|
||||||
|
static let LEFTMOST_8PXL_SPRITE = MaskRegisterInternal(rawValue: 0b00000100)
|
||||||
|
static let SHOW_BACKGROUND = MaskRegisterInternal(rawValue: 0b00001000)
|
||||||
|
static let SHOW_SPRITES = MaskRegisterInternal(rawValue: 0b00010000)
|
||||||
|
static let EMPHASISE_RED = MaskRegisterInternal(rawValue: 0b00100000)
|
||||||
|
static let EMPHASISE_GREEN = MaskRegisterInternal(rawValue: 0b01000000)
|
||||||
|
static let EMPHASISE_BLUE = MaskRegisterInternal(rawValue: 0b10000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MaskRegister {
|
||||||
|
private var val = MaskRegisterInternal()
|
||||||
|
|
||||||
|
func isGrayscale() -> Bool {
|
||||||
|
val.contains(.GREYSCALE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func leftmost8pxlBackground() -> Bool {
|
||||||
|
val.contains(.LEFTMOST_8PXL_BACKGROUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
func leftmost8pxlSprite() -> Bool {
|
||||||
|
val.contains(.LEFTMOST_8PXL_SPRITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func showBackground() -> Bool {
|
||||||
|
val.contains(.SHOW_BACKGROUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
func showSprites() -> Bool {
|
||||||
|
val.contains(.SHOW_SPRITES)
|
||||||
|
}
|
||||||
|
|
||||||
|
func emphasise() -> [Color] {
|
||||||
|
var res: [Color] = []
|
||||||
|
if val.contains(.EMPHASISE_RED) {
|
||||||
|
res.append(.Red)
|
||||||
|
}
|
||||||
|
if val.contains(.EMPHASISE_BLUE) {
|
||||||
|
res.append(.Blue)
|
||||||
|
}
|
||||||
|
if val.contains(.EMPHASISE_GREEN) {
|
||||||
|
res.append(.Green)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(_ data: UInt8) {
|
||||||
|
val.rawValue = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Color {
|
||||||
|
case Red, Green, Blue
|
||||||
|
}
|
18
Sources/PPU/Registers/ScrollRegister.swift
Normal file
18
Sources/PPU/Registers/ScrollRegister.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
class ScrollRegister {
|
||||||
|
public var x: UInt8 = 0
|
||||||
|
public var y: UInt8 = 0
|
||||||
|
public var latch = false
|
||||||
|
|
||||||
|
func write(_ data: UInt8) {
|
||||||
|
if !latch {
|
||||||
|
x = data
|
||||||
|
} else {
|
||||||
|
y = data
|
||||||
|
}
|
||||||
|
latch.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetLatch() {
|
||||||
|
latch = false
|
||||||
|
}
|
||||||
|
}
|
73
Sources/PPU/Registers/StatusRegister.swift
Normal file
73
Sources/PPU/Registers/StatusRegister.swift
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// 7 bit 0
|
||||||
|
// ---- ----
|
||||||
|
// VSO. ....
|
||||||
|
// |||| ||||
|
||||||
|
// |||+-++++- Least significant bits previously written into a PPU register
|
||||||
|
// ||| (due to register not being updated for this address)
|
||||||
|
// ||+------- Sprite overflow. The intent was for this flag to be set
|
||||||
|
// || whenever more than eight sprites appear on a scanline, but a
|
||||||
|
// || hardware bug causes the actual behavior to be more complicated
|
||||||
|
// || and generate false positives as well as false negatives; see
|
||||||
|
// || PPU sprite evaluation. This flag is set during sprite
|
||||||
|
// || evaluation and cleared at dot 1 (the second dot) of the
|
||||||
|
// || pre-render line.
|
||||||
|
// |+-------- Sprite 0 Hit. Set when a nonzero pixel of sprite 0 overlaps
|
||||||
|
// | a nonzero background pixel; cleared at dot 1 of the pre-render
|
||||||
|
// | line. Used for raster timing.
|
||||||
|
// +--------- Vertical blank has started (0: not in vblank; 1: in vblank).
|
||||||
|
// Set at dot 1 of line 241 (the line *after* the post-render
|
||||||
|
// line); cleared after reading $2002 and at dot 1 of the
|
||||||
|
// pre-render line.
|
||||||
|
|
||||||
|
private struct StatusRegisterInternal: OptionSet {
|
||||||
|
var rawValue: UInt8
|
||||||
|
|
||||||
|
static let NOTUSED = StatusRegisterInternal(rawValue: 0b00000001)
|
||||||
|
static let NOTUSED2 = StatusRegisterInternal(rawValue: 0b00000010)
|
||||||
|
static let NOTUSED3 = StatusRegisterInternal(rawValue: 0b00000100)
|
||||||
|
static let NOTUSED4 = StatusRegisterInternal(rawValue: 0b00001000)
|
||||||
|
static let NOTUSED5 = StatusRegisterInternal(rawValue: 0b00010000)
|
||||||
|
static let SPRITE_OVERFLOW = StatusRegisterInternal(rawValue: 0b00100000)
|
||||||
|
static let SPRITE_ZERO_HIT = StatusRegisterInternal(rawValue: 0b01000000)
|
||||||
|
static let VBLANK_STARTED = StatusRegisterInternal(rawValue: 0b10000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatusRegister {
|
||||||
|
fileprivate var val = StatusRegisterInternal()
|
||||||
|
|
||||||
|
func setVblankStatus(_ status: Bool) {
|
||||||
|
if status {
|
||||||
|
val.insert(.VBLANK_STARTED)
|
||||||
|
} else {
|
||||||
|
val.remove(.VBLANK_STARTED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSpriteZeroHit(_ status: Bool) {
|
||||||
|
if status {
|
||||||
|
val.insert(.SPRITE_ZERO_HIT)
|
||||||
|
} else {
|
||||||
|
val.remove(.SPRITE_ZERO_HIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSpriteOverflow(_ status: Bool) {
|
||||||
|
if status {
|
||||||
|
val.insert(.SPRITE_OVERFLOW)
|
||||||
|
} else {
|
||||||
|
val.remove(.SPRITE_OVERFLOW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetVblankStatus() {
|
||||||
|
val.remove(.VBLANK_STARTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInVblank() -> Bool {
|
||||||
|
val.contains(.VBLANK_STARTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshot() -> UInt8 {
|
||||||
|
val.rawValue
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user