diff --git a/build.gradle.kts b/build.gradle.kts index a726d62..6bfeec7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,7 @@ -import org.gradle.api.internal.plugins.UnixStartScriptGenerator -import org.gradle.api.internal.plugins.WindowsStartScriptGenerator import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import java.util.* import kotlin.math.max -import java.util.Properties plugins { diff --git a/src/main/kotlin/razorvine/c64emu/c64Main.kt b/src/main/kotlin/razorvine/c64emu/c64Main.kt index eab4a0a..84a68ab 100644 --- a/src/main/kotlin/razorvine/c64emu/c64Main.kt +++ b/src/main/kotlin/razorvine/c64emu/c64Main.kt @@ -1,7 +1,6 @@ package razorvine.c64emu import razorvine.examplemachines.DebugWindow -import kotlin.concurrent.scheduleAtFixedRate import razorvine.ksim65.Bus import razorvine.ksim65.Cpu6502 import razorvine.ksim65.IVirtualMachine @@ -98,9 +97,16 @@ class C64Machine(title: String) : IVirtualMachine { debugWindow.updateCpu(cpu, bus) }.start() - java.util.Timer("cpu-clock", true).scheduleAtFixedRate(1, 1) { - if(!paused) { - repeat(400) { + // busy waiting loop, averaging cpu speed to ~1 Mhz: + var numInstructionsteps = 600 + val targetSpeedKhz = 1000 + while (true) { + if (paused) { + Thread.sleep(100) + } else { + cpu.startSpeedMeasureInterval() + Thread.sleep(0, 1000) + repeat(numInstructionsteps) { step() if(vic.currentRasterLine == 255) { // we force an irq here ourselves rather than fully emulating the VIC-II's raster IRQ @@ -108,6 +114,11 @@ class C64Machine(title: String) : IVirtualMachine { cpu.irq() } } + val speed = cpu.measureAvgIntervalSpeedKhz() + if (speed < targetSpeedKhz - 50) + numInstructionsteps++ + else if (speed > targetSpeedKhz + 50) + numInstructionsteps-- } } } diff --git a/src/main/kotlin/razorvine/examplemachines/GUI.kt b/src/main/kotlin/razorvine/examplemachines/GUI.kt index 0df9f90..c768491 100644 --- a/src/main/kotlin/razorvine/examplemachines/GUI.kt +++ b/src/main/kotlin/razorvine/examplemachines/GUI.kt @@ -172,7 +172,6 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("debugger"), ActionL it.disabledTextColor = Color.DARK_GRAY it.font = Font(Font.MONOSPACED, Font.PLAIN, 12) } - private val startTime = System.currentTimeMillis() init { contentPane.layout = GridBagLayout() @@ -300,17 +299,17 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("debugger"), ActionL } } - fun updateCpu(cpu2: Cpu6502, bus: Bus) { - val state = cpu2.snapshot() + fun updateCpu(cpu: Cpu6502, bus: Bus) { + val state = cpu.snapshot() cyclesTf.text = state.cycles.toString() - regAtf.text = cpu2.hexB(state.A) - regXtf.text = cpu2.hexB(state.X) - regYtf.text = cpu2.hexB(state.Y) + regAtf.text = cpu.hexB(state.A) + regXtf.text = cpu.hexB(state.X) + regYtf.text = cpu.hexB(state.Y) regPtf.text = "NV-BDIZC\n" + state.P.asInt().toString(2).padStart(8, '0') - regPCtf.text = cpu2.hexW(state.PC) - regSPtf.text = cpu2.hexB(state.SP) + regPCtf.text = cpu.hexW(state.PC) + regSPtf.text = cpu.hexB(state.SP) val memory = bus.memoryComponentFor(state.PC) - disassemTf.text = cpu2.disassembleOneInstruction(memory.data, state.PC, memory.startAddress).first.substringAfter(' ').trim() + disassemTf.text = cpu.disassembleOneInstruction(memory.data, state.PC, memory.startAddress).first.substringAfter(' ').trim() val pages = vm.getZeroAndStackPages() if(pages.isNotEmpty()) { val zpLines = (0..0xff step 32).map { location -> @@ -329,9 +328,7 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("debugger"), ActionL stackpageTf.text = stackLines.joinToString ("\n") } - val spentTime = System.currentTimeMillis() - startTime - val speedKhz = state.cycles.toDouble() / spentTime - speedKhzTf.text = "%.1f".format(speedKhz) + speedKhzTf.text = "%.1f".format(cpu.averageSpeedKhzSinceReset) } } diff --git a/src/main/kotlin/razorvine/examplemachines/ehBasicMain.kt b/src/main/kotlin/razorvine/examplemachines/ehBasicMain.kt index e4bde5d..7ef93ee 100644 --- a/src/main/kotlin/razorvine/examplemachines/ehBasicMain.kt +++ b/src/main/kotlin/razorvine/examplemachines/ehBasicMain.kt @@ -1,10 +1,12 @@ package razorvine.examplemachines -import kotlin.concurrent.scheduleAtFixedRate import razorvine.ksim65.Bus import razorvine.ksim65.Cpu6502 import razorvine.ksim65.Version -import razorvine.ksim65.components.* +import razorvine.ksim65.components.Display +import razorvine.ksim65.components.Keyboard +import razorvine.ksim65.components.Ram +import razorvine.ksim65.components.Rom import javax.swing.ImageIcon /** @@ -38,16 +40,29 @@ class EhBasicMachine(title: String) { hostDisplay.start() } + private fun step() { + // step a full single instruction + while (cpu.instrCycles > 0) bus.clock() + bus.clock() + while (cpu.instrCycles > 0) bus.clock() + } + fun start() { - val cpuTimer = java.util.Timer("cpu-clock", true) - cpuTimer.scheduleAtFixedRate(1, 1) { - if(!paused) { - repeat(500) { - // step a full single instruction - while (cpu.instrCycles > 0) bus.clock() - bus.clock() - while (cpu.instrCycles > 0) bus.clock() - } + // busy waiting loop, averaging cpu speed to ~1 Mhz: + var numInstructionsteps = 600 + val targetSpeedKhz = 1000 + while (true) { + if (paused) { + Thread.sleep(100) + } else { + cpu.startSpeedMeasureInterval() + Thread.sleep(0, 1000) + repeat(numInstructionsteps) { step() } + val speed = cpu.measureAvgIntervalSpeedKhz() + if (speed < targetSpeedKhz - 50) + numInstructionsteps++ + else if (speed > targetSpeedKhz + 50) + numInstructionsteps-- } } } diff --git a/src/main/kotlin/razorvine/examplemachines/machineMain.kt b/src/main/kotlin/razorvine/examplemachines/machineMain.kt index c556668..d68188c 100644 --- a/src/main/kotlin/razorvine/examplemachines/machineMain.kt +++ b/src/main/kotlin/razorvine/examplemachines/machineMain.kt @@ -1,12 +1,10 @@ package razorvine.examplemachines -import kotlin.concurrent.scheduleAtFixedRate import razorvine.ksim65.Bus import razorvine.ksim65.Cpu6502 import razorvine.ksim65.IVirtualMachine import razorvine.ksim65.Version import razorvine.ksim65.components.* -import razorvine.ksim65.components.Timer import java.io.File import javax.swing.ImageIcon @@ -22,9 +20,11 @@ class VirtualMachine(title: String) : IVirtualMachine { private val debugWindow = DebugWindow(this) private val hostDisplay = MainWindow(title) - private val display = Display(0xd000, 0xd00a, hostDisplay, + private val display = Display( + 0xd000, 0xd00a, hostDisplay, ScreenDefs.SCREEN_WIDTH_CHARS, ScreenDefs.SCREEN_HEIGHT_CHARS, - ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT) + ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT + ) private val mouse = Mouse(0xd300, 0xd305, hostDisplay) private val keyboard = Keyboard(0xd400, 0xd400, hostDisplay) private var paused = false @@ -32,7 +32,7 @@ class VirtualMachine(title: String) : IVirtualMachine { init { hostDisplay.iconImage = ImageIcon(javaClass.getResource("/icon.png")).image debugWindow.iconImage = hostDisplay.iconImage - debugWindow.setLocation(hostDisplay.location.x+hostDisplay.width, hostDisplay.location.y) + debugWindow.setLocation(hostDisplay.location.x + hostDisplay.width, hostDisplay.location.y) ram[Cpu6502.RESET_vector] = 0x00 ram[Cpu6502.RESET_vector + 1] = 0x10 @@ -55,7 +55,7 @@ class VirtualMachine(title: String) : IVirtualMachine { override fun getZeroAndStackPages(): Array = ram.getPages(0, 2) override fun loadFileInRam(file: File, loadAddress: Address?) { - if(file.extension=="prg" && loadAddress==null) + if (file.extension == "prg" && loadAddress == null) ram.loadPrg(file.inputStream()) else ram.load(file.readBytes(), loadAddress!!) @@ -76,11 +76,22 @@ class VirtualMachine(title: String) : IVirtualMachine { javax.swing.Timer(10) { debugWindow.updateCpu(cpu, bus) }.start() - java.util.Timer("cpu-clock", true).scheduleAtFixedRate(1, 1) { - if(!paused) { - repeat(50) { - step() - } + + // busy waiting loop, averaging cpu speed to ~1 Mhz: + var numInstructionsteps = 600 + val targetSpeedKhz = 1000 + while (true) { + if (paused) { + Thread.sleep(100) + } else { + cpu.startSpeedMeasureInterval() + Thread.sleep(0, 1000) + repeat(numInstructionsteps) { step() } + val speed = cpu.measureAvgIntervalSpeedKhz() + if (speed < targetSpeedKhz - 50) + numInstructionsteps++ + else if (speed > targetSpeedKhz + 50) + numInstructionsteps-- } } } diff --git a/src/main/kotlin/razorvine/ksim65/Cpu6502.kt b/src/main/kotlin/razorvine/ksim65/Cpu6502.kt index 032422d..6c507bc 100644 --- a/src/main/kotlin/razorvine/ksim65/Cpu6502.kt +++ b/src/main/kotlin/razorvine/ksim65/Cpu6502.kt @@ -13,8 +13,11 @@ import razorvine.ksim65.components.UByte open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() { open val name = "6502" var tracing: ((state:String) -> Unit)? = null - var totalCycles: Long = 0 + var totalCycles = 0L protected set + private var speedMeasureCycles = 0L + private var speedMeasureStart = System.nanoTime() + private var resetTime = System.nanoTime() class InstructionError(msg: String) : RuntimeException(msg) @@ -25,25 +28,6 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() { const val resetCycles = 8 } - protected enum class AddrMode { - Imp, - Acc, - Imm, - Zp, - Zpr, // special addressing mode used by the 65C02 - ZpX, - ZpY, - Rel, - Abs, - AbsX, - AbsY, - Ind, - IzX, - IzY, - Izp, // special addressing mode used by the 65C02 - IaX, // special addressing mode used by the 65C02 - } - class StatusRegister( var C: Boolean = false, var Z: Boolean = false, @@ -88,8 +72,6 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() { } } - protected class Instruction(val mnemonic: String, val mode: AddrMode, val cycles: Int) - class BreakpointResult(val newPC: Address?, val newOpcode: Int?) class State ( @@ -102,6 +84,27 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() { val cycles: Long ) + protected enum class AddrMode { + Imp, + Acc, + Imm, + Zp, + Zpr, // special addressing mode used by the 65C02 + ZpX, + ZpY, + Rel, + Abs, + AbsX, + AbsY, + Ind, + IzX, + IzY, + Izp, // special addressing mode used by the 65C02 + IaX, // special addressing mode used by the 65C02 + } + + protected class Instruction(val mnemonic: String, val mode: AddrMode, val cycles: Int) + var regA: Int = 0 var regX: Int = 0 var regY: Int = 0 @@ -117,6 +120,9 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() { val currentMnemonic: String get() = currentInstruction.mnemonic + val averageSpeedKhzSinceReset: Double + get() = totalCycles.toDouble() / (System.nanoTime() - resetTime) * 1_000_000 + @Synchronized fun snapshot(): State { val status = StatusRegister().also { it.fromInt(regP.asInt()) } return State(regA.toShort(), @@ -128,6 +134,14 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() { totalCycles) } + fun startSpeedMeasureInterval() { + speedMeasureCycles = totalCycles + speedMeasureStart = System.nanoTime() + } + + fun measureAvgIntervalSpeedKhz() = + (totalCycles-speedMeasureCycles).toDouble() / (System.nanoTime() - speedMeasureStart) * 1_000_000 + // has an interrupt been requested? protected enum class Interrupt { IRQ, @@ -285,21 +299,23 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() { * Reset the cpu */ override fun reset() { + regP.I = true + regP.C = false + regP.Z = false + regP.D = false + regP.B = false + regP.V = false + regP.N = false regSP = 0xfd regPC = readWord(RESET_vector) regA = 0 regX = 0 regY = 0 - regP.C = false - regP.Z = false - regP.I = true - regP.D = false - regP.B = false - regP.V = false - regP.N = false instrCycles = resetCycles // a reset takes time as well currentOpcode = 0 currentInstruction = instructions[0] + totalCycles = 0 + resetTime = System.nanoTime() } /**