The NES has three major parts, the CPU, the Picture Processing Unit for generating graphics, and the Audio Processing Unit for sound. Let's start modelling the processor first, which is a slightly modified 6502 CPU.
So, let's start modelling the 6502 CPU in Swift. Its cost reduced design makes it extremely simple.
First, it has some registers. Only 5, plus flags:
struct RegisterSet {
var a : UInt8 = 0 // Accumulator
var x : UInt8 = 0 // Index X
var y : UInt8 = 0 // Index Y
var pc : UInt16 = 0 // Program Counter
var sp : UInt8 = 0 // Stack Pointer
var p : FlagSet = FlagSet() // Processor status / flags
}
And it has some six flags, plus one non physical-flag that only appears when the flags are pushed on the stack:
struct FlagSet {
// Represent the flags of the processor
var c = false // carry
var z = false // zero
var i = false // interrupt disable
var d = false // decimal mode
var b = true // break, virtual flag, normally on, except during IRQ processing
var v = false // signed overflow
var n = false // negative
}
I've decided to model the flags as individual booleans for clarity of code. However, the processor occasionally needs to pack them as a byte when it stacks them for interrupt processing, or when the program uses the PHP or PLP opcodes to push or pull the flag register to/from the stack. Since the Swift standard library includes a type OptionSet for bitmasks, I'll try using that rather than writing the C style bitmasking myself. Let's make it a private nested type:
// Values used for converting to and from a byte (ie, when putting on the stack)
private struct FlagValue : OptionSet {
// NV1B_DIZC
let rawValue : UInt8
static let c = FlagValue(rawValue: 0x01)
static let z = FlagValue(rawValue: 0x02)
static let i = FlagValue(rawValue: 0x04)
static let d = FlagValue(rawValue: 0x08)
static let b = FlagValue(rawValue: 0x10)
static let always = FlagValue(rawValue: 0x20)
static let v = FlagValue(rawValue: 0x40)
static let n = FlagValue(rawValue: 0x80)
}
The processor has a quirk that the unused bit of the bitmask is always pushed as a 1.
A quick function to serialize it to a byte:
func toByte() -> UInt8 {
var values = FlagValue.always
if c { values.insert(.c) }
if z { values.insert(.z) }
if i { values.insert(.i) }
if d { values.insert(.d) }
if b { values.insert(.b) }
if v { values.insert(.v) }
if n { values.insert(.n) }
return values.rawValue
}
And the reverse, creating a new FlagSet from a byte:
init(fromByte : UInt8) {
let values = FlagValue(rawValue:fromByte)
c = values.contains(.c)
z = values.contains(.z)
i = values.contains(.i)
d = values.contains(.d)
b = values.contains(.b)
v = values.contains(.v)
n = values.contains(.n)
}I'm trying not to worry too much about efficiency right now, but I was curious and did check that these contains and insert function calls of the OptionSet bitset can be inlined, though the assembly seemed overly complex. (Did you know Godbolt has Swift now?)
In future installments: modelling the memory space and opcodes.