Page 1 of 2

How to implement bareback interrupts?

Posted: Thu Aug 01, 2024 9:21 pm
by SunSailor
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?

Posted: Thu Aug 01, 2024 9:35 pm
by ahenry3068
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...
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)

Re: How to implement bareback interrupts?

Posted: Thu Aug 01, 2024 10:25 pm
by SunSailor
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):

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
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 #<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

Re: How to implement bareback interrupts?

Posted: Fri Aug 02, 2024 6:01 am
by kliepatsch
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?

Posted: Fri Aug 02, 2024 12:28 pm
by SunSailor
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.

Code: Select all

    ; Setup first VBLANK
    lda #1
    sta VERA::IRQ_EN
    sta VERA::IRQ_FLAGS
    stz VERA::IRQ_RASTER
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?

Re: How to implement bareback interrupts?

Posted: Sat Aug 03, 2024 3:38 pm
by kliepatsch
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.

Re: How to implement bareback interrupts?

Posted: Sat Aug 03, 2024 3:54 pm
by kliepatsch
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.

Re: How to implement bareback interrupts?

Posted: Sat Aug 03, 2024 4:22 pm
by SunSailor
Thanks, but yes, the cli is already in my full source in the first post. This is my current state:

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
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.

Re: How to implement bareback interrupts?

Posted: Sat Aug 03, 2024 6:53 pm
by Stefan
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.

Re: How to implement bareback interrupts?

Posted: Sun Aug 04, 2024 1:01 am
by SunSailor
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:

Code: Select all

; ---------------------------------------------------------------------------
; Vector and other locations

IRQVec          := $0314
BRKVec          := $0316
NMIVec          := $0318

...

    lda #<loop_irq
    sta IRQVec
    lda #>loop_irq
    sta IRQVec+1
- 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.