0% found this document useful (0 votes)
4 views14 pages

PIC32 Stack Example

The document provides an overview of the memory architecture and stack implementation for Microchip PIC32 devices, detailing the organization of memory, types of variables, and the structure of stack frames in the XC32 C/C++ compiler. It explains the differences between local and global variables, the allocation of auto and non-auto type variables, and the behavior of the stack during function calls. Additionally, it includes an example of C code and its corresponding assembly output, illustrating the relationship between high-level code and low-level operations in the context of stack management.

Uploaded by

Hong Hai Nguyen
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views14 pages

PIC32 Stack Example

The document provides an overview of the memory architecture and stack implementation for Microchip PIC32 devices, detailing the organization of memory, types of variables, and the structure of stack frames in the XC32 C/C++ compiler. It explains the differences between local and global variables, the allocation of auto and non-auto type variables, and the behavior of the stack during function calls. Additionally, it includes an example of C code and its corresponding assembly output, illustrating the relationship between high-level code and low-level operations in the context of stack management.

Uploaded by

Hong Hai Nguyen
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

EE4341 Spring 2023

The PIC32 Stack

Microchip PIC32 devices have a single 4 GB memory address space with program and data memory
corresponding to specific regions in this memory address space. The memory is organized in bytes. A
32-bit word in the PIC32 spans 4 addresses. All addresses used in instructions are virtual addresses.
Physical memory is where the memory hardware is implemented. The virtual and physical memory
maps are related by a Fixed Mapping Translation (FMT) unit in the PIC32.

Memory regions in this virtual address space map to different physical memory, these include

• Flash EEPROM used for program memory


• Static RAM used for data
• Special Function Registers
• Configuration Registers

The stack is implemented in Static RAM in the Kernel Program RAM partition, either KSEG0 or
KSEG1. The PIC32 processor and the XC32 C/C++ compiler assume little-endian byte ordering of the
32-bit, 4 byte words.

Types of Variables

Generally variables can be classed as either local or global. The scope of a local variable is the function
for which it is defined, the scope of a global variable is the entire program.

C/C++ traditionally has three common scopes, the block, the file, and the program. The block and
program correspond to local and global scopes. The scope of a file corresponds to a static variable
which is defined outside a function but only have scope inside a file.

For XC32 C/C++ it is more meaningful to differentiate between auto type variables and non-auto type
variable which are allocated memory in different ways. Auto type variable (default) have automatic
storage duration and are placed on the stack. Non-auto type variables have permanent storage duration
and can be located anywhere in data RAM.

For the XC32 C/C++ compiler the stack grows down in memory. The initial stack pointer is located at
the near the top of KSEG0/KSEG11 memory and initialized in the startup code with the value of
_stack which is determined by the linker based on space available and is specific to a device. The
minimum default stack size is 1024 bytes. Some facts about stacks for the XC32 compiler:

• The stack pointer is always aligned to an 8-byte boundary, consequently stack space is
allocated 8 bytes at a time.

• Integer types are converted to a 32-bit value if they are smaller.

1 For a device with an L1 cache the stack is located in KSEG1, otherwise it is in KSEG0.

1
EE4341 Spring 2023

• Space is always allocated on the stack for the first 4 function arguments even though they may
be passed in registers.

For a function the stack frame has the form shown in the figure.

For the PIC32 the register r29 is used for the stack pointer sp, register r30 is used for the frame
pointer, fp. The stack is affected by the level of optimization used. With no optimization (-O0) a stack
frame is created for every function and both the frame pointer and stack pointer are used. With level 1
optimization (-O1) the frame pointer is not used and a stack frame may not be created for all functions.

Two linker directives, which appear in the assembly code, are used to describe the stack frame2.

.frame framereg, frameoffset, retreg


