Assembly Language Programming

Course Index 
Dr. Margush's page 

Lab on Functions

This lab will explore various parameter passing techniques in the context of a simple function to compute a maximum value. You will be developing one program to exercise a collection of functions. Write a separate function for each part of the lab. Your main program will just call the separate parts. Each part will include specific function calls and other actions. No part should rely on a previous part. This way you can easily comment out earlier parts as you work on the later ones.

As you implement max functions, document what registers are changed by each function. You should try to preserve registers if it is not too much trouble. You do not have to worry about the partX code saving registers; these are supposed to be independent of each other, so register contents are meaningless when each begins its task.

reset:
;initialization as needed (stack, for example)
rcall part1
rcall part2
rcall part3
rcall part4
rjmp PC ;to finish up

Part I - Register arguments:

We will start with a function named max1 that accepts two unsigned ints and returns the maximum of the two. The function heading might be expressed as follows:

uint_16 max1(uint_16 b1, uint_16 b2); //return the largest of the arguments

Include these data definitions in the program - these will be used as arguments for the functions:

.cseg
args .dw 3201, $CE6, 056473, 0b0010110001101
;.equ num_args = (this will be filled in in part IV - leave as a comment for now)

.dseg
max1_result .byte 2 ;the answers will be stored here
max2_result .byte 2
max3_result .byte 2
max4_result .byte 2

The max1 function expects arguments to be placed in registers (register parameters) so part1 code must be written to load the arguments into R25:R24 and R23:R22. The function will return the result in R25:R24. To compare 16 bit unsigned data, use cp followed by cpc followed by a brsh or brlo. Use the following style to define the parameters for the max1 code:

.def max1_b1H = R25

Using C notation, your program will be implementing the statement:

max1_result = max1(args[0], args[1]); //where args is an array of words (unsigned).

The part1 code must store the result into memory as indicated by the assignment statement.

Document the function and part1 code.

Part II - Stack arguments

Next, we will write a second max function; this time the function will expect the parameters to be on the (hardware) stack. The function heading looks similar (we are adding an third argument so this function computes the max of three numbers), but it will expect arguments in a different way. The return value will be in R25:R24 as before.

uint_16 max2(uint_16 b1, uint_16 b2, uint_16 b3); //return the largest of the arguments

The arguments are expected to be pushed onto the stack before the function call occurs. Always push the arguments form right to left (push the last argument first). Your function will set Y to point to the stack frame so the parameters can be accessed using indirect addressing with displacement. Design the function so only two data words are held in registers at any time. Your max2 function must call max1 (twice) to determine the correct maximum of the three parameters. Do not concern yourself with saving and restoring registers for this lab.

You are implementing this statement:

max2_result = max2(args[2], args[1], args[0]); //where args is an array of words (unsigned).

Write the part2 code to push the three arguments onto the stack and then call max2. Push the words so they are stored in little-endian order (just like they are in flash). Draw a picture of the stack after the call. Label the parameters, then use equate directives to define the parameter offsets in the stack frame.Use the following style.

.equ max2_b1L = 3 (This will be the offset into the stack of the low byte of b1)

After returning to part2, the code must remove the parameters from the stack and store the result in max2_result (add this to the data segment).

Document max2 and part2 code.

Part III - Call by Reference

In this variation, the max3 function will store the maximum of two numbers directly into sram using the call by reference technique. The function heading is a little different:

void max3(uint_16 * result, uint_16 b1, uint_16 b2); //save the largest of the arguments

You are implementing this function call:

max3(&max3_result, args[0], args[3]); & means address of

Once again, write the part3 code to push the arguments onto the (hardware) stack. Always push high bytes first to maintain little-endian order. This function call setup will require additional pushes to get the address of max3_result on the stack. You must call max1 from max3 to get the answer.

In max3, define symbols representing the offsets to the parameters using the .equ method as before. A picture showing the locations of all of the parameters is always helpful. Keep in mind that max3 actually stores the result somewhere in sram; there is no return value. Once you return to part3 code, you still have to remove the arguments from the stack.

Document max3 and part3 code.

Part IV - Passing an array

Since the arguments are located in physical sequential memory (an array), we could pass the array address and the array size. This would allow the function to operate on a variable number of items. Write a new version of the max function using this prototype:

uint_16 max4(uint_16 * args, byte n); //return the largest of the n numbers in the array

You are implementing the call:

max4_result = max4(&args, num_args);

Use the stack as before to pass the arguments and use .equ to name the parameters. In max4, use a loop (and call max1) to determine the largest of the values in the array. Remember that the address of the array that is passed to the function is an address in flash. Somewhere you will need to use a factor of 2. Now is the time to actually define the symbol num_args that was mentioned in Part I; this comes immediately after the args array that represents the number of items in the array (use PC in the defining expression). Use this symbol when setting up the arguments to the function. Add some additional numbers to the args array and verify that the correct maximum is stored; the equated symbol should be set to an expression that automagically calculates the number of items in the array.

Document max4 and part4 code.

Part V - Inline parameters

This part is optional, and not to be attempted by the faint of heart. The last version of max will be similar to max4, however the arguments will be provided inline. This will work since the address of the result, the address of the array, and the number of values in the array are constant for our sample calls. The function heading looks like this:

void max5(uint_16 * args, byte n, uint_16 * result); //store the largest of the n numbers in the array into result

In part 5, code the arguments using two .dw's and a .db immediately after the rcall. Draw a picture of how this will look in flash. You should place the parameters in the same order they appear in the function heading. The max5 function must take the return address off the stack and use it to access the parameters. When finished, Z should hold the return address. Don't forget that Z will sometimes need to hold a byte address, and other times a word address. Call max4 from max5. Remember that max5 must also store the result into sram. The part5 code will be very short.

Document max5 and part 5 code.