-
Notifications
You must be signed in to change notification settings - Fork 3
8051 Memory Spaces
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.
First, we need to understand our hardware's physical memory and memory addressing scheme.
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.
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 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.
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 |
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).
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
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.
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.
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.
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.
- 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.
- When writing a new function, don't use more arguments than necessary
- Aim to use the smallest datatype possible. For instance, if you only need
int8_t
, useint8_t
and notint
. - Avoid
printf()
like the devil, especially calls with many arguments. - Bear in mind that an interrupt may occur while the stack is deep.
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 |
|
|
On XDATA |
|
|
- 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")
- 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)"
- 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®"