This directive describes the shape of the stack frame. The virtual frame pointer in use
is framereg; normally this is either $fp or $sp. The frame pointer is frameoffset
bytes below the canonical frame address (CFA), which is the value of the stack pointer
on entry to the function. The return address is initially located in retreg until it is saved
as indicated in .mask.

.mask mask, offset


Indicate which of the integer registers are saved in the current function's stack frame.
mask is interpreted a bit mask in which bit n set indicates that register n is saved. The
registers are saved in a block located offset bytes from the canonical frame address
(CFA), which is the value of the stack pointer on entry to the function.

2 Linker directives are described in the MPLAB XC32 Assembler, Linker and Utilities User's Guide.

2
EE4341 Spring 2023

There are 3 regions in the stack frame for a function; these are for the saved registers, the local
variables and temporaries, and called function arguments.

Example: To illustrate the format and use of the stack with the XC32 C compiler the consider the
following C code consisting of a main routine which passes to a function two integers values which are
added in the function and result returned:

/* function prototype */
int add(int,int);

/* example of a simple adder function */


int main(void)
{
volatile int a,b,c;

a=2; b=3;

c=add(a,b);

return 0;

}
/* two term adder */
int add(int x, int y)
{
int z;

z=x+y;

return z;
}

This code consists of main routine which calls the function adder. The source file is adder.c

Unoptimized Code

When this code is compiled with the Microchip XC32 compiler as

xc32-gcc -O0 -S adder.c

the -O0 flag is used to force level 0 optimizations and the -S flag causes the output from the
compilation process to be the assembly code file adder.s. With no optimization used the frame
pointer $fp is used and a stack frame is created for both the main and add functions along with

3
EE4341 Spring 2023

significantly more stack operations. A listing of this file is the following

.file 1 "adder.c"
.section .mdebug.abi32
.previous
.gnu_attribute 4, 3
.section .text,code
.align 2
.globl main
.set nomips16
.set nomicromips
.ent main
.type main, @function
main:
.frame $fp,40,$31 # vars= 16, regs= 2/0, args= 16, gp= 0
.mask 0xc0000000,-4
.fmask 0x00000000,0
.set noreorder
.set nomacro
# End mchp_output_function_prologue
addiu $sp,$sp,-40
sw $31,36($sp)
sw $fp,32($sp)
move $fp,$sp
li $2,2 # 0x2
sw $2,16($fp)
li $2,3 # 0x3
sw $2,20($fp)
lw $3,16($fp)
lw $2,20($fp)
move $4,$3
move $5,$2
jal add
nop

sw $2,24($fp)
move $2,$0
move $sp,$fp
lw $31,36($sp)
lw $fp,32($sp)
addiu $sp,$sp,40
j $31
nop

.set macro
.set reorder
# Begin mchp_output_function_epilogue
# End mchp_output_function_epilogue
.end main
.size main, .-main

4
EE4341 Spring 2023

.align 2
.globl add
.set nomips16
.set nomicromips
.ent add
.type add, @function
add:
.frame $fp,16,$31 # vars= 8, regs= 1/0, args= 0, gp= 0
.mask 0x40000000,-4
.fmask 0x00000000,0
.set noreorder
.set nomacro
# End mchp_output_function_prologue
addiu $sp,$sp,-16
sw $fp,12($sp)
move $fp,$sp
sw $4,16($fp)
sw $5,20($fp)
lw $3,16($fp)
lw $2,20($fp)
addu $2,$3,$2
sw $2,0($fp)
lw $2,0($fp)
move $sp,$fp
lw $fp,12($sp)
addiu $sp,$sp,16
j $31
nop

.set macro
.set reorder
# Begin mchp_output_function_epilogue
# End mchp_output_function_epilogue
.end add
.size add, .-add
.ident "GCC: (Microchip Technology) 4.5.2 MPLAB XC32 Compiler v1.34"
# Begin MCHP vector dispatch table
# End MCHP vector dispatch table
# Microchip Technology PIC32 MCU configuration words

