mirror of https://github.com/irmen/ksim65.git
fix disassembly issues, added ehBasic machine
This commit is contained in:
parent
ba8946c29c
commit
8aad9795f7
|
@ -24,6 +24,12 @@ Properties of this simulator:
|
|||
- passes several extensive unit test suites that verify instruction and cpu flags behavior
|
||||
- maximum simulated performance is a 6502 running at ~100 Mhz (on my machine)
|
||||
|
||||
## Virtual machine examples
|
||||
|
||||
Two virtual example machines are included.
|
||||
The default one starts with ``gradle run`` or run the ``ksim64vm`` command.
|
||||
There's another one ``ehBasicMain`` that is configured to run the "enhanced 6502 basic" ROM.
|
||||
|
||||
## Documentation
|
||||
|
||||
Still to be written. For now, use the source ;-)
|
||||
|
|
|
@ -42,7 +42,7 @@ dependencies {
|
|||
|
||||
application {
|
||||
applicationName = "ksim65vm"
|
||||
mainClassName = "razorvine.examplemachine.SystemMainKt"
|
||||
mainClassName = "razorvine.examplemachine.MachineMainKt"
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package razorvine.examplemachine
|
||||
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import java.awt.*
|
||||
import java.awt.image.BufferedImage
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.event.MouseInputListener
|
||||
import razorvine.ksim65.IHostInterface
|
||||
import razorvine.ksim65.components.MemoryComponent
|
||||
import java.awt.event.*
|
||||
import java.util.*
|
||||
import javax.swing.*
|
||||
|
@ -22,7 +22,7 @@ object ScreenDefs {
|
|||
const val SCREEN_HEIGHT_CHARS = 30
|
||||
const val SCREEN_WIDTH = SCREEN_WIDTH_CHARS * 8
|
||||
const val SCREEN_HEIGHT = SCREEN_HEIGHT_CHARS * 16
|
||||
const val DISPLAY_PIXEL_SCALING: Double = 1.5
|
||||
const val DISPLAY_PIXEL_SCALING: Double = 1.25
|
||||
val BG_COLOR = Color(0, 10, 20)
|
||||
val FG_COLOR = Color(200, 255, 230)
|
||||
val BORDER_COLOR = Color(20, 30, 40)
|
||||
|
@ -159,7 +159,6 @@ class DebugWindow(val vm: VirtualMachine) : JFrame("debugger"), ActionListener {
|
|||
private val pauseBt = JButton("Pause").also { it.actionCommand = "pause" }
|
||||
|
||||
init {
|
||||
isFocusable = true
|
||||
defaultCloseOperation = EXIT_ON_CLOSE
|
||||
preferredSize = Dimension(350, 600)
|
||||
val cpuPanel = JPanel(GridBagLayout())
|
||||
|
@ -222,11 +221,11 @@ class DebugWindow(val vm: VirtualMachine) : JFrame("debugger"), ActionListener {
|
|||
when(e.actionCommand) {
|
||||
"reset" -> {
|
||||
vm.bus.reset()
|
||||
updateCpu(vm.cpu, vm.ram)
|
||||
updateCpu(vm.cpu, vm.bus)
|
||||
}
|
||||
"step" -> {
|
||||
vm.stepInstruction()
|
||||
updateCpu(vm.cpu, vm.ram)
|
||||
updateCpu(vm.cpu, vm.bus)
|
||||
}
|
||||
"pause" -> {
|
||||
vm.paused = true
|
||||
|
@ -246,7 +245,7 @@ class DebugWindow(val vm: VirtualMachine) : JFrame("debugger"), ActionListener {
|
|||
}
|
||||
}
|
||||
|
||||
fun updateCpu(cpu: Cpu6502, mem: MemoryComponent) {
|
||||
fun updateCpu(cpu: Cpu6502, bus: Bus) {
|
||||
cyclesTf.text = cpu.totalCycles.toString()
|
||||
regAtf.text = cpu.hexB(cpu.regA)
|
||||
regXtf.text = cpu.hexB(cpu.regX)
|
||||
|
@ -254,7 +253,8 @@ class DebugWindow(val vm: VirtualMachine) : JFrame("debugger"), ActionListener {
|
|||
regPtf.text = "NV-BDIZC\n" + cpu.regP.asByte().toString(2).padStart(8, '0')
|
||||
regPCtf.text = cpu.hexW(cpu.regPC)
|
||||
regSPtf.text = cpu.hexB(cpu.regSP)
|
||||
disassemTf.text = cpu.disassembleOneInstruction(mem.data, cpu.regPC, mem.startAddress).first.substringAfter(' ').trim()
|
||||
val memory = bus.memoryComponentFor(cpu.regPC)
|
||||
disassemTf.text = cpu.disassembleOneInstruction(memory.data, cpu.regPC, memory.startAddress).first.substringAfter(' ').trim()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,6 +327,8 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener
|
|||
setLocationRelativeTo(null)
|
||||
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, mutableSetOf())
|
||||
isVisible = true
|
||||
toFront()
|
||||
requestFocus()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package razorvine.examplemachine
|
||||
|
||||
import kotlin.concurrent.scheduleAtFixedRate
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.Version
|
||||
import razorvine.ksim65.components.*
|
||||
import javax.swing.ImageIcon
|
||||
|
||||
/**
|
||||
* A virtual computer constructed from the various virtual components,
|
||||
* running the 6502 Enhanced Basic ROM.
|
||||
*/
|
||||
class EhBasicMachine(title: String) {
|
||||
val bus = Bus()
|
||||
val cpu = Cpu6502(false)
|
||||
val ram = Ram(0x0000, 0xbfff)
|
||||
val rom = Rom(0xc000, 0xffff).also { it.load(javaClass.getResourceAsStream("/ehbasic_C000.bin").readAllBytes()) }
|
||||
|
||||
private val hostDisplay = MainWindow(title)
|
||||
private val display = Display(0xd000, 0xd00a, hostDisplay,
|
||||
ScreenDefs.SCREEN_WIDTH_CHARS, ScreenDefs.SCREEN_HEIGHT_CHARS,
|
||||
ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
|
||||
private val keyboard = Keyboard(0xd400, 0xd400, hostDisplay)
|
||||
|
||||
init {
|
||||
hostDisplay.iconImage = ImageIcon(javaClass.getResource("/icon.png")).image
|
||||
|
||||
bus += display
|
||||
bus += keyboard
|
||||
bus += rom
|
||||
bus += ram
|
||||
bus += cpu
|
||||
bus.reset()
|
||||
|
||||
hostDisplay.start()
|
||||
hostDisplay.requestFocus()
|
||||
}
|
||||
|
||||
var paused = false
|
||||
|
||||
fun stepInstruction() {
|
||||
while (cpu.instrCycles > 0) bus.clock()
|
||||
bus.clock()
|
||||
while (cpu.instrCycles > 0) bus.clock()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
val timer = java.util.Timer("clock", true)
|
||||
timer.scheduleAtFixedRate(1, 1) {
|
||||
if(!paused) {
|
||||
repeat(500) {
|
||||
stepInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val machine = EhBasicMachine("KSim65 demo virtual machine - using ksim65 v${Version.version}")
|
||||
machine.start()
|
||||
}
|
|
@ -18,8 +18,8 @@ class VirtualMachine(title: String) {
|
|||
private val rtc = RealTimeClock(0xd100, 0xd108)
|
||||
private val timer = Timer(0xd200, 0xd203, cpu)
|
||||
|
||||
private val hostDisplay = MainWindow(title)
|
||||
private val debugWindow = DebugWindow(this)
|
||||
private val hostDisplay = MainWindow(title)
|
||||
private val display = Display(0xd000, 0xd00a, hostDisplay,
|
||||
ScreenDefs.SCREEN_WIDTH_CHARS, ScreenDefs.SCREEN_HEIGHT_CHARS,
|
||||
ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
|
||||
|
@ -47,6 +47,7 @@ class VirtualMachine(title: String) {
|
|||
|
||||
debugWindow.setLocation(hostDisplay.location.x+hostDisplay.width, hostDisplay.location.y)
|
||||
debugWindow.isVisible = true
|
||||
hostDisplay.requestFocus()
|
||||
}
|
||||
|
||||
var paused = false
|
||||
|
@ -62,10 +63,10 @@ class VirtualMachine(title: String) {
|
|||
val startTime = System.currentTimeMillis()
|
||||
timer.scheduleAtFixedRate(1, 1) {
|
||||
if(!paused) {
|
||||
repeat(20) {
|
||||
repeat(50) {
|
||||
stepInstruction()
|
||||
}
|
||||
debugWindow.updateCpu(cpu, ram)
|
||||
debugWindow.updateCpu(cpu, bus)
|
||||
val duration = System.currentTimeMillis() - startTime
|
||||
val speedKhz = cpu.totalCycles.toDouble() / duration
|
||||
debugWindow.speedKhzTf.text = "%.1f".format(speedKhz)
|
|
@ -1,9 +1,6 @@
|
|||
package razorvine.ksim65
|
||||
|
||||
import razorvine.ksim65.components.Address
|
||||
import razorvine.ksim65.components.BusComponent
|
||||
import razorvine.ksim65.components.MemMappedComponent
|
||||
import razorvine.ksim65.components.UByte
|
||||
import razorvine.ksim65.components.*
|
||||
|
||||
/**
|
||||
* The system bus that connects all other components together.
|
||||
|
@ -73,4 +70,13 @@ class Bus {
|
|||
it[address] = data
|
||||
}
|
||||
}
|
||||
|
||||
fun memoryComponentFor(address: Address): MemoryComponent {
|
||||
memComponents.forEach {
|
||||
if (it is MemoryComponent && address >= it.startAddress && address <= it.endAddress) {
|
||||
return it
|
||||
}
|
||||
}
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,116 +151,130 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
|||
disassemble(memory.data, memory.startAddress, from, to)
|
||||
|
||||
fun disassemble(memory: Array<UByte>, baseAddress: Address, from: Address, to: Address): List<String> {
|
||||
var location = from - baseAddress
|
||||
var location = from
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
while (location <= (to - baseAddress)) {
|
||||
while (location <= to) {
|
||||
val dis = disassembleOneInstruction(memory, location, baseAddress)
|
||||
result.add(dis.first)
|
||||
location = dis.second
|
||||
location += dis.second
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun disassembleOneInstruction(memory: Array<UByte>, address: Address, baseAddress: Address): Pair<String, Address> {
|
||||
fun disassembleOneInstruction(memory: Array<UByte>, address: Address, baseAddress: Address): Pair<String, Int> {
|
||||
val spacing1 = " "
|
||||
val spacing2 = " "
|
||||
val spacing3 = " "
|
||||
var location = address
|
||||
val location = address-baseAddress
|
||||
val byte = memory[location]
|
||||
var line = "\$${hexW(location+baseAddress)} ${hexB(byte)} "
|
||||
location++
|
||||
val opcode = instructions[byte.toInt()]
|
||||
when (opcode.mode) {
|
||||
return when (opcode.mode) {
|
||||
AddrMode.Acc -> {
|
||||
line += "$spacing1 ${opcode.mnemonic} a"
|
||||
Pair(line, 1)
|
||||
}
|
||||
AddrMode.Imp -> {
|
||||
line += "$spacing1 ${opcode.mnemonic}"
|
||||
Pair(line, 1)
|
||||
}
|
||||
AddrMode.Imm -> {
|
||||
val value = memory[location++]
|
||||
val value = memory[location+1]
|
||||
line += "${hexB(value)} $spacing2 ${opcode.mnemonic} #\$${hexB(value)}"
|
||||
Pair(line, 2)
|
||||
}
|
||||
AddrMode.Zp -> {
|
||||
val zpAddr = memory[location++]
|
||||
val zpAddr = memory[location+1]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)}"
|
||||
Pair(line, 2)
|
||||
}
|
||||
AddrMode.Zpr -> {
|
||||
// addressing mode used by the 65C02, put here for convenience
|
||||
val zpAddr = memory[location++]
|
||||
val rel = memory[location++]
|
||||
val zpAddr = memory[location+1]
|
||||
val rel = memory[location+2]
|
||||
val target =
|
||||
if (rel <= 0x7f)
|
||||
location + rel + baseAddress
|
||||
location + 3 + rel + baseAddress
|
||||
else
|
||||
location - (256 - rel) + baseAddress
|
||||
location + 3 - (256 - rel) + baseAddress
|
||||
line += "${hexB(zpAddr)} ${hexB(rel)} $spacing3 ${opcode.mnemonic} \$${hexB(zpAddr)}, \$${hexW(target, true)}"
|
||||
Pair(line, 3)
|
||||
}
|
||||
AddrMode.Izp -> {
|
||||
// addressing mode used by the 65C02, put here for convenience
|
||||
val zpAddr = memory[location++]
|
||||
val zpAddr = memory[location+1]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$(${hexB(zpAddr)})"
|
||||
Pair(line, 2)
|
||||
}
|
||||
AddrMode.IaX -> {
|
||||
// addressing mode used by the 65C02, put here for convenience
|
||||
val lo = memory[location++]
|
||||
val hi = memory[location++]
|
||||
val lo = memory[location+1]
|
||||
val hi = memory[location+2]
|
||||
val absAddr = lo.toInt() or (hi.toInt() shl 8)
|
||||
line += "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$(${hexW(absAddr)},x)"
|
||||
Pair(line, 3)
|
||||
}
|
||||
AddrMode.ZpX -> {
|
||||
val zpAddr = memory[location++]
|
||||
val zpAddr = memory[location+1]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},x"
|
||||
Pair(line, 2)
|
||||
}
|
||||
AddrMode.ZpY -> {
|
||||
val zpAddr = memory[location++]
|
||||
val zpAddr = memory[location+1]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},y"
|
||||
Pair(line, 2)
|
||||
}
|
||||
AddrMode.Rel -> {
|
||||
val rel = memory[location++]
|
||||
val rel = memory[location+1]
|
||||
val target =
|
||||
if (rel <= 0x7f)
|
||||
location + rel + baseAddress
|
||||
location + 2 + rel + baseAddress
|
||||
else
|
||||
location - (256 - rel) + baseAddress
|
||||
location + 2 - (256 - rel) + baseAddress
|
||||
line += "${hexB(rel)} $spacing2 ${opcode.mnemonic} \$${hexW(target, true)}"
|
||||
Pair(line, 2)
|
||||
}
|
||||
AddrMode.Abs -> {
|
||||
val lo = memory[location++]
|
||||
val hi = memory[location++]
|
||||
val lo = memory[location+1]
|
||||
val hi = memory[location+2]
|
||||
val absAddr = lo.toInt() or (hi.toInt() shl 8)
|
||||
line += "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)}"
|
||||
Pair(line, 3)
|
||||
}
|
||||
AddrMode.AbsX -> {
|
||||
val lo = memory[location++]
|
||||
val hi = memory[location++]
|
||||
val lo = memory[location+1]
|
||||
val hi = memory[location+2]
|
||||
val absAddr = lo.toInt() or (hi.toInt() shl 8)
|
||||
line += "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},x"
|
||||
Pair(line, 3)
|
||||
}
|
||||
AddrMode.AbsY -> {
|
||||
val lo = memory[location++]
|
||||
val hi = memory[location++]
|
||||
val lo = memory[location+1]
|
||||
val hi = memory[location+2]
|
||||
val absAddr = lo.toInt() or (hi.toInt() shl 8)
|
||||
line += "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},y"
|
||||
Pair(line, 3)
|
||||
}
|
||||
AddrMode.Ind -> {
|
||||
val lo = memory[location++]
|
||||
val hi = memory[location++]
|
||||
val lo = memory[location+1]
|
||||
val hi = memory[location+2]
|
||||
val indirectAddr = lo.toInt() or (hi.toInt() shl 8)
|
||||
line += "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} (\$${hexW(indirectAddr)})"
|
||||
Pair(line, 3)
|
||||
}
|
||||
AddrMode.IzX -> {
|
||||
val zpAddr = memory[location++]
|
||||
val zpAddr = memory[location+1]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)},x)"
|
||||
Pair(line, 2)
|
||||
}
|
||||
AddrMode.IzY -> {
|
||||
val zpAddr = memory[location++]
|
||||
val zpAddr = memory[location+1]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)}),y"
|
||||
Pair(line, 2)
|
||||
}
|
||||
}
|
||||
return Pair(line, location)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -53,11 +53,6 @@ class Ram(startAddress: Address, endAddress: Address) : MemoryComponent(startAdd
|
|||
load(bytes, address)
|
||||
}
|
||||
|
||||
fun load(source: URL, address: Address) {
|
||||
val bytes = source.readBytes()
|
||||
load(bytes, address)
|
||||
}
|
||||
|
||||
fun load(data: Array<UByte>, address: Address) =
|
||||
data.forEachIndexed { index, byte ->
|
||||
val baseAddress = address - startAddress
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package razorvine.ksim65.components
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* A ROM chip (read-only memory).
|
||||
*/
|
||||
class Rom(startAddress: Address, endAddress: Address, initialData: Array<UByte>? = null) : MemoryComponent(startAddress, endAddress) {
|
||||
override val data: Array<UByte> =
|
||||
initialData?.copyOf() ?: Array<UByte>(endAddress - startAddress - 1) { 0 }
|
||||
initialData?.copyOf() ?: Array<UByte>(endAddress - startAddress + 1) { 0 }
|
||||
|
||||
init {
|
||||
require(endAddress - startAddress + 1 == data.size) { "rom address range doesn't match size of data bytes" }
|
||||
|
@ -15,4 +17,26 @@ class Rom(startAddress: Address, endAddress: Address, initialData: Array<UByte>?
|
|||
override operator fun set(address: Address, data: UByte) { /* read-only */ }
|
||||
override fun clock() {}
|
||||
override fun reset() {}
|
||||
|
||||
/**
|
||||
* load a binary program at the given address
|
||||
*/
|
||||
fun load(filename: String) {
|
||||
val bytes = File(filename).readBytes()
|
||||
load(bytes)
|
||||
}
|
||||
|
||||
fun load(data: Array<UByte>) =
|
||||
data.forEachIndexed { index, byte ->
|
||||
this.data[index] = byte
|
||||
}
|
||||
|
||||
fun load(data: ByteArray) =
|
||||
data.forEachIndexed { index, byte ->
|
||||
this.data[index] =
|
||||
if (byte >= 0)
|
||||
byte.toShort()
|
||||
else
|
||||
(256 + byte).toShort()
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -116,7 +116,7 @@ class Test6502CpuBasics {
|
|||
val ram = Ram(0, 0xffff)
|
||||
ram[Cpu6502.RESET_vector] = 0x00
|
||||
ram[Cpu6502.RESET_vector +1] = 0x10
|
||||
val bytes = javaClass.getResource("bcdtest6502.bin")!! // only works on 6502, not on the 65c02
|
||||
val bytes = javaClass.getResource("bcdtest6502.bin").readBytes() // only works on 6502, not on the 65c02
|
||||
ram.load(bytes, 0x1000)
|
||||
bus.add(ram)
|
||||
bus.reset()
|
||||
|
|
|
@ -29,7 +29,7 @@ class TestDisassembler {
|
|||
fun testDisassembleRockwell65C02() {
|
||||
val cpu = Cpu65C02()
|
||||
val memory = Ram(0, 0x0fff)
|
||||
val source = javaClass.classLoader.getResource("disassem_r65c02.bin")!!
|
||||
val source = javaClass.classLoader.getResource("disassem_r65c02.bin").readBytes()
|
||||
memory.load(source, 0x0200)
|
||||
val resultLines = cpu.disassemble(memory, 0x0200, 0x0250)
|
||||
val result = resultLines.joinToString("\n")
|
||||
|
@ -74,7 +74,7 @@ ${'$'}0250 00 brk""", result)
|
|||
fun testDisassembleWDC65C02() {
|
||||
val cpu = Cpu65C02()
|
||||
val memory = Ram(0, 0x0fff)
|
||||
val source = javaClass.classLoader.getResource("disassem_wdc65c02.bin")!!
|
||||
val source = javaClass.classLoader.getResource("disassem_wdc65c02.bin").readBytes()
|
||||
memory.load(source, 0x200)
|
||||
val resultLines = cpu.disassemble(memory, 0x0200, 0x0215)
|
||||
val result = resultLines.joinToString("\n")
|
||||
|
|
Loading…
Reference in New Issue