Twenty Three Hundred
Dr Charles Martin
Semester 1, 2022
but first, a few more instructions
Not all instructions treat the bit patterns in the registers as “numbers”
Some treat them like bit vectors (and
, orr
, etc.)
There are even some instructions (e.g. cmp
, tst
) which don’t calculate a
“result” but they do set the flags
Look at the bit operations section of your cheat sheet
mov r1, 0xFF
mov r2, 0b10101010
bic r3, r1, r2
r1
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
r2
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
r3
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
Other instructions will shift (or rotate) the bits in the register, and there are lots of different ways to do this!
See the Shift/Rotate section of the cheat sheet
Be careful of the difference between logical shift and arithmetic shift
Your microbit’s CPU actually has special hardware (called a “barrel shifter”)
to perform these shifts as part of another instruction (e.g. an add); that’s
what {, <shift>}
means on e.g. the cheat sheet
There are dedicated bit shift instructions (e.g. lsl
) and other instructions
which can take an extra shift argument, e.g.
@ some examples
adds r0, r2, r1, lsl 4
mov r3, 4
mov r3, r3, lsr 2
mov r3, r3, lsr 3 @ off the end!
We haven’t looked at everything on the cheat sheet
(not even close!)
The cheat sheet doesn’t have everything in the reference manual
(not even close!)
But you can do a lot with just the basics, and you can refer to the cheat sheet whenever you need it
Yes, they can! That’s what we’ve been doing with the instructions so far (e.g.
mov
, add
, etc.) manipulating values in registers.
Registers are super-convenient for the CPU, because they’re inside the CPU itself.
And we can give them all special names—r0
, r9
, lr
, pc
, etc.
RAM (Random Access Memory) is for storing lots data
Perhaps
Current price: ~$100 for 16GB
Three technologies to keep in mind:
static RAM (SRAM): uses flip flops (faster, but more expensive and physically larger)—it’s used in registers & caches
dynamic RAM (DRAM): is slow(er), more power-efficient, cheaper and physically denser—it’s used where you need more capacity (bytes)
Flash: flash, or “non-volatile” memory is used for storage with power turned off (e.g., SSD), it’s slower again and more complicated to read/write.
Most computers have all of these types, but the “RAM” in your computer usually refers to DRAM
(a bit small compared to your laptop/desktop!)
Essentials of Computer Organization and Architecture, Ch. 6 “Memory”
The solution: refer to each different section of memory with a (numerical) address
Each of these addressable units is called a cell
Think of it like a giant array in your favourite programming language:
byte[] memory = { 80, 65, 54, /* etc. */ };
One interesting question: what should the smallest addressable unit be? In other words, how many bits are in each bucket? 1, 8, 16, 32, 167?
The ARMv7-M ISA uses 8-bit byte addressing (so do most of the systems you’ll come across these days)
8 bits == 1 byte
Usually, we use a lowercase b to mean bits, and an uppercase B to mean bytes, e.g. 1Mbps == 1 million bits per second, 3.9 GB means 3.9 billion bytes
Again, there’s no fundamental reason it had to be that way
But there’s a trade-off between the number of bits you can store and the address granularity (why?)
8 bits provides 256 ($2^8$) different values, which is enough to store an ASCII character
It’s a one-dimensional array (i.e. there’s just a single numerical address for each memory cell)
When “drawing a picture” of memory (like in the earlier slides) sometimes we draw left-to-right (with line wrapping!), sometimes top-to-bottom, sometimes bottom-to-top
It doesn’t matter! The address is all that matters
Can you get data in and out of memory with the instructions we’ve covered already in the course?
nope.
We need a new instruction (well, a bunch of them actually)
ldr
is the the l
oad
r
egister instruction
It’s on the cheat sheet under Load & Store
Any load instruction loads (reads) some bits from memory and puts them in a register of your choosing
The data in memory is unaffected (it doesn’t take the bits “out” of memory, they’re still there after the instruction)
@ load some data into r0
ldr r0, [r1]
[r1]
?Here’s some new syntax for your .S
files: using a register name inside square
brackets (e.g. [r1]
)
This means interpret the value in r1
as a memory address, and read the
32-bit word at that memory address into r0
remember, memory addresses are just a number
Can we specify the memory address in an immediate value?
Yes, but the number of addresses would be limited to what could fit in the instruction encoding (remember, that’s what immediates are!)
But more often you’ll read the address from a register (so you get the full
$2^{32}$ possible addresses, but you have to get the address into a register
before the ldr
instruction)
ldr
examplemov r1, 0x20000000 @ put the address in r1
ldr r0, [r1] @ load the data into r0
What value will be in r0
?
Decimal | |
Hex | |
Binary |
0x55
0x5444666
-9
0x467ab787e
Answers: yes, yes, yes, no (too big!)
ARM instructions have at most 12 bits of room for immediate values (depending on encoding), but it can’t represent all the values 0 to 4096 ($2^{12}$)
Instead, it uses an 8-bit immediate with a 4-bit rotation—Alistair McDiarmid has a really nice blog post which explains how it works
Ok, so we probably want to put some data in memory first
The ARMv7-M has a paired st
ore r
egister instruction for ldr
, which takes a
value in a register and stores (writes) it to a memory location
str r0, [r1]
Again, the [r1]
syntax means “use the value in r1
as the memory
address”—this time the address to store the data to
str
examplemov r0, 42
mov r1, 0x20000000
str r0, [r1]
What will the memory at 0x20000000
look like after this?
Memory is byte addressable, but a register can fit 4 bytes
So we can load up to 4 bytes into a register—which order do we “combine” them in?
Because the memory at those addresses might have been:
the result of a str
operation from your microbit
read from a file created on some other machine
received over the network
Little-endian is now more common, but it’s important to know that other options exist
Sometimes, you just want to read a byte or a halfword (2 bytes), even though you’ve got a 4 byte register
The instruction set provides additional load/store instructions for this:
ldrb @ load byte from register
ldrh @ load halfword from register
strb @ store byte to register
strh @ store halfword to register
They work just the same, but they read fewer bytes from memory (and pad the value in the register with zeroes)
Are these byte/halfword versions of the instructions necessary? Or could you live without them?
the address & the value at that address are different (but they’re both just numbers)
The address space is the set of all valid addresses
So on a machine with 32-bit addresses (like your microbit) that’s $2^{32} = 4 294 967 296$ different addresses
So you can address about 4GB of memory (is that a lot?)
You can see from the diagram on the previous slide: the address space is divided into “chunks”
Some parts look like “memory” as we’ve been talking about so far (e.g., SRAM, External RAM) but some parts don’t (e.g. Peripherals)
What if everything the CPU did in interacting with the outside world was treated like a load or a store to a memory address?
This is the idea behind the load/store architecture, and it’s the model your microbit CPU uses
You’ll see “Memory diagrams” (picture representations of data in memory, or at least in the Cortex address space)
Look for the addresses—which direction are they ascending/descending?
Remember that the spatial layout can be misleading!
Some of it is:
This is a consequence of the load/store model: we treat everything like memory, because it makes the CPU simpler
The microbit conforms to that Cortex M memory map (since it’s a Cortex M CPU)
But even within those memory ranges the addresses of specific peripherals (e.g. timers, GPIO, LCD, audio codec) are unique to this particular model of microbit
To find out more, you need the nRF52833 Product Specification
You probably noticed the Code section at the bottom (i.e. the lower memory addresses) of the address space/memory map diagram
That’s where the encoded instructions are: this is sometimes called the instruction stream
Each instruction has a memory address
That’s where the fetch-decode-execute cycle fetches from
(based on the address in the pc
register)
All these 32-bit numbers are fine for the microbit, but not so good for humans
Labels provide a way to (temporarily) give a name to a memory address
You’ve seen labels already—main
is one! Any word followed by a colon (:
)
in your assembly code is a label
@ these two are the same
label1: mov r0, 5
label1:
mov r0, 5
How do we get back to a previous part of our program?
The answer: change the value in the program counter (pc
) to “jump back” to an
earlier instruction
To do this, use a b
(branch) instruction, e.g.
b 0x80001c8
but where to branch to?
You don’t want to have to figure out the address of the instruction “by hand”
and move it into the pc
So we use labels in the assembly code to keep track of the addresses of specific instructions
And there’s a b
(branch) instruction to tell your microbit to make the jump
to that instruction
main:
mov r0, 0
@ infinite loop - r0 will overflow eventually
loop:
add r0, 1
b loop
b<c> <label>
The <c>
suffix tells us that the branch instruction knows about the condition
flags, i.e. NZCV
This is huge.
beq <label> @ branch if Z = 1
bne <label> @ branch if Z = 0
bcs <label> @ branch if C = 1
bcc <label> @ branch if C = 0
bmi <label> @ branch if N = 1
bpl <label> @ branch if N = 0
bvs <label> @ branch if V = 1
bvc <label> @ branch if V = 0
See the back of the cheat sheet for the full list
Does your microbit need to know about the labels? Where is that information stored?
When you build your program, the linker program:
arm-none-eabi-ld -nostdlib -T lib/link.ld --print-memory-usage src/main.o lib/startup.o -o program.elf
Memory region Used Size Region Size %age Used
FLASH: 800 B 512 KB 0.15%
RAM: 184 B 124 KB 0.14%
CODERAM: 0 GB 4 KB 0.00%
The linker replaces them all with addresses before you create the binary file
(e.g. program.elf
) which is uploaded to your microbit
The other thing that the linker does is to make sure that the various parts of your program get put in the right part of the address space
This is a good thing™
It’s all controlled by the linker file.