#47 bankswitching revisited


Hi Maarten, hi folks,

this is a proposal which might be a little hard to read
all the interesting stuff is hidden in the comments of the
source below. Please take my apologees.

I hope it's not just Maarten replying on me and me
replying on
Maarten, so please add your 0.02$.

I'd want to propose bank switching code which would
look like
this f.e. for calling bank 1 from bank 0:


;--untested and buggy, RFC
inc sp
inc sp
xch a,r0
push acc
mov a,r1
push acc
mov a,sp
add a,#-3
mov r1,a
mov a,#__sdcc_bank_switch_0 >> 8
mov @r1,a
dec r1
mov a,#__sdcc_bank_switch_0
mov @r1,a
mov a,r0
ljmp __sdcc_bank_switch_1


anl P2, #(0x03)
orl P2, #1



The above code could be auto-generated from
something like the c-code below:


/* Proposal for SDCC bank switching routines (Request
For Comments)
(LGPL 2005 Frieder Ferlemann)

On first sight this bank switching code seems more
awkward than
the present code in sdcc/device/lib/mcs51/crtbank.asm
so I feel some advertisement is needed:

* banked functions do end with "ret" instead of "ljmp"
(saving 2 byte per function)

* inter bank function calls do not need to specify
destination bank in a register.
(2 bytes less for an inter bank call.
Register usage currently does not matter but might
at a cost in future)

* intra bank function calls can be made directly
(no need to setup R0,R1,R2, saving 6 byte per

* intra bank function calls need only 2 byte on stack
(instead of 3).

And as you'll most likely find out the weaknesses of
my proposal anyway
I might as well include some most (?) of them here:

- needs more cycles for inter bank calls (my
comment: this is offset
by the fact that intra bank function calls do not
need any additional

- needs more code in common area

(my comment: and 8 byte less per function in the
banked area).

- inter bank calls need one more byte stack space

(my comment: needs 1 byte less stack space per
intra bank call.
Probably it's save to assume that intra bank calls
are more
frequent than inter bank calls. In this case the
code uses less stack)

- the destination bank of a function call must be
known at
compile time, so no calling of a function in a
bank by a function pointer.

(my comment: would you really want this? - it's
8051 embedded

- the current code does not scale well beyond 4
paged areas because
you need to have code for each page calling any other

(my comment: good argument. The code size of this
bank switching
code basically scales with n! so if you should use
the current code
with many pages my proposal is on lost grounds.
On the other hand switching between 3 banks
(giving you
128k on a memory layout with 1*32k common and
3*32k paged memory)
is probably the "sweet spot" for 8051 if you
choose to do
bank switching at all.
The scaling problem additionally might be slightly
softened by the
linker including only the bank-switching code that
is needed.
A more dedicated bank-switch routine which doesn't
suffer from
n! (but probably needing more stack and cycles but
less code size)
could be written.)

Note: this bank switching code requires the compiler
(as opposed to
the linker) to know about the bank a function of
your code resides in.
So a prototype for banked function would need to
specify the
code bank:

int my_function( int i ) __banked (3);

This is pretty much like the syntax for the __using

IMHO this compile time information puts SDCC in a
much better position
with respect to code banking than compilers which
leave the assignment
of functions to code banks to the linking stage (and
thus (unless the
linker is pretty clever) do the check for bank
switching at runtime).

#define BANK_MASK (0x03)

#define ASM(...) __asm __VA_ARGS__ __endasm

#define sdcc_banked_call(x,y) \ ASM(;--untested and
buggy---------------------------------------------------------);\ ASM(.area CSEG_SDCC_BANKED_CALL_ ## x ##_##y
(CODE) ; extra segment so linker might remove it);\ ASM(__sdcc_banked_call_ ## x ##_##y:: );\ ASM( inc sp );\ ASM( inc sp );\ ASM( xch a,r0 );\ ASM( push acc );\ ASM( mov a,r1 );\ ASM( push acc );\ ASM( ; );\ ASM( mov a,sp );\ ASM( ; how to insert '#' with cpp for
immediate addressing? );\ ASM( add a,-3 ; '#' );\ ASM( mov r1,a );\ ASM( mov a,__sdcc_bank_switch_ ##x >> 8 ;'#' );\ ASM( mov @r1,a );\ ASM( dec r1 );\ ASM( mov a,__sdcc_bank_switch_ ##x ; '#' );\ ASM( mov @r1,a );\ ASM( mov a,r0 );\ ASM( ljmp __sdcc_bank_switch_ ##y ; ajmp? );\ ASM( ; );

#define bank_sel(x) \ ASM(;--untested and
buggy---------------------------------------------------------);\ ASM(.area CSEG_SDCC_BANK_SWITCH_ ## x (CODE) );\ ASM(__sdcc_bank_switch_ ##x:: );\ ASM( ; anl BANK_REGISTER, BANK_MASK ; '#' );\ ASM( ; orl BANK_REGISTER, x ; '#' );\ ASM( nop nop nop nop nop nop ; adapt );\ ASM( ret );

static void dummy(void) __naked

/* linker decides which ones are needed */
sdcc_banked_call (0, 1);
sdcc_banked_call (0, 2);
sdcc_banked_call (0, 3);