There are three parts to this assembly file: the general overhead at the beginning, the directives and
assembly instructions corresponding to main function, and the directives and assembly corresponding
to the add function.

The first 5 lines of the file contains assembler directives with information about the file and minimal
debug information.

5
EE4341 Spring 2023

The unoptimized main function

The next 7 lines contain assembler directions with information related to the function main. Here we
start with the entry point label main and have the following code for this function

main:
.frame $fp,40,$31 # vars= 16, regs= 2/0, args= 16, gp= 0
.mask 0xc0000000,-4
.fmask 0x00000000,0
.set noreorder
.set nomacro

The .frame directive indicates we will used use $fp, the frame pointer, as the virtual frame pointer
and this is offset 40 bytes from the $sp on entry to this function. The return address, to be saved on the
stack, is located in register $31. The .mask directive indicates which of the 32 general purpose
registers are saved by which of the 32 bits are set. In this case $31 and $30. The -4 indicates the saved
registers are in a block located -4 bytes below the top of the frame.

The remaining two directives ensure that code is not moved around delay slots and macros are not
expanded. The stack frame that will be created is the following

Stack Frame for the unoptimized version of main

6
EE4341 Spring 2023

The next part of the code contains the assembly instructions. Most of the assembly instructions that
follow move data to or from this stack frame.

The next part of the code contains the assembly instructions.

addiu $sp,$sp,-40
sw $31,36($sp)
sw $fp,32($sp)
move $fp,$sp
li $2,2 # 0x2
sw $2,16($fp)
li $2,3 # 0x3
sw $2,20($fp)
lw $3,16($fp)
lw $2,20($fp)
move $4,$3
move $5,$2
jal add
nop

sw $2,24($fp)
move $2,$0
move $sp,$fp
lw $31,36($sp)
lw $fp,32($sp)
addiu $sp,$sp,40
j $31
nop

The first thing done on entry to the function is to use the addiu instruction to allocate 40 bytes for the
stack frame. Immediately after two sw instructions put the two saved registers, $ra and $fp, in the
top two elements of the stack and move updates the frame pointer $fp to point to this stack frame.

The next four instructions use $2 as a scratch register. The expression a =2 is implemented by using
an li instruction to load $2 with 2 followed by a sw instruction to put this value on the stack in the
location allocated to local variable a. Then a similar pair of instructions repeat this for b =3.

Next we set up and call the function add. Registers $3 and $2 are used as scratch registers and two lw
instructions copy the values for a and b from the stack. The following two move instructions then load
the first two argument registers, $4 and $5 with these values. A jal instruction is used to transfer
execution to the add function and automatically save the return address in $31. A nop instruction is
used to fill the delay slot that which follows.

On return from add the result is in $2, we use a sw instruction to put the this result on the stack in the
location reserved for c and then use a move instruction to set $2 to 0. The remaining instructions
then clean up and return to the calling routine. Two lw instructions restore the saved values on the
stack to $31 and $fp, the addiu instruction deallocates the memory used for the stack frame and the

7
EE4341 Spring 2023

jump instruction, j, returns execution to the calling routine.

After the assembly code macros and reordering are turned back on.

.set macro
.set reorder

and we wrap up things for this function.

# Begin mchp_output_function_epilogue
# End mchp_output_function_epilogue
.end main
.size main, .-main

Note that for this nonoptimized code there is a straight forward relationship between the C code and the
assembly code. This is useful in debugging code.

The nonoptimized add function

.align 2
.globl add
.set nomips16
.set nomicromips
.ent add
.type add, @function
add:
.frame $fp,16,$31 # vars= 8, regs= 1/0, args= 0, gp= 0
.mask 0x40000000,-4
.fmask 0x00000000,0
.set noreorder
.set nomacro
# End mchp_output_function_prologue
addiu $sp,$sp,-16
sw $fp,12($sp)
move $fp,$sp
sw $4,16($fp)
sw $5,20($fp)
lw $3,16($fp)
lw $2,20($fp)
addu $2,$3,$2
sw $2,0($fp)
lw $2,0($fp)
move $sp,$fp
lw $fp,12($sp)
addiu $sp,$sp,16
j $31
nop

