mirror of
https://github.com/irmen/ksim65.git
synced 2025-03-14 04:30:21 +00:00
hook into kernel LOAD including directory listing
This commit is contained in:
parent
9dc8721a43
commit
bc3a0f8daa
@ -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!!)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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!!)
|
||||
}
|
||||
|
@ -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 ||
|
||||
|
21
src/main/kotlin/razorvine/ksim65/Utils.kt
Normal file
21
src/main/kotlin/razorvine/ksim65/Utils.kt
Normal 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]
|
||||
}
|
@ -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()
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user