How to implement bareback interrupts?
How to implement bareback interrupts?
As mentioned before, I'm approaching the X16 development from the cartridge side of things, which means, I already occupy the ROM space and really would like to not use the system ROM to prevent constant bank switching. Things work pretty well and I'm now at the point that I need a system tick. Just like the official ROM, I plan to use the VBLANK IRQ for this, but I'm struggeling to understand how to do this. So please, if somebody could help me out in bridging the gaps in my understanding.
Coming from the C64, one would hide the ROM bank and write the custom IRQ vector in FFFE/FFFF (If I remember correctly, it is years, since I did this the last time). But from my understanding, the X16 has in no configuration a writable area from C000? Or does it? From my understanding, all other banks than bank 0 are masked out for the vectors anyway, right?
So, how - or better where - would I hook in my bareback IRQ, which does the timing handling, reading joysticks and keyboard and such? Am I understanding the concept correctly in the first place?
Thanks in advance - any help is very appreciated here...
Coming from the C64, one would hide the ROM bank and write the custom IRQ vector in FFFE/FFFF (If I remember correctly, it is years, since I did this the last time). But from my understanding, the X16 has in no configuration a writable area from C000? Or does it? From my understanding, all other banks than bank 0 are masked out for the vectors anyway, right?
So, how - or better where - would I hook in my bareback IRQ, which does the timing handling, reading joysticks and keyboard and such? Am I understanding the concept correctly in the first place?
Thanks in advance - any help is very appreciated here...
- ahenry3068
- Posts: 1136
- Joined: Tue Apr 04, 2023 9:57 pm
Re: How to implement bareback interrupts?
Search on GITHUB for MooingLemurs ZSMKIT there is good example code in that library. On the X16 you probably don't want to completely disable the system interrupt, what you want to do is CHAIN it with yours. Nothing hardware stopping you from completely replacing it but it does stuff like read the keyboard and mouse and updates the System Timer variable.(which is separate from the RTC)SunSailor wrote: ↑Thu Aug 01, 2024 9:21 pm As mentioned before, I'm approaching the X16 development from the cartridge side of things, which means, I already occupy the ROM space and really would like to not use the system ROM to prevent constant bank switching. Things work pretty well and I'm now at the point that I need a system tick. Just like the official ROM, I plan to use the VBLANK IRQ for this, but I'm struggeling to understand how to do this. So please, if somebody could help me out in bridging the gaps in my understanding.
Coming from the C64, one would hide the ROM bank and write the custom IRQ vector in FFFE/FFFF (If I remember correctly, it is years, since I did this the last time). But from my understanding, the X16 has in no configuration a writable area from C000? Or does it? From my understanding, all other banks than bank 0 are masked out for the vectors anyway, right?
So, how - or better where - would I hook in my bareback IRQ, which does the timing handling, reading joysticks and keyboard and such? Am I understanding the concept correctly in the first place?
Thanks in advance - any help is very appreciated here...
Re: How to implement bareback interrupts?
Thanks so far, had a look in it, although I still don't get, why my code doesn't work, as I already tried exactly, what he does there as well... It actually works, if I chain into the existing IRQ, but if I return with a RTI, the system crashes. I'm sure, I'm still missing something. My code so far, maybe somebody has an idea (and it may help some others, crossing here from searching):
Edit: Ok, got it working, forgot to call ply/ply/pla before RTIing! This source is working now:
Code: Select all
.include "cx16.inc"
.export _setup_irq
_setup_irq:
nop
php
sei
lda IRQVec
sta _old_isr+1
lda IRQVec+1
sta _old_isr+2
lda #<loop_irq
sta IRQVec
lda #>loop_irq
sta IRQVec+1
cli
plp
lda #1
sta $9F27
rts
loop_irq:
jsr handle_clock
jsr handle_joystick
jsr handle_keyboard
lda #1
sta $9F27
;rti
_old_isr:
jmp $ffff
handle_clock:
inc $22
rts
handle_joystick:
rts
handle_keyboard:
rts
Code: Select all
.include "cx16.inc"
.export _setup_irq
_setup_irq:
nop
php
sei
lda #<loop_irq
sta IRQVec
lda #>loop_irq
sta IRQVec+1
cli
plp
lda #1
sta $9F27
rts
loop_irq:
jsr handle_clock
jsr handle_joystick
jsr handle_keyboard
lda #1
sta $9F27
ply
plx
pla
rti
handle_clock:
inc $22
rts
handle_joystick:
rts
handle_keyboard:
rts
Last edited by SunSailor on Thu Aug 01, 2024 10:28 pm, edited 1 time in total.
- kliepatsch
- Posts: 247
- Joined: Thu Oct 08, 2020 9:54 pm
Re: How to implement bareback interrupts?
Yes, that looks about right. The Kernal's ISR backs up the processor registers before it calls the function at the IRQ vector. If you return yourself you have to undo that. If you chain the old ISR, then it will take care of that.
Re: How to implement bareback interrupts?
And yet, my code works well on a vanilla booted system, but not from my cartridge. The interrupt doesn't get called at all anymore, if setuped from the cartridge entry point.
My expectation from browsing through the kernal code is, that system interrupts were registered already at the boot time of the cartridge. Is this assumption correct?
Further, from the masking statement in the docs, my expectation is, that interrupts - which I still expect to origin from $FFFE/$FFFF, as it is an 6502 rooted system - are working with any ROM bank set. How about this assumption? Or are interrupts simply disabled, if another ROM bank than 0 is selected? Can this even be a misbehaviour of the emulator?
Or am I missing simply some VERA setup I'm not aware of?
I'm a bit stuck here, because I need to rely on a lot of assumptions I can't validate at the moment, any further help or explaination is very appreciated
Edit: Investigated it further. in 00:FFFE/FFFF is the to be expected vector to $038b. A breakpoint there is unreached, which tells me, the interrupt isn't triggered at all. Conclusion is, that there must be something missing in my initialization code. I already extended it with these lines, but that doesn't change anything. Setting the raster line shouldn't be necessary, as I only want the VBLANK IRQ, but shouldn't harm as well. Tried with 3 or 7 into IRQ_EN, didn't change anything either.
Edit2: It keeps getting weirder, there's something, I must be missing. If I remove the write of the clear bit in $9F27 in the initialization code, the interrupt gets at least called once. If I don't set the clear bit in the IRQ routine, my other code doesn't get executed anymore - I guess, that's because the interrupt is constantly called and doesn't leave time for that? Would make sense. But what doesn't is, that if I clear that bit, the IRQ isn't triggered anymore. Any ideas?
My expectation from browsing through the kernal code is, that system interrupts were registered already at the boot time of the cartridge. Is this assumption correct?
Further, from the masking statement in the docs, my expectation is, that interrupts - which I still expect to origin from $FFFE/$FFFF, as it is an 6502 rooted system - are working with any ROM bank set. How about this assumption? Or are interrupts simply disabled, if another ROM bank than 0 is selected? Can this even be a misbehaviour of the emulator?
Or am I missing simply some VERA setup I'm not aware of?
I'm a bit stuck here, because I need to rely on a lot of assumptions I can't validate at the moment, any further help or explaination is very appreciated
Edit: Investigated it further. in 00:FFFE/FFFF is the to be expected vector to $038b. A breakpoint there is unreached, which tells me, the interrupt isn't triggered at all. Conclusion is, that there must be something missing in my initialization code. I already extended it with these lines, but that doesn't change anything. Setting the raster line shouldn't be necessary, as I only want the VBLANK IRQ, but shouldn't harm as well. Tried with 3 or 7 into IRQ_EN, didn't change anything either.
Code: Select all
; Setup first VBLANK
lda #1
sta VERA::IRQ_EN
sta VERA::IRQ_FLAGS
stz VERA::IRQ_RASTER
Last edited by SunSailor on Fri Aug 02, 2024 5:03 pm, edited 5 times in total.
- kliepatsch
- Posts: 247
- Joined: Thu Oct 08, 2020 9:54 pm
Re: How to implement bareback interrupts?
No idea, to be honest.
From reading the docs, I would have expected that as soon as control is handed to the code in cartridge ROM, the Kernal has finished hardware initialization and the main difference to running code from RAM is that the VERA interrupts are disabled (though it isn't specified what exactly is meant by disabled interrupts -- and also I don't know whether hardware initialization means IRQ vectors are initialized).
The emulator readme explains that cartridge ROMs don't need IRQ vectors at the top of their memory as the vectors are always fetched from ROM page 0. Just a hypothesis, maybe this isn't working in the emulator? Or maybe this has the side effect that the ROM page is switched to page zero (permanently)?
To find out if the problem lies with the ROM page itself and not with any of what the Kernal does differently in a Cartridge bootup, one could decouple one from the other:
In the cartridge, you basically just load another test program into low RAM and jump to that. In that test program, you have your ISR, the initialization and the main loop.
That way you can have the Cartridge boot process combined with a normal low RAM execution.
If I can help with how to load a program in assembly, I can share some code.
From reading the docs, I would have expected that as soon as control is handed to the code in cartridge ROM, the Kernal has finished hardware initialization and the main difference to running code from RAM is that the VERA interrupts are disabled (though it isn't specified what exactly is meant by disabled interrupts -- and also I don't know whether hardware initialization means IRQ vectors are initialized).
The emulator readme explains that cartridge ROMs don't need IRQ vectors at the top of their memory as the vectors are always fetched from ROM page 0. Just a hypothesis, maybe this isn't working in the emulator? Or maybe this has the side effect that the ROM page is switched to page zero (permanently)?
To find out if the problem lies with the ROM page itself and not with any of what the Kernal does differently in a Cartridge bootup, one could decouple one from the other:
In the cartridge, you basically just load another test program into low RAM and jump to that. In that test program, you have your ISR, the initialization and the main loop.
That way you can have the Cartridge boot process combined with a normal low RAM execution.
If I can help with how to load a program in assembly, I can share some code.
- kliepatsch
- Posts: 247
- Joined: Thu Oct 08, 2020 9:54 pm
Re: How to implement bareback interrupts?
Have you checked if the interrupt flag is set?
I think I know now what is meant by "disabled interrupts", it basically means that the interrupt flag is set (can be undone with CLI). Might this be the issue why no interrupts are triggered?
As you can see in the Kernal code, it looks like pretty much all initialization is being done before the cartridge check. The only two additional things done in a vanilla bootup are the CLI and the jump to BASIC.
I think I know now what is meant by "disabled interrupts", it basically means that the interrupt flag is set (can be undone with CLI). Might this be the issue why no interrupts are triggered?
As you can see in the Kernal code, it looks like pretty much all initialization is being done before the cartridge check. The only two additional things done in a vanilla bootup are the CLI and the jump to BASIC.
Re: How to implement bareback interrupts?
Thanks, but yes, the cli is already in my full source in the first post. This is my current state:
Otherwise, my analysis of the kernal is identical to what you say as well, so I really wonder, what makes the difference now. And yet, if run as prg, the interrupt works as expected and if called from $C004, it doesn't. The only difference, I can see here, is that in case of the cartridge, ROM bank $20 is active, but the code runs from memory and even if I switch back to bank $00 with "stz $01" before, it doesn't make a difference.
Code: Select all
_setup_irq:
nop
php
sei
stz ClockLo
stz ClockHi
lda #<loop_irq
sta IRQVec
lda #>loop_irq
sta IRQVec+1
; Setup first VBLANK
lda #$1
sta VERA::IRQ_EN
cli
plp
rts
loop_irq:
jsr handle_clock
jsr handle_joystick
jsr handle_keyboard
; Sign IRQ
lda #$1
sta VERA::IRQ_FLAGS
; Cleanup
ply
plx
pla
rti
Re: How to implement bareback interrupts?
It's been a while since I implemented an ISR in ROM, and some things have changed. This is what I think you need to look out for:
- On IRQ, the ROM bank is set to 0 by hardware
- Consequently, the vector at $fffe in ROM bank 0 is always called
- That vector points to Kernal RAM code at $038b
- The RAM value $01 still holds the ROM bank that was active when the IRQ occurred (even though the bank has been changed by the hardware). The Kernal RAM code, amongst other, backs up that original ROM bank, and eventually calls the address stored in $0314.
- A custom ISR should normally live in low RAM or have a stub that lives in low RAM. A custom ISR that is stored in another ROM bank than 0 is not accessible to the current Kernal RAM code at $038b. It could be changed of course, but that is probably a really bad idea. Better to go for the small stub in low RAM.
- On IRQ, the ROM bank is set to 0 by hardware
- Consequently, the vector at $fffe in ROM bank 0 is always called
- That vector points to Kernal RAM code at $038b
- The RAM value $01 still holds the ROM bank that was active when the IRQ occurred (even though the bank has been changed by the hardware). The Kernal RAM code, amongst other, backs up that original ROM bank, and eventually calls the address stored in $0314.
- A custom ISR should normally live in low RAM or have a stub that lives in low RAM. A custom ISR that is stored in another ROM bank than 0 is not accessible to the current Kernal RAM code at $038b. It could be changed of course, but that is probably a really bad idea. Better to go for the small stub in low RAM.
Re: How to implement bareback interrupts?
Thanks for answering, Stefan. Will go through your list here:
- On IRQ, the ROM bank is set to 0 by hardware
- Consequently, the vector at $fffe in ROM bank 0 is always called
That's good to know, wasn't aware of this, but this hits some of my expectations and questions.
- That vector points to Kernal RAM code at $038b
- The RAM value $01 still holds the ROM bank that was active when the IRQ occurred (even though the bank has been changed by the hardware). The Kernal RAM code, amongst other, backs up that original ROM bank, and eventually calls the address stored in $0314.
Ack. That's where I write my IRQ address to:
- The RAM value $01 still holds the ROM bank that was active when the IRQ occurred (even though the bank has been changed by the hardware). The Kernal RAM code, amongst other, backs up that original ROM bank, and eventually calls the address stored in $0314.
Good to know, thank you.
- A custom ISR should normally live in low RAM or have a stub that lives in low RAM. A custom ISR that is stored in another ROM bank than 0 is not accessible to the current Kernal RAM code at $038b. It could be changed of course, but that is probably a really bad idea. Better to go for the small stub in low RAM.
That I'm already aware of. None of my code remains in ROM, as I need to pull assets from there. Everything beyond my bootloader runs from unbanked RAM. My loop_irq label is reported to be at $36CA (Just double checked, just in case), so this should be good. But I will check, if there are any unexpected calls back into the ROM, maybe I forgot a segment or something. Although I don't carry high hope on that.
Anyway, that cleared some things up, although it still doesn't run. The "running once" issue would have matched quite well with the ROM banking idea, but actually, the IRQ isn't triggered at all - really at all. I had a breakpoint at $038b and even there nothing arrives after the first IRQ. It's not only my routine, which isn't called again.
Edit: Just tried to simply daisy chain my ISR with the previous one, but still the same effect. IRQ is triggered only once, then silence.
- On IRQ, the ROM bank is set to 0 by hardware
- Consequently, the vector at $fffe in ROM bank 0 is always called
That's good to know, wasn't aware of this, but this hits some of my expectations and questions.
- That vector points to Kernal RAM code at $038b
- The RAM value $01 still holds the ROM bank that was active when the IRQ occurred (even though the bank has been changed by the hardware). The Kernal RAM code, amongst other, backs up that original ROM bank, and eventually calls the address stored in $0314.
Ack. That's where I write my IRQ address to:
Code: Select all
; ---------------------------------------------------------------------------
; Vector and other locations
IRQVec := $0314
BRKVec := $0316
NMIVec := $0318
...
lda #<loop_irq
sta IRQVec
lda #>loop_irq
sta IRQVec+1
Good to know, thank you.
- A custom ISR should normally live in low RAM or have a stub that lives in low RAM. A custom ISR that is stored in another ROM bank than 0 is not accessible to the current Kernal RAM code at $038b. It could be changed of course, but that is probably a really bad idea. Better to go for the small stub in low RAM.
That I'm already aware of. None of my code remains in ROM, as I need to pull assets from there. Everything beyond my bootloader runs from unbanked RAM. My loop_irq label is reported to be at $36CA (Just double checked, just in case), so this should be good. But I will check, if there are any unexpected calls back into the ROM, maybe I forgot a segment or something. Although I don't carry high hope on that.
Anyway, that cleared some things up, although it still doesn't run. The "running once" issue would have matched quite well with the ROM banking idea, but actually, the IRQ isn't triggered at all - really at all. I had a breakpoint at $038b and even there nothing arrives after the first IRQ. It's not only my routine, which isn't called again.
Edit: Just tried to simply daisy chain my ISR with the previous one, but still the same effect. IRQ is triggered only once, then silence.