package net.razorvine.ksim65.components // TODO: implement the illegal opcodes, see http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes // TODO: add the optional additional cycles to certain instructions and addressing modes open class Cpu6502(private val stopOnBrk: Boolean) : BusComponent() { var tracing: Boolean = false var totalCycles: Long = 0 private set class InstructionError(msg: String) : RuntimeException(msg) companion object { const val NMI_vector = 0xfffa const val RESET_vector = 0xfffc const val IRQ_vector = 0xfffe const val resetCycles = 8 fun hexW(number: Address, allowSingleByte: Boolean = false): String { val msb = number ushr 8 val lsb = number and 0xff return if (msb == 0 && allowSingleByte) hexB(lsb) else hexB(msb) + hexB( lsb ) } private const val hexdigits = "0123456789abcdef" fun hexB(number: Short): String = hexB(number.toInt()) fun hexB(number: Int): String { val loNibble = number and 15 val hiNibble = number ushr 4 return hexdigits[hiNibble].toString() + hexdigits[loNibble] } } enum class AddrMode { Imp, Acc, Imm, Zp, ZpX, ZpY, Rel, Abs, AbsX, AbsY, Ind, IzX, IzY } class Instruction(val opcode: UByte, val mnemonic: String, val mode: AddrMode, val cycles: Int, val execute: () -> Unit) { override fun toString(): String { return "[${hexB(opcode)}: $mnemonic $mode]" } } class StatusRegister( var C: Boolean = false, var Z: Boolean = false, var I: Boolean = false, var D: Boolean = false, var B: Boolean = false, var V: Boolean = false, var N: Boolean = false ) { fun asByte(): UByte { return (0b00100000 or (if (N) 0b10000000 else 0) or (if (V) 0b01000000 else 0) or (if (B) 0b00010000 else 0) or (if (D) 0b00001000 else 0) or (if (I) 0b00000100 else 0) or (if (Z) 0b00000010 else 0) or (if (C) 0b00000001 else 0) ).toShort() } fun fromByte(byte: Int) { N = (byte and 0b10000000) != 0 V = (byte and 0b01000000) != 0 B = (byte and 0b00010000) != 0 D = (byte and 0b00001000) != 0 I = (byte and 0b00000100) != 0 Z = (byte and 0b00000010) != 0 C = (byte and 0b00000001) != 0 } override fun toString(): String { return asByte().toString(2).padStart(8, '0') } override fun hashCode(): Int = asByte().toInt() override fun equals(other: Any?): Boolean { if (other !is StatusRegister) return false return asByte() == other.asByte() } } var instrCycles: Int = 0 var A: Int = 0 var X: Int = 0 var Y: Int = 0 var SP: Int = 0 var PC: Address = 0 val Status = StatusRegister() var currentOpcode: Int = 0 private lateinit var currentInstruction: Instruction // has an interrupt been requested? private var pendingInterrupt: Pair? = null // data byte from the instruction (only set when addr.mode is Accumulator, Immediate or Implied) private var fetchedData: Int = 0 // all other addressing modes yield a fetched memory address private var fetchedAddress: Address = 0 private val addressingModes = mapOf Unit>( AddrMode.Imp to ::amImp, AddrMode.Acc to ::amAcc, AddrMode.Imm to ::amImm, AddrMode.Zp to ::amZp, AddrMode.ZpX to ::amZpx, AddrMode.ZpY to ::amZpy, AddrMode.Rel to ::amRel, AddrMode.Abs to ::amAbs, AddrMode.AbsX to ::amAbsx, AddrMode.AbsY to ::amAbsy, AddrMode.Ind to ::amInd, AddrMode.IzX to ::amIzx, AddrMode.IzY to ::amIzy ) private val breakpoints = mutableMapOf Unit>() fun breakpoint(address: Address, action: (cpu: Cpu6502, pc: Address) -> Unit) { breakpoints[address] = action } fun disassemble(component: MemoryComponent, from: Address, to: Address) = disassemble(component.cloneContents(), component.startAddress, from, to) fun disassemble(memory: Array, baseAddress: Address, from: Address, to: Address): List { var address = from - baseAddress val spacing1 = " " val spacing2 = " " val spacing3 = " " val result = mutableListOf() while (address <= (to - baseAddress)) { val byte = memory[address] var line = "\$${hexW(address)} ${hexB( byte )} " address++ val opcode = opcodes[byte.toInt()] when (opcode.mode) { AddrMode.Acc -> { line += "$spacing1 ${opcode.mnemonic} a" } AddrMode.Imp -> { line += "$spacing1 ${opcode.mnemonic}" } AddrMode.Imm -> { val value = memory[address++] line += "${hexB(value)} $spacing2 ${opcode.mnemonic} #\$${hexB( value )}" } AddrMode.Zp -> { val zpAddr = memory[address++] line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB( zpAddr )}" } AddrMode.ZpX -> { val zpAddr = memory[address++] line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB( zpAddr )},x" } AddrMode.ZpY -> { val zpAddr = memory[address++] line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB( zpAddr )},y" } AddrMode.Rel -> { val rel = memory[address++] val target = if (rel <= 0x7f) address + rel else address - (256 - rel) line += "${hexB(rel)} $spacing2 ${opcode.mnemonic} \$${hexW( target, true )}" } AddrMode.Abs -> { val lo = memory[address++] val hi = memory[address++] val absAddr = lo.toInt() or (hi.toInt() shl 8) line += "${hexB(lo)} ${hexB( hi )} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)}" } AddrMode.AbsX -> { val lo = memory[address++] val hi = memory[address++] val absAddr = lo.toInt() or (hi.toInt() shl 8) line += "${hexB(lo)} ${hexB( hi )} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},x" } AddrMode.AbsY -> { val lo = memory[address++] val hi = memory[address++] val absAddr = lo.toInt() or (hi.toInt() shl 8) line += "${hexB(lo)} ${hexB( hi )} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},y" } AddrMode.Ind -> { val lo = memory[address++] val hi = memory[address++] val indirectAddr = lo.toInt() or (hi.toInt() shl 8) line += "${hexB(lo)} ${hexB( hi )} $spacing3 ${opcode.mnemonic} (\$${hexW( indirectAddr )})" } AddrMode.IzX -> { val zpAddr = memory[address++] line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB( zpAddr )},x)" } AddrMode.IzY -> { val zpAddr = memory[address++] line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB( zpAddr )}),y" } } result.add(line) } return result } override fun reset() { SP = 0xfd PC = readWord(RESET_vector) A = 0 X = 0 Y = 0 Status.C = false Status.Z = false Status.I = true Status.D = false Status.B = false Status.V = false Status.N = false instrCycles = resetCycles // a reset takes time as well currentOpcode = 0 currentInstruction = opcodes[0] } override fun clock() { if (instrCycles == 0) { if (pendingInterrupt != null) { // NMI or IRQ interrupt. // handled by the BRK instruction logic. currentOpcode = 0 currentInstruction = opcodes[0] } else { // no interrupt, continue with next instruction currentOpcode = read(PC) currentInstruction = opcodes[currentOpcode] if (tracing) printState() breakpoints[PC]?.let { val oldPC = PC val oldOpcode = currentOpcode it(this, PC) if (PC != oldPC) return clock() if (oldOpcode != currentOpcode) currentInstruction = opcodes[currentOpcode] } if (currentOpcode == 0 && stopOnBrk) { throw InstructionError( "stopped on BRK instruction at ${hexW( PC )}" ) } } PC++ instrCycles = currentInstruction.cycles addressingModes.getValue(currentInstruction.mode)() currentInstruction.execute() } instrCycles-- totalCycles++ } fun step() { // step a whole instruction while (instrCycles > 0) clock() // remaining instruction subcycles from the previous instruction clock() // the actual instruction execution cycle while (instrCycles > 0) clock() // instruction subcycles } fun nmi(source: BusComponent) { pendingInterrupt = Pair(true, source) } fun irq(source: BusComponent) { if (!Status.I) pendingInterrupt = Pair(false, source) } fun printState() { println("cycle:$totalCycles - pc=${hexW(PC)} " + "A=${hexB(A)} " + "X=${hexB(X)} " + "Y=${hexB(Y)} " + "SP=${hexB(SP)} " + " n=" + (if (Status.N) "1" else "0") + " v=" + (if (Status.V) "1" else "0") + " b=" + (if (Status.B) "1" else "0") + " d=" + (if (Status.D) "1" else "0") + " i=" + (if (Status.I) "1" else "0") + " z=" + (if (Status.Z) "1" else "0") + " c=" + (if (Status.C) "1" else "0") + " icycles=$instrCycles instr=${hexB(currentOpcode)}:${currentInstruction.mnemonic}" ) } private fun amImp() { fetchedData = A } private fun amAcc() { fetchedData = A } private fun amImm() { fetchedData = readPc() } private fun amZp() { fetchedAddress = readPc() } private fun amZpx() { // note: zeropage index will not leave Zp when page boundary is crossed fetchedAddress = (readPc() + X) and 0xff } private fun amZpy() { // note: zeropage index will not leave Zp when page boundary is crossed fetchedAddress = (readPc() + Y) and 0xff } private fun amRel() { val relative = readPc() fetchedAddress = if (relative >= 0x80) { PC - (256 - relative) and 0xffff } else PC + relative and 0xffff } private fun amAbs() { val lo = readPc() val hi = readPc() fetchedAddress = lo or (hi shl 8) } private fun amAbsx() { val lo = readPc() val hi = readPc() fetchedAddress = X + (lo or (hi shl 8)) and 0xffff } private fun amAbsy() { val lo = readPc() val hi = readPc() fetchedAddress = Y + (lo or (hi shl 8)) and 0xffff } private fun amInd() { // not able to fetch an adress which crosses the page boundary (6502, fixed in 65C02) var lo = readPc() var hi = readPc() fetchedAddress = lo or (hi shl 8) if (lo == 0xff) { // emulate bug lo = read(fetchedAddress) hi = read(fetchedAddress and 0xff00) } else { // normal behavior lo = read(fetchedAddress) hi = read(fetchedAddress + 1) } fetchedAddress = lo or (hi shl 8) } private fun amIzx() { // note: not able to fetch an adress which crosses the page boundary fetchedAddress = readPc() val lo = read((fetchedAddress + X) and 0xff) val hi = read((fetchedAddress + X + 1) and 0xff) fetchedAddress = lo or (hi shl 8) } private fun amIzy() { // note: not able to fetch an adress which crosses the page boundary fetchedAddress = readPc() val lo = read(fetchedAddress) val hi = read((fetchedAddress + 1) and 0xff) fetchedAddress = Y + (lo or (hi shl 8)) and 0xffff } private fun getFetched() = if (currentInstruction.mode == AddrMode.Imm || currentInstruction.mode == AddrMode.Acc || currentInstruction.mode == AddrMode.Imp ) fetchedData else read(fetchedAddress) private fun readPc(): Int = bus.read(PC++).toInt() private fun pushStackAddr(address: Address) { val lo = address and 0xff val hi = (address ushr 8) pushStack(hi) pushStack(lo) } private fun pushStack(status: StatusRegister) { pushStack(status.asByte().toInt()) } private fun pushStack(data: Int) { write(SP or 0x0100, data) SP = (SP - 1) and 0xff } private fun popStack(): Int { SP = (SP + 1) and 0xff return read(SP or 0x0100) } private fun popStackAddr(): Address { val lo = popStack() val hi = popStack() return lo or (hi shl 8) } private fun read(address: Address): Int = bus.read(address).toInt() private fun readWord(address: Address): Int = bus.read(address).toInt() or (bus.read(address + 1).toInt() shl 8) private fun write(address: Address, data: Int) = bus.write(address, data.toShort()) // opcodes table from http://www.oxyron.de/html/opcodes02.html private val opcodes = listOf( Instruction(0x00, "brk", AddrMode.Imp, 7, ::iBrk), Instruction(0x01, "ora", AddrMode.IzX, 6, ::iOra), Instruction(0x02, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0x03, "slo", AddrMode.IzX, 8, ::iSlo), Instruction(0x04, "nop", AddrMode.Zp, 3, ::iNop), Instruction(0x05, "ora", AddrMode.Zp, 3, ::iOra), Instruction(0x06, "asl", AddrMode.Zp, 5, ::iAsl), Instruction(0x07, "slo", AddrMode.Zp, 5, ::iSlo), Instruction(0x08, "php", AddrMode.Imp, 3, ::iPhp), Instruction(0x09, "ora", AddrMode.Imm, 2, ::iOra), Instruction(0x0a, "asl", AddrMode.Acc, 2, ::iAsl), Instruction(0x0b, "anc", AddrMode.Imm, 2, ::iAnc), Instruction(0x0c, "nop", AddrMode.Abs, 4, ::iNop), Instruction(0x0d, "ora", AddrMode.Abs, 4, ::iOra), Instruction(0x0e, "asl", AddrMode.Abs, 6, ::iAsl), Instruction(0x0f, "slo", AddrMode.Abs, 6, ::iSlo), Instruction(0x10, "bpl", AddrMode.Rel, 2, ::iBpl), Instruction(0x11, "ora", AddrMode.IzY, 5, ::iOra), Instruction(0x12, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0x13, "slo", AddrMode.IzY, 6, ::iSlo), Instruction(0x14, "nop", AddrMode.ZpX, 4, ::iNop), Instruction(0x15, "ora", AddrMode.ZpX, 4, ::iOra), Instruction(0x16, "asl", AddrMode.ZpX, 6, ::iAsl), Instruction(0x17, "slo", AddrMode.ZpX, 6, ::iSlo), Instruction(0x18, "clc", AddrMode.Imp, 2, ::iClc), Instruction(0x19, "ora", AddrMode.AbsY, 4, ::iOra), Instruction(0x1a, "nop", AddrMode.Imp, 2, ::iNop), Instruction(0x1b, "slo", AddrMode.AbsY, 7, ::iSlo), Instruction(0x1c, "nop", AddrMode.AbsX, 4, ::iNop), Instruction(0x1d, "ora", AddrMode.AbsX, 4, ::iOra), Instruction(0x1e, "asl", AddrMode.AbsX, 7, ::iAsl), Instruction(0x1f, "slo", AddrMode.AbsX, 7, ::iSlo), Instruction(0x20, "jsr", AddrMode.Abs, 6, ::iJsr), Instruction(0x21, "and", AddrMode.IzX, 6, ::iAnd), Instruction(0x22, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0x23, "rla", AddrMode.IzX, 8, ::iRla), Instruction(0x24, "bit", AddrMode.Zp, 3, ::iBit), Instruction(0x25, "and", AddrMode.Zp, 3, ::iAnd), Instruction(0x26, "rol", AddrMode.Zp, 5, ::iRol), Instruction(0x27, "rla", AddrMode.Zp, 5, ::iRla), Instruction(0x28, "plp", AddrMode.Imp, 4, ::iPlp), Instruction(0x29, "and", AddrMode.Imm, 2, ::iAnd), Instruction(0x2a, "rol", AddrMode.Acc, 2, ::iRol), Instruction(0x2b, "anc", AddrMode.Imm, 2, ::iAnc), Instruction(0x2c, "bit", AddrMode.Abs, 4, ::iBit), Instruction(0x2d, "and", AddrMode.Abs, 4, ::iAnd), Instruction(0x2e, "rol", AddrMode.Abs, 6, ::iRol), Instruction(0x2f, "rla", AddrMode.Abs, 6, ::iRla), Instruction(0x30, "bmi", AddrMode.Rel, 2, ::iBmi), Instruction(0x31, "and", AddrMode.IzY, 5, ::iAnd), Instruction(0x32, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0x33, "rla", AddrMode.IzY, 8, ::iRla), Instruction(0x34, "nop", AddrMode.ZpX, 4, ::iNop), Instruction(0x35, "and", AddrMode.ZpX, 4, ::iAnd), Instruction(0x36, "rol", AddrMode.ZpX, 6, ::iRol), Instruction(0x37, "rla", AddrMode.ZpX, 6, ::iRla), Instruction(0x38, "sec", AddrMode.Imp, 2, ::iSec), Instruction(0x39, "and", AddrMode.AbsY, 4, ::iAnd), Instruction(0x3a, "nop", AddrMode.Imp, 2, ::iNop), Instruction(0x3b, "rla", AddrMode.AbsY, 7, ::iRla), Instruction(0x3c, "nop", AddrMode.AbsX, 4, ::iNop), Instruction(0x3d, "and", AddrMode.AbsX, 4, ::iAnd), Instruction(0x3e, "rol", AddrMode.AbsX, 7, ::iRol), Instruction(0x3f, "rla", AddrMode.AbsX, 7, ::iRla), Instruction(0x40, "rti", AddrMode.Imp, 6, ::iRti), Instruction(0x41, "eor", AddrMode.IzX, 6, ::iEor), Instruction(0x42, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0x43, "sre", AddrMode.IzX, 8, ::iSre), Instruction(0x44, "nop", AddrMode.Zp, 3, ::iNop), Instruction(0x45, "eor", AddrMode.Zp, 3, ::iEor), Instruction(0x46, "lsr", AddrMode.Zp, 5, ::iLsr), Instruction(0x47, "sre", AddrMode.Zp, 5, ::iSre), Instruction(0x48, "pha", AddrMode.Imp, 3, ::iPha), Instruction(0x49, "eor", AddrMode.Imm, 2, ::iEor), Instruction(0x4a, "lsr", AddrMode.Acc, 2, ::iLsr), Instruction(0x4b, "alr", AddrMode.Imm, 2, ::iAlr), Instruction(0x4c, "jmp", AddrMode.Abs, 3, ::iJmp), Instruction(0x4d, "eor", AddrMode.Abs, 4, ::iEor), Instruction(0x4e, "lsr", AddrMode.Abs, 6, ::iLsr), Instruction(0x4f, "sre", AddrMode.Abs, 6, ::iSre), Instruction(0x50, "bvc", AddrMode.Rel, 2, ::iBvc), Instruction(0x51, "eor", AddrMode.IzY, 5, ::iEor), Instruction(0x52, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0x53, "sre", AddrMode.IzY, 8, ::iSre), Instruction(0x54, "nop", AddrMode.ZpX, 4, ::iNop), Instruction(0x55, "eor", AddrMode.ZpX, 4, ::iEor), Instruction(0x56, "lsr", AddrMode.ZpX, 6, ::iLsr), Instruction(0x57, "sre", AddrMode.ZpX, 6, ::iSre), Instruction(0x58, "cli", AddrMode.Imp, 2, ::iCli), Instruction(0x59, "eor", AddrMode.AbsY, 4, ::iEor), Instruction(0x5a, "nop", AddrMode.Imp, 2, ::iNop), Instruction(0x5b, "sre", AddrMode.AbsY, 7, ::iSre), Instruction(0x5c, "nop", AddrMode.AbsX, 4, ::iNop), Instruction(0x5d, "eor", AddrMode.AbsX, 4, ::iEor), Instruction(0x5e, "lsr", AddrMode.AbsX, 7, ::iLsr), Instruction(0x5f, "sre", AddrMode.AbsX, 7, ::iSre), Instruction(0x60, "rts", AddrMode.Imp, 6, ::iRts), Instruction(0x61, "adc", AddrMode.IzX, 6, ::iAdc), Instruction(0x62, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0x63, "rra", AddrMode.IzX, 8, ::iRra), Instruction(0x64, "nop", AddrMode.Zp, 3, ::iNop), Instruction(0x65, "adc", AddrMode.Zp, 3, ::iAdc), Instruction(0x66, "ror", AddrMode.Zp, 5, ::iRor), Instruction(0x67, "rra", AddrMode.Zp, 5, ::iRra), Instruction(0x68, "pla", AddrMode.Imp, 4, ::iPla), Instruction(0x69, "adc", AddrMode.Imm, 2, ::iAdc), Instruction(0x6a, "ror", AddrMode.Acc, 2, ::iRor), Instruction(0x6b, "arr", AddrMode.Imm, 2, ::iArr), Instruction(0x6c, "jmp", AddrMode.Ind, 5, ::iJmp), Instruction(0x6d, "adc", AddrMode.Abs, 4, ::iAdc), Instruction(0x6e, "ror", AddrMode.Abs, 6, ::iRor), Instruction(0x6f, "rra", AddrMode.Abs, 6, ::iRra), Instruction(0x70, "bvs", AddrMode.Rel, 2, ::iBvs), Instruction(0x71, "adc", AddrMode.IzY, 5, ::iAdc), Instruction(0x72, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0x73, "rra", AddrMode.IzY, 8, ::iRra), Instruction(0x74, "nop", AddrMode.ZpX, 4, ::iNop), Instruction(0x75, "adc", AddrMode.ZpX, 4, ::iAdc), Instruction(0x76, "ror", AddrMode.ZpX, 6, ::iRor), Instruction(0x77, "rra", AddrMode.ZpX, 6, ::iRra), Instruction(0x78, "sei", AddrMode.Imp, 2, ::iSei), Instruction(0x79, "adc", AddrMode.AbsY, 4, ::iAdc), Instruction(0x7a, "nop", AddrMode.Imp, 2, ::iNop), Instruction(0x7b, "rra", AddrMode.AbsY, 7, ::iRra), Instruction(0x7c, "nop", AddrMode.AbsX, 4, ::iNop), Instruction(0x7d, "adc", AddrMode.AbsX, 4, ::iAdc), Instruction(0x7e, "ror", AddrMode.AbsX, 7, ::iRor), Instruction(0x7f, "rra", AddrMode.AbsX, 7, ::iRra), Instruction(0x80, "nop", AddrMode.Imm, 2, ::iNop), Instruction(0x81, "sta", AddrMode.IzX, 6, ::iSta), Instruction(0x82, "nop", AddrMode.Imm, 2, ::iNop), Instruction(0x83, "sax", AddrMode.IzX, 6, ::iSax), Instruction(0x84, "sty", AddrMode.Zp, 3, ::iSty), Instruction(0x85, "sta", AddrMode.Zp, 3, ::iSta), Instruction(0x86, "stx", AddrMode.Zp, 3, ::iStx), Instruction(0x87, "sax", AddrMode.Zp, 3, ::iSax), Instruction(0x88, "dey", AddrMode.Imp, 2, ::iDey), Instruction(0x89, "nop", AddrMode.Imm, 2, ::iNop), Instruction(0x8a, "txa", AddrMode.Imp, 2, ::iTxa), Instruction(0x8b, "xaa", AddrMode.Imm, 2, ::iXaa), Instruction(0x8c, "sty", AddrMode.Abs, 4, ::iSty), Instruction(0x8d, "sta", AddrMode.Abs, 4, ::iSta), Instruction(0x8e, "stx", AddrMode.Abs, 4, ::iStx), Instruction(0x8f, "sax", AddrMode.Abs, 4, ::iSax), Instruction(0x90, "bcc", AddrMode.Rel, 2, ::iBcc), Instruction(0x91, "sta", AddrMode.IzY, 6, ::iSta), Instruction(0x92, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0x93, "ahx", AddrMode.IzY, 6, ::iAhx), Instruction(0x94, "sty", AddrMode.ZpX, 4, ::iSty), Instruction(0x95, "sta", AddrMode.ZpX, 4, ::iSta), Instruction(0x96, "stx", AddrMode.ZpY, 4, ::iStx), Instruction(0x97, "sax", AddrMode.ZpY, 4, ::iSax), Instruction(0x98, "tya", AddrMode.Imp, 2, ::iTya), Instruction(0x99, "sta", AddrMode.AbsY, 5, ::iSta), Instruction(0x9a, "txs", AddrMode.Imp, 2, ::iTxs), Instruction(0x9b, "tas", AddrMode.AbsY, 5, ::iTas), Instruction(0x9c, "shy", AddrMode.AbsX, 5, ::iShy), Instruction(0x9d, "sta", AddrMode.AbsX, 5, ::iSta), Instruction(0x9e, "shx", AddrMode.AbsY, 5, ::iShx), Instruction(0x9f, "ahx", AddrMode.AbsY, 5, ::iAhx), Instruction(0xa0, "ldy", AddrMode.Imm, 2, ::iLdy), Instruction(0xa1, "lda", AddrMode.IzX, 6, ::iLda), Instruction(0xa2, "ldx", AddrMode.Imm, 2, ::iLdx), Instruction(0xa3, "lax", AddrMode.IzX, 6, ::iLax), Instruction(0xa4, "ldy", AddrMode.Zp, 3, ::iLdy), Instruction(0xa5, "lda", AddrMode.Zp, 3, ::iLda), Instruction(0xa6, "ldx", AddrMode.Zp, 3, ::iLdx), Instruction(0xa7, "lax", AddrMode.Zp, 3, ::iLax), Instruction(0xa8, "tay", AddrMode.Imp, 2, ::iTay), Instruction(0xa9, "lda", AddrMode.Imm, 2, ::iLda), Instruction(0xaa, "tax", AddrMode.Imp, 2, ::iTax), Instruction(0xab, "lax", AddrMode.Imm, 2, ::iLax), Instruction(0xac, "ldy", AddrMode.Abs, 4, ::iLdy), Instruction(0xad, "lda", AddrMode.Abs, 4, ::iLda), Instruction(0xae, "ldx", AddrMode.Abs, 4, ::iLdx), Instruction(0xaf, "lax", AddrMode.Abs, 4, ::iLax), Instruction(0xb0, "bcs", AddrMode.Rel, 2, ::iBcs), Instruction(0xb1, "lda", AddrMode.IzY, 5, ::iLda), Instruction(0xb2, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0xb3, "lax", AddrMode.IzY, 5, ::iLax), Instruction(0xb4, "ldy", AddrMode.ZpX, 4, ::iLdy), Instruction(0xb5, "lda", AddrMode.ZpX, 4, ::iLda), Instruction(0xb6, "ldx", AddrMode.ZpY, 4, ::iLdx), Instruction(0xb7, "lax", AddrMode.ZpY, 4, ::iLax), Instruction(0xb8, "clv", AddrMode.Imp, 2, ::iClv), Instruction(0xb9, "lda", AddrMode.AbsY, 4, ::iLda), Instruction(0xba, "tsx", AddrMode.Imp, 2, ::iTsx), Instruction(0xbb, "las", AddrMode.AbsY, 4, ::iLas), Instruction(0xbc, "ldy", AddrMode.AbsX, 4, ::iLdy), Instruction(0xbd, "lda", AddrMode.AbsX, 4, ::iLda), Instruction(0xbe, "ldx", AddrMode.AbsY, 4, ::iLdx), Instruction(0xbf, "lax", AddrMode.AbsY, 4, ::iLax), Instruction(0xc0, "cpy", AddrMode.Imm, 2, ::iCpy), Instruction(0xc1, "cmp", AddrMode.IzX, 6, ::iCmp), Instruction(0xc2, "nop", AddrMode.Imm, 2, ::iNop), Instruction(0xc3, "dcp", AddrMode.IzX, 8, ::iDcp), Instruction(0xc4, "cpy", AddrMode.Zp, 3, ::iCpy), Instruction(0xc5, "cmp", AddrMode.Zp, 3, ::iCmp), Instruction(0xc6, "dec", AddrMode.Zp, 5, ::iDec), Instruction(0xc7, "dcp", AddrMode.Zp, 5, ::iDcp), Instruction(0xc8, "iny", AddrMode.Imp, 2, ::iIny), Instruction(0xc9, "cmp", AddrMode.Imm, 2, ::iCmp), Instruction(0xca, "dex", AddrMode.Imp, 2, ::iDex), Instruction(0xcb, "axs", AddrMode.Imm, 2, ::iAxs), Instruction(0xcc, "cpy", AddrMode.Abs, 4, ::iCpy), Instruction(0xcd, "cmp", AddrMode.Abs, 4, ::iCmp), Instruction(0xce, "dec", AddrMode.Abs, 6, ::iDec), Instruction(0xcf, "dcp", AddrMode.Abs, 6, ::iDcp), Instruction(0xd0, "bne", AddrMode.Rel, 2, ::iBne), Instruction(0xd1, "cmp", AddrMode.IzY, 5, ::iCmp), Instruction(0xd2, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0xd3, "dcp", AddrMode.IzY, 8, ::iDcp), Instruction(0xd4, "nop", AddrMode.ZpX, 4, ::iNop), Instruction(0xd5, "cmp", AddrMode.ZpX, 4, ::iCmp), Instruction(0xd6, "dec", AddrMode.ZpX, 6, ::iDec), Instruction(0xd7, "dcp", AddrMode.ZpX, 6, ::iDcp), Instruction(0xd8, "cld", AddrMode.Imp, 2, ::iCld), Instruction(0xd9, "cmp", AddrMode.AbsY, 4, ::iCmp), Instruction(0xda, "nop", AddrMode.Imp, 2, ::iNop), Instruction(0xdb, "dcp", AddrMode.AbsY, 7, ::iDcp), Instruction(0xdc, "nop", AddrMode.AbsX, 4, ::iNop), Instruction(0xdd, "cmp", AddrMode.AbsX, 4, ::iCmp), Instruction(0xde, "dec", AddrMode.AbsX, 7, ::iDec), Instruction(0xdf, "dcp", AddrMode.AbsX, 7, ::iDcp), Instruction(0xe0, "cpx", AddrMode.Imm, 2, ::iCpx), Instruction(0xe1, "sbc", AddrMode.IzX, 6, ::iSbc), Instruction(0xe2, "nop", AddrMode.Imm, 2, ::iNop), Instruction(0xe3, "isc", AddrMode.IzX, 8, ::iIsc), Instruction(0xe4, "cpx", AddrMode.Zp, 3, ::iCpx), Instruction(0xe5, "sbc", AddrMode.Zp, 3, ::iSbc), Instruction(0xe6, "inc", AddrMode.Zp, 5, ::iInc), Instruction(0xe7, "isc", AddrMode.Zp, 5, ::iIsc), Instruction(0xe8, "inx", AddrMode.Imp, 2, ::iInx), Instruction(0xe9, "sbc", AddrMode.Imm, 2, ::iSbc), Instruction(0xea, "nop", AddrMode.Imp, 2, ::iNop), Instruction(0xeb, "sbc", AddrMode.Imm, 2, ::iSbc), Instruction(0xec, "cpx", AddrMode.Abs, 4, ::iCpx), Instruction(0xed, "sbc", AddrMode.Abs, 4, ::iSbc), Instruction(0xee, "inc", AddrMode.Abs, 6, ::iInc), Instruction(0xef, "isc", AddrMode.Abs, 6, ::iIsc), Instruction(0xf0, "beq", AddrMode.Rel, 2, ::iBeq), Instruction(0xf1, "sbc", AddrMode.IzY, 5, ::iSbc), Instruction(0xf2, "???", AddrMode.Imp, 0, ::iInvalid), Instruction(0xf3, "isc", AddrMode.IzY, 8, ::iIsc), Instruction(0xf4, "nop", AddrMode.ZpX, 4, ::iNop), Instruction(0xf5, "sbc", AddrMode.ZpX, 4, ::iSbc), Instruction(0xf6, "inc", AddrMode.ZpX, 6, ::iInc), Instruction(0xf7, "isc", AddrMode.ZpX, 6, ::iIsc), Instruction(0xf8, "sed", AddrMode.Imp, 2, ::iSed), Instruction(0xf9, "sbc", AddrMode.AbsY, 4, ::iSbc), Instruction(0xfa, "nop", AddrMode.Imp, 2, ::iNop), Instruction(0xfb, "isc", AddrMode.AbsY, 7, ::iIsc), Instruction(0xfc, "nop", AddrMode.AbsX, 4, ::iNop), Instruction(0xfd, "sbc", AddrMode.AbsX, 4, ::iSbc), Instruction(0xfe, "inc", AddrMode.AbsX, 7, ::iInc), Instruction(0xff, "isc", AddrMode.AbsX, 7, ::iIsc ) ).toTypedArray() private fun iInvalid() { throw InstructionError( "invalid instruction encountered: opcode=${hexB( currentOpcode )} instr=${currentInstruction.mnemonic}" ) } // official instructions private fun iAdc() { val operand = getFetched() if (Status.D) { // BCD add // see http://www.6502.org/tutorials/decimal_mode.html // and http://nesdev.com/6502.txt // and https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/6510core.c#l598 // (the implementation below is based on the code used by Vice) var tmp = (A and 0xf) + (operand and 0xf) + (if (Status.C) 1 else 0) if (tmp > 9) tmp += 6 tmp = if (tmp <= 0x0f) { (tmp and 0xf) + (A and 0xf0) + (operand and 0xf0) } else { (tmp and 0xf) + (A and 0xf0) + (operand and 0xf0) + 0x10 } Status.Z = A + operand + (if (Status.C) 1 else 0) and 0xff == 0 Status.N = tmp and 0b10000000 != 0 Status.V = (A xor tmp) and 0x80 != 0 && (A xor operand) and 0b10000000 == 0 if (tmp and 0x1f0 > 0x90) tmp += 0x60 Status.C = tmp > 0xf0 // original: (tmp and 0xff0) > 0xf0 A = tmp and 0xff } else { // normal add val tmp = operand + A + if (Status.C) 1 else 0 Status.N = (tmp and 0b10000000) != 0 Status.Z = (tmp and 0xff) == 0 Status.V = (A xor operand).inv() and (A xor tmp) and 0b10000000 != 0 Status.C = tmp > 0xff A = tmp and 0xff } } private fun iAnd() { A = A and getFetched() Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } private fun iAsl() { if (currentInstruction.mode == AddrMode.Acc) { Status.C = (A and 0b10000000) != 0 A = (A shl 1) and 0xff Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } else { val data = read(fetchedAddress) Status.C = (data and 0b10000000) != 0 val shifted = (data shl 1) and 0xff write(fetchedAddress, shifted) Status.Z = shifted == 0 Status.N = (shifted and 0b10000000) != 0 } } private fun iBcc() { if (!Status.C) PC = fetchedAddress } private fun iBcs() { if (Status.C) PC = fetchedAddress } private fun iBeq() { if (Status.Z) PC = fetchedAddress } private fun iBit() { val operand = getFetched() Status.Z = (A and operand) == 0 Status.V = (operand and 0b01000000) != 0 Status.N = (operand and 0b10000000) != 0 } private fun iBmi() { if (Status.N) PC = fetchedAddress } private fun iBne() { if (!Status.Z) PC = fetchedAddress } private fun iBpl() { if (!Status.N) PC = fetchedAddress } private fun iBrk() { // handle BRK ('software interrupt') or a real hardware IRQ val interrupt = pendingInterrupt val nmi = interrupt?.first == true if (interrupt != null) { pushStackAddr(PC - 1) } else { PC++ pushStackAddr(PC) } Status.B = interrupt == null pushStack(Status) Status.I = true // interrupts are now disabled // NMOS 6502 doesn't clear the D flag (CMOS version does...) PC = readWord(if (nmi) NMI_vector else IRQ_vector) pendingInterrupt = null } private fun iBvc() { if (!Status.V) PC = fetchedAddress } private fun iBvs() { if (Status.V) PC = fetchedAddress } private fun iClc() { Status.C = false } private fun iCld() { Status.D = false } private fun iCli() { Status.I = false } private fun iClv() { Status.V = false } private fun iCmp() { val fetched = getFetched() Status.C = A >= fetched Status.Z = A == fetched Status.N = ((A - fetched) and 0b10000000) != 0 } private fun iCpx() { val fetched = getFetched() Status.C = X >= fetched Status.Z = X == fetched Status.N = ((X - fetched) and 0b10000000) != 0 } private fun iCpy() { val fetched = getFetched() Status.C = Y >= fetched Status.Z = Y == fetched Status.N = ((Y - fetched) and 0b10000000) != 0 } private fun iDec() { val data = (read(fetchedAddress) - 1) and 0xff write(fetchedAddress, data) Status.Z = data == 0 Status.N = (data and 0b10000000) != 0 } private fun iDex() { X = (X - 1) and 0xff Status.Z = X == 0 Status.N = (X and 0b10000000) != 0 } private fun iDey() { Y = (Y - 1) and 0xff Status.Z = Y == 0 Status.N = (Y and 0b10000000) != 0 } private fun iEor() { A = A xor getFetched() Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } private fun iInc() { val data = (read(fetchedAddress) + 1) and 0xff write(fetchedAddress, data) Status.Z = data == 0 Status.N = (data and 0b10000000) != 0 } private fun iInx() { X = (X + 1) and 0xff Status.Z = X == 0 Status.N = (X and 0b10000000) != 0 } private fun iIny() { Y = (Y + 1) and 0xff Status.Z = Y == 0 Status.N = (Y and 0b10000000) != 0 } private fun iJmp() { PC = fetchedAddress } private fun iJsr() { pushStackAddr(PC - 1) PC = fetchedAddress } private fun iLda() { A = getFetched() Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } private fun iLdx() { X = getFetched() Status.Z = X == 0 Status.N = (X and 0b10000000) != 0 } private fun iLdy() { Y = getFetched() Status.Z = Y == 0 Status.N = (Y and 0b10000000) != 0 } private fun iLsr() { if (currentInstruction.mode == AddrMode.Acc) { Status.C = (A and 1) == 1 A = A ushr 1 Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } else { val data = read(fetchedAddress) Status.C = (data and 1) == 1 val shifted = data ushr 1 write(fetchedAddress, shifted) Status.Z = shifted == 0 Status.N = (shifted and 0b10000000) != 0 } } private fun iNop() {} private fun iOra() { A = A or getFetched() Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } private fun iPha() { pushStack(A) } private fun iPhp() { val origBreakflag = Status.B Status.B = true pushStack(Status) Status.B = origBreakflag } private fun iPla() { A = popStack() Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } private fun iPlp() { Status.fromByte(popStack()) Status.B = true // break is always 1 except when pushing on stack } private fun iRol() { val oldCarry = Status.C if (currentInstruction.mode == AddrMode.Acc) { Status.C = (A and 0b10000000) != 0 A = (A shl 1 and 0xff) or (if (oldCarry) 1 else 0) Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } else { val data = read(fetchedAddress) Status.C = (data and 0b10000000) != 0 val shifted = (data shl 1 and 0xff) or (if (oldCarry) 1 else 0) write(fetchedAddress, shifted) Status.Z = shifted == 0 Status.N = (shifted and 0b10000000) != 0 } } private fun iRor() { val oldCarry = Status.C if (currentInstruction.mode == AddrMode.Acc) { Status.C = (A and 1) == 1 A = (A ushr 1) or (if (oldCarry) 0b10000000 else 0) Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } else { val data = read(fetchedAddress) Status.C = (data and 1) == 1 val shifted = (data ushr 1) or (if (oldCarry) 0b10000000 else 0) write(fetchedAddress, shifted) Status.Z = shifted == 0 Status.N = (shifted and 0b10000000) != 0 } } private fun iRti() { Status.fromByte(popStack()) Status.B = true // break is always 1 except when pushing on stack PC = popStackAddr() } private fun iRts() { PC = popStackAddr() PC = (PC + 1) and 0xffff } private fun iSbc() { val operand = getFetched() val tmp = (A - operand - if (Status.C) 0 else 1) and 0xffff Status.V = (A xor operand) and (A xor tmp) and 0b10000000 != 0 if (Status.D) { // BCD subtract // see http://www.6502.org/tutorials/decimal_mode.html // and http://nesdev.com/6502.txt // and https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/6510core.c#l1396 // (the implementation below is based on the code used by Vice) var tmpA = ((A and 0xf) - (operand and 0xf) - if (Status.C) 0 else 1) and 0xffff tmpA = if ((tmpA and 0x10) != 0) { ((tmpA - 6) and 0xf) or (A and 0xf0) - (operand and 0xf0) - 0x10 } else { (tmpA and 0xf) or (A and 0xf0) - (operand and 0xf0) } if ((tmpA and 0x100) != 0) tmpA -= 0x60 A = tmpA and 0xff } else { // normal subtract A = tmp and 0xff } Status.C = tmp < 0x100 Status.Z = (tmp and 0xff) == 0 Status.N = (tmp and 0b10000000) != 0 } private fun iSec() { Status.C = true } private fun iSed() { Status.D = true } private fun iSei() { Status.I = true } private fun iSta() { write(fetchedAddress, A) } private fun iStx() { write(fetchedAddress, X) } private fun iSty() { write(fetchedAddress, Y) } private fun iTax() { X = A Status.Z = X == 0 Status.N = (X and 0b10000000) != 0 } private fun iTay() { Y = A Status.Z = Y == 0 Status.N = (Y and 0b10000000) != 0 } private fun iTsx() { X = SP Status.Z = X == 0 Status.N = (X and 0b10000000) != 0 } private fun iTxa() { A = X Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } private fun iTxs() { SP = X } private fun iTya() { A = Y Status.Z = A == 0 Status.N = (A and 0b10000000) != 0 } // unofficial/illegal instructions private fun iAhx() { TODO("ahx - ('illegal' instruction)") } private fun iAlr() { TODO("alr=asr - ('illegal' instruction)") } private fun iAnc() { TODO("anc - ('illegal' instruction)") } private fun iArr() { TODO("arr - ('illegal' instruction)") } private fun iAxs() { TODO("axs - ('illegal' instruction)") } private fun iDcp() { TODO("dcp - ('illegal' instruction)") } private fun iIsc() { TODO("isc=isb - ('illegal' instruction)") } private fun iLas() { TODO("las=lar - ('illegal' instruction)") } private fun iLax() { TODO("lax - ('illegal' instruction)") } private fun iRla() { TODO("rla - ('illegal' instruction)") } private fun iRra() { TODO("rra - ('illegal' instruction)") } private fun iSax() { TODO("sax - ('illegal' instruction)") } private fun iShx() { TODO("shx - ('illegal' instruction)") } private fun iShy() { TODO("shy - ('illegal' instruction)") } private fun iSlo() { TODO("slo=aso - ('illegal' instruction)") } private fun iSre() { TODO("sre=lse - ('illegal' instruction)") } private fun iTas() { TODO("tas - ('illegal' instruction)") } private fun iXaa() { TODO("xaa - ('illegal' instruction)") } }