Skip to content
g-oikonomou edited this page Dec 6, 2012 · 3 revisions

The Flash size on SoCs supported by Contiki is either 256KB (cc253x) or 128KB (Sensinode). As discussed in the Understanding 8051 Memory Spaces guide, the flash is mapped to the CODE memory space, which is 16 bits wide and can therefore only address up to 64KB.

When our firmware's code footprint is lower than 64KB, banking is irrelevant, since the entire program memory can be addressed by the CODE memory space without any tricks. When the footprint exceeds this 64KB threshold, in order to address the entire flash, the 8051 uses a technique called Code Banking.

This introductory page aims to describe the basics and only focuses on SoCs supported by Contiki. It is not intended as a replacement for the SDCC manual nor the SoC datasheets. Please see the reference list.

Table of Contents

How does it Work

The flash is conceptually broken down into a number of segments called banks. The number of banks varies between devices but the technique is the same. Each bank is of a 32KB size, thus the cc2430 has 4 banks and the cc2530 has 8.

Since the size of the flash is over 64KB, we use 3 bytes to address all of it. So for instance on the cc2530 the physical addresses will be from 0x000000 to 0x03FFFF

Common Segment

The lower 32KB of the flash (physical addresses 0x000000 to 0x007FFF), also called the common segment (or HOME or BANK0) is always addressed by the lower 32KB of the CODE memory space (address range 0x0000 - 0x7FFF).

Note how the MSB of the physical address does not appear in this virtual address.

Switched Segments

The higher 32KB of the CODE space (0x8000 - 0xFFFF) are used to address the remaining N-1 code segments. Only one segment is referenced at each given point in time. A Special Function Register is used to tell the micro which bank is currently being referenced (this SFR is often called PSBANK or FMAP).

Physical and Virtual Addresses

This table shows the mapping between physical addresses (on flash), the address in the CODE memory space and the value of the FMAP SFR. See how the virtual address (the address as SDCC defines it), is made up of the value of FMAP (MSB), followed by the address in CODE.

Mapping between physical and virtual addresses and the value of FMAP
Segment Physical Address Virtual Address FMAP Address in CODE
HOME 0x000000 - 0x007FFF 0x000000 - 0x007FFF 0x00 0x0000 - 0x7FFF
BANK1 0x008000 - 0x00FFFF 0x018000 - 0x01FFFF 0x01 0x8000 - 0xFFFF
BANK2 0x010000 - 0x017FFF 0x028000 - 0x02FFFF 0x02 0x8000 - 0xFFFF
BANK3 0x018000 - 0x01FFFF 0x038000 - 0x03FFFF 0x03 0x8000 - 0xFFFF
...
BANK7 0x038000 - 0x03FFFF 0x078000 - 0x07FFFF 0x07 0x8000 - 0xFFFF

The Problem

So far it sounds simple. However, there is a problem when code in bank X tries to call code in bank Y, if both X and Y are switched segments. If we make the call before we switch banks, we will end up calling code in the wrong flash area. If we switch to the correct bank before the call, we will no longer be able to make the call since the caller will have been switched out.

Writing Banked Software with SDCC

In order to solve this, SDCC uses a technique called a trampoline. Banked function calls go through a small piece of intermediate code which resides in the common segment. In simple terms, the story goes like this:

  • The caller:
    • Writes the address of the intended callee, including bank number, in registers R0, R1 and R2
    • Invokes the trampoline (banked call)
  • The trampoline:
    • Saves the current bank on stack
    • Loads the new bank
    • Makes the call
  • The callee:
    • Runs (and possibly makes more banked calls)
    • Returns to the trampoline (banked return)
  • The trampoline:
    • Loads the original bank (which is read from stack)
    • Returns to the caller

Normal vs Banked Calls

It should be obvious from the above that banked calls place some burden on stack and impose some code size and processing overhead. However, banked calls are not always necessary. For example, we don't need a trampoline when the callee is in the common segment.

SDCC Memory Models

As discussed in the Understanding 8051 Memory Spaces guide, SDCC builds software using one of four possible memory models (Small, Medium, Large, Huge).

Small, Medium and Large - Explicit Banking

With Small, Medium or Large, the developer has to request banked calls explicitly in the code.

  • A function prototype must have the __banked keyword so that the calling code will invoke the trampoline instead of making a normal call.
  • The actual routine must also have the keyword __banked so that the function itself knows to return to the trampoline.
For instance:
void foo() __banked; 

void bar() __banked
{
  /* This becomes a banked call because foo()'s prototype has the __banked keyword */
  foo();

  /* When we reach here, bar does a banked return, because it's own definition has the __banked keyword */
}

Huge - Implicit Banking

With the huge memory model, all function calls/returns invoke the trampoline. Example again:

void foo(); 

void bar()
{
  /* This automatically becomes a banked call */
  foo();

  /* This automatically becomes a banked return */
}

