implemented IRQ handling

This commit is contained in:
Irmen de Jong 2019-09-09 23:28:41 +02:00
parent 61d4ca1d24
commit c493c3e5c6
10 changed files with 119 additions and 67 deletions

View File

@ -1,6 +1,8 @@
package sim65
import sim65.components.*
import sim65.components.Cpu6502.Companion.IRQ_vector
import sim65.components.Cpu6502.Companion.NMI_vector
import sim65.components.Cpu6502.Companion.RESET_vector
@ -25,6 +27,11 @@ private fun startSimulator(args: Array<String>) {
val ram = Ram(0, 0xffff)
ram[RESET_vector] = 0x00
ram[RESET_vector + 1] = 0x10
ram[IRQ_vector] = 0x00
ram[IRQ_vector + 1] = 0x20
ram[NMI_vector] = 0x00
ram[NMI_vector + 1] = 0x30
// // read the RTC and write the date+time to $2000
// for(b in listOf(0xa0, 0x00, 0xb9, 0x00, 0xd1, 0x99, 0x00, 0x20, 0xc8, 0xc0, 0x09, 0xd0, 0xf5, 0x00).withIndex()) {
// ram[0x1000+b.index] = b.value.toShort()
@ -37,9 +44,16 @@ private fun startSimulator(args: Array<String>) {
}
// load the irq routine that prints 'irq!' to the parallel port
for(b in listOf(0x48, 0xa9, 0x09, 0x8d, 0x00, 0xd0, 0xee, 0x01, 0xd0, 0xa9, 0x12, 0x8d, 0x00, 0xd0,
0xee, 0x01, 0xd0, 0xa9, 0x11, 0x8d, 0x00, 0xd0, 0xee, 0x01, 0xd0, 0xa9, 0x21, 0x8d, 0x00, 0xd0,
0xee, 0x01, 0xd0, 0x68, 0x40).withIndex()) {
ram[0x2000+b.index] = b.value.toShort()
}
val parallel = ParallelPort(0xd000, 0xd001)
val clock = RealTimeClock(0xd100, 0xd108)
val timer = Timer(0xd200, 0xd203)
val timer = Timer(0xd200, 0xd203, cpu)
val bus = Bus()
bus.add(cpu)
@ -49,13 +63,19 @@ private fun startSimulator(args: Array<String>) {
bus.add(ram)
bus.reset()
cpu.Status.I = false // enable interrupts
try {
while (true) {
bus.clock()
}
} catch (ix: InstructionError) {
println("HMMM $ix")
// ignore
}
ram.hexDump(0x1000, 0x1020)
val dis = cpu.disassemble(ram, 0x1000, 0x1020)
println(dis.joinToString("\n"))
ram.hexDump(0x2000, 0x2008)
}

View File

@ -2,29 +2,14 @@ package sim65.components
class InstructionError(msg: String) : RuntimeException(msg)
interface ICpu {
fun disassemble(memory: Array<UByte>, baseAddress: Address, from: Address, to: Address): List<String>
fun disassemble(component: MemoryComponent, from: Address, to: Address) =
disassemble(component.cloneContents(), component.startAddress, from, to)
fun clock()
fun reset()
fun step()
fun breakpoint(address: Address, action: (cpu: ICpu, pc: Address) -> Unit)
var tracing: Boolean
val totalCycles: Long
}
// TODO: implement the illegal opcodes, see http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes
// TODO: add the optional additional cycles to certain instructions and addressing modes
// TODO: add IRQ and NMI signaling.
// TODO: make a 65c02 variant as well (and re-enable the unit tests for that).
class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu {
override var tracing: Boolean = false
override var totalCycles: Long = 0
open class Cpu6502(private val stopOnBrk: Boolean) : BusComponent() {
var tracing: Boolean = false
var totalCycles: Long = 0
private set
companion object {
@ -128,9 +113,11 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu {
var PC: Address = 0
val Status = StatusRegister()
var currentOpcode: Int = 0
var waiting: Boolean = false // 65c02
private lateinit var currentInstruction: Instruction
// has an interrupt been requested?
private var pendingInterrupt: Pair<Boolean, BusComponent>? = null
// data byte from the instruction (only set when addr.mode is Accumulator, Immediate or Implied)
private var fetchedData: Int = 0
@ -153,13 +140,16 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu {
AddrMode.IzY to ::amIzy
)
private val breakpoints = mutableMapOf<Address, (cpu: ICpu, pc: Address) -> Unit>()
private val breakpoints = mutableMapOf<Address, (cpu: Cpu6502, pc: Address) -> Unit>()
override fun breakpoint(address: Address, action: (cpu: ICpu, pc: Address) -> Unit) {
fun breakpoint(address: Address, action: (cpu: Cpu6502, pc: Address) -> Unit) {
breakpoints[address] = action
}
override fun disassemble(memory: Array<UByte>, baseAddress: Address, from: Address, to: Address): List<String> {
fun disassemble(component: MemoryComponent, from: Address, to: Address) =
disassemble(component.cloneContents(), component.startAddress, from, to)
fun disassemble(memory: Array<UByte>, baseAddress: Address, from: Address, to: Address): List<String> {
var address = from - baseAddress
val spacing1 = " "
val spacing2 = " "
@ -258,27 +248,35 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu {
instrCycles = resetCycles // a reset takes time as well
currentOpcode = 0
currentInstruction = opcodes[0]
waiting = false
}
override fun clock() {
if (instrCycles == 0) {
currentOpcode = read(PC)
currentInstruction = opcodes[currentOpcode]
if (tracing) printState()
if (pendingInterrupt != null) {
// NMI or IRQ interrupt.
// handled by the BRK instruction logic.
currentOpcode = 0
currentInstruction = opcodes[0]
} else {
// no interrupt, continue with next instruction
currentOpcode = read(PC)
currentInstruction = opcodes[currentOpcode]
breakpoints[PC]?.let {
val oldPC = PC
val oldOpcode = currentOpcode
it(this, PC)
if (PC != oldPC)
return clock()
if (oldOpcode != currentOpcode)
currentInstruction = opcodes[currentOpcode]
}
if (tracing) printState()
if (currentOpcode == 0 && stopOnBrk) {
throw InstructionError("stopped on BRK instruction at ${hexW(PC)}")
breakpoints[PC]?.let {
val oldPC = PC
val oldOpcode = currentOpcode
it(this, PC)
if (PC != oldPC)
return clock()
if (oldOpcode != currentOpcode)
currentInstruction = opcodes[currentOpcode]
}
if (currentOpcode == 0 && stopOnBrk) {
throw InstructionError("stopped on BRK instruction at ${hexW(PC)}")
}
}
PC++
@ -291,13 +289,22 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu {
totalCycles++
}
override fun step() {
fun step() {
// step a whole instruction
while (instrCycles > 0) clock() // remaining instruction subcycles from the previous instruction
clock() // the actual instruction execution cycle
while (instrCycles > 0) clock() // instruction subcycles
}
fun nmi(source: BusComponent) {
pendingInterrupt = Pair(true, source)
}
fun irq(source: BusComponent) {
if (!Status.I)
pendingInterrupt = Pair(false, source)
}
fun printState() {
println("cycle:$totalCycles - pc=${hexW(PC)} " +
"A=${hexB(A)} " +
@ -794,12 +801,21 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu {
}
private fun iBrk() {
PC++
pushStackAddr(PC)
Status.B = true
// handle BRK ('software interrupt') or a real hardware IRQ
val interrupt = pendingInterrupt
val nmi = interrupt?.first == true
if (interrupt != null) {
pushStackAddr(PC - 1)
} else {
PC++
pushStackAddr(PC)
}
Status.B = interrupt == null
pushStack(Status)
Status.I = true
PC = readWord(IRQ_vector)
Status.I = true // interrupts are now disabled
// NMOS 6502 doesn't clear the D flag (CMOS version does...)
PC = readWord(if (nmi) NMI_vector else IRQ_vector)
pendingInterrupt = null
}
private fun iBvc() {
@ -1167,5 +1183,4 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu {
private fun iXaa() {
TODO("xaa - ('illegal' instruction)")
}
}

View File

@ -0,0 +1,10 @@
package sim65.components
class Cpu65C02(stopOnBrk: Boolean): Cpu6502(stopOnBrk) {
val waiting: Boolean = false
// TODO implement this CPU type 65C02, and re-enable the unit tests for that
}

View File

@ -8,7 +8,7 @@ import sim65.Petscii
* Second address = control byte (bit 0 high = write byte)
*/
class ParallelPort(startAddress: Address, endAddress: Address) : MemMappedComponent(startAddress, endAddress) {
private var dataByte: UByte = 0
var dataByte: UByte = 0
init {
require(endAddress - startAddress + 1 == 2) { "parallel needs exactly 2 memory bytes (data + control)" }

View File

@ -8,11 +8,18 @@ package sim65.components
* 02 24 bits interval value, bits 8-15 (mid)
* 03 24 bits interval value, bits 16-23 (hi)
*/
class Timer(startAddress: Address, endAddress: Address) : MemMappedComponent(startAddress, endAddress) {
class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) {
private var counter: Int = 0
private var interval: Int = 0
private var enabled = false
private var nmi = false
private var enabled = false
set(value) {
if(value && !field) {
// timer is set to enabled (was disabled) - reset the counter
counter = 0
}
field = value
}
init {
require(endAddress - startAddress + 1 == 4) { "timer needs exactly 4 memory bytes" }
@ -23,9 +30,9 @@ class Timer(startAddress: Address, endAddress: Address) : MemMappedComponent(sta
counter++
if (counter == interval) {
if (nmi)
println("TODO: timer causes CPU nmi $counter") // TODO
cpu.nmi(this)
else
println("TODO: timer causes CPU irq $counter") // TODO
cpu.irq(this)
counter = 0
}
}
@ -63,13 +70,8 @@ class Timer(startAddress: Address, endAddress: Address) : MemMappedComponent(sta
when (address - startAddress) {
0 -> {
val i = data.toInt()
val newEnabled = (i and 0b00000001) != 0
enabled = (i and 0b00000001) != 0
nmi = (i and 0b00000010) != 0
if(newEnabled && !enabled) {
// timer is set to enabled (was disabled) - reset the counter
counter = 0
}
enabled = newEnabled
}
1 -> {
interval = (interval and 0x7fffff00) or data.toInt()

View File

@ -1,13 +1,11 @@
import sim65.Petscii
import sim65.components.Address
import sim65.components.Cpu6502
import sim65.components.ICpu
import sim65.components.Ram
class C64KernalStubs(private val ram: Ram) {
fun handleBreakpoint(cpu: ICpu, pc: Address) {
cpu as Cpu6502
fun handleBreakpoint(cpu: Cpu6502, pc: Address) {
when(pc) {
0xffd2 -> {
// CHROUT
@ -21,7 +19,7 @@ class C64KernalStubs(private val ram: Ram) {
}
0xffe4 -> {
// GETIN
throw InputRequired()
throw KernalInputRequired()
// print("[Input required:] ")
// val s = readLine()
// if(s.isNullOrEmpty())
@ -31,7 +29,7 @@ class C64KernalStubs(private val ram: Ram) {
// cpu.currentOpcode = 0x60 // rts to end the stub
}
0xe16f -> {
throw LoadNextPart()
throw KernalLoadNextPart()
// LOAD/VERIFY
// val loc = ram[0xbb].toInt() or (ram[0xbc].toInt() shl 8)
// val len = ram[0xb7].toInt()
@ -44,6 +42,6 @@ class C64KernalStubs(private val ram: Ram) {
}
}
class LoadNextPart: Exception()
class InputRequired: Exception()
internal class KernalLoadNextPart: Exception()
internal class KernalInputRequired: Exception()

View File

@ -43,6 +43,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
class Test6502 : TestCommon6502() {
// NMOS 6502 tests
override fun createCpu() = Cpu6502(false)
// ADC Indirect, Indexed (X)
@Test

View File

@ -58,9 +58,9 @@ class Test6502TestSuite {
fail("test hangs")
} catch (e: InstructionError) {
println(">>> INSTRUCTION ERROR: ${e.message}")
} catch (le: LoadNextPart) {
} catch (le: KernalLoadNextPart) {
return // test ok
} catch (ie: InputRequired) {
} catch (ie: KernalInputRequired) {
fail("test failed")
}
fail("test failed")

View File

@ -1,6 +1,7 @@
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import sim65.components.Cpu65C02
import kotlin.test.*
@ -40,9 +41,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
@Disabled("there is no 65C02 cpu implementation at this time") // TODO create a 65c02 cpu!
@Disabled("there is no 65C02 cpu implementation at this time") // TODO create a 65c02 cpu and enable this again
class Test65C02 : TestCommon6502() {
// CMOS 65C02 Tests
override fun createCpu() = Cpu65C02(false)
// Reset
@ -1535,6 +1537,7 @@ class Test65C02 : TestCommon6502() {
@Test
fun test_wai_sets_waiting() {
mpu as Cpu65C02
assertFalse(mpu.waiting)
// $0240 WAI
memory[0x0204] = 0xcb

View File

@ -45,10 +45,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
abstract class TestCommon6502 {
// Tests common to 6502-based microprocessors
val mpu = Cpu6502(stopOnBrk = false) // TODO make a 65C02 cpu as well and let the subclasses testsuites define the appropriate instance
val mpu = createCpu()
val memory = Ram(0, 0xffff)
val bus = Bus()
abstract fun createCpu(): Cpu6502
init {
bus.add(mpu)
bus.add(memory)