1
0
mirror of https://github.com/irmen/ksim65.git synced 2024-05-29 03:41:30 +00:00

moved stuff around, added some more kdocs

This commit is contained in:
Irmen de Jong 2019-09-14 21:11:20 +02:00
parent 3f0469dbea
commit 9ec77a81b4
23 changed files with 3389 additions and 3318 deletions

View File

@ -26,4 +26,4 @@ Properties of this simulator:
## Documentation
Still to be written. For now,
Still to be written. For now, use the source ;-)

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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() {

View 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"
}
}

View File

@ -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>
}

View File

@ -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

View File

@ -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() {}

View File

@ -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) {

View File

@ -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() {}
}

View File

@ -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

View File

@ -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 {

View File

@ -0,0 +1 @@
version=1.0

View File

@ -1 +0,0 @@
1.0

View File

@ -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 ' '..'~')

View File

@ -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()

View File

@ -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
}
}

View File

@ -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)

View File

@ -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

View File

@ -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.*