Skip to content

8051 Memory Spaces

Benoît Thébaudeau edited this page Sep 23, 2019 · 10 revisions

TI's cc2x3x SoCs are based on the Intel 8051 microcontroller. This is a harvard-based architecture, which means that program memory and data memory are separate. Terms such as .bss, .text etc don't apply.

Compared to other CPUs running Contiki, the 8051 has a couple of unique characteristics: The first is its very limited stack, the second is code banking. This guide's ulterior motives is to highlight the stack-related limitations and to point out a few tips to keep in mind when writing 8051 code. Following these tips will help you avoid stack overflows and frustration.

This guide only applies to contiki's 8051-based platforms. If you are using some other micro, such as AVR or MSP430, information in this page is totally irrelevant.

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

SoC Physical Memory and Memory Spaces

First, we need to understand our hardware's physical memory and memory addressing scheme.

Physical Memory

The physical memory on SoCs has the following parts:

  • Flash: Aimed for program code and const data.
  • Static RAM - (S)RAM: For data memory
  • Special Function Registers (SFRs): To control the hardware
  • Flash Information Page (Info Page): Device information and configuration
  • XREG: Additional registers, which are not called SFRs for reasons that will become clear below.

Memory Spaces

In very simple terms, the 8051-based SoCs have 4 distinct but partially overlapping memory spaces: DATA, CODE, SFR and XDATA.

  • CODE: Read-only program memory. Can address 64KB. Maps the flash.
  • DATA: Fast access (single instruction), read/write data memory. Addresses 256 Bytes. Maps the SRAM.
    • The lower 128 bytes of DATA can be addressed either directly or indirectly.
    • The upper 128 bytes of DATA can be addressed only indirectly.
  • SFR: Read/Write register memory, directly accessible by a single CPU instruction.
    • SFR registers whose address is divisible by eight are also bit-addressable.
    • XREGs are NOT mapped in SFR (which is why they are not called SFRs)
  • XDATA: Slow access (4-5 instruction cycles, usually), 16-bit wide, read/write data memory. XDATA addresses the entire RAM (including stuff addressed by DATA). It also addresses SFRs, parts of the flash, RF registers, XREGs. On the cc2530, XDATA also maps the Info Page.

SDCC Terminology

SDCC uses its own terms (storage classes) to describe memory locations. On most (but not all) cases, it is easy to understand which hardware memory space each storage class refers to. The table below disambiguates.

Mapping between physical memory, hardware memory spaces and SDCC storage classes
SoC Memory Space SDCC Storage Class Specifier Physical Memory
XDATA xdata / far / external RAM __xdata or __far SRAM, (parts of) Flash,
SFR, RFR, XREG, Info Page...
DATA idata / internal RAM __idata SRAM
lower 128 bytes of DATA
(the directly-addressable part)
data / near __data or __near SRAM
lower 256 bytes of XDATA pdata / Paged Data __pdata SRAM
CODE code __code Flash
SFR sfr __sfr or __sfr16
or __sfr32 or __sbit
SFR

Building Software with SDCC

Now that we understand our hardware, we need to understand what variables will get allocated to which memory space. This is heavily influenced by SDCC's command line arguments (or #pragma directives which achieve similar things but are not discussed here).

SDCC Memory Models. Variables, Function Parameters and the Stack

When building code for 8051-compatible micros, SDCC uses one out of a possible four memory models: Small, Medium, Large, Huge. These are specified in the command line using the option --model-foo (e.g. --model-large). The choice of memory model influences the following things:

  • Allocation strategy for variables lacking a storage class specifier.
  • Code banking strategy
This guide focuses on the former. More on the latter in the guide on code banking.

The user also has the choice of enabling an option called --stack-auto. The presence of --stack-auto (or lack thereof) influences the allocation strategy for function parameters and automatic variables. This also has an indirect but very important implication on function re-entrancy.

When --stack-auto is not specified, the default action of the compiler is to place local variables in the internal or external RAM (depending on memory model). This in fact makes those variables behave as if they were static! Thus, without --stack-auto, functions are non-reentrant. When --stack-auto is specified, local variables are moved to the stack.

If this sounds confusing, the table below lists a bunch of variable declaration examples and shows how they will be allocated for various memory model / stack-auto combinations.

SDCC Memory Models and Variable Allocation
Example Code Small Medium Large / Huge Small + stack-auto Medium + stack-auto Large / Huge + stack-auto
File Scope
int foo; data pdata xdata data pdata xdata
static int foo; data pdata xdata data pdata xdata
__data int foo; data data data data data data
__xdata int foo; xdata xdata xdata xdata xdata xdata
__data static int foo; data data data data data data
__xdata static int foo; xdata xdata xdata xdata xdata xdata
Local Scope
int foo; data pdata xdata stack stack stack
static int foo; data pdata xdata data pdata xdata
__data int foo; data data data error error error
__xdata int foo; xdata xdata xdata error error error
__data static int foo; data data data data data data
__xdata static int foo; xdata xdata xdata xdata xdata xdata
Function Parameters
void bar(int a, int b, int c); data pdata xdata registers and stack registers and stack registers and stack

