One strategy is to have a small, identical, routine located in a special part of the ROM space that does the call and return. Suppose that there is a latch addressed at the address ROMBANK in the I/O area, that have the bottom three bits tied directly to the high address pins of a 128KB or 512KB FlashROM. I'll assume it's a blind latch, so there is a zero page location THISROM that is kept current with the current ROM bank state ... if it's readable, that simplifies the external function dispatch.
You are calling an assembly language stub, so it will know it's own target ROM bank and function call index. The 6502 C compiler needs an external call facility, and you'd need to do the stub functions and library call vector in assembler, but if you can compile library code and get the call address for them, it seems like you'd be able to do most of the development in C.
I'll assume X and Y are free at the point of the call to the stub ... if for efficiency they are needed to be passed through to the library call, they can be saved in the zero page in the callee stub, before calling the external call routine, and and reloaded in the external call routine just prior to "JSR +".
The EXTCALL would be up near the top of ROM with the interrupt vector table stuff that will be linked in with all ROM banks. EXTCALL existing in parallel in all ROM banks is how you are able to switch banks using code that resides in the ROM banks being switched.
Code: Select all
; calling function FOO, 65C02
EXT_FOO:
LDY #RB_FOO
STY CALLROM
LDX #IDX_FOO
JMP EXTCALL
; ...
EXTCALL:
LDY THISROM ; we are still in the caller ROM bank
PHY
LDY CALLROM
STY THISROM
STY ROMBANK
LDY TMPY ; now we are in the callee ROM bank
JSR +
PLY
STY THISROM
STY ROMBANK
RTS ; now we are back in the caller ROM
+
JMP (ECALLS,X)
You use the C facilities for calling an external function to call the code in the other bank as a library function, and use the C facilities for compiling a library function, extracting the address for the ECALLS jump vector table, which goes into an assembled binary chunk that the linker knows about.
If a maximum number of external calls in per ROMBANK has been picked, the ECALLS vector table doesn't have to set aside an entire binary page. If it is placed immediately below the common binary chunk, that makes a fixed amount allocated at the top of each ROM, so you know how much space you have to play with in each ROM bank.
Starting from a working 16K floating point Basic compiled from C, I guess you'd clear out any transcendentals and/or other floating point routines that are going to be cross bank called, to make room in the base ROM for development, and you can develop new BASIC keywords in the base basic ROM (even if they are targeted for cross bank calls) until they are stable, and then do small assembly language chore of recompiling the library bank functions including the new keyword, getting the target address, extending the inbound call jump table, and writing the new stub in the base bank, and adding it to the assembled binary chunk.
Getting the line editor into its own ROMBANK would also be really handy for allowing the capabilities of BASIC to grow, since then the keyword table is not in the base BASIC ROM bank, so you are only adding the call stub overhead when you add a new keyword ... 10 bytes per stub unless you have to retain X and/or Y when doing the external call.