8
EE4341 Spring 2023

.set macro
.set reorder
# Begin mchp_output_function_epilogue
# End mchp_output_function_epilogue
.end add
.size add, .-add
.ident "GCC: (Microchip Technology) 4.5.2 MPLAB XC32 Compiler v1.34"
# Begin MCHP vector dispatch table
# End MCHP vector dispatch table
# Microchip Technology PIC32 MCU configuration words

The add function begins similarly to the previous function with some housekeeping and identification
of the entry point

.align 2
.globl add
.set nomips16
.set nomicromips
.ent add
.type add, @function

And next the entry point label followed by stack frame information.

add:
.frame $fp,16,$31 # vars= 8, regs= 1/0, args= 0, gp= 0
.mask 0x40000000,-4
.fmask 0x00000000,0
.set noreorder
.set nomacro

Here the .frame directive indicates the 16 bytes will be used for the stack frame. This is less space
that was used for main. Because add is a leaf function, it calls no other functions, we do not need to
reserve space on the stack for the return address or the arguments. The only saved register is register
$30, the frame pointer. The stack frame for add is

9
EE4341 Spring 2023

Next are the assembly instructions for add

addiu $sp,$sp,-16
sw $fp,12($sp)
move $fp,$sp
sw $4,16($fp)
sw $5,20($fp)
lw $3,16($fp)
lw $2,20($fp)
addu $2,$3,$2
sw $2,0($fp)
lw $2,0($fp)
move $sp,$fp
lw $fp,12($sp)
addiu $sp,$sp,16
j $31
nop

Here we use addiu to allocate 16 bytes for stack frame, use sw to save the old $fp on stack, and then
initialize $fp for this stack frame with move.

Next two sw instructions to store the values in the argument registers $4 and $5, in this case the values
of a and b passed to add, into the corresponding stack locations in the previous frame. Two lw
instructions are then used to put them in $3 and $2 which again function as scratch registers. The addiu
instruction then adds these values placing the result in $2. The result in $2 is then put in the stack
location reserved for local variable z with sw. An lw is then used to load register $2 with this value in
the stack location corresponding to z; this is the value that will be returned. A move instuction updates
$sp with the current value of $fp, an lw instruction restores the old value of $fp, and addiu is used to
deallocate the 16 bytes of memory used by the stack frame. The function concludes with a jump
instruction which returns to main.

The marco and reorder capability is reenabled

.set macro
.set reorder

and the code ends with housekeeping directives for add

# Begin mchp_output_function_epilogue
# End mchp_output_function_epilogue
.end add
.size add, .-add

and for the entire file

.ident "GCC: (Microchip Technology) 4.5.2 MPLAB XC32 Compiler v1.34"


# Begin MCHP vector dispatch table

10
EE4341 Spring 2023

# End MCHP vector dispatch table


# Microchip Technology PIC32 MCU configuration words

Optimized Code

When this code is compiled with the Microchip XC32 compiler as

xc32-gcc -O1 -S adder.c

the -O1 flag is used to force level 0 optimizations and the -S flag causes the output from the
compilation process to be the assembly code file adder.s. With level 1 optimization the stack pointer
$sp is used instead of the frame pointer. A listing of this file is the following

.file 1 "adder.c"
.section .mdebug.abi32
.previous
.gnu_attribute 4, 3
.section .text,code
.align 2
.globl add
.set nomips16
.set nomicromips
.ent add
.type add, @function
add:
.frame $sp,0,$31 # vars= 0, regs= 0/0, args= 0, gp= 0
.mask 0x00000000,0
.fmask 0x00000000,0
.set noreorder
.set nomacro
# End mchp_output_function_prologue
j $31
addu $2,$5,$4

