6 Compiled Stack Example

The example code in this chapter shows how you can use the linker to place objects on a compiled stack.

Not to be confused with a software stack, which is a dynamic stack allocation accessed via a stack pointer, a compiled stack is a memory area that is designated for static allocation of local objects that should only consume memory for the duration of a routine, in the same way that a function's auto and parameter objects behave in C programs.

The main advantage of using objects on a compiled stack is that the memory used by each routine for its local objects can potentially be reused by local objects defined by other routines. This substantially reduces the amount of data memory used by your program.

The drawback of a compiled stack is that the programmer must maintain the directives used to indicate the memory requirements of each routine and how those routines interact. Failure to do so could lead to code failure.

Compiled stacks do not use a stack pointer. Objects on such a stack are assigned a static address, which can be accessed via a symbol, thus there is no code size or speed penalty for using a compiled stack over ordinary objects. The allocation of memory on the stack is performed by the linker, so the total size of the stack can be determined, and the linker will issue a memory error if space cannot be found for the requested stack objects.

As the memory assigned to compiled stack objects is statically allocated, routines that use these objects are not reentrant, so they cannot be called recursively, nor can they be called from both main-line code and interrupt routines (or anything called by an interrupt routine).

Assembly code which illustrates the basic principles of using a compiled stack on a PIC18F47K42 device is shown below.

A Compiled Stack Example

#include <xc.inc>

CONFIG "FEXTOSC = OFF"          // External Oscillator not enabled
CONFIG "RSTOSC = HFINTOSC_1MHZ" // HFINTOSC, HFFRQ=4MHz, CDIV=4:1
CONFIG "CLKOUTEN = OFF"         // Clock out Enable->CLKOUT function is disabled
CONFIG "PR1WAY = ON"            // PRLOCKED One-Way cleared/set only once
CONFIG "CSWEN = ON"             // Writing to NOSC and NDIV is allowed
CONFIG "FCMEN = ON"             // Clock Monitor enabled

CONFIG "MCLRE = EXTMCLR"        // LVP=0: MCLR pin is MCLR; LVP=1: RE3 pin is MCLR
CONFIG "PWRTS = PWRT_OFF"       // PWRT is disabled
CONFIG "MVECEN = OFF"           // Vector table isn't used to prioritize interrupts
CONFIG "IVT1WAY = ON"           // IVTLOCK bit can be cleared and set only once
CONFIG "LPBOREN = OFF"          // ULPBOR disabled
CONFIG "BOREN = SBORDIS"        // Brown-out Reset enabled, SBOREN bit is ignored
CONFIG "BORV = VBOR_2P45"       // Brown-out Reset Voltage (VBOR) set to 2.45V
CONFIG "ZCD = OFF"              // ZCD disabled, enable by setting ZCDSEN in ZCDCON
CONFIG "PPS1WAY = ON"           // PPSLOCK cleared/set only once
CONFIG "STVREN = ON"            // Stack full/underflow will cause Reset
CONFIG "DEBUG = OFF"            // Background debugger disabled
CONFIG "XINST = OFF"            // Extended Instruction Set disabled
 
CONFIG "WDTCPS = WDTCPS_31"     // Divider ratio 1:65536; software control of WDTPS
CONFIG "WDTE = OFF"             // WDT Disabled; SWDTEN is ignored
CONFIG "WDTCWS = WDTCWS_7"      // window open 100%; software control
CONFIG "WDTCCS = SC"            // Software Control

CONFIG "BBSIZE = BBSIZE_512"    // Boot Block size is 512 words
CONFIG "BBEN = OFF"             // Boot block disabled
CONFIG "SAFEN = OFF"            // SAF disabled
CONFIG "WRTAPP = OFF"           // Application Block not incr protected
CONFIG "WRTB = OFF"             // Configuration registers not incr-protected
CONFIG "WRTC = OFF"             // Boot Block (000000-0007FFh) not incr-protected
CONFIG "WRTD = OFF"             // Data EEPROM not incr-protected
CONFIG "WRTSAF = OFF"           // SAF not Write Protected
CONFIG "LVP = ON"               // LV programming enabled, MCLR pin, MCLRE ignored

CONFIG "CP = OFF"               // PFM and Data EEPROM code protection disabled

;place the compiled stack in Access bank memory (udata_acs psect)
;use the ?au_  prefix for autos, the ?pa_  prefix for parameters
FNCONF udata_acs,?au_,?pa_

PSECT resetVec,class=CODE,reloc=2
resetVec:
    goto       main

PSECT code
;add needs 4 bytes of parameters, but no autos
FNSIZE add,0,4       ;two 2-byte parameters
GLOBAL ?pa_add

;add the two 'int' parameters, returning the result in
;the first parameter location
add:
    movf       ?pa_add+2,w,c
    addwf      ?pa_add+0,f,c
    movf       ?pa_add+3,w,c
    addwfc     ?pa_add+1,f,c
    return

;incr needs one 2-byte parameter
FNSIZE incr,0,2
GLOBAL ?pa_incr

;return the additional of the 2-byte parameter with the
;value in the W register
incr:
    addwf      ?pa_incr+0,c
    movlw      0h
    addwfc     ?pa_incr+1,c
    return

GLOBAL ?au_main
GLOBAL result
result  EQU    ?au_main+0          ;create an alias for this auto location
  
GLOBAL incval
incval EQU     ?au_main+2          ;create an alias for this auto location
  
FNROOT main                        ;this is the root of a call graph
FNSIZE main,4,0                    ;main needs two 2-byte 'autos' (for result and incval)
FNCALL main,add                    ;main calls add
FNCALL main,incr                   ;main calls incr

PSECT code
main:
    clrf       result+0,c
    clrf       result+1,c
    movlw      2                       ;increment amount
    movwf      incval+0,c
    clrf       incval+1,c
loop:
    movff      result+0,?pa_add+0      ;load 1st parameter for add routine
    movff      result+1,?pa_add+1
    movff      incval+0,?pa_add+2      ;load 2nd parameter for add routine
    movff      incval+1,?pa_add+3
    call       add                     ;add result and incval
    movff      ?pa_add+0,result+0      ;store add's return value back to result
    movff      ?pa_add+1,result+1

    movff      incval+0,?pa_incr+0     ;load the parameter for incr routine
    movff      incval+1,?pa_incr+1
    movlw      2
    call       incr                    ;add 2 to incval
    movff      ?pa_incr+0,incval+0     ;store the result of incr back to incval
    movff      ?pa_incr+1,incval+1
    goto       loop

    END        resetVec
The function of the above assembly code is similar to that of the below C code.
int add(int a, int b) {
    return a + b;
}

void incr(int val, char amount) {
    return val + amount;
}

void main(void) {
    int result, incval;

    result = 0;
    incval = 2;
    while(1) {
        result = add(result, incval);
        incval = incr(incval, 2);
    }
}