Starting from:

$30

CSC230-Lab 6 Subroutines Solved

Recall from the previous lab, a function call implies a transfer (branch, jump) to an address representing the entry point of the function, causing execution of the body of the function. When the function is finished, another transfer occurs to resume execution at the statement following the original call. The first transfer is the function call (for invocation); the second transfer is the return. Together, this constitutes the processor’s call-return mechanism for subroutine execution.

 

During the subroutine execution, the corresponding code would likely need to use some registers. We would NOT want that code to override the information stored in the registers by the caller. 

 

How to determine which registers are safe to use?

 

The best practice is to protect the caller’s registers by saving the information stored there on the stack. After the subroutine completes execution, that information can be easily restored. In other words, we can back-up the registers using the stack before executing the subroutine and restore them afterwards. 

 

Who protects the registers, the caller or the subroutine?

 

Arguably, the most convenient option is for the subroutine to protect any registers that it uses.

Following is an example of a safe subroutine which does not produce any undesirable side effects. This subroutine copies a c-string from the program memory location labeled “msg” to the data memory location labeled “msg_copy”. 

 

Example: Create an Assembler project in Atmel Studio 7 and replace the contents of its main.asm with the program provided in copy_string.asm file on conneX. Build the program and debug it. Observe that the initial values in r16 and r30 are changed while the subroutine executes, but then they are restored before the program reaches the end-loop (“done: rjmp done”). Verify the contents of the stack as the program executes and the values are pushed onto it.

 

Below is the diagram of the internal data memory (SRAM) when “lpm r16, Z+” is executed for the first time. 

 

Address
content
details
notes
0x0000 ~ 

0x001F
 
General Purpose Registers 
 
0x0020 ~ 

0x005F
 
64 I/O Registers
 
0x0060 ~ 

0x01FF
 
416 extended I/O

Registers
 
0x0200
 
 
.DSEG
 



 
 
 
 
0x21F7
      
 <-SP
“0x21F7” is stored in the stack pointer register
0x21F8
R16
  saved register
In the subroutine “copy_string”, the registers to be used by the subroutine are pushed onto the stack to protect them from being changed.
0x21F9
XL (R26)
  saved register
0x21FA
XH (R27)
  saved register
0x21FB
ZL (R30)
  saved register 
0x21FC
ZH (R31)
  saved register
0x21FD
high(ret)  
  return address
The CPU pushes the return address onto the stack automatically when “call copy_string” is executed.
0x21FE
mid(ret)  
  return address
0x21FF
low(ret)  
  return address
 

Recall that ATmega2060 has 256KB of word-addressable program memory, and thus the program counter (PC) needs to count as high as 128K word addresses (217 words) (refer to page 7, Table 2-1 of the datasheet). Therefore, the PC register uses 3 bytes, which are stored on the stack when a call instruction executes. Then, the called subroutine stores the protected registers on top of the return address. After the registers are restored, the return address is on the top of the stack again when the ret instruction executes and writes the top three bytes on the stack to the PC register, thereby returning the control flow back to the instruction which immediately follows the initial call instruction.

 

 

II. Passing (and returning) parameters via the stack.

 

There are two ways to pass parameters to a subroutine: by value and by reference. Below are two examples illustrating each method.

 

Example 1 (pass by value): 

 

Consider the following C function that takes two 8-bit unsigned integers as parameters, multiplies them, and returns the result.

 

 

uint16_t multiply(uint8_t multiplicand, uint8_t multiplier) {

       uint16 result = multiplicand * multiplier;

       return result;

}

 
 

An equivalent Assembly function is provided in the multiply_by_value.asm file on conneX. Here, the caller (main program) pushes the 8-bit values of the two parameters onto the stack and also reserves the required 16-bit space on the stack for the return value. The function protects all registers that it uses, retrieves the parameters from the stack, multiplies the integers, and stores the result in the reserved location on the stack before returning.

 

Download the multiply_by_value.asm file and put it in the same location as main.asm for the project that you started earlier in this lab. Then, add the file to your project by right-clicking the lab in the Solution Explorer window and choosing “Add-Existing item…”:

 

 

 

 

Then, right-click the file and choose “Set As EntryFile”:

 

 

 

Only one file can be set as EntryFile at any time; this indicates which Assembly file will be built and debugged by the Atmel Studio. 

 