/* sdcc_banked_call (1, 0); bank 0 is common,
should not be generated */
/* sdcc_banked_call (1, 1); should not be generated */
sdcc_banked_call (1, 2);
sdcc_banked_call (1, 3);

/* sdcc_banked_call (2, 0); bank 0 is common,
should not be generated */
sdcc_banked_call (2, 1);
/* sdcc_banked_call (2, 2); should not be generated */
sdcc_banked_call (2, 3);

/* sdcc_banked_call (3, 0); bank 0 is common,
should not be generated */
sdcc_banked_call (3, 1);
sdcc_banked_call (3, 2);
/* sdcc_banked_call (3, 3); should not be generated */

/* linker decides which ones are needed */
bank_sel (0);
bank_sel (1);
bank_sel (2);
bank_sel (3);


  • RFC as in mail but white space preserved

  • Maarten Brock
    Maarten Brock

    Logged In: YES

    Who filed this patch? Frieder?

    Some comments:
    1) If you use RET instead of LJMP where is it returning to?
    The common area or another bank? What restores this bank?

    2) Instead of letting the compiler/linker set a register now the
    user must adapt n! banked_call functions to his/her hardware
    instead of just one.

    3) Intra bank function calls don't need to be banked at all.
    Just don't specify the banked keyword. Nothing lost/saved.

    4) Function pointers are exactly the type of stuff that make
    programs big. I don't think we can do away with them.


  • Logged In: YES

    Hi Maarten,

    thank you for digging through my cryptic proposal:)

    > Who filed this patch? Frieder?

    Yes it was me. I would have preferred a mail to sdcc-devel
    but the list wouldn't accept it (my mail provider does not
    define postmaster@web.de so my mail bounced:(

    > 1)
    The mechanism of calling a function in bank 3 from bank 1
    would be:

    mov r0,#dest
    mov r1,#dest>>8
    lcall __sdcc_banked_call_1_3

    then __sdcc_banked_call_1_3 pushes the address of
    __sdcc_bank_switch_1 onto the stack, then pushes the address
    of (R0,R1) onto the stack and then LJMPs to
    The RET instruction of __sdcc_bank_switch_3 then jumps to
    the target address previously in registers (R0,R1).

    After the code of the target address is finished the RET
    instruction returns to the code which switches back to bank
    1 (and then to the address where the call originated from).

    The mechanism of calling a function in bank 3 from bank 0
    would be:
    lcall __bank_sel_3
    lcall my_func_in_bank_3

    or (if the previously used bank was bank 3) simply:

    lcall my_func_in_bank_3

    > 2)

    No not really, the user would have to provide the n
    __bank_sel_n functions (4==n in this case)
    The __sdcc_banked_call_n_m functions don't contain code
    which would be specific for the bank switching mechanism.
    The linker then would decide which of these end up in the

    > 3)

    My proposal would additionally allow to put code into a
    banked region and call it either via __sdcc_banked_call_n_m
    or directly.
    If you are under extreme pressure you could theoretically
    even put library stuff (like floating point code) into a
    banked area...

    > 4)

    You cannot call them _directly_ but could get away with one
    additional level of indirection. A 'trampoline' function
    like "void my_func_trampoline(void){my_func();}" would add
    an overhead of 3 bytes code (and no stack overhead) . Should
    not be an issue.

    Note, the code generated by bank.c maybe didn't shine enough:

    It would generate unnecessary bank switching code for
    switching from the common bank 0 other banks.
    (in this case the calling bank doesn't need to be pushed and
    the compiler could insert an lcall __bank_sel_n if needed).

    In the end the proposal seems to need about 150 bytes code
    more in common memory (if the linker needs to include
    each(!) __sdcc_banked_call_n_m) than crtbank.asm.
    As it saves 3 to 6 bytes (in common bank) per call of a
    banked function from common bank there would be a break even
    at latest at 25..50 calls (this break even point can
    probably be reached relatively early).

    I'm attaching file bank2.lst which contains the complete
    bank switching code for 128kByte separated in 32k common
    area and 3 banked pages.
    (The file bank2.lst is more or less automatically generated
    by executing "sdcc -c bank2.c". bank2.c contains some
    additional comments about the pros and cons of the proposal.)



  • bank switching code generated from "sdcc -c bank2.c"

  • some code removed, comment updated