.set macro
.set reorder
# Begin mchp_output_function_epilogue
# End mchp_output_function_epilogue
.end add
.size add, .-add
.align 2
.globl main
.set nomips16
.set nomicromips
.ent main
.type main, @function
main:
.frame $sp,40,$31 # vars= 16, regs= 1/0, args= 16, gp= 0

11
EE4341 Spring 2023

.mask 0x80000000,-4
.fmask 0x00000000,0
.set noreorder
.set nomacro
# End mchp_output_function_prologue
addiu $sp,$sp,-40
sw $31,36($sp)
li $2,2 # 0x2
sw $2,16($sp)
li $2,3 # 0x3
sw $2,20($sp)
lw $4,16($sp)
lw $5,20($sp)
jal add
nop

sw $2,24($sp)
move $2,$0
lw $31,36($sp)
j $31
addiu $sp,$sp,40

.set macro
.set reorder
# Begin mchp_output_function_epilogue
# End mchp_output_function_epilogue
.end main
.size main, .-main
.ident "GCC: (Microchip Technology) 4.5.2 MPLAB XC32 Compiler v1.34"
# Begin MCHP vector dispatch table
# End MCHP vector dispatch table
# Microchip Technology PIC32 MCU configuration words

For the main routine the the directives are

main:
.frame $sp,40,$31 # vars= 16, regs= 1/0, args= 16, gp= 0
.mask 0x80000000,-4
.fmask 0x00000000,0
.set noreorder
.set nomacro
# End mchp_output_function_prologue

In this case only one registers is saved. The stack frame looks like

12
EE4341 Spring 2023

Stack frame for optimized function main.

Since with optimization the frame pointer is not used the entry on the stack for it remains unused. Stack
references are with respect to the stack pointer.

The assembly code for main is then the following

addiu $sp,$sp,-40
sw $31,36($sp)
li $2,2 # 0x2
sw $2,16($sp)
li $2,3 # 0x3
sw $2,20($sp)
lw $4,16($sp)
lw $5,20($sp)
jal add
nop

sw $2,24($sp)
move $2,$0
lw $31,36($sp)
j $31
addiu $sp,$sp,40

This code for main is similar to that in the version which was not optimized. However the level 1

13
EE4341 Spring 2023

optimization has eliminated $fp along with the stack manipulations associated with it, has eliminated
some additional stack manipulation, and filled the delay slot after the jump instruction with an addiu
instruction. The resulting code has 15 instead of 22 instructions and consequently occupies less
memory and executes in a shorter time.

The optimized add function

The code at the entry point of the add function is

add:
.frame $sp,0,$31 # vars= 0, regs= 0/0, args= 0, gp= 0
.mask 0x00000000,0
.fmask 0x00000000,0
.set noreorder
.set nomacro

For the add function with the level 1 optimization there is no stack frame. This is evident from the
.mask and .fmask directives.

In this case there are two assembly instructions. The addu instruction adds the contents of the two
argument registers, $4 and $5 placing the result in register $2 to return. This instruction is placed in the
delay slot following the jump instruction that returns execution to main

# End mchp_output_function_prologue
j $31
addu $2,$5,$4

Note that by eliminating the stack we save the space that would be be allocated for it, 16 bytes in the
unoptimized example, and avoid the need for additional instructions with associated stack
manipulations. Moreover all the data is now in processor register. The code has a smaller footprint, a
total of 8 bytes, and executes faster, in 2 cycles.

References

[1] MPLAB® XC32 C/C++ Compiler User's Guide (Mircochip, DS50001686G)

[2] MPLAB® XC32 Assembler, Linker and Utilities User's Guide (Microchip, DS50002186A)

[3] SYSTEM V APPLICATION BINARY INTERFACE, MIPS RISC Processor Supplement


3rd Edition (The Santa Cruz Operation, Inc., 1996)

14

You might also like