LDR, STR, LDI & STI
Base+offset and indirect addressing modes.
The Problem with PC-Relative
LD and ST use a 9-bit offset, giving a range of only -256 to +255 from the PC. What if your data is further away?
The LC-3 provides two solutions: base+offset and indirect addressing.
LDR — Load Base+Offset
LDR DR, BaseR, #offset6 loads the value at memory[BaseR + offset] into DR. The base register holds a full 16-bit address, so you can reach any memory location. The 6-bit offset gives a range of -32 to +31 from the base..ORIG x3000
LEA R1, ARRAY ; R1 = address of ARRAY
LDR R0, R1, #0 ; R0 = ARRAY[0] = 10
LDR R2, R1, #1 ; R2 = ARRAY[1] = 20
LDR R3, R1, #2 ; R3 = ARRAY[2] = 30
ADD R0, R0, R2 ; R0 = 10 + 20
ADD R0, R0, R3 ; R0 = 30 + 30 = 60
HALT
ARRAY .FILL #10
.FILL #20
.FILL #30
.ENDSTR — Store Base+Offset
STR SR, BaseR, #offset6 writes the value in SR to memory[BaseR + offset]..ORIG x3000
LEA R1, DATA ; R1 = address of DATA
AND R0, R0, #0
ADD R0, R0, #5
STR R0, R1, #0 ; memory[DATA + 0] = 5
ADD R0, R0, #3
STR R0, R1, #1 ; memory[DATA + 1] = 8
HALT
DATA .FILL #0
.FILL #0
.ENDLDI — Load Indirect
LDI DR, LABEL loads the value at the address pointed to by LABEL into DR. It's a double dereference: DR = memory[memory[LABEL]]..ORIG x3000
LDI R0, PTR ; R0 = memory[memory[PTR]] = memory[x4000]
HALT
PTR .FILL x4000 ; PTR contains the address x4000
.ENDSTI — Store Indirect
STI SR, LABEL stores the value in SR to the address pointed to by LABEL. It follows the pointer at LABEL and writes there: memory[memory[LABEL]] = SR..ORIG x3000
AND R0, R0, #0
ADD R0, R0, #7
STI R0, PTR ; memory[memory[PTR]] = memory[x4000] = 7
HALT
PTR .FILL x4000 ; PTR contains the address x4000
.ENDUse LDR/STR when you have the base address in a register (great for arrays). Use LDI/STI when you need to follow a pointer stored in memory.
Choosing an Addressing Mode
This is one of the trickiest parts of LC-3. Here's the decision guide:
"My data is near the PC" (within ±256 words) → Use LD/ST with a label. This is the simplest case — your .FILL or .STRINGZ is right there in the same program. "I have a pointer in a register and want to access data through it" → Use LDR/STR. This is what you need for walking arrays and strings. First load the base address into a register (with LEA or LD), then use LDR to read elements at offsets from that base. "My data is far away (like x5000) and I don't have the address in a register yet" → Use LDI/STI to go through a pointer. Store the far address in a nearby .FILL, then LDI follows that pointer to reach the distant data. Or use LD to load the far address into a register, then switch to LDR for repeated access.For most projects: Load the starting address with LD R1, ADDR (where ADDR .FILL x5000), then use LDR to access individual elements through R1. Store final results with STI.
Write a program that uses LDR to sum all 4 elements of an array and prints the sum as an ASCII digit. The array contains {1, 2, 3, 4} so the sum is 10... let's use {1, 2, 3, 1} for a sum of 7.