Due to Contiki's size, it will not compile without --stack-auto. For the same reason, the small and medium memory models are unsuitable. Therefore, for all 8051-based ports, we use either the large or huge memory model.

The Stack

By now it should be clear which variables get allocated to the stack and how the developer may influence this. This is all very important because the 8051's stack is very limited. Basically, the stack resides in the DATA memory space. DATA also hosts bit variables, R0-R7 register banks and variables placed there by the developer. The stack goes and sits in whatever's left in DATA. Thus, the absolute theoretical maximum stack depth is 256 bytes, assuming nothing else resides in DATA. Contiki's 8051-based ports leave 223 bytes for the stack.

Understand (and Avoid) Stack Overflows

As seen above, our stack's maximum depth is 223 bytes. This is very important: If during a particular execution branch we get more bytes on the stack, the node will crash spectacularly. A few tips will help you write stack-friendly code. Let's first summarise the variable allocation rules from previous sections (for --model-large (or -huge) --stack-auto):

  • A variable declared with a storage class is allocated where the storage class says.
    • Local, non-static variables may not be declared with a storage class (this leads to a compiler error).
  • A dynamic, locally scoped variable gets allocated on stack and destroyed when it falls out of scope.
  • Global variables get allocated to external RAM (XRAM).
  • Static variables (regardless of scope) get allocated to external RAM (XRAM).
  • A const is allocated on flash, thus shares space with code.
  • Each function call places bytes on the stack, which are removed when the function returns. Exactly how many bytes depends on the number and type of its parameters and return value.

Tips

  1. Careful when allocating big variables dynamically. If you define a 50 byte long struct and allocate a variable of that type on stack, you are playing with fire. Only do this if you really know that the variable will fall out of scope safely without crashing the node.
  2. When writing a new function, don't use more arguments than necessary
  3. Aim to use the smallest datatype possible. For instance, if you only need int8_t, use int8_t and not int.
  4. Avoid printf() like the devil, especially calls with many arguments.
  5. Bear in mind that an interrupt may occur while the stack is deep.
Let's elaborate a few of the above with an example:
struct some_big_struct {
  uint32_t first_field;
  uint32_t second_field;
  uint8_t and_a_buffer[64];
};

static struct some_big_struct do_this; /* This goes to XDATA */

int some_function {
  struct some_big_struct eeek;     /* Doing this is asking for trouble */
  unsigned char huge_buffer[128];  /* and so is this */

  /* This is a very evil printf */
  printf("Avoid this way of printing the values of a, b, c and d, which are %u, %lu, %lu and %d respectively\n", a, b, c, d);

  /* This is somewhat safer but will increase code footprint... */
  printf("Vals: \n");
  printf("a= %u ", a);
  printf("b= %lu ", b);
  printf("c= %lu ", c);
  printf("d= %d\n", d);
}

In the example above, some_big_struct is 72 bytes long. The eeek variable, is local scope and allocated dynamically. See rules above, this will get allocated on stack. This means that you just used about 30% of our MCU's stack on a single variable.

The do_this variable is allocated in XRAM, of which we have just under 8KBytes. This variable also consumes 72 bytes but it won't crash our node. However, there are two downsides: i) we still need to be careful not to overassign and exhaust our XRAM space. ii) This variable is static. Once it's allocated, it will stay there. Forever. So if you only every use it once during the software's lifetime, you are again effectively wasting valuable space.

At the end of the day, it's a design decision and you need good understanding of what your software is doing.

Pros Cons
On Stack
  • It will fall out of scope when no longer used
  • Ideal when we won't be using the variable for the entire software lifecycle
  • It may lead to stack overflows
  • Overallocating will lead to a crash: The toolchain does not warn us at all
On XDATA
  • Ideal when the varible will be used a lot
  • Takes burden away from the stack
  • Overallocating is easy to debug: The linker throws an error
  • Once declared, the space is reserved "forever"
  • May result in "Insufficient XRAM errors" (but this happens at build time)

More Reading

  1. SDCC Manual: "SDCC Compiler User Guide" (In revision 7399, 2012 - 03 - 04, the relevant sections are: sec. 3.4.1 "MCS51/DS390 Storage Class Language Extensions" and 3.18.1 "MCS51 Memory Models")
  2. cc253x/4x User Guide (Rev. C), Sec 2.2 "Memory": "CC253x System-on-Chip Solution for 2.4-GHz IEEE 802.15.4 and ZigBee® Applications (Literature Number: SWRU191C April 2009–Revised January 2012)"
  3. CC2430 Data Sheet (rev. 2.1) SWRS036F, Sec 11.2 "Memory": "A True System-on-Chip solution for 2.4 GHz IEEE 802.15.4 / ZigBee®"
Clone this wiki locally