cpu unit test suite ported from Py65

This commit is contained in:
Irmen de Jong 2019-09-04 22:23:31 +02:00
parent bed0e33b4f
commit 42f8e98cab
7 changed files with 8950 additions and 168 deletions

View File

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

View File

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

View 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

File diff suppressed because it is too large Load Diff

6866
sim65/test/TestCommon6502.kt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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}'")
}
}