Initial.
This commit is contained in:
commit
d716ba641c
21
LICENSE
Normal file
21
LICENSE
Normal 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
59
README.md
Normal 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.
|
||||
21
src/main/kotlin/de/platon42/mc68kemu/CiaChip.kt
Normal file
21
src/main/kotlin/de/platon42/mc68kemu/CiaChip.kt
Normal 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)}")
|
||||
}
|
||||
144
src/main/kotlin/de/platon42/mc68kemu/CustomChipPaulaRecorder.kt
Normal file
144
src/main/kotlin/de/platon42/mc68kemu/CustomChipPaulaRecorder.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/main/kotlin/de/platon42/mc68kemu/CustomChips.kt
Normal file
41
src/main/kotlin/de/platon42/mc68kemu/CustomChips.kt
Normal 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)}")
|
||||
}
|
||||
}
|
||||
6
src/main/kotlin/de/platon42/mc68kemu/Extensions.kt
Normal file
6
src/main/kotlin/de/platon42/mc68kemu/Extensions.kt
Normal 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')
|
||||
}
|
||||
753
src/main/kotlin/de/platon42/mc68kemu/M68kCpu.kt
Normal file
753
src/main/kotlin/de/platon42/mc68kemu/M68kCpu.kt
Normal 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)}")
|
||||
}
|
||||
}
|
||||
15
src/main/kotlin/de/platon42/mc68kemu/MemoryRegion.kt
Normal file
15
src/main/kotlin/de/platon42/mc68kemu/MemoryRegion.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
12
src/main/kotlin/de/platon42/mc68kemu/PaulaChannelInfo.kt
Normal file
12
src/main/kotlin/de/platon42/mc68kemu/PaulaChannelInfo.kt
Normal 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,
|
||||
)
|
||||
49
src/main/kotlin/de/platon42/mc68kemu/Ram.kt
Normal file
49
src/main/kotlin/de/platon42/mc68kemu/Ram.kt
Normal 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)}")
|
||||
}
|
||||
}
|
||||
28
src/main/kotlin/de/platon42/mc68kemu/Rom.kt
Normal file
28
src/main/kotlin/de/platon42/mc68kemu/Rom.kt
Normal 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)}")
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user