Build and debug the solution. Observe the stack memory and SP as you step through the program. For convenience, use a breakpoint at the instruction immediately after the multiply loop and the debugger’s “run” command to skip over the loop execution. Below is the stack frame diagram of the stack contents when the instruction “in ZH, SPH” is executed. An equivalent diagram is also available in the comments right before the function code in the multiply_by_value.asm file.

 

Address
content
details
notes
0x0200
 
 
.DSEG
 



 
 
 
 
0x21F1
      
 <-SP
“0x21F1” is stored in the stack pointer register
0x21F2
zero
saved register R0
The registers to be used by the “multiply” subroutine are pushed onto the stack at the very beginning of the subroutine to protect their values from being changed by the subroutine.
0x21F3
result_low
saved register R4
0x21F4
result_high
saved register R3
0x21F5
multiplier
saved register R2
0x21F6
multiplicand
saved register R1
0x21F7
ZH
saved register R30
0x21F8
ZL
saved register R31
0x21F9
high(ret)  
return address
The CPU pushes the return address (the address of the next command) onto the stack automatically when “call add_num” is executed.
0x21FA
mid(ret)  
return address
0x21FB
low(ret)  
return address
0x21FC
0x00
result_high
 
0x21FD
0x00
result_low
0x21FE
0xCD
parameter 2
The caller (in this case the main program) pushes these parameters onto the stack.
0x21FF
0xAB
parameter 1
 

Example 2 (pass by reference): 

 

Below is the stack frame diagram for a very similar multiplication function as above, except instead of the values, their corresponding memory addresses are passed to the multiply subroutine.

  

Address
content
details
notes
0x0200
 
 
.DSEG
 



 
 
 
 
 
 
 
 
 
 
 
 
 
      
 <-SP
“0x21F1” is stored in the stack pointer register
0x21EE
zero
saved register R0
The registers to be used by the “multiply” subroutine are pushed onto the stack at the very beginning of the subroutine to protect their values from being changed by the subroutine.
0x21EF
result_low
saved register R4
0x21F0
result_high
saved register R3
0x21F1
multiplier
saved register R2
0x21F2
multiplicand
saved register R1
0x21F3
YH
saved register R28
0x21F4
YL
saved register R29
0x21F5
ZH
saved register R30
0x21F6
ZL
saved register R31
0x21F7
high(ret)  
return address
The CPU pushes the return address (the address of the next command) onto the stack automatically when “call add_num” is executed.
0x21F8
mid(ret)  
return address
0x21F9
low(ret)  
return address
0x21FA
0x02
address of answer
The caller (in this case the main program) pushes these addresses onto the stack in big-endian format (most significant byte in the lower memory location than the least significant byte).
0x21FB
0x02
address of answer
0x21FC
0x02
address num2
0x21FD
0x01
address num2
0x21FE
0x02
address num1
0x21FF
0x00
address num1
 

As in the previous example, download the multiply_by_reference.asm file, put it in the same location as main.asm and the multiply_by_value.asm files, add it to your Atmel Studio solution, and set it as “EntryFile”. Build and debug the solution. Observe the stack memory and SP as you step through the program. The stack frame diagram above illustrates the stack contents when the instruction “in ZH, SPH” is executed. An equivalent diagram is also available in the comments right before the function code in the multiply_by_value.asm file.

 

III.  Exercises.

 

You may find it helpful to use the stack_frame.docx file provided on conneX or a piece of scrap paper for designing and tracing the stack when answering the questions below.

1.     For this exercise you will write a modified version of the copy_string function in the main.asm file (from the first section of this lab). First, design the stack for it and then modify this function to take the program memory address and the data memory address via the stack instead of using a pre-defined memory location as it does now. This way you can re-use this code in the future to copy any string(s) from program memory to data memory.

2.     Extend the program from the previous exercise. First, design the stack for a new function called string_length, and then write the function. This function should take a parameter via the stack (in big-endian format) which contains the data memory address where a c-string resides. The function should count the number of characters in that string and return it by via the stack. Remember, the caller has to reserve the space on the stack for the return value.

3.     Use the Word file provided on conneX or a blank piece of paper and trace the stack for the recursive function in the triangle_number.asm file, which is located on conneX, from the beginning until the program finishes by reaching the end-loop (“done: rjmp done”). What does the program do?

More products