Loads, stores, and the address calculation
Trace byte-addressed memory, sign extension, and bounds separately from whether an address can be formed.
On this page
The question for today
What you’ll understand
- Compute byte addresses from base, stride, and index.
- Distinguish sign-extending and zero-extending loads in RV32I.
- Explain why a valid address calculation does not prove a safe memory access.
Arithmetic on a pointer-like value does not itself fetch the bytes at that address. A load reads memory into a register, and a store writes selected register bits to memory, subject to the architecture and execution environment. Keeping address formation, translation, access permission, and data interpretation separate prevents several common mistakes in tensor kernels and low-level parsers. This lesson uses ordinary non-device memory and RV32I examples.
Count addresses in bytes
In a byte-addressed system, adjacent address numbers identify adjacent bytes. A packed array of four-byte elements therefore advances its element address by four, not one. RV32I loads and stores form an effective address by adding a sign-extended 12-bit immediate to a base register. An array index larger than that immediate range may require separate arithmetic. The effective address is an address-sized bit pattern; whether an application owns the corresponding memory is a different question.
Endianness describes how a multibyte value is arranged across addressed bytes. In a little-endian environment, the least significant byte occupies the lowest address. For bytes 0x78, 0x56, 0x34, 0x12 at increasing addresses, a four-byte integer load yields 0x12345678. State the environment because RISC-V permits execution environments to define endianness. Reinterpreting a network packet by blindly loading a host integer can reverse the intended significance, and unaligned or forbidden accesses introduce additional concerns.
Width and extension change the loaded value
An RV32I LB reads one byte and sign-extends its eight-bit value to 32 bits; LBU reads one byte and zero-extends it. If that byte is 0xFE, LB produces 0xFFFFFFFE, interpreted as minus two when signed, while LBU produces 254. Both read the same memory byte. The distinction belongs to the load instruction, not to a tag stored alongside that byte. Stores select low-order source bits for the requested width; they do not preserve an abstract high-level type.
Naturally aligned accesses have addresses divisible by their width. The RISC-V execution environment specifies how misaligned loads and stores are handled, including whether support is invisible or an exception occurs. Do not assume a misaligned access is cheap or atomic merely because it works on one machine. Similarly, crossing a page boundary can require access to two mappings. An aligned address calculation avoids one class of problem but does not establish that every accessed byte belongs to the object or is permitted.
Safety and ordering need their own arguments
Before loading element i of an n-element array, establish that i lies from zero through n minus one, that the address arithmetic has the required language semantics, and that the array is still alive. Memory protection operates at mappings and permissions, so an out-of-bounds access can land inside a mapped page and still violate the program’s contract. A successful load is not proof of bounds safety. Concurrent mutation, device memory, and atomics bring further rules outside this ordinary single-threaded example.
The function lab shows the exact curve y=ax², with its tangent and area. It can illustrate how a quadratic pair table’s payload grows when positive x represents normalized item count and positive a represents normalized bytes per pair entry. Use x=0.5 and then x=1 to predict the fourfold change. It does not display addresses, endian conversion, cache misses, or page faults. Use hand traces for those semantics, then use real tools and the documented memory model when diagnosing an implementation.
Work it through
Read one field without confusing its width
A little-endian byte buffer starts at address 4,096 and contains eight four-byte records. Record 3 has first byte 0xFE. Compute its start and compare RV32I LB and LBU there.
Use zero-based indexing and a four-byte stride: record 3 starts at 4,096+3×4=4,108. Its first byte is within the eight-record buffer, whose payload is 32 bytes.
LB reads 0xFE and sign-extends its sign bit, producing 0xFFFFFFFE. Interpreted as a 32-bit signed number, that is minus two. No four-byte record value was loaded by this byte operation.
LBU reads the identical byte but fills higher bits with zero, yielding 0x000000FE, or 254. Neither interpretation is automatically the correct field type; the record format must say whether the field is signed.
The same byte can yield minus two or 254 under different documented loads. Stride, width, and extension must all match the data format.
The living notebook
Explore the model
Interactive lab · function
Payload growth before address tracing
Use y=ax² with positive normalized count x and normalized per-entry byte coefficient a to inspect a dense square table’s payload. Compare x=0.5 and x=1 to predict the fourfold increase. The diagram does not simulate loads, addressing, or caches.
- y at chosen x
- 0.608
- Tangent slope · 2ax
- 1.35
- Signed area · ax³/3
- 0.182
This lab uses only y = ax². Playback traces the curve within the fixed integration interval from 0 to x; it does not estimate numerical error or change the chosen point. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forRepresentational choices determine how many bytes must eventually be addressed, before access behavior is measured.
Pause & reason
Check your understanding
Your turn
Try it yourself
A packed 16-bit array begins at byte address 8,192. Find element 7 and the payload of 12 elements. Does mapping the page prove element 12 is valid?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Use zero-based indexing and distinguish a mapping from an object boundary.
Compare with the solution
Element 7 starts at 8,192+7×2=8,206. Twelve elements occupy 24 bytes. Element 12 is one past the final valid index, 11; a mapped containing page does not authorize reading that element or prove a valid language-level object access.
Keep the idea
Take it with you
- Address formation and memory access are distinct operations.
- Load width and signedness determine extension into a register.
- Alignment, permissions, bounds, lifetime, and ordering need separate checks.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.