mirror of
https://github.com/irmen/prog8.git
synced 2024-10-19 07:23:56 +00:00
implemented IRQ handling
This commit is contained in:
parent
61d4ca1d24
commit
c493c3e5c6
@ -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)
|
||||
}
|
||||
|
@ -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)")
|
||||
}
|
||||
|
||||
}
|
||||
|
10
sim65/src/components/Cpu65C02.kt
Normal file
10
sim65/src/components/Cpu65C02.kt
Normal 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
|
||||
|
||||
}
|
@ -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)" }
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user