mirror of https://github.com/irmen/ksim65.git
303 lines
9.9 KiB
Kotlin
303 lines
9.9 KiB
Kotlin
import razorvine.ksim65.*
|
|
import razorvine.ksim65.components.Ram
|
|
import kotlin.test.*
|
|
import kotlin.system.measureNanoTime
|
|
import kotlin.test.assertEquals
|
|
|
|
|
|
class Test6502CpuBasics {
|
|
|
|
@Test
|
|
fun testCpuFlagsAfterReset6502() {
|
|
val cpu = Cpu6502()
|
|
val bus = Bus()
|
|
bus.add(cpu)
|
|
cpu.reset()
|
|
assertEquals(0xfd, cpu.regSP)
|
|
assertEquals(0xffff, cpu.regPC)
|
|
assertEquals(0, cpu.totalCycles)
|
|
assertEquals(8, cpu.instrCycles)
|
|
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.regP)
|
|
assertEquals(0b00100100, cpu.regP.asInt())
|
|
}
|
|
|
|
@Test
|
|
fun testCpuFlagsAfterReset65c02() {
|
|
val cpu = Cpu65C02()
|
|
val bus = Bus()
|
|
bus.add(cpu)
|
|
cpu.reset()
|
|
assertEquals(0xfd, cpu.regSP)
|
|
assertEquals(0xffff, cpu.regPC)
|
|
assertEquals(0, cpu.totalCycles)
|
|
assertEquals(8, cpu.instrCycles)
|
|
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.regP)
|
|
assertEquals(0b00100100, cpu.regP.asInt())
|
|
}
|
|
|
|
@Test
|
|
fun testCpuPerformance6502() {
|
|
val cpu = Cpu6502()
|
|
val ram = Ram(0x1000, 0x1fff)
|
|
// load a simple program that loops a few instructions
|
|
for(b in listOf(0xa9, 0x63, 0xaa, 0x86, 0x22, 0x8e, 0x22, 0x22, 0x91, 0x22, 0x6d, 0x33, 0x33, 0xcd, 0x55, 0x55, 0xd0, 0xee, 0xf0, 0xec).withIndex()) {
|
|
ram[b.index] = b.value.toShort()
|
|
}
|
|
|
|
val bus = Bus()
|
|
bus.add(cpu)
|
|
bus.add(ram)
|
|
cpu.reset()
|
|
cpu.regPC = 0x1000
|
|
|
|
// warmup
|
|
while(cpu.totalCycles<5000000)
|
|
cpu.clock()
|
|
|
|
// timing
|
|
val cycles = 100000000
|
|
val duration = measureNanoTime {
|
|
while (cpu.totalCycles < cycles)
|
|
cpu.clock()
|
|
}
|
|
val seconds = duration.toDouble() / 1e9
|
|
val mhz = (cycles.toDouble() / seconds) / 1e6
|
|
println("duration $seconds sec for $cycles = $mhz Mhz")
|
|
|
|
}
|
|
|
|
@Test
|
|
fun testCpuPerformance65C02() {
|
|
val cpu = Cpu65C02()
|
|
val ram = Ram(0x0000, 0x1fff)
|
|
// load a simple program that loops a few instructions
|
|
for(b in listOf(0xa9, 0x63, 0xaa, 0x86, 0x22, 0x8e, 0x22, 0x22, 0x91, 0x22, 0x6d, 0x33, 0x33, 0xcd, 0x55, 0x55,
|
|
0xff, 0xff, 0x79, 0x9e, 0x56, 0x34, 0xd0, 0xe8, 0xf0, 0xe6).withIndex()) {
|
|
ram[0x1000+b.index] = b.value.toShort()
|
|
}
|
|
|
|
val bus = Bus()
|
|
bus.add(cpu)
|
|
bus.add(ram)
|
|
cpu.reset()
|
|
cpu.regPC = 0x1000
|
|
|
|
// warmup
|
|
while(cpu.totalCycles<5000000)
|
|
cpu.clock()
|
|
|
|
// timing
|
|
val cycles = 100000000
|
|
val duration = measureNanoTime {
|
|
while (cpu.totalCycles < cycles)
|
|
cpu.clock()
|
|
}
|
|
val seconds = duration.toDouble() / 1e9
|
|
val mhz = (cycles.toDouble() / seconds) / 1e6
|
|
println("duration $seconds sec for $cycles = $mhz Mhz")
|
|
|
|
}
|
|
|
|
@Test
|
|
fun testBCD6502() {
|
|
// this test only works on 6502, not on the 65c02
|
|
val cpu = Cpu6502()
|
|
val bus = Bus()
|
|
bus.add(cpu)
|
|
val ram = Ram(0, 0xffff)
|
|
ram[Cpu6502.RESET_vector] = 0x00
|
|
ram[Cpu6502.RESET_vector +1] = 0x10
|
|
val bytes = javaClass.getResource("bcdtest6502.bin").readBytes()
|
|
ram.load(bytes, 0x1000)
|
|
bus.add(ram)
|
|
bus.reset()
|
|
|
|
try {
|
|
while (true) {
|
|
bus.clock()
|
|
}
|
|
} catch(e: Cpu6502.InstructionError) {
|
|
// do nothing
|
|
}
|
|
|
|
if(ram[0x0400] ==0.toShort()) {
|
|
println("BCD TEST for 6502: OK!")
|
|
}
|
|
else {
|
|
val code = ram[0x0400]
|
|
val v1 = ram[0x0401]
|
|
val v2 = ram[0x0402]
|
|
val predictedA = ram[0x00fc]
|
|
val actualA = ram[0x00fd]
|
|
val predictedF = ram[0x00fe]
|
|
val actualF = ram[0x00ff]
|
|
println("BCD TEST: FAIL!! code=${hexB(code)} value1=${hexB(v1)} value2=${hexB(v2)}")
|
|
println(" predictedA=${hexB(predictedA)}")
|
|
println(" actualA=${hexB(actualA)}")
|
|
println(" predictedF=${predictedF.toString(2).padStart(8,'0')}")
|
|
println(" actualF=${actualF.toString(2).padStart(8,'0')}")
|
|
fail("BCD test failed")
|
|
}
|
|
}
|
|
|
|
private fun runBCDbeebTest(cpu: Cpu6502, testChoice: Char) {
|
|
// bcd test code from https://github.com/hoglet67/AtomSoftwareArchive/tree/master/tests/clark
|
|
val bus = Bus()
|
|
bus.add(cpu)
|
|
cpu.breakpointForBRK = { _, pc -> fail("brk instruction at \$${hexW(pc)}") }
|
|
cpu.addBreakpoint(0xffee) { cpu, pc ->
|
|
// OSWRCH write character
|
|
print("${cpu.regA.toChar()}")
|
|
Cpu6502.BreakpointResultAction()
|
|
}
|
|
cpu.addBreakpoint(0xffe0) { cpu, pc ->
|
|
// OSRDCH read character
|
|
cpu.regA = testChoice.code
|
|
Cpu6502.BreakpointResultAction()
|
|
}
|
|
val ram = Ram(0, 0xffff)
|
|
val bytes = javaClass.getResource("BCDTEST_beeb.bin").readBytes()
|
|
ram.load(bytes, 0x2900)
|
|
ram[0x0200] = 0x20 // jsr $2900
|
|
ram[0x0201] = 0x00
|
|
ram[0x0202] = 0x29
|
|
ram[0x0203] = 0x00 // brk
|
|
ram[0xffe0] = 0x60 // rts
|
|
ram[0xffee] = 0x60 // rts
|
|
bus.add(ram)
|
|
bus.reset()
|
|
cpu.regPC = 0x0200
|
|
|
|
while (cpu.regPC!=0x203 && cpu.totalCycles < 200000000L) {
|
|
bus.clock()
|
|
}
|
|
|
|
assertEquals(0x0203, cpu.regPC, "test hangs: "+cpu.snapshot())
|
|
assertEquals(0, ram[0x84], "test failed- check the console output for diag message")
|
|
}
|
|
|
|
@Test
|
|
fun testBCDbeeb6502() {
|
|
runBCDbeebTest(Cpu6502(), 'D')
|
|
}
|
|
|
|
@Test
|
|
fun testBCDbeeb65c02() {
|
|
runBCDbeebTest(Cpu65C02(), 'H')
|
|
}
|
|
|
|
@Test
|
|
fun testBRKbreakpoint() {
|
|
val cpu = Cpu6502()
|
|
val bus = Bus()
|
|
val ram = Ram(0, 0x0ffff)
|
|
ram[0xfffe] = 0x00
|
|
ram[0xffff] = 0xc0
|
|
ram[0x3333] = 0xea
|
|
ram[0x3334] = 0xea
|
|
ram[0x200] = 0x00
|
|
bus.add(cpu)
|
|
bus.add(ram)
|
|
bus.reset()
|
|
cpu.regPC = 0x200
|
|
cpu.breakpointForBRK = null
|
|
cpu.step()
|
|
assertEquals(0xc000, cpu.regPC)
|
|
cpu.regPC = 0x200
|
|
cpu.breakpointForBRK = { theCpu, _ ->
|
|
theCpu.regA = 123
|
|
Cpu6502.BreakpointResultAction(changePC = 0x3333)
|
|
}
|
|
cpu.step()
|
|
assertEquals(123, cpu.regA)
|
|
assertEquals(0x3334, cpu.regPC)
|
|
}
|
|
|
|
@Test
|
|
fun testNesTest() {
|
|
// http://www.qmtpro.com/~nes/misc/nestest.txt
|
|
|
|
class NesCpu: Cpu6502() {
|
|
override fun reset() {
|
|
super.reset()
|
|
instrCycles = 7 // the nintdulator cpu emu starts with this number of cycles
|
|
}
|
|
|
|
override fun iAdc(): Boolean {
|
|
// NES cpu doesn't have BCD mode
|
|
val decimal = regP.D
|
|
regP.D = false
|
|
val result = super.iAdc()
|
|
regP.D = decimal
|
|
return result
|
|
}
|
|
|
|
override fun iSbc(operandOverride: Int?): Boolean {
|
|
// NES cpu doesn't have BCD mode
|
|
val decimal = regP.D
|
|
regP.D = false
|
|
val result = super.iSbc(operandOverride)
|
|
regP.D = decimal
|
|
return result
|
|
}
|
|
}
|
|
|
|
val cpu = NesCpu()
|
|
val ram = Ram(0, 0xffff)
|
|
|
|
val bytes = javaClass.getResource("nestest.nes").readBytes().drop(0x10).take(0x4000).toByteArray()
|
|
ram.load(bytes, 0x8000)
|
|
ram.load(bytes, 0xc000)
|
|
val bus = Bus()
|
|
bus.add(cpu)
|
|
bus.add(ram)
|
|
bus.reset()
|
|
cpu.regPC = 0xc000
|
|
var tracingSnapshot = cpu.snapshot()
|
|
cpu.tracing = { tracingSnapshot=it }
|
|
// val disassembler = Disassembler(cpu)
|
|
|
|
val neslog = javaClass.getResource("nestest.log").readText().lineSequence()
|
|
for(logline in neslog) {
|
|
if(logline.isEmpty())
|
|
break
|
|
|
|
cpu.step()
|
|
|
|
val nesAddressHex = logline.substring(0, 4).toInt(16)
|
|
assertEquals(nesAddressHex, tracingSnapshot.PC)
|
|
|
|
// println("NES: $logline")
|
|
// val disassem = disassembler.disassembleOneInstruction(ram.data, tracingSnapshot.PC, 0).first.substring(1)
|
|
// val spaces = " ".substring(disassem.length-1)
|
|
// println("EMU: $disassem $spaces A:${hexB(tracingSnapshot.A)} X:${hexB(tracingSnapshot.X)} Y:${hexB(tracingSnapshot.Y)} P:${hexB(tracingSnapshot.P.asInt())} SP:${hexB(tracingSnapshot.SP)} PPU: 0, 0 CYC:${tracingSnapshot.cycles}")
|
|
|
|
val nesRegsLog = logline.substring(48).split(':')
|
|
val nesA = nesRegsLog[1].substring(0, 2).toShort(16)
|
|
val nesX = nesRegsLog[2].substring(0, 2).toShort(16)
|
|
val nesY = nesRegsLog[3].substring(0, 2).toShort(16)
|
|
val nesP = nesRegsLog[4].substring(0, 2).toInt(16)
|
|
val nesSP = nesRegsLog[5].substring(0, 2).toInt(16)
|
|
val nesCycles = nesRegsLog[7].toLong()
|
|
assertEquals(nesA, tracingSnapshot.A)
|
|
assertEquals(nesX, tracingSnapshot.X)
|
|
assertEquals(nesY, tracingSnapshot.Y)
|
|
assertEquals(nesP, tracingSnapshot.P.asInt())
|
|
assertEquals(nesSP, tracingSnapshot.SP)
|
|
assertEquals(nesCycles, tracingSnapshot.cycles)
|
|
}
|
|
|
|
val two = ram[0x02]
|
|
val three = ram[0x03]
|
|
assertEquals(0, two, "test failed, code ${hexB(two)} ${hexB(three)}")
|
|
}
|
|
}
|