mirror of
https://github.com/Candygoblen123/SwiftNES.git
synced 2024-11-24 18:06:47 -06:00
PPU: Implement registers
This commit is contained in:
parent
9c5cb22223
commit
05927d8e91
@ -1,6 +1,7 @@
|
||||
class Bus {
|
||||
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_MIRRORS_END: UInt16 = 0x1FFF
|
||||
@ -10,7 +11,8 @@ class Bus {
|
||||
fileprivate let ROM_ADDRESS_END: UInt16 = 0xFFFF
|
||||
|
||||
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:
|
||||
let mirrorDownAddr = addr & 0b00000111_11111111
|
||||
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;
|
||||
fatalError("PPU not implemented yet")
|
||||
return self.memRead(mirrorDownAddr)
|
||||
case ROM_ADDRESS_START...ROM_ADDRESS_END:
|
||||
return readProgramRom(addr)
|
||||
default:
|
||||
@ -37,9 +47,27 @@ extension Bus: Memory {
|
||||
case RAM...RAM_MIRRORS_END:
|
||||
let mirrorDownAddr = addr & 0b11111111111
|
||||
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
|
||||
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:
|
||||
print("Ignorming mem-write at \(addr)")
|
||||
}
|
||||
@ -47,10 +75,10 @@ extension Bus: Memory {
|
||||
|
||||
func readProgramRom(_ addr: UInt16) -> UInt8 {
|
||||
var addr = addr - 0x8000
|
||||
if rom.program.count == 0x4000 && addr >= 0x4000 {
|
||||
if prgRom.count == 0x4000 && addr >= 0x4000 {
|
||||
// rom mirroring
|
||||
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