Memory & Addressing
Assembly Fundamentals
Chapter 3 · Memory & Addressing
assembly1-2 named an "Evaluate Address" phase that only runs for instructions touching memory, without ever saying exactly how that address gets computed. This chapter closes that gap. You'll see what memory actually looks like from the CPU's point of view, why a 16-bit instruction physically cannot hold a full 16-bit address, and the four distinct addressing modes LC-3 uses to work around that limit.
Memory as an Addressable Array
Picture LC-3's memory as one enormous array — 65,536 numbered slots (216, since LC-3 addresses are 16 bits wide), each one holding exactly one 16-bit word. The address is the array index; the word is the value stored there. Reading memory means "give me the value at index N"; writing memory means "store this value at index N."
Why Instructions Can't Just Hold a Full Address
Every LC-3 instruction is exactly 16 bits wide — the same width as a single memory word. A handful of those bits are always spent on the opcode (4 bits) and, often, a destination register (3 bits) and other bookkeeping. That leaves nowhere near enough room to also embed a full, standalone 16-bit address inside the instruction itself.
This single hardware constraint is the entire reason addressing modes exist at all: instead of an instruction saying "the address is exactly this," it says "compute the address this specific way," using whatever few bits it has left. That's what the rest of this chapter walks through.
The Four Addressing Modes
1. Immediate — the value is right there in the instruction
No address computation at all — the operand is embedded directly in the instruction's own bits.
AND R1, R1, #0 ; the value 0 is baked directly into this instruction
Fast and simple, but limited: LC-3 only reserves 5 bits for an immediate value in ADD/AND, meaning it can represent numbers from -16 to 15 — nowhere near a full 16-bit range.
2. PC-relative — the address is computed from where you already are
Used by LD, ST, LEA, and BR. The instruction holds a small, signed 9-bit offset; the CPU adds that offset to the (already-incremented) PC to get the target address:
effective address = PC + SEXT(offset9)
START LD R1, VALUE ; address computed as PC + a small offset ... VALUE .FILL #5
This is the exact LD R1, VALUE from assembly1-1's pipeline example and assembly1-2's own Evaluate Address trace — you now have the actual formula behind that step. The assembler computes the offset for you at assemble time (per the pipeline from assembly1-1) so you can just write a label.
3. Indirect — the address of the address
Used by LDI and STI. The 9-bit offset works exactly like PC-relative — but instead of pointing at the data itself, it points at another address, which in turn holds the real target address:
pointer address = PC + SEXT(offset9) effective address = mem[pointer address]
START LDI R1, PTR ; PTR itself holds an address, not the value ... PTR .FILL VALUE ; a pointer VALUE .FILL #42 ; the real data
If you've ever worked with a pointer-to-a-pointer in C, this is the exact same idea: one extra hop through memory before you reach the real value.
4. Base+offset (indexed) — an address relative to a register
Used by LDR and STR. Instead of offsetting from the PC, the address is computed relative to whatever value currently sits in a chosen base register:
effective address = BaseR + SEXT(offset6)
LDR R1, R2, #3 ; address = (value in R2) + 3
This is the mode that makes arrays and data structures practical — R2 could hold the starting address of a list, and the offset selects which element to read, exactly the way indexed access works in a high-level language.
| Mode | Example | Effective address | When you'd reach for it |
|---|---|---|---|
| Immediate | AND R1, R1, #0 | N/A — value is in the instruction | Small constants (clearing a register, small increments) |
| PC-relative | LD R1, VALUE | PC + offset9 | A single, known variable defined nearby in your program |
| Indirect | LDI R1, PTR | mem[PC + offset9] | A pointer whose target can change, or lives far away |
| Base+offset | LDR R1, R2, #3 | R2 + offset6 | Array elements, struct fields, anything computed at runtime |
assembly1-2 deliberately left this step abstract — now you know precisely what's happening inside it for every kind of memory-referencing instruction.
cpu8bit1 and the future x86-64 course apply to their own real chips' addressing quirks.
Hands-On Exercises
Explain, using this chapter's own "Why Instructions Can't Just Hold a Full Address" section, why LC-3 needs multiple addressing modes at all instead of just one instruction format that always stores a full 16-bit address.
📄 View solutionGiven LDI R1, PTR where PTR is stored at address x3050 and contains the value x4000, and memory address x4000 contains the value #99, trace the two-step effective-address computation and state what ends up in R1.
You need to read the 4th element of an array whose starting address is already sitting in R3 at runtime (the exact index isn't known until the program runs). Which of the four addressing modes from this chapter fits that job, and why would the other three modes fail to work for this specific case?
📄 View solutionChapter 3 Quick Reference
- LC-3 memory: 65,536 word-addressable locations (16 bits each) — not byte-addressable like most modern chips
- A 16-bit instruction has no room to hold a full 16-bit address — this is why addressing modes exist
- Immediate — the value is embedded in the instruction itself (small range only)
- PC-relative — address = PC + a small signed offset (LD/ST/LEA/BR)
- Indirect — address = mem[PC + offset] — a pointer to the real address (LDI/STI)
- Base+offset (indexed) — address = a register's value + a small offset (LDR/STR) — the mode behind array/struct access
- PC-relative/indirect offsets are only 9 bits wide, limiting their reach to roughly ±256 words