The crash report with one line
A delivery company's scanner crashes in the field. The report it sends back names the last instruction it ran: lw a0, 8(a0).
There is no source code on the scanner, only instructions. Someone who can read them can say what the program was doing when it stopped.
By the end of this lesson
Note
I can read a short RISC-V program.
Key words
| Word | What it means | Example |
|---|---|---|
| Register | a small, fast store inside the processor | a0 |
| Immediate | a number written inside the instruction itself | 93 |
| Load | an instruction that copies a value from memory into a register | lw |
| Store | an instruction that copies a register into memory | sw |
| Branch | an instruction that may jump to another place in the program | bne |
| Label | a name for one place in the program | loop: |
The cells students fill in are left blank in this preview.
Thirty-two registers with jobs
A 64-bit RISC-V processor has 32 registers of 64 bits each, called x0 to x31. Assembly gives most of them a name for their usual job.
x0 is called zero: it always reads as 0, and anything written to it is thrown away. The a registers carry a function's inputs and its answer. The t registers hold short-lived working values.
Worked example: registers you will meet
| Number | Name | Usual job |
|---|---|---|
| x0 | zero | Always 0 |
| x2 | sp | The stack pointer: where the stack is in memory |
| x5 to x7 | t0 to t2 | Working values |
| x10, x11 | a0, a1 | Inputs to a function, and its answer in a0 |
| x17 | a7 | Which system call ecall asks Linux for |
The cells students fill in are left blank in this preview.
Go to the architecture folder
cd ~/archShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Three shapes of instruction
Most instructions name a destination register first, then where the inputs come from.
add a0, a0, t0 adds two registers. addi t0, t0, 1 adds a register and an immediate, a number inside the instruction. li a0, 2 looks like a third kind, but it is a shorthand. For a small number the assembler writes addi a0, zero, 2.
Loads and stores reach memory
Registers are few, so data lives in memory. RISC-V does arithmetic on registers only. A load copies from memory into a register and a store copies the other way.
lw a0, 8(a1) loads a 4-byte word from the address in a1 plus 8. An int in C is 4 bytes, so item 2 of a list starts 8 bytes in. The lab's loop.c fills a table and adds it up, so gcc has to load and store.
Compile loop.c to RISC-V assembly
riscv64-linux-gnu-gcc -O2 -march=rv64gc -S loop.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Find the loads and the store
grep -E 'lw|sw' loop.sShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Show the row by row loop
grep -A 4 'L6:' loop.sShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Worked example: C beside RISC-V
| C in loop.c | RISC-V | What it does |
|---|---|---|
| grid[r][c] = 1; | sw a3,0(a5) | Writes a3, which holds 1, to the address in a5 |
| sum = sum + grid[r][c]; | lw a4,0(a5) | Reads 4 bytes from the address in a5 into a4 |
| sum = sum + grid[r][c]; | add a3,a3,a4 | Adds the number to the total, in registers |
| the next c | addi a5,a5,4 | Moves the address on by one int, 4 bytes |
The cells students fill in are left blank in this preview.
A common mistake
Warning
Many people think add can add a number straight from memory. On RISC-V it cannot. Only loads and stores touch memory, so a value is loaded, worked on in registers, and stored back. That is why the sum needs lw and then add.
A branch makes a loop
A branch compares two registers and jumps when the test passes. beq jumps when they are equal and bne when they are not.
A label such as loop: names the place to jump to. The next program adds 1 to 10. t0 counts from 1, a0 keeps the total, and bne t0, t1, loop jumps back until t0 reaches 11.
Write a loop in RISC-V
printf '.globl _start\n_start:\n li a0, 0\n li t0, 1\n li t1, 11\nloop:\n add a0, a0, t0\n addi t0, t0, 1\n bne t0, t1, loop\n li a7, 93\n ecall\n' > sum.sShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Assemble it
riscv64-linux-gnu-as sum.s -o sum.oShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Link it
riscv64-linux-gnu-ld sum.o -o sumShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run it on the emulator
qemu-riscv64 ./sumShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Read the total
echo $?Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Read it back without shorthands
riscv64-linux-gnu-objdump -d -M no-aliases sumShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Going further: multiply without mul
Write a RISC-V program that works out 6 times 7 using only li, add, addi and a branch: add 6 to a total seven times.
Run it in your interpreter first. Then write it to a file with printf, as you did for sum.s, build it and run it in the lab. The exit status should be 42.