commit d716ba641ce1cfbf8e9564b9f62c0c064f827f6c Author: chrisly42 Date: Sun Apr 12 17:08:20 2026 +0200 Initial. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d381f3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Chris 'platon42' Hodges + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8bb5acc --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# m68kemu + +A very minimalistic Motorola 68000 user-mode emulation written in Kotlin, +with the actual cpu core emulation being less than 750 lines of code. + +It can be used to emulate short pieces of 68000 binary code. + +I used it for validation of the [Raspberry Casket Pretracker replayer](https://git.platon42.de/chrisly42/PretrackerRaspberryCasket). + +I had it lying around for so many years, I thought I could just publish +it and maybe someone finds it useful. + +## Features + +- Memory access abstraction +- User-mode only (no exceptions, interrupts, supervisor-instructions etc.) +- Instructions not implemented (might not be decoded correctly!): + - abcd, sbcd, nbcd + - movep, cmpm, addx/subx/tas on memory + - ori/andi/eori to CCR/SR, move from SR, move to CCR/SR, move USP + - illegal, trap, trapv, chk, stop, reset, rte, rtr + +Note: Illegal (unavailable) addressing modes may not be detected as such. +No 68020+ instructions or addressing modes are supported. + +## Usage + +Create an instance of M68kCpu(): +```kotlin +val cpu = M68kCpu() +``` + +Create some memory, custom chips, etc: +```kotlin +val ram = Ram(0x0, 0x10000) +val rom = Rom(0xfc0000, kickromimage) +val customChips = CustomChips() + +ram.writeWord(0x100, 0x4e75) // just an rts +``` + +Configure your system, registers, program counter and run `decode()` for every instruction as long as you like: +```kotlin +with(cpu) { + memRegions.add(ram) + memRegions.add(rom) + memRegions.add(customChips) + + a[7] = ram.start + ram.size // setup stack register + pushStack(OpSize.OS_LONG, 0) // write 0 return address + pc = 0x100 // start of program + + while (pc != 0) { + decode() + } +} +``` + +The CustomChip classes are minimal stubs. There's also some Paula-Recorder class you might find useful. diff --git a/src/main/kotlin/de/platon42/mc68kemu/CiaChip.kt b/src/main/kotlin/de/platon42/mc68kemu/CiaChip.kt new file mode 100644 index 0000000..b6164fd --- /dev/null +++ b/src/main/kotlin/de/platon42/mc68kemu/CiaChip.kt @@ -0,0 +1,21 @@ +package de.platon42.mc68kemu + +class CiaChip(ciaA: Boolean = true, val debug: Boolean = false) : MemoryRegion(if (ciaA) 0xbfe001 else 0xbfd000, 0x1000) { + override fun readByte(address: Int): Int { + val v = 0 + if (debug) println("${v.toHex(4)} = R[${address.toHex(2)}]") + return v + } + + override fun readWord(address: Int) = throw UnsupportedOperationException("Attempt to read word from cia ${address.toHex(8)}") + + override fun readLong(address: Int) = throw UnsupportedOperationException("Attempt to read long from cia ${address.toHex(8)}") + + override fun writeByte(address: Int, value: Int) { + if (debug) println("W[${address.toHex(8)}] = ${value.toHex(2)}") + } + + override fun writeWord(address: Int, value: Int) = throw UnsupportedOperationException("Attempt to write word to cia ${address.toHex(8)}") + + override fun writeLong(address: Int, value: Int) = throw UnsupportedOperationException("Attempt to write long to cia ${address.toHex(8)}") +} \ No newline at end of file diff --git a/src/main/kotlin/de/platon42/mc68kemu/CustomChipPaulaRecorder.kt b/src/main/kotlin/de/platon42/mc68kemu/CustomChipPaulaRecorder.kt new file mode 100644 index 0000000..e669163 --- /dev/null +++ b/src/main/kotlin/de/platon42/mc68kemu/CustomChipPaulaRecorder.kt @@ -0,0 +1,144 @@ +package de.platon42.mc68kemu + +import de.platon42.demoscene.tools.comprestor.PaulaChannel +import de.platon42.demoscene.tools.comprestor.PaulaMixer + +class CustomChipPaulaRecorder(private val sampleMem: MemoryRegion, private val outputFrequency: Float = 44100f, debug: Boolean = false) : CustomChips(debug) { + val channelInfoFrames = ArrayList>() + val paulaChannels = Array(4) { PaulaChannel(sampleMem, outputFrequency = outputFrequency) } + val mixer = PaulaMixer(sampleMem, outputFrequency, paulaChannels) + + fun startOfFrame() { + val newChans = + if (channelInfoFrames.isEmpty()) Array(4) { PaulaChannelInfo() } + else channelInfoFrames.last().map { it.copy(stopDma = false) }.toTypedArray() + if (debug) println("${channelInfoFrames.size}: StartOfFrame") + channelInfoFrames.add(newChans) + } + + fun recordPaulaFrameMono(amigaFrameTime: Float): List { + return mixer.monoMix(amigaFrameTime) + } + + fun recordPaulaFrameStereo(amigaFrameTime: Float): Pair, List> { + return mixer.stereoMix(amigaFrameTime) + } + + private fun getAudioParams(address: Int): Triple { + val chanNum = (address - 0xdff0a0) / 0x10 + val reg = address and 0x0f + val chan = channelInfoFrames.last()[chanNum] + val paula = paulaChannels[chanNum] + return Triple(reg, chan, paula) + } + + override fun writeByte(address: Int, value: Int) { + if (address in 0xdff0a0..0xdff0df) { + if (channelInfoFrames.isEmpty()) return + val (reg, chan, paula) = getAudioParams(address) + when (reg) { + 0x9 -> { + if (debug) println("${channelInfoFrames.lastIndex}.${(address - 0xdff0a0) / 0x10}: Volume = $value") + if (value > 64) { + println("${channelInfoFrames.lastIndex}: WARNING Volume $value > 64") + } + + chan.volume = value + paula.volume = value + } + + else -> throw UnsupportedOperationException("Byte write to audio $address unsupported") + } + } else { + super.writeByte(address, value) + } + } + + override fun writeWord(address: Int, value: Int) { + if (address in 0xdff0a0..0xdff0df) { + if (channelInfoFrames.isEmpty()) return + val (reg, chan, paula) = getAudioParams(address) + when (reg) { + 0x4 -> { + if (debug) println("${channelInfoFrames.lastIndex}.${(address - 0xdff0a0) / 0x10}: Length = ${value * 2}") + if (value == 0) { + println("${channelInfoFrames.lastIndex}.${(address - 0xdff0a0) / 0x10}: WARNING Length $value == 0") + } + chan.loopLengthWords = value + if (!chan.startDma) chan.lengthWords = value + paula.setLength(value * 2) + } + + 0x6 -> { + if (debug) println("${channelInfoFrames.lastIndex}.${(address - 0xdff0a0) / 0x10}: Period = $value") + chan.period = value + paula.period = value + } + + 0x8 -> { + if (debug) println("${channelInfoFrames.lastIndex}.${(address - 0xdff0a0) / 0x10}: Volume = $value") + if (value > 64) { + println("${channelInfoFrames.lastIndex}.${(address - 0xdff0a0) / 0x10}: WARNING Volume $value > 64") + } + chan.volume = value + paula.volume = value + } + + else -> throw UnsupportedOperationException("Word write to audio $address unsupported") + } + } else { + // handle dmacon + if (address == 0xdff096) { + if (channelInfoFrames.isEmpty()) return + if (value and 0xf != 0) { + for (chanNum in 0..3) { + if ((value and (1 shl chanNum)) != 0) { + val chan = channelInfoFrames.last()[chanNum] + if (value and 0x8000 != 0) { + // start DMA + if (debug) println("${channelInfoFrames.lastIndex}.$chanNum: StartDMA(${paulaChannels[chanNum].dmaStartAddress}, ${paulaChannels[chanNum].dmaStartLength})") + if (!chan.startDma) { + chan.startDma = true + if (paulaChannels[chanNum].dmaStartAddress == 0) { + println("${channelInfoFrames.lastIndex}.$chanNum: WARNING Start Address 0") + } + if (paulaChannels[chanNum].dmaStartLength == 0) { + println("${channelInfoFrames.lastIndex}.$chanNum: WARNING Start Length 0") + } + paulaChannels[chanNum].startDma() + } + } else { + // stop DMA + if (debug) println("${channelInfoFrames.lastIndex}.$chanNum: StopDMA") + chan.stopDma = true + chan.startDma = false + paulaChannels[chanNum].stopDma() + } + } + } + } + } else { + super.writeWord(address, value) + } + } + } + + override fun writeLong(address: Int, value: Int) { + if (address in 0xdff0a0..0xdff0df) { + if (channelInfoFrames.isEmpty()) return + val (reg, chan, paula) = getAudioParams(address) + when (reg) { + 0x0 -> { + if (debug) println("${channelInfoFrames.lastIndex}.${(address - 0xdff0a0) / 0x10}: Start = ${value.toHex(8)}") + chan.loopAddress = value + if (!chan.startDma) chan.startAddress = value + paula.setStart(value) + } + + else -> throw UnsupportedOperationException("Long write to audio $address unsupported") + } + } else { + super.writeLong(address, value) + } + } +} diff --git a/src/main/kotlin/de/platon42/mc68kemu/CustomChips.kt b/src/main/kotlin/de/platon42/mc68kemu/CustomChips.kt new file mode 100644 index 0000000..80bc066 --- /dev/null +++ b/src/main/kotlin/de/platon42/mc68kemu/CustomChips.kt @@ -0,0 +1,41 @@ +package de.platon42.mc68kemu + +open class CustomChips(var debug: Boolean = false) : MemoryRegion(0xdff000, 0x200) { + var vhpos = 0 + override fun readByte(address: Int): Int { + val v = 0 + println("${v.toHex(4)} = R[${address.toHex(2)}]") + return v + } + + override fun readWord(address: Int): Int { + val v = when (address) { + 0xdff004 -> (vhpos shr 16) + 0xdff006 -> (vhpos and 0xffff).also { vhpos = (vhpos + 0x400) % (311 shl 8) } + else -> 0 + } + if (debug) println("${v.toHex(4)} = R[${address.toHex(8)}]") + return v + } + + override fun readLong(address: Int): Int { + val v = when (address) { + 0xdff004 -> vhpos.also { vhpos = (vhpos + 0x400) % (311 shl 8) } + else -> (readWord(address) shl 16) or (readWord(address + 2) and 0xffff) + } + if (debug) println("${v.toHex(8)} = R[${address.toHex(8)}]") + return v + } + + override fun writeByte(address: Int, value: Int) { + println("W[${address.toHex(8)}] = ${value.toHex(2)}") + } + + override fun writeWord(address: Int, value: Int) { + if (debug) println("W[${address.toHex(8)}] = ${value.toHex(4)}") + } + + override fun writeLong(address: Int, value: Int) { + if (debug) println("W[${address.toHex(8)}] = ${value.toHex(8)}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/platon42/mc68kemu/Extensions.kt b/src/main/kotlin/de/platon42/mc68kemu/Extensions.kt new file mode 100644 index 0000000..b61eb02 --- /dev/null +++ b/src/main/kotlin/de/platon42/mc68kemu/Extensions.kt @@ -0,0 +1,6 @@ +package de.platon42.mc68kemu + +fun Int.toHex(digits: Int): String { + val v = if (this < 0) (1 shl digits * 4) + this else this + return "$" + v.toUInt().toString(16).padStart(digits, '0') +} \ No newline at end of file diff --git a/src/main/kotlin/de/platon42/mc68kemu/M68kCpu.kt b/src/main/kotlin/de/platon42/mc68kemu/M68kCpu.kt new file mode 100644 index 0000000..24382cc --- /dev/null +++ b/src/main/kotlin/de/platon42/mc68kemu/M68kCpu.kt @@ -0,0 +1,753 @@ +package de.platon42.mc68kemu + +// Minimalistic MC68000 CPU emulation by Chris Hodges +// +// User-mode only (no exceptions, supervisor-instructions etc.). +// Instructions not implemented (might not be decoded correctly!): +// - abcd, sbcd, nbcd +// - movep, cmpm, addx/subx/tas on memory +// - ori/andi/eori to CCR/SR, move from SR, move to CCR/SR, move USP +// - illegal, trap, trapv, chk, stop, reset, rte, rtr +// Illegal (unavailable) addressing modes may not be detected as such. No 68020+ instructions or addressing modes! + +typealias InstExec = M68kCpu.(Int) -> Unit + +class M68kCpu { + companion object { + private const val CCB_X = 4 + private const val CCB_N = 3 + private const val CCB_Z = 2 + private const val CCB_V = 1 + private const val CCB_C = 0 + + private const val CCF_N = 1 shl CCB_N + private const val CCF_Z = 1 shl CCB_Z + private const val CCF_V = 1 shl CCB_V + private const val CCF_C = 1 shl CCB_C + + enum class OpSize { OS_BYTE, OS_WORD, OS_LONG } + + private val STD_OP_SIZE = arrayOf(OpSize.OS_BYTE, OpSize.OS_WORD, OpSize.OS_LONG, null) + private val MOVE_OP_SIZE = arrayOf(null, OpSize.OS_BYTE, OpSize.OS_LONG, OpSize.OS_WORD) + private val WL_OP_SIZE = arrayOf(OpSize.OS_WORD, OpSize.OS_LONG) + private val OP_SIZE_MSB = hashMapOf(OpSize.OS_BYTE to 7, OpSize.OS_WORD to 15, OpSize.OS_LONG to 31) + private val OP_SIZE_BYTES = hashMapOf(OpSize.OS_BYTE to 1, OpSize.OS_WORD to 2, OpSize.OS_LONG to 4) + + private fun Int.bits(startbit: Int, size: Int) = (this ushr ((startbit + 1) - size) and ((1 shl size) - 1)) + private fun Int.extractMSB(opSize: OpSize) = this.bits(OP_SIZE_MSB[opSize]!!, 1) + + private fun stdOpSize(instWord: Int) = STD_OP_SIZE[instWord.bits(7, 2)] ?: throw IllegalArgumentException("Illegal std op size $instWord") + private fun moveOpSize(instWord: Int) = MOVE_OP_SIZE[instWord.bits(13, 2)] ?: throw IllegalArgumentException("Illegal move op size $instWord") + + private fun trimToOpSizeUnsigned(opSize: OpSize, data: Int) = when (opSize) { + OpSize.OS_BYTE -> data and 0xff + OpSize.OS_WORD -> data and 0xffff + OpSize.OS_LONG -> data + } + + private fun trimToOpSizeSignExt(opSize: OpSize, data: Int) = when (opSize) { + OpSize.OS_BYTE -> data.toByte().toInt() + OpSize.OS_WORD -> data.toShort().toInt() + OpSize.OS_LONG -> data + } + + private val NOP_EXEC: InstExec = {} + + private val MOVE_EXEC: InstExec = { + val opSize = moveOpSize(it) + decWriteEa(it.bits(8, 3), it.bits(11, 3), opSize, decReadStdEa(it, opSize)) + } + + private val MOVEA_EXEC: InstExec = { + val opSize = moveOpSize(it) + writeAreg(it.bits(11, 3), opSize, decReadStdEa(it, opSize)) + } + + private val MOVEQ_EXEC: InstExec = { + writeDreg(it.bits(11, 3), OpSize.OS_LONG, it.bits(7, 8).toByte().toInt()) + } + + private val CLR_EXEC: InstExec = { + decWriteStdEa(it, stdOpSize(it), 0) + } + + private val TST_EXEC: InstExec = { + val opSize = stdOpSize(it) + updateCC(opSize, decReadStdEa(it, opSize)) + } + + private val LINK_EXEC: InstExec = { + val imm = readImm(OpSize.OS_WORD) + val reg = it.bits(2, 3) + pushStack(OpSize.OS_LONG, a[reg]) + writeAreg(reg, OpSize.OS_LONG, a[7]) + writeAreg(7, OpSize.OS_LONG, a[7] + imm) + } + + private val UNLK_EXEC: InstExec = { + val reg = it.bits(2, 3) + writeAreg(7, OpSize.OS_LONG, a[reg]) + writeAreg(reg, OpSize.OS_LONG, popStack(OpSize.OS_LONG)) + } + + private val TAS_EXEC: InstExec = { + val reg = it.bits(2, 3) + updateCC(OpSize.OS_BYTE, d[reg]) + writeDreg(reg, OpSize.OS_BYTE, d[reg] or 0x80, false) + } + + private val ADDI_EXEC: InstExec = { + val opSize = stdOpSize(it) + val imm = readImm(opSize) + val value = decReadStdEa(it, opSize) + val result = value + imm + updateAddSubCC(opSize, imm, value, result, true) + decUpdateStdEa(it, opSize, result, false) + } + + private val ADDQ_SUBQ_EXEC: InstExec = { + val isAddQ = (it.bits(8, 1) == 0) + val isAddA = (it.bits(5, 3) == 0b001) + val opSize = if (isAddA) OpSize.OS_LONG else stdOpSize(it) // address register destination is always long + val quickImm = ((it.bits(11, 3) - 1) and 7) + 1 + val value = decReadStdEa(it, opSize) + val result = value + if (isAddQ) quickImm else -quickImm + if (!isAddA) updateAddSubCC(opSize, quickImm, value, result, isAddQ) + decUpdateStdEa(it, opSize, result, false) + } + + private val ADDX_SUBX_EXEC: InstExec = { + val isAddX = (it.bits(14, 1) == 1) + val opSize = stdOpSize(it) + val x = cc.bits(CCB_X, 1) + val destReg = it.bits(2, 3) + val (source, dest) = d[it.bits(11, 3)] to d[destReg] + val result = if (isAddX) dest + source + x else dest - source - x + updateAddSubCC(opSize, source, dest, result, isAdd = isAddX, mergeZ = true) + writeDreg(destReg, opSize, result, false) + } + + private val ADDA_EXEC: InstExec = { + val opSize = WL_OP_SIZE[it.bits(8, 1)] + val aReg = it.bits(11, 3) + writeAreg(aReg, OpSize.OS_LONG, a[aReg] + decReadStdEa(it, opSize)) + } + + private val ADD_EXEC: InstExec = { + val opSize = stdOpSize(it) + val dReg = it.bits(11, 3) + val destIsDReg = (it.bits(8, 1) == 0) + val value = decReadStdEa(it, opSize) + val result = d[dReg] + value + updateAddSubCC(opSize, d[dReg], value, result, true) + if (destIsDReg) writeDreg(dReg, opSize, result, false) else decUpdateStdEa(it, opSize, result, false) + } + + private val CMPI_SUBI_EXEC: InstExec = { + val opSize = stdOpSize(it) + val (imm, value) = readImm(opSize) to decReadStdEa(it, opSize) + val result = value - imm + updateAddSubCC(opSize, imm, value, result, false) + if (it.bits(11, 1) == 0) decUpdateStdEa(it, opSize, result, false) // subi + } + + private val CMPA_SUBA_EXEC: InstExec = { + val isSubNotCmp = (it.bits(13, 1) == 0) + val opSize = WL_OP_SIZE[it.bits(8, 1)] + val aReg = it.bits(11, 3) + val value = decReadStdEa(it, opSize) + val result = a[aReg] - value + if (isSubNotCmp) writeAreg(aReg, OpSize.OS_LONG, result) else updateAddSubCC(OpSize.OS_LONG, value, a[aReg], result, false) + } + + private val CMP_SUB_EXEC: InstExec = { + val isSubNotCmp = (it.bits(13, 1) == 0) + val opSize = stdOpSize(it) + val dReg = it.bits(11, 3) + val destIsDReg = (it.bits(8, 1) == 0) + val (source, dest) = if (destIsDReg) decReadStdEa(it, opSize) to d[dReg] else d[dReg] to decReadStdEa(it, opSize) + val result = dest - source + updateAddSubCC(opSize, source, dest, result, false) + if (isSubNotCmp) (if (destIsDReg) writeDreg(dReg, opSize, result, false) else decUpdateStdEa(it, opSize, result, false)) + } + + private val NEG_EXEC: InstExec = { + val opSize = stdOpSize(it) + val value = decReadStdEa(it, opSize) + val result = 0 - value + updateAddSubCC(opSize, value, 0, result, false) + decUpdateStdEa(it, opSize, result, false) + } + + private val NEGX_EXEC: InstExec = { + val opSize = stdOpSize(it) + val value = decReadStdEa(it, opSize) + val x = cc.bits(CCB_X, 1) + val result = 0 - value - x + updateAddSubCC(opSize, value, 0, result, isAdd = false, mergeZ = true) + decUpdateStdEa(it, opSize, result, false) + } + + private val ORI_ANDI_EORI_EXEC: InstExec = { + val bitOp = it.bits(11, 3) + val opSize = stdOpSize(it) + val imm = readImm(opSize) + val value = decReadStdEa(it, opSize) + val result = when (bitOp) { + 0b000 -> value or imm + 0b001 -> value and imm + 0b101 -> value xor imm + else -> throw IllegalArgumentException("Wrong bitOp!") + } + decUpdateStdEa(it, opSize, result) + } + + private val OR_AND_EOR_EXEC: InstExec = { + val bitOp = it.bits(15, 4) + val opSize = stdOpSize(it) + val dReg = it.bits(11, 3) + val destIsDReg = (it.bits(8, 1) == 0) + val value = decReadStdEa(it, opSize) + val result = when (bitOp) { // operations are commutative + 0b1000 -> value or d[dReg] + 0b1011 -> value xor d[dReg] + 0b1100 -> value and d[dReg] + else -> throw IllegalArgumentException("Wrong op!") + } + if (destIsDReg) writeDreg(dReg, opSize, result) else decUpdateStdEa(it, opSize, result) + } + + private val NOT_EXEC: InstExec = { + val opSize = stdOpSize(it) + decUpdateStdEa(it, opSize, decReadStdEa(it, opSize).inv()) + } + + private val BITOPS_I_EXEC: InstExec = { + val bitOp = it.bits(7, 2) + val bitFlag = 1 shl (readImm(OpSize.OS_BYTE) and 7) + executeBitop(bitOp, it, OpSize.OS_BYTE, bitFlag) + } + + private val BITOPS_EXEC: InstExec = { + val bitOp = it.bits(7, 2) + val isDataRegisterMode = (it.bits(5, 3) == 0b000) + val opSize = if (isDataRegisterMode) OpSize.OS_LONG else OpSize.OS_BYTE + val bitFlag = 1 shl (d[it.bits(11, 3)] and (if (isDataRegisterMode) 31 else 7)) + executeBitop(bitOp, it, opSize, bitFlag) + } + + private val EXT_EXEC: InstExec = { + val opSize = WL_OP_SIZE[it.bits(6, 1)] + val reg = it.bits(2, 3) + writeDreg(reg, opSize, if (opSize == OpSize.OS_WORD) d[reg].toByte().toInt() else d[reg].toShort().toInt()) + } + + private val SWAP_EXEC: InstExec = { + val reg = it.bits(2, 3) + writeDreg(reg, OpSize.OS_LONG, ((d[reg] and 0xffff) shl 16) or ((d[reg] ushr 16) and 0xffff)) + } + + private val EXG_EXEC: InstExec = { + val mode = it.bits(7, 5) + val (reg1, reg2) = it.bits(2, 3) to it.bits(11, 3) + when (mode) { + 0b01000 -> d[reg1] = d[reg2].also { d[reg2] = d[reg1] } // data registers + 0b01001 -> a[reg1] = a[reg2].also { a[reg2] = a[reg1] } // address registers + 0b10001 -> a[reg1] = d[reg2].also { d[reg2] = a[reg1] } // data and address registers + } + } + + private val LEA_EXEC: InstExec = { + val reg = it.bits(11, 3) + writeAreg(reg, OpSize.OS_LONG, decodeStdEa(it)) + } + + private val PEA_EXEC: InstExec = { + pushStack(OpSize.OS_LONG, decodeStdEa(it)) + } + + private val RTS_EXEC: InstExec = { + pc = popStack(OpSize.OS_LONG) + } + + private val JSR_JMP_EXEC: InstExec = { + val newPc = decodeStdEa(it) + if (it.bits(6, 1) == 0) pushStack(OpSize.OS_LONG, pc) // jsr + pc = newPc + } + + private val BSR_BCC_EXEC: InstExec = { + var displacement = it.bits(7, 8).toByte().toInt() + if (displacement == 0) displacement = readPcSignExtWord() + val condition = it.bits(11, 4) + if (condition == 0b0001) { // bsr + pushStack(OpSize.OS_LONG, pc) + pc = relPc + displacement + } else if (checkCC(condition)) { + pc = relPc + displacement // bcc branch true + } + } + + private val SCC_EXEC: InstExec = { + val condition = it.bits(11, 4) + val result = if (checkCC(condition)) 0xff else 0x00 + decWriteStdEa(it, OpSize.OS_BYTE, result) + } + + private val DBCC_EXEC: InstExec = { + val displacement = readPcSignExtWord() + val condition = it.bits(11, 4) + val reg = it.bits(2, 3) + if (!checkCC(condition)) { + writeDreg(reg, OpSize.OS_WORD, d[reg] - 1, false) + if ((d[reg] and 0xffff) != 0xffff) pc = relPc + displacement + } + } + + private val MOVEM_EXEC: InstExec = { + var mask = readPcWord() + val opSize = WL_OP_SIZE[it.bits(6, 1)] + val (mode, reg) = it.bits(5, 3) to it.bits(2, 3) + var address = decodeEa(mode, reg) + if (it.bits(10, 1) == 0) { // register to memory + if (mode == 0b100) { // predecrement mode + d.plus(a).reversed().withIndex().filter { mask and (1 shl it.index) != 0 }.map { it.value }.forEach { + address -= OP_SIZE_BYTES[opSize]!! + writeEa(address, opSize, it, false) + } + } else { + d.plus(a).withIndex().filter { mask and (1 shl it.index) != 0 }.map { it.value }.forEach { + writeEa(address, opSize, it, false) + address += OP_SIZE_BYTES[opSize]!! + } + } + } else { // memory to register + for (daSel in 0..1) { + for (regNr in 0..7) { + if ((mask and 1) == 1) { + val value = trimToOpSizeSignExt(opSize, readEa(address, opSize)) + if (daSel == 0) writeDreg(regNr, OpSize.OS_LONG, value, false) else writeAreg(regNr, OpSize.OS_LONG, value) + address += OP_SIZE_BYTES[opSize]!! + } + mask = mask shr 1 + } + } + } + if (mode == 0b011 || mode == 0b100) { + writeAreg(reg, OpSize.OS_LONG, address) // update register on predecrement/postincrement + } + } + + private val MULU_EXEC: InstExec = { + val dReg = it.bits(11, 3) + val value = trimToOpSizeUnsigned(OpSize.OS_WORD, decReadStdEa(it, OpSize.OS_WORD)) + val result = trimToOpSizeUnsigned(OpSize.OS_WORD, d[dReg]) * value + writeDreg(dReg, OpSize.OS_LONG, result) + } + + private val MULS_EXEC: InstExec = { + val dReg = it.bits(11, 3) + val value = decReadStdEa(it, OpSize.OS_WORD) + val result = trimToOpSizeSignExt(OpSize.OS_WORD, d[dReg]) * value + writeDreg(dReg, OpSize.OS_LONG, result) + } + + private val DIVU_EXEC: InstExec = { + val dReg = it.bits(11, 3) + val value = decReadStdEa(it, OpSize.OS_WORD).toUShort() + if (value == 0.toUShort()) throw ArithmeticException("Division by zero") + val quotient = d[dReg].toUInt() / value + if (quotient > 0xffffu) { + changeCC(CCB_V, true) + } else { + val remainder = d[dReg].toUInt() % value + updateCC(OpSize.OS_WORD, quotient.toInt()) + writeDreg(dReg, OpSize.OS_LONG, ((remainder shl 16) or quotient).toInt(), false) + } + } + + private val DIVS_EXEC: InstExec = { + val dReg = it.bits(11, 3) + val value = decReadStdEa(it, OpSize.OS_WORD) + if (value == 0) throw ArithmeticException("Division by zero") + val quotient = d[dReg] / value + if ((quotient > 0x7fff) || (quotient < -0x8000)) { + changeCC(CCB_V, true) + } else { + val remainder = d[dReg] % value + updateCC(OpSize.OS_WORD, quotient) + writeDreg(dReg, OpSize.OS_LONG, (remainder shl 16) or (quotient and 0xffff), false) + } + } + + private val BITSHIFTS_EXEC: InstExec = { + val memOp = (it.bits(7, 2) == 3) + val rotReg = it.bits(11, 3) + val (mode, rotation) = if (memOp) it.bits(5, 3) to 1 else 0b000 to (if (it.bits(5, 1) == 1) (d[rotReg] and 63) else ((rotReg - 1) and 7) + 1) + val (reg, direction) = it.bits(2, 3) to it.bits(8, 1) + val (opSize, opMode) = if (memOp) OpSize.OS_WORD to it.bits(10, 2) else stdOpSize(it) to it.bits(4, 2) + var value = decReadEa(mode, reg, opSize) + val origSign = value.extractMSB(opSize) + var overflow = 0 + var (lastCarry, lastX) = 0 to ((cc shr CCB_X) and 1) + for (r in 1..rotation) { + lastCarry = if (direction == 0) (value and 1) else value.extractMSB(opSize) + val lastCarryOrX = (if ((opMode and 1) == 1) lastCarry else lastX) + value = when (opMode or (direction shl 2)) { + 0b000 -> value shr 1 // asr + 0b001 -> trimToOpSizeUnsigned(opSize, value) ushr 1 // lsr + 0b010, 0b011 -> ((value ushr 1) and ((1 shl OP_SIZE_MSB[opSize]!!).inv())) or (lastCarryOrX shl OP_SIZE_MSB[opSize]!!) // roxr/ror + 0b110, 0b111 -> (value shl 1) + lastCarryOrX // roxl/rol + 0b100, 0b101 -> value shl 1 // asl/lsl + else -> throw UnsupportedOperationException("Unsupported op") + } + overflow = overflow or (value.extractMSB(opSize) xor origSign) // overflow calculation for asl + lastX = lastCarry + } + decUpdateEa(mode, reg, opSize, value) + if (rotation > 0) { + changeCC(CCB_C, lastCarry) + if (opMode != 0b11) setXSameAsCarry() // X is set to C for all ops EXCEPT rol/ror + if (opMode == 0b00) changeCC(CCB_V, overflow) // asl has different V handling, asr will never set overflow + } + } + + data class InstDecode(val mask: Int, val value: Int, val exec: InstExec, val name: String) + + private val INSTRUCTION_DECODING = arrayOf( + listOf( // 0b0000 + InstDecode(0b1111_110_1_00_000_000, 0b0000_000_0_00_000_000, ORI_ANDI_EORI_EXEC, "ori/andi"), + InstDecode(0b1111_011_1_00_000_000, 0b0000_010_0_00_000_000, CMPI_SUBI_EXEC, "subi/cmpi"), + InstDecode(0b1111_111_1_00_000_000, 0b0000_011_0_00_000_000, ADDI_EXEC, "addi"), + InstDecode(0b1111_111_1_00_000_000, 0b0000_100_0_00_000_000, BITOPS_I_EXEC, "btst/bchg/bclr/bset"), + InstDecode(0b1111_111_1_00_000_000, 0b0000_101_0_00_000_000, ORI_ANDI_EORI_EXEC, "eori"), + InstDecode(0b1111_000_1_00_000_000, 0b0000_000_1_00_000_000, BITOPS_EXEC, "btst/bchg/bclr/bset") + ), + listOf(InstDecode(0b1111_000_000_000_000, 0b0001_000_000_000_000, MOVE_EXEC, "move.b")), // 0b0001 + listOf( // 0b0010 + InstDecode(0b1110_000_111_000_000, 0b0010_000_001_000_000, MOVEA_EXEC, "movea.l"), + InstDecode(0b1110_000_000_000_000, 0b0010_000_000_000_000, MOVE_EXEC, "move.l") + ), + listOf( // 0b0011 + InstDecode(0b1110_000_111_000_000, 0b0010_000_001_000_000, MOVEA_EXEC, "movea.w"), + InstDecode(0b1110_000_000_000_000, 0b0010_000_000_000_000, MOVE_EXEC, "move.w") + ), + listOf( // 0b0100 + InstDecode(0b1111_1111_00_000_000, 0b0100_0000_00_000_000, NEGX_EXEC, "negx"), + InstDecode(0b1111_1111_00_000_000, 0b0100_0010_00_000_000, CLR_EXEC, "clr"), + InstDecode(0b1111_1111_00_000_000, 0b0100_0100_00_000_000, NEG_EXEC, "neg"), + InstDecode(0b1111_1111_00_000_000, 0b0100_0110_00_000_000, NOT_EXEC, "not"), + InstDecode(0b1111_111_110_111_000, 0b0100_100_010_000_000, EXT_EXEC, "ext"), + InstDecode(0b1111_111_111_111_000, 0b0100_100_001_000_000, SWAP_EXEC, "swap"), + InstDecode(0b1111_111_111_000_000, 0b0100_100_001_000_000, PEA_EXEC, "pea"), + InstDecode(0b1111_111_111_111_000, 0b0100_1010_11_000_000, TAS_EXEC, "tas"), + InstDecode(0b1111_1111_00_000_000, 0b0100_1010_00_000_000, TST_EXEC, "tst"), + InstDecode(0b1111_1111_11111_000, 0b0100_1110_01010_000, LINK_EXEC, "link"), + InstDecode(0b1111_1111_11111_000, 0b0100_1110_01011_000, UNLK_EXEC, "unlk"), + InstDecode(0b1111_1111_1111_1111, 0b0100_1110_0111_0001, NOP_EXEC, "nop"), + InstDecode(0b1111_1111_1111_1111, 0b0100_1110_0111_0101, RTS_EXEC, "rts"), + InstDecode(0b1111_111_11_0_000_000, 0b0100_111_01_0_000_000, JSR_JMP_EXEC, "jsr/jmp"), + InstDecode(0b1111_101_11_0_000_000, 0b0100_100_01_0_000_000, MOVEM_EXEC, "movem"), + InstDecode(0b1111_000_11_1_000_000, 0b0100_000_11_1_000_000, LEA_EXEC, "lea") + ), + listOf( // 0b0101 + InstDecode(0b1111_0000_11_111_000, 0b0101_0000_11_001_000, DBCC_EXEC, "dbCC"), + InstDecode(0b1111_0000_11_000_000, 0b0101_0000_11_000_000, SCC_EXEC, "sCC"), + InstDecode(0b1111_000_0_00_000_000, 0b0101_000_0_00_000_000, ADDQ_SUBQ_EXEC, "addq/subq") + ), + listOf(InstDecode(0b1111_0000_00000000, 0b0110_0000_00000000, BSR_BCC_EXEC, "bsr/bCC")), // 0b0110 + listOf(InstDecode(0b1111_000_1_00000000, 0b0111_000_0_00000000, MOVEQ_EXEC, "moveq")), // 0b0111 + listOf( // 0b1000 + InstDecode(0b1111_000_1_11_000_000, 0b1000_000_0_11_000_000, DIVU_EXEC, "divu"), + InstDecode(0b1111_000_1_11_000_000, 0b1000_000_1_11_000_000, DIVS_EXEC, "divs"), + InstDecode(0b1111_000_0_00_000_000, 0b1000_000_0_00_000_000, OR_AND_EOR_EXEC, "or") + ), + listOf( // 0b1001 + InstDecode(0b1111_000_0_11_000_000, 0b1001_000_0_11_000_000, CMPA_SUBA_EXEC, "suba"), + InstDecode(0b1111_000_1_00_111_000, 0b1001_000_1_00_000_000, ADDX_SUBX_EXEC, "subx"), // subx to memory unsupported + InstDecode(0b1111_000_0_00_000_000, 0b1001_000_0_00_000_000, CMP_SUB_EXEC, "sub") + ), + emptyList(), // 0b1010 + listOf( // 0b1011 + InstDecode(0b1111_000_0_11_000_000, 0b1011_000_0_11_000_000, CMPA_SUBA_EXEC, "cmpa"), + InstDecode(0b1111_000_1_00_000_000, 0b1011_000_1_00_000_000, OR_AND_EOR_EXEC, "eor"), + InstDecode(0b1111_000_1_00_000_000, 0b1011_000_0_00_000_000, CMP_SUB_EXEC, "cmp") + ), + listOf( // 0b1100 + InstDecode(0b1111_000_1_11_000_000, 0b1100_000_0_11_000_000, MULU_EXEC, "mulu"), + InstDecode(0b1111_000_1_11_000_000, 0b1100_000_1_11_000_000, MULS_EXEC, "muls"), + InstDecode(0b1111_000_1_11110_000, 0b1100_000_1_01000_000, EXG_EXEC, "exg"), + InstDecode(0b1111_000_1_11111_000, 0b1100_000_1_10001_000, EXG_EXEC, "exg"), + InstDecode(0b1111_000_0_00_000_000, 0b1100_000_0_00_000_000, OR_AND_EOR_EXEC, "and") + ), + listOf( // 0b1101 + InstDecode(0b1111_000_0_11_000_000, 0b1101_000_0_11_000_000, ADDA_EXEC, "adda"), + InstDecode(0b1111_000_1_00_111_000, 0b1101_000_1_00_000_000, ADDX_SUBX_EXEC, "addx"), // addx to memory unsupported + InstDecode(0b1111_000_0_00_000_000, 0b1101_000_0_00_000_000, ADD_EXEC, "add") + ), + listOf(InstDecode(0b1111_000_0_00_0_00_000, 0b1110_000_0_00_0_00_000, BITSHIFTS_EXEC, "asx/lsx/roxx/rox")), // 0b1110 + emptyList() // 0b1111 + ) + } + + private fun executeBitop(bitOp: Int, inst: Int, opSize: OpSize, bitFlag: Int) { + val value = decReadStdEa(inst, opSize) + updateZ(opSize, value and bitFlag) + val result = when (bitOp) { + 0b01 -> value xor bitFlag // bchg + 0b10 -> value and bitFlag.inv() // bclr + 0b11 -> value or bitFlag // bset + else -> value // btst + } + if (bitOp != 0b00) decUpdateStdEa(inst, opSize, result, false) + } + + private fun checkCC(condition: Int) = when (condition) { + 0b0000 -> true + 0b0001 -> false + 0b0010 -> (cc and (CCF_C or CCF_Z)) == 0 // HI ~C && ~Z + 0b0011 -> (cc and (CCF_C or CCF_Z)) != 0 // LS C || Z + 0b0100 -> (cc and CCF_C) == 0 // CC + 0b0101 -> (cc and CCF_C) != 0 // CS + 0b0110 -> (cc and CCF_Z) == 0 // NE + 0b0111 -> (cc and CCF_Z) != 0 // EQ + 0b1000 -> (cc and CCF_V) == 0 // VC + 0b1001 -> (cc and CCF_V) != 0 // VS + 0b1010 -> (cc and CCF_N) == 0 // PL + 0b1011 -> (cc and CCF_N) != 0 // MI + 0b1100 -> (cc and (CCF_N or CCF_V) == (CCF_N or CCF_V)) || (cc and (CCF_N or CCF_V) == 0) // GE (N && V) || (~N && ~V) + 0b1101 -> (cc and (CCF_N or CCF_V) == CCF_N) || (cc and (CCF_N or CCF_V) == CCF_V) // LT (N && ~V) || (~N && V) + 0b1110 -> (cc and (CCF_N or CCF_V or CCF_Z) == (CCF_N or CCF_V)) || (cc and (CCF_N or CCF_V or CCF_Z) == 0) // GT (N && V && ~Z) || (~N && ~V && ~Z) + 0b1111 -> ((cc and CCF_Z) != 0) || (cc and (CCF_N or CCF_V) == CCF_N) || (cc and (CCF_N or CCF_V) == CCF_V) // LE Z || (N && ~V) || (~N && V) + else -> throw IllegalStateException("Should not happen") + } + + private fun updateAddSubCC(opSize: OpSize, source: Int, dest: Int, result: Int, isAdd: Boolean, mergeZ: Boolean = false) { + updateCC(opSize, result, mergeZ) + val (op1, op2) = if (isAdd) result to dest else dest to result + val hasCarry = ((source and op2) or (op1.inv() and (source or op2))).extractMSB(opSize) + val hasOverflow = ((source xor op1) and (dest xor result)).extractMSB(opSize) + changeCC(CCB_C, hasCarry) + changeCC(CCB_V, hasOverflow) + setXSameAsCarry() + } + + private fun updateZ(opSize: OpSize, value: Int, mergeZ: Boolean = false) { + val extVal = trimToOpSizeUnsigned(opSize, value) + if (mergeZ) { + if (extVal != 0) changeCC(CCB_Z, false) + } else { + changeCC(CCB_Z, extVal == 0) + } + } + + private fun updateN(opSize: OpSize, value: Int) { + changeCC(CCB_N, trimToOpSizeSignExt(opSize, value) < 0) + } + + private fun updateCC(opSize: OpSize, value: Int, mergeZ: Boolean = false) { + changeCC(CCB_C, false) // clear C + changeCC(CCB_V, false) // clear V + updateZ(opSize, value, mergeZ) + updateN(opSize, value) + } + + private fun setXSameAsCarry() { + changeCC(CCB_X, cc.bits(CCB_C, 1)) + } + + private fun writeDreg(reg: Int, opSize: OpSize, value: Int, updateCC: Boolean = true) { + if (updateCC) updateCC(opSize, value) + d[reg] = ((d[reg] xor trimToOpSizeUnsigned(opSize, d[reg])) or trimToOpSizeUnsigned(opSize, value)) + .also { if (debug) println("d[$reg].$opSize = ${value.toHex(8)} (${d[reg].toHex(8)} -> ${it.toHex(8)})") } + } + + private fun writeAreg(reg: Int, opSize: OpSize, value: Int) { + a[reg] = when (opSize) { + OpSize.OS_BYTE -> throw UnsupportedOperationException("Byte access for address register not allowed") + OpSize.OS_WORD -> trimToOpSizeSignExt(opSize, value) + OpSize.OS_LONG -> value + }.also { if (debug) println("a[$reg].$opSize = ${value.toHex(8)} (${a[reg].toHex(8)} -> ${it.toHex(8)})") } + } + + private val readEaMap = hashMapOf(OpSize.OS_BYTE to ::readByte, OpSize.OS_WORD to ::readWord, OpSize.OS_LONG to ::readLong) + private val writeEaMap = hashMapOf(OpSize.OS_BYTE to ::writeByte, OpSize.OS_WORD to ::writeWord, OpSize.OS_LONG to ::writeLong) + + private fun readEa(ea: Int, opSize: OpSize) = readEaMap[opSize]!!.invoke(ea) + private fun writeEa(ea: Int, opSize: OpSize, value: Int, updateCC: Boolean = false) { + if (updateCC) updateCC(opSize, value) + writeEaMap[opSize]!!.invoke(ea, value) + } + + private fun readImm(opSize: OpSize) = trimToOpSizeSignExt(opSize, if (opSize == OpSize.OS_LONG) readPcLong() else readPcWord()) + + private fun decodeStdEa(inst: Int) = decodeEa(inst.bits(5, 3), inst.bits(2, 3)) + private fun decodeEa(mode: Int, reg: Int) = when (mode) { + 0b010, 0b011, 0b100 -> a[reg] // address register indirect (and pre/post base address) + 0b101 -> a[reg] + readPcSignExtWord() // address with displacement + 0b110 -> a[reg] + decodeBriefExtWordEa(readPcWord()) // address with index + 0b111 -> when (reg) { + 0b000 -> readPcWord().toShort().toInt() // absolute short + 0b001 -> readPcLong() // absolute long + 0b010 -> relPc + readPcSignExtWord() // program counter with displacement + 0b011 -> relPc + decodeBriefExtWordEa(readPcWord()) // program counter with index + else -> throw UnsupportedOperationException("Unsupported ea mode $mode with reg $reg") + } + + else -> throw UnsupportedOperationException("Unsupported ea mode $mode with reg $reg") + }.also { lastEa = it } + + private fun decodeBriefExtWordEa(extWord: Int): Int { + val reg = extWord.bits(14, 3) + val regSize = WL_OP_SIZE[extWord.bits(11, 1)] + val regAddress = trimToOpSizeSignExt(regSize, if (extWord.bits(15, 1) == 1) a[reg] else d[reg]) + val displacement = trimToOpSizeSignExt(OpSize.OS_BYTE, extWord) + return regAddress + displacement + } + + private fun preDecAreg(reg: Int, opSize: OpSize) { + a[reg] -= OP_SIZE_BYTES[if ((reg == 7) && (opSize == OpSize.OS_BYTE)) OpSize.OS_WORD else opSize]!! + } + + private fun postIncAreg(reg: Int, opSize: OpSize) { + a[reg] += OP_SIZE_BYTES[if ((reg == 7) && (opSize == OpSize.OS_BYTE)) OpSize.OS_WORD else opSize]!! + } + + private fun decReadStdEa(inst: Int, opSize: OpSize) = decReadEa(inst.bits(5, 3), inst.bits(2, 3), opSize) + + private fun decReadEa(mode: Int, reg: Int, opSize: OpSize): Int { + return when (mode) { + 0b000 -> trimToOpSizeSignExt(opSize, d[reg]) // data register direct + 0b001 -> when (opSize) { // address register direct + OpSize.OS_WORD -> a[reg].toShort().toInt() + OpSize.OS_LONG -> a[reg] + else -> throw UnsupportedOperationException("Byte access for address register not allowed") + } + + 0b010, 0b101, 0b110 -> readEa(decodeEa(mode, reg), opSize) // address register indirect/with displacement/with index + 0b011 -> readEa(decodeEa(mode, reg), opSize).also { postIncAreg(reg, opSize) } // address register indirect with postincrement + 0b100 -> { // address register indirect with predecrement + preDecAreg(reg, opSize) + readEa(decodeEa(mode, reg), opSize) + } + + 0b111 -> when (reg) { + 0b000, 0b001, 0b010, 0b011 -> readEa(decodeEa(mode, reg), opSize) // absolute short/long, program counter with displacement/with index + 0b100 -> readImm(opSize) + else -> throw UnsupportedOperationException("Unsupported read mode $mode with reg $reg") + } + + else -> throw UnsupportedOperationException("Unsupported read mode $mode with reg $reg") + } + } + + private fun decWriteStdEa(inst: Int, opSize: OpSize, value: Int, updateCC: Boolean = true) = + decWriteEa(inst.bits(5, 3), inst.bits(2, 3), opSize, value, updateCC) + + private fun decWriteEa(mode: Int, reg: Int, opSize: OpSize, value: Int, updateCC: Boolean = true) { + when (mode) { + 0b000 -> writeDreg(reg, opSize, value, updateCC) // data register direct + 0b001 -> writeAreg(reg, opSize, value) // address register direct + 0b010, 0b101, 0b110 -> writeEa(decodeEa(mode, reg), opSize, value, updateCC) // address register indirect/with displacement/with index + 0b011 -> { // address register indirect with postincrement + writeEa(decodeEa(0b010, reg), opSize, value, updateCC) + postIncAreg(reg, opSize) + } + + 0b100 -> { // address register indirect with predecrement + preDecAreg(reg, opSize) + writeEa(decodeEa(0b010, reg), opSize, value, updateCC) + } + + 0b111 -> when (reg) { + 0b000, 0b001 -> writeEa(decodeEa(mode, reg), opSize, value, updateCC) // absolute short/long + else -> throw UnsupportedOperationException("Unsupported write mode $mode with reg $reg") + } + + else -> throw UnsupportedOperationException("Unsupported write mode $mode with reg $reg") + } + } + + private fun decUpdateStdEa(inst: Int, opSize: OpSize, value: Int, updateCC: Boolean = true) = + decUpdateEa(inst.bits(5, 3), inst.bits(2, 3), opSize, value, updateCC) + + private fun decUpdateEa(mode: Int, reg: Int, opSize: OpSize, value: Int, updateCC: Boolean = true) = + when (mode) { + 0b000 -> writeDreg(reg, opSize, value, updateCC) // data register direct + 0b001 -> writeAreg(reg, opSize, value) // address register direct + else -> writeEa(lastEa, opSize, value, updateCC) // all other modes + } + + private fun findRegion(address: Int) = memRegions.firstOrNull { address in it.start until it.start + it.size } + ?: throw IllegalArgumentException("Unknown memory location ${address.toHex(8)}") + + private fun ensureEven(address: Int) = + if (address and 1 == 0) address else throw UnsupportedOperationException("Read from odd address ${address.toHex(8)}") + + private var relPc: Int = 0 + private var lastEa: Int = 0 + private val lookupCache = HashMap() + + var debug = false + val memRegions = ArrayList() + val d: IntArray = IntArray(8) + val a: IntArray = IntArray(8) + var pc: Int = 0 + var cc: Int = 0 + + fun decode(): String { + val inst = readPcWord() + val instDecode = lookupCache.getOrPut(inst) { + INSTRUCTION_DECODING[inst shr 12].firstOrNull { (inst and it.mask) == it.value } + ?: throw UnsupportedOperationException("Not found ${inst.toHex(4)}") + } + if (debug) println("${(pc - 2).toHex(8)}(cc=${cc.toHex(2)}): ${instDecode.name} ${inst.toHex(4)} (${readLong(pc).toHex(8)})") + relPc = pc + instDecode.exec(this, inst) + return instDecode.name + } + + fun changeCC(bit: Int, value: Int) { + cc = (cc and ((1 shl bit).inv()) or ((value and 1) shl bit)) + } + + fun changeCC(bit: Int, setclr: Boolean) { + cc = if (setclr) (cc or (1 shl bit)) else (cc and ((1 shl bit).inv())) + } + + fun readByte(address: Int) = findRegion(address).readByte(address) + fun readWord(address: Int) = findRegion(address).readWord(ensureEven(address)) + fun readLong(address: Int) = findRegion(address).readLong(ensureEven(address)) + + fun writeByte(address: Int, value: Int) { + findRegion(address).writeByte(address, value) + } + + fun writeWord(address: Int, value: Int) { + findRegion(address).writeWord(ensureEven(address), value) + } + + fun writeLong(address: Int, value: Int) { + findRegion(address).writeLong(ensureEven(address), value) + } + + fun popStack(opSize: OpSize) = readEa(a[7], opSize).also { postIncAreg(7, opSize) } + fun pushStack(opSize: OpSize, value: Int) { + preDecAreg(7, opSize) + writeEa(a[7], opSize, value, false) + } + + fun readPcWord() = readWord(pc.also { pc += 2 }) + fun readPcSignExtWord() = readPcWord().toShort().toInt() + fun readPcLong() = readLong(pc.also { pc += 4 }) + + fun dumpRegs() { + val dregs = d.withIndex().map { " d${it.index} ${it.value.toHex(8)}" }.chunked(4) + .joinToString(separator = "\n") { it.joinToString(separator = "") } + val aregs = a.withIndex().map { " a${it.index} ${it.value.toHex(8)}" }.chunked(4) + .joinToString(separator = "\n") { it.joinToString(separator = "") } + println(dregs) + println(aregs) + println("CC=${cc.toHex(2)} (${"CVZNX".withIndex().map { if ((cc and (1 shl it.index)) != 0) it.value else "-" }.joinToString("")}) PC=${pc.toHex(8)}") + } +} diff --git a/src/main/kotlin/de/platon42/mc68kemu/MemoryRegion.kt b/src/main/kotlin/de/platon42/mc68kemu/MemoryRegion.kt new file mode 100644 index 0000000..b84c1c8 --- /dev/null +++ b/src/main/kotlin/de/platon42/mc68kemu/MemoryRegion.kt @@ -0,0 +1,15 @@ +package de.platon42.mc68kemu + +abstract class MemoryRegion(val start: Int, val size: Int) { + abstract fun readByte(address: Int): Int + abstract fun readWord(address: Int): Int + open fun readLong(address: Int): Int = (readWord(address) shl 16) or readWord(address + 2) + + abstract fun writeByte(address: Int, value: Int) + abstract fun writeWord(address: Int, value: Int) + + open fun writeLong(address: Int, value: Int) { + writeWord(address, value ushr 16) + writeWord(address + 2, value) + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/platon42/mc68kemu/PaulaChannelInfo.kt b/src/main/kotlin/de/platon42/mc68kemu/PaulaChannelInfo.kt new file mode 100644 index 0000000..ff9cabc --- /dev/null +++ b/src/main/kotlin/de/platon42/mc68kemu/PaulaChannelInfo.kt @@ -0,0 +1,12 @@ +package de.platon42.mc68kemu + +data class PaulaChannelInfo( + var stopDma: Boolean = false, + var startDma: Boolean = false, + var startAddress: Int = 0, + var lengthWords: Int = 0, + var loopAddress: Int = 0, + var loopLengthWords: Int = 0, + var volume: Int = 0, + var period: Int = 0, +) \ No newline at end of file diff --git a/src/main/kotlin/de/platon42/mc68kemu/Ram.kt b/src/main/kotlin/de/platon42/mc68kemu/Ram.kt new file mode 100644 index 0000000..bbe1b1f --- /dev/null +++ b/src/main/kotlin/de/platon42/mc68kemu/Ram.kt @@ -0,0 +1,49 @@ +package de.platon42.mc68kemu + + +@OptIn(ExperimentalUnsignedTypes::class) +class Ram(start: Int, size: Int, val mem: UByteArray = UByteArray(size), var debug: Boolean = false) : MemoryRegion(start, size) { + override fun readByte(address: Int): Int { + val v = mem[address - start].toInt() + if (debug) println("${v.toHex(4)} = R[${address.toHex(2)}]") + return v + } + + override fun readWord(address: Int): Int { + val i = address - start + val v = (mem[i].toInt() shl 8) or mem[i + 1].toInt() + if (debug) println("${v.toHex(4)} = R[${address.toHex(8)}]") + return v + } + + override fun readLong(address: Int): Int { + val i = address - start + val v = (mem[i].toInt() shl 24) or + (mem[i + 1].toInt() shl 16) or + (mem[i + 2].toInt() shl 8) or + mem[i + 3].toInt() + if (debug) println("${v.toHex(8)} = R[${address.toHex(8)}]") + return v + } + + override fun writeByte(address: Int, value: Int) { + mem[address - start] = value.toUByte() + if (debug) println("W[${address.toHex(8)}] = ${value.toHex(2)}") + } + + override fun writeWord(address: Int, value: Int) { + val i = address - start + mem[i] = (value ushr 8).toUByte() + mem[i + 1] = value.toUByte() + if (debug) println("W[${address.toHex(8)}] = ${value.toHex(4)}") + } + + override fun writeLong(address: Int, value: Int) { + val i = address - start + mem[i] = (value ushr 24).toUByte() + mem[i + 1] = (value ushr 16).toUByte() + mem[i + 2] = (value ushr 8).toUByte() + mem[i + 3] = value.toUByte() + if (debug) println("W[${address.toHex(8)}] = ${value.toHex(8)}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/platon42/mc68kemu/Rom.kt b/src/main/kotlin/de/platon42/mc68kemu/Rom.kt new file mode 100644 index 0000000..a8fa697 --- /dev/null +++ b/src/main/kotlin/de/platon42/mc68kemu/Rom.kt @@ -0,0 +1,28 @@ +package de.platon42.mc68kemu + + +@OptIn(ExperimentalUnsignedTypes::class) +class Rom(start: Int, val rom: UByteArray) : MemoryRegion(start, rom.size) { + override fun readByte(address: Int): Int { + return rom[address - start].toInt() + } + + override fun readWord(address: Int): Int { + val i = address - start + return (rom[i].toInt() shl 8) or rom[i + 1].toInt() + } + + override fun readLong(address: Int): Int { + val i = address - start + return (rom[i].toInt() shl 24) or + (rom[i + 1].toInt() shl 16) or + (rom[i + 2].toInt() shl 8) or + rom[i + 3].toInt() + } + + override fun writeByte(address: Int, value: Int) = throw UnsupportedOperationException("Attempt to write to rom ${address.toHex(8)}") + + override fun writeWord(address: Int, value: Int) = throw UnsupportedOperationException("Attempt to write to rom ${address.toHex(8)}") + + override fun writeLong(address: Int, value: Int) = throw UnsupportedOperationException("Attempt to write to rom ${address.toHex(8)}") +} \ No newline at end of file