mirror of
https://github.com/irmen/ksim65.git
synced 2025-01-07 16:30:55 +00:00
introduced separate Assembler class
This commit is contained in:
parent
c619ddabf1
commit
deaf79fcc2
237
src/main/kotlin/razorvine/ksim65/Assembler.kt
Normal file
237
src/main/kotlin/razorvine/ksim65/Assembler.kt
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
package razorvine.ksim65
|
||||||
|
|
||||||
|
import razorvine.ksim65.components.Address
|
||||||
|
import razorvine.ksim65.components.MemMappedComponent
|
||||||
|
|
||||||
|
|
||||||
|
class Assembler(cpu: Cpu6502, val memory: MemMappedComponent, initialStartAddress: Address? = null) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun parseRelativeToPC(relative: String, currentAddress: Int): Int {
|
||||||
|
val rest = relative.substring(1).trim()
|
||||||
|
if(rest.isNotEmpty()) {
|
||||||
|
return when(rest[0]) {
|
||||||
|
'-' -> currentAddress-parseNumber(rest.substring(1))
|
||||||
|
'+' -> currentAddress+parseNumber(rest.substring(1))
|
||||||
|
else -> throw NumberFormatException("invalid address syntax")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseNumber(number: String, decimalFirst: Boolean = false): Int {
|
||||||
|
val num = number.trim()
|
||||||
|
if (num.isBlank()) return 0
|
||||||
|
if (decimalFirst && num[0].isDigit()) return num.toInt(10)
|
||||||
|
return when (num[0]) {
|
||||||
|
'$' -> num.substring(1).trimStart().toInt(16)
|
||||||
|
'#' -> num.substring(1).trimStart().toInt(10)
|
||||||
|
'%' -> num.substring(1).trimStart().toInt(2)
|
||||||
|
else -> num.toInt(16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private var address = initialStartAddress ?: 0
|
||||||
|
private var assembledSize = 0
|
||||||
|
|
||||||
|
private val instructions by lazy {
|
||||||
|
val instr = cpu.instructions.withIndex().associate {
|
||||||
|
Pair(it.value.mnemonic, it.value.mode) to it.index
|
||||||
|
}.toMutableMap()
|
||||||
|
instr[Pair("nop", Cpu6502.AddrMode.Imp)] = 0xea
|
||||||
|
instr.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Result(val success: Boolean, val error: String, val startAddress: Address, val numBytes: Int)
|
||||||
|
|
||||||
|
fun assemble(lines: Iterable<String>): Result {
|
||||||
|
for(line in lines) {
|
||||||
|
val result = assemble(line)
|
||||||
|
if(!result.success)
|
||||||
|
return result
|
||||||
|
assembledSize += result.numBytes
|
||||||
|
}
|
||||||
|
return Result(true, "", address, assembledSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(line: String): Result {
|
||||||
|
|
||||||
|
/*
|
||||||
|
The command is a line of the form:
|
||||||
|
|
||||||
|
"<address> <instruction> [<arguments>]"
|
||||||
|
" <instruction> [<arguments>]"
|
||||||
|
"* = <address>"
|
||||||
|
*/
|
||||||
|
|
||||||
|
var args = line.trim().split(' ')
|
||||||
|
if(args.isEmpty() || args.size == 1 && args[0] == "")
|
||||||
|
return Result(true, "", address, 0)
|
||||||
|
if(args[0].startsWith("*=") && args.size==1) {
|
||||||
|
address = parseNumber(args[0].substring(2))
|
||||||
|
return Result(true, "", address, 0)
|
||||||
|
}
|
||||||
|
else if(args[0] == "*" && args[1] == "=") {
|
||||||
|
address = parseNumber(args[2])
|
||||||
|
return Result(true, "", address, 0)
|
||||||
|
} else {
|
||||||
|
// line with an instruction, may be preceded by a 4 or 5 char address
|
||||||
|
if(args[0].length == 4 || args[0].length==5) {
|
||||||
|
if(args.size!=2 && args.size !=3)
|
||||||
|
return Result(false, "syntax error", address, 0)
|
||||||
|
address = parseNumber(args[0])
|
||||||
|
args = args.drop(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val instructionSize: Int
|
||||||
|
val mnemonic = args[0].toLowerCase().trim()
|
||||||
|
when (args.size) {
|
||||||
|
1 -> {
|
||||||
|
// implied or acc
|
||||||
|
instructionSize = 1
|
||||||
|
var instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imp)]
|
||||||
|
if (instruction == null) instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Acc)]
|
||||||
|
if (instruction == null) return Result(false, "invalid instruction", this.address, 0)
|
||||||
|
memory[address] = instruction.toShort()
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
val arg = args[1]
|
||||||
|
when {
|
||||||
|
arg.startsWith('#') -> {
|
||||||
|
// immediate
|
||||||
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imm)] ?: return Result(false, "invalid instruction",
|
||||||
|
this.address, 0)
|
||||||
|
memory[address] = instruction.toShort()
|
||||||
|
memory[address+1] = parseNumber(arg.substring(1), decimalFirst = true).toShort()
|
||||||
|
instructionSize = 2
|
||||||
|
}
|
||||||
|
arg.startsWith("(") && arg.endsWith(",x)") -> {
|
||||||
|
// indirect X
|
||||||
|
val indAddress = try {
|
||||||
|
parseNumber(arg.substring(1, arg.length-3))
|
||||||
|
} catch (x: NumberFormatException) {
|
||||||
|
return Result(false, "invalid instruction", this.address, 0)
|
||||||
|
}
|
||||||
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.IzX)] ?: return Result(false, "invalid instruction",
|
||||||
|
this.address, 0)
|
||||||
|
memory[address] = instruction.toShort()
|
||||||
|
memory[address+1] = indAddress.toShort()
|
||||||
|
instructionSize = 2
|
||||||
|
}
|
||||||
|
arg.startsWith("(") && arg.endsWith("),y") -> {
|
||||||
|
// indirect Y
|
||||||
|
val indAddress = try {
|
||||||
|
parseNumber(arg.substring(1, arg.length-3))
|
||||||
|
} catch (x: NumberFormatException) {
|
||||||
|
return Result(false, "invalid instruction", this.address, 0)
|
||||||
|
}
|
||||||
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.IzY)] ?: return Result(false, "invalid instruction",
|
||||||
|
this.address, 0)
|
||||||
|
memory[address] = instruction.toShort()
|
||||||
|
memory[address+1] = indAddress.toShort()
|
||||||
|
instructionSize = 2
|
||||||
|
}
|
||||||
|
arg.endsWith(",x") -> {
|
||||||
|
// indexed X or zpIndexed X
|
||||||
|
val indAddress = try {
|
||||||
|
parseNumber(arg.substring(1, arg.length-2))
|
||||||
|
} catch (x: NumberFormatException) {
|
||||||
|
return Result(false, "invalid instruction", this.address, 0)
|
||||||
|
}
|
||||||
|
instructionSize = if (indAddress <= 255) {
|
||||||
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.ZpX)] ?: return Result(false, "invalid instruction",
|
||||||
|
this.address, 0)
|
||||||
|
memory[address] = instruction.toShort()
|
||||||
|
memory[address+1] = indAddress.toShort()
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.AbsX)] ?: return Result(false, "invalid instruction",
|
||||||
|
this.address, 0)
|
||||||
|
memory[address] = instruction.toShort()
|
||||||
|
memory[address+1] = (indAddress and 255).toShort()
|
||||||
|
memory[address+2] = (indAddress ushr 8).toShort()
|
||||||
|
3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arg.endsWith(",y") -> {
|
||||||
|
// indexed Y or zpIndexed Y
|
||||||
|
val indAddress = try {
|
||||||
|
parseNumber(arg.substring(1, arg.length-2))
|
||||||
|
} catch (x: NumberFormatException) {
|
||||||
|
return Result(false, "invalid instruction", this.address, 0)
|
||||||
|
}
|
||||||
|
instructionSize = if (indAddress <= 255) {
|
||||||
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.ZpY)] ?: return Result(false, "invalid instruction",
|
||||||
|
this.address, 0)
|
||||||
|
memory[address] = instruction.toShort()
|
||||||
|
memory[address+1] = indAddress.toShort()
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.AbsY)] ?: return Result(false, "invalid instruction",
|
||||||
|
this.address, 0)
|
||||||
|
memory[address] = instruction.toShort()
|
||||||
|
memory[address+1] = (indAddress and 255).toShort()
|
||||||
|
memory[address+2] = (indAddress ushr 8).toShort()
|
||||||
|
3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arg.endsWith(")") -> {
|
||||||
|
// indirect (jmp)
|
||||||
|
val indAddress = try {
|
||||||
|
parseNumber(arg.substring(1, arg.length-1))
|
||||||
|
} catch (x: NumberFormatException) {
|
||||||
|
return Result(false, "invalid instruction", this.address, 0)
|
||||||
|
}
|
||||||
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Ind)]
|
||||||
|
?: return Result(false, "invalid instruction", this.address, 0)
|
||||||
|
memory[address] = instruction.toShort()
|
||||||
|
memory[address+1] = (indAddress and 255).toShort()
|
||||||
|
memory[address+2] = (indAddress ushr 8).toShort()
|
||||||
|
instructionSize = 3
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val instr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Rel)]
|
||||||
|
if (instr != null) {
|
||||||
|
// relative address
|
||||||
|
val rel = try {
|
||||||
|
parseRelativeToPC(arg, address)
|
||||||
|
} catch (x: NumberFormatException) {
|
||||||
|
return Result(false, "invalid numeral", this.address, 0)
|
||||||
|
}
|
||||||
|
memory[address] = instr.toShort()
|
||||||
|
memory[address+1] = (rel-address-2 and 255).toShort()
|
||||||
|
instructionSize = 2
|
||||||
|
} else {
|
||||||
|
// absolute or absZp
|
||||||
|
val absAddress = try {
|
||||||
|
if(arg.startsWith('*')) parseRelativeToPC(arg, address) else parseNumber(arg)
|
||||||
|
} catch (x: NumberFormatException) {
|
||||||
|
return Result(false, "invalid numeral", this.address, 0)
|
||||||
|
}
|
||||||
|
val zpInstruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Zp)]
|
||||||
|
instructionSize = if (absAddress <= 255 && zpInstruction != null) {
|
||||||
|
memory[address] = zpInstruction.toShort()
|
||||||
|
memory[address+1] = absAddress.toShort()
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
val absInstr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Abs)] ?: return Result(false, "invalid instruction",
|
||||||
|
this.address, 0)
|
||||||
|
memory[address] = absInstr.toShort()
|
||||||
|
memory[address+1] = (absAddress and 255).toShort()
|
||||||
|
memory[address+2] = (absAddress ushr 8).toShort()
|
||||||
|
3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
return Result(false, "syntax error", this.address, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result(true, "", this.address, instructionSize)
|
||||||
|
}
|
||||||
|
}
|
@ -64,4 +64,8 @@ open class Bus {
|
|||||||
it[address-it.startAddress] = data
|
it[address-it.startAddress] = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun memoryComponentFor(address: Address) =
|
||||||
|
memComponents.first { it is MemoryComponent && address >= it.startAddress && address <= it.endAddress } as MemoryComponent
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,6 @@ import kotlin.math.max
|
|||||||
|
|
||||||
class Monitor(val bus: Bus, val cpu: Cpu6502) {
|
class Monitor(val bus: Bus, val cpu: Cpu6502) {
|
||||||
|
|
||||||
private val instructions by lazy {
|
|
||||||
val instr = cpu.instructions.withIndex().associate {
|
|
||||||
Pair(it.value.mnemonic, it.value.mode) to it.index
|
|
||||||
}.toMutableMap()
|
|
||||||
instr[Pair("nop", Cpu6502.AddrMode.Imp)] = 0xea
|
|
||||||
instr.toMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val disassembler = Disassembler(cpu)
|
private val disassembler = Disassembler(cpu)
|
||||||
|
|
||||||
fun command(command: String): IVirtualMachine.MonitorCmdResult {
|
fun command(command: String): IVirtualMachine.MonitorCmdResult {
|
||||||
@ -26,9 +18,9 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
|
|||||||
val parts = command.substring(1).trim().split(' ')
|
val parts = command.substring(1).trim().split(' ')
|
||||||
if (parts.size != 3) IVirtualMachine.MonitorCmdResult("?syntax error", command, false)
|
if (parts.size != 3) IVirtualMachine.MonitorCmdResult("?syntax error", command, false)
|
||||||
else {
|
else {
|
||||||
val start = parseNumber(parts[0])
|
val start = Assembler.parseNumber(parts[0])
|
||||||
val end = parseNumber(parts[1])
|
val end = Assembler.parseNumber(parts[1])
|
||||||
val value = parseNumber(parts[2]).toShort()
|
val value = Assembler.parseNumber(parts[2]).toShort()
|
||||||
for (addr in start..end) {
|
for (addr in start..end) {
|
||||||
bus.write(addr, value)
|
bus.write(addr, value)
|
||||||
}
|
}
|
||||||
@ -37,8 +29,8 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
|
|||||||
}
|
}
|
||||||
'm' -> {
|
'm' -> {
|
||||||
val addresses = command.substring(1).trim().split(' ')
|
val addresses = command.substring(1).trim().split(' ')
|
||||||
val start = parseNumber(addresses[0])
|
val start = Assembler.parseNumber(addresses[0])
|
||||||
val end = if (addresses.size > 1) parseNumber(addresses[1]) else start+1
|
val end = if (addresses.size > 1) Assembler.parseNumber(addresses[1]) else start+1
|
||||||
val result = mutableListOf<String>()
|
val result = mutableListOf<String>()
|
||||||
for (addr in start until end step 16) {
|
for (addr in start until end step 16) {
|
||||||
result.add("m$${hexW(addr)} "+(0..15).joinToString(" ") { hexB(bus.read(addr+it)) }+" "+(0..15).joinToString("") {
|
result.add("m$${hexW(addr)} "+(0..15).joinToString(" ") { hexB(bus.read(addr+it)) }+" "+(0..15).joinToString("") {
|
||||||
@ -51,15 +43,15 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
|
|||||||
}
|
}
|
||||||
'p' -> {
|
'p' -> {
|
||||||
val numbers = command.substring(1).trim().split(' ')
|
val numbers = command.substring(1).trim().split(' ')
|
||||||
val address = parseNumber(numbers[0])
|
val address = Assembler.parseNumber(numbers[0])
|
||||||
val values = numbers.drop(1).map { parseNumber(it) }
|
val values = numbers.drop(1).map { Assembler.parseNumber(it) }
|
||||||
values.forEachIndexed { index, i -> bus.write(address+index, i.toShort()) }
|
values.forEachIndexed { index, i -> bus.write(address+index, i.toShort()) }
|
||||||
IVirtualMachine.MonitorCmdResult("ok", "", true)
|
IVirtualMachine.MonitorCmdResult("ok", "", true)
|
||||||
}
|
}
|
||||||
'i' -> {
|
'i' -> {
|
||||||
val addresses = command.substring(1).trim().split(' ')
|
val addresses = command.substring(1).trim().split(' ')
|
||||||
val start = parseNumber(addresses[0])
|
val start = Assembler.parseNumber(addresses[0])
|
||||||
val end = if (addresses.size > 1) parseNumber(addresses[1]) else start+1
|
val end = if (addresses.size > 1) Assembler.parseNumber(addresses[1]) else start+1
|
||||||
val result = mutableListOf<String>()
|
val result = mutableListOf<String>()
|
||||||
for (addr in start until end step 64) {
|
for (addr in start until end step 64) {
|
||||||
result.add("i$${hexW(addr)} "+(0..63).joinToString("") {
|
result.add("i$${hexW(addr)} "+(0..63).joinToString("") {
|
||||||
@ -71,33 +63,41 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
|
|||||||
IVirtualMachine.MonitorCmdResult(result.joinToString("\n"), "", true)
|
IVirtualMachine.MonitorCmdResult(result.joinToString("\n"), "", true)
|
||||||
}
|
}
|
||||||
'$' -> {
|
'$' -> {
|
||||||
val number = parseNumber(command)
|
val number = Assembler.parseNumber(command)
|
||||||
val output = "$${hexW(number)} #$number %${number.toString(2)}"
|
val output = "$${hexW(number)} #$number %${number.toString(2)}"
|
||||||
IVirtualMachine.MonitorCmdResult(output, "", true)
|
IVirtualMachine.MonitorCmdResult(output, "", true)
|
||||||
}
|
}
|
||||||
'#' -> {
|
'#' -> {
|
||||||
val number = parseNumber(command)
|
val number = Assembler.parseNumber(command)
|
||||||
val output = "$${hexW(number)} #$number %${number.toString(2)}"
|
val output = "$${hexW(number)} #$number %${number.toString(2)}"
|
||||||
IVirtualMachine.MonitorCmdResult(output, "", true)
|
IVirtualMachine.MonitorCmdResult(output, "", true)
|
||||||
}
|
}
|
||||||
'%' -> {
|
'%' -> {
|
||||||
val number = parseNumber(command)
|
val number = Assembler.parseNumber(command)
|
||||||
val output = "$${hexW(number)} #$number %${number.toString(2)}"
|
val output = "$${hexW(number)} #$number %${number.toString(2)}"
|
||||||
IVirtualMachine.MonitorCmdResult(output, "", true)
|
IVirtualMachine.MonitorCmdResult(output, "", true)
|
||||||
}
|
}
|
||||||
'g' -> {
|
'g' -> {
|
||||||
val address = parseNumber(command.substring(1))
|
val address = Assembler.parseNumber(command.substring(1))
|
||||||
cpu.regPC = address
|
cpu.regPC = address
|
||||||
IVirtualMachine.MonitorCmdResult("", "", true)
|
IVirtualMachine.MonitorCmdResult("", "", true)
|
||||||
}
|
}
|
||||||
'a' -> {
|
'a' -> {
|
||||||
val parts = command.substring(1).trim().split(' ')
|
val address = 0 // TODO parse from line
|
||||||
assemble(command, parts)
|
val assembler = Assembler(cpu, bus.memoryComponentFor(address), address)
|
||||||
|
val result = assembler.assemble(command.substring(1).trimStart())
|
||||||
|
if(result.success) {
|
||||||
|
val memory = (result.startAddress..result.startAddress+result.numBytes).map { bus[it] }.toTypedArray()
|
||||||
|
val d = disassembler.disassembleOneInstruction(memory, 0, result.startAddress)
|
||||||
|
IVirtualMachine.MonitorCmdResult(d.first, "a$${hexW(result.startAddress+result.numBytes)} ", false)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
IVirtualMachine.MonitorCmdResult(result.error, command, false)
|
||||||
}
|
}
|
||||||
'd' -> {
|
'd' -> {
|
||||||
val addresses = command.substring(1).trim().split(' ')
|
val addresses = command.substring(1).trim().split(' ')
|
||||||
val start = parseNumber(addresses[0])
|
val start = Assembler.parseNumber(addresses[0])
|
||||||
val end = if (addresses.size > 1) parseNumber(addresses[1]) else start
|
val end = if (addresses.size > 1) Assembler.parseNumber(addresses[1]) else start
|
||||||
val memory = (start .. max(0xffff, end+3)).map {bus[it]}.toTypedArray()
|
val memory = (start .. max(0xffff, end+3)).map {bus[it]}.toTypedArray()
|
||||||
val disassem = disassembler.disassemble(memory, 0 .. end-start, start)
|
val disassem = disassembler.disassemble(memory, 0 .. end-start, start)
|
||||||
IVirtualMachine.MonitorCmdResult(disassem.first.joinToString("\n") { "d$it" }, "d$${hexW(disassem.second)}", false)
|
IVirtualMachine.MonitorCmdResult(disassem.first.joinToString("\n") { "d$it" }, "d$${hexW(disassem.second)}", false)
|
||||||
@ -107,164 +107,4 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assemble(command: String, parts: List<String>): IVirtualMachine.MonitorCmdResult {
|
|
||||||
if (parts.size < 2) return IVirtualMachine.MonitorCmdResult("done", "", false)
|
|
||||||
|
|
||||||
val address = parseNumber(parts[0])
|
|
||||||
val mnemonic = parts[1].toLowerCase()
|
|
||||||
when (parts.size) {
|
|
||||||
2 -> {
|
|
||||||
// implied or acc
|
|
||||||
var instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imp)]
|
|
||||||
if (instruction == null) instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Acc)]
|
|
||||||
if (instruction == null) return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
bus.write(address, instruction.toShort())
|
|
||||||
}
|
|
||||||
3 -> {
|
|
||||||
val arg = parts[2]
|
|
||||||
when {
|
|
||||||
arg.startsWith('#') -> {
|
|
||||||
// immediate
|
|
||||||
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imm)] ?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
bus.write(address, instruction.toShort())
|
|
||||||
bus.write(address+1, parseNumber(arg.substring(1), decimalFirst = true).toShort())
|
|
||||||
}
|
|
||||||
arg.startsWith("(") && arg.endsWith(",x)") -> {
|
|
||||||
// indirect X
|
|
||||||
val indAddress = try {
|
|
||||||
parseNumber(arg.substring(1, arg.length-3))
|
|
||||||
} catch (x: NumberFormatException) {
|
|
||||||
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
}
|
|
||||||
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.IzX)] ?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
bus.write(address, instruction.toShort())
|
|
||||||
bus.write(address+1, indAddress.toShort())
|
|
||||||
}
|
|
||||||
arg.startsWith("(") && arg.endsWith("),y") -> {
|
|
||||||
// indirect Y
|
|
||||||
val indAddress = try {
|
|
||||||
parseNumber(arg.substring(1, arg.length-3))
|
|
||||||
} catch (x: NumberFormatException) {
|
|
||||||
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
}
|
|
||||||
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.IzY)] ?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
bus.write(address, instruction.toShort())
|
|
||||||
bus.write(address+1, indAddress.toShort())
|
|
||||||
}
|
|
||||||
arg.endsWith(",x") -> {
|
|
||||||
// indexed X or zpIndexed X
|
|
||||||
val indAddress = try {
|
|
||||||
parseNumber(arg.substring(1, arg.length-2))
|
|
||||||
} catch (x: NumberFormatException) {
|
|
||||||
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
}
|
|
||||||
if (indAddress <= 255) {
|
|
||||||
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.ZpX)] ?: return IVirtualMachine.MonitorCmdResult(
|
|
||||||
"?invalid instruction", command, false)
|
|
||||||
bus.write(address, instruction.toShort())
|
|
||||||
bus.write(address+1, indAddress.toShort())
|
|
||||||
} else {
|
|
||||||
val instruction =
|
|
||||||
instructions[Pair(mnemonic, Cpu6502.AddrMode.AbsX)] ?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
bus.write(address, instruction.toShort())
|
|
||||||
bus.write(address+1, (indAddress and 255).toShort())
|
|
||||||
bus.write(address+2, (indAddress ushr 8).toShort())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arg.endsWith(",y") -> {
|
|
||||||
// indexed Y or zpIndexed Y
|
|
||||||
val indAddress = try {
|
|
||||||
parseNumber(arg.substring(1, arg.length-2))
|
|
||||||
} catch (x: NumberFormatException) {
|
|
||||||
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
}
|
|
||||||
if (indAddress <= 255) {
|
|
||||||
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.ZpY)] ?: return IVirtualMachine.MonitorCmdResult(
|
|
||||||
"?invalid instruction", command, false)
|
|
||||||
bus.write(address, instruction.toShort())
|
|
||||||
bus.write(address+1, indAddress.toShort())
|
|
||||||
} else {
|
|
||||||
val instruction =
|
|
||||||
instructions[Pair(mnemonic, Cpu6502.AddrMode.AbsY)] ?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
bus.write(address, instruction.toShort())
|
|
||||||
bus.write(address+1, (indAddress and 255).toShort())
|
|
||||||
bus.write(address+2, (indAddress ushr 8).toShort())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arg.endsWith(")") -> {
|
|
||||||
// indirect (jmp)
|
|
||||||
val indAddress = try {
|
|
||||||
parseNumber(arg.substring(1, arg.length-1))
|
|
||||||
} catch (x: NumberFormatException) {
|
|
||||||
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
}
|
|
||||||
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Ind)] ?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
bus.write(address, instruction.toShort())
|
|
||||||
bus.write(address+1, (indAddress and 255).toShort())
|
|
||||||
bus.write(address+2, (indAddress ushr 8).toShort())
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val instr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Rel)]
|
|
||||||
if (instr != null) {
|
|
||||||
// relative address
|
|
||||||
val rel = try {
|
|
||||||
parseRelativeToPC(arg, address)
|
|
||||||
} catch (x: NumberFormatException) {
|
|
||||||
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
}
|
|
||||||
bus.write(address, instr.toShort())
|
|
||||||
bus.write(address+1, (rel-address-2 and 255).toShort())
|
|
||||||
} else {
|
|
||||||
// absolute or absZp
|
|
||||||
val absAddress = try {
|
|
||||||
if(arg.startsWith('*')) parseRelativeToPC(arg, address) else parseNumber(arg)
|
|
||||||
} catch (x: NumberFormatException) {
|
|
||||||
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
}
|
|
||||||
val zpInstruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Zp)]
|
|
||||||
if (absAddress <= 255 && zpInstruction!=null) {
|
|
||||||
bus.write(address, zpInstruction.toShort())
|
|
||||||
bus.write(address+1, absAddress.toShort())
|
|
||||||
} else {
|
|
||||||
val absInstr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Abs)] ?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
|
|
||||||
bus.write(address, absInstr.toShort())
|
|
||||||
bus.write(address+1, (absAddress and 255).toShort())
|
|
||||||
bus.write(address+2, (absAddress ushr 8).toShort())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> return IVirtualMachine.MonitorCmdResult("?syntax error", command, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val memory = listOf(bus[address], bus[address+1], bus[address+2]).toTypedArray()
|
|
||||||
val disassem = disassembler.disassembleOneInstruction(memory, 0, address)
|
|
||||||
return IVirtualMachine.MonitorCmdResult(disassem.first, "a$${hexW(disassem.second + address)} ", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseRelativeToPC(relative: String, currentAddress: Int): Int {
|
|
||||||
val rest = relative.substring(1).trim()
|
|
||||||
if(rest.isNotEmpty()) {
|
|
||||||
return when(rest[0]) {
|
|
||||||
'-' -> currentAddress-parseNumber(rest.substring(1))
|
|
||||||
'+' -> currentAddress+parseNumber(rest.substring(1))
|
|
||||||
else -> throw Cpu6502.InstructionError("invalid address syntax")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseNumber(number: String, decimalFirst: Boolean = false): Int {
|
|
||||||
val num = number.trim()
|
|
||||||
if (num.isBlank()) return 0
|
|
||||||
if (decimalFirst && num[0].isDigit()) return num.toInt(10)
|
|
||||||
return when (num[0]) {
|
|
||||||
'$' -> num.substring(1).trimStart().toInt(16)
|
|
||||||
'#' -> num.substring(1).trimStart().toInt(10)
|
|
||||||
'%' -> num.substring(1).trimStart().toInt(2)
|
|
||||||
else -> num.toInt(16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
43
src/test/kotlin/TestAssembler.kt
Normal file
43
src/test/kotlin/TestAssembler.kt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import razorvine.ksim65.*
|
||||||
|
import razorvine.ksim65.components.Ram
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
|
||||||
|
class TestAssembler {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAssembleSingleInstruction() {
|
||||||
|
val cpu = Cpu6502()
|
||||||
|
val ram = Ram(0, 0xffff)
|
||||||
|
val assembler = Assembler(cpu, ram)
|
||||||
|
|
||||||
|
val result = assembler.assemble("${'$'}c000 jmp ${'$'}ea31")
|
||||||
|
assertTrue(result.success)
|
||||||
|
assertEquals("", result.error)
|
||||||
|
assertEquals(0xc000, result.startAddress)
|
||||||
|
assertEquals(3, result.numBytes)
|
||||||
|
assertEquals(0x4c, ram[0xc000])
|
||||||
|
assertEquals(0x31, ram[0xc001])
|
||||||
|
assertEquals(0xea, ram[0xc002])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAssembleMulti() {
|
||||||
|
val cpu = Cpu6502()
|
||||||
|
val ram = Ram(0, 0xffff)
|
||||||
|
val assembler = Assembler(cpu, ram)
|
||||||
|
val result = assembler.assemble("""
|
||||||
|
*=${'$'}a2b3
|
||||||
|
nop
|
||||||
|
jmp ${'$'}ea31
|
||||||
|
bne *-2
|
||||||
|
""".lines())
|
||||||
|
assertEquals("", result.error)
|
||||||
|
assertTrue(result.success)
|
||||||
|
assertEquals(0xa2b3, result.startAddress)
|
||||||
|
assertEquals(6, result.numBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user