Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Reading a processor's own instructions (RISC-V)

After this lesson: I can read a short RISC-V program.

55 min lesson10 commandsCompilers and computer architecture lab: gcc, flex, bison and RISC-V in one VM

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

WordWhat it meansExample
Registera small, fast store inside the processora0
Immediatea number written inside the instruction itself93
Loadan instruction that copies a value from memory into a registerlw
Storean instruction that copies a register into memorysw
Branchan instruction that may jump to another place in the programbne
Labela name for one place in the programloop:

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

NumberNameUsual job
x0zeroAlways 0
x2spThe stack pointer: where the stack is in memory
x5 to x7t0 to t2Working values
x10, x11a0, a1Inputs to a function, and its answer in a0
x17a7Which system call ecall asks Linux for

The cells students fill in are left blank in this preview.

Go to the architecture folder

Command
cd ~/arch

Shown 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

Command
riscv64-linux-gnu-gcc -O2 -march=rv64gc -S loop.c

Shown 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

Command
grep -E 'lw|sw' loop.s

Shown 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

Command
grep -A 4 'L6:' loop.s

Shown 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.cRISC-VWhat 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,a4Adds the number to the total, in registers
the next caddi a5,a5,4Moves 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

Command
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.s

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Assemble it

Command
riscv64-linux-gnu-as sum.s -o sum.o

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Link it

Command
riscv64-linux-gnu-ld sum.o -o sum

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run it on the emulator

Command
qemu-riscv64 ./sum

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Read the total

Command
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

Command
riscv64-linux-gnu-objdump -d -M no-aliases sum

Shown 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.

About this lab and its source: Compilers and computer architecture lab: gcc, flex, bison and RISC-V in one VM

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.