Archive

Assembly Language - Part 7



Chris Johnson

Loading and storing data

This month, I wish to look at loading data from memory, and storing data to memory, although we have, of course, seen some of these instructions in previous example code. On the face of it, we have an easy task, since we are dealing with a very small number of instructions only. These have the very general format

instruction, register, location

and consist of the following instructions.

LDR - load the register with the contents of the 32­bit word at the specified location.

STR - save the register contents in the specified memory location.

LDRB - load the register with the contents of the 8­bit byte at the specified location.

STRB - save the low byte of the register contents in the specified memory location.

When using the single byte operations, the top three bytes are automatically cleared during the operation. The LDR/STR operations should normally use word aligned addresses (you can actually use non-word aligned addresses to load words, but you may not get what you think you should get), whereas the LDRB/STRB instructions can access any memory location.

There are a number of ways we can specify the memory location to be used. It is perhaps useful to consider the way Basic does the same operations. For my examples, I shall use the ! operator, although, with very little change to the examples, we could use the ? operator for byte operations. Basic would use the ! (pling) operator for simple word load and saves in the following way.

value% = !(location%)

or

!(location%) = value%

The assembler counterparts would be

LDR R0, [R1]

and

STR R0, [R1]

Note that the location is always a register, and is specified by the square brackets. We are assuming that the register R0 is used to hold the value we are loading or storing, and that the register R1 has already been assigned to point to the appropriate memory location.

In Basic, we can be more subtle as to how we specify the location, since the pling operator can take two 'parameters' in the form offset%!base%. This comes into its own when memory locations are being accessed in a loop. For example,

FOR B% = 0 TO 7
  V%(B%) = (B%*4)!buffer%
NEXT

which would load seven words of data from the buffer into the array V%. Assembler has similarly powerful addressing modes, and the simple examples we had above are really simple cases of what is called pre-indexed addressing.

Pre-indexed addressing

Pre-indexed addressing has syntax of the form

LDR dataregister, [base address,offset]

where offset may be positive (the location is at a higher address), or negative (the location is lower in memory). The offset is optional and may be omitted (we did not use any offset in the examples above), and may be specified either in a register, or as an immediate constant. The address actually used to store or load the data is (base address + offset). If it is specified in a register, it may have a shift applied, but only by an immediate number. If the offset is specified as an immediate constant, it is stored as twelve bits magnitude, and a direction bit. It may thus have the range -4095 to +4095.

Let us consider an example. Suppose we wished to copy 8 words of data from buffer1 to buffer2. The Basic might look thus.

FOR B% = 0 TO 7
  (B%*4)!buffer2% = (B%*4)!buffer1%
NEXT

The assembler could be implemented as follows.

ADR R0, buffer1  ; point R0 at buffer1
ADR R1, buffer2  ; point R1 at buffer2
MOV R2, #7   ; number of words to copy
.copyword
LDR R3, [R0,R2,LSL #2] ; the LSL #2 multiplies by 4
STR R3, [R1,R2,LSL #2] ; i.e. steps of 4 bytes (1 word)
SUBS R2, R2, #1 ; decrement counter
BGE copyword    ; another word to copy

ADR is a Basic assembler directive, which assembles an instruction (often an ADD or SUB involving the program counter) to produce, in the destination register, the required address.

It was noted above that the offset may be positive or negative. For an immediate constant, the instruction could be

LDR R0, [R1, #-4]

This would load the data from the address R1-4. If we were specifying a negative offset contained in a register, we would use the instruction

LDR R0, [R1, -R2]

Note how the negative sign is placed in front of the register name.

The assembler will also accept the LDR instruction in a slightly different form. If a label has been defined, e.g. named somewhere, then assembling the instruction

LDR R0, somewhere

will generate an instruction which uses the program counter as the base register, and will calculate the relative offset to the label, which must lie within the range -4095 to +4095. This is very useful for pulling in fixed data or constants for use in the program. These constants can be embedded in the code at assembly time.

The writing or reading of a block of memory, by stepping through in sequence, is a very common requirement. To help in some of these situations, the LDR/STR instruction can itself do the incrementing (or decrementing) using what is called "write back".

Write back

Write back is performed simply by placing a ! at the end of the instruction. For example, the instruction

LDR R0, [R1, #-4]!

will load data into R0 from the memory location R1-4, as above, but will then perform an automatic

SUB R1, R1, #4

When write back is performed, it does not take any more time than when no write back is done. In effect, in certain circumstances this can save the SUB or ADD instruction. One instruction does not seem much. However, if we are dealing with large areas of memory and looping thousands of times, the saving in time can be quite significant. The example given above could be written in several ways using write back. In this particular case, it does not save any instructions, since by counting down to zero, we have already saved a compare instruction. Anyway - here is one way.

ADR R0, buffer1  ; point R0 at buffer1
ADR R1, buffer2  ; point R1 at buffer2
ADD R2, R0, #32  ; first word after those we wish to copy
ADD R1, R1, #32
.copyword
LDR R3, [R2, #-4]! ; use write back
STR R3, [R1, #-4]! ; steps of 4 bytes (1 word)
CMP R2, R0      ; have we got back to start of buffer
BNE copyword    ; another word to copy

In this case, the contents of R1, and R2, are updated after each load or store, and we need to do a compare to check for completion. Remember, the offset is added (or subtracted) before the operation, which is why we add 32, rather than 28, to get our starting point.

Post-indexed addressing

In this alternative way of addressing, the offset is not added (or subtracted) until after the operation has been carried out, and write back always takes place, so the ! becomes redundant. The syntax is such that the offset is placed after the square brackets as in

LDR R0, [R1], #-4

This will load the data from the address held in R1, and then do the subtraction. Using post-indexed addressing, our example from above becomes as follows.

ADR R0, buffer1  ; point R0 at buffer1
ADR R1, buffer2  ; point R1 at buffer2
ADD R2, R0, #28  ; last word we wish to copy
ADD R1, R1, #28
.copyword
LDR R3, [R2], #-4 ; post-indexed addressing
STR R3, [R1], #-4 ; steps of 4 bytes (1 word)
CMP R2, R0      ; have we got back to start of buffer
BGE copyword    ; another word to copy

Note how we add 28 this time rather than 32, because the operation uses the current value in the base register, and then the write back calculation is done. It also means we have to change the condition used after the compare instruction.

All of these addressing modes can be used with both load and store, and with word or byte operations.

More to note

The load and store instructions do not effect any of the processor flags. Therefore if you wish to check, for example, whether a word or byte just loaded is zero, you must do it explicitly, using perhaps the TEQ instruction.

If you are using conditional execution with one of the byte operations, then the B follows the condition, as in

LDRNEB R0, [R2] ; load byte only if Z flag is unset

Next month

I hope to introduce the use of stacks, and multiple load and store.


Contents - The Archives - Archive Articles