CMPUT 229 - Computer Organization and Architecture I

Lab 2: Circular Queue

229 Lab 2: Circular Queue

Writing A Simple Program

Assignment

In this lab you will write your first RISC-V assembly program. Your task is to write a function that prints the contents of a circular queue.

Queues

A queue is a data structure in which data elements can be stored (referred to as "enqueuing"). Elements of a queue can later be retrieved (known as "dequeuing") in a First In, First Out ("FIFO") order, meaning that elements are dequeued in the order that they were enqueued. Some important terminology: the position in memory at which the next element is to be enqueued is called the "tail", and the position of the next element to be dequeued is called the "head". The FIFO model creates an issue in memory that can create inefficiency: if a limited amount of memory is allocated for a queue, and the head is always located at the first position in that memory, then what happens when an element is dequeued? The head would then contain no active element, and thus the following element would need to be shifted to fill in the gap. Unfortunately, this shift creates another gap, so every other element in the queue would need to be shifted in the same manner, which is a costly operation.

Circular Queues

A modified version of the queue data structure, called a circular queue, can avoid this inefficiency. By allowing the head to point to different locations in the memory allocated for the queue, shifting becomes unnecessary. However, if one only allowed the head to move and nothing else, the queue's capacity (the number of elements that can fit in the queue) would decrease every time the head moved closer to the end of the memory space for the queue. The avoid this issue, the queue is made "circular" by allowing the head and tail to go back to the beginning of the memory space for the queue when it reaches the end of it. The queue is empty when the head and tail point to the same position. In order to enable differentiating between a full and an empty queue, we allocate space for one more element than the queue's actual capacity, meaning one element must always be left empty.1 With this design, a circular queue is full when the tail is pointing at the element right before the head. An element that is dequeued is not cleared in memory, the head simply points at the next element. Therefore, the data will still be in memory, it is just no longer within the bounds of the circular queue and should no longer be accessed. That data will be overwritten when the tail reaches that position and a new element is enqueued in its place.

Input

The input to this assignment is a file containing three separate lines of text:

This file is read by the assembly code provided to you in the common.s file. Thus, when your code starts the queue will be already stored in memory. The code in common.s expects the content of the input file to be correct and consistent. Therefore, if you create new input files, make sure that it follows the specification above.

Assignment

Write a RISC-V assembly program for the function printQueue that prints every element in a circular queue in the order that they were enqueued. The elements of the circular queue will be ASCII characters, meaning they will each be stored as an individual byte and can be loaded with lb.

Printing

The printQueue function is prints the characters in the queue, in order, to standard output. The way to do this in RISC-V is to use the PrintChar syscall. This can be done by loading the value 11 into register a7, loading the character you want to print into register a0, and then using the ecall instruction.

Remainder

The RISC-V rem instruction will likely be useful in solving this lab. This instruction has the form rem d, s, t (where d, s, and t are integer registers) and will store the remainder of s divided by t into register d. This operation is equivalent to modulo or % in many programming languages. Remainder can be very helpful in looping around a circular queue. For example, say that the capacity of the queue is 3. As mentioned previously, this means that the memory space containing the queue has room for 4 elements, though one is restricted. This means that the offsets in memory at which there can be a valid element of the queue are 0, 1, 2, and 3. Conveniently, the remainder of any number divided by 4 will be between 0 and 3, and as the number being divided by 4 increments, the remainder will increment within this range until it loops back around to the other side of the range. For example, the remainder of 10 / 4 is 2, the remainder of 11 / 4 is 3, and the remainder of 12 / 4 is 0, having looped back around. This can be used to iterate over the elements of the circular queue.

Suggested Algorithm

  1. Initialize an index variable to the head of the queue
  2. Check that the index is not equal to the tail of the queue
  3. Otherwise, add the index to the base address of the queue to find the address of the current element
  4. Load and print the current element
  5. Increment the index variable
  6. Set the index variable to the remainder of index / (capacity + 1) to allow for looping around
  7. Loop back to step 2

An illustration of how printQueue operates. The position pointed to by the tail, and the reserved position, both contain data ('l' and 'y', respectively). These characters are not to be printed because they are not in the queur. They are in this illustration as a reminder that dequeued elements are not cleared from memory, they simply no longer belong to the circular queue. The tail points to the position at which the next element is to be enqueued and thus it does not point to an active element, so its content is not to be printed.

The common.s File

When your program starts executing, the circular queue and all its elements will already be stored in memory. Your program will receive three memory addresses: (1) the address of the first byte of the memory space containing the queue; (2) the address of the element that is the head o the queue; (3) the address of the element that is the tail of the queue. Your program will also receive the total capacity of your queue, meaning the number of elements that can be contained in the queue (do keep in mind that the circular queue is stored using capacity + 1 bytes because one space must be reserved in order to differentiate between a full and an empty queue). These arguments will be provided in registers a0, a1, a2, and a3. You must use the label printQueue: at the start of your code as the very first label under the .text directive.

Specification

When RARS starts executing, it looks for a main: label where it will find the user code. This common.s file, that is included in your solution template, contains a main function that sets up the circular queue in memory, and then calls your printQueue function using arguments as described above. You are encouraged to read the code in the common.s file to understand how the whole program works.

Here are some instructions to aid you in running and testing the program using the files that were provided in the original git repo.

Guarantees:

Testing

The Code directory contains the following files and directories:

CheckMyLab

You can use CheckMyLab to assess the correctness of your assignment. To access CheckMyLab, you must use the University of Alberta virtual private network. If you have not yet set up the VPN, instructions can be found here. Once connected to the University of Alberta VPN, you can access CheckMyLab. Log in with your CCID and select the assignment, "229_Fa20 - Lab #2: Lab_CircularQueue".

On the CheckMyLab assignment page you can:

  1. Submit a Test Case: Add a test case you've created to the existing pool of tests. Before submitting a test case, please ensure that the test case is valid.
  2. Run a Solution: You must modify your solution as follows to run it on CheckMyLab. Replace the line .include "common.s" with all of the code from common.s. Do this with a copy of your solution because you need to submit a solution that contains the line .include "common.s". After running your solution on some selected tests from the pool, you can view how your program's output and the expected output differs. If the output doesn't match for a test case, it might be helpful to download it and test it yourself.

Resources

Marking Guide

Submission

There is 1 file to submit for this assignment:

For the file printQueue.s, make sure to keep the line ".include "common.s"" and make sure that you do not add a main label.

[1] There is an alternative design where a special value is used to signal that the queue is empty and in that case it is not necessary to have one element always empty. However, the logic gets more complex and therefore we will use this design with a simpler logic for this lab.