Obviously this increases our code footprint a fair bit.

Here be Dragons

This is an undocumented feature in SDCC - Use at your own risk

In order to decrease code size and increase performance, the developer can use the __nonbanked keyword to prevent SDCC from emitting banked calls/returns. Basically, __nonbanked in --model-huge does the inverse that __banked does in the other models.

Be careful though, the following rules apply:

  • It is safe to use __nonbanked for functions guaranteed to be in the common segment
  • For functions which reside in switched segments, it is safe to use __nonbanked if all calls to the function are performed by code residing in the same segment.

Bank Allocations

When writing banked software with SDCC, the developer must specify segments for each code module. This can be done with a #pragma directive or from the command line. The main thing to stress here is that the linker is oblivious and does not check for bank overflows. If you allocate too many files in the same segment and its resulting code size is over 32 KB you will end up with a broken image.

Banking in Contiki's 8051-based Ports

For both platforms of interest, the contiki build system is banking-aware. Have a look in the various Makefiles in examples/cc2530dk or examples/sensinode. Some of the have the following line: HAVE_BANKING=1

The HAVE_BANKING instructs the build system whether to generate a bankable image or not.

  • When HAVE_BANKING is undefined, the build system performs some guesswork: If we are building an image with uIPv6 support, HAVE_BANKING is assigned the value of 1, otherwise it becomes 0.
  • When banking is requested, the system will automatically build contiki with the huge memory model
  • When banking is not requested, the build uses the large model
When the build finishes, you will see some informative output, aiming to help you detect image anomalies before programming your device with a potentially broken firmware.

Code Segment Sizes and memory footprint

The very last output of your build will look something like that:

Report
===============
Code footprint:
Area                                Addr        Size          Decimal
----------------------------------  --------    --------      --------
HOME,CSEG,CONST,XINIT,GS*           00000000    00007FF5 =       32757. bytes (REL,CON,CODE)
BANK1                               00018000    00007FFC =       32764. bytes (REL,CON,CODE)
BANK2                               00028000    00007FF9 =       32761. bytes (REL,CON,CODE)
BANK3                               00038000    0000472C =       18220. bytes (REL,CON,CODE)
Other memory:
	 Name             Start    End      Size     Max     
	 ---------------- -------- -------- -------- --------
	 PAGED EXT. RAM                         0      256   
	 EXTERNAL RAM     0x0000   0x14a9    5290     7936   
	 ROM/EPROM/FLASH  0x0000   0x3c72b  116502   262144  

This makes it possible to spot BANK overflows and other problems at a glance:

  • The value listed under Decimal for all code segments must be lower than 32768.
  • For Sensinode devices, the value for BANK3 must be lower than 30720.

Automatic Bank Allocation

Both ports use an automatic bank allocator. When building images with SDCC banking, the allocator will automatically meet the segment size requirements discussed in the previous section. The only thing you have to make sure is that if you write a new interrupt service routine, you must tell the allocator that it must reside in the HOME bank. There are two ways you can achieve this:

  • Name your file with an intr.c suffix. For example foo_intr.c or bar-intr.c. When the allocator encounters a filename ending in intr.c, it will automatically assign the file to the HOME bank.
  • If you want to give a different name to your file (e.g. foo.c) then add a line to the segment.rules file in your CPU dir. The line should be: HOME foo.c
If you don't write new ISRs then you don't need to worry about anything.

Towards the end of the build, when the allocator runs, it will throw the following output (numbers will obviously vary between builds):

Bank Allocation
===============
python ../../../cpu/cc253x/bank-alloc.py client obj_cc2530dk/segment.rules 
Total Size = 116502 bytes (101779 bankable, 2664 user-allocated, 12059 const+libs)
Preallocations: HOME=14723
Bin-Packing results (target allocation):
Segment - max - alloc
  HOME   32768  32757
  BANK1  32768  32764
  BANK2  32768  32761
  BANK3  32768  18220

The line starting with Preallocations lists the number of bytes that can not or may not be moved around. For example, standard libraries and files containing ISRs must reside in the HOME segment.

The lines following Segment - max - alloc are of high importance. The last column will list how many bytes should be in each segment after the build is finished. The values listed here MUST be identical to the values reported under Decimal at the end of the build output. Compare the target allocation listed above with the final segment sizes at the start of this section and see how the respective values are exact matches.

If you spot any differences, try a make clean; make cycle. If the differences persist, you may be looking at a bug in the allocator. Please contact me with details.

More Reading

  1. The SDCC manual. Look for the section entitled 'Bankswitching'.
  2. Section 2.2.2 'CPU Memory Space' of the 'CC253x/4x User Guide (Rev. C)' (Literature Number: SWRU191C April 2009–Revised January 2012)
  3. Section 11.2.2 'CPU Memory Space' of the 'CC2430 Data Sheet (rev. 2.1)' (Literature Number: SWRS036F)