Learning Center
Plans & pricing Sign in
Sign Out



									The Memory Map and Addressing

Last lecture we showed how to build and call subroutines in assembly code

Lecture 7 The Memory Map and Addressing Modes

– We used jump-and-link and jump-register to go to and return from a subroutine

This lecture is about memory – How it is organised – How to access it

References: – Stallings Chapter 11 – Tanenbaum 5.4 – Patterson Chapter 3

Dr Iain Styles, School of Computer Science October 2006



MIPS Addresses
Address = 4n Byte 0 Byte 1 Byte 2 Byte 3 Word n

What exactly is memory? – It's just a collection of boxes – Each box stores one item – In MIPS, each item is a 32-bit binary string MIPS also allows access to individual bytes

– Each box is referenced by an address – The address is just a number (in binary format) – An address will typically have 32 bits Memory addresses run from 0 to 232 -1 In MIPS, this allows access to 232 bytes, since MIPS is byte-addressable
¡ ¡

Address = 8 Address = 4 Address = 0

Byte 0 Byte 0 Byte 0

Byte 1 Byte 1 Byte 1

Byte 2 Byte 2 Byte 2

Byte 3 Byte 3 Byte 3

Word 2 Word 1 Word 0

MIPS word addresses are always multiples of 4 – They are said to be aligned – We won't deal with unaligned data in this course

– MIPS is a big-endian machine The word address is the address of the left-most byte

There are special instructions for loading and storing individual bytes (lb, sb) which can be useful



The Memory Map

The Code and Data Memory

The memory is not just a collection of boxes, there is a bit more structure than that

The code segment of memory is where program instructions are stored – Usually occupies the lower part of the memory space At the very bottom is some protected memory, which is used for critical OS code The code segment is of fixed size

Different parts of the memory are used for different purposes

Broadly speaking, there are four main segments into which memory is divided

High memory

Stack Free memory Heap Data Code

The data memory is where globally defined program data is stored – Anything defined at the top-level of a program is stored here – Memory allocation in this block is static, although the contents need not be


Low memory


The Heap

The Stack

The heap is a segment of variable size, starting at some fixed memory address and growing upwards

The stack is used to store data temporarily within a program

It is used to store dynamically allocated data – Arrays which are created at run-time

It occupies the upper regions of memory, with the first stack entry at the highest address and the most recent entry at the lowest address – It fills up from the top to the bottom

Allocation of heap addresses is dynamic – Each time a new variable is created it will be allocated a free slot on the heap

Unlike the heap, entries can only go onto the stack at one point – “Pushing” a value onto the stack adds an entry at the highest available address (stack height increases) – “Popping” a value from the stack clears the most recent entry off the stack (stack height decreases)

The heap is essentially just a big pile of boxes and data goes wherever it will fit

Can access data at any point on the stack using offsets, but can only add/remove items from the bottom of the stack




How the stack works

Pushing and Popping the Stack
&stack Top of stack Push r9 onto the stack Top of stack Contents of r1 Contents of r5 Contents of r9

The address of the bottom of the stack is &stack – Stored in a special register called the stack pointer, $sp

An item is added (pushed) onto the stack, it is placed at &stack (stored in $sp) sw $r1, $sp // push r1 onto the stack

Push r1 onto the stack Top of stack Contents of r1 &stack Pop the stack Push r5 onto the stack Top of stack Contents of r1 Contents of r5 &stack

The stack pointer is then changed accordingly subi $sp, $sp, 4

// update stack pointer


We update the stack pointer by subtracting four – Remember that MIPS is byte-addressable Items are removed (popped) from the stack from address &stack+4 addi $sp, $sp, 4 lw $r1, $sp // update stack pointer // pop the stack into r1

Top of stack Contents of r1 Contents of r5


&stack 10

Using the stack: a detailed example

Using the stack
lw $r1, &b lw $r2, &c add $r1, $r1, $r2 subi $sp,4 sw $r1, $sp lw $r1, &d lw $r2, &e add $r1, $r1, $r2 lw $r2, $sp addi $sp,4 mult $r1, $r1, $r2 // Load b into r1 // Load c into r2 // b+c, result into r1 // update stack_ptr // Push r1 onto the stack // Load d into r1 // Load e into r2 // d+e, result into r1 // pop the stack // update stack_ptr // (b+c)*(d+e)

Imagine that you only have registers $r1 and $r2 available for you to use freely – This could be because the other registers are being used to store other global variables – Or the machine you are working on may only have a few registers

// Can't load d or e into r1 yet

The stack allows you to store intermediate values temporarily

// Now we can load d and e

Consider the code fragment a = (b+c)*(d+e);

