In the third year of the Bachelor program, you have done the course unit Computer architecture (3435). In this course you made an implementation for a RISC-V processor. This course (of which you are currently reading the course material) starts with one of the earlier implementations you made: the RV32I implementation. The processor runs a program and the ins-and-outs of this will be the handover point between both courses.
The implementation of the ISA is capable of executing instructions. These instructions are written in assembly. As you have implemented the processor itself, you will probably be familiar with the concept.
An example is shown here that calculates the first n numbers of the Fibonacci row. Before setting of to calculate the famous sequence, all the registers in the register file are initialised on 0x0.
Next, some initialisations are done. These instructions ensure the temporary registers t0, t1, t4, t5, and t6 are set.
Subquently a loop is started (as long as the content of t4 is not zero) which adds t0 and t1 into t2; then adds t1 and t2 into t0; and thirdly adds t2 and t0 into t1. Before the loop is re-evaluated, the value in t4 is shifted right 1 position.
Running this program in a simulator will give you a waveform, similar to the one that is shown below.
Maybe the assembly code that is shown already adds a level of abstraction to what you used earlier. Let’s take an example add t0, t1, t2. For a human (like yourself, I’d take it 😉) this is somewhat understandable: Add t1 to t2 and put the sum in t0. Unfortunately, this is not what can be asked from the RISC-V implementation. Some encoding is required:
If all these bits are concatenated, the 32-bit binary string is formed 0b00000000011100110000001010110011. Writing it hexadecimally, this becomes: 0x007302b3. Many tools exist that help us automate this, however (e.g. instruction encoder/decoders).
.global start
.section .init, "ax"
start:
addi x1, zero, 0
addi x2, zero, 0
addi x3, zero, 0
addi x4, zero, 0
addi x5, zero, 0
addi x6, zero, 0
addi x7, zero, 0
addi x8, zero, 0
addi x9, zero, 0
addi x10, zero, 0
addi x11, zero, 0
addi x12, zero, 0
addi x13, zero, 0
addi x14, zero, 0
addi x15, zero, 0
addi x16, zero, 0
addi x17, zero, 0
addi x18, zero, 0
addi x19, zero, 0
addi x20, zero, 0
addi x21, zero, 0
addi x22, zero, 0
addi x23, zero, 0
addi x24, zero, 0
addi x25, zero, 0
addi x26, zero, 0
addi x27, zero, 0
addi x28, zero, 0
addi x29, zero, 0
addi x30, zero, 0
addi x31, zero, 0
j fibonacci
fibonacci:
lui t6, 0x80000
addi t5, zero, 44
addi t4, zero, 255
addi t0, zero, 1
addi t1, zero, 1
_loop_start_1:
beq t4, zero, done
add t2, t0, t1
sw t2, 0(t6)
sw t5, 0(t6)
add t0, t1, t2
sw t0, 0(t6)
sw t5, 0(t6)
add t1, t2, t0
sw t1, 0(t6)
sw t5, 0(t6)
srli t4, t4, 1
j _loop_start_1
done:
j done
00000093
00000113
00000193
00000213
00000293
00000313
00000393
00000413
00000493
00000513
00000593
00000613
00000693
00000713
00000793
00000813
00000893
00000913
00000993
00000a13
00000a93
00000b13
00000b93
00000c13
00000c93
00000d13
00000d93
00000e13
00000e93
00000f13
00000f93
0040006f
80000fb7
02c00f13
0ff00e93
00100293
00100313
020e8863
006283b3
007fa023
01efa023
007302b3
005fa023
01efa023
00538333
006fa023
01efa023
001ede93
fd5ff06f
0000006f
00001941
73697200
01007663
0000000f
33767205
70326932
00000031
00000000
00000000
Adding layers of abstraction helps us (the human programmer) to write code more easily: add t0, t1, t2 vs 0x007302b3.
Note the endless loop at the end. What is it for?
Another level of abstraction can make programmer-live even more manageable. In this course you will be programming in a higher-level programming language: C.
#include "print.h"
void main(void) {
unsigned int x, y, z;
unsigned int i;
x = 1;
y = 1;
print_hex(x, 2);
print_chr('-');
print_hex(y, 2);
print_chr('-');
for(i=0;i<2;i++) {
z = x + y;
print_hex(z,2);
print_chr('-');
x = y + z;
print_hex(x,2);
print_chr('-');
y = z + x;
print_hex(y,2);
print_chr('-');
}
while(1);
}
The example shown here, is written in C and is much more human-friendly. Four variables are declared and two of them are initialised. After printing these values, a loop is executed in which the Fibonacci sequence is calculated and printed. All printed values are 2-digit hexadecimal and they are seperated with a dash (’-’).
The output of this program can be seen here:
Before your implementation of the RISC-V core is capable of running this very simple code, there is still some work left. The remainder of this chapter will cover ‘all you need to know’.