diff --git a/FruitMachine.xcodeproj/project.pbxproj b/FruitMachine.xcodeproj/project.pbxproj index de4da2f..b05db01 100644 --- a/FruitMachine.xcodeproj/project.pbxproj +++ b/FruitMachine.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 2AD458E11F2064CB00F05121 /* MemoryInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD458E01F2064CB00F05121 /* MemoryInterface.swift */; }; 2AD458E31F20661300F05121 /* CPUInstructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD458E21F20661300F05121 /* CPUInstructions.swift */; }; 2AD458E51F2070DF00F05121 /* Opcodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD458E41F2070DF00F05121 /* Opcodes.swift */; }; + 2AD6D5841F26E6BF008F3CF5 /* DebuggerCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AD6D5831F26E6BF008F3CF5 /* DebuggerCommands.swift */; }; 2AE5BA041F23DE4400FAA343 /* Disassembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE5BA031F23DE4400FAA343 /* Disassembly.swift */; }; 2AE5BA061F2469EB00FAA343 /* AddressConversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AE5BA051F2469EB00FAA343 /* AddressConversions.swift */; }; /* End PBXBuildFile section */ @@ -33,6 +34,7 @@ 2AD458E01F2064CB00F05121 /* MemoryInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryInterface.swift; sourceTree = ""; }; 2AD458E21F20661300F05121 /* CPUInstructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUInstructions.swift; sourceTree = ""; }; 2AD458E41F2070DF00F05121 /* Opcodes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Opcodes.swift; sourceTree = ""; }; + 2AD6D5831F26E6BF008F3CF5 /* DebuggerCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebuggerCommands.swift; sourceTree = ""; }; 2AE5BA031F23DE4400FAA343 /* Disassembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disassembly.swift; sourceTree = ""; }; 2AE5BA051F2469EB00FAA343 /* AddressConversions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressConversions.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -70,6 +72,7 @@ 2AD458DD1F205F0D00F05121 /* M6502 */, 2AD458CD1F205EB700F05121 /* AppDelegate.swift */, 2AD458CF1F205EB700F05121 /* DebuggerViewController.swift */, + 2AD6D5831F26E6BF008F3CF5 /* DebuggerCommands.swift */, 2AD458D11F205EB700F05121 /* Assets.xcassets */, 2AD458D31F205EB700F05121 /* Main.storyboard */, 2AD458D61F205EB700F05121 /* Info.plist */, @@ -166,6 +169,7 @@ 2AD458D01F205EB700F05121 /* DebuggerViewController.swift in Sources */, 2AD458CE1F205EB700F05121 /* AppDelegate.swift in Sources */, 2A22EBFB1F21A7A700A36A61 /* IntegerExtensions.swift in Sources */, + 2AD6D5841F26E6BF008F3CF5 /* DebuggerCommands.swift in Sources */, 2AE5BA041F23DE4400FAA343 /* Disassembly.swift in Sources */, 2AD458E51F2070DF00F05121 /* Opcodes.swift in Sources */, 2AE5BA061F2469EB00FAA343 /* AddressConversions.swift in Sources */, diff --git a/FruitMachine/Base.lproj/Main.storyboard b/FruitMachine/Base.lproj/Main.storyboard index 07316cb..08b9b8b 100644 --- a/FruitMachine/Base.lproj/Main.storyboard +++ b/FruitMachine/Base.lproj/Main.storyboard @@ -707,11 +707,11 @@ - + - + @@ -720,7 +720,7 @@ - + @@ -729,7 +729,7 @@ - + @@ -738,7 +738,7 @@ - + @@ -747,7 +747,7 @@ - + @@ -756,7 +756,7 @@ - + @@ -765,7 +765,7 @@ - + @@ -774,7 +774,7 @@ - + @@ -783,7 +783,7 @@ - + @@ -792,7 +792,7 @@ - + @@ -801,7 +801,7 @@ - + @@ -810,7 +810,7 @@ - + @@ -819,7 +819,7 @@ - + @@ -969,7 +969,7 @@ + @@ -991,6 +1043,8 @@ + + diff --git a/FruitMachine/DebugConsole.swift b/FruitMachine/DebugConsole.swift new file mode 100644 index 0000000..be9861b --- /dev/null +++ b/FruitMachine/DebugConsole.swift @@ -0,0 +1,25 @@ +// +// DebugConsole.swift +// FruitMachine +// +// Created by Christopher Rohl on 7/24/17. +// Copyright © 2017 Christopher Rohl. All rights reserved. +// + +import Cocoa + +class DebugConsole: NSObject { + func interpretCommand(command: String) { + let commandSplit = command.split(separator: " ") + if(commandSplit[0] == "bplist") { + + } + + } +} + +class DebugCommands: NSObject { + static func bplist() -> String { + + } +} diff --git a/FruitMachine/DebuggerCommands.swift b/FruitMachine/DebuggerCommands.swift new file mode 100644 index 0000000..85f33b0 --- /dev/null +++ b/FruitMachine/DebuggerCommands.swift @@ -0,0 +1,86 @@ +// +// DebuggerCommands.swift +// FruitMachine +// +// Created by Christopher Rohl on 7/24/17. +// Copyright © 2017 Christopher Rohl. All rights reserved. +// + +import Cocoa + +extension DebuggerViewController { + func interpretCommand(command: String) { + debugConsolePrint(str: "> \(command)", newline: true) + + let commandSplit = command.components(separatedBy: " ") + var parameters: [String] = commandSplit + parameters.remove(at: 0) + + if(commandSplit[0] == "bplist") + { + debugConsolePrint(str: DebuggerCommands.bplist(state: cpuInstance, parameters: parameters), newline: true) + } + else if(commandSplit[0] == "bpdel") + { + debugConsolePrint(str: DebuggerCommands.bpdel(state: cpuInstance, parameters: parameters), newline: true) + } + else if(commandSplit[0] == "bpadd") + { + debugConsolePrint(str: DebuggerCommands.bpadd(state: cpuInstance, parameters: parameters), newline: true) + } + else + { + debugConsolePrint(str: "Unrecognized command", newline: true) + } + text_debugger_output.scrollToEndOfDocument(self) + } +} + +class DebuggerCommands: NSObject { + static func bplist(state: CPU, parameters: [String]) -> String { + var output = "" + for (index, bp) in state.breakpoints.enumerated() { + output += "Breakpoint \(index): $\(bp.asHexString())\r\n" + } + + return output + } + + static func bpadd(state: CPU, parameters: [String]) -> String { + var output = "" + let val = UInt16(parameters[0]) + + if(val != nil) { + state.breakpoints.append(val!) + output += "Breakpoint added at $\(val!.asHexString())." + } + else { + output += "Usage: bpadd
" + } + + return output + } + + static func bpdel(state: CPU, parameters: [String]) -> String { + var output = "" + let val = Int(parameters[0]) + + if(val != nil) + { + if (val! >= 0 && val! < state.breakpoints.count) { + state.breakpoints.remove(at: val!) + output += "Breakpoint \(val!) deleted." + } + else + { + output += "Breakpoint \(val!) does not exist." + } + } + else + { + output += "Usage: bpdel . Use bplist to find breakpoint numbers." + } + + return output + } +} diff --git a/FruitMachine/DebuggerViewController.swift b/FruitMachine/DebuggerViewController.swift index f019f74..997fe63 100644 --- a/FruitMachine/DebuggerViewController.swift +++ b/FruitMachine/DebuggerViewController.swift @@ -16,6 +16,9 @@ class DebuggerViewController: NSViewController { @IBOutlet weak var text_CPU_SR: NSTextField! @IBOutlet weak var text_CPU_Flags: NSTextField! + @IBOutlet weak var text_debugger_output: NSTextView! + @IBOutlet weak var text_debugger_input: NSTextField! + @IBOutlet weak var debuggerTableView: NSTableView! var cpuInstance = CPU.sharedInstance @@ -43,10 +46,8 @@ class DebuggerViewController: NSViewController { text_CPU_SR.stringValue = String(format:"%02X", cpuInstance.stack_pointer) text_CPU_Flags.stringValue = String(cpuInstance.status_register.asString()) - if(!highlightCurrentInstruction()) { - disassembly = cpuInstance.disassemble(fromAddress: cpuInstance.program_counter, length: 256) - highlightCurrentInstruction() - } + disassembly = cpuInstance.disassemble(fromAddress: 0, length: 10000) + highlightCurrentInstruction() } override func viewDidLoad() { @@ -59,9 +60,11 @@ class DebuggerViewController: NSViewController { cpuInstance.performReset() cpuInstance.program_counter = 0x400 //entry point for the test program updateCPUStatusFields() - disassembly = cpuInstance.disassemble(fromAddress: cpuInstance.program_counter, length: 10000) + disassembly = cpuInstance.disassemble(fromAddress: 0, length: 10000) debuggerTableView.reloadData() + cpuInstance.breakpoints.append(0x0528) + // Do any additional setup after loading the view. } @@ -75,7 +78,8 @@ class DebuggerViewController: NSViewController { do { try cpuInstance.executeNextInstruction() } catch CPUExceptions.invalidInstruction { - print("*** 6502 Exception: Invalid instruction 0xXX at 0xXXXX") + isRunning = false + } catch { print(error) } @@ -83,20 +87,26 @@ class DebuggerViewController: NSViewController { func cpuRun() { isRunning = true - let queue = DispatchQueue(label: "com.luigithirty.m6502.instructions") - let main = DispatchQueue.main - queue.async { - while(self.isRunning == true) - { - queue.asyncAfter(deadline: .now() + .seconds(1), execute: { - self.cpuStep() - main.sync { - self.updateCPUStatusFields() - } - }) + cpuInstance.cycles = 0 + cpuInstance.cyclesInBatch = 1000 + + while(!cpuInstance.checkOutOfCycles() && isRunning) { + cpuStep() + + if (cpuInstance.breakpoints.contains(cpuInstance.program_counter)) { + isRunning = false + updateCPUStatusFields() + debugConsolePrint(str: "Breakpoint reached at $\(cpuInstance.program_counter.asHexString())", newline: true) } } + + } + + func queueCPUStep(queue: DispatchQueue) { + queue.async { + self.cpuStep() + } } @IBAction func btn_CPUStep(_ sender: Any) { @@ -112,8 +122,26 @@ class DebuggerViewController: NSViewController { @IBAction func btn_CPURun(_ sender: Any) { cpuRun() } + + @IBAction func btn_CPU_Restart(_ sender: Any) { + cpuInstance.performReset() + cpuInstance.program_counter = 0x400 + debugConsolePrint(str: "CPU restarted from $0400", newline: true) + } + @IBAction func debuggerInput_submit(_ sender: NSTextField) { + interpretCommand(command: sender.stringValue) + sender.stringValue = "" + } + + func debugConsolePrint(str: String, newline: Bool) { + text_debugger_output.appendText(line: str) + if(newline) { + text_debugger_output.appendText(line:"\r\n") + } + } + } extension DebuggerViewController: NSTableViewDelegate { @@ -150,7 +178,7 @@ extension DebuggerViewController: NSTableViewDelegate { if((operation.data[1] & 0x80) == 0x80) { destination = destination + 1 - UInt16(~operation.data[1]) } else { - destination = destination + UInt16(operation.data[1]) + destination = destination + 2 + UInt16(operation.data[1]) } cellText = String(format: "%@ #$%04X", operation.instruction!.mnemonic, destination) case .absolute: @@ -204,3 +232,21 @@ extension DebuggerViewController: NSTableViewDataSource { return disassembly[atIndex] } } + +extension NSTextView { + func appendText(line: String) { + let attrDict = [NSAttributedStringKey.font: NSFont.userFixedPitchFont(ofSize: 11)] + let astring = NSAttributedString(string: "\(line)", attributes: attrDict) + self.textStorage?.append(astring) + let loc = self.string.lengthOfBytes(using: String.Encoding.utf8) + + let range = NSRange(location: loc, length: 0) + self.scrollRangeToVisible(range) + } +} + +func xlog(logView:NSTextView?, line:String) { + if let view = logView { + view.appendText(line: line) + } +} diff --git a/FruitMachine/M6502/CPU.swift b/FruitMachine/M6502/CPU.swift index 225816d..0b3e747 100644 --- a/FruitMachine/M6502/CPU.swift +++ b/FruitMachine/M6502/CPU.swift @@ -113,6 +113,7 @@ class CPU: NSObject { static var sharedInstance = CPU() var cycles: Int + var cyclesInBatch: Int var instruction_register: UInt8 @@ -128,8 +129,11 @@ class CPU: NSObject { var memoryInterface: MemoryInterface + var breakpoints: [UInt16] + override init() { cycles = 0 + cyclesInBatch = 0 instruction_register = 0 @@ -146,6 +150,8 @@ class CPU: NSObject { //Branches incur a 1-cycle penalty if taken plus the page boundary penalty if necessary. branch_was_taken = false + breakpoints = [UInt16]() + } func getOperandByte() -> UInt8 { @@ -205,6 +211,14 @@ class CPU: NSObject { } } + func checkOutOfCycles() -> Bool { + if(cycles > cyclesInBatch) { + return true + } else { + return false + } + } + func performReset() { program_counter = memoryInterface.readWord(offset: RESET_VECTOR) stack_pointer = 0xFF diff --git a/FruitMachine/M6502/IntegerExtensions.swift b/FruitMachine/M6502/IntegerExtensions.swift index 4d6262f..516a2c5 100644 --- a/FruitMachine/M6502/IntegerExtensions.swift +++ b/FruitMachine/M6502/IntegerExtensions.swift @@ -12,4 +12,8 @@ extension UInt16 { static func + (left: UInt16, right: UInt8) -> UInt16 { return left + UInt16(right) } + + func asHexString() -> String { + return String(format: "%04X", self) + } } diff --git a/FruitMachine/M6502/Opcodes.swift b/FruitMachine/M6502/Opcodes.swift index 738b762..54aec8c 100644 --- a/FruitMachine/M6502/Opcodes.swift +++ b/FruitMachine/M6502/Opcodes.swift @@ -29,7 +29,7 @@ extension CPU { func pushWord(data: UInt16) -> Void { let low = UInt8(data & 0x00FF) - let high = UInt8(data & 0xFF00) + let high = UInt8((data & 0xFF00) >> 8) pushByte(data: low) pushByte(data: high) @@ -52,8 +52,18 @@ extension CPU { } } - program_counter = UInt16(Int(program_counter) + Int(distance)) + if(distance > 0) { + program_counter = UInt16(Int(program_counter) + Int(distance)) + } else { + program_counter = UInt16(Int(program_counter) + Int(distance)) + } + branch_was_taken = true + + if(distance == -2) { + print("Infinite loop at $\(program_counter.asHexString()). Halting execution.") + cyclesInBatch = 0 + } } } @@ -116,7 +126,7 @@ func getOperandWordForAddressingMode(state: CPU, mode: AddressingMode) -> UInt16 let pointer: UInt16 = state.memoryInterface.readWord(offset: UInt16(zp)) + UInt16(state.index_y) return state.memoryInterface.readWord(offset: pointer) default: - print("Called getOperand: UInt16 on an instruction that expects a UInt8") + print("Called getOperand: UInt16 on an instruction that expects a UInt8. Address: \(state.program_counter.asHexString())") return 0 } @@ -231,7 +241,7 @@ class Opcodes: NSObject { return } } - + static func STY(state: CPU, addressingMode: AddressingMode) -> Void { let address: UInt16 @@ -294,14 +304,14 @@ class Opcodes: NSObject { } static func DEY(state: CPU, addressingMode: AddressingMode) -> Void { - state.index_y = state.index_x &- 1 + state.index_y = state.index_y &- 1 state.updateZeroFlag(value: state.index_y); state.updateNegativeFlag(value: state.index_y); } static func INY(state: CPU, addressingMode: AddressingMode) -> Void { - state.index_y = state.index_x &+ 1 + state.index_y = state.index_y &+ 1 state.updateZeroFlag(value: state.index_y); state.updateNegativeFlag(value: state.index_y);