1
0
mirror of https://github.com/irmen/ksim65.git synced 2024-06-06 22:29:33 +00:00

hook into kernel LOAD including directory listing

This commit is contained in:
Irmen de Jong 2019-09-29 01:26:31 +02:00
parent 9dc8721a43
commit bc3a0f8daa
9 changed files with 173 additions and 63 deletions

View File

@ -1,13 +1,13 @@
package razorvine.c64emu
import razorvine.examplemachines.DebugWindow
import razorvine.ksim65.Bus
import razorvine.ksim65.Cpu6502
import razorvine.ksim65.IVirtualMachine
import razorvine.ksim65.Version
import razorvine.ksim65.*
import razorvine.ksim65.components.*
import java.io.File
import java.io.FileFilter
import java.io.FileNotFoundException
import java.io.IOException
import java.io.Serializable
import java.nio.file.Path
import java.nio.file.Paths
import javax.swing.ImageIcon
@ -34,10 +34,113 @@ class C64Machine(title: String) : IVirtualMachine {
private val hostDisplay = MainC64Window(title, chargenData, ram, cpu, cia1)
private var paused = false
fun breakpointKernelLoad(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResult {
if(cpu.regA==0) {
val fnlen = ram[0xb7] // file name length
val fa = ram[0xba] // device number
val sa = ram[0xb9] // secondary address
val txttab = ram[0x2b] + 256*ram[0x2c] // basic load address ($0801 usually)
val fnaddr = ram[0xbb] + 256*ram[0xbc] // file name address
return if(fnlen>0) {
val filename = (0 until fnlen).map { ram[fnaddr+it].toChar() }.joinToString("")
val loadEndAddress = searchAndLoadFile(filename, fa, sa, txttab)
if(loadEndAddress!=null) {
ram[0x90] = 0 // status OK
ram[0xae] = (loadEndAddress and 0xff).toShort()
ram[0xaf] = (loadEndAddress ushr 8).toShort()
Cpu6502.BreakpointResult(0xf5a9, 0) // success!
} else Cpu6502.BreakpointResult(0xf704, null) // 'file not found'
} else Cpu6502.BreakpointResult(0xf710, null) // 'missing file name'
} else return Cpu6502.BreakpointResult(0xf707, null) // 'device not present' (VERIFY command not supported)
}
// TODO: breakpoint on kernel SAVE
private fun searchAndLoadFile(filename: String, device: UByte, secondary: UByte, basicLoadAddress: Address): Address? {
when (filename) {
"*" -> {
// load the first file in the directory
return searchAndLoadFile(File(".").listFiles()?.firstOrNull()?.name ?: "", device, secondary, basicLoadAddress)
}
"$" -> {
// load the directory
val files = File(".")
.listFiles(FileFilter { it.isFile })!!
.associate {
val name = it.nameWithoutExtension.toUpperCase()
val ext = it.extension.toUpperCase()
val fileAndSize = Pair(it, it.length())
if(name.isEmpty())
Pair("." + ext, "") to fileAndSize
else
Pair(name, ext) to fileAndSize
}
val dirname = File(".").canonicalPath.substringAfterLast(File.separator).toUpperCase()
val dirlisting = makeDirListing(dirname, files, basicLoadAddress)
ram.load(dirlisting, basicLoadAddress)
return basicLoadAddress + dirlisting.size - 1
}
else -> {
fun findHostFile(filename: String): String? {
val file = File(".").listFiles(FileFilter { it.isFile })?.firstOrNull {
it.name.toUpperCase()==filename
}
return file?.name
}
val hostFileName = findHostFile(filename) ?: findHostFile("$filename.PRG") ?: return null
return try {
return if (secondary == 1.toShort()) {
val (loadAddress, size) = ram.loadPrg(hostFileName, null)
loadAddress + size - 1
} else {
val (loadAddress, size) = ram.loadPrg(hostFileName, basicLoadAddress)
loadAddress + size - 1
}
} catch (iox: IOException) {
println("LOAD ERROR $iox")
null
}
}
}
}
private fun makeDirListing(
dirname: String,
files: Map<Pair<String, String>, Pair<File, Long>>,
basicLoadAddress: Address
): Array<UByte> {
var address = basicLoadAddress
val listing = mutableListOf<UByte>()
fun addLine(lineNumber: Int, line: String) {
address += line.length + 3
listing.add((address and 0xff).toShort())
listing.add((address ushr 8).toShort())
listing.add((lineNumber and 0xff).toShort())
listing.add((lineNumber ushr 8).toShort())
listing.addAll(line.map{ it.toShort() })
listing.add(0)
}
addLine(0, "\u0012\"${dirname.take(16).padEnd(16)}\" 00 2A")
var totalBlocks = 0
files.forEach {
val blocksize = (it.value.second / 256).toInt()
totalBlocks += blocksize
val filename = it.key.first.take(16)
val padding1 = " ".substring(blocksize.toString().length)
val padding2 = " ".substring(filename.length)
addLine(blocksize, "$padding1 \"$filename\" $padding2 ${it.key.second.take(3).padEnd(3)}" )
}
addLine(kotlin.math.max(0, 664-totalBlocks), "BLOCKS FREE.")
listing.add(0)
listing.add(0)
return listing.toTypedArray()
}
init {
hostDisplay.iconImage = ImageIcon(javaClass.getResource("/icon.png")).image
debugWindow.iconImage = hostDisplay.iconImage
debugWindow.setLocation(hostDisplay.location.x+hostDisplay.width, hostDisplay.location.y)
cpu.addBreakpoint(0xffd5, ::breakpointKernelLoad)
bus += basicRom
bus += kernalRom
@ -73,8 +176,8 @@ class C64Machine(title: String) : IVirtualMachine {
}
override fun loadFileInRam(file: File, loadAddress: Address?) {
if(file.extension=="prg" && loadAddress==null)
ram.loadPrg(file.inputStream())
if(file.extension=="prg" && (loadAddress==null || loadAddress==0x0801))
ram.loadPrg(file.inputStream(), null)
else
ram.load(file.readBytes(), loadAddress!!)
}

View File

@ -1,13 +1,10 @@
package razorvine.examplemachines
import razorvine.ksim65.Bus
import razorvine.ksim65.Cpu6502
import razorvine.ksim65.*
import java.awt.*
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import javax.swing.event.MouseInputListener
import razorvine.ksim65.IHostInterface
import razorvine.ksim65.IVirtualMachine
import java.awt.event.*
import java.io.File
import java.lang.Integer.parseInt
@ -302,12 +299,12 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("debugger"), ActionL
fun updateCpu(cpu: Cpu6502, bus: Bus) {
val state = cpu.snapshot()
cyclesTf.text = state.cycles.toString()
regAtf.text = cpu.hexB(state.A)
regXtf.text = cpu.hexB(state.X)
regYtf.text = cpu.hexB(state.Y)
regAtf.text = hexB(state.A)
regXtf.text = hexB(state.X)
regYtf.text = hexB(state.Y)
regPtf.text = "NV-BDIZC\n" + state.P.asInt().toString(2).padStart(8, '0')
regPCtf.text = cpu.hexW(state.PC)
regSPtf.text = cpu.hexB(state.SP)
regPCtf.text = hexW(state.PC)
regSPtf.text = hexB(state.SP)
val memory = bus.memoryComponentFor(state.PC)
disassemTf.text = cpu.disassembleOneInstruction(memory.data, state.PC, memory.startAddress).first.substringAfter(' ').trim()
val pages = vm.getZeroAndStackPages()

View File

@ -36,7 +36,7 @@ class VirtualMachine(title: String) : IVirtualMachine {
ram[Cpu6502.RESET_vector] = 0x00
ram[Cpu6502.RESET_vector + 1] = 0x10
ram.loadPrg(javaClass.getResourceAsStream("/vmdemo.prg"))
ram.loadPrg(javaClass.getResourceAsStream("/vmdemo.prg"), null)
bus += rtc
bus += timer
@ -56,7 +56,7 @@ class VirtualMachine(title: String) : IVirtualMachine {
override fun loadFileInRam(file: File, loadAddress: Address?) {
if (file.extension == "prg" && loadAddress == null)
ram.loadPrg(file.inputStream())
ram.loadPrg(file.inputStream(), null)
else
ram.load(file.readBytes(), loadAddress!!)
}

View File

@ -5,6 +5,7 @@ import razorvine.ksim65.components.BusComponent
import razorvine.ksim65.components.MemoryComponent
import razorvine.ksim65.components.UByte
/**
* 6502 cpu simulation (the NMOS version) including the 'illegal' opcodes.
* TODO: actually implement the illegal opcodes, see http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes
@ -82,7 +83,22 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
val P: StatusRegister,
val PC: Address,
val cycles: Long
)
) {
override fun toString(): String {
return "cycle:$cycles - pc=${hexW(PC)} " +
"A=${hexB(A)} " +
"X=${hexB(X)} " +
"Y=${hexB(Y)} " +
"SP=${hexB(SP)} " +
" n=" + (if (P.N) "1" else "0") +
" v=" + (if (P.V) "1" else "0") +
" b=" + (if (P.B) "1" else "0") +
" d=" + (if (P.D) "1" else "0") +
" i=" + (if (P.I) "1" else "0") +
" z=" + (if (P.Z) "1" else "0") +
" c=" + (if (P.C) "1" else "0")
}
}
protected enum class AddrMode {
Imp,
@ -163,24 +179,6 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
fun removeBreakpoint(address: Address) = breakpoints.remove(address)
fun hexW(number: Address, allowSingleByte: Boolean = false): String {
val msb = number ushr 8
val lsb = number and 0xff
return if (msb == 0 && allowSingleByte)
hexB(lsb)
else
hexB(msb) + hexB(lsb)
}
fun hexB(number: Short): String = hexB(number.toInt())
fun hexB(number: Int): String {
val hexdigits = "0123456789abcdef"
val loNibble = number and 15
val hiNibble = number ushr 4
return hexdigits[hiNibble].toString() + hexdigits[loNibble]
}
fun disassemble(memory: MemoryComponent, from: Address, to: Address) =
disassemble(memory.data, memory.startAddress, from, to)
@ -333,7 +331,7 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
currentOpcode = read(regPC)
currentInstruction = instructions[currentOpcode]
tracing?.invoke(logState())
tracing?.invoke(snapshot().toString())
breakpoints[regPC]?.let { breakpoint ->
val oldPC = regPC
@ -381,21 +379,6 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
pendingInterrupt = Interrupt.IRQ
}
fun logState(): String =
"cycle:$totalCycles - pc=${hexW(regPC)} " +
"A=${hexB(regA)} " +
"X=${hexB(regX)} " +
"Y=${hexB(regY)} " +
"SP=${hexB(regSP)} " +
" n=" + (if (regP.N) "1" else "0") +
" v=" + (if (regP.V) "1" else "0") +
" b=" + (if (regP.B) "1" else "0") +
" d=" + (if (regP.D) "1" else "0") +
" i=" + (if (regP.I) "1" else "0") +
" z=" + (if (regP.Z) "1" else "0") +
" c=" + (if (regP.C) "1" else "0") +
" icycles=$instrCycles instr=${hexB(currentOpcode)}:${currentInstruction.mnemonic}"
protected fun getFetched() =
if (currentInstruction.mode == AddrMode.Imm ||
currentInstruction.mode == AddrMode.Acc ||

View File

@ -0,0 +1,21 @@
package razorvine.ksim65
import razorvine.ksim65.components.Address
fun hexW(number: Address, allowSingleByte: Boolean = false): String {
val msb = number ushr 8
val lsb = number and 0xff
return if (msb == 0 && allowSingleByte)
hexB(lsb)
else
hexB(msb) + hexB(lsb)
}
fun hexB(number: Short): String = hexB(number.toInt())
fun hexB(number: Int): String {
val hexdigits = "0123456789abcdef"
val loNibble = number and 15
val hiNibble = number ushr 4
return hexdigits[hiNibble].toString() + hexdigits[loNibble]
}

View File

@ -1,6 +1,7 @@
package razorvine.ksim65.components
import java.io.File
import java.io.IOException
import java.io.InputStream
/**
@ -26,14 +27,17 @@ class Ram(startAddress: Address, endAddress: Address) : MemoryComponent(startAdd
/**
* Load a c64-style prg program. This file type has the load address as the first two bytes.
*/
fun loadPrg(filename: String) = loadPrg(File(filename).inputStream())
fun loadPrg(filename: String, overrideLoadAddress: Address?)
= loadPrg(File(filename).inputStream(), overrideLoadAddress)
/**
* Load a c64-style prg program. This file type has the load address as the first two bytes.
*/
fun loadPrg(stream: InputStream) {
fun loadPrg(stream: InputStream, overrideLoadAddress: Address?): Pair<Address, Int> {
val bytes = stream.readBytes()
val loadAddress = (bytes[0].toInt() or (bytes[1].toInt() shl 8)) and 65535
if(bytes.size > 0xffff)
throw IOException("file too large")
val loadAddress = overrideLoadAddress ?: bytes[0] + 256* bytes[1]
val baseAddress = loadAddress - startAddress
bytes.drop(2).forEachIndexed { index, byte ->
data[baseAddress + index] =
@ -42,10 +46,11 @@ class Ram(startAddress: Address, endAddress: Address) : MemoryComponent(startAdd
else
(256 + byte).toShort()
}
return Pair(baseAddress, bytes.size-2)
}
/**
* load a binary program at the given address
* load a binary data file at the given address
*/
fun load(filename: String, address: Address) {
val bytes = File(filename).readBytes()

View File

@ -42,7 +42,7 @@ abstract class FunctionalTestsBase {
}
protected fun runTest(testprogram: String) {
ram.loadPrg("src/test/kotlin/6502testsuite/$testprogram")
ram.loadPrg("src/test/kotlin/6502testsuite/$testprogram", null)
bus.reset()
cpu.regSP = 0xfd
cpu.regP.fromInt(0b00100100)
@ -50,7 +50,7 @@ abstract class FunctionalTestsBase {
while (cpu.totalCycles < 50000000L) {
bus.clock()
}
fail("test hangs: " + cpu.logState())
fail("test hangs: " + cpu.snapshot())
} catch (e: Cpu6502.InstructionError) {
println(">>> INSTRUCTION ERROR: ${e.message}")
} catch (le: KernalLoadNextPart) {

View File

@ -2,6 +2,7 @@ import razorvine.ksim65.Bus
import razorvine.ksim65.Cpu6502
import razorvine.ksim65.Cpu65C02
import razorvine.ksim65.components.Ram
import razorvine.ksim65.hexB
import kotlin.test.*
import kotlin.system.measureNanoTime
import kotlin.test.assertEquals
@ -140,9 +141,9 @@ class Test6502CpuBasics {
val actualA = ram[0x00fd]
val predictedF = ram[0x00fe]
val actualF = ram[0x00ff]
println("BCD TEST: FAIL!! code=${cpu.hexB(code)} value1=${cpu.hexB(v1)} value2=${cpu.hexB(v2)}")
println(" predictedA=${cpu.hexB(predictedA)}")
println(" actualA=${cpu.hexB(actualA)}")
println("BCD TEST: FAIL!! code=${hexB(code)} value1=${hexB(v1)} value2=${hexB(v2)}")
println(" predictedA=${hexB(predictedA)}")
println(" actualA=${hexB(actualA)}")
println(" predictedF=${predictedF.toString(2).padStart(8,'0')}")
println(" actualF=${actualF.toString(2).padStart(8,'0')}")
fail("BCD test failed")

View File

@ -36,7 +36,7 @@ class Test6502Functional {
return
}
println(cpu.logState())
println(cpu.snapshot())
val d = cpu.disassemble(ram, cpu.regPC-20, cpu.regPC+20)
println(d.joinToString ("\n"))
fail("test failed")
@ -68,7 +68,7 @@ class Test6502Functional {
return
}
println(cpu.logState())
println(cpu.snapshot())
val d = cpu.disassemble(ram, cpu.regPC-20, cpu.regPC+20)
println(d.joinToString ("\n"))
fail("test failed")