ARMAssembly:BinaryIntro D

Copyright 2018, Clinton A Staley

Concepts

  1. Memory
  2. Registers

RAM or Main Memory

RAM is an acronym for Random Access Memory, which describes lots of different types of memory, but the term has become a near synonym for the large working memory in which a computer stores currently executing programs and their data. Main memory is another common term.

Any program reads and writes information to/from RAM. In a higher level language like C, this takes the form of reading and writing variables, which are named locations in memory. In assembly, it's a little more detailed, dealing more directly with memory as a raw series of bytes, though you still organize these bytes into variables.

From an assembly viewpoint, the RAM is a large series of bytes ("large" meaning billions of bytes). And each byte is individually addressed, with addresses starting from 0 and going up to whatever number of bytes there are in the RAM. You can think of RAM as a big array of bytes, with addresses as array indexes.

But, there's more structure than that. While each byte can be individually addressed and read/written by assembly instructions, the underlying hardware requires that bytes be fetched and written in words of 8 bytes each, or in simpler CPUs, 4 bytes each. So, let's break the RAM picture in the diagram into words.

Our examples will use 4 byte words because they're easier to draw and talk about, and because most embedded programming uses 4 byte words.

Bytes are grouped into words because memory data comes into the CPU via a group of 32 or 64 wires termed a bus, each wire carrying one bit. So with a 32-bit memory bus, even if the CPU reads a single byte from memory, it gets three other bytes "for free".

A 32-bit bus is designed so that it fetches words beginning at addresses that are divisible by 4. So, as the diagram shows, the bytes at addresses 0-3, 4-7, and 8-11 each form a word, as do the bytes at addresses 120040 - 120043. But the bytes from 5-8, 21-24, etc do not form words, and could not be read/written as a unit by the RAM hardware. Similarly, a 64-bit bus would require addresses divisible by 8.

As we saw in an earlier segment, large integers occupy four or more bytes. When the CPU reads/writes such integers, it must do so in words. This means that multibyte values in RAM must start at divisible-by-4 addresses. (We'll see later that failure to follow this rule can cause program errors.)

(Another historical note. The size of "words" has increased across the decades. Older machines had 2-byte words, and 16-bit memory busses. VERY old machines had 8-bit busses and read/wrote memory one byte at a time.

Question 1

If a machine has exactly 1 billion bytes of RAM, how many words is that? (This is a slightly trick question -- answer carefully)

Answer 1

This would depend on the word size of the machine. Assuming exactly 1 billion bytes (a gigabyte is actually a little more than 1 billion) then if the word size is 4 bytes, the memory has 250,000,000 words, but if the word size is 8 bytes, then there are only 125,000,000 words. And an antique with 2 byte words would have 500,000,000 words (though it probably wouldn't be able to handle that large a RAM memory).

Registers

In some CPUs, assembly instructions can work directly from memory, e.g. adding two memory words together and putting the result into a third word written directly back into RAM.

But this is quite slow, from a CPU standpoint. The 100 nanoseconds or so it takes to read/write a word from/to RAM seem pretty fast to us, but a modern CPU can do 100 or more computations in that time. That's quite a mismatch, and it makes memory access a speed bottleneck.

In a later module, we'll look at caching, a clever way to get around this problem. But for now, the simplest way to deal with the slowness of memory is for the CPU to have its own internal memory that runs fast enough not to slow the CPU down. Then the CPU can copy (slowly, from its perspective) information such as a variable from RAM into this internal memory, work quickly with the variable there across many instructions, and write any changes to the variable back into RAM when it's done.

This internal memory, in almost every CPU, takes the form of registers, typically one-word memory areas that can be directly worked on by assembly instructions. Recall from an earlier segment that our example add instruction added two registers and put the result into a third. Those registers would first have been loaded from RAM via other assembly instructions, and the sum might later have been written back into RAM, or perhaps just used without being saved.

An ARM CPU has 16 4-byte registers, dubbed R0 through R15. Each can contain a 32-bit integer. An ARM processor also has some other registers we'll encounter later, but those 16 are the main working set for most ARM programs. Other CPUs and assembly languages, e.g. Intel and MASM assembly, have more registers, but the principle is always the same. Registers are the CPU's "working space" for fast computation, and the most important data the CPU is working with at any time is generally held in registers.

Let's end with a thought question that sets up a time-model we'll refer back to in later lectures. It's helpful to imagine time slowed down to "CPU perspective", so that the relative speed of different operations that are to us instantaneous, is more readily seen.

Question 2 If we slow down time by a factor of 1 billion, so that a CPU takes perhaps one second per instruction, how many seconds would a RAM fetch take? What about a disk access, which in normal time takes around 10 milliseconds (not microseconds)?

Answer 2 RAM access takes 100 s, or over a minute and a half. Disk access is much slower, taking 10 million seconds (10 milliseconds is 10 million nanoseconds). That's almost four months!

And On to Some Code

By now you've probably had enough of preliminaries. In the next segment we'll look at some ARM assembly code.