The CPUlator: https://cpulator.01xz.net/?sys=arm-de1soc.
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:
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
:
It is very important that the subroutine do two things:
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)
ldmfd
(LoaD Memory Full Descending stack)
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:
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.
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.)
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.