mirror of
https://github.com/Candygoblen123/SwiftNES.git
synced 2024-11-21 17:46:23 -06:00
add LDA, STA, TAX, INX, BRK
This commit is contained in:
commit
f0b3dcd95b
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/configuration/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
15
Package.swift
Normal file
15
Package.swift
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// swift-tools-version: 5.9
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "SwiftNES",
|
||||||
|
targets: [
|
||||||
|
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||||
|
// Targets can depend on other targets in this package and products from dependencies.
|
||||||
|
.executableTarget(
|
||||||
|
name: "SwiftNES"),
|
||||||
|
.testTarget(name: "SwiftNESTests", dependencies: ["SwiftNES"])
|
||||||
|
]
|
||||||
|
)
|
182
Sources/CPU.swift
Normal file
182
Sources/CPU.swift
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
|
||||||
|
|
||||||
|
enum AddressingMode {
|
||||||
|
case Immediate
|
||||||
|
case ZeroPage
|
||||||
|
case ZeroPage_X
|
||||||
|
case ZeroPage_Y
|
||||||
|
case Absolute
|
||||||
|
case Absolute_X
|
||||||
|
case Absolute_Y
|
||||||
|
case Indirect_X
|
||||||
|
case Indirect_Y
|
||||||
|
case NoneAddressing
|
||||||
|
}
|
||||||
|
|
||||||
|
class CPU {
|
||||||
|
var register_a: UInt8 = 0
|
||||||
|
var register_x: UInt8 = 0
|
||||||
|
var register_y: UInt8 = 0
|
||||||
|
|
||||||
|
var status: UInt8 = 0
|
||||||
|
var programCounter: UInt16 = 0
|
||||||
|
private var memory = [UInt8](repeating: 0, count: 0xFFFF)
|
||||||
|
|
||||||
|
|
||||||
|
func getOpperandAddress(_ mode: AddressingMode) -> UInt16 {
|
||||||
|
switch mode {
|
||||||
|
case .Immediate:
|
||||||
|
return programCounter
|
||||||
|
case .ZeroPage:
|
||||||
|
return UInt16(memRead(programCounter))
|
||||||
|
case .Absolute:
|
||||||
|
return memReadU16(programCounter)
|
||||||
|
case .ZeroPage_X:
|
||||||
|
let pos = memRead(programCounter)
|
||||||
|
let addr = pos &+ register_x
|
||||||
|
return UInt16(addr)
|
||||||
|
case .ZeroPage_Y:
|
||||||
|
let pos = memRead(programCounter)
|
||||||
|
let addr = pos &+ register_y
|
||||||
|
return UInt16(addr)
|
||||||
|
case .Absolute_X:
|
||||||
|
let base = memReadU16(programCounter)
|
||||||
|
return base &+ UInt16(register_x)
|
||||||
|
case .Absolute_Y:
|
||||||
|
let base = memReadU16(programCounter)
|
||||||
|
return base &+ UInt16(register_y)
|
||||||
|
case .Indirect_X:
|
||||||
|
let base = memRead(programCounter)
|
||||||
|
|
||||||
|
let ptr = UInt8(base) &+ register_x
|
||||||
|
let lo = memRead(UInt16(ptr))
|
||||||
|
let hi = memRead(UInt16(ptr &+ 1))
|
||||||
|
return UInt16(hi) << 8 | UInt16(lo)
|
||||||
|
case .Indirect_Y:
|
||||||
|
let base = memRead(programCounter)
|
||||||
|
|
||||||
|
let lo = memRead(UInt16(base))
|
||||||
|
let hi = memRead(UInt16(base &+ 1))
|
||||||
|
let deref_base = UInt16(hi) << 8 | UInt16(lo)
|
||||||
|
let deref = deref_base &+ UInt16(register_y)
|
||||||
|
return deref
|
||||||
|
case .NoneAddressing:
|
||||||
|
fatalError("mode \(mode) is not implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func memRead(_ addr: UInt16) -> UInt8 {
|
||||||
|
memory[Int(addr)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func memReadU16(_ addr: UInt16) -> UInt16 {
|
||||||
|
let lo = UInt16(memRead(addr))
|
||||||
|
let hi = UInt16(memRead(addr + 1))
|
||||||
|
return (hi << 8) | lo
|
||||||
|
}
|
||||||
|
|
||||||
|
func memWriteU16(addr: UInt16, data: UInt16) {
|
||||||
|
let hi = UInt8(data >> 8)
|
||||||
|
let lo = UInt8(data & 0xff)
|
||||||
|
self.memWrite(addr: addr, data: lo)
|
||||||
|
self.memWrite(addr: addr + 1, data: hi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func memWrite(addr: UInt16, data: UInt8) {
|
||||||
|
memory[Int(addr)] = data
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset() {
|
||||||
|
register_a = 0
|
||||||
|
register_x = 0
|
||||||
|
register_y = 0
|
||||||
|
status = 0
|
||||||
|
|
||||||
|
programCounter = self.memReadU16(0xFFFC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAndRun(_ program: [UInt8]) {
|
||||||
|
load(program)
|
||||||
|
reset()
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func load(_ program: [UInt8]) {
|
||||||
|
memory[0x8000 ..< (0x8000 + program.count)] = program[0..<program.count]
|
||||||
|
memWriteU16(addr: 0xFFFC, data: 0x8000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() {
|
||||||
|
let opcodes = OPCODES_MAP
|
||||||
|
|
||||||
|
while true {
|
||||||
|
let code = memRead(programCounter)
|
||||||
|
programCounter += 1
|
||||||
|
|
||||||
|
let programCounterState = programCounter
|
||||||
|
guard let opcode = opcodes[code] else {fatalError("OpCode \(code) not recgonized!")}
|
||||||
|
|
||||||
|
switch code {
|
||||||
|
/// LDA
|
||||||
|
case 0xa9, 0xa5, 0xb5, 0xad, 0xbd, 0xb9, 0xa1, 0xb1:
|
||||||
|
lda(opcode.mode)
|
||||||
|
/// STA
|
||||||
|
case 0x85, 0x95, 0x8d, 0x9d, 0x99, 0x81, 0x91:
|
||||||
|
sta(opcode.mode)
|
||||||
|
/// TAX
|
||||||
|
case 0xAA:
|
||||||
|
tax()
|
||||||
|
/// INX
|
||||||
|
case 0xE8:
|
||||||
|
inx()
|
||||||
|
/// BRK
|
||||||
|
case 0x00:
|
||||||
|
return
|
||||||
|
default: fatalError("TODO!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if programCounterState == programCounter {
|
||||||
|
programCounter += UInt16(opcode.len - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateZeroAndNegativeFlags(_ result: UInt8) {
|
||||||
|
if result == 0 {
|
||||||
|
status = status | 0b0000_0010
|
||||||
|
} else {
|
||||||
|
status = status & 0b1111_1101
|
||||||
|
}
|
||||||
|
|
||||||
|
if result & 0b1000_0000 != 0 {
|
||||||
|
status = status | 0b1000_0000
|
||||||
|
} else {
|
||||||
|
status = status & 0b0111_1111
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lda(_ mode: AddressingMode) {
|
||||||
|
let addr = getOpperandAddress(mode)
|
||||||
|
let value = memRead(addr)
|
||||||
|
|
||||||
|
register_a = value
|
||||||
|
updateZeroAndNegativeFlags(register_a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tax() {
|
||||||
|
register_x = register_a
|
||||||
|
updateZeroAndNegativeFlags(register_x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func inx() {
|
||||||
|
register_x = register_x &+ 1
|
||||||
|
updateZeroAndNegativeFlags(register_x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sta(_ mode: AddressingMode) {
|
||||||
|
let addr = getOpperandAddress(mode)
|
||||||
|
memWrite(addr: addr, data: register_a)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
7
Sources/main.swift
Normal file
7
Sources/main.swift
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// The Swift Programming Language
|
||||||
|
// https://docs.swift.org/swift-book
|
||||||
|
|
||||||
|
let cpu = CPU()
|
||||||
|
let program: [UInt8] = [0xa9, 0xc0, 0xaa, 0xe8, 0x00]
|
||||||
|
cpu.loadAndRun(program)
|
||||||
|
print(cpu.status)
|
38
Sources/opcodes.swift
Normal file
38
Sources/opcodes.swift
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
struct OpCode {
|
||||||
|
let code: UInt8
|
||||||
|
let mnemonic: String
|
||||||
|
let len: UInt8
|
||||||
|
let cycles: UInt8
|
||||||
|
let mode: AddressingMode
|
||||||
|
}
|
||||||
|
|
||||||
|
let CPU_OP_CODES: [OpCode] = [
|
||||||
|
OpCode(code: 0x00, mnemonic: "BRK", len: 1, cycles: 7, mode: .NoneAddressing),
|
||||||
|
OpCode(code: 0xaa, mnemonic: "TAX", len: 1, cycles: 2, mode: .NoneAddressing),
|
||||||
|
OpCode(code: 0xe8, mnemonic: "INX", len: 1, cycles: 2, mode: .NoneAddressing),
|
||||||
|
|
||||||
|
OpCode(code: 0xa9, mnemonic: "LDA", len: 2, cycles: 2, mode: .Immediate),
|
||||||
|
OpCode(code: 0xa5, mnemonic: "LDA", len: 2, cycles: 3, mode: .ZeroPage),
|
||||||
|
OpCode(code: 0xb5, mnemonic: "LDA", len: 2, cycles: 4, mode: .ZeroPage_X),
|
||||||
|
OpCode(code: 0xad, mnemonic: "LDA", len: 3, cycles: 4, mode: .Absolute),
|
||||||
|
OpCode(code: 0xbd, mnemonic: "LDA", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_X),
|
||||||
|
OpCode(code: 0xb9, mnemonic: "LDA", len: 3, cycles: 4 /* +1 if page crossed */, mode: .Absolute_Y),
|
||||||
|
OpCode(code: 0xa1, mnemonic: "LDA", len: 2, cycles: 6, mode: .Indirect_X),
|
||||||
|
OpCode(code: 0xb1, mnemonic: "LDA", len: 2, cycles: 5 /* +1 if page crossed */, mode: .Indirect_Y),
|
||||||
|
|
||||||
|
OpCode(code: 0xa5, mnemonic: "STA", len: 2, cycles: 3, mode: .ZeroPage),
|
||||||
|
OpCode(code: 0x95, mnemonic: "STA", len: 2, cycles: 4, mode: .ZeroPage_X),
|
||||||
|
OpCode(code: 0x8d, mnemonic: "STA", len: 3, cycles: 4, mode: .Absolute),
|
||||||
|
OpCode(code: 0x9d, mnemonic: "STA", len: 3, cycles: 5, mode: .Absolute_X),
|
||||||
|
OpCode(code: 0x99, mnemonic: "STA", len: 3, cycles: 5, mode: .Absolute_Y),
|
||||||
|
OpCode(code: 0x81, mnemonic: "STA", len: 2, cycles: 6, mode: .Indirect_X),
|
||||||
|
OpCode(code: 0x91, mnemonic: "STA", len: 2, cycles: 6, mode: .Indirect_Y),
|
||||||
|
]
|
||||||
|
|
||||||
|
let OPCODES_MAP: [UInt8: OpCode] = {
|
||||||
|
var map: [UInt8:OpCode] = [:]
|
||||||
|
for cpuop in CPU_OP_CODES {
|
||||||
|
map[cpuop.code] = cpuop
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}()
|
72
Tests/CPU.swift
Normal file
72
Tests/CPU.swift
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import SwiftNES
|
||||||
|
|
||||||
|
class CPUTests: XCTestCase {
|
||||||
|
func test_0xa9_lda_immediate_load_data() {
|
||||||
|
let cpu = CPU()
|
||||||
|
cpu.loadAndRun([0xa9, 0x05, 0x00])
|
||||||
|
|
||||||
|
XCTAssertEqual(cpu.register_a, 0x05)
|
||||||
|
XCTAssert(cpu.status & 0b0000_0010 == 0b00)
|
||||||
|
XCTAssert(cpu.status & 0b1000_0000 == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_lda_from_memory() {
|
||||||
|
let cpu = CPU()
|
||||||
|
cpu.memWrite(addr: 0x10, data: 0x55)
|
||||||
|
|
||||||
|
cpu.loadAndRun([0xa5, 0x10, 0x00])
|
||||||
|
|
||||||
|
XCTAssertEqual(cpu.register_a, 0x55)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_0xa9_lda_zero_flag() {
|
||||||
|
let cpu = CPU()
|
||||||
|
cpu.loadAndRun([0xa9, 0x00, 0x00])
|
||||||
|
XCTAssert(cpu.status & 0b0000_0010 == 0b10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_0xa9_lda_neg_flag() {
|
||||||
|
let cpu = CPU()
|
||||||
|
cpu.loadAndRun([0xa9, 0xFF, 0x00])
|
||||||
|
XCTAssert(cpu.status & 0b1000_0000 == 0b1000_0000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_0xaa_tax_move_a_to_x() {
|
||||||
|
let cpu = CPU()
|
||||||
|
|
||||||
|
cpu.load([0xaa, 0x00])
|
||||||
|
cpu.reset()
|
||||||
|
cpu.register_a = 10
|
||||||
|
cpu.run()
|
||||||
|
XCTAssertEqual(cpu.register_x, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_0xe8_inx_incriment_x() {
|
||||||
|
let cpu = CPU()
|
||||||
|
|
||||||
|
cpu.load([0xe8, 0x00])
|
||||||
|
cpu.reset()
|
||||||
|
cpu.register_x = 10
|
||||||
|
cpu.run()
|
||||||
|
|
||||||
|
XCTAssertEqual(cpu.register_x, 11)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_5_ops_working_together() {
|
||||||
|
let cpu = CPU()
|
||||||
|
cpu.loadAndRun([0xa9, 0xc0, 0xaa, 0xe8, 0x00])
|
||||||
|
|
||||||
|
XCTAssertEqual(cpu.register_x, 0xc1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_inx_overflow() {
|
||||||
|
let cpu = CPU()
|
||||||
|
|
||||||
|
cpu.load([0xe8, 0xe8, 0x00])
|
||||||
|
cpu.reset()
|
||||||
|
cpu.register_x = 0xff
|
||||||
|
cpu.run()
|
||||||
|
XCTAssertEqual(cpu.register_x, 1)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user