pete > courses > CS 202 Spring 24 > Lecture 16: LDR and STR


Lecture 16: LDR and STR

Goals


where are we going with this?

all these instructions will be combined to form higher-level language constructs

and you may be surprised when you see C and learn that it was once considered a "high-level language"

but we’ll also look at how C is used to implement even higher-level languages like Python


recall that in a RISC architecture, of which ARM32 is a representative, instructions don’t perform operations on memory

we need to use dedicated instructions to grab values from memory into a register (load)

and to save values from registers into memory (store)


it’s worth revisiting the entire idea of memory (and especially how it contrasts with that of registers)

because memory and the operations involving it are without question the most confusing aspect of this course

registers are a very small set of storage elements whose values we can operate on

(ARM32 has 16 general-purpose registers, the processor you’ll build has 8)

registers typically store a number of bits matching the word size of the ISA

in the case of ARM32, this means registers are 32 bits


memory is a huge array of storage elements

we identify an element of the memory array by its location, which we call an address

the amount of data we can store at each address is called the addressability of the memory

the word size is the number of bits the ISA uses to store an address

in ARM32, the word size is 32 bits

(again, the "word size" is often defined differently: this is the definition we’re using in this course; if you encounter the term elsewhere, verify its definition in that context)


for a load, we need to specify the source address in memory and the target register

for a store, we need to specify the source register and the target address in memory

problem: if addresses are 32 bits and instructions are 32 bits (which they are), how do we specify a) the load/store opcode, b) the register, and c) the memory address all in the same instruction?

this is indeed a conundrum!

we’re going to talk about the three methods used in ARM32, collectively referred to as "addressing modes"

ie, ways we can specify addresses

(arm ref manual F, pdf p18)


base register

 3   2                   1                   0
 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+---------------------------------------------------------------+
|       |0 1 0 1 1|0 0 1|  Rn   |  Rt   |                       |
+---------------------------------------------------------------+


assembly:   ldr Rt, Rn

effect:     Rt <- mem[Rn]

example:    ldr r7, r9

this is the simplest one: a register holds 32 bits, so we set a register to the address we want and read that register to get the address


base + immediate

 3   2                   1                   0
 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+---------------------------------------------------------------+
|       |0 1 0 1 1|0 0 1|  Rn   |  Rt   |         imm12         |
+---------------------------------------------------------------+


assembly:   ldr Rt, Rn, #imm12

effect:     Rt <- mem[Rn + zero-extend(imm12)]

example:    ldr r5, r6, #12

this is a more general version of the former instruction, and in fact they’re one and the same

it’s referred to as "load (immediate)" because of the immediate value used as the offset

NOTE! this immediate value is zero-extended rather than sign-extended

(same for all immediate values in load/store instructions)


base + register offset

 3   2                   1                   0
 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+---------------------------------------------------------------+
|       |0 1 1 1 1|0 0 1|  Rn   |  Rt   |         |   |0|  Rm   |
+---------------------------------------------------------------+


assembly:   ldr Rt, Rn, Rm

effect:     Rt <- mem[Rn + Rm]

example:    ldr r9, r1, r0

instead of an immediate value, we can use another register as the offset


base + scaled register

 3   2                   1                   0
 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+---------------------------------------------------------------+
|       |0 1 1 1 1|0 0 1|  Rn   |  Rt   |  imm5   |   |0|  Rm   |
+---------------------------------------------------------------+


assembly:   ldr Rt, Rn, Rm, shift

effect:     Rt <- mem[Rn + (Rm << imm5)]

example:    ldr r3, r2, r5, 2

finally, the most complicated: we can use a base register plus the value of another register shifted by an immediate value

(yep, this is again a general case of the previous instruction)


I could go over the store instructions, but they’re EXACTLY THE SAME

except bit 20 is a zero instead of a one

and they result in values going from registers to memory


why all these different modes?

surely they are redundant!

yes, they most certainly are, but some operations are so frequent that it’s advantageous to have dedicated operations to perform them

this is, in fact, a pattern that I’ve discussed before but bears repeating in this context

if you notice a particular operation being performed over and over and over again, optimize it


I promise all of these are useful

and I will prove it

but not for a couple weeks

that will be part of the discussion demonstrating how we use all these tiny assembly-language building blocks to make a fancy high-level language like C


byte-addressable memory but 32-bit registers?

all of the instructions discussed today operate on 32-bit values in memory

but memory is byte-addressable, how does this even work?

first, if the calculated memory address to load or store is X, these instructions actually load from/store to X through X+3

why X+3? because X, X+1, X+2, and X+3 are each 8 bits (because this is byte-addressable memory) and a register holds 32 bits

bytes at the higher memory addresses go in the higher bits of the register

so mem[X+3] will end up at the left end of the register and mem[X] will end up at the right end of the register

second, there are variants of all these instructions that load or store single bytes or pairs of bytes (the latter are sometimes called "half words")

there are also variants that let us subtract the offsets instead of add them (bit 23)

you won’t need to do anything with these variants, just know that they exist


next: how can we do interesting things with assembly instructions?


Definitions

The following definitions introduced in this lecture are fair-game for future quizzes. You will be expected to give the exact definition as provided in these lecture notes.

Last modified: