mirror of
https://github.com/irmen/prog8.git
synced 2025-02-27 03:29:22 +00:00
cpu unit test suite ported from Py65
This commit is contained in:
parent
bed0e33b4f
commit
42f8e98cab
@ -8,9 +8,9 @@ interface ICpu {
|
||||
fun disassemble(component: MemMappedComponent, from: Address, to: Address) =
|
||||
disassemble(component.cloneMem(), component.startAddress, from, to)
|
||||
|
||||
fun dumpState()
|
||||
fun clock()
|
||||
fun reset()
|
||||
fun step()
|
||||
|
||||
var tracing: Boolean
|
||||
val totalCycles: Int
|
||||
@ -98,17 +98,30 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
Z = (byte and 0b00000010) != 0
|
||||
C = (byte and 0b00000001) != 0
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return asByte().toString(2).padStart(8, '0')
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = asByte().toInt()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other !is StatusRegister)
|
||||
return false
|
||||
return asByte()==other.asByte()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var instrCycles: Int = 0
|
||||
private var A: Int = 0
|
||||
private var X: Int = 0
|
||||
private var Y: Int = 0
|
||||
private var sp: Int = 0
|
||||
private var pc: Address = 0
|
||||
private val status = StatusRegister()
|
||||
private var currentOpcode: Int = 0
|
||||
var instrCycles: Int = 0
|
||||
var A: Int = 0
|
||||
var X: Int = 0
|
||||
var Y: Int = 0
|
||||
var SP: Int = 0
|
||||
var PC: Address = 0
|
||||
val Status = StatusRegister()
|
||||
var currentOpcode: Int = 0
|
||||
var waiting: Boolean = false // 65c02
|
||||
private lateinit var currentInstruction: Instruction
|
||||
|
||||
// data byte from the instruction (only set when addr.mode is Accumulator, Immediate or Implied)
|
||||
@ -221,30 +234,31 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
sp = 0xfd
|
||||
pc = readWord(RESET_vector)
|
||||
SP = 0xfd
|
||||
PC = readWord(RESET_vector)
|
||||
A = 0
|
||||
X = 0
|
||||
Y = 0
|
||||
status.C = false
|
||||
status.Z = false
|
||||
status.I = false
|
||||
status.D = false
|
||||
status.B = false
|
||||
status.V = false
|
||||
status.N = false
|
||||
Status.C = false
|
||||
Status.Z = false
|
||||
Status.I = false
|
||||
Status.D = false
|
||||
Status.B = false
|
||||
Status.V = false
|
||||
Status.N = false
|
||||
instrCycles = 8
|
||||
currentOpcode = 0
|
||||
currentInstruction = opcodes[0]
|
||||
waiting = false
|
||||
}
|
||||
|
||||
override fun clock() {
|
||||
if (instrCycles == 0) {
|
||||
currentOpcode = read(pc++)
|
||||
currentOpcode = read(PC++)
|
||||
currentInstruction = opcodes[currentOpcode]
|
||||
|
||||
if (tracing) {
|
||||
dumpState()
|
||||
printState()
|
||||
}
|
||||
|
||||
if (!currentInstruction.official && !illegalInstrsAllowed) {
|
||||
@ -260,19 +274,25 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
totalCycles++
|
||||
}
|
||||
|
||||
override fun dumpState() {
|
||||
println("cycle:$totalCycles - pc=${hexW(pc)} " +
|
||||
override fun step() {
|
||||
// step a whole instruction
|
||||
while(instrCycles>0) clock() // instruction subcycles
|
||||
clock() // the actual instruction execution cycle
|
||||
}
|
||||
|
||||
fun printState() {
|
||||
println("cycle:$totalCycles - pc=${hexW(PC)} " +
|
||||
"A=${hexB(A)} " +
|
||||
"X=${hexB(X)} " +
|
||||
"Y=${hexB(Y)} " +
|
||||
"SP=${hexB(sp)} " +
|
||||
" n=" + (if (status.N) "1" else "0") +
|
||||
" v=" + (if (status.V) "1" else "0") +
|
||||
" b=" + (if (status.B) "1" else "0") +
|
||||
" d=" + (if (status.D) "1" else "0") +
|
||||
" i=" + (if (status.I) "1" else "0") +
|
||||
" z=" + (if (status.Z) "1" else "0") +
|
||||
" c=" + (if (status.C) "1" else "0") +
|
||||
"SP=${hexB(SP)} " +
|
||||
" n=" + (if (Status.N) "1" else "0") +
|
||||
" v=" + (if (Status.V) "1" else "0") +
|
||||
" b=" + (if (Status.B) "1" else "0") +
|
||||
" d=" + (if (Status.D) "1" else "0") +
|
||||
" i=" + (if (Status.I) "1" else "0") +
|
||||
" z=" + (if (Status.Z) "1" else "0") +
|
||||
" c=" + (if (Status.C) "1" else "0") +
|
||||
" icycles=$instrCycles instr=${hexB(currentOpcode)}:${currentInstruction.mnemonic}"
|
||||
)
|
||||
}
|
||||
@ -306,9 +326,9 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
private fun amRel() {
|
||||
val relative = readPc().toByte()
|
||||
fetchedAddress = if (relative >= 0x80)
|
||||
pc - (256 - relative)
|
||||
PC - (256 - relative)
|
||||
else
|
||||
pc + relative
|
||||
PC + relative
|
||||
}
|
||||
|
||||
private fun amAbs() {
|
||||
@ -362,7 +382,7 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
fetchedAddress = Y + (lo or (hi shl 8))
|
||||
}
|
||||
|
||||
private fun readPc(): Int = bus.read(pc++).toInt()
|
||||
private fun readPc(): Int = bus.read(PC++).toInt()
|
||||
|
||||
private fun pushStackAddr(address: Address) {
|
||||
val lo = address and 255
|
||||
@ -376,13 +396,13 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
}
|
||||
|
||||
private fun pushStack(data: Int) {
|
||||
write(sp or 0x0100, data)
|
||||
sp = (sp - 1) and 0xff
|
||||
write(SP or 0x0100, data)
|
||||
SP = (SP - 1) and 0xff
|
||||
}
|
||||
|
||||
private fun popStack(): Int {
|
||||
sp = (sp + 1) and 0xff
|
||||
return read(sp or 0x0100)
|
||||
SP = (SP + 1) and 0xff
|
||||
return read(SP or 0x0100)
|
||||
}
|
||||
|
||||
private fun popStackAddr(): Address {
|
||||
@ -668,113 +688,113 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
} else {
|
||||
read(fetchedAddress)
|
||||
}
|
||||
if (status.D) {
|
||||
if (Status.D) {
|
||||
// BCD add
|
||||
var lo = (A and 0x0f) + (operand and 0x0f) + if (status.C) 1 else 0
|
||||
var lo = (A and 0x0f) + (operand and 0x0f) + if (Status.C) 1 else 0
|
||||
if (lo and 0xff > 9) lo += 6
|
||||
var hi = (A shr 4) + (operand shr 4) + if (lo > 15) 1 else 0
|
||||
if (hi and 0xff > 9) hi += 6
|
||||
var result = lo and 0x0f or (hi shl 4)
|
||||
result = result and 0xff
|
||||
status.C = hi > 15
|
||||
status.Z = result == 0
|
||||
status.V = false // BCD never sets overflow flag
|
||||
status.N = false // BCD is never negative on NMOS 6502 (bug)
|
||||
Status.C = hi > 15
|
||||
Status.Z = result == 0
|
||||
Status.V = false // BCD never sets overflow flag
|
||||
Status.N = false // BCD is never negative on NMOS 6502 (bug)
|
||||
} else {
|
||||
// normal add
|
||||
val result = A + operand + if (status.C) 1 else 0
|
||||
status.C = result > 255
|
||||
status.V = (A xor operand).inv() and (A xor result) and 0x0080 != 0
|
||||
val result = A + operand + if (Status.C) 1 else 0
|
||||
Status.C = result > 255
|
||||
Status.V = (A xor operand).inv() and (A xor result) and 0x0080 != 0
|
||||
A = result and 255
|
||||
status.N = (A and 0b10000000) != 0
|
||||
status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
Status.Z = A == 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun iAnd() {
|
||||
A = A and fetchedData
|
||||
status.Z = A == 0
|
||||
status.N = (A and 0b10000000) != 0
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
}
|
||||
|
||||
private fun iAsl() {
|
||||
if (currentInstruction.mode == AddrMode.Acc) {
|
||||
status.C = (A and 0b10000000) == 1
|
||||
Status.C = (A and 0b10000000) == 1
|
||||
A = (A shl 1) and 255
|
||||
status.Z = A == 0
|
||||
status.N = (A and 0b10000000) == 1
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) == 1
|
||||
} else {
|
||||
val data = read(fetchedAddress)
|
||||
status.C = (data and 0b10000000) == 1
|
||||
Status.C = (data and 0b10000000) == 1
|
||||
val shifted = (data shl 1) and 255
|
||||
write(fetchedAddress, shifted)
|
||||
status.Z = shifted == 0
|
||||
status.N = (shifted and 0b10000000) == 1
|
||||
Status.Z = shifted == 0
|
||||
Status.N = (shifted and 0b10000000) == 1
|
||||
}
|
||||
}
|
||||
|
||||
private fun iBcc() {
|
||||
if (!status.C) pc = fetchedAddress
|
||||
if (!Status.C) PC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iBcs() {
|
||||
if (status.C) pc = fetchedAddress
|
||||
if (Status.C) PC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iBeq() {
|
||||
if (status.Z) pc = fetchedAddress
|
||||
if (Status.Z) PC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iBit() {
|
||||
status.Z = (A and fetchedData) == 0
|
||||
status.V = (fetchedData and 0b01000000) != 0
|
||||
status.N = (fetchedData and 0b10000000) != 0
|
||||
Status.Z = (A and fetchedData) == 0
|
||||
Status.V = (fetchedData and 0b01000000) != 0
|
||||
Status.N = (fetchedData and 0b10000000) != 0
|
||||
}
|
||||
|
||||
private fun iBmi() {
|
||||
if (status.N) pc = fetchedAddress
|
||||
if (Status.N) PC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iBne() {
|
||||
if (!status.Z) pc = fetchedAddress
|
||||
if (!Status.Z) PC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iBpl() {
|
||||
if (!status.N) pc = fetchedAddress
|
||||
if (!Status.N) PC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iBrk() {
|
||||
pc++
|
||||
status.I = true
|
||||
status.B = true
|
||||
pushStackAddr(pc)
|
||||
pushStack(status)
|
||||
status.B = false
|
||||
pc = readWord(IRQ_vector)
|
||||
PC++
|
||||
Status.I = true
|
||||
Status.B = true
|
||||
pushStackAddr(PC)
|
||||
pushStack(Status)
|
||||
Status.B = false
|
||||
PC = readWord(IRQ_vector)
|
||||
}
|
||||
|
||||
private fun iBvc() {
|
||||
if (!status.V) pc = fetchedAddress
|
||||
if (!Status.V) PC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iBvs() {
|
||||
if (status.V) pc = fetchedAddress
|
||||
if (Status.V) PC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iClc() {
|
||||
status.C = false
|
||||
Status.C = false
|
||||
}
|
||||
|
||||
private fun iCld() {
|
||||
status.D = false
|
||||
Status.D = false
|
||||
}
|
||||
|
||||
private fun iCli() {
|
||||
status.I = false
|
||||
Status.I = false
|
||||
}
|
||||
|
||||
private fun iClv() {
|
||||
status.V = false
|
||||
Status.V = false
|
||||
}
|
||||
|
||||
private fun iCmp() {
|
||||
@ -784,9 +804,9 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
} else {
|
||||
read(fetchedAddress)
|
||||
}
|
||||
status.C = A >= fetched
|
||||
status.Z = A == fetched
|
||||
status.N = ((A - fetched) and 0b10000000) == 1
|
||||
Status.C = A >= fetched
|
||||
Status.Z = A == fetched
|
||||
Status.N = ((A - fetched) and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iCpx() {
|
||||
@ -796,9 +816,9 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
} else {
|
||||
read(fetchedAddress)
|
||||
}
|
||||
status.C = X >= fetched
|
||||
status.Z = X == fetched
|
||||
status.N = ((X - fetched) and 0b10000000) == 1
|
||||
Status.C = X >= fetched
|
||||
Status.Z = X == fetched
|
||||
Status.N = ((X - fetched) and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iCpy() {
|
||||
@ -808,28 +828,28 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
} else {
|
||||
read(fetchedAddress)
|
||||
}
|
||||
status.C = Y >= fetched
|
||||
status.Z = Y == fetched
|
||||
status.N = ((Y - fetched) and 0b10000000) == 1
|
||||
Status.C = Y >= fetched
|
||||
Status.Z = Y == fetched
|
||||
Status.N = ((Y - fetched) and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iDec() {
|
||||
val data = (read(fetchedAddress) - 1) and 255
|
||||
write(fetchedAddress, data)
|
||||
status.Z = data == 0
|
||||
status.N = (data and 0b10000000) == 1
|
||||
Status.Z = data == 0
|
||||
Status.N = (data and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iDex() {
|
||||
X = (X - 1) and 255
|
||||
status.Z = X == 0
|
||||
status.N = (X and 0b10000000) == 1
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iDey() {
|
||||
Y = (Y - 1) and 255
|
||||
status.Z = Y == 0
|
||||
status.N = (Y and 0b10000000) == 1
|
||||
Status.Z = Y == 0
|
||||
Status.N = (Y and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iEor() {
|
||||
@ -838,36 +858,36 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
} else {
|
||||
A xor read(fetchedAddress)
|
||||
}
|
||||
status.Z = A == 0
|
||||
status.N = (A and 0b10000000) == 1
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iInc() {
|
||||
val data = (read(fetchedAddress) + 1) and 255
|
||||
write(fetchedAddress, data)
|
||||
status.Z = data == 0
|
||||
status.N = (data and 0b10000000) == 1
|
||||
Status.Z = data == 0
|
||||
Status.N = (data and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iInx() {
|
||||
X = (X + 1) and 255
|
||||
status.Z = X == 0
|
||||
status.N = (X and 0b10000000) == 1
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iIny() {
|
||||
Y = (Y + 1) and 255
|
||||
status.Z = Y == 0
|
||||
status.N = (Y and 0b10000000) == 1
|
||||
Status.Z = Y == 0
|
||||
Status.N = (Y and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iJmp() {
|
||||
pc = fetchedAddress
|
||||
PC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iJsr() {
|
||||
pushStackAddr(pc)
|
||||
pc = fetchedAddress
|
||||
pushStackAddr(PC)
|
||||
PC = fetchedAddress
|
||||
}
|
||||
|
||||
private fun iLda() {
|
||||
@ -875,8 +895,8 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
fetchedData
|
||||
else
|
||||
read(fetchedAddress)
|
||||
status.Z = A == 0
|
||||
status.N = (A and 0b10000000) == 1
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iLdx() {
|
||||
@ -884,8 +904,8 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
fetchedData
|
||||
else
|
||||
read(fetchedAddress)
|
||||
status.Z = X == 0
|
||||
status.N = (X and 0b10000000) == 1
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iLdy() {
|
||||
@ -893,23 +913,23 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
fetchedData
|
||||
else
|
||||
read(fetchedAddress)
|
||||
status.Z = Y == 0
|
||||
status.N = (Y and 0b10000000) == 1
|
||||
Status.Z = Y == 0
|
||||
Status.N = (Y and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iLsr() {
|
||||
if (currentInstruction.mode == AddrMode.Acc) {
|
||||
status.C = (A and 1) == 1
|
||||
Status.C = (A and 1) == 1
|
||||
A = A ushr 1
|
||||
status.Z = A == 0
|
||||
status.N = (A and 0b10000000) == 1
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) == 1
|
||||
} else {
|
||||
val data = read(fetchedAddress)
|
||||
status.C = (data and 1) == 1
|
||||
Status.C = (data and 1) == 1
|
||||
val shifted = data ushr 1
|
||||
write(fetchedAddress, shifted)
|
||||
status.Z = shifted == 0
|
||||
status.N = (shifted and 0b10000000) == 1
|
||||
Status.Z = shifted == 0
|
||||
Status.N = (shifted and 0b10000000) == 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -920,8 +940,8 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
A or fetchedData
|
||||
else
|
||||
A or read(fetchedAddress)
|
||||
status.Z = A == 0
|
||||
status.N = (A and 0b10000000) == 1
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iPha() {
|
||||
@ -929,60 +949,60 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
}
|
||||
|
||||
private fun iPhp() {
|
||||
pushStack(status)
|
||||
pushStack(Status)
|
||||
}
|
||||
|
||||
private fun iPla() {
|
||||
A = popStack()
|
||||
status.Z = A == 0
|
||||
status.N = (A and 0b10000000) == 1
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) == 1
|
||||
}
|
||||
|
||||
private fun iPlp() {
|
||||
status.fromByte(popStack())
|
||||
Status.fromByte(popStack())
|
||||
}
|
||||
|
||||
private fun iRol() {
|
||||
val oldCarry = status.C
|
||||
val oldCarry = Status.C
|
||||
if (currentInstruction.mode == AddrMode.Acc) {
|
||||
status.C = (A and 0b10000000) == 1
|
||||
Status.C = (A and 0b10000000) == 1
|
||||
A = (A shl 1) or (if (oldCarry) 1 else 0)
|
||||
status.Z = A == 0
|
||||
status.N = (A and 0b10000000) == 1
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) == 1
|
||||
} else {
|
||||
val data = read(fetchedAddress)
|
||||
status.C = (data and 0b10000000) == 1
|
||||
Status.C = (data and 0b10000000) == 1
|
||||
val shifted = (data shl 1) or (if (oldCarry) 1 else 0) and 255
|
||||
write(fetchedAddress, shifted)
|
||||
status.Z = shifted == 0
|
||||
status.N = (shifted and 0b10000000) == 1
|
||||
Status.Z = shifted == 0
|
||||
Status.N = (shifted and 0b10000000) == 1
|
||||
}
|
||||
}
|
||||
|
||||
private fun iRor() {
|
||||
val oldCarry = status.C
|
||||
val oldCarry = Status.C
|
||||
if (currentInstruction.mode == AddrMode.Acc) {
|
||||
status.C = (A and 1) == 1
|
||||
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) == 1
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) == 1
|
||||
} else {
|
||||
val data = read(fetchedAddress)
|
||||
status.C = (data and 1) == 1
|
||||
Status.C = (data and 1) == 1
|
||||
val shifted = (data ushr 1) or (if (oldCarry) 0b10000000 else 0)
|
||||
write(fetchedAddress, shifted)
|
||||
status.Z = shifted == 0
|
||||
status.N = (shifted and 0b10000000) == 1
|
||||
Status.Z = shifted == 0
|
||||
Status.N = (shifted and 0b10000000) == 1
|
||||
}
|
||||
}
|
||||
|
||||
private fun iRti() {
|
||||
status.fromByte(popStack())
|
||||
pc = popStackAddr()
|
||||
Status.fromByte(popStack())
|
||||
PC = popStackAddr()
|
||||
}
|
||||
|
||||
private fun iRts() {
|
||||
pc = popStackAddr()
|
||||
PC = popStackAddr()
|
||||
}
|
||||
|
||||
private fun iSbc() {
|
||||
@ -991,39 +1011,39 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
} else {
|
||||
read(fetchedAddress)
|
||||
}
|
||||
if (status.D) {
|
||||
var lo = (A and 0x0f) - (operand and 0x0f) - if (status.C) 0 else 1
|
||||
if (Status.D) {
|
||||
var lo = (A and 0x0f) - (operand and 0x0f) - if (Status.C) 0 else 1
|
||||
if (lo and 0x10 != 0) lo -= 6
|
||||
var h = (A shr 4) - (operand shr 4) - if (lo and 0x10 != 0) 1 else 0
|
||||
if (h and 0x10 != 0) h -= 6
|
||||
val result = lo and 0x0f or (h shl 4 and 0xff)
|
||||
status.C = h and 255 < 15
|
||||
status.Z = result == 0
|
||||
status.V = false // BCD never sets overflow flag
|
||||
status.N = false // BCD is never negative on NMOS 6502 (bug)
|
||||
Status.C = h and 255 < 15
|
||||
Status.Z = result == 0
|
||||
Status.V = false // BCD never sets overflow flag
|
||||
Status.N = false // BCD is never negative on NMOS 6502 (bug)
|
||||
A = result and 255
|
||||
} else {
|
||||
// normal sub
|
||||
val invertedOperand = operand xor 255
|
||||
val result = A + invertedOperand + if (status.C) 1 else 0
|
||||
status.C = result > 255
|
||||
status.V = (A xor invertedOperand) and (A xor result) and 0x0080 != 0
|
||||
val result = A + invertedOperand + if (Status.C) 1 else 0
|
||||
Status.C = result > 255
|
||||
Status.V = (A xor invertedOperand) and (A xor result) and 0x0080 != 0
|
||||
A = result and 255
|
||||
status.N = (A and 0b10000000) != 0
|
||||
status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
Status.Z = A == 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun iSec() {
|
||||
status.C = true
|
||||
Status.C = true
|
||||
}
|
||||
|
||||
private fun iSed() {
|
||||
status.D = true
|
||||
Status.D = true
|
||||
}
|
||||
|
||||
private fun iSei() {
|
||||
status.I = true
|
||||
Status.I = true
|
||||
}
|
||||
|
||||
private fun iSta() {
|
||||
@ -1040,36 +1060,36 @@ class Cpu6502(private val illegalInstrsAllowed: Boolean) : BusComponent(), ICpu
|
||||
|
||||
private fun iTax() {
|
||||
X = A
|
||||
status.Z = X == 0
|
||||
status.N = (X and 0b10000000) != 0
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) != 0
|
||||
}
|
||||
|
||||
private fun iTay() {
|
||||
Y = A
|
||||
status.Z = Y == 0
|
||||
status.N = (Y and 0b10000000) != 0
|
||||
Status.Z = Y == 0
|
||||
Status.N = (Y and 0b10000000) != 0
|
||||
}
|
||||
|
||||
private fun iTsx() {
|
||||
X = status.asByte().toInt()
|
||||
status.Z = X == 0
|
||||
status.N = (X and 0b10000000) != 0
|
||||
X = Status.asByte().toInt()
|
||||
Status.Z = X == 0
|
||||
Status.N = (X and 0b10000000) != 0
|
||||
}
|
||||
|
||||
private fun iTxa() {
|
||||
A = X
|
||||
status.Z = A == 0
|
||||
status.N = (A and 0b10000000) != 0
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
}
|
||||
|
||||
private fun iTxs() {
|
||||
status.fromByte(X)
|
||||
Status.fromByte(X)
|
||||
}
|
||||
|
||||
private fun iTya() {
|
||||
A = Y
|
||||
status.Z = A == 0
|
||||
status.N = (A and 0b10000000) != 0
|
||||
Status.Z = A == 0
|
||||
Status.N = (A and 0b10000000) != 0
|
||||
}
|
||||
|
||||
// unofficial/illegal instructions
|
||||
|
@ -11,6 +11,9 @@ class Ram(startAddress: Address, endAddress: Address): MemMappedComponent(startA
|
||||
memory[address-startAddress] = data
|
||||
}
|
||||
|
||||
operator fun get(address: Address) = read(address)
|
||||
operator fun set(address: Address, data: UByte) = write(address, data)
|
||||
|
||||
override fun cloneMem(): Array<UByte> = memory.toTypedArray()
|
||||
|
||||
override fun clock() { }
|
||||
|
321
sim65/test/Test6502.kt
Normal file
321
sim65/test/Test6502.kt
Normal file
@ -0,0 +1,321 @@
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Unit test suite adapted from Py65 https://github.com/mnaberez/py65
|
||||
|
||||
Copyright (c) 2008-2018, Mike Naberezny and contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
|
||||
class Test6502 : TestCommon6502() {
|
||||
// NMOS 6502 tests
|
||||
|
||||
// ADC Indirect, Indexed (X)
|
||||
|
||||
@Test
|
||||
fun test_adc_ind_indexed_has_page_wrap_bug() {
|
||||
mpu.A = 0x01
|
||||
mpu.X = 0xFF
|
||||
// $0000 ADC ($80,X)
|
||||
// $007f Vector to $BBBB (read if page wrapped)
|
||||
// $017f Vector to $ABCD (read if no page wrap)
|
||||
writeMem(memory, 0x0000, listOf(0x61, 0x80))
|
||||
writeMem(memory, 0x007f, listOf(0xBB, 0xBB))
|
||||
writeMem(memory, 0x017f, listOf(0xCD, 0xAB))
|
||||
memory[0xABCD] = 0x01
|
||||
memory[0xBBBB] = 0x02
|
||||
mpu.step()
|
||||
assertEquals(0x03, mpu.A)
|
||||
}
|
||||
|
||||
// ADC Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_adc_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0x42
|
||||
mpu.Y = 0x02
|
||||
// $1000 ADC ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0x71, 0xff))
|
||||
// Vector
|
||||
memory[0x00ff] = 0x10 // low byte
|
||||
memory[0x0100] = 0x20 // high byte if no page wrap
|
||||
memory[0x0000] = 0x00 // high byte if page wrapped
|
||||
// Data
|
||||
memory[0x2012] = 0x14 // read if no page wrap
|
||||
memory[0x0012] = 0x42 // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x84, mpu.A)
|
||||
}
|
||||
|
||||
// LDA Zero Page, X-Indexed
|
||||
|
||||
@Test
|
||||
fun test_lda_zp_x_indexed_page_wraps() {
|
||||
mpu.A = 0x00
|
||||
mpu.X = 0xFF
|
||||
// $0000 LDA $80,X
|
||||
writeMem(memory, 0x0000, listOf(0xB5, 0x80))
|
||||
memory[0x007F] = 0x42
|
||||
mpu.step()
|
||||
assertEquals(0x0002, mpu.PC)
|
||||
assertEquals(0x42, mpu.A)
|
||||
}
|
||||
|
||||
// AND Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_and_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0x42
|
||||
mpu.Y = 0x02
|
||||
// $1000 AND ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0x31, 0xff))
|
||||
// Vector
|
||||
memory[0x00ff] = 0x10 // low byte
|
||||
memory[0x0100] = 0x20 // high byte if no page wrap
|
||||
memory[0x0000] = 0x00 // high byte if page wrapped
|
||||
// Data
|
||||
memory[0x2012] = 0x00 // read if no page wrap
|
||||
memory[0x0012] = 0xFF // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x42, mpu.A)
|
||||
}
|
||||
|
||||
// BRK
|
||||
|
||||
@Test
|
||||
fun test_brk_preserves_decimal_flag_when_it_is_set() {
|
||||
mpu.Status.D = true
|
||||
// $C000 BRK
|
||||
memory[0xC000] = 0x00
|
||||
mpu.PC = 0xC000
|
||||
mpu.step()
|
||||
assertTrue(mpu.Status.B)
|
||||
assertTrue(mpu.Status.D)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_brk_preserves_decimal_flag_when_it_is_clear() {
|
||||
// $C000 BRK
|
||||
memory[0xC000] = 0x00
|
||||
mpu.PC = 0xC000
|
||||
mpu.step()
|
||||
assertTrue(mpu.Status.B)
|
||||
assertFalse(mpu.Status.D)
|
||||
}
|
||||
|
||||
// CMP Indirect, Indexed (X)
|
||||
|
||||
@Test
|
||||
fun test_cmp_ind_x_has_page_wrap_bug() {
|
||||
mpu.A = 0x42
|
||||
mpu.X = 0xFF
|
||||
// $0000 CMP ($80,X)
|
||||
// $007f Vector to $BBBB (read if page wrapped)
|
||||
// $017f Vector to $ABCD (read if no page wrap)
|
||||
writeMem(memory, 0x0000, listOf(0xC1, 0x80))
|
||||
writeMem(memory, 0x007f, listOf(0xBB, 0xBB))
|
||||
writeMem(memory, 0x017f, listOf(0xCD, 0xAB))
|
||||
memory[0xABCD] = 0x00
|
||||
memory[0xBBBB] = 0x42
|
||||
mpu.step()
|
||||
assertTrue(mpu.Status.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
|
||||
// $1000 CMP ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0xd1, 0xff))
|
||||
// Vector
|
||||
memory[0x00ff] = 0x10 // low byte
|
||||
memory[0x0100] = 0x20 // high byte if no page wrap
|
||||
memory[0x0000] = 0x00 // high byte if page wrapped
|
||||
// Data
|
||||
memory[0x2012] = 0x14 // read if no page wrap
|
||||
memory[0x0012] = 0x42 // read if page wrapped
|
||||
mpu.step()
|
||||
assertTrue(mpu.Status.Z)
|
||||
}
|
||||
|
||||
// EOR Indirect, Indexed (X)
|
||||
|
||||
@Test
|
||||
fun test_eor_ind_x_has_page_wrap_bug() {
|
||||
mpu.A = 0xAA
|
||||
mpu.X = 0xFF
|
||||
// $0000 EOR ($80,X)
|
||||
// $007f Vector to $BBBB (read if page wrapped)
|
||||
// $017f Vector to $ABCD (read if no page wrap)
|
||||
writeMem(memory, 0x0000, listOf(0x41, 0x80))
|
||||
writeMem(memory, 0x007f, listOf(0xBB, 0xBB))
|
||||
writeMem(memory, 0x017f, listOf(0xCD, 0xAB))
|
||||
memory[0xABCD] = 0x00
|
||||
memory[0xBBBB] = 0xFF
|
||||
mpu.step()
|
||||
assertEquals(0x55, mpu.A)
|
||||
}
|
||||
|
||||
// EOR Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_eor_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0xAA
|
||||
mpu.Y = 0x02
|
||||
// $1000 EOR ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0x51, 0xff))
|
||||
// Vector
|
||||
memory[0x00ff] = 0x10 // low byte
|
||||
memory[0x0100] = 0x20 // high byte if no page wrap
|
||||
memory[0x0000] = 0x00 // high byte if page wrapped
|
||||
// Data
|
||||
memory[0x2012] = 0x00 // read if no page wrap
|
||||
memory[0x0012] = 0xFF // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x55, mpu.A)
|
||||
}
|
||||
|
||||
// LDA Indirect, Indexed (X)
|
||||
|
||||
@Test
|
||||
fun test_lda_ind_indexed_x_has_page_wrap_bug() {
|
||||
mpu.A = 0x00
|
||||
mpu.X = 0xff
|
||||
// $0000 LDA ($80,X)
|
||||
// $007f Vector to $BBBB (read if page wrapped)
|
||||
// $017f Vector to $ABCD (read if no page wrap)
|
||||
writeMem(memory, 0x0000, listOf(0xA1, 0x80))
|
||||
writeMem(memory, 0x007f, listOf(0xBB, 0xBB))
|
||||
writeMem(memory, 0x017f, listOf(0xCD, 0xAB))
|
||||
memory[0xABCD] = 0x42
|
||||
memory[0xBBBB] = 0xEF
|
||||
mpu.step()
|
||||
assertEquals(0xEF, mpu.A)
|
||||
}
|
||||
|
||||
// LDA Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_lda_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0x00
|
||||
mpu.Y = 0x02
|
||||
// $1000 LDA ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0xb1, 0xff))
|
||||
// Vector
|
||||
memory[0x00ff] = 0x10 // low byte
|
||||
memory[0x0100] = 0x20 // high byte if no page wrap
|
||||
memory[0x0000] = 0x00 // high byte if page wrapped
|
||||
// Data
|
||||
memory[0x2012] = 0x14 // read if no page wrap
|
||||
memory[0x0012] = 0x42 // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x42, mpu.A)
|
||||
}
|
||||
|
||||
// LDA Zero Page, X-Indexed
|
||||
|
||||
@Test
|
||||
fun test_lda_zp_x_has_page_wrap_bug() {
|
||||
mpu.A = 0x00
|
||||
mpu.X = 0xFF
|
||||
// $0000 LDA $80,X
|
||||
writeMem(memory, 0x0000, listOf(0xB5, 0x80))
|
||||
memory[0x007F] = 0x42
|
||||
mpu.step()
|
||||
assertEquals(0x0002, mpu.PC)
|
||||
assertEquals(0x42, mpu.A)
|
||||
}
|
||||
|
||||
// JMP Indirect
|
||||
|
||||
@Test
|
||||
fun test_jmp_jumps_to_address_with_page_wrap_bug() {
|
||||
memory[0x00ff] = 0
|
||||
// $0000 JMP ($00)
|
||||
writeMem(memory, 0, listOf(0x6c, 0xff, 0x00))
|
||||
mpu.step()
|
||||
assertEquals(0x6c00, mpu.PC)
|
||||
assertEquals(5, mpu.totalCycles)
|
||||
}
|
||||
|
||||
// ORA Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_ora_indexed_ind_y_has_page_wrap_bug() {
|
||||
mpu.PC = 0x1000
|
||||
mpu.A = 0x00
|
||||
mpu.Y = 0x02
|
||||
// $1000 ORA ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0x11, 0xff))
|
||||
// Vector
|
||||
memory[0x00ff] = 0x10 // low byte
|
||||
memory[0x0100] = 0x20 // high byte if no page wrap
|
||||
memory[0x0000] = 0x00 // high byte if page wrapped
|
||||
// Data
|
||||
memory[0x2012] = 0x00 // read if no page wrap
|
||||
memory[0x0012] = 0x42 // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x42, mpu.A)
|
||||
}
|
||||
|
||||
// SBC Indexed, Indirect (Y)
|
||||
|
||||
@Test
|
||||
fun test_sbc_indexed_ind_y_has_page_wrap_bug() {
|
||||
|
||||
mpu.PC = 0x1000
|
||||
mpu.Status.C = true
|
||||
mpu.A = 0x42
|
||||
mpu.Y = 0x02
|
||||
// $1000 SBC ($FF),Y
|
||||
writeMem(memory, 0x1000, listOf(0xf1, 0xff))
|
||||
// Vector
|
||||
memory[0x00ff] = 0x10 // low byte
|
||||
memory[0x0100] = 0x20 // high byte if no page wrap
|
||||
memory[0x0000] = 0x00 // high byte if page wrapped
|
||||
// Data
|
||||
memory[0x2012] = 0x02 // read if no page wrap
|
||||
memory[0x0012] = 0x03 // read if page wrapped
|
||||
mpu.step()
|
||||
assertEquals(0x3f, mpu.A)
|
||||
}
|
||||
}
|
25
sim65/test/Test6502CpuBasics.kt
Normal file
25
sim65/test/Test6502CpuBasics.kt
Normal file
@ -0,0 +1,25 @@
|
||||
import org.junit.jupiter.api.Test
|
||||
import sim65.components.Bus
|
||||
import sim65.components.Cpu6502
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class Test6502CpuBasics {
|
||||
|
||||
@Test
|
||||
fun testCpuFlagsAfterReset() {
|
||||
val cpu = Cpu6502(true)
|
||||
val bus = Bus()
|
||||
bus.add(cpu)
|
||||
cpu.reset()
|
||||
assertEquals(0xfd, cpu.SP)
|
||||
assertEquals(0xffff, cpu.PC)
|
||||
assertEquals(0, cpu.totalCycles)
|
||||
assertEquals(8, cpu.instrCycles)
|
||||
assertEquals(0, cpu.A)
|
||||
assertEquals(0, cpu.X)
|
||||
assertEquals(0, cpu.Y)
|
||||
assertEquals(0, cpu.currentOpcode)
|
||||
assertEquals(Cpu6502.StatusRegister(C = false, Z = false, I = false, D = false, B = false, V = false, N = false), cpu.Status)
|
||||
}
|
||||
|
||||
}
|
1547
sim65/test/Test65C02.kt
Normal file
1547
sim65/test/Test65C02.kt
Normal file
File diff suppressed because it is too large
Load Diff
6866
sim65/test/TestCommon6502.kt
Normal file
6866
sim65/test/TestCommon6502.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,15 +10,15 @@ class TestDisassembler {
|
||||
fun testDisassembleAllOpcodes() {
|
||||
val cpu = Cpu6502(true)
|
||||
val memory = Ram(0, 0xffff)
|
||||
memory.load("test/testfiles/disassem_instr_test.prg", 0x1000-2)
|
||||
memory.load("test/testfiles/disassem_instr_test.prg", 0x1000 - 2)
|
||||
val result = cpu.disassemble(memory, 0x1000, 0x1221)
|
||||
assertEquals(256, result.size)
|
||||
assertEquals("\$1000 69 01 adc #\$01", result[0])
|
||||
|
||||
val reference = File("test/testfiles/disassem_ref_output.txt").readLines()
|
||||
assertEquals(256, reference.size)
|
||||
for(line in result.zip(reference)) {
|
||||
if(line.first!=line.second) {
|
||||
for (line in result.zip(reference)) {
|
||||
if (line.first != line.second) {
|
||||
fail("disassembled instruction mismatch: '${line.first}', expected '${line.second}'")
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user