A program for a processor you do not have
Phones, laptops and servers use different processors, and each understands only its own instructions. RISC-V is an open design that anyone may build. This machine is not RISC-V, but an emulator lets it run a RISC-V program all the same.
By the end of this lesson
Note
I can follow a short RISC-V program from its assembly to its exit status.
Key words
- Processor: the part of a computer that runs instructions.
- Instruction: one small step a processor can do, such as add.
- Register: a tiny, fast store inside the processor.
- Emulator: a program that behaves like another processor.
- Exit status: the number a program hands back when it ends.
- Cache: a small, fast memory holding what was used recently.
Starter: what you already know
Note
Answer in your head. 1. What does a compiler turn source code into? 2. Which step joins machine code into one program? 3. What is assembly? Today you read assembly written by a person.
Before you start
Note
Press Start the lab on the bar at the top of this lesson. This lab is a whole Ubuntu machine of your own, so it takes a little longer to come up than a container. Each Run button types its command into the machine's terminal, one after another in the same session. If one says the machine is still starting, wait a few seconds and press it again. Where the lesson says Type this, the program waits for you to type to it, so press Open terminal on the bar and type it yourself.
A RISC-V program, line by line
add.s has five instructions. li puts a number in a register: 2 in a0 and 3 in a1. add adds them and keeps the answer in a0. li a7, 93 picks the Linux system call that ends a program, and ecall makes the call. Linux takes the number in a0 as the exit status.
cd ~/archShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
cat add.sShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Check
Tip
After add a0, a0, a1, what is in a0? Answer: 5, the sum of 2 and 3.
Build it, then run it on an emulator
The assembler turns the words into machine code, and the linker makes a program. file says it is for RISC-V. This processor cannot run it, so qemu-riscv64 runs it one RISC-V instruction at a time. It prints nothing. echo $? prints the exit status the program left behind: 5.
riscv64-linux-gnu-as add.s -o add.oShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
riscv64-linux-gnu-ld add.o -o addShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
file addShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
qemu-riscv64 ./addShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
echo $?Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A common mistake
Warning
Many people think echo $? prints the last thing a program printed. It prints the exit status, a number from 0 to 255 that the program hands back as it ends. Change 2 and 3 to 200 and 100, build again, and the answer is 44: 300 does not fit, so it wraps round.
Instructions are numbers
objdump -d shows the program as the processor sees it. Each line is an address, the instruction as a number in hexadecimal, then the instruction in words. Every number has eight hex digits, because each of these instructions is 32 bits long. ecall is 00000073.
riscv64-linux-gnu-objdump -d addShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A cache makes memory seem fast
loop.c adds up a table of 16 million numbers twice. Row by row, the next number sits beside the last one in memory, and the cache has already fetched it. Column by column, each number is 16 KB further on, so the processor waits for memory nearly every time. The sum is the same, 16777216; the time is not: column by column takes several times as long.
gcc -O2 -o loop loop.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
./loopShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Type this: watch the processor step
In the terminal, type gdb ./loop. Type break main, then run: the program stops at the start of main. Type info registers pc to see the program counter, the address of the next instruction. Type stepi to run one instruction, and look at pc again: it has moved on. Type quit, then y.
Exit check
Tip
Answer without looking back. 1. What is a register? 2. Which command runs a RISC-V program on this machine? 3. What does echo $? print? 4. Why is row by row faster? Answers: a tiny, fast store inside the processor; qemu-riscv64; the exit status of the last command; the next number is already in the cache.
For teachers
Tip
This lesson is a small example of a lesson worked in a lab: the steps and the Run buttons are the lesson, and the lab is each student's own copy. This lesson is worked in a VM lab: each student has an Ubuntu machine of their own, and a Run types into its terminal and shows what it printed. Key learning points: 1. A processor runs instructions that work on registers. 2. An assembler and a linker turn assembly into a program, and an emulator runs one for another processor. 3. Memory near the last read is fast because of the cache. Common mistake: reading the exit status as output. Equipment: the lab and a browser. Tip: before ./loop, ask the class to guess which way is faster and by how much. The lab has no check and marks nothing; set a hand-in of your own if you want one. Copy it into your own module and change it, or build one from scratch with the Lab menu in the lesson builder.