Jump to content
  • 0

Interrupt problem when storing code in ROM




I see that @StephenHorn has written an article on interrupts.

I have a question on how to setup interrupts if your code is placed in a ROM bank (not the same as the Kernal).

I just can't get it to work. Any pointers on how to do this are welcome.

The alternatives I tried:

  • Placing bridge code in RAM that is pointed to by $0314-0315. The bridge code change ROM bank, and executes my code therein. When done it restores X,Y,A and exits with RTI (without changing the ROM bank).
  • Storing pointers to my interrupt handler directly in the $FFFE-FFFF in my ROM bank.

Either nothing happens, or the emulator crashes.

EDIT: I've looked a lot at the monitor code, but I do not fully understand it. One thing is that its interrupt handler seems to exit with a rts. How is that possible? Looking at the compiler config file for the monitor, it's apparent that it includes some vectors, amongst other things, from the Kernal. These two memory ranges are defined:

  • KSUP_CODE2: start = $FB00, size = $03C0, fill=yes, fillval=$AA; 
  • KSUP_VEC2: start = $FEC0, size = $0140, fill=yes, fillval=$AA; => Range $fec0-ffff: May include entry points of Kernal routines, and all interrupt vectors

It all comes down to what happens on an interrupt:

  • Is the interrupt vector read from FFFE/FFFF in the active ROM bank (and not always ROM bank 0)? I guess that there's no possibility that the processor on interrupt could change the ROM bank before code execution starts.
  • If so, what parts of the Kernal ROM need to be copied to your own ROM to make this work?
Edited by Stefan
Link to comment
Share on other sites

4 answers to this question

Recommended Posts

  • 0

Is it feasible to just copy your interrupt code from your ROM bank into zeropage and then just change the FFFE/FFFF pointer to your code?  Then you would just call the original interrupt handler at the end of it.  I'm pretty sure that's what I've seen other people do.  If you don't want to copy the whole interrupt handler, you could copy bridge code that saves the states of the registers and banks and switches to the desired ROM bank and calls it.  For this sort of thing, it's best to have code in the zeropage that can be called from any bank.

Link to comment
Share on other sites

  • 0

@Ender Thank you.

I understand a bit more of what the monitor does now.

  • Vector in ROM bank 5/FFFE = 038b
  • The routine in 038b is called banked_irq, which basically does the following, I think (it's easy to get sidetracked):
    • Saves active ROM bank number on stack
    • Switches to ROM bank 0 (Kernal)
    • Jumps to Kernal FFFE vector. Before that pushes return address and processor status (?) on stack
    • Switches back to the ROM bank we started with
    • Restores registers
    • Exits with RTI

My next try will be based on a FFFE vector pointing to the same 038b routine and a custom irq handler stored in RAM pointed to by 0314-0315.

The custom irq handler will switch ROM bank and call my code, and when done switch the ROM bank back. Exit with jump to Kernal irq routine.

Link to comment
Share on other sites

  • 0

I got the ROM version of my program and interrupts working now, finally.

I learned some new things in the process. I write it down, should you have any use for it.

I use the CA65 assembler. A minimal config file could contain the following:

ROM: start = $c000, size = $3ffa, fill=yes, fillval=$aa;
IRQ_VECTORS: start = $fffa, size = $06, fill=yes, fillval=$aa;
CODE: load = ROM, type = ro;
IRQ: load = IRQ_VECTORS, type = ro;

On a maskable interrupt request, the 65C02 processor jumps to the address in $FFFE/$FFFF. Your ROM needs to hold a reference to a valid interrupt handler. Otherwise it's likely that the computer just locks up.

That interrupt handler needs to switch to Kernal ROM bank, and call the Kernal interrupt handler in Bank 0/$FFFE-$FFFF. When done it needs to switch back to the ROM bank that was active before, i.e. the ROM bank you are using.

The interrupt handler must be stored in RAM. If in your ROM bank, the program execution would continue at the following address in the Kernal ROM as soon as you switch bank, and probably cause unexpected results.

There is already such an interrupt handler in RAM location $038B ("banked_irq") when the computer is booted. To use this enter the following somewhere in your code to be stored in the IRQ segment, i.e. in memory addresses $FFFA-$FFFF.

.segment "IRQ"
.byt $ff, $ff, $ff, $ff, $8b, $03

In your code you may then change the interrupt vectors in $0314-$0315 as usual.

lda #<($0780)
sta $0314
lda #>($0780)
sta $0315

Here my custom interrupt handler is stored in RAM address $0780. Normally you would store the handler in ROM, and copy it to RAM on starting your program.

My custom interrupt handler looks like this:

lda #7  ;assuming your code is in bank 7
jsr irq_handler
jmp (irq_default_handler)

irq_handler is code in your ROM bank that is run during the interrupt. The bridge code is only what's needed to switch between Kernal bank and your bank.

Assemble your rom image with the following command:

cl65 -v -t cx16 -C yourconfig.cfg -o yourrom.bin <source files>

You may attach your ROM image to the standard ROM file with the cat command (on Linux or MacOS at least). This is also what the X16 Kernal project does in it's makefile. For this to work your ROM image must be exactly 16,384 bytes.

cat rom.bin yourrom.bin > customrom.bin

When starting the emulator, select the custom rom with the -rom option.

I don't think it's possible to run code in another ROM bank from BASIC. Instead you may type in the following short startup program in the built-in monitor.

.A1000 LDA#$07 ;assuming your code is in ROM bank 7
.A1002 STA$9F60
.A1005 JSR$C000 ;assuming this is the entry point of your code
.A1008 RTS


Edited by Stefan
Link to comment
Share on other sites

  • 0
4 hours ago, Stefan said:

I don't think it's possible to run code in another ROM bank from BASIC.

With standard Commodore ROM BASIC, you could POKE a small machine program into a buffer (in the olden times it would have been the second tape buffer). This could do the bank switch and then call your 'real' subroutine.

The Commander X16 BASIC could also support a special SYS command with an extra parameter for the bank number, e.g. SYS 4563,32.


When I read your first post and looked up the source, I also was puzzled by the irq.s files in kernal/cbm and in monitor. The 6502 IRQ vectors are at the end of the memory (and that better be ROM, because they need to be there during boot). So I looked in vectors.s and found 'puls'.

You are right, these routines do not handle the interrupt properly. That code looks like a 'red herring' to me hat probably needs to be cleaned up.

Today I looked up a PET BASIC4 ROM listing and found the IRQ handler I vaguely remembered. (The CBM4032 at our school had the first computer keyboard I ever touched, back in 1981). Here is a link for reference: http://www.zimmers.net/anonftp/pub/cbm/src/pet/pet_rom4_disassembly.txt

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Create New...

Important Information

Please review our Terms of Use