mirror of
https://github.com/irmen/ksim65.git
synced 2025-04-11 03:37:01 +00:00
moved stuff around, added some more kdocs
This commit is contained in:
parent
3f0469dbea
commit
9ec77a81b4
@ -26,4 +26,4 @@ Properties of this simulator:
|
||||
|
||||
## Documentation
|
||||
|
||||
Still to be written. For now,
|
||||
Still to be written. For now, use the source ;-)
|
||||
|
@ -1,22 +1,29 @@
|
||||
import org.jetbrains.dokka.gradle.DokkaTask
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import kotlin.math.max
|
||||
import java.util.Properties
|
||||
|
||||
|
||||
plugins {
|
||||
// Apply the Kotlin JVM plugin to add support for Kotlin on the JVM.
|
||||
id("org.jetbrains.kotlin.jvm") version "1.3.50"
|
||||
id("com.gradle.build-scan") version "2.4.2"
|
||||
id("org.jetbrains.dokka") version "0.9.18"
|
||||
id("com.jfrog.bintray") version "1.7.3"
|
||||
id("maven-publish")
|
||||
}
|
||||
|
||||
|
||||
version = rootProject.file("src/main/resources/version.txt").readText().trim()
|
||||
val versionProps = Properties().also {
|
||||
it.load(File("src/main/resources/version.properties").inputStream())
|
||||
}
|
||||
version = versionProps["version"] as String
|
||||
base.archivesBaseName = "ksim65"
|
||||
|
||||
repositories {
|
||||
// Use jcenter for resolving dependencies.
|
||||
// You can declare any Maven/Ivy/file repository here.
|
||||
jcenter()
|
||||
maven("https://jitpack.io")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -1,5 +1,17 @@
|
||||
package razorvine.ksim65.components
|
||||
package razorvine.ksim65
|
||||
|
||||
import razorvine.ksim65.components.Address
|
||||
import razorvine.ksim65.components.BusComponent
|
||||
import razorvine.ksim65.components.MemMappedComponent
|
||||
import razorvine.ksim65.components.UByte
|
||||
|
||||
/**
|
||||
* The system bus that connects all other components together.
|
||||
* Usually, there is just a single Bus present.
|
||||
*
|
||||
* It distributes reset and clock signals to every connected component.
|
||||
* Data bytes can be read from the bus or written to the bus. It's distributed to the corresponding component(s).
|
||||
*/
|
||||
class Bus {
|
||||
|
||||
private val components = mutableListOf<BusComponent>()
|
||||
@ -25,6 +37,10 @@ class Bus {
|
||||
component.bus = this
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a data byte at the given address.
|
||||
* The first memory mapped component that listens to that address, will respond.
|
||||
*/
|
||||
fun read(address: Address): UByte {
|
||||
memComponents.forEach {
|
||||
if (address >= it.startAddress && address <= it.endAddress)
|
||||
@ -33,6 +49,10 @@ class Bus {
|
||||
return 0xff
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a data byte to the given address.
|
||||
* Any memory mapped component that listens to the address, will receive the data.
|
||||
*/
|
||||
fun write(address: Address, data: UByte) {
|
||||
memComponents.forEach {
|
||||
if (address >= it.startAddress && address <= it.endAddress)
|
@ -1,5 +1,9 @@
|
||||
package razorvine.ksim65.components
|
||||
package razorvine.ksim65
|
||||
|
||||
import razorvine.ksim65.components.Address
|
||||
import razorvine.ksim65.components.BusComponent
|
||||
import razorvine.ksim65.components.MemoryComponent
|
||||
import razorvine.ksim65.components.UByte
|
||||
|
||||
/**
|
||||
* 6502 cpu simulation (the NMOS version) including the 'illegal' opcodes.
|
||||
@ -86,12 +90,12 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
protected class Instruction(val mnemonic: String, val mode: AddrMode, val cycles: Int)
|
||||
|
||||
|
||||
var A: Int = 0
|
||||
var X: Int = 0
|
||||
var Y: Int = 0
|
||||
var SP: Int = 0
|
||||
var PC: Address = 0
|
||||
val Status = StatusRegister()
|
||||
var regA: Int = 0
|
||||
var regX: Int = 0
|
||||
var regY: Int = 0
|
||||
var regSP: Int = 0
|
||||
var regPC: Address = 0
|
||||
val regP = StatusRegister()
|
||||
var currentOpcode: Int = 0
|
||||
protected set
|
||||
var instrCycles: Int = 0
|
||||
@ -137,7 +141,7 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
}
|
||||
|
||||
fun disassemble(component: MemoryComponent, from: Address, to: Address) =
|
||||
disassemble(component.cloneContents(), component.startAddress, from, to)
|
||||
disassemble(component.copyOfMem(), component.startAddress, from, to)
|
||||
|
||||
fun disassemble(memory: Array<UByte>, baseAddress: Address, from: Address, to: Address): List<String> {
|
||||
var location = from - baseAddress
|
||||
@ -246,18 +250,18 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
}
|
||||
|
||||
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
|
||||
regSP = 0xfd
|
||||
regPC = readWord(RESET_vector)
|
||||
regA = 0
|
||||
regX = 0
|
||||
regY = 0
|
||||
regP.C = false
|
||||
regP.Z = false
|
||||
regP.I = true
|
||||
regP.D = false
|
||||
regP.B = false
|
||||
regP.V = false
|
||||
regP.N = false
|
||||
instrCycles = resetCycles // a reset takes time as well
|
||||
currentOpcode = 0
|
||||
currentInstruction = instructions[0]
|
||||
@ -272,17 +276,17 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
currentInstruction = instructions[0]
|
||||
} else {
|
||||
// no interrupt, fetch next instruction from memory
|
||||
currentOpcode = read(PC)
|
||||
currentOpcode = read(regPC)
|
||||
currentInstruction = instructions[currentOpcode]
|
||||
|
||||
tracing?.invoke(logState())
|
||||
|
||||
breakpoints[PC]?.let { breakpoint ->
|
||||
val oldPC = PC
|
||||
val result = breakpoint(this, PC)
|
||||
breakpoints[regPC]?.let { breakpoint ->
|
||||
val oldPC = regPC
|
||||
val result = breakpoint(this, regPC)
|
||||
if(result.newPC!=null)
|
||||
PC = result.newPC
|
||||
if (PC != oldPC)
|
||||
regPC = result.newPC
|
||||
if (regPC != oldPC)
|
||||
return clock()
|
||||
else if(result.newOpcode!=null) {
|
||||
currentOpcode = result.newOpcode
|
||||
@ -291,11 +295,11 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
}
|
||||
|
||||
if (stopOnBrk && currentOpcode == 0) {
|
||||
throw InstructionError("stopped on BRK instruction at ${hexW(PC)}")
|
||||
throw InstructionError("stopped on BRK instruction at ${hexW(regPC)}")
|
||||
}
|
||||
}
|
||||
|
||||
PC++
|
||||
regPC++
|
||||
instrCycles = currentInstruction.cycles
|
||||
applyAddressingMode(currentInstruction.mode)
|
||||
dispatchOpcode(currentOpcode)
|
||||
@ -317,23 +321,23 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
}
|
||||
|
||||
fun irq(source: BusComponent) {
|
||||
if (!Status.I)
|
||||
if (!regP.I)
|
||||
pendingInterrupt = Pair(false, source)
|
||||
}
|
||||
|
||||
fun logState(): String =
|
||||
"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") +
|
||||
"cycle:$totalCycles - pc=${hexW(regPC)} " +
|
||||
"A=${hexB(regA)} " +
|
||||
"X=${hexB(regX)} " +
|
||||
"Y=${hexB(regY)} " +
|
||||
"SP=${hexB(regSP)} " +
|
||||
" n=" + (if (regP.N) "1" else "0") +
|
||||
" v=" + (if (regP.V) "1" else "0") +
|
||||
" b=" + (if (regP.B) "1" else "0") +
|
||||
" d=" + (if (regP.D) "1" else "0") +
|
||||
" i=" + (if (regP.I) "1" else "0") +
|
||||
" z=" + (if (regP.Z) "1" else "0") +
|
||||
" c=" + (if (regP.C) "1" else "0") +
|
||||
" icycles=$instrCycles instr=${hexB(currentOpcode)}:${currentInstruction.mnemonic}"
|
||||
|
||||
protected fun getFetched() =
|
||||
@ -345,7 +349,7 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
else
|
||||
read(fetchedAddress)
|
||||
|
||||
protected fun readPc(): Int = bus.read(PC++).toInt()
|
||||
protected fun readPc(): Int = bus.read(regPC++).toInt()
|
||||
|
||||
protected fun pushStackAddr(address: Address) {
|
||||
val lo = address and 0xff
|
||||
@ -359,13 +363,13 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
}
|
||||
|
||||
protected fun pushStack(data: Int) {
|
||||
write(SP or 0x0100, data)
|
||||
SP = (SP - 1) and 0xff
|
||||
write(regSP or 0x0100, data)
|
||||
regSP = (regSP - 1) and 0xff
|
||||
}
|
||||
|
||||
protected fun popStack(): Int {
|
||||
SP = (SP + 1) and 0xff
|
||||
return read(SP or 0x0100)
|
||||
regSP = (regSP + 1) and 0xff
|
||||
return read(regSP or 0x0100)
|
||||
}
|
||||
|
||||
protected fun popStackAddr(): Address {
|
||||
@ -379,7 +383,7 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
protected fun write(address: Address, data: Int) = bus.write(address, data.toShort())
|
||||
|
||||
// opcodes table from http://www.oxyron.de/html/opcodes02.html
|
||||
protected open val instructions: Array<Instruction> by lazy {
|
||||
protected open val instructions: Array<Instruction> =
|
||||
listOf(
|
||||
/* 00 */ Instruction("brk", AddrMode.Imp, 7),
|
||||
/* 01 */ Instruction("ora", AddrMode.IzX, 6),
|
||||
@ -638,12 +642,11 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
/* fe */ Instruction("inc", AddrMode.AbsX, 7),
|
||||
/* ff */ Instruction("isc", AddrMode.AbsX, 7)
|
||||
).toTypedArray()
|
||||
}
|
||||
|
||||
protected open fun applyAddressingMode(addrMode: AddrMode) {
|
||||
when (addrMode) {
|
||||
AddrMode.Imp, AddrMode.Acc -> {
|
||||
fetchedData = A
|
||||
fetchedData = regA
|
||||
}
|
||||
AddrMode.Imm -> {
|
||||
fetchedData = readPc()
|
||||
@ -653,18 +656,18 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
}
|
||||
AddrMode.ZpX -> {
|
||||
// note: zeropage index will not leave Zp when page boundary is crossed
|
||||
fetchedAddress = (readPc() + X) and 0xff
|
||||
fetchedAddress = (readPc() + regX) and 0xff
|
||||
}
|
||||
AddrMode.ZpY -> {
|
||||
// note: zeropage index will not leave Zp when page boundary is crossed
|
||||
fetchedAddress = (readPc() + Y) and 0xff
|
||||
fetchedAddress = (readPc() + regY) and 0xff
|
||||
}
|
||||
AddrMode.Rel -> {
|
||||
val relative = readPc()
|
||||
fetchedAddress = if (relative >= 0x80) {
|
||||
PC - (256 - relative) and 0xffff
|
||||
regPC - (256 - relative) and 0xffff
|
||||
} else
|
||||
PC + relative and 0xffff
|
||||
regPC + relative and 0xffff
|
||||
}
|
||||
AddrMode.Abs -> {
|
||||
val lo = readPc()
|
||||
@ -674,12 +677,12 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
AddrMode.AbsX -> {
|
||||
val lo = readPc()
|
||||
val hi = readPc()
|
||||
fetchedAddress = X + (lo or (hi shl 8)) and 0xffff
|
||||
fetchedAddress = regX + (lo or (hi shl 8)) and 0xffff
|
||||
}
|
||||
AddrMode.AbsY -> {
|
||||
val lo = readPc()
|
||||
val hi = readPc()
|
||||
fetchedAddress = Y + (lo or (hi shl 8)) and 0xffff
|
||||
fetchedAddress = regY + (lo or (hi shl 8)) and 0xffff
|
||||
}
|
||||
AddrMode.Ind -> {
|
||||
// not able to fetch an address which crosses the page boundary (6502, fixed in 65C02)
|
||||
@ -700,8 +703,8 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
AddrMode.IzX -> {
|
||||
// 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)
|
||||
val lo = read((fetchedAddress + regX) and 0xff)
|
||||
val hi = read((fetchedAddress + regX + 1) and 0xff)
|
||||
fetchedAddress = lo or (hi shl 8)
|
||||
}
|
||||
AddrMode.IzY -> {
|
||||
@ -709,7 +712,7 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
fetchedAddress = readPc()
|
||||
val lo = read(fetchedAddress)
|
||||
val hi = read((fetchedAddress + 1) and 0xff)
|
||||
fetchedAddress = Y + (lo or (hi shl 8)) and 0xffff
|
||||
fetchedAddress = regY + (lo or (hi shl 8)) and 0xffff
|
||||
}
|
||||
AddrMode.Zpr, AddrMode.Izp, AddrMode.IaX -> {
|
||||
// addressing mode used by the 65C02 only
|
||||
@ -986,87 +989,87 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
|
||||
protected open fun iAdc() {
|
||||
val operand = getFetched()
|
||||
if (Status.D) {
|
||||
if (regP.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)
|
||||
var tmp = (regA and 0xf) + (operand and 0xf) + (if (regP.C) 1 else 0)
|
||||
if (tmp > 9) tmp += 6
|
||||
tmp = if (tmp <= 0x0f) {
|
||||
(tmp and 0xf) + (A and 0xf0) + (operand and 0xf0)
|
||||
(tmp and 0xf) + (regA and 0xf0) + (operand and 0xf0)
|
||||
} else {
|
||||
(tmp and 0xf) + (A and 0xf0) + (operand and 0xf0) + 0x10
|
||||
(tmp and 0xf) + (regA 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
|
||||
regP.Z = regA + operand + (if (regP.C) 1 else 0) and 0xff == 0
|
||||
regP.N = tmp and 0b10000000 != 0
|
||||
regP.V = (regA xor tmp) and 0x80 != 0 && (regA 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
|
||||
regP.C = tmp > 0xf0 // original: (tmp and 0xff0) > 0xf0
|
||||
regA = 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
|
||||
val tmp = operand + regA + if (regP.C) 1 else 0
|
||||
regP.N = (tmp and 0b10000000) != 0
|
||||
regP.Z = (tmp and 0xff) == 0
|
||||
regP.V = (regA xor operand).inv() and (regA xor tmp) and 0b10000000 != 0
|
||||
regP.C = tmp > 0xff
|
||||
regA = tmp and 0xff
|
||||
}
|
||||
}
|
||||
|
||||
protected fun iAnd() {
|
||||
A = A and getFetched()
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
regA = regA and getFetched()
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected 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
|
||||
regP.C = (regA and 0b10000000) != 0
|
||||
regA = (regA shl 1) and 0xff
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
} else {
|
||||
val data = read(fetchedAddress)
|
||||
Status.C = (data and 0b10000000) != 0
|
||||
regP.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
|
||||
regP.Z = shifted == 0
|
||||
regP.N = (shifted and 0b10000000) != 0
|
||||
}
|
||||
}
|
||||
|
||||
protected fun iBcc() {
|
||||
if (!Status.C) PC = fetchedAddress
|
||||
if (!regP.C) regPC = fetchedAddress
|
||||
}
|
||||
|
||||
protected fun iBcs() {
|
||||
if (Status.C) PC = fetchedAddress
|
||||
if (regP.C) regPC = fetchedAddress
|
||||
}
|
||||
|
||||
protected fun iBeq() {
|
||||
if (Status.Z) PC = fetchedAddress
|
||||
if (regP.Z) regPC = fetchedAddress
|
||||
}
|
||||
|
||||
protected open fun iBit() {
|
||||
val operand = getFetched()
|
||||
Status.Z = (A and operand) == 0
|
||||
Status.V = (operand and 0b01000000) != 0
|
||||
Status.N = (operand and 0b10000000) != 0
|
||||
regP.Z = (regA and operand) == 0
|
||||
regP.V = (operand and 0b01000000) != 0
|
||||
regP.N = (operand and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iBmi() {
|
||||
if (Status.N) PC = fetchedAddress
|
||||
if (regP.N) regPC = fetchedAddress
|
||||
}
|
||||
|
||||
protected fun iBne() {
|
||||
if (!Status.Z) PC = fetchedAddress
|
||||
if (!regP.Z) regPC = fetchedAddress
|
||||
}
|
||||
|
||||
protected fun iBpl() {
|
||||
if (!Status.N) PC = fetchedAddress
|
||||
if (!regP.N) regPC = fetchedAddress
|
||||
}
|
||||
|
||||
protected open fun iBrk() {
|
||||
@ -1074,309 +1077,309 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
val interrupt = pendingInterrupt
|
||||
val nmi = interrupt?.first == true
|
||||
if (interrupt != null) {
|
||||
pushStackAddr(PC - 1)
|
||||
pushStackAddr(regPC - 1)
|
||||
} else {
|
||||
PC++
|
||||
pushStackAddr(PC)
|
||||
regPC++
|
||||
pushStackAddr(regPC)
|
||||
}
|
||||
Status.B = interrupt == null
|
||||
pushStack(Status)
|
||||
Status.I = true // interrupts are now disabled
|
||||
regP.B = interrupt == null
|
||||
pushStack(regP)
|
||||
regP.I = true // interrupts are now disabled
|
||||
// NMOS 6502 doesn't clear the D flag (CMOS 65C02 version does...)
|
||||
PC = readWord(if (nmi) NMI_vector else IRQ_vector)
|
||||
regPC = readWord(if (nmi) NMI_vector else IRQ_vector)
|
||||
pendingInterrupt = null
|
||||
}
|
||||
|
||||
protected fun iBvc() {
|
||||
if (!Status.V) PC = fetchedAddress
|
||||
if (!regP.V) regPC = fetchedAddress
|
||||
}
|
||||
|
||||
protected fun iBvs() {
|
||||
if (Status.V) PC = fetchedAddress
|
||||
if (regP.V) regPC = fetchedAddress
|
||||
}
|
||||
|
||||
protected fun iClc() {
|
||||
Status.C = false
|
||||
regP.C = false
|
||||
}
|
||||
|
||||
protected fun iCld() {
|
||||
Status.D = false
|
||||
regP.D = false
|
||||
}
|
||||
|
||||
protected fun iCli() {
|
||||
Status.I = false
|
||||
regP.I = false
|
||||
}
|
||||
|
||||
protected fun iClv() {
|
||||
Status.V = false
|
||||
regP.V = false
|
||||
}
|
||||
|
||||
protected fun iCmp() {
|
||||
val fetched = getFetched()
|
||||
Status.C = A >= fetched
|
||||
Status.Z = A == fetched
|
||||
Status.N = ((A - fetched) and 0b10000000) != 0
|
||||
regP.C = regA >= fetched
|
||||
regP.Z = regA == fetched
|
||||
regP.N = ((regA - fetched) and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iCpx() {
|
||||
val fetched = getFetched()
|
||||
Status.C = X >= fetched
|
||||
Status.Z = X == fetched
|
||||
Status.N = ((X - fetched) and 0b10000000) != 0
|
||||
regP.C = regX >= fetched
|
||||
regP.Z = regX == fetched
|
||||
regP.N = ((regX - fetched) and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iCpy() {
|
||||
val fetched = getFetched()
|
||||
Status.C = Y >= fetched
|
||||
Status.Z = Y == fetched
|
||||
Status.N = ((Y - fetched) and 0b10000000) != 0
|
||||
regP.C = regY >= fetched
|
||||
regP.Z = regY == fetched
|
||||
regP.N = ((regY - fetched) and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected open fun iDec() {
|
||||
val data = (read(fetchedAddress) - 1) and 0xff
|
||||
write(fetchedAddress, data)
|
||||
Status.Z = data == 0
|
||||
Status.N = (data and 0b10000000) != 0
|
||||
regP.Z = data == 0
|
||||
regP.N = (data and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iDex() {
|
||||
X = (X - 1) and 0xff
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) != 0
|
||||
regX = (regX - 1) and 0xff
|
||||
regP.Z = regX == 0
|
||||
regP.N = (regX and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iDey() {
|
||||
Y = (Y - 1) and 0xff
|
||||
Status.Z = Y == 0
|
||||
Status.N = (Y and 0b10000000) != 0
|
||||
regY = (regY - 1) and 0xff
|
||||
regP.Z = regY == 0
|
||||
regP.N = (regY and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iEor() {
|
||||
A = A xor getFetched()
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
regA = regA xor getFetched()
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected open fun iInc() {
|
||||
val data = (read(fetchedAddress) + 1) and 0xff
|
||||
write(fetchedAddress, data)
|
||||
Status.Z = data == 0
|
||||
Status.N = (data and 0b10000000) != 0
|
||||
regP.Z = data == 0
|
||||
regP.N = (data and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iInx() {
|
||||
X = (X + 1) and 0xff
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) != 0
|
||||
regX = (regX + 1) and 0xff
|
||||
regP.Z = regX == 0
|
||||
regP.N = (regX and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iIny() {
|
||||
Y = (Y + 1) and 0xff
|
||||
Status.Z = Y == 0
|
||||
Status.N = (Y and 0b10000000) != 0
|
||||
regY = (regY + 1) and 0xff
|
||||
regP.Z = regY == 0
|
||||
regP.N = (regY and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iJmp() {
|
||||
PC = fetchedAddress
|
||||
regPC = fetchedAddress
|
||||
}
|
||||
|
||||
protected fun iJsr() {
|
||||
pushStackAddr(PC - 1)
|
||||
PC = fetchedAddress
|
||||
pushStackAddr(regPC - 1)
|
||||
regPC = fetchedAddress
|
||||
}
|
||||
|
||||
protected fun iLda() {
|
||||
A = getFetched()
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
regA = getFetched()
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iLdx() {
|
||||
X = getFetched()
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) != 0
|
||||
regX = getFetched()
|
||||
regP.Z = regX == 0
|
||||
regP.N = (regX and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iLdy() {
|
||||
Y = getFetched()
|
||||
Status.Z = Y == 0
|
||||
Status.N = (Y and 0b10000000) != 0
|
||||
regY = getFetched()
|
||||
regP.Z = regY == 0
|
||||
regP.N = (regY and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected 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
|
||||
regP.C = (regA and 1) == 1
|
||||
regA = regA ushr 1
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
} else {
|
||||
val data = read(fetchedAddress)
|
||||
Status.C = (data and 1) == 1
|
||||
regP.C = (data and 1) == 1
|
||||
val shifted = data ushr 1
|
||||
write(fetchedAddress, shifted)
|
||||
Status.Z = shifted == 0
|
||||
Status.N = (shifted and 0b10000000) != 0
|
||||
regP.Z = shifted == 0
|
||||
regP.N = (shifted and 0b10000000) != 0
|
||||
}
|
||||
}
|
||||
|
||||
protected fun iNop() {}
|
||||
|
||||
protected fun iOra() {
|
||||
A = A or getFetched()
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
regA = regA or getFetched()
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iPha() {
|
||||
pushStack(A)
|
||||
pushStack(regA)
|
||||
}
|
||||
|
||||
protected fun iPhp() {
|
||||
val origBreakflag = Status.B
|
||||
Status.B = true
|
||||
pushStack(Status)
|
||||
Status.B = origBreakflag
|
||||
val origBreakflag = regP.B
|
||||
regP.B = true
|
||||
pushStack(regP)
|
||||
regP.B = origBreakflag
|
||||
}
|
||||
|
||||
protected fun iPla() {
|
||||
A = popStack()
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
regA = popStack()
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iPlp() {
|
||||
Status.fromByte(popStack())
|
||||
Status.B = true // break is always 1 except when pushing on stack
|
||||
regP.fromByte(popStack())
|
||||
regP.B = true // break is always 1 except when pushing on stack
|
||||
}
|
||||
|
||||
protected fun iRol() {
|
||||
val oldCarry = Status.C
|
||||
val oldCarry = regP.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
|
||||
regP.C = (regA and 0b10000000) != 0
|
||||
regA = (regA shl 1 and 0xff) or (if (oldCarry) 1 else 0)
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
} else {
|
||||
val data = read(fetchedAddress)
|
||||
Status.C = (data and 0b10000000) != 0
|
||||
regP.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
|
||||
regP.Z = shifted == 0
|
||||
regP.N = (shifted and 0b10000000) != 0
|
||||
}
|
||||
}
|
||||
|
||||
protected fun iRor() {
|
||||
val oldCarry = Status.C
|
||||
val oldCarry = regP.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
|
||||
regP.C = (regA and 1) == 1
|
||||
regA = (regA ushr 1) or (if (oldCarry) 0b10000000 else 0)
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
} else {
|
||||
val data = read(fetchedAddress)
|
||||
Status.C = (data and 1) == 1
|
||||
regP.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
|
||||
regP.Z = shifted == 0
|
||||
regP.N = (shifted and 0b10000000) != 0
|
||||
}
|
||||
}
|
||||
|
||||
protected fun iRti() {
|
||||
Status.fromByte(popStack())
|
||||
Status.B = true // break is always 1 except when pushing on stack
|
||||
PC = popStackAddr()
|
||||
regP.fromByte(popStack())
|
||||
regP.B = true // break is always 1 except when pushing on stack
|
||||
regPC = popStackAddr()
|
||||
}
|
||||
|
||||
protected fun iRts() {
|
||||
PC = popStackAddr()
|
||||
PC = (PC + 1) and 0xffff
|
||||
regPC = popStackAddr()
|
||||
regPC = (regPC + 1) and 0xffff
|
||||
}
|
||||
|
||||
protected open 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) {
|
||||
val tmp = (regA - operand - if (regP.C) 0 else 1) and 0xffff
|
||||
regP.V = (regA xor operand) and (regA xor tmp) and 0b10000000 != 0
|
||||
if (regP.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
|
||||
var tmpA = ((regA and 0xf) - (operand and 0xf) - if (regP.C) 0 else 1) and 0xffff
|
||||
tmpA = if ((tmpA and 0x10) != 0) {
|
||||
((tmpA - 6) and 0xf) or (A and 0xf0) - (operand and 0xf0) - 0x10
|
||||
((tmpA - 6) and 0xf) or (regA and 0xf0) - (operand and 0xf0) - 0x10
|
||||
} else {
|
||||
(tmpA and 0xf) or (A and 0xf0) - (operand and 0xf0)
|
||||
(tmpA and 0xf) or (regA and 0xf0) - (operand and 0xf0)
|
||||
}
|
||||
if ((tmpA and 0x100) != 0) tmpA -= 0x60
|
||||
A = tmpA and 0xff
|
||||
regA = tmpA and 0xff
|
||||
} else {
|
||||
// normal subtract
|
||||
A = tmp and 0xff
|
||||
regA = tmp and 0xff
|
||||
}
|
||||
Status.C = tmp < 0x100
|
||||
Status.Z = (tmp and 0xff) == 0
|
||||
Status.N = (tmp and 0b10000000) != 0
|
||||
regP.C = tmp < 0x100
|
||||
regP.Z = (tmp and 0xff) == 0
|
||||
regP.N = (tmp and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iSec() {
|
||||
Status.C = true
|
||||
regP.C = true
|
||||
}
|
||||
|
||||
protected fun iSed() {
|
||||
Status.D = true
|
||||
regP.D = true
|
||||
}
|
||||
|
||||
protected fun iSei() {
|
||||
Status.I = true
|
||||
regP.I = true
|
||||
}
|
||||
|
||||
protected fun iSta() {
|
||||
write(fetchedAddress, A)
|
||||
write(fetchedAddress, regA)
|
||||
}
|
||||
|
||||
protected fun iStx() {
|
||||
write(fetchedAddress, X)
|
||||
write(fetchedAddress, regX)
|
||||
}
|
||||
|
||||
protected fun iSty() {
|
||||
write(fetchedAddress, Y)
|
||||
write(fetchedAddress, regY)
|
||||
}
|
||||
|
||||
protected fun iTax() {
|
||||
X = A
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) != 0
|
||||
regX = regA
|
||||
regP.Z = regX == 0
|
||||
regP.N = (regX and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iTay() {
|
||||
Y = A
|
||||
Status.Z = Y == 0
|
||||
Status.N = (Y and 0b10000000) != 0
|
||||
regY = regA
|
||||
regP.Z = regY == 0
|
||||
regP.N = (regY and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iTsx() {
|
||||
X = SP
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) != 0
|
||||
regX = regSP
|
||||
regP.Z = regX == 0
|
||||
regP.N = (regX and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iTxa() {
|
||||
A = X
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
regA = regX
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
}
|
||||
|
||||
protected fun iTxs() {
|
||||
SP = X
|
||||
regSP = regX
|
||||
}
|
||||
|
||||
protected fun iTya() {
|
||||
A = Y
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
regA = regY
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
}
|
||||
|
||||
// unofficial/illegal 6502 instructions
|
@ -1,4 +1,6 @@
|
||||
package razorvine.ksim65.components
|
||||
package razorvine.ksim65
|
||||
|
||||
import razorvine.ksim65.components.Address
|
||||
|
||||
/**
|
||||
* 65C02 cpu simulation (the CMOS version of the 6502).
|
||||
@ -34,7 +36,7 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
Wait.Stopped -> {
|
||||
if (pendingInterrupt != null) {
|
||||
// jump to reset vector after hardware interrupt
|
||||
PC = readWord(RESET_vector)
|
||||
regPC = readWord(RESET_vector)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,9 +82,9 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
fetchedAddress = readPc()
|
||||
val relative = readPc()
|
||||
fetchedAddressZpr = if (relative >= 0x80) {
|
||||
PC - (256 - relative) and 0xffff
|
||||
regPC - (256 - relative) and 0xffff
|
||||
} else
|
||||
PC + relative and 0xffff
|
||||
regPC + relative and 0xffff
|
||||
}
|
||||
AddrMode.Izp -> {
|
||||
// addressing mode used by the 65C02 only
|
||||
@ -96,8 +98,8 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
var lo = readPc()
|
||||
var hi = readPc()
|
||||
fetchedAddress = lo or (hi shl 8)
|
||||
lo = read((fetchedAddress + X) and 0xffff)
|
||||
hi = read((fetchedAddress + X + 1) and 0xffff)
|
||||
lo = read((fetchedAddress + regX) and 0xffff)
|
||||
hi = read((fetchedAddress + regX + 1) and 0xffff)
|
||||
fetchedAddress = lo or (hi shl 8)
|
||||
}
|
||||
}
|
||||
@ -367,7 +369,7 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
}
|
||||
|
||||
// opcode list: http://www.oxyron.de/html/opcodesc02.html
|
||||
override val instructions: Array<Instruction> by lazy {
|
||||
override val instructions: Array<Instruction> =
|
||||
listOf(
|
||||
/* 00 */ Instruction("brk", AddrMode.Imp, 7),
|
||||
/* 01 */ Instruction("ora", AddrMode.IzX, 6),
|
||||
@ -626,63 +628,62 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
/* fe */ Instruction("inc", AddrMode.AbsX, 7),
|
||||
/* ff */ Instruction("bbs7", AddrMode.Zpr, 5)
|
||||
).toTypedArray()
|
||||
}
|
||||
|
||||
override 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)
|
||||
pushStackAddr(regPC - 1)
|
||||
} else {
|
||||
PC++
|
||||
pushStackAddr(PC)
|
||||
regPC++
|
||||
pushStackAddr(regPC)
|
||||
}
|
||||
Status.B = interrupt == null
|
||||
pushStack(Status)
|
||||
Status.I = true // interrupts are now disabled
|
||||
Status.D = false // this is different from NMOS 6502
|
||||
PC = readWord(if (nmi) Cpu6502.NMI_vector else Cpu6502.IRQ_vector)
|
||||
regP.B = interrupt == null
|
||||
pushStack(regP)
|
||||
regP.I = true // interrupts are now disabled
|
||||
regP.D = false // this is different from NMOS 6502
|
||||
regPC = readWord(if (nmi) Cpu6502.NMI_vector else Cpu6502.IRQ_vector)
|
||||
pendingInterrupt = null
|
||||
}
|
||||
|
||||
override fun iBit() {
|
||||
val data = getFetched()
|
||||
Status.Z = (A and data) == 0
|
||||
regP.Z = (regA and data) == 0
|
||||
if (currentInstruction.mode != AddrMode.Imm) {
|
||||
Status.V = (data and 0b01000000) != 0
|
||||
Status.N = (data and 0b10000000) != 0
|
||||
regP.V = (data and 0b01000000) != 0
|
||||
regP.N = (data and 0b10000000) != 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun iAdc() {
|
||||
val value = getFetched()
|
||||
if (Status.D) {
|
||||
if (regP.D) {
|
||||
// BCD add
|
||||
// see http://www.6502.org/tutorials/decimal_mode.html
|
||||
// and https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/65c02core.c#l542
|
||||
// (the implementation below is based on the code used by Vice)
|
||||
var tmp = (A and 0x0f) + (value and 0x0f) + if (Status.C) 1 else 0
|
||||
var tmp2 = (A and 0xf0) + (value and 0xf0)
|
||||
var tmp = (regA and 0x0f) + (value and 0x0f) + if (regP.C) 1 else 0
|
||||
var tmp2 = (regA and 0xf0) + (value and 0xf0)
|
||||
if (tmp > 9) {
|
||||
tmp2 += 0x10
|
||||
tmp += 6
|
||||
}
|
||||
Status.V = (A xor value).inv() and (A xor tmp2) and 0b10000000 != 0
|
||||
regP.V = (regA xor value).inv() and (regA xor tmp2) and 0b10000000 != 0
|
||||
if (tmp2 > 0x90) tmp2 += 0x60
|
||||
Status.C = tmp2 >= 0x100
|
||||
regP.C = tmp2 >= 0x100
|
||||
tmp = (tmp and 0x0f) + (tmp2 and 0xf0)
|
||||
Status.N = (tmp and 0b10000000) != 0
|
||||
Status.Z = tmp == 0
|
||||
A = tmp and 0xff
|
||||
regP.N = (tmp and 0b10000000) != 0
|
||||
regP.Z = tmp == 0
|
||||
regA = tmp and 0xff
|
||||
} else {
|
||||
// normal add (identical to 6502)
|
||||
val tmp = value + A + if (Status.C) 1 else 0
|
||||
Status.N = (tmp and 0b10000000) != 0
|
||||
Status.Z = (tmp and 0xff) == 0
|
||||
Status.V = (A xor value).inv() and (A xor tmp) and 0b10000000 != 0
|
||||
Status.C = tmp > 0xff
|
||||
A = tmp and 0xff
|
||||
val tmp = value + regA + if (regP.C) 1 else 0
|
||||
regP.N = (tmp and 0b10000000) != 0
|
||||
regP.Z = (tmp and 0xff) == 0
|
||||
regP.V = (regA xor value).inv() and (regA xor tmp) and 0b10000000 != 0
|
||||
regP.C = tmp > 0xff
|
||||
regA = tmp and 0xff
|
||||
}
|
||||
}
|
||||
|
||||
@ -691,50 +692,50 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
// and https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/65c02core.c#l1205
|
||||
// (the implementation below is based on the code used by Vice)
|
||||
val value = getFetched()
|
||||
var tmp = (A - value - if (Status.C) 0 else 1) and 0xffff
|
||||
Status.V = (A xor tmp) and (A xor value) and 0b10000000 != 0
|
||||
if (Status.D) {
|
||||
var tmp = (regA - value - if (regP.C) 0 else 1) and 0xffff
|
||||
regP.V = (regA xor tmp) and (regA xor value) and 0b10000000 != 0
|
||||
if (regP.D) {
|
||||
if (tmp > 0xff) tmp = (tmp - 0x60) and 0xffff
|
||||
val tmp2 = ((A and 0x0f) - (value and 0x0f) - if (Status.C) 0 else 1) and 0xffff
|
||||
val tmp2 = ((regA and 0x0f) - (value and 0x0f) - if (regP.C) 0 else 1) and 0xffff
|
||||
if (tmp2 > 0xff) tmp -= 6
|
||||
}
|
||||
Status.C = (A - if (Status.C) 0 else 1) >= value
|
||||
Status.Z = (tmp and 0xff) == 0
|
||||
Status.N = (tmp and 0b10000000) != 0
|
||||
A = tmp and 0xff
|
||||
regP.C = (regA - if (regP.C) 0 else 1) >= value
|
||||
regP.Z = (tmp and 0xff) == 0
|
||||
regP.N = (tmp and 0b10000000) != 0
|
||||
regA = tmp and 0xff
|
||||
}
|
||||
|
||||
override fun iDec() {
|
||||
if (currentInstruction.mode == AddrMode.Acc) {
|
||||
A = (A - 1) and 0xff
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
regA = (regA - 1) and 0xff
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
} else super.iDec()
|
||||
}
|
||||
|
||||
override fun iInc() {
|
||||
if (currentInstruction.mode == AddrMode.Acc) {
|
||||
A = (A + 1) and 0xff
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
regA = (regA + 1) and 0xff
|
||||
regP.Z = regA == 0
|
||||
regP.N = (regA and 0b10000000) != 0
|
||||
} else super.iInc()
|
||||
}
|
||||
|
||||
private fun iBra() {
|
||||
// unconditional branch
|
||||
PC = fetchedAddress
|
||||
regPC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iTrb() {
|
||||
val data = getFetched()
|
||||
Status.Z = data and A == 0
|
||||
write(fetchedAddress, data and A.inv())
|
||||
regP.Z = data and regA == 0
|
||||
write(fetchedAddress, data and regA.inv())
|
||||
}
|
||||
|
||||
private fun iTsb() {
|
||||
val data = getFetched()
|
||||
Status.Z = data and A == 0
|
||||
write(fetchedAddress, data or A)
|
||||
regP.Z = data and regA == 0
|
||||
write(fetchedAddress, data or regA)
|
||||
}
|
||||
|
||||
private fun iStz() {
|
||||
@ -750,119 +751,119 @@ class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
}
|
||||
|
||||
private fun iPhx() {
|
||||
pushStack(X)
|
||||
pushStack(regX)
|
||||
}
|
||||
|
||||
private fun iPlx() {
|
||||
X = popStack()
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) != 0
|
||||
regX = popStack()
|
||||
regP.Z = regX == 0
|
||||
regP.N = (regX and 0b10000000) != 0
|
||||
}
|
||||
|
||||
private fun iPhy() {
|
||||
pushStack(Y)
|
||||
pushStack(regY)
|
||||
}
|
||||
|
||||
private fun iPly() {
|
||||
Y = popStack()
|
||||
Status.Z = Y == 0
|
||||
Status.N = (Y and 0b10000000) != 0
|
||||
regY = popStack()
|
||||
regP.Z = regY == 0
|
||||
regP.N = (regY and 0b10000000) != 0
|
||||
}
|
||||
|
||||
private fun iBbr0() {
|
||||
val data = getFetched()
|
||||
if (data and 1 == 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbr1() {
|
||||
val data = getFetched()
|
||||
if (data and 2 == 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbr2() {
|
||||
val data = getFetched()
|
||||
if (data and 4 == 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbr3() {
|
||||
val data = getFetched()
|
||||
if (data and 8 == 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbr4() {
|
||||
val data = getFetched()
|
||||
if (data and 16 == 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbr5() {
|
||||
val data = getFetched()
|
||||
if (data and 32 == 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbr6() {
|
||||
val data = getFetched()
|
||||
if (data and 64 == 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbr7() {
|
||||
val data = getFetched()
|
||||
if (data and 128 == 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbs0() {
|
||||
val data = getFetched()
|
||||
if (data and 1 != 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbs1() {
|
||||
val data = getFetched()
|
||||
if (data and 2 != 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbs2() {
|
||||
val data = getFetched()
|
||||
if (data and 4 != 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbs3() {
|
||||
val data = getFetched()
|
||||
if (data and 8 != 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbs4() {
|
||||
val data = getFetched()
|
||||
if (data and 16 != 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbs5() {
|
||||
val data = getFetched()
|
||||
if (data and 32 != 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbs6() {
|
||||
val data = getFetched()
|
||||
if (data and 64 != 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iBbs7() {
|
||||
val data = getFetched()
|
||||
if (data and 128 != 0)
|
||||
PC = fetchedAddressZpr
|
||||
regPC = fetchedAddressZpr
|
||||
}
|
||||
|
||||
private fun iSmb0() {
|
16
src/main/kotlin/razorvine/ksim65/Version.kt
Normal file
16
src/main/kotlin/razorvine/ksim65/Version.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package razorvine.ksim65
|
||||
|
||||
import java.util.*
|
||||
|
||||
object Version {
|
||||
val version: String by lazy {
|
||||
val props = Properties()
|
||||
props.load(javaClass.getResourceAsStream("/version.properties"))
|
||||
props["version"] as String
|
||||
}
|
||||
|
||||
val copyright: String by lazy {
|
||||
"KSim65 6502 cpu simulator v$version by Irmen de Jong (irmen@razorvine.net)" +
|
||||
"\nThis software is free and licensed under the MIT open-source license\n"
|
||||
}
|
||||
}
|
@ -1,9 +1,14 @@
|
||||
package razorvine.ksim65.components
|
||||
|
||||
import razorvine.ksim65.Bus
|
||||
|
||||
typealias UByte = Short
|
||||
typealias Address = Int
|
||||
|
||||
|
||||
/**
|
||||
* Base class for any component connected to the system bus.
|
||||
*/
|
||||
abstract class BusComponent {
|
||||
lateinit var bus: Bus
|
||||
|
||||
@ -11,6 +16,10 @@ abstract class BusComponent {
|
||||
abstract fun reset()
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for components that have registers mapped into the cpu's address space.
|
||||
* Most I/O components fall into this category.
|
||||
*/
|
||||
abstract class MemMappedComponent(val startAddress: Address, val endAddress: Address) : BusComponent() {
|
||||
abstract operator fun get(address: Address): UByte
|
||||
abstract operator fun set(address: Address, data: UByte)
|
||||
@ -38,7 +47,10 @@ abstract class MemMappedComponent(val startAddress: Address, val endAddress: Add
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for components that actually contain memory (RAM or ROM chips).
|
||||
*/
|
||||
abstract class MemoryComponent(startAddress: Address, endAddress: Address) :
|
||||
MemMappedComponent(startAddress, endAddress) {
|
||||
abstract fun cloneContents(): Array<UByte>
|
||||
abstract fun copyOfMem(): Array<UByte>
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
package razorvine.ksim65.components
|
||||
|
||||
/**
|
||||
* A parallel output device (basically, prints bytes as characters to the screen)
|
||||
* First address = data byte (8 parallel bits)
|
||||
* Second address = control byte (bit 0 high = write byte)
|
||||
* A simple parallel output device (basically, prints bytes as characters to the console)
|
||||
*
|
||||
* byte value
|
||||
* ---- ---------
|
||||
* 00 data (the 8 parallel bits)
|
||||
* 01 control latch (set bit 0 to write the data byte)
|
||||
*/
|
||||
class ParallelPort(startAddress: Address, endAddress: Address) : MemMappedComponent(startAddress, endAddress) {
|
||||
private var dataByte: UByte = 0
|
||||
|
@ -3,6 +3,9 @@ package razorvine.ksim65.components
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* A RAM chip with read/write memory.
|
||||
*/
|
||||
class Ram(startAddress: Address, endAddress: Address) : MemoryComponent(startAddress, endAddress) {
|
||||
private val memory = ShortArray(endAddress - startAddress + 1)
|
||||
|
||||
@ -12,7 +15,7 @@ class Ram(startAddress: Address, endAddress: Address) : MemoryComponent(startAdd
|
||||
memory[address - startAddress] = data
|
||||
}
|
||||
|
||||
override fun cloneContents(): Array<UByte> = memory.toTypedArray()
|
||||
override fun copyOfMem(): Array<UByte> = memory.toTypedArray()
|
||||
|
||||
override fun clock() {}
|
||||
|
||||
|
@ -5,17 +5,20 @@ import java.time.LocalTime
|
||||
|
||||
|
||||
/**
|
||||
* A real-time clock (time of day clock).
|
||||
* A real-time time of day clock.
|
||||
* (System timers are elsewhere)
|
||||
*
|
||||
* byte value
|
||||
* 00 year (lsb)
|
||||
* 01 year (msb)
|
||||
* 02 month, 1-12
|
||||
* 03 day, 1-31
|
||||
* 04 hour, 0-23
|
||||
* 05 minute, 0-59
|
||||
* 06 second, 0-59
|
||||
* 07 millisecond, 0-999 (lsb)
|
||||
* 08 millisecond, 0-999 (msb)
|
||||
* ---- ----------
|
||||
* 00 year (lsb)
|
||||
* 01 year (msb)
|
||||
* 02 month, 1-12
|
||||
* 03 day, 1-31
|
||||
* 04 hour, 0-23
|
||||
* 05 minute, 0-59
|
||||
* 06 second, 0-59
|
||||
* 07 millisecond, 0-999 (lsb)
|
||||
* 08 millisecond, 0-999 (msb)
|
||||
*/
|
||||
class RealTimeClock(startAddress: Address, endAddress: Address) : MemMappedComponent(startAddress, endAddress) {
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
package razorvine.ksim65.components
|
||||
|
||||
/**
|
||||
* A ROM chip (read-only memory).
|
||||
*/
|
||||
class Rom(startAddress: Address, endAddress: Address, data: Array<UByte>? = null) : MemoryComponent(startAddress, endAddress) {
|
||||
private val memory =
|
||||
if (data == null)
|
||||
@ -13,8 +16,8 @@ class Rom(startAddress: Address, endAddress: Address, data: Array<UByte>? = null
|
||||
}
|
||||
|
||||
override operator fun get(address: Address): UByte = memory[address - startAddress]
|
||||
override operator fun set(address: Address, data: UByte) {}
|
||||
override fun cloneContents(): Array<UByte> = memory.toTypedArray()
|
||||
override operator fun set(address: Address, data: UByte) { /* read-only */ }
|
||||
override fun copyOfMem(): Array<UByte> = memory.toTypedArray()
|
||||
override fun clock() {}
|
||||
override fun reset() {}
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
package razorvine.ksim65.components
|
||||
|
||||
import razorvine.ksim65.Cpu6502
|
||||
|
||||
/**
|
||||
* A programmable timer. Causes an IRQ or NMI at specified 24-bits intervals.
|
||||
*
|
||||
* byte value
|
||||
* ---- --------------
|
||||
* 00 control register bit 0=enable bit 1=nmi (instead of irq)
|
||||
* 01 24 bits interval value, bits 0-7 (lo)
|
||||
* 02 24 bits interval value, bits 8-15 (mid)
|
||||
* 03 24 bits interval value, bits 16-23 (hi)
|
||||
*
|
||||
*/
|
||||
class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) {
|
||||
private var counter: Int = 0
|
||||
|
@ -1,22 +1,16 @@
|
||||
package razorvine.ksim65
|
||||
package testmain
|
||||
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.Version
|
||||
import razorvine.ksim65.components.*
|
||||
import razorvine.ksim65.components.Cpu6502.Companion.IRQ_vector
|
||||
import razorvine.ksim65.components.Cpu6502.Companion.NMI_vector
|
||||
import razorvine.ksim65.components.Cpu6502.Companion.RESET_vector
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
printSoftwareHeader()
|
||||
println(Version.copyright)
|
||||
startSimulator(args)
|
||||
}
|
||||
|
||||
internal fun printSoftwareHeader() {
|
||||
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
|
||||
println("\nKSim65 6502 cpu simulator v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
|
||||
println("This software is free and licensed under the MIT open-source license\n")
|
||||
}
|
||||
|
||||
|
||||
private fun startSimulator(args: Array<String>) {
|
||||
|
||||
@ -25,12 +19,12 @@ private fun startSimulator(args: Array<String>) {
|
||||
// it determines the priority of reads and writes.
|
||||
val cpu = Cpu6502(true)
|
||||
val ram = Ram(0, 0xffff)
|
||||
ram[RESET_vector] = 0x00
|
||||
ram[RESET_vector + 1] = 0x10
|
||||
ram[IRQ_vector] = 0x00
|
||||
ram[IRQ_vector + 1] = 0x20
|
||||
ram[NMI_vector] = 0x00
|
||||
ram[NMI_vector + 1] = 0x30
|
||||
ram[Cpu6502.RESET_vector] = 0x00
|
||||
ram[Cpu6502.RESET_vector + 1] = 0x10
|
||||
ram[Cpu6502.IRQ_vector] = 0x00
|
||||
ram[Cpu6502.IRQ_vector + 1] = 0x20
|
||||
ram[Cpu6502.NMI_vector] = 0x00
|
||||
ram[Cpu6502.NMI_vector + 1] = 0x30
|
||||
|
||||
// // read the RTC and write the date+time to $2000
|
||||
// for(b in listOf(0xa0, 0x00, 0xb9, 0x00, 0xd1, 0x99, 0x00, 0x20, 0xc8, 0xc0, 0x09, 0xd0, 0xf5, 0x00).withIndex()) {
|
||||
@ -63,7 +57,7 @@ private fun startSimulator(args: Array<String>) {
|
||||
bus.add(ram)
|
||||
bus.reset()
|
||||
|
||||
cpu.Status.I = false // enable interrupts
|
||||
cpu.regP.I = false // enable interrupts
|
||||
|
||||
// TODO
|
||||
// try {
|
1
src/main/resources/version.properties
Normal file
1
src/main/resources/version.properties
Normal file
@ -0,0 +1 @@
|
||||
version=1.0
|
@ -1 +0,0 @@
|
||||
1.0
|
@ -1,5 +1,5 @@
|
||||
import razorvine.ksim65.components.Address
|
||||
import razorvine.ksim65.components.Cpu6502
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.components.Ram
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ class C64KernalStubs(private val ram: Ram) {
|
||||
0xffd2 -> {
|
||||
// CHROUT
|
||||
ram[0x030c] = 0
|
||||
val char = cpu.A.toChar()
|
||||
val char = cpu.regA.toChar()
|
||||
if(char==13.toChar())
|
||||
println()
|
||||
else if(char in ' '..'~')
|
||||
|
@ -1,5 +1,5 @@
|
||||
import razorvine.ksim65.components.Bus
|
||||
import razorvine.ksim65.components.Cpu6502
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.components.Ram
|
||||
import kotlin.test.*
|
||||
|
||||
@ -44,8 +44,8 @@ abstract class FunctionalTestsBase {
|
||||
protected fun runTest(testprogram: String) {
|
||||
ram.loadPrg("src/test/kotlin/6502testsuite/$testprogram")
|
||||
bus.reset()
|
||||
cpu.SP = 0xfd
|
||||
cpu.Status.fromByte(0b00100100)
|
||||
cpu.regSP = 0xfd
|
||||
cpu.regP.fromByte(0b00100100)
|
||||
try {
|
||||
while (cpu.totalCycles < 50000000L) {
|
||||
bus.clock()
|
||||
|
@ -1,4 +1,4 @@
|
||||
import razorvine.ksim65.components.Cpu6502
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import kotlin.test.*
|
||||
|
||||
@ -48,8 +48,8 @@ class Test6502 : TestCommon6502() {
|
||||
|
||||
@Test
|
||||
fun test_adc_ind_indexed_has_page_wrap_bug() {
|
||||
mpu.A = 0x01
|
||||
mpu.X = 0xFF
|
||||
mpu.regA = 0x01
|
||||
mpu.regX = 0xFF
|
||||
// $0000 ADC ($80,X)
|
||||
// $007f Vector to $BBBB (read if page wrapped)
|
||||
// $017f Vector to $ABCD (read if no page wrap)
|
||||
@ -59,16 +59,16 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0xABCD] = 0x01
|
||||
memory[0xBBBB] = 0x02
|
||||
mpu.step()
|
||||
assertEquals(0x03, mpu.A)
|
||||
assertEquals(0x03, mpu.regA)
|
||||
}
|
||||
|
||||
// ADC Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_adc_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0x42
|
||||
mpu.Y = 0x02
|
||||
mpu.regPC = 0x1000
|
||||
mpu.regA = 0x42
|
||||
mpu.regY = 0x02
|
||||
// $1000 ADC ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0x71, 0xff))
|
||||
// Vector
|
||||
@ -79,30 +79,30 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0x2012] = 0x14 // read if no page wrap
|
||||
memory[0x0012] = 0x42 // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x84, mpu.A)
|
||||
assertEquals(0x84, mpu.regA)
|
||||
}
|
||||
|
||||
// LDA Zero Page, X-Indexed
|
||||
|
||||
@Test
|
||||
fun test_lda_zp_x_indexed_page_wraps() {
|
||||
mpu.A = 0x00
|
||||
mpu.X = 0xFF
|
||||
mpu.regA = 0x00
|
||||
mpu.regX = 0xFF
|
||||
// $0000 LDA $80,X
|
||||
writeMem(memory, 0x0000, listOf(0xB5, 0x80))
|
||||
memory[0x007F] = 0x42
|
||||
mpu.step()
|
||||
assertEquals(0x0002, mpu.PC)
|
||||
assertEquals(0x42, mpu.A)
|
||||
assertEquals(0x0002, mpu.regPC)
|
||||
assertEquals(0x42, mpu.regA)
|
||||
}
|
||||
|
||||
// AND Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_and_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0x42
|
||||
mpu.Y = 0x02
|
||||
mpu.regPC = 0x1000
|
||||
mpu.regA = 0x42
|
||||
mpu.regY = 0x02
|
||||
// $1000 AND ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0x31, 0xff))
|
||||
// Vector
|
||||
@ -113,38 +113,38 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0x2012] = 0x00 // read if no page wrap
|
||||
memory[0x0012] = 0xFF // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x42, mpu.A)
|
||||
assertEquals(0x42, mpu.regA)
|
||||
}
|
||||
|
||||
// BRK
|
||||
|
||||
@Test
|
||||
fun test_brk_preserves_decimal_flag_when_it_is_set() {
|
||||
mpu.Status.D = true
|
||||
mpu.regP.D = true
|
||||
// $C000 BRK
|
||||
memory[0xC000] = 0x00
|
||||
mpu.PC = 0xC000
|
||||
mpu.regPC = 0xC000
|
||||
mpu.step()
|
||||
assertTrue(mpu.Status.B)
|
||||
assertTrue(mpu.Status.D)
|
||||
assertTrue(mpu.regP.B)
|
||||
assertTrue(mpu.regP.D)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_brk_preserves_decimal_flag_when_it_is_clear() {
|
||||
// $C000 BRK
|
||||
memory[0xC000] = 0x00
|
||||
mpu.PC = 0xC000
|
||||
mpu.regPC = 0xC000
|
||||
mpu.step()
|
||||
assertTrue(mpu.Status.B)
|
||||
assertFalse(mpu.Status.D)
|
||||
assertTrue(mpu.regP.B)
|
||||
assertFalse(mpu.regP.D)
|
||||
}
|
||||
|
||||
// CMP Indirect, Indexed (X)
|
||||
|
||||
@Test
|
||||
fun test_cmp_ind_x_has_page_wrap_bug() {
|
||||
mpu.A = 0x42
|
||||
mpu.X = 0xFF
|
||||
mpu.regA = 0x42
|
||||
mpu.regX = 0xFF
|
||||
// $0000 CMP ($80,X)
|
||||
// $007f Vector to $BBBB (read if page wrapped)
|
||||
// $017f Vector to $ABCD (read if no page wrap)
|
||||
@ -154,16 +154,16 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0xABCD] = 0x00
|
||||
memory[0xBBBB] = 0x42
|
||||
mpu.step()
|
||||
assertTrue(mpu.Status.Z)
|
||||
assertTrue(mpu.regP.Z)
|
||||
}
|
||||
|
||||
// CMP Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_cmp_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0x42
|
||||
mpu.Y = 0x02
|
||||
mpu.regPC = 0x1000
|
||||
mpu.regA = 0x42
|
||||
mpu.regY = 0x02
|
||||
// $1000 CMP ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0xd1, 0xff))
|
||||
// Vector
|
||||
@ -174,15 +174,15 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0x2012] = 0x14 // read if no page wrap
|
||||
memory[0x0012] = 0x42 // read if page wrapped
|
||||
mpu.step()
|
||||
assertTrue(mpu.Status.Z)
|
||||
assertTrue(mpu.regP.Z)
|
||||
}
|
||||
|
||||
// EOR Indirect, Indexed (X)
|
||||
|
||||
@Test
|
||||
fun test_eor_ind_x_has_page_wrap_bug() {
|
||||
mpu.A = 0xAA
|
||||
mpu.X = 0xFF
|
||||
mpu.regA = 0xAA
|
||||
mpu.regX = 0xFF
|
||||
// $0000 EOR ($80,X)
|
||||
// $007f Vector to $BBBB (read if page wrapped)
|
||||
// $017f Vector to $ABCD (read if no page wrap)
|
||||
@ -192,16 +192,16 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0xABCD] = 0x00
|
||||
memory[0xBBBB] = 0xFF
|
||||
mpu.step()
|
||||
assertEquals(0x55, mpu.A)
|
||||
assertEquals(0x55, mpu.regA)
|
||||
}
|
||||
|
||||
// EOR Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_eor_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0xAA
|
||||
mpu.Y = 0x02
|
||||
mpu.regPC = 0x1000
|
||||
mpu.regA = 0xAA
|
||||
mpu.regY = 0x02
|
||||
// $1000 EOR ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0x51, 0xff))
|
||||
// Vector
|
||||
@ -212,15 +212,15 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0x2012] = 0x00 // read if no page wrap
|
||||
memory[0x0012] = 0xFF // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x55, mpu.A)
|
||||
assertEquals(0x55, mpu.regA)
|
||||
}
|
||||
|
||||
// LDA Indirect, Indexed (X)
|
||||
|
||||
@Test
|
||||
fun test_lda_ind_indexed_x_has_page_wrap_bug() {
|
||||
mpu.A = 0x00
|
||||
mpu.X = 0xff
|
||||
mpu.regA = 0x00
|
||||
mpu.regX = 0xff
|
||||
// $0000 LDA ($80,X)
|
||||
// $007f Vector to $BBBB (read if page wrapped)
|
||||
// $017f Vector to $ABCD (read if no page wrap)
|
||||
@ -230,16 +230,16 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0xABCD] = 0x42
|
||||
memory[0xBBBB] = 0xEF
|
||||
mpu.step()
|
||||
assertEquals(0xEF, mpu.A)
|
||||
assertEquals(0xEF, mpu.regA)
|
||||
}
|
||||
|
||||
// LDA Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_lda_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0x00
|
||||
mpu.Y = 0x02
|
||||
mpu.regPC = 0x1000
|
||||
mpu.regA = 0x00
|
||||
mpu.regY = 0x02
|
||||
// $1000 LDA ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0xb1, 0xff))
|
||||
// Vector
|
||||
@ -250,21 +250,21 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0x2012] = 0x14 // read if no page wrap
|
||||
memory[0x0012] = 0x42 // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x42, mpu.A)
|
||||
assertEquals(0x42, mpu.regA)
|
||||
}
|
||||
|
||||
// LDA Zero Page, X-Indexed
|
||||
|
||||
@Test
|
||||
fun test_lda_zp_x_has_page_wrap_bug() {
|
||||
mpu.A = 0x00
|
||||
mpu.X = 0xFF
|
||||
mpu.regA = 0x00
|
||||
mpu.regX = 0xFF
|
||||
// $0000 LDA $80,X
|
||||
writeMem(memory, 0x0000, listOf(0xB5, 0x80))
|
||||
memory[0x007F] = 0x42
|
||||
mpu.step()
|
||||
assertEquals(0x0002, mpu.PC)
|
||||
assertEquals(0x42, mpu.A)
|
||||
assertEquals(0x0002, mpu.regPC)
|
||||
assertEquals(0x42, mpu.regA)
|
||||
}
|
||||
|
||||
// JMP Indirect
|
||||
@ -275,7 +275,7 @@ class Test6502 : TestCommon6502() {
|
||||
// $0000 JMP ($00)
|
||||
writeMem(memory, 0, listOf(0x6c, 0xff, 0x00))
|
||||
mpu.step()
|
||||
assertEquals(0x6c00, mpu.PC)
|
||||
assertEquals(0x6c00, mpu.regPC)
|
||||
assertEquals((5+ Cpu6502.resetCycles).toLong(), mpu.totalCycles)
|
||||
}
|
||||
|
||||
@ -283,9 +283,9 @@ class Test6502 : TestCommon6502() {
|
||||
|
||||
@Test
|
||||
fun test_ora_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0x00
|
||||
mpu.Y = 0x02
|
||||
mpu.regPC = 0x1000
|
||||
mpu.regA = 0x00
|
||||
mpu.regY = 0x02
|
||||
// $1000 ORA ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0x11, 0xff))
|
||||
// Vector
|
||||
@ -296,7 +296,7 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0x2012] = 0x00 // read if no page wrap
|
||||
memory[0x0012] = 0x42 // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x42, mpu.A)
|
||||
assertEquals(0x42, mpu.regA)
|
||||
}
|
||||
|
||||
// SBC Indexed, Indirect (Y)
|
||||
@ -304,10 +304,10 @@ class Test6502 : TestCommon6502() {
|
||||
@Test
|
||||
fun test_sbc_indexed_ind_y_has_page_wrap_bug() {
|
||||
|
||||
mpu.PC = 0x1000
|
||||
mpu.Status.C = true
|
||||
mpu.A = 0x42
|
||||
mpu.Y = 0x02
|
||||
mpu.regPC = 0x1000
|
||||
mpu.regP.C = true
|
||||
mpu.regA = 0x42
|
||||
mpu.regY = 0x02
|
||||
// $1000 SBC ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0xf1, 0xff))
|
||||
// Vector
|
||||
@ -318,44 +318,44 @@ class Test6502 : TestCommon6502() {
|
||||
memory[0x2012] = 0x02 // read if no page wrap
|
||||
memory[0x0012] = 0x03 // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x3f, mpu.A)
|
||||
assertEquals(0x3f, mpu.regA)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_sbc_bcd_on_immediate_20_minus_0a_carry_unset() {
|
||||
mpu.Status.D = true
|
||||
mpu.Status.C = false
|
||||
mpu.A = 0x20
|
||||
mpu.regP.D = true
|
||||
mpu.regP.C = false
|
||||
mpu.regA = 0x20
|
||||
// $0000 SBC #$0a
|
||||
writeMem(memory, 0x0000, listOf(0xe9, 0x0a))
|
||||
mpu.step()
|
||||
assertEquals(0x0002, mpu.PC)
|
||||
assertEquals(0x1f, mpu.A) // 0x1f on 6502, 0x0f on 65c02
|
||||
assertFalse(mpu.Status.Z)
|
||||
assertTrue(mpu.Status.C)
|
||||
assertFalse(mpu.Status.N)
|
||||
assertFalse(mpu.Status.V)
|
||||
assertEquals(0x0002, mpu.regPC)
|
||||
assertEquals(0x1f, mpu.regA) // 0x1f on 6502, 0x0f on 65c02
|
||||
assertFalse(mpu.regP.Z)
|
||||
assertTrue(mpu.regP.C)
|
||||
assertFalse(mpu.regP.N)
|
||||
assertFalse(mpu.regP.V)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_adc_bcd_on_immediate_9c_plus_9d() {
|
||||
mpu.Status.D = true
|
||||
mpu.Status.C = false
|
||||
mpu.Status.N = true
|
||||
mpu.A = 0x9c
|
||||
mpu.regP.D = true
|
||||
mpu.regP.C = false
|
||||
mpu.regP.N = true
|
||||
mpu.regA = 0x9c
|
||||
// $0000 ADC #$9d
|
||||
// $0002 ADC #$9d
|
||||
writeMem(memory, 0x0000, listOf(0x69, 0x9d))
|
||||
writeMem(memory, 0x0002, listOf(0x69, 0x9d))
|
||||
mpu.step()
|
||||
assertEquals(0x9f, mpu.A)
|
||||
assertTrue(mpu.Status.C)
|
||||
assertEquals(0x9f, mpu.regA)
|
||||
assertTrue(mpu.regP.C)
|
||||
mpu.step()
|
||||
assertEquals(0x0004, mpu.PC)
|
||||
assertEquals(0x93, mpu.A)
|
||||
assertFalse(mpu.Status.Z)
|
||||
assertTrue(mpu.Status.C)
|
||||
assertTrue(mpu.Status.V)
|
||||
assertFalse(mpu.Status.N) // False on 6502, True on 65C02
|
||||
assertEquals(0x0004, mpu.regPC)
|
||||
assertEquals(0x93, mpu.regA)
|
||||
assertFalse(mpu.regP.Z)
|
||||
assertTrue(mpu.regP.C)
|
||||
assertTrue(mpu.regP.V)
|
||||
assertFalse(mpu.regP.N) // False on 6502, True on 65C02
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import razorvine.ksim65.components.Bus
|
||||
import razorvine.ksim65.components.Cpu6502
|
||||
import razorvine.ksim65.components.Cpu65C02
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.Cpu65C02
|
||||
import razorvine.ksim65.components.Ram
|
||||
import kotlin.test.*
|
||||
import kotlin.system.measureNanoTime
|
||||
@ -15,16 +15,16 @@ class Test6502CpuBasics {
|
||||
val bus = Bus()
|
||||
bus.add(cpu)
|
||||
cpu.reset()
|
||||
assertEquals(0xfd, cpu.SP)
|
||||
assertEquals(0xffff, cpu.PC)
|
||||
assertEquals(0xfd, cpu.regSP)
|
||||
assertEquals(0xffff, cpu.regPC)
|
||||
assertEquals(0, cpu.totalCycles)
|
||||
assertEquals(8, cpu.instrCycles)
|
||||
assertEquals(0, cpu.A)
|
||||
assertEquals(0, cpu.X)
|
||||
assertEquals(0, cpu.Y)
|
||||
assertEquals(0, cpu.regA)
|
||||
assertEquals(0, cpu.regX)
|
||||
assertEquals(0, cpu.regY)
|
||||
assertEquals(0, cpu.currentOpcode)
|
||||
assertEquals(Cpu6502.StatusRegister(C = false, Z = false, I = true, D = false, B = false, V = false, N = false), cpu.Status)
|
||||
assertEquals(0b00100100, cpu.Status.asByte())
|
||||
assertEquals(Cpu6502.StatusRegister(C = false, Z = false, I = true, D = false, B = false, V = false, N = false), cpu.regP)
|
||||
assertEquals(0b00100100, cpu.regP.asByte())
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -33,16 +33,16 @@ class Test6502CpuBasics {
|
||||
val bus = Bus()
|
||||
bus.add(cpu)
|
||||
cpu.reset()
|
||||
assertEquals(0xfd, cpu.SP)
|
||||
assertEquals(0xffff, cpu.PC)
|
||||
assertEquals(0xfd, cpu.regSP)
|
||||
assertEquals(0xffff, cpu.regPC)
|
||||
assertEquals(0, cpu.totalCycles)
|
||||
assertEquals(8, cpu.instrCycles)
|
||||
assertEquals(0, cpu.A)
|
||||
assertEquals(0, cpu.X)
|
||||
assertEquals(0, cpu.Y)
|
||||
assertEquals(0, cpu.regA)
|
||||
assertEquals(0, cpu.regX)
|
||||
assertEquals(0, cpu.regY)
|
||||
assertEquals(0, cpu.currentOpcode)
|
||||
assertEquals(Cpu6502.StatusRegister(C = false, Z = false, I = true, D = false, B = false, V = false, N = false), cpu.Status)
|
||||
assertEquals(0b00100100, cpu.Status.asByte())
|
||||
assertEquals(Cpu6502.StatusRegister(C = false, Z = false, I = true, D = false, B = false, V = false, N = false), cpu.regP)
|
||||
assertEquals(0b00100100, cpu.regP.asByte())
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -58,7 +58,7 @@ class Test6502CpuBasics {
|
||||
bus.add(cpu)
|
||||
bus.add(ram)
|
||||
cpu.reset()
|
||||
cpu.PC = 0x1000
|
||||
cpu.regPC = 0x1000
|
||||
|
||||
// warmup
|
||||
while(cpu.totalCycles<5000000)
|
||||
@ -90,7 +90,7 @@ class Test6502CpuBasics {
|
||||
bus.add(cpu)
|
||||
bus.add(ram)
|
||||
cpu.reset()
|
||||
cpu.PC = 0x1000
|
||||
cpu.regPC = 0x1000
|
||||
|
||||
// warmup
|
||||
while(cpu.totalCycles<5000000)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import razorvine.ksim65.components.Bus
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.components.Ram
|
||||
import razorvine.ksim65.components.Cpu6502
|
||||
import razorvine.ksim65.components.Cpu65C02
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.Cpu65C02
|
||||
import java.lang.Exception
|
||||
import kotlin.test.*
|
||||
|
||||
@ -19,7 +19,7 @@ class Test6502Functional {
|
||||
bus.add(cpu)
|
||||
bus.add(ram)
|
||||
cpu.reset()
|
||||
cpu.PC = 0x0400
|
||||
cpu.regPC = 0x0400
|
||||
cpu.addBreakpoint(0x3469) { _, _ ->
|
||||
// reaching this address means successful test result
|
||||
if(cpu.currentOpcode==0x4c)
|
||||
@ -37,7 +37,7 @@ class Test6502Functional {
|
||||
}
|
||||
|
||||
println(cpu.logState())
|
||||
val d = cpu.disassemble(ram, cpu.PC-20, cpu.PC+20)
|
||||
val d = cpu.disassemble(ram, cpu.regPC-20, cpu.regPC+20)
|
||||
println(d.joinToString ("\n"))
|
||||
fail("test failed")
|
||||
}
|
||||
@ -51,7 +51,7 @@ class Test6502Functional {
|
||||
bus.add(cpu)
|
||||
bus.add(ram)
|
||||
cpu.reset()
|
||||
cpu.PC = 0x0400
|
||||
cpu.regPC = 0x0400
|
||||
cpu.addBreakpoint(0x24f1) { _, _ ->
|
||||
// reaching this address means successful test result
|
||||
if(cpu.currentOpcode==0x4c)
|
||||
@ -69,7 +69,7 @@ class Test6502Functional {
|
||||
}
|
||||
|
||||
println(cpu.logState())
|
||||
val d = cpu.disassemble(ram, cpu.PC-20, cpu.PC+20)
|
||||
val d = cpu.disassemble(ram, cpu.regPC-20, cpu.regPC+20)
|
||||
println(d.joinToString ("\n"))
|
||||
fail("test failed")
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
import razorvine.ksim65.components.Cpu6502
|
||||
import razorvine.ksim65.components.Cpu65C02
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.Cpu65C02
|
||||
import razorvine.ksim65.components.Ram
|
||||
import kotlin.test.*
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user