The CPUlator: https://cpulator.01xz.net/?sys=arm-de1soc.
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):
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:
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:
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:
r2
into r0
.
This is the start of the list, so r0
now contains the
first value in the list.
r0
, the value of r2
is incremented by 4, because of the index value #4
at the
end of the instruction. r2
now points to the second value
in the list.
(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.
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.)
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
.
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?
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.