8051 Memory Configurations with C Compilers

by Andrew Ayre, based on the C51 Primer by Mike Beach, Hitex UK

The Keil and Raisonance C Compilers provide a variety of memory models. When do we choose which model?

Physical Location Of The Memory Spaces

Perhaps the most initially confusing thing about the 8051 is that there are three different memory spaces, all of which start at the same address.

Other microcontrollers, such as the 68HC11, have a single Von Neuman memory configuration, where memory areas are located at sequential addresses; regardless of in what device they physically exist.

Within the 8051 CPU there is one such memory, the DATA on-chip RAM. This starts at D:00H (the 'D:' prefix implies DATA segment) and ends at D:7fH (127 decimal). This RAM can be used for program variables. It is directly addressable, so that instructions like 'MOV A,x' are usable. Above 80H the special function registers are located, which are again directly addressable. However, a second memory area exists between 80H and 0FFH which is only indirectly addressable and is prefixed by I: and known as IDATA. It is only accessible via indirect addressing (MOV A,@Ri) and effectively overlays the directly addressable SFR area. This constitutes an extended on-chip RAM area and was added to the ordinary 8051 design when the 8052 appeared. As it is only indirectly addressable, it is best left for stack use, which is, by definition, always indirectly addressed via the stack pointer SP. Just to confuse things, the normal directly addressable RAM from D:00H-D:80H can also be indirectly addressed by the MOV A,@Ri instruction!

8051 memory spaces

A third memory space, the CODE segment, also starts at zero, but this is reserved for the program. It typically runs from C:0000H to C:0FFFFH (65536 bytes) but as it is held within an external Flash, it can be any size up to 64KB (65536 bytes). The CODE segment is accessed via the program counter (PC) for opcode fetches and by DPTR for data. Obviously, being ROM, only constants can be stored here.

A fourth memory area is also off-chip, starting at X:0000H. This exists in an external RAM device and, like the C:0000H segment, can extend up to X:0FFFFH (65536 bytes). The 'X:' prefix implies the external XDATA segment (sometimes also referred to as XRAM). The 8051's only 16-bit register, the DPTR (data pointer) is used to access the XDATA. Finally, 256 bytes of XDATA can also be addressed in a paged mode. Here an 8-bit register (R0) is used to access this area, termed PDATA.

The obvious question is: "How does the 8051 prevent an access to C:0000H resulting in data being fetched from D:00H?"

The answer is in the 8051 hardware: When the CPU intends to access D:00H, the on-chip RAM is enabled by a purely internal READ signal - the external /RD pin is unchanged.

  1. MOV A,40 ; Put value held in location 40 into the accumulator.

This addressing mode (direct) is the basis of the SMALL memory model.

  1. MOV R0,#0A0H ; Put the value held in IDATA location 0A0H into
  2. MOV A,@R0    ; the accumulator

This addressing mode is used to access the indirectly addressable on-chip memory above 80H and as an alternative way to get at the direct memory below this address.

A variation on DATA is BDATA (bit data). This is a 16 byte (128 bit) area, starting at 020H in the direct segment. It is useful in that it can be both accessed byte-wise by the normal MOV instructions and addressed by special bit-orientated instructions, as shown below:

  1. SETB 20.0 ;
  2. CLRB 20.0 ;

The external ROM device (C:0000H) is not enabled during RAM access. In fact, the external ROM is only enabled when a pin on the 8051 named the PSEN (program store enable) is pulled low. The name indicates that the main function of the ROM is to hold the program that is executed on the CPU.

The XDATA RAM and CODE ROM do not clash, as the XDATA device is only active during a request from the 8051 pins named READ or WRITE, whereas the CODE device only responds when the PSEN pin is low.

To help access the external XDATA RAM, special instructions exist, conveniently containing an 'X'...

  1. MOV DPTR,#08000H
  2. MOVX A,@DPTR ; "Put a value in A located at address in the external RAM, contained in the DPTR register (8000H)".

The above addressing mode forms the basis of the LARGE model.

  1. MOVX R0,#080H ;
  2. MOVX A,@R0 ;

This alternative access mode to external RAM forms the basis of the COMPACT memory model. Note that if Port 2 is attached to the upper address lines of the RAM, it can act like a manually operated "paging" control.

