CP216: Lab 04 - Spring 2025 - Lists

Due 11:59 PM, Thursday, June 12, 2025

The CPUlator: https://cpulator.01xz.net/?sys=arm-de1soc.

Data Alignment

The program list_demo.s walks through and displays the contents of a list of word-sized numbers to r0 (Use Step Into to watch the values in the register):

        
.org 0x1000 // Start at memory location 1000
.text           // Code section
.global _start
_start:

ldr    r2, =Data    // Store address of start of list
ldr    r3, =_Data   // Store address of end of list

Loop:
ldr    r0, [r2], #4 // Read address with post-increment (r0 = *r2, r2 += 4)
cmp    r3, r2       // Compare current address with end of list
bne    Loop         // If not at end, continue

_stop:
b _stop

.data
.align
Data:
.word   4,5,-9,0,3,0,8,-7,12    // The list of data
_Data: // End of list address

.end

      

The list begins at the address labeled Data. The list values are comma separated, and are word sized because the .word directive defines them so. The address of the end of the list (i.e. immediately after the last item) is labeled as _Data. This is important as otherwise we have no way to know where the list ends. (With null-terminated strings, we can look for the 0x00 character.)

A word value must start on a word boundary, and just the use of the .word directive is no guarantee that a value starts on a word boundary, just that the value is word-sized. The .align directive inserts from 0 to 3 values of 0x00 at its memory location to make sure that the following value starts on a word boundary. (You do not have to tell it how many bytes to use - it automatically uses the required number.)

With the .align directive as given above, the data in memory looks like (displayed in signed decimal):

Data Aligned in Memory

Now we force the list to be unaligned by adding a single byte with a value of 1 in front of it instead of using .align:

        
.data
BAD:
.byte   1
Data:
.word   4,5,-9,0,3,0,8,-7,12    // The list of data
_Data:  // End of list address

      

The resulting memory looks like:

Unaligned Data in Memory

The original list values are still there (switch to hex display to see them), but because they are now shifted by a byte, they no longer make sense in decimal.

We can correct this with the .align directive:

        
.data
BAD:
.byte   1
.align  // Force list to start on a word boundary
Data:
.word   4,5,-9,0,3,0,8,-7,12    // The list of data
_Data:  // End of list address

      

and the list values are re-aligned:

Realigned Data in Memory

Traversing a List

There are a number of ways to walk through the list, for this lab we will look only at ARM's post-indexing addressing mode. We would like, wherever possible, to let the language do as much of the work as possible for us. The instruction:

        
ldr r2, =Data     // Store address of start of list
…
ldr r0, [r2], #4  // Read address with post-increment (r0 = *r2, r2 += 4)

      

does the following:

(Note that if the immediate value used is negative (#-4), then in effect is it a post-decrement.)

The end condition in this case is to compare the current address in r3 against the address immediately following the end of the list, i.e. the value in _Data:

        
ldr r2, =Data     // Store address of start of list
ldr r3, =_Data    // Store address of end of list
…
Loop:
ldr r0, [r2], #4  // Read address with post-increment (r0 = *r2, r2 += 4)
…
cmp r3, r2        // Compare current address with end of list
bne Loop          // If not at end, continue

      

No need to keep track of the length of the list or use the add instruction on a register to increment the address, the ldr instruction takes care of that.


Conditional Execution

Imagine that we wish to process a list of values, and count how many positive, negative, and zero values are in the list. Python code for this would look something like:

        
neg_count = 0
pos_count = 0
zer_count = 0

for v in values:
    if v < 0:
        neg_count += 1
    elif v > 0:
        pos_count += 1
    else:
        zer_count += 1

      

Given what we know about value comparison and branching in ARM assembly, the matching assembly code would look something like:

        
ldr r2, =Data   // Store address of start of list
ldr r3, =_Data  // Store address of end of list
mov r5, #0      // Initialize negative count
mov r6, #0      // Initialize positive count
mov r7, #0      // Initialize zero count

Loop:
ldr r0, [r2], #4  // Read address with post-increment (r0 = *r2, r2 += 4)
cmp r0, #0        // Compare number to 0 - required only once as other instructions
                  // before Next do not change the condition flags
TestNegative:
bge TestPositive  // Number is not negative - skip to next test
add r5, r5, #1    // Increment the negative count
bal Next          // Get the next value
TestPositive:
beq TestZero      // Number is zero - skip to next test
add r6, r6, #1    // Increment the positive count
bal Next          // Get the next value
TestZero:
add r7, r7, #1    // Increment the zero count

Next:
cmp r2, r3        // Compare current address with end of list
bne Loop          // If not at end, continue

      

(See the complete code in the file branching_demo.s.)

ARM assembly provides conditional execution for its instructions that can significantly reduce the amount of code necessary to do these comparisons. Most ARM instructions have extensions that match the condition flag checks for the Branch instruction that we saw in the Breakpoints lab. Thus, where we were able to Branch on EQual (beq) or Branch on Greater Than (bgt), we can also Add on EQual (addeq) or move on Greater Than (movgt). In other words, addition or the copying of registers can be dependent on the state of the condition flags. If a condition is not met, then the instruction is not executed. The code above could be written as:

        
ldr r2, =Data   // Store address of start of list
ldr r3, =_Data  // Store address of end of list
mov r5, #0      // Initialize negative count
mov r6, #0      // Initialize positive count
mov r7, #0      // Initialize zero count

Loop:
ldr r0, [r2], #4    // Read address with post-increment (r0 = *r2, r2 += 4)

cmp r0, #0          // Compare number to 0
// Conditional executions
addlt   r5, r5, #1  // Increment the negative value count if r1 is negative
addgt   r6, r6, #1  // Increment the positive value count if r1 is positive
addeq   r7, r7, #1  // Increment the zero value count if r1 is zero

cmp     r2, r3      // Compare current address with end of list
bne     Loop        // If not at end, continue

      

which is significantly simpler than the branching approach.

(See the complete code in the file conditional_demo.s.)

  1. Using the code from list_demo.s write a program named l04_t01.s that calculates the sum of all the values in the list and displays that value in r1.


  2. Create a new version of l04_t01.s named l04_t02.s that counts the values in the list and displays the count in r4, and displays the number of bytes in the list in r5.

    Can you determine the number of bytes in the list without using the loop?


  3. Create a new version of l04_t02.s named l04_t03.s calculates the minimum and maximum values in the list, and displays them to r6 and r7 respectively. Use conditional execution to determine the minimum and maximum values.

    Note: initialize the minimum and maximum values to the first value in the list. You may assume the list has at least two values.


Zip your files together in zip file named login_l04.zip (using your Laurier login, of course) and submit that zip file to the MLS dropbox.