From 66c4033eb4e587654f2542083087b0bad95c4ff4 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 14 Sep 2019 17:25:41 +0200 Subject: [PATCH] fix disassembler address display --- LICENSE | 2 +- README.md | 23 +++++-- src/main/kotlin/razorvine/ksim65/Sim65Main.kt | 5 +- .../razorvine/ksim65/components/Cpu6502.kt | 58 ++++++++--------- src/test/kotlin/Test6502CpuBasics.kt | 61 ++++++++++++++++-- src/test/kotlin/TestDisassembler.kt | 32 ++++----- .../{bcdtest.bin => bcdtest6502.bin} | Bin 7 files changed, 121 insertions(+), 60 deletions(-) rename src/test/resources/{bcdtest.bin => bcdtest6502.bin} (100%) diff --git a/LICENSE b/LICENSE index abfce8a..779c04f 100644 --- a/LICENSE +++ b/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 diff --git a/README.md b/README.md index da0985a..4bf1920 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ [![saythanks](https://img.shields.io/badge/say-thanks-ff69b4.svg)](https://saythanks.io/to/irmen) [![Build Status](https://travis-ci.org/irmen/ksim65.svg?branch=master)](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 ![6502](https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/KL_MOS_6502.jpg/320px-KL_MOS_6502.jpg) -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, diff --git a/src/main/kotlin/razorvine/ksim65/Sim65Main.kt b/src/main/kotlin/razorvine/ksim65/Sim65Main.kt index 049438a..3d1383e 100644 --- a/src/main/kotlin/razorvine/ksim65/Sim65Main.kt +++ b/src/main/kotlin/razorvine/ksim65/Sim65Main.kt @@ -14,7 +14,7 @@ fun main(args: Array) { 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) { bus.clock() } } catch (ix: Cpu6502.InstructionError) { - println("HMMM $ix") - // ignore + println("Hmmm... $ix") } ram.hexDump(0x1000, 0x1020) diff --git a/src/main/kotlin/razorvine/ksim65/components/Cpu6502.kt b/src/main/kotlin/razorvine/ksim65/components/Cpu6502.kt index 315d474..a06c60f 100644 --- a/src/main/kotlin/razorvine/ksim65/components/Cpu6502.kt +++ b/src/main/kotlin/razorvine/ksim65/components/Cpu6502.kt @@ -133,16 +133,16 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() { disassemble(component.cloneContents(), component.startAddress, from, to) fun disassemble(memory: Array, baseAddress: Address, from: Address, to: Address): List { - var address = from - baseAddress + var location = from - baseAddress val spacing1 = " " val spacing2 = " " val spacing3 = " " val result = mutableListOf() - 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" } } diff --git a/src/test/kotlin/Test6502CpuBasics.kt b/src/test/kotlin/Test6502CpuBasics.kt index 8ddd454..9a30d47 100644 --- a/src/test/kotlin/Test6502CpuBasics.kt +++ b/src/test/kotlin/Test6502CpuBasics.kt @@ -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] diff --git a/src/test/kotlin/TestDisassembler.kt b/src/test/kotlin/TestDisassembler.kt index 494a742..4911f28 100644 --- a/src/test/kotlin/TestDisassembler.kt +++ b/src/test/kotlin/TestDisassembler.kt @@ -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) } } diff --git a/src/test/resources/bcdtest.bin b/src/test/resources/bcdtest6502.bin similarity index 100% rename from src/test/resources/bcdtest.bin rename to src/test/resources/bcdtest6502.bin