CP216: Lab 05 - Spring 2025 - Subroutines

Due 11:59 PM, Thursday, June 19, 2025

The CPUlator: https://cpulator.01xz.net/?sys=arm-de1soc.

Defining a Subroutine

ARM allows the creation of named subroutines, chunks of code that are the rough equivalent of functions in higher-level languages.

The following subroutine swaps the values in two locations in memory:

//-------------------------------------------------------
swap:
/*
-------------------------------------------------------
Swaps location of two values in memory.
Equivalent of: swap(*x, *y)
-------------------------------------------------------
Parameters:
  r0 - address of x
  r1 - address of y
Uses:
  r2 - value of x
  r3 - value of y
-------------------------------------------------------
*/
stmfd   sp!, {r0-r3}    // preserve other registers

ldr     r2, [r0]        // get value at x
ldr     r3, [r1]        // get value at y
str     r2, [r1]        // store value of x in y
str     r3, [r0]        // store value of y in x

ldmfd   sp!, {r0-r3}    // pop preserved registers
bx      lr              // return from subroutine

and to call this subroutine we issue the instructions:

ldr    r0, =x   // store location of x
ldr    r1, =y   // store location of y
bl     swap     // call the swap subroutine

The data is stored as:

.data
.align
x:
.word    9
y:
.word    4

Download the full program: swap.s.

Note that our subroutines are placed after the _stop B _stop instruction, but before the .data section. This guarantees that subroutines will be not executed by mistake.

Loading values into r0 and r1 is the equivalent of passing a parameter - the subroutine documentation tells you that r0 must contain the address of x and r1 contains the address of y. These registers are arbitrary, and any could be used for this. There are more sophisticated methods for passing parameters to subroutines that are covered in later labs.

The subroutine call is executed with the bl (Branch Link) instruction. Immediately before any line is executed, register pc (Program Counter), contains the address of the line. In this case, the pc contains the address 1008, the address of the bl instruction:

bl Instruction
bl Instruction

and the bl instruction contains the address 1010, the address of the swap subroutine.

When the bl instruction is executed, control of the program passes to the line labeled swap. The pc now contains the address of this line, in this case, 1010. However, the program needs to be able to return to the line following the bl instruction once the subroutine has finished. The L (Link) part of the bl instruction instructs the processor to copy the address of the instruction following the bl into Register lr (Link Register). In this case the address 100c is stored in lr:

Updates to pc and lr

It is very important that the subroutine do two things:


Preserving Registers

Registers are preserved by pushing their values onto the system stack. The stack is an area of memory set aside for program use. A typical use is to push values onto it at the start of a subroutine and pop them off when finished. ARM provides instructions for pushing and popping multiple register values at once, which is far simpler than having to deal with the values on the stack one at a time.

The stack pointer is store in the register sp (Stack Pointer). The instructions to push / pop the stack are:

stmfd (STore Memory Full Descending stack)
copies data from a list of registers to the stack
ldmfd (LoaD Memory Full Descending stack)
copies data from the stack to a list of registers

If multiple registers are adjacent, they can be listed in the push and pop with a dash (-) representing the registers in between. For example:

stmfd sp!, {r0, r3, r6}  // Pushes registers r0, r3, and r6
…
stmfd sp!, {r0-r4, lr}       // Pushes registers r0, r1, r2, r3, r4, and lr

Subroutines should thus be written in such a way that any registers that are not actually being used to return values should be preserved. This includes the stack pointer and the stack itself, which must be returned to their original states at the end of the subroutine.

The swap subroutine stores the values of r2, and r3 on the stack with the line:

stmfd  sp!, {r0-r3}

and the stack contains:

Stack
Address Value Description
Call from main
fffffff0 1038 contents of r0
fffffff4 103c contents of r1
fffffff8 0 contents of r2
fffffffc 0 contents of r3

00000000 0 bottom of stack

When finished, r0 through r3 need to be set back to their original values by being popped off the stack. The following line does this:

// recover temporary registers
ldmfd sp!, {r0-r3}

Note that if a register is being updated, or used to return a value (typically r0 is used for this purpose), then that register is not saved on the stack.

The pc register needs to contain the address of the that followed the bl subroutine call so that the program knows where to continue execution. The following line does this:

bx    lr                 // return from subroutine

The bx lr (Branch and eXchange) instruction copies the contents of lr to the pc register and continues program execution from that line.

Program execution then continues from the address in the pc.

This subroutine call process is not automated as it is in a high-level language like Python. It is up to you as the programmer to make sure the subroutine starts with the proper stmfd and ends with the proper ldmfd and that the pc register is properly reset with the bx instruction.

Forgetting to preserve a register may cause a Function clobbered register(s): error. (Details are found by clicking on the Details link that appears in the simulator Messages dialog box.) Although this warning can be turned off, for this lab at least make sure that you fix the error, rather than ignore it!

For all subroutines fill in the registers used in the Parameters and Uses sections as appropriate.

  1. Complete the list_total subroutine in l05_t01.s. (Hint: you covered working with lists in Lab 4.) (Second Hint: the total is returned in r0 - do not preserve it.)


  2. Complete the list_stats subroutine in l05_t02.s.

    This program uses the same data as in the previous task. Note that the registers used for the list addresses are different. In CPULator, registers r0 through r3 can be used to return data from a subroutine. If the contents of registers r4 and are changed within a subroutine body and are not preserved, they will cause a Function clobbered register(s): error. Try it with this program - change the contents of r4 within the subroutine without preserving the register and see what happens.


Zip your files together in zip file named login_l05.zip (using your Laurier login, of course) and submit that zip file to the MLS dropbox.