• LOGIN

Wesley Peck

Home › Tutorials › MicroBlaze Tutorials

Using Functions

Most high level languages provide some notion of functions or subroutines. These abstractions provide a way to break up programs into smaller, self-contained sections which can be written independently and then composed to provide greater functionality. This abstraction is essential for effective programming as it allows us to build programmer up incrementally. When programming in assembly, however, their is no built in support for defining functions. Defining and using functions is just another idiom that we can, and should, use.

Defining a Function

int funcdef( char *val, char p2, int p3 )
{
    ...
    ...
    return 15; 
}
Figure 1: A Function Definition

Consider the function defined in figure 1. If we examine this function closely we will find that there are four major components: the function name, the parameters, the function body, and the return value. To define a function correctly in assembly we must implement all of these components while following the ABI for our architecture. The following gives a generic template for defining a function in MicroBlazei assembly:

    .global funcdef
funcdef:
    <at this point R5 contains *val,
     R6 contains p3, and R7 contains p3>
    ...
    ...
    ...
    addi r3, r0, 15
    rtsd r15, 8

In this example of a function we can see the definition of the four component of a function. First, the function name is defined using the combination of the .global directive and the label. The .global directive is necessary to inform the compiler that the label of the same name should be globally visible to the entire program. Second, there are three parameters that are passed to the function. These parameters, *val, p2, and p3, are stored in the registers R5, R6, and R7 respectively. It is not shown in this example, but, in many functions it may be necessary to save the parameters to the stack in order to preserve their values. Third, the function body is represented by the ellipses. The function body is implemented using what assembly is necessary to get the functionality required. The body will make use of the parameters stored in R5, R6, and R7 during its execution. Last, the return value of the function is placed in R3 using the addi opcode. Return values are always placed in R3 and R4 with R4 only being used if more than 32-bits is needed for the return value. The opcode rtsd is then used to return to the calling function. This opcode uses the value in R15, which is setup during function invocation and should not be modified by the callee function, along with an offset to return to the caller function. The offset should almost always be the value 8.

Invoking a Function

retval = funcdef(val1, val2, val3);
Figure 2: Function Invocation

Now that we are capable of defining functions correctly it is necessary to learn how to invoke functions. An example function invocation is shown in figure 2.In this figure we see that there are three components to a function invocation: the function name, the arguments, and the return value. The following gives a generic template for invoking functions in MicroBlazei Assembly:

addi r5, r0, <val1>
addi r6, r0, <val2>
addi r7, r0, <val3>

addi r1, r1, -4
swi  r15, r1, 0

brlid r15, funcdef
nop

<at this point R3 contains retval>

lwi  r15, r1, 0
addi r1, r1, 4

In this template we see that the first step taken is to load the function arguments into registers R5, R6, and R7. The assembly used for this part of the function invocation, however, depends on the number of parameters in the function that is being called. If the function being called has no parameters then there is no need for this step at all. If the function only has one parameter then it should be placed into R5. Generally, the first parameter is placed into R5, the second into R6, the third into R7, and so on for as many parameters as the function required. The only requirement is that parameters can only be stored in R5 through R10. If the function call required more arguments than 6 then the parameters should be placed in the stack as decribed by the MicroBlaze Reference manual.

The next two opcodes, addi and swi, are used in combination to push the value of R15 onto the stack. Pushing R15 onto the stack is required because the next instruction, brlid, places the value of the program counter into R15 when the branch is executed. This instruction, in addition to saving the program counter in R15, branches to the function funcdef. When funcdef is executed it will have the environment that we setup for it: the values of all of the parameters will be in registers R5 through R10 and R15 will contain the return address which points back to the caller. When funcdef finishes with the rtsd instruction shown in the section above, it will return to the lwi instruction shown above. At this point R3, and possibly R4, will contain the return value from the function. The only thing we need to do is pop R15 off of the stack and place it back into the register. The opcodes lwi and addi above perform this stack popping. This will restore the contents of R15, which were modified by the brlid opcode, to the original value.

Invoking Functions Written in C

printf("Say Hello: %s, %s\n", "Hello", "MicroBlaze");
Figure 3: Invoking C Functions

Invoking a C function, either one written by you or one contained in a library, is done in the same way as invoking an assembly function. When the C compiler compiles the code you have written it follows the same conventions that were given in the sections above. By doing this we give our assembly functions easy access to our C functions and our C functions easy access to our assembly program. As an example, the MicroBlazei assembly given below performs the same function invocation shown in figure 3:

    .global printhello
printhello:
    addi  r5,  r0, FORMAT
    addi  r6,  r0, HELLO
    addi  r7,  r0, MBLAZE

    addi  r1,  r1, -4
    swi   r15, r1, 0

    brlid r15, printf
    nop

    lwi   r15, r1, 0
    addi  r1,  r1, 4

    .data
    .align 4
FORMAT:
    .asciz "Say Hello: %s, %s\n"

    .data
    .align 4
HELLO:
    .asciz "Hello"

    .data
    .align 4
MBLAZE:
    .asciz "MicroBlaze"

The code given above is a complete implementation of a function, named printhello, which will print out "Say Hello: Hello, MicroBlazei" to the console. Notice that the assembly function simply uses the name printf, which is a C function defined in a library, to print to the screen. The printf function is neither declared in assembly nor is its definitinon part of the program we wrote. However, we can call the function just like any assembly function that we write manually because the conventions used are the same.

‹ Basic Control Flow up Interrupt Handling ›
  • Printer-friendly version

Navigation

  • Home
  • EECS 388
  • EECS 665
  • Glossary
  • Search

Outline

  • EECS 388 Laboratory
  • EECS 665 Laboratory
  • Tutorials
    • FPGA Tutorials
    • MicroBlaze Tutorials
      • Introduction to Assembly
      • Basic Data Structures
      • Basic Control Flow
      • Using Functions
      • Interrupt Handling

(C) 2008 Wesley Peck
All Rights Reserved