Intro to Computer Engineering

Assignment 8 - Assembly Puzzles

Click here to access the Canvas page with the repository for this assignment.



It’s easy to take high level programming languages, like C and Java, for granted. They make writing code convenient for humans at the expense of adding in an extra step (compilation) that turns your code into something that the computer can execute, like machine language.

For this lab we are taking out the compiler! You are tasked with writing a series of functions entirely in assembly. You will definitely want to keep the AVR Assembly Reference handy. We’re going to ask you to use some instructions that you probably haven’t seen before.

Please note that you should not use any branching instructions. You may use the “skip” instructions, however (and in fact may be required to do so!).

An important note on registers: as summarized here (and described in the documentation but watch out for errors in Table 5.1), there are different kinds of registers in assembly. A caller-saved register may be used and modified by a subroutine, so any calling function must save them before calling a subroutine, either by moving them to another register or pushing them to the stack. A callee-saved register should not be affected by calling a subroutine (without likely crashing the calling C code), so your subroutine must save them before modifying them and restore them before returning. In other words, if you change the values in any callee-saved registers, like r28 or r29, you must restore them back to their original values before your function returns.

The easiest way to save a register is to use the stack. Interaction with the stack utilizes two primary instructions: push and pop. Push will put the contents of a register on the top of a stack. Pop will remove the last value placed on the stack and put it in a register.

This particular stack is often referred to as the call stack or the run-time stack. The call stack is an important concept in computer security — some vulnerabilities take advantage of it (this is covered in CSE 361S). The call stack acts like the stack data structure you may have seen in other classes.


The assignment

Assembly Language

In your repository you will find two files: assemblyFunctions.h and assemblyFunctionTestCases.ino. The .h file contains the prototypes for all the functions we are asking you to write for this lab. The test file contains some examples of those functions in action. You’ll notice that a lot of stuff is commented out in the test file. As you complete the functions, you should uncomment the corresponding lines to test your code.

You need to create a file called assemblyFunctions.S and set it up so that you can start defining your functions in that file. If you’re confused on how to do this, refer back to the studio.

Below is a brief description of each of the functions we’re asking you to implement (some of which should look familiar!). Each can be done without using a branch (br... or jmp) instruction, but the various skip instructions (cpse, sbrc, sbrs) may be useful. DO NOT USE the branching instructions on this assignment.

boolean hasAOne(byte num): Returns a Boolean true if the binary representation of the given 8-bit value contains a one, false otherwise. (Hint: look at the comparison instructions). When using boolean return values a 0 represents a false and a 1 represents a true. (Hint: you can use the skip instructions to “skip over” an instruction that will change the return value)

char byteToAscii(byte num): Takes in an integer value from 0-9 (you can assume that the given byte will always be in that range - no need to check for errors), and returns the corresponding ASCII value as a char.

int int8ToInt(int8_t num): Takes in an 8-bit signed value and converts it to a 16-bit signed value.

int addInt8ToInt(int8_t num1, int num2): Adds an 8-bit signed value to a 16-bit signed value and returns a 16-bit signed result. To do this properly, you should convert the 8-bit argument to a 16-bit argument before performing the addition. Use your int8ToInt function to accomplish this. Be careful with your registers! Even if nothing bad happens when you use caller-saved or callee-saved registers we will be checking to make sure that you used them properly when you demo.

int add4Int8(int8_t num1, int8_t num2, int8_t num3, int8_t num4): Adds the four 8-bit signed values and returns an 16-bit signed result. this must use int8ToInt() (1 call) and addInt8ToInt() (3 calls).

byte average(byte num1, byte num2): Averages the two unsigned 8-bit values and returns an unsigned 8-bit result. Look into the shifting and rotation operations for division.

Although not necessary, we challenge you to complete each with as few instructions as possible. As a reference, it’s possible to do:


In addition to assembly language, you should test your work reasonably thoroughly.

It’s easy to exhaustively test hasAOne, byteToAscii, and int8ToInt. That is, it’s possible to test every possible test case. The provided code includes exhaustive tests for hasAOne and int8ToInt, but the code for byteToAscii is only partially complete. You must finish it. (In this case “exhaustive” just refers to the valid input values — the integers 0 through 9)

It’s more difficult to exhaustively test addInt8ToInt(), add4Int8(), and average(), but it’s easy to test a reasonable set of test cases. The provided code includes test cases for each, but you should modify the code to do more extensive testing.


The check-in

  1. Commit your code and verify in your web browser that it is all there.
  2. Check out with a TA

The rubric

Generated at 2024-04-06 20:30:52 +0000.
Page written by Doug Shook Modified by Bill Siever & Roger Chamberlain & James Orr.