fix disassembly issues, added ehBasic machine

This commit is contained in:
Irmen de Jong 2019-09-19 01:22:11 +02:00
parent ba8946c29c
commit 8aad9795f7
12 changed files with 167 additions and 56 deletions

View File

@ -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 ;-)

View File

@ -42,7 +42,7 @@ dependencies {
application {
applicationName = "ksim65vm"
mainClassName = "razorvine.examplemachine.SystemMainKt"
mainClassName = "razorvine.examplemachine.MachineMainKt"
}
tasks.named<Test>("test") {

View File

@ -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() {

View File

@ -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()
}

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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)
}
/**

View File

@ -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

View File

@ -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.

View File

@ -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()

View File

@ -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")