c64 irq handling routines

This commit is contained in:
Irmen de Jong 2019-01-02 02:47:52 +01:00
parent aea1292f92
commit 2f9eabeac7
12 changed files with 186 additions and 90 deletions

View File

@ -6,6 +6,7 @@
ubyte time_changed ubyte time_changed
sub irq() { sub irq() {
; activated automatically if run in StackVm
global_time++ global_time++
time_changed = 1 time_changed = 1
} }
@ -31,7 +32,6 @@
float[len(zcoor)] rotatedz float[len(zcoor)] rotatedz
sub start() { sub start() {
set_irqvec()
while true { while true {
if irq.time_changed { if irq.time_changed {
irq.time_changed = 0 irq.time_changed = 0

View File

@ -53,24 +53,28 @@
@(SP0Y+i*2) = rnd() @(SP0Y+i*2) = rnd()
} }
c64.SPENA = 255 ; enable all sprites c64.SPENA = 255 ; enable all sprites
c64utils.set_rasterirq(51) ; enable animation
set_irqvec() ; enable animation
} }
} }
~ irq { ~ irq {
sub irq() { sub irq() {
c64.EXTCOL--
; float up & wobble horizontally ; float up & wobble horizontally
; @todo for loop with step 2 doesn't work
for ubyte i in 0 to 7 { for ubyte i in 0 to 7 {
@(main.SP0Y+i*2)-- @(main.SP0Y+i+i)--
ubyte r = rnd() ubyte r = rnd()
if r>208 if r>208
@(main.SP0X+i*2)++ @(main.SP0X+i+i)++
else if r<48 else if r<48
@(main.SP0X+i*2)-- @(main.SP0X+i+i)--
} }
c64.EXTCOL++
} }
} }

View File

@ -1658,6 +1658,12 @@ class Subroutine(override val name: String,
override fun toString(): String { override fun toString(): String {
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)" return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
} }
fun amountOfRtsInAsm(): Int = statements
.asSequence()
.filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
} }

View File

@ -100,12 +100,21 @@ class AstChecker(private val namespace: INameScope,
} }
// there can be an optional 'irq' block with a 'irq' subroutine in it, // there can be an optional 'irq' block with a 'irq' subroutine in it,
// which will be used as the 60hz irq routine in the vm if it's present (and enabled via set_irqvec()/set_irqvec_excl()) // which will be used as the 60hz irq routine in the vm if it's present
val irqBlock = module.statements.singleOrNull { it is Block && it.name=="irq" } as? Block? val irqBlock = module.statements.singleOrNull { it is Block && it.name=="irq" } as? Block?
val irqSub = irqBlock?.subScopes()?.get("irq") as? Subroutine val irqSub = irqBlock?.subScopes()?.get("irq") as? Subroutine
if(irqSub!=null) { if(irqSub!=null) {
if(irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty()) if(irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)) checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position))
} else {
// @todo this is a little hack to make the assembler happy;
// certain assembler routines are -for now- always included and *require* an irq.irq routine to be present
val pos = module.statements.last().position
val dummyIrqBlock = Block("irq", address = null, statements = mutableListOf(
Subroutine("irq", listOf(), listOf(), listOf(), listOf(), setOf(), null, true,mutableListOf(), pos)
), position = pos)
dummyIrqBlock.linkParents(module)
module.statements.add(dummyIrqBlock)
} }
} }
@ -238,12 +247,7 @@ class AstChecker(private val namespace: INameScope,
// subroutine must contain at least one 'return' or 'goto' // subroutine must contain at least one 'return' or 'goto'
// (or if it has an asm block, that must contain a 'rts' or 'jmp') // (or if it has an asm block, that must contain a 'rts' or 'jmp')
if(subroutine.statements.count { it is Return || it is Jump } == 0) { if(subroutine.statements.count { it is Return || it is Jump } == 0) {
val amountOfRtsInAsm = subroutine.statements if (subroutine.amountOfRtsInAsm() == 0) {
.asSequence()
.filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
if (amountOfRtsInAsm == 0) {
if (subroutine.returntypes.isNotEmpty()) { if (subroutine.returntypes.isNotEmpty()) {
// for asm subroutines with an address, no statement check is possible. // for asm subroutines with an address, no statement check is possible.
if (subroutine.asmAddress == null) if (subroutine.asmAddress == null)

View File

@ -101,7 +101,8 @@ class StatementReorderer(private val namespace: INameScope, private val heap: He
if(subroutine.returntypes.isEmpty()) { if(subroutine.returntypes.isEmpty()) {
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine. // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
if(subroutine.asmAddress==null) { // and if an assembly block doesn't contain a rts/rti
if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) {
if (subroutine.statements.lastOrNull {it !is VarDecl} !is Return) { if (subroutine.statements.lastOrNull {it !is VarDecl} !is Return) {
val returnStmt = Return(emptyList(), subroutine.position) val returnStmt = Return(emptyList(), subroutine.position)
returnStmt.linkParents(subroutine) returnStmt.linkParents(subroutine)

View File

@ -433,49 +433,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr }) if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr })
throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}") throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}")
val call = Syscall.values().find { it.callNr==ins.arg.numericValue() } val call = Syscall.values().find { it.callNr==ins.arg.numericValue() }
when (call) { " jsr prog8_lib.${call.toString().toLowerCase()}"
Syscall.FUNC_SET_IRQVEC ->
"""
sei
lda #<_prog8_irq_handler
sta c64.CINV
lda #>_prog8_irq_handler
sta c64.CINV+1
cli
jmp +
_prog8_irq_handler jsr irq.irq
jmp c64.IRQDFRT ; continue with normal kernel irq routine
+
"""
Syscall.FUNC_SET_IRQVEC_EXCL ->
"""
sei
lda #<_prog8_irq_handler_excl
sta c64.CINV
lda #>_prog8_irq_handler_excl
sta c64.CINV+1
cli
jmp +
_prog8_irq_handler_excl
jsr irq.irq
lda ${'$'}dc0d ; acknowledge CIA interrupt
jmp c64.IRQDFEND ; end irq processing - don't call kernel
+
"""
Syscall.FUNC_RESTORE_IRQVEC ->
"""
sei
lda #<c64.IRQDFRT
sta c64.CINV
lda #>c64.IRQDFRT
sta c64.CINV+1
cli
"""
else -> " jsr prog8_lib.${call.toString().toLowerCase()}"
}
} }
Opcode.BREAKPOINT -> { Opcode.BREAKPOINT -> {
breakpointCounter++ breakpointCounter++

View File

@ -54,10 +54,7 @@ val BuiltinFunctions = mapOf(
"clear_carry" to FunctionSignature(false, emptyList(), null), "clear_carry" to FunctionSignature(false, emptyList(), null),
"set_irqd" to FunctionSignature(false, emptyList(), null), "set_irqd" to FunctionSignature(false, emptyList(), null),
"clear_irqd" to FunctionSignature(false, emptyList(), null), "clear_irqd" to FunctionSignature(false, emptyList(), null),
"set_irqvec" to FunctionSignature(false, emptyList(), null), "memcopy" to FunctionSignature(false, listOf(
"set_irqvec_excl" to FunctionSignature(false, emptyList(), null),
"restore_irqvec" to FunctionSignature(false, emptyList(), null),
"memcopy" to FunctionSignature(false, listOf(
BuiltinFunctionParam("from", IntegerDatatypes + IterableDatatypes), BuiltinFunctionParam("from", IntegerDatatypes + IterableDatatypes),
BuiltinFunctionParam("to", IntegerDatatypes + IterableDatatypes), BuiltinFunctionParam("to", IntegerDatatypes + IterableDatatypes),
BuiltinFunctionParam("numbytes", IntegerDatatypes)), null), BuiltinFunctionParam("numbytes", IntegerDatatypes)), null),

View File

@ -70,9 +70,6 @@ enum class Syscall(val callNr: Short) {
FUNC_SUM_UW(132), FUNC_SUM_UW(132),
FUNC_SUM_W(133), FUNC_SUM_W(133),
FUNC_SUM_F(134), FUNC_SUM_F(134),
FUNC_SET_IRQVEC(135),
FUNC_SET_IRQVEC_EXCL(136),
FUNC_RESTORE_IRQVEC(137),
FUNC_MEMCOPY(138) FUNC_MEMCOPY(138)
// note: not all builtin functions of the Prog8 language are present as functions: // note: not all builtin functions of the Prog8 language are present as functions:
@ -133,7 +130,7 @@ class StackVm(private var traceOutputFile: String?) {
private val rnd = Random() private val rnd = Random()
private val bootTime = System.currentTimeMillis() private val bootTime = System.currentTimeMillis()
private lateinit var currentIns: Instruction private lateinit var currentIns: Instruction
private var irqStartInstruction: Instruction? = null // set to first instr of irq routine, if any private var irqStartInstruction: Instruction? = null
var sourceLine: String = "" var sourceLine: String = ""
private set private set
@ -161,7 +158,7 @@ class StackVm(private var traceOutputFile: String?) {
P_irqd = false P_irqd = false
sourceLine = "" sourceLine = ""
currentIns = this.program[0] currentIns = this.program[0]
irqStartInstruction = null irqStartInstruction = labels["irq.irq"] // set to first instr of irq routine, if any
} }
fun step(instructionCount: Int = 5000) { fun step(instructionCount: Int = 5000) {
@ -1647,12 +1644,6 @@ class StackVm(private var traceOutputFile: String?) {
val value = heap.get(iterable.heapId) val value = heap.get(iterable.heapId)
evalstack.push(Value(DataType.UBYTE, if (value.array!!.all { v -> v != 0 }) 1 else 0)) evalstack.push(Value(DataType.UBYTE, if (value.array!!.all { v -> v != 0 }) 1 else 0))
} }
Syscall.FUNC_SET_IRQVEC, Syscall.FUNC_SET_IRQVEC_EXCL -> {
irqStartInstruction = labels["irq.irq"]
}
Syscall.FUNC_RESTORE_IRQVEC -> {
irqStartInstruction = null
}
Syscall.FUNC_MEMCOPY -> { Syscall.FUNC_MEMCOPY -> {
val numbytes = evalstack.pop().integerValue() val numbytes = evalstack.pop().integerValue()
val to = evalstack.pop().integerValue() val to = evalstack.pop().integerValue()

View File

@ -633,20 +633,6 @@ set_irqd() / clear_irqd()
Set (or clear) the CPU status register Interrupt Disable flag. No result value. Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction) (translated into ``SEI`` or ``CLI`` cpu instruction)
set_irqvec_excl()
Sets the system's IRQ vector to the special ``irq.irq`` subroutine exclusively -- the system's
default IRQ handler is no longer called. The routine should be defined as a parameterless subroutine
``irq`` in a block ``irq``.
set_irqvec()
Add the special ``irq.irq`` subroutine to the system's IRQ vector -- the system's
default IRQ handler will still be called after your custom subroutine finishes.
The routine should be defined as a parameterless subroutine
``irq`` in a block ``irq``.
restore_irqvec()
Restore the IRQ vector to the default system IRQ handling subroutine.
rsave() rsave()
Saves the CPU registers and the status flags. Saves the CPU registers and the status flags.
You can now more or less 'safely' use the registers directly, until you You can now more or less 'safely' use the registers directly, until you

View File

@ -94,6 +94,44 @@
; ---- end of VIC-II registers ---- ; ---- end of VIC-II registers ----
; ---- CIA 1 & 2 registers ----
memory ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive
memory ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port
memory ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
memory ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
memory ubyte CIA1TALO = $DC04 ; CIA 1 timer A low byte
memory ubyte CIA1TAHI = $DC05 ; CIA 1 timer A high byte
memory ubyte CIA1TBLO = $DC06 ; CIA 1 timer B low byte
memory ubyte CIA1TBHI = $DC07 ; CIA 1 timer B high byte
memory ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
memory ubyte CIA1TODS = $DC09 ; time of day, seconds
memory ubyte CIA1TODM = $DC0A ; time of day, minutes
memory ubyte CIA1TODH = $DC0B ; time of day, hours
memory ubyte CIA1SDR = $DC0C ; Serial Data Register
memory ubyte CIA1ICR = $DC0D
memory ubyte CIA1CRA = $DC0E
memory ubyte CIA1CRB = $DC0F
memory ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
memory ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
memory ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
memory ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
memory ubyte CIA2TALO = $DD04 ; CIA 2 timer A low byte
memory ubyte CIA2TAHI = $DD05 ; CIA 2 timer A high byte
memory ubyte CIA2TBLO = $DD06 ; CIA 2 timer B low byte
memory ubyte CIA2TBHI = $DD07 ; CIA 2 timer B high byte
memory ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
memory ubyte CIA2TODS = $DD09 ; time of day, seconds
memory ubyte CIA2TODM = $DD0A ; time of day, minutes
memory ubyte CIA2TODH = $DD0B ; time of day, hours
memory ubyte CIA2SDR = $DD0C ; Serial Data Register
memory ubyte CIA2ICR = $DD0D
memory ubyte CIA2CRA = $DD0E
memory ubyte CIA2CRB = $DD0F
; ---- end of CIA registers ----
; ---- C64 basic and kernal ROM float constants and functions ---- ; ---- C64 basic and kernal ROM float constants and functions ----
; note: the fac1 and fac2 are working registers and take 6 bytes each, ; note: the fac1 and fac2 are working registers and take 6 bytes each,

View File

@ -408,7 +408,120 @@ _mod2b lda #0 ; self-modified
bne - bne -
_done rts _done rts
.pend .pend
}}
}}
asmsub set_irqvec_excl() -> clobbers(A) -> () {
%asm {{
sei
lda #<_irq_handler
sta c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr irq.irq
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
asmsub set_irqvec() -> clobbers(A) -> () {
%asm {{
sei
lda #<_irq_handler
sta c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr irq.irq
jmp c64.IRQDFRT ; continue with normal kernel irq routine
}}
}
asmsub restore_irqvec() -> clobbers() -> () {
%asm {{
sei
lda #<c64.IRQDFRT
sta c64.CINV
lda #>c64.IRQDFRT
sta c64.CINV+1
lda #0
sta c64.IREQMASK ; disable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
rts
}}
}
asmsub set_rasterirq(uword rasterpos @ AY) -> clobbers(A) -> () {
%asm {{
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr irq.irq
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFRT
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ;enable raster interrupt signals from vic
rts
}}
}
asmsub set_rasterirq_excl(uword rasterpos @ AY) -> clobbers(A) -> () {
%asm {{
sei
jsr set_rasterirq._setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr irq.irq
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
} ; ------ end of block c64utils } ; ------ end of block c64utils

View File

@ -476,7 +476,6 @@ abs_f .proc
add_w .proc add_w .proc
; -- push word+word / uword+uword ; -- push word+word / uword+uword
; @todo INLINE THIS
inx inx
clc clc
lda ESTACK_LO,x lda ESTACK_LO,x
@ -490,7 +489,6 @@ add_w .proc
sub_w .proc sub_w .proc
; -- push word-word ; -- push word-word
; @todo INLINE THIS
inx inx
sec sec
lda ESTACK_LO+1,x lda ESTACK_LO+1,x