owned this note
owned this note
Published
Linked with GitHub
# Lab3: Reindeer - RISCV `RV32I[M]` Soft CPU
###### tags: `108-1_RISC-V`
## [Reindeer](https://github.com/PulseRain/Reindeer) - RISCV `RV32I[M]` Soft CPU
PulseRain [Reindeer](https://github.com/PulseRain/Reindeer) is a soft CPU of Von Neumann architecture. It supports RISC-V `RV32I[M]` instruction set, and features a 2 x 2 pipeline. It strives to make a balance between speed and area, and offers a flexible choice for soft CPU across all FPGA platforms.
## Converting assembly code(.S) into ELF file
### 1. Installation
* Installing [Veirlator](https://hackmd.io/@xl86305955/Reideer_RV32I)
```
$ verilator --version
Verilator 4.008 2018-12-01 rev UNKNOWN_REV
```
* Installing the [GTKWave](http://gtkwave.sourceforge.net/) for checking the waveform `.vcd` file generate by [Reideer](https://github.com/PulseRain/Reindeer)
```
$ sudo apt-get install gtkwave
```
* Get the [Reideer](https://github.com/PulseRain/Reindeer) via git
```
git clone https://github.com/PulseRain/Reindeer.git
```
* Under the `Reindeer/sim/verilator/` place the C++ program simulate the soft CPU with [Verilator](https://www.veripool.org/wiki/verilator)
* Installing `riscvOVPsim` simulator by
`git clone https://github.com/riscv-ovpsim/imperas-riscv-tests.git`
(because there is no riscvOVPsim.exe file in riscv-compliance so we have to additional downloading `riscvOVPsim` simulator )
### 2. Setting environment
* Defining some path into **Makefile** and adding name of the file into **Makefrag** file
##### file location: `/home/disk6T/jayhua267/Com_Arch/imperas-riscv-tests/riscv-test-suite/rv32i/`
```
ROOTDIR ?= /home/disk6T/jayhua267/Com_Arch/imperas-riscv-tests/
TARGETDIR := /home/disk6T/jayhua267/Com_Arch/imperas-riscv-tests/riscv-target
RISCV_TARGET := riscvOVPsim
RISCV_DEVICE := rv32i
```
* Creating a .S file in `./src` folder
* Adding name of the file into **Makefile.include**
##### file location: `/home/disk6T/jayhua267/Com_Arch/rv32emu/riscv-compliance/riscv-target/riscvOVPsim/device/rv32i/`
```
RISCV_PREFIX ?= riscv-none-embed-
RISCV_GCC ?= $(RISCV_PREFIX)gcc
RISCV_OBJDUMP ?= $(RISCV_PREFIX)objdump
RISCV_GCC_OPTS ?= -static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles $(RVTEST_DEFINES)
```
* Modifying RISCV_PREFIX
##### file location: `/home/disk6T/jayhua267/Com_Arch/imperas-riscv-tests/`
from ~~`export RISCV_PREFIX ?= riscv64-unknown-elf- `~~
change to `export RISCV_PREFIX ?= riscv-none-embed-`
* Compiling I-ADD-01.S (given by author) and converting it to .elf
file location: `/home/disk6T/jayhua267/Com_Arch/imperas-riscv-tests/`
```
make RISCV_TARGET=riscvOVPsim RISCV_DEVICE=rv32i RISCV_TEST='I-ADD-01'
```
After compiling the file, it will generate some files as below:

`.signature.output` is a file which defines memory area where the result of a test suite is stored
`.elf.objdump` storing assembly code
### 3. Creating your own file
#### [Find largest element(HW1)](https://hackmd.io/Z-MxXzmbQyeAeWJxzZhB8A?view)
:::info
It uses a for-loop that repeatedly steps through the array,then compares each value of the array with a 'flag' parameter used to store the largest value.
After for-loop, we output the value stored in 'flag' which is the largest value of the array.
:::
Array of 10: `9, 28, 63, 88, 52, 9, 75, 6, 26, 7`
:::spoiler **Assembly code**
```cpp=
#include "riscv_test_macros.h"
#include "compliance_test.h"
#include "compliance_io.h"
RV_COMPLIANCE_RV32M
RV_COMPLIANCE_CODE_BEGIN
RVTEST_IO_INIT
RVTEST_IO_ASSERT_GPR_EQ(x31, x0, 0x00000000)
RVTEST_IO_WRITE_STR(x31, "Test begin\n")
# ----------------------------------------------------------------------------------
initial1:
la x5, test_1_res
la x10, array # s1 = base address of array
li x11, 10 # size = 10
main1:
sw x10, 0(x5)
sw x11, 4(x5)
add x4, x0, x0 # i = 0
add x7, x0, x0 # define Flag
jal ra, forloop # Jump-and-link to the 'forloop' label
lw x10, 0(x5)
lw x11, 4(x5)
sw x12, 8(x5)
j end
RVTEST_IO_CHECK()
RVTEST_IO_ASSERT_GPR_EQ(x5, x10, 0x09)
RVTEST_IO_ASSERT_GPR_EQ(x5, x11, 0x0A)
RVTEST_IO_WRITE_STR(x31, "#Argument test - complete\n")
RVTEST_IO_ASSERT_GPR_EQ(x5, x12, 0x58)
RVTEST_IO_WRITE_STR(x31, "#Combination test - complete")
# ----------------------------------------------------------------------------------
forloop:
addi x4, x4, 1 #i = i+1
lw x6,0(x10) #t1 = next value of the array
addi x10, x10, 4 #update new position of the next address
bge x7, x6, then #if Flag >= A[i] jump to 'then'
add x7, x0, x6 #else Flag = A[i]
beq x4, x11, printResult #if i = 10 => stop
j forloop #back to for loop
then:
beq x4, x11, printResult #if i = 10 => stop
j forloop #else continue run for loop
printResult:
mv x12, x7
ret
end:
RV_COMPLIANCE_HALT
RV_COMPLIANCE_CODE_END
#Input data section.
.data
array: .word 9, 28, 63, 88, 52, 9, 75, 6, 26,7
#Output data section.
RV_COMPLIANCE_DATA_BEGIN
.align 4
test_1_res:
.fill 3, 4, -1
RV_COMPLIANCE_DATA_END
```
:::
* Creating a **.S** file in `/home/disk6T/jayhua267/Com_Arch/imperas-riscv-tests/riscv-test-suite/rv32i/src/`
* Copy **.elf.objdump** and **.elf** from `/home/disk6T/jayhua267/Com_Arch/imperas-riscv-tests/work/rv32i/` to `/home/disk6T/jayhua267/Com_Arch/Reindeer/sim/compliance/`
* Copy **.signature.output** and change its name to **.reference_output** in`riscv-test-suite/rv32i/references/`
### 5.Generating VCD file and observing the wave
##### file location:
* Copy the **.elf.objdump** and **.elf** file from `/imperas-riscv-tests/work/rv32i/` to `/Reindeer/sim/compliance`
* Copy the **.reference_output** file to `/Reindeer/sim/compliance/reference/`
* Generating **.vcd** file using the code below
`make test FindLargest`
* After that using GTKwave to view the wave of **.vcd** file
`gtkwave FindLargest.vcd`
* Wave observation( you can select the signal that you want to observe and display it

### 6. My thoughts and progress
* What is 2 x 2 Pipeline? How can we benefit from such pipeline design?
:::success
2 x 2 Pipeline is composed of 4 stages:
1. Instruction Fetch(IF)
2. Instruction Decode(ID)
3. Instruction Execution(IE)
4. Register Write Back and Memory Access(MEM)

* In the 2 x 2 layout, each stage is active every other clock cycle.
* For the even cycle, only IF and IE stages are active.
* For the odd cycle, only ID and MEM stages are active.
* the Instruction Fetch and Memory Access always happen on different clock cycles to **avoid the structural hazard** caused by the single port memory.
:::
* What is "Hold and Load"? And, how the simulation does for bootstraping?
:::success

* After reset, the soft CPU will be put into a hold state, and it will have access to the UART TX port by default.
* A valid debug frame sending from the host PC can let OCD to reconfigure the mux and switch the UART TX to OCD side, for which the memory can be accessed, and the control frames can be exchanged.
* A new software image can be loaded into the memory during the CPU hold state, which gives rise to the name "hold-and-load".
* Can you show some signals/events inside [Reindeer](https://github.com/PulseRain/Reindeer) and describe?
:::
* How Reindeer works with Verilator
:::success
* We can obesve sim/verilator/tb_PulseRain_RV2T.cpp file and see
```cpp
uut = new UUT; // Create instance
VerilatedVcdC* tfp = new VerilatedVcdC;
```
* We simulate hold and wait and initialize the register.
* When the OCD loads program into memory, the start signal will be arised.Then, the circuit will start to execute the program.
```cpp
load_elf_sections(&t, uut);
t.run();
std::cout << "\n=============> start running ..." << "\n";
uut->start = 1;
t.run();
uut->start = 0;
t.run();
```
* Because Verilator is a software tool converts Verilog (a hardware description language) to a cycle-accurate behavioral model in C++ or SystemC.
* The **uut** which is object is also soft cpu.
* Therefore, the Reindeer can work with Verilator
:::