The important point to remember is that the PSEN pin is active when instructions are being fetched; READ and WRITE are active when MOVX.... ("move external") instructions are being carried-out.

Note that the 'X' means that the address is not within the 8051 but is contained in an external device, enabled by the READ and WRITE pins.

Possible Memory Models

With a microcontroller like the 8051, the first decision is which memory model to use. Whereas the PC programmer chooses between TINY, SMALL, MEDIUM, COMPACT, LARGE and HUGE to control how the processor segmentation of the RAM is to be used (overcome!), the 8051 user has to decide where the program and data are to reside.

The Keil and Raisonance compilers currently support the following memory configurations:

  1. ROM: currently the largest single object file that can be produced is 64K, although up to 1MB (Keil Compiler) or 4MB (Raisonance Compiler) can be supported with the BANKED model described below. All compiler output to be directed to ROM, constants, look-up tables etc., should be declared as "code".
  2. RAM: There are three memory models, SMALL, COMPACT and LARGE
    • SMALL: all variables and parameter-passing segments will be placed in the 8051's internal memory.
    • COMPACT: variables are stored in paged memory addressed by ports 0 and 2. Indirect addressing opcodes are used. On-chip registers are still used for locals and parameters.
    • LARGE: variables etc. are placed in external memory addressed by @DPTR. On-chip registers are still used for locals and parameters.
  3. BANKED: Code can occupy up to 1MB (Keil Compiler) or 4MB (Raisonance Compiler) by using either CPU port pins or memory-mapped latches to page memory above 0FFFFH. Within each 64KB memory block a COMMON area must be set aside for C library code. Inter-bank function calls are possible.

In addition, the Raisonance Compiler provides a TINY memory model, which is identical to the SMALL memory model, except that ACALL and AJMP instructions are generated rather than LCALL and LJMP. This limits the code size to 2K bytes and is useful for those devices that do not support the LCALL and LJMP instructions. However when considering memory spaces the TINY and SMALL memory models are identical.

A variation on these models is to use one model globally and then to force certain variables and data objects into other memory spaces.

Choosing The Best Memory Configuration/Model

With the three memory models, a decision has to be made as to which one to use. Single chip 8051 users may only use the SMALL model, unless they have an external RAM fitted which can be page addressed from Port 0 and optionally, Port 2, using MOVX A,@R0 addressing.

This permits the COMPACT model. While it is possible to change the global memory model half way through a project, it is not recommended!

SMALL: Total RAM 128 bytes

Using this memory model, the number of global variables must be kept to a minimum to allow the linker's OVERLAY function to work to best effect. With 8052/32 versions, the manual use of the 128 byte IDATA area above 80H can allow applications additional variable storage space, however the amount of space required for the stack must be kept in mind.

The SMALL model can support very large programs by manually forcing large and/or slow data objects in to an external RAM, if fitted. Also variables that need to be viewed in real time are best located here, as dual-ported emulators (like the ones from Hitex and Raisonance) can read their values on the fly. This approach is generally best for large, time-critical applications, as the SMALL global model guarantees that local variables and function parameters will have the fastest access, while large arrays can be located off-chip.

COMPACT: Total RAM 256 bytes off-chip, 128 or 256 bytes on-chip.

Suitable for programs where, for example, the on-chip memory is applied to an operating system. The compact model is rarely used for an entire program, but more usual in combination with the SMALL switch reserved for interrupt routines.

COMPACT is especially useful for programs with a large number of medium speed 8 bit variables, for which the MOVX A,@R0 is very suitable.

It can be useful in applications where stack usage is very high, meaning that data needs to be off-chip. Note that register variables are still used, so the loss of speed will not be significant in situations where only a small number of local variables and/or passed parameters are used.

LARGE: Total RAM up to 64KB, 128 or 256 bytes on-chip.

Permits slow access to a very large memory space and is perhaps the easiest model to use. Again, not often used for an entire program, but in combination with SMALL. As with COMPACT, register variables are still used and so efficiency remains reasonable.
In summary, there are five memory spaces available for data storage, each of which has particular pros and cons.

Here are some recommendations for the best use of each:

DATA: 128 bytes; SMALL model default location

