mirror of
https://github.com/irmen/ksim65.git
synced 2025-04-29 12:37:27 +00:00
fix disassembler address display
This commit is contained in:
parent
b64c382968
commit
66c4033eb4
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Irmen de Jong
|
||||
Copyright (c) 2019 Irmen de Jong
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
23
README.md
23
README.md
@ -1,8 +1,7 @@
|
||||
[](https://saythanks.io/to/irmen)
|
||||
[](https://travis-ci.org/irmen/ksim65)
|
||||
|
||||
KSim65 - Kotlin 6502/65C02 microprocessor simulator
|
||||
===================================================
|
||||
# KSim65 - Kotlin/JVM 6502/65C02 microprocessor simulator
|
||||
|
||||
*Written by Irmen de Jong (irmen@razorvine.net)*
|
||||
|
||||
@ -11,8 +10,20 @@ KSim65 - Kotlin 6502/65C02 microprocessor simulator
|
||||
|
||||

|
||||
|
||||
This is a Kotlin library to simulate the 8-bit 6502 and 65C02 microprocessors of the early 80's.
|
||||
This is a Kotlin/JVM library that simulates the 8-bit 6502 and 65C02 microprocessors,
|
||||
which became very popular in the the early 1980's.
|
||||
|
||||
The simulation is cycle precise, includes BCD mode, and all opcodes are implemented (including the 'illegal' opcodes of the 6502).
|
||||
|
||||
On my machine the library can simulate a 6502 running at up to ~100Mhz.
|
||||
Properties of this simulator:
|
||||
|
||||
- Written in Kotlin. It is low-level code, but hopefully still readable :-)
|
||||
- Designed to simulate hardware components (bus, cpu, memory, i/o controllers)
|
||||
- IRQ and NMI simulation
|
||||
- Aims to be clock cycle-precise (not yet 100% correct right now)
|
||||
- Aims to implements all 6502 and 65c02 instructions, including the 'illegal' 6502 instructions (not yet done)
|
||||
- correct BCD mode for adc/sbc instructions on both cpu types
|
||||
- passes several extensive unit test suites that verify instruction and cpu flags behavior
|
||||
- maximum simulated performance is a 6502 running at ~100 Mhz (on my machine)
|
||||
|
||||
## Documentation
|
||||
|
||||
Still to be written. For now,
|
||||
|
@ -14,7 +14,7 @@ fun main(args: Array<String>) {
|
||||
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 licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
|
||||
println("This software is free and licensed under the MIT open-source license\n")
|
||||
}
|
||||
|
||||
|
||||
@ -70,8 +70,7 @@ private fun startSimulator(args: Array<String>) {
|
||||
bus.clock()
|
||||
}
|
||||
} catch (ix: Cpu6502.InstructionError) {
|
||||
println("HMMM $ix")
|
||||
// ignore
|
||||
println("Hmmm... $ix")
|
||||
}
|
||||
|
||||
ram.hexDump(0x1000, 0x1020)
|
||||
|
@ -133,16 +133,16 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
disassemble(component.cloneContents(), component.startAddress, from, to)
|
||||
|
||||
fun disassemble(memory: Array<UByte>, baseAddress: Address, from: Address, to: Address): List<String> {
|
||||
var address = from - baseAddress
|
||||
var location = from - baseAddress
|
||||
val spacing1 = " "
|
||||
val spacing2 = " "
|
||||
val spacing3 = " "
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
while (address <= (to - baseAddress)) {
|
||||
val byte = memory[address]
|
||||
var line = "\$${hexW(address)} ${hexB(byte)} "
|
||||
address++
|
||||
while (location <= (to - baseAddress)) {
|
||||
val byte = memory[location]
|
||||
var line = "\$${hexW(location+baseAddress)} ${hexB(byte)} "
|
||||
location++
|
||||
val opcode = instructions[byte.toInt()]
|
||||
when (opcode.mode) {
|
||||
AddrMode.Acc -> {
|
||||
@ -152,83 +152,83 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
line += "$spacing1 ${opcode.mnemonic}"
|
||||
}
|
||||
AddrMode.Imm -> {
|
||||
val value = memory[address++]
|
||||
val value = memory[location++]
|
||||
line += "${hexB(value)} $spacing2 ${opcode.mnemonic} #\$${hexB(value)}"
|
||||
}
|
||||
AddrMode.Zp -> {
|
||||
val zpAddr = memory[address++]
|
||||
val zpAddr = memory[location++]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)}"
|
||||
}
|
||||
AddrMode.Zpr -> {
|
||||
// addressing mode used by the 65C02, put here for convenience
|
||||
val zpAddr = memory[address++]
|
||||
val rel = memory[address++]
|
||||
val zpAddr = memory[location++]
|
||||
val rel = memory[location++]
|
||||
val target =
|
||||
if (rel <= 0x7f)
|
||||
address + rel
|
||||
location + rel + baseAddress
|
||||
else
|
||||
address - (256 - rel)
|
||||
location - (256 - rel) + baseAddress
|
||||
line += "${hexB(zpAddr)} ${hexB(rel)} $spacing3 ${opcode.mnemonic} \$${hexB(zpAddr)}, \$${hexW(target, true)}"
|
||||
}
|
||||
AddrMode.Izp -> {
|
||||
// addressing mode used by the 65C02, put here for convenience
|
||||
val zpAddr = memory[address++]
|
||||
val zpAddr = memory[location++]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$(${hexB(zpAddr)})"
|
||||
}
|
||||
AddrMode.IaX -> {
|
||||
// addressing mode used by the 65C02, put here for convenience
|
||||
val lo = memory[address++]
|
||||
val hi = memory[address++]
|
||||
val lo = memory[location++]
|
||||
val hi = memory[location++]
|
||||
val absAddr = lo.toInt() or (hi.toInt() shl 8)
|
||||
line += "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$(${hexW(absAddr)},x)"
|
||||
}
|
||||
AddrMode.ZpX -> {
|
||||
val zpAddr = memory[address++]
|
||||
val zpAddr = memory[location++]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},x"
|
||||
}
|
||||
AddrMode.ZpY -> {
|
||||
val zpAddr = memory[address++]
|
||||
val zpAddr = memory[location++]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},y"
|
||||
}
|
||||
AddrMode.Rel -> {
|
||||
val rel = memory[address++]
|
||||
val rel = memory[location++]
|
||||
val target =
|
||||
if (rel <= 0x7f)
|
||||
address + rel
|
||||
location + rel + baseAddress
|
||||
else
|
||||
address - (256 - rel)
|
||||
location - (256 - rel) + baseAddress
|
||||
line += "${hexB(rel)} $spacing2 ${opcode.mnemonic} \$${hexW(target, true)}"
|
||||
}
|
||||
AddrMode.Abs -> {
|
||||
val lo = memory[address++]
|
||||
val hi = memory[address++]
|
||||
val lo = memory[location++]
|
||||
val hi = memory[location++]
|
||||
val absAddr = lo.toInt() or (hi.toInt() shl 8)
|
||||
line += "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)}"
|
||||
}
|
||||
AddrMode.AbsX -> {
|
||||
val lo = memory[address++]
|
||||
val hi = memory[address++]
|
||||
val lo = memory[location++]
|
||||
val hi = memory[location++]
|
||||
val absAddr = lo.toInt() or (hi.toInt() shl 8)
|
||||
line += "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},x"
|
||||
}
|
||||
AddrMode.AbsY -> {
|
||||
val lo = memory[address++]
|
||||
val hi = memory[address++]
|
||||
val lo = memory[location++]
|
||||
val hi = memory[location++]
|
||||
val absAddr = lo.toInt() or (hi.toInt() shl 8)
|
||||
line += "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},y"
|
||||
}
|
||||
AddrMode.Ind -> {
|
||||
val lo = memory[address++]
|
||||
val hi = memory[address++]
|
||||
val lo = memory[location++]
|
||||
val hi = memory[location++]
|
||||
val indirectAddr = lo.toInt() or (hi.toInt() shl 8)
|
||||
line += "${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} (\$${hexW(indirectAddr)})"
|
||||
}
|
||||
AddrMode.IzX -> {
|
||||
val zpAddr = memory[address++]
|
||||
val zpAddr = memory[location++]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)},x)"
|
||||
}
|
||||
AddrMode.IzY -> {
|
||||
val zpAddr = memory[address++]
|
||||
val zpAddr = memory[location++]
|
||||
line += "${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)}),y"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import razorvine.ksim65.components.Bus
|
||||
import razorvine.ksim65.components.Cpu6502
|
||||
import razorvine.ksim65.components.Cpu65C02
|
||||
import razorvine.ksim65.components.Ram
|
||||
import kotlin.test.*
|
||||
import kotlin.system.measureNanoTime
|
||||
@ -9,7 +10,7 @@ import kotlin.test.assertEquals
|
||||
class Test6502CpuBasics {
|
||||
|
||||
@Test
|
||||
fun testCpuFlagsAfterReset() {
|
||||
fun testCpuFlagsAfterReset6502() {
|
||||
val cpu = Cpu6502(true)
|
||||
val bus = Bus()
|
||||
bus.add(cpu)
|
||||
@ -27,7 +28,25 @@ class Test6502CpuBasics {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCpuPerformance() {
|
||||
fun testCpuFlagsAfterReset65c02() {
|
||||
val cpu = Cpu65C02(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 = true, D = false, B = false, V = false, N = false), cpu.Status)
|
||||
assertEquals(0b00100100, cpu.Status.asByte())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCpuPerformance6502() {
|
||||
val cpu = Cpu6502(true)
|
||||
val ram = Ram(0x1000, 0x2000)
|
||||
// load a simple program that loops a few instructions
|
||||
@ -58,14 +77,46 @@ class Test6502CpuBasics {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBCD() {
|
||||
fun testCpuPerformance65C02() {
|
||||
val cpu = Cpu65C02(true)
|
||||
val ram = Ram(0x0000, 0x2000)
|
||||
// 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.PC = 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() {
|
||||
val cpu = Cpu6502(true)
|
||||
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("bcdtest.bin")!!
|
||||
val bytes = javaClass.getResource("bcdtest6502.bin")!! // only works on 6502, not on the 65c02
|
||||
ram.load(bytes, 0x1000)
|
||||
bus.add(ram)
|
||||
bus.reset()
|
||||
@ -79,7 +130,7 @@ class Test6502CpuBasics {
|
||||
}
|
||||
|
||||
if(ram[0x0400] ==0.toShort()) {
|
||||
println("BCD TEST: OK!")
|
||||
println("BCD TEST for 6502: OK!")
|
||||
}
|
||||
else {
|
||||
val code = ram[0x0400]
|
||||
|
@ -75,23 +75,23 @@ ${'$'}0050 00 brk""", result)
|
||||
val cpu = Cpu65C02()
|
||||
val memory = Ram(0, 0x1000)
|
||||
val source = javaClass.classLoader.getResource("disassem_wdc65c02.bin")!!
|
||||
memory.load(source, 0)
|
||||
val resultLines = cpu.disassemble(memory, 0x0000, 0x0015)
|
||||
memory.load(source, 0x200)
|
||||
val resultLines = cpu.disassemble(memory, 0x0200, 0x0215)
|
||||
val result = resultLines.joinToString("\n")
|
||||
assertEquals("""${'$'}0000 cb wai
|
||||
${'$'}0001 db stp
|
||||
${'$'}0002 3a dec a
|
||||
${'$'}0003 1a inc a
|
||||
${'$'}0004 64 12 stz ${'$'}12
|
||||
${'$'}0006 14 12 trb ${'$'}12
|
||||
${'$'}0008 04 12 tsb ${'$'}12
|
||||
${'$'}000a 72 12 adc ${'$'}(12)
|
||||
${'$'}000c b2 12 lda ${'$'}(12)
|
||||
${'$'}000e 92 12 sta ${'$'}(12)
|
||||
${'$'}0010 7c 00 20 jmp ${'$'}(2000,x)
|
||||
${'$'}0013 00 brk
|
||||
${'$'}0014 00 brk
|
||||
${'$'}0015 00 brk""", result)
|
||||
assertEquals("""${'$'}0200 cb wai
|
||||
${'$'}0201 db stp
|
||||
${'$'}0202 3a dec a
|
||||
${'$'}0203 1a inc a
|
||||
${'$'}0204 64 12 stz ${'$'}12
|
||||
${'$'}0206 14 12 trb ${'$'}12
|
||||
${'$'}0208 04 12 tsb ${'$'}12
|
||||
${'$'}020a 72 12 adc ${'$'}(12)
|
||||
${'$'}020c b2 12 lda ${'$'}(12)
|
||||
${'$'}020e 92 12 sta ${'$'}(12)
|
||||
${'$'}0210 7c 00 20 jmp ${'$'}(2000,x)
|
||||
${'$'}0213 00 brk
|
||||
${'$'}0214 00 brk
|
||||
${'$'}0215 00 brk""", result)
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user