We have a instruction called "call", which takes a label of the address where the function instructions begin. We also have an instruction called "ret", which indicates the end of a function.
So "call" invokes a subroutine(callee) by using "call Imm26", where Imm26 is an immediate 26-bit value. On NIOS, sets r31(also known as ra) to the next instruction after the call(PC+4), and sets PC to Imm26. Since PC requires a 32-bit number, it sets the first four bits of PC to the first four major bits, assuming that the other functions of the program will be around the point of invocation, then the next 26 to Imm26, then 00 for the last two bits to ensure that the memory address is aligned by four.
Then the callee returns to the caller using "ret". Sets PC to ra.
There are a few problems conceptually with this:
.section .text .equ LEDR 0x10000000 .global main main: movia r8,LEDR repeat: call off call delay call on call delay bru repeat end off: movi r9, 0 // but what if r9 is in use? stwio, r9, (r8) ret
Our off routine won't work, since we'd be overwriting registers that might be in use in other places. So we need to find a way to save our register values when we start calling the function and set them back when the function returns.
Requires a pointer to keep track of the top of the stack called the stack pointer (sp). On NIOS II, sp is r27.
Recall the memory layout:
Data (grows down)
Dynamic memory allocated
Stack (sp on top) (grows up, so decreasing addresses)
Push operation: push the content of a register on to the stack, and sets the stack pointer to be the beginning of that memory that just got pushed on.
subi sp, sp, 4 // move the stack pointer up one word stw r9, (sp)
Pop operation: Get the first thing on top of the stack.
ldw r10, (sp) addi sp, sp, 4
Exchange of info between caller and callee is established:
We have different means of accomplishing the actual parameter passing, as well:
We use the stack because it's flexible and is being recycled.
We have two ways of saving the info: caller-save and callee-save. The former delegates the responsibility of saving the info to the function that is invoking the subroutine, while the latter delegates to the subroutine being invoked.
e.g. main calls foo. caller-save makes main do the work, callee-save makes foo do the work.
Register usage in the NIOS 2:
r2 - r3: Subroutine return value (caller-save)
r4 - r7: Subrotuine arguments (caller-save)
r8 - r15: General use (caller-save)
r16 - r23: General use (callee-save)
r27(sp): stack pointer
r31 (ra): return address