Best For:
Frequently accessed data requiring the fastest access. Interrupt routines whose run time is critical should use DATA, usually by declaring the function as "SMALL". Another recommended usage is for background code that is frequently run and has many parameters to pass. If you are using re-entrant functions, the re-entrant stacks should be located here as a priority.

Worst For:
Variable arrays and structures that contain more than a few bytes.

IDATA; 128 bytes or 256 bytes; Not model-dependant

Best For:
Fast access to data arrays and structures of limited size (up to around 32 bytes each) but not totalling more than 64 or so bytes. As these data types require indirect addressing, they are ideally placed in the indirectly addressable area. The stack is also located in IDATA as it is indirectly addressed.

Worst For:
Large data arrays and/or fast access words.

CODE: 64K bytes

Best For:
Constants and large lookup tables, plus opcodes, of course!

Worst For:
Variables! It's ROM - Read Only Memory that can not be written to.

PDATA: 256 bytes; COMPACT model default area

Best For:
Medium speed interrupt and fast background char (8 bit) variables and moderate-sized arrays and structures. Also good for variables which need to be viewed in real-time using an in-circuit emulator with dual ported memory.

Worst For:
Very large data arrays and structure above 256 bytes.
Very frequently used data (in interrupts etc..).
Integer and long data.

XDATA; up to 64K bytes; LARGE model default area

Best For:
Large variable arrays and structures (over 256 bytes)
Slow or infrequently-used background variables. Also good for variables which need to be viewed in real-time using an in-circuit emulator with dual ported memory.

Worst For:
Frequently-accessed or fast interrupt variables.

Setting The Memory Model - #Pragma Usage

The overall memory type is selected by including the line

  1. #pragma <MemoryModel> (for example "SMALL")

as the first line in the C source file.

SMALL is the default model and can be used for quite large programs, provided that full use is made of PDATA and XDATA memory spaces for less time-critical data.

Special note on COMPACT model usage

The COMPACT model makes certain assumptions about the state of Port 2. The XDATA space is addressed by the DPTR instructions that place the 16-bit address on Ports 0 and 2. The COMPACT model uses R0 as an 8-bit pointer that places an address on port 0. Port 2 is under user control and is effectively a memory page control. The compiler has no information about Port 2 and unless the user has explicitly set it to a value it will be undefined, although generally it will be at 0xff. The linker has the job of combining XDATA and PDATA variables and unless told otherwise it puts the PDATA (COMPACT default space) at zero. Hence, the resulting COMPACT program will not work.

When using the Keil Compiler it is essential to set the PPAGE number in the startup.a51 file to some definite value - zero is a good choice. The PPAGEENABLE must be set to 1 to enable paged mode. Failure to do this properly can result in very dangerous results, as data placement is at the whim of PORT2!

The Raisonance Compiler sets up P2 and enables paged mode automatically.
When linking, the PDATA(ADDR) control must be used to tell the linker/locator where the PDATA area is.

Local Memory Model Specification

The C compiler allows memory models to be assigned to individual functions. Within a single module, functions can be declared as SMALL, COMPACT or LARGE:

  1. #pragma COMPACT
  2. /* A SMALL Model Function */
  3. fsmall() small {
  4.   printf("HELLO") ;
  5. }
  6. /* A LARGE Model Function */
  7. flarge() large {
  8.   printf("HELLO") ;
  9. }
  10. /* Caller */
  11. main() {
  12.   fsmall() ; // Call small func.
  13.   flarge() ; // Call large func.
  14.  }

A Point To Watch In Multi-Model Programs

A typical 8051 C program might be arranged with all background loop functions compiled as COMPACT and all (fast) interrupt functions treated as SMALL. The obvious approach of using the #pragma MODEL to set the model can cause odd side effects. The problem usually manifests itself at link time as a MULTIPLE PUBLIC DEFINITION error related to, for instance, putchar().

The cause is that in modules compiled as COMPACT, the compiler creates references to library functions in the COMPACT library and the SMALL modules will access the SMALL library. When linking, two putchars() from two different libraries are found.

The solution is to stick to one global memory model and then use the SMALL function attribute, covered in the previous section, to set the memory model locally.

  1. #pragma COMPACT
  2. void fast_func(void) SMALL{
  3. /*code*/