Click here to access the Canvas page with the repository for this studio.
Now that we’ve had some experience with working in assembly, we’ll use assembly to perform some more complicated tasks that work with references and get more experience with the C memory model.
By the end of this studio, you should:
We’ll start out by taking a look at how to use global variables. We will do this while exploring two addressing modes: data direct and data indirect, both of which can be used to read and write main memory (i.e., load a memory value into a register or store a register value into memory).
Set up your repository. We have written a bare bones project,
memoryModel. In the
assembly.S file are assembly stubs for three functions. Let’s start by looking at increment and decrement.
The goal of increment and decrement is simple: we want to add one or subtract one from a global variable. First, define a global variable in your assembly code. We wish to use a single byte for our counter. This counter should have an initial value of 0.
Complete the increment function. This function should add one to the global counter variable that you created. Notice that there are no input parameters for this function. To access the global variable, you will use the data direct addressing mode (here is it’s description) associated with the
lds (load) and
sts (store) instructions. Use the name of the global variable for the memory address in the load and store instructions. After loading the value of the global counter variable into a register, increment it and store it back in the global variable. Make sure you are properly using a caller-saved register (or save the register before using it and restore it before the return instruction).
Next complete the decrement function. This function should subtract one from the global counter variable that you created above. Again, there are no input parameters for this function. For decrement, you will use the data indirect addressing mode (here is its description). With indirect addressing, to access the global variable, you will need to get a reference to it, which you can do using the
lo8() functions, as explained in the prep work. You will then need to copy this reference into the appropriate registers (called indirect address registers in the documentation, we typically just say address registers) so you can access it using
st. Make sure you are properly using these registers (i.e. pay attention to whether they are caller-saved or callee-saved). After you decrement the value, store the updated value back to the global variable.
The return value of both these functions should be the value of the global variable after the increment/decrement has taken place. You can check your work by running the tests provided in
Now that you have some practice using global variables, let’s switch to arrays. There’s just one assembly function to complete here:
uint16_t sumArray(uint8_t a, uint8_t length): Add all the elements in an array.
Before you start on these functions, it will be helpful to get some practice using pointers in C to understand how they will work in your assembly functions. Complete the C function
sumArrayPointers(uint8_t *a, uint8_t length). Notice that this function takes in a pointer (reference). You should use this pointer to access the array values and sum up the array.
Some notes to help you with this:
Recall that we can add and subtract values from the pointer to move the index. So at the beginning of the function,
a will be pointing at the first value of the array. If we then run a statement like
a++, the pointer will now be pointing at the second value of the array. We can use this idea to access all of the array values.
To see the value that
a is pointing at, you will need to use the dereference operator. A line of code like
uint8_t x = *a; will grab the value that the array pointer is currently pointing at and store it in a variable. You can then use this value for whatever you like (such as computing a sum).
Note that in practice, we typically would not write
sumArray using pointers in C. The use of brackets
 makes array access much more convenient for us and there is nothing wrong with using arrays in that fashion. We are asking you to write your function using pointers because it is much closer to how the assembly code will operate.
Once you have finished
sumArrayPointers, complete the assembly functions
sumArray. Some notes to help you with these functions:
We now have input parameters, some of which are references, some of which are values. Make sure you understand which parameters fall into each category, and make sure you use them appropriately.
Finding the length of an array in assembly is doable but tricky. Instead of making you do this, we have simply provided the length of the array as an input parameter.
sumArrayPointer function as a guide - these two functions should behave the same way and will follow a similar path to compute the result.
In addition to the
printRegs() function from the previous assignment,
the studio also contains a file called
asmMacros.S. This file includes
macros. A macro is a set of assembly language instructions that can be
added to a file in place of a simple name. This file defines two
macros, one named
printAReg. When either word
assembly.S, many instructions will be inserted in place of the
name. As the names imply, they are intended to print out things, which may
help you debug your code.
print WORD. To print multiple words,
print [MULTIPLE WORDS],
which will print the brackets and the words within. Note that
printAReg will print the contents of a single register. For example,
printAReg 22 will print
r22 = 0xXX. Note that it prints the value in
hex and only the number is used for the register (
22 rather than
These macros and
printRegs can be used to study how individual values
change as your assembly code executes.
When done, your code should run all test cases without error.
This is a mental checklist for you to see what the Studio is designed to teach you.
Generated at 2023-11-17 18:42:25 +0000.
Page written by Doug Shook & Roger Chamberlain.