diff --git a/src/main/kotlin/razorvine/ksim65/Assembler.kt b/src/main/kotlin/razorvine/ksim65/Assembler.kt new file mode 100644 index 0000000..cd8d45f --- /dev/null +++ b/src/main/kotlin/razorvine/ksim65/Assembler.kt @@ -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): 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: + + "
[]" + " []" + "* =
" + */ + + 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) + } +} diff --git a/src/main/kotlin/razorvine/ksim65/Bus.kt b/src/main/kotlin/razorvine/ksim65/Bus.kt index 79c2b40..43002f4 100644 --- a/src/main/kotlin/razorvine/ksim65/Bus.kt +++ b/src/main/kotlin/razorvine/ksim65/Bus.kt @@ -64,4 +64,8 @@ open class Bus { it[address-it.startAddress] = data } } + + + fun memoryComponentFor(address: Address) = + memComponents.first { it is MemoryComponent && address >= it.startAddress && address <= it.endAddress } as MemoryComponent } diff --git a/src/main/kotlin/razorvine/ksim65/Monitor.kt b/src/main/kotlin/razorvine/ksim65/Monitor.kt index a04e35d..8b1cab4 100644 --- a/src/main/kotlin/razorvine/ksim65/Monitor.kt +++ b/src/main/kotlin/razorvine/ksim65/Monitor.kt @@ -4,14 +4,6 @@ import kotlin.math.max 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) 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(' ') if (parts.size != 3) IVirtualMachine.MonitorCmdResult("?syntax error", command, false) else { - val start = parseNumber(parts[0]) - val end = parseNumber(parts[1]) - val value = parseNumber(parts[2]).toShort() + val start = Assembler.parseNumber(parts[0]) + val end = Assembler.parseNumber(parts[1]) + val value = Assembler.parseNumber(parts[2]).toShort() for (addr in start..end) { bus.write(addr, value) } @@ -37,8 +29,8 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) { } 'm' -> { val addresses = command.substring(1).trim().split(' ') - val start = parseNumber(addresses[0]) - val end = if (addresses.size > 1) parseNumber(addresses[1]) else start+1 + val start = Assembler.parseNumber(addresses[0]) + val end = if (addresses.size > 1) Assembler.parseNumber(addresses[1]) else start+1 val result = mutableListOf() for (addr in start until end step 16) { 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' -> { val numbers = command.substring(1).trim().split(' ') - val address = parseNumber(numbers[0]) - val values = numbers.drop(1).map { parseNumber(it) } + val address = Assembler.parseNumber(numbers[0]) + val values = numbers.drop(1).map { Assembler.parseNumber(it) } values.forEachIndexed { index, i -> bus.write(address+index, i.toShort()) } IVirtualMachine.MonitorCmdResult("ok", "", true) } 'i' -> { val addresses = command.substring(1).trim().split(' ') - val start = parseNumber(addresses[0]) - val end = if (addresses.size > 1) parseNumber(addresses[1]) else start+1 + val start = Assembler.parseNumber(addresses[0]) + val end = if (addresses.size > 1) Assembler.parseNumber(addresses[1]) else start+1 val result = mutableListOf() for (addr in start until end step 64) { 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) } '$' -> { - val number = parseNumber(command) + val number = Assembler.parseNumber(command) val output = "$${hexW(number)} #$number %${number.toString(2)}" IVirtualMachine.MonitorCmdResult(output, "", true) } '#' -> { - val number = parseNumber(command) + val number = Assembler.parseNumber(command) val output = "$${hexW(number)} #$number %${number.toString(2)}" IVirtualMachine.MonitorCmdResult(output, "", true) } '%' -> { - val number = parseNumber(command) + val number = Assembler.parseNumber(command) val output = "$${hexW(number)} #$number %${number.toString(2)}" IVirtualMachine.MonitorCmdResult(output, "", true) } 'g' -> { - val address = parseNumber(command.substring(1)) + val address = Assembler.parseNumber(command.substring(1)) cpu.regPC = address IVirtualMachine.MonitorCmdResult("", "", true) } 'a' -> { - val parts = command.substring(1).trim().split(' ') - assemble(command, parts) + val address = 0 // TODO parse from line + 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' -> { val addresses = command.substring(1).trim().split(' ') - val start = parseNumber(addresses[0]) - val end = if (addresses.size > 1) parseNumber(addresses[1]) else start + val start = Assembler.parseNumber(addresses[0]) + val end = if (addresses.size > 1) Assembler.parseNumber(addresses[1]) else start val memory = (start .. max(0xffff, end+3)).map {bus[it]}.toTypedArray() val disassem = disassembler.disassemble(memory, 0 .. end-start, start) 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): 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) - } - } } diff --git a/src/test/kotlin/TestAssembler.kt b/src/test/kotlin/TestAssembler.kt new file mode 100644 index 0000000..2d33b6f --- /dev/null +++ b/src/test/kotlin/TestAssembler.kt @@ -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) + } + +} + +