Memory, little endian and alignment

The four gigabytes a RISC-V program can address and where this editor puts the code, the data and the stack in them. The byte order a word is written in, the five loads and three stores, and the addresses that end a run.

Thirty two registers hold thirty two words, and a program has more than that to keep. Everything else lives in memory, which RISC-V reaches with a 32 bit address and with nothing but the load and store instructions.

The address space

An address is 32 bits, so it runs from 0x00000000 to 0xFFFFFFFF: four gigabytes. Nothing in the hardware divides that up, and this simulator follows RARS in putting each thing at a fixed address:

fromwhat is there
0x00400000text, the instructions you wrote
0x10000000global data
0x10008000where gp starts
0x10010000static data, where .data puts your first label
0x10040000the heap, which ecall service 9 hands out
0x7FFFEFFCwhere sp starts, and the stack grows downwards from it
0xFFFF0000the memory-mapped devices, the screen and the keyboard

A byte nobody has written reads 0 here. Open the memory panel of any program on this page, look anywhere your program did not touch, and every byte is 00. That is this simulator's choice, and the M68K's is the opposite, so a program that reads uninitialised memory behaves differently on the two.

The instructions are the one region you cannot read. li t0, 0x00400000 and then lw t1, 0(t0) ends the run with

Cannot read directly from text segment!0x00400000

The assembled program is there, at four bytes per instruction, and this simulator keeps it where a load cannot reach it. Everything else in the table is memory like any other.

There is no kernel region in that table. MIPS puts an exception handler at a fixed address in a segment of its own, and RISC-V does not: a handler here is ordinary code in .text whose address your program writes into a control register, which "Exceptions, CSRs and interrupts" gets to.

Little endian

RISC-V is little endian: the least significant byte of a word goes at the lowest address. The word 0x12345678 written at 0x10010000 is 78 at 0x10010000, 56 at 0x10010001, 34 at 0x10010002 and 12 at 0x10010003, which is backwards from the way you wrote the number down.

Build this one with the memory panel open, type 10010000 in its address box, then Run.

addressbytein
0x1001000078t1
0x1001000156t2
0x1001000234t3
0x1001000312t4

t5 comes out at 00005678 and t6 at 00001234, the two halves each read back the right way round, and s0 at 12345678, the whole word as you wrote it. A load of a word or a half puts the bytes back in order; only reading them one at a time shows you which way they are stored.

The M68K is big endian and stores the same word as 12 34 56 78. Nothing about a program that only reads and writes whole words changes between the two. What changes is a program that takes a word apart a byte at a time, which is what the table above does.

The size is in the instruction's name

RISC-V has no size suffix. Which instruction you use says how many bytes it touches, and the loads say what to do with the bits above them:

instructionbyteswhat it does
lb1loads a byte and sign extends it to 32 bits
lbu1loads a byte and fills the rest with zeroes
lh2loads a half and sign extends it
lhu2loads a half and fills the rest with zeroes
lw4loads a whole word
sb1stores the lowest byte of the register
sh2stores the lowest half
sw4stores the whole word

The stores have no signed and unsigned pair, because a store writes the bits it is given and there is nothing above them to fill in.

The eight bytes at 0x10010000 come out as DD CC BB AA 11 00 33 22, and t4 reads 22330011. The sb wrote one byte and left the one after it alone, the sh wrote two, and the lw picked up all four as a little endian word, which is why the 11 your program stored first ends up at the bottom of it.

0(t0), 4(t0) and 6(t0) are the only addressing mode there is: a register plus a constant offset in bytes. "Loads, stores and immediates" is the lecture on it.

Alignment

A word has to start at an address that is a multiple of 4, and a half at a multiple of 2. A byte can go anywhere. Break the rule and the run ends with a message naming the address:

Store address not aligned to word boundary 0x10010005

A load says Load address instead of Store address, and a half says halfword boundary.

The assembler keeps .word and .half aligned for you: put a .byte in front of a .word and it leaves the gap, so .word data is always safe. What it does not align is .space and the strings, because those are byte data and a byte needs no alignment. So a buffer you reserve after an odd length string starts at an odd address, and the first sw into it ends the run.

.align n is the fix: it moves the next thing up to a multiple of 2 to the n, so .align 2 gives you a multiple of 4.

t0 is 10010000 and t1 is 10010004, the three bytes of "Hi" rounded up to four, and t3 comes back at 42.

Delete the .align 2 line and press Build and Run. counts moves to 0x10010003, the sw stops the program, and the message under the editor gives you that address. Then put it back.

Two more addresses end a run. One outside everything in the table, such as li t0, 4 and lw t1, 0(t0), says address out of range 0x00000004. And a loop that never stops is simply cut off when the Playground's two million instructions run out, with no message at all, which is what an accidental infinite loop looks like here.

Your turn

The word at value is 0x12345678. Leave its lowest byte in t0, which is the 0x78, and its highest byte in t1, which is the 0x12, each as a number on its own with zeroes above it.

Show solution

The second one has a string of five bytes and a buffer after it, so the buffer starts at an odd address. Make it word aligned and store 0x11223344 in its first word.

Show solution