Don't have enough registers to store everything, so we have to use the stack

// Get b+c back from the stack



Comments on the stack

Accessing the stack using an offset
Top of stack

Using the stack to hold register contents directly is known as “spill”

We can also use the stack to hold local variables in a subroutine – It's very easy to push them onto the stack, and pop them off the stack without needing to worry about where they're going to go – they just take the next free stack entry

Any stack elements can be accessed, but can only add/remove elements from the bottom – We access these elements using a stack offset – This is especially useful when the variable on the stack is an array In general, if a[N]is an array of N elements (from 0 to N-1), then &a is the address of a[0] – The address of subsequent array entries is &a[i] = &a + i*sizeof(a)

&stack+20 &stack+16 &stack+12 &stack+8 &stack+4 &stack

s[3] s[2] s[1] s[0] b a

Provided that we know what order we pushed things onto the stack, it is trivial to access them directly

In this way, we can use the stack to quickly and easily allocate new space to local variables



Computing memory addresses

Addressing Modes

We have seen how different parts of the memory have distinct uses

In lecture 4 we showed how the 32 bits of each machine instruction were used to encode the instruction

It is the segregation of program from data that allows us to use the program counter to keep track of whereabouts in our program we are – Allows instructions to be stored sequentially without being interspersed by data

Load/store instructions use 16 bits to encode addresses

Jumps have a 6-bit opcode and a 26-bit address segment

Branches have a 6-bit opcode, 2x5-bit register addresses, and a 16-bit address segment

So far we’ve just referred to addresses in memory with a symbol (e.g. &a, &x etc)

Our full memory addresses are 32-bits long

Let’s now figure out how to compute the addresses explicitly

It appears that we can’t access all memory locations using these instruction formats

This takes us one step closer to really understanding what happens when a program runs

A variety of addressing modes allow us to get around this problem



Load/Store Instructions
If the memory address of the variable is < 216-1=65535 we can use direct or immediate addressing lw $r1, 10000 If we want to access higher addresses, we must do something else

Jumps and Branches

A standard jump j has 26 bits available to encode an address – This is enough to span 256MB of memory – Program code is rarely this large and so a plain jump uses immediate addressing


In base addressing, the address is stored in one of the registers: lw $r1, $r2 – Loads the data at the address specified by the contents of $r2, i.e. $r2=&variable

If a bigger jump is needed, we can use base addressing with the jr instruction Branches are more complex: only 16 bits are available for address encoding

We must use some kind of base/displacement addressing

Displacement addressing is similar, but allows us to add a constant: lw $r1, 32($r2) – Loads from the address stored in r2, plus 32 – Often used when accessing arrays

Since branches tend to be over quite short distances (e.g. out of a loop, or to a clause in an if statement), it makes sense to use the current point in the code as the base address



Comments on addressing

PC-Relative Addressing

There are many more addressing modes that we haven’t looked at today

The address of the current instruction is given by the program counter

CISC processors generally have more addressing modes than RISC machines – Intel is especially complex

Since the PC is incremented early in the instruction cycle, branch addressing is done relative to the next instruction bne $r1, $r2, L1 8000 add $r3, $r4, $r5 j NEXT L1:

Using an appropriate addressing mode can save you time and effort – Arrays are much easier to access using displacement addressing – You also use displacement addressing to access the stack – lw $r1, 8($sp) points to the third element of the stack

8004 8008 8012 8016

sub $r3, $r4, $r5

NEXT: mult $r1, $r3, $r3

After the branch, PC=8004, so the branch distance to L1 (8012) is 8 – this is the address that will be encoded in the machine instruction Incidentally, the jump will encode 8016 in its address field – a direct address



We can now write the actual machine code!

Writing the machine code


All we need to do is replace the instructions with their opcodes, specify sources and targets, and calculate addresses bne $r1,$r2,L1 add $r3,$r4,$r5 j NEXT L1: sub $r3,$r4,$r5 NEXT: mul $r1,$r3,$r4

In this lecture we have – Seen how memory is partitioned into code, data, heap and stack – We’ve discussed the purpose of each partition and spent some time discussing the stack and its importance as a temporary resource that is easy to manage – We’ve discussed different methods for addressing the memory and when each is appropriate – We've translated our assembly language into machine code

5 0 2 0 0

1 4 4 3

2 5 5 3

3 8016 3 1

8 0 0 0

32 34 30

In binary, this is exactly what would sit in the code section of the memory

Next lecture we will – Begin to study the microarchitecture of the CPU to see how our instructions are executed

Note that add, sub, mul have the same opcodes, but a different func specifier – they are essentially variants of the same instruction

There are some subleties I have not discussed


– See appendix A of Patterson for details


To top