This commit is contained in:
Chris Hodges 2026-04-12 17:08:20 +02:00
commit d716ba641c
11 changed files with 1149 additions and 0 deletions

21
LICENSE Normal file
View File

@ -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.

59
README.md Normal file
View File

@ -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.

View File

@ -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)}")
}

View File

@ -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<Array<PaulaChannelInfo>>()
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<Short> {
return mixer.monoMix(amigaFrameTime)
}
fun recordPaulaFrameStereo(amigaFrameTime: Float): Pair<List<Short>, List<Short>> {
return mixer.stereoMix(amigaFrameTime)
}
private fun getAudioParams(address: Int): Triple<Int, PaulaChannelInfo, PaulaChannel> {
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)
}
}
}

View File

@ -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)}")
}
}

View File

@ -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')
}

View File

@ -0,0 +1,753 @@
package de.platon42.mc68kemu
// Minimalistic MC68000 CPU emulation by Chris Hodges <chrisly@platon42.de>
//
// 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<Int, InstDecode>()
var debug = false
val memRegions = ArrayList<MemoryRegion>()
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)}")
}
}

View File

@ -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)
}
}

View File

@ -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,
)

View File

@ -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)}")
}
}

View File

@ -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)}")
}