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

View File

@ -53,24 +53,28 @@
@(SP0Y+i*2) = rnd()
}
c64.SPENA = 255 ; enable all sprites
set_irqvec() ; enable animation
c64.SPENA = 255 ; enable all sprites
c64utils.set_rasterirq(51) ; enable animation
}
}
~ irq {
sub irq() {
c64.EXTCOL--
; float up & wobble horizontally
; @todo for loop with step 2 doesn't work
for ubyte i in 0 to 7 {
@(main.SP0Y+i*2)--
@(main.SP0Y+i+i)--
ubyte r = rnd()
if r>208
@(main.SP0X+i*2)++
@(main.SP0X+i+i)++
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 {
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,
// 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 irqSub = irqBlock?.subScopes()?.get("irq") as? Subroutine
if(irqSub!=null) {
if(irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
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'
// (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) {
val amountOfRtsInAsm = subroutine.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 }
if (amountOfRtsInAsm == 0) {
if (subroutine.amountOfRtsInAsm() == 0) {
if (subroutine.returntypes.isNotEmpty()) {
// for asm subroutines with an address, no statement check is possible.
if (subroutine.asmAddress == null)

View File

@ -101,7 +101,8 @@ class StatementReorderer(private val namespace: INameScope, private val heap: He
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.
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) {
val returnStmt = Return(emptyList(), subroutine.position)
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 })
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() }
when (call) {
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()}"
}
" jsr prog8_lib.${call.toString().toLowerCase()}"
}
Opcode.BREAKPOINT -> {
breakpointCounter++

View File

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

View File

@ -70,9 +70,6 @@ enum class Syscall(val callNr: Short) {
FUNC_SUM_UW(132),
FUNC_SUM_W(133),
FUNC_SUM_F(134),
FUNC_SET_IRQVEC(135),
FUNC_SET_IRQVEC_EXCL(136),
FUNC_RESTORE_IRQVEC(137),
FUNC_MEMCOPY(138)
// 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 bootTime = System.currentTimeMillis()
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 = ""
private set
@ -161,7 +158,7 @@ class StackVm(private var traceOutputFile: String?) {
P_irqd = false
sourceLine = ""
currentIns = this.program[0]
irqStartInstruction = null
irqStartInstruction = labels["irq.irq"] // set to first instr of irq routine, if any
}
fun step(instructionCount: Int = 5000) {
@ -1647,12 +1644,6 @@ class StackVm(private var traceOutputFile: String?) {
val value = heap.get(iterable.heapId)
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 -> {
val numbytes = 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.
(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()
Saves the CPU registers and the status flags.
You can now more or less 'safely' use the registers directly, until you

View File

@ -94,6 +94,44 @@
; ---- 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 ----
; 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 -
_done rts
.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

View File

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