1
0
mirror of https://github.com/irmen/ksim65.git synced 2024-05-29 03:41:30 +00:00
This commit is contained in:
Irmen de Jong 2019-09-14 18:58:45 +02:00
parent 66c4033eb4
commit 84831adb07
10 changed files with 104 additions and 1213 deletions

View File

@ -1,5 +1,6 @@
import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import kotlin.math.max
plugins {
// Apply the Kotlin JVM plugin to add support for Kotlin on the JVM.
@ -46,9 +47,9 @@ tasks.named<Test>("test") {
testLogging.events("failed")
// parallel tests.
systemProperties["junit.jupiter.execution.parallel.enabled"] = true
systemProperties["junit.jupiter.execution.parallel.mode.default"] = "concurrent"
maxParallelForks = Runtime.getRuntime().availableProcessors() / 2
systemProperty("junit.jupiter.execution.parallel.enabled", "true")
systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent")
maxParallelForks = max(1, Runtime.getRuntime().availableProcessors() / 2)
}
tasks.withType<KotlinCompile>().all {

View File

@ -65,13 +65,14 @@ private fun startSimulator(args: Array<String>) {
cpu.Status.I = false // enable interrupts
try {
while (true) {
bus.clock()
}
} catch (ix: Cpu6502.InstructionError) {
println("Hmmm... $ix")
}
// TODO
// try {
// while (true) {
// bus.clock()
// }
// } catch (ix: Cpu6502.InstructionError) {
// println("Hmmm... $ix")
// }
ram.hexDump(0x1000, 0x1020)
val dis = cpu.disassemble(ram, 0x1000, 0x1020)

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,5 @@
package razorvine.ksim65.components
import razorvine.ksim65.c64.Petscii
typealias UByte = Short
typealias Address = Int
@ -22,7 +20,7 @@ abstract class MemMappedComponent(val startAddress: Address, val endAddress: Add
require(startAddress >= 0 && endAddress <= 0xffff) { "can only have 16-bit address space" }
}
fun hexDump(from: Address, to: Address) {
fun hexDump(from: Address, to: Address, charmapper: ((Short)->Char)? = null) {
(from..to).chunked(16).forEach {
print("\$${it.first().toString(16).padStart(4, '0')} ")
val bytes = it.map { address -> get(address) }
@ -30,8 +28,12 @@ abstract class MemMappedComponent(val startAddress: Address, val endAddress: Add
print(byte.toString(16).padStart(2, '0') + " ")
}
print(" ")
print(Petscii.decodeScreencode(bytes, false).replace('\ufffe', '.'))
println()
val chars =
if(charmapper!=null)
bytes.map { b -> charmapper(b) }
else
bytes.map { b -> if(b in 32..255) b.toChar() else '.' }
println(chars.joinToString(""))
}
}
}

View File

@ -7,7 +7,7 @@ package razorvine.ksim65.components
* TODO: add the optional additional cycles to certain instructions and addressing modes
*/
open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
var tracing: Boolean = false
var tracing: ((state:String) -> Unit)? = null
var totalCycles: Long = 0
protected set
@ -20,7 +20,7 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
const val resetCycles = 8
}
enum class AddrMode {
protected enum class AddrMode {
Imp,
Acc,
Imm,
@ -39,8 +39,6 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
IaX, // special addressing mode used by the 65C02
}
class Instruction(val mnemonic: String, val mode: AddrMode, val cycles: Int)
class StatusRegister(
var C: Boolean = false,
var Z: Boolean = false,
@ -85,8 +83,9 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
}
}
protected class Instruction(val mnemonic: String, val mode: AddrMode, val cycles: Int)
var instrCycles: Int = 0
var A: Int = 0
var X: Int = 0
var Y: Int = 0
@ -94,24 +93,32 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
var PC: Address = 0
val Status = StatusRegister()
var currentOpcode: Int = 0
protected set
var instrCycles: Int = 0
protected set
protected lateinit var currentInstruction: Instruction
// has an interrupt been requested?
protected var pendingInterrupt: Pair<Boolean, BusComponent>? = null
// data byte from the instruction (only set when addr.mode is Accumulator, Immediate or Implied)
private var fetchedData: Int = 0
protected var fetchedData: Int = 0
// all other addressing modes yield a fetched memory address
protected var fetchedAddress: Address = 0
private val breakpoints = mutableMapOf<Address, (cpu: Cpu6502, pc: Address) -> Unit>()
class BreakpointResult(val newPC: Address?, val newOpcode: Int?)
fun breakpoint(address: Address, action: (cpu: Cpu6502, pc: Address) -> Unit) {
private val breakpoints = mutableMapOf<Address, (cpu: Cpu6502, pc: Address) -> BreakpointResult>()
fun addBreakpoint(address: Address, action: (cpu: Cpu6502, pc: Address) -> BreakpointResult) {
breakpoints[address] = action
}
internal fun hexW(number: Address, allowSingleByte: Boolean = false): String {
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)
@ -120,9 +127,9 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
hexB(msb) + hexB(lsb)
}
internal fun hexB(number: Short): String = hexB(number.toInt())
fun hexB(number: Short): String = hexB(number.toInt())
internal fun hexB(number: Int): String {
fun hexB(number: Int): String {
val hexdigits = "0123456789abcdef"
val loNibble = number and 15
val hiNibble = number ushr 4
@ -268,16 +275,19 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
currentOpcode = read(PC)
currentInstruction = instructions[currentOpcode]
if (tracing) printState()
tracing?.invoke(logState())
breakpoints[PC]?.let {
breakpoints[PC]?.let { breakpoint ->
val oldPC = PC
val oldOpcode = currentOpcode
it(this, PC)
val result = breakpoint(this, PC)
if(result.newPC!=null)
PC = result.newPC
if (PC != oldPC)
return clock()
if (oldOpcode != currentOpcode)
else if(result.newOpcode!=null) {
currentOpcode = result.newOpcode
currentInstruction = instructions[currentOpcode]
}
}
if (stopOnBrk && currentOpcode == 0) {
@ -311,9 +321,8 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
pendingInterrupt = Pair(false, source)
}
fun printState() {
println(
"cycle:$totalCycles - pc=${hexW(PC)} " +
fun logState(): String =
"cycle:$totalCycles - pc=${hexW(PC)} " +
"A=${hexB(A)} " +
"X=${hexB(X)} " +
"Y=${hexB(Y)} " +
@ -326,8 +335,6 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
" z=" + (if (Status.Z) "1" else "0") +
" c=" + (if (Status.C) "1" else "0") +
" icycles=$instrCycles instr=${hexB(currentOpcode)}:${currentInstruction.mnemonic}"
)
}
protected fun getFetched() =
if (currentInstruction.mode == AddrMode.Imm ||
@ -361,7 +368,7 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
return read(SP or 0x0100)
}
private fun popStackAddr(): Address {
protected fun popStackAddr(): Address {
val lo = popStack()
val hi = popStack()
return lo or (hi shl 8)

View File

@ -1,14 +1,12 @@
package razorvine.ksim65.components
import razorvine.ksim65.c64.Petscii
/**
* A parallel output device (basically, prints bytes as characters to the screen)
* First address = data byte (8 parallel bits)
* Second address = control byte (bit 0 high = write byte)
*/
class ParallelPort(startAddress: Address, endAddress: Address) : MemMappedComponent(startAddress, endAddress) {
var dataByte: UByte = 0
private var dataByte: UByte = 0
init {
require(endAddress - startAddress + 1 == 2) { "parallel needs exactly 2 memory bytes (data + control)" }
@ -29,7 +27,7 @@ class ParallelPort(startAddress: Address, endAddress: Address) : MemMappedCompon
dataByte = data
else if (address == endAddress) {
if ((data.toInt() and 1) == 1) {
val char = Petscii.decodeScreencode(listOf(dataByte), false).first()
val char = dataByte.toChar()
println("PARALLEL WRITE: '$char'")
}
}

View File

@ -1,4 +1,3 @@
import razorvine.ksim65.c64.Petscii
import razorvine.ksim65.components.Address
import razorvine.ksim65.components.Cpu6502
import razorvine.ksim65.components.Ram
@ -6,17 +5,17 @@ import razorvine.ksim65.components.Ram
class C64KernalStubs(private val ram: Ram) {
fun handleBreakpoint(cpu: Cpu6502, pc: Address) {
fun handleBreakpoint(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResult {
when(pc) {
0xffd2 -> {
// CHROUT
ram[0x030c] = 0
val char = Petscii.decodePetscii(listOf(cpu.A.toShort()), true).first()
val char = cpu.A.toChar()
if(char==13.toChar())
println()
else if(char in ' '..'~')
print(char)
cpu.currentOpcode = 0x60 // rts to end the stub
return Cpu6502.BreakpointResult(null, 0x60) // perform an RTS to exit this subroutine
}
0xffe4 -> {
// GETIN
@ -40,6 +39,8 @@ class C64KernalStubs(private val ram: Ram) {
// cpu.PC = 0x0816 // continue in next module
}
}
return Cpu6502.BreakpointResult(null, null)
}
}

View File

@ -20,10 +20,11 @@ class Test6502Functional {
bus.add(ram)
cpu.reset()
cpu.PC = 0x0400
cpu.breakpoint(0x3469) { _, _ ->
cpu.addBreakpoint(0x3469) { _, _ ->
// reaching this address means successful test result
if(cpu.currentOpcode==0x4c)
throw SuccessfulTestResult()
Cpu6502.BreakpointResult(null, null)
}
try {
@ -35,7 +36,7 @@ class Test6502Functional {
return
}
cpu.printState()
println(cpu.logState())
val d = cpu.disassemble(ram, cpu.PC-20, cpu.PC+20)
println(d.joinToString ("\n"))
fail("test failed")
@ -51,10 +52,11 @@ class Test6502Functional {
bus.add(ram)
cpu.reset()
cpu.PC = 0x0400
cpu.breakpoint(0x24f1) { _, _ ->
cpu.addBreakpoint(0x24f1) { _, _ ->
// reaching this address means successful test result
if(cpu.currentOpcode==0x4c)
throw SuccessfulTestResult()
Cpu6502.BreakpointResult(null, null)
}
try {
@ -66,7 +68,7 @@ class Test6502Functional {
return
}
cpu.printState()
println(cpu.logState())
val d = cpu.disassemble(ram, cpu.PC-20, cpu.PC+20)
println(d.joinToString ("\n"))
fail("test failed")

View File

@ -7,7 +7,7 @@ import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
@Disabled("this test suite takes a long time")
// @Disabled("this test suite takes a long time")
class Test6502TestSuite {
val cpu: Cpu6502 = Cpu6502(stopOnBrk = false)
@ -16,10 +16,10 @@ class Test6502TestSuite {
val kernalStubs = C64KernalStubs(ram)
init {
cpu.tracing = false
cpu.breakpoint(0xffd2) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) }
cpu.breakpoint(0xffe4) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) }
cpu.breakpoint(0xe16f) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) }
cpu.tracing = null
cpu.addBreakpoint(0xffd2) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) }
cpu.addBreakpoint(0xffe4) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) }
cpu.addBreakpoint(0xe16f) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) }
// create the system bus and add device to it.
// note that the order is relevant w.r.t. where reads and writes are going.
@ -54,7 +54,7 @@ class Test6502TestSuite {
while (cpu.totalCycles < 50000000L) {
bus.clock()
}
fail("test hangs")
fail("test hangs: " + cpu.logState())
} catch (e: Cpu6502.InstructionError) {
println(">>> INSTRUCTION ERROR: ${e.message}")
} catch (le: KernalLoadNextPart) {

View File

@ -30,44 +30,44 @@ class TestDisassembler {
val cpu = Cpu65C02()
val memory = Ram(0, 0x1000)
val source = javaClass.classLoader.getResource("disassem_r65c02.bin")!!
memory.load(source, 0)
val resultLines = cpu.disassemble(memory, 0x0000, 0x0050)
memory.load(source, 0x0200)
val resultLines = cpu.disassemble(memory, 0x0200, 0x0250)
val result = resultLines.joinToString("\n")
assertEquals("""${'$'}0000 07 12 rmb0 ${'$'}12
${'$'}0002 17 12 rmb1 ${'$'}12
${'$'}0004 27 12 rmb2 ${'$'}12
${'$'}0006 37 12 rmb3 ${'$'}12
${'$'}0008 47 12 rmb4 ${'$'}12
${'$'}000a 57 12 rmb5 ${'$'}12
${'$'}000c 67 12 rmb6 ${'$'}12
${'$'}000e 77 12 rmb7 ${'$'}12
${'$'}0010 87 12 smb0 ${'$'}12
${'$'}0012 97 12 smb1 ${'$'}12
${'$'}0014 a7 12 smb2 ${'$'}12
${'$'}0016 b7 12 smb3 ${'$'}12
${'$'}0018 c7 12 smb4 ${'$'}12
${'$'}001a d7 12 smb5 ${'$'}12
${'$'}001c e7 12 smb6 ${'$'}12
${'$'}001e f7 12 smb7 ${'$'}12
${'$'}0020 0f 12 2a bbr0 ${'$'}12, ${'$'}4d
${'$'}0023 1f 12 27 bbr1 ${'$'}12, ${'$'}4d
${'$'}0026 2f 12 24 bbr2 ${'$'}12, ${'$'}4d
${'$'}0029 3f 12 21 bbr3 ${'$'}12, ${'$'}4d
${'$'}002c 4f 12 1e bbr4 ${'$'}12, ${'$'}4d
${'$'}002f 5f 12 1b bbr5 ${'$'}12, ${'$'}4d
${'$'}0032 6f 12 18 bbr6 ${'$'}12, ${'$'}4d
${'$'}0035 8f 12 15 bbs0 ${'$'}12, ${'$'}4d
${'$'}0038 9f 12 12 bbs1 ${'$'}12, ${'$'}4d
${'$'}003b af 12 0f bbs2 ${'$'}12, ${'$'}4d
${'$'}003e bf 12 0c bbs3 ${'$'}12, ${'$'}4d
${'$'}0041 cf 12 09 bbs4 ${'$'}12, ${'$'}4d
${'$'}0044 df 12 06 bbs5 ${'$'}12, ${'$'}4d
${'$'}0047 ef 12 03 bbs6 ${'$'}12, ${'$'}4d
${'$'}004a ff 12 00 bbs7 ${'$'}12, ${'$'}4d
${'$'}004d 00 brk
${'$'}004e 00 brk
${'$'}004f 00 brk
${'$'}0050 00 brk""", result)
assertEquals("""${'$'}0200 07 12 rmb0 ${'$'}12
${'$'}0202 17 12 rmb1 ${'$'}12
${'$'}0204 27 12 rmb2 ${'$'}12
${'$'}0206 37 12 rmb3 ${'$'}12
${'$'}0208 47 12 rmb4 ${'$'}12
${'$'}020a 57 12 rmb5 ${'$'}12
${'$'}020c 67 12 rmb6 ${'$'}12
${'$'}020e 77 12 rmb7 ${'$'}12
${'$'}0210 87 12 smb0 ${'$'}12
${'$'}0212 97 12 smb1 ${'$'}12
${'$'}0214 a7 12 smb2 ${'$'}12
${'$'}0216 b7 12 smb3 ${'$'}12
${'$'}0218 c7 12 smb4 ${'$'}12
${'$'}021a d7 12 smb5 ${'$'}12
${'$'}021c e7 12 smb6 ${'$'}12
${'$'}021e f7 12 smb7 ${'$'}12
${'$'}0220 0f 12 2a bbr0 ${'$'}12, ${'$'}024d
${'$'}0223 1f 12 27 bbr1 ${'$'}12, ${'$'}024d
${'$'}0226 2f 12 24 bbr2 ${'$'}12, ${'$'}024d
${'$'}0229 3f 12 21 bbr3 ${'$'}12, ${'$'}024d
${'$'}022c 4f 12 1e bbr4 ${'$'}12, ${'$'}024d
${'$'}022f 5f 12 1b bbr5 ${'$'}12, ${'$'}024d
${'$'}0232 6f 12 18 bbr6 ${'$'}12, ${'$'}024d
${'$'}0235 8f 12 15 bbs0 ${'$'}12, ${'$'}024d
${'$'}0238 9f 12 12 bbs1 ${'$'}12, ${'$'}024d
${'$'}023b af 12 0f bbs2 ${'$'}12, ${'$'}024d
${'$'}023e bf 12 0c bbs3 ${'$'}12, ${'$'}024d
${'$'}0241 cf 12 09 bbs4 ${'$'}12, ${'$'}024d
${'$'}0244 df 12 06 bbs5 ${'$'}12, ${'$'}024d
${'$'}0247 ef 12 03 bbs6 ${'$'}12, ${'$'}024d
${'$'}024a ff 12 00 bbs7 ${'$'}12, ${'$'}024d
${'$'}024d 00 brk
${'$'}024e 00 brk
${'$'}024f 00 brk
${'$'}0250 00 brk""", result)
}
@Test