# Assignment3: SoftCPU contributed by < `handsomehsia` > ## Requirements :::spoiler 1. Following the instructions of [Lab3: Reindeer - RISCV RV32I[M] Soft CPU](https://hackmd.io/@sysprog/rJw2A5DqS), you shall modify the assembly programs used/done with [Assignment1](https://hackmd.io/@sysprog/2020-arch-homework1) or [Assignment2](https://hackmd.io/@sysprog/2020-arch-homework2) as new test case(s) for [Reindeer](https://github.com/PulseRain/Reindeer) Simulation with Verilator. * [ I-ADD-01](https://github.com/riscv/riscv-compliance/blob/master/riscv-test-suite/rv32i/src/I-ADD-01.S) is a good starting point for writing test cases. * You have to ensure signature matched with the requirements described in [RISC-V Compliance Tests](https://github.com/riscv/riscv-compliance/blob/master/doc/README.adoc). 2. Check the generated VCD file and use GTKwave to view the waveform. Then, explain how your program is executed along with [Reindeer](https://github.com/PulseRain/Reindeer) Simulation. 3. Write down your thoughts and progress in [HackMD notes](https://hackmd.io/s/features). * Summarize how [RISC-V Compliance Tests](https://github.com/riscv/riscv-compliance/blob/master/doc/README.adoc) works and why the signature should be matched. * Explain how [Reindeer](https://github.com/PulseRain/Reindeer) works with [Verilator](https://www.veripool.org/wiki/verilator). * What is 2 x 2 Pipeline? How can we benefit from such pipeline design? * What is "Hold and Load"? And, how the simulation does for bootstraping? * Can you show some signals/events inside [Reindeer](https://github.com/PulseRain/Reindeer) and describe? ::: ## Modified the assembly code of new test case ### Code from [Assignment1](https://hackmd.io/YVHNqq4vTU-_9E8lWq4FDw?viewhttps://hackmd.io/@sysprog/2020-arch-homework2) `MIT.s` ```cpp= .data length: .word 8 #legs size star: .string "*" spa: .string " " newline: .string "\n" .text #s2 = i #s3 = j #s4 = legs size #t0,t1,t2 = temp size main: add s2, x0, x0 # i =0 add s3, x0, x0 # j = 0 lw s4, length # size = 8 loop1: la a0, newline #print newline li a7, 4 ecall addi s2, s2, 1 #i = i+1 addi t0, s4, 1 #t0 = size+1 blt s2, t0, cond #for(i < size+1) j exit loop2: addi s3, s3, 1 #j = j+1 add t2, s4, s2 #t0=size+i bge s3, t2, loop1 #for(j < size+i ) sub t1, s4, s2 #t1=size-i addi t1, t1, 1 #t1=size-i+1 blt s3, t1, printSpace #if (j < size - i+1) j printStar cond: add s3, x0, x0 # init j j loop2 printStar: la a0, star li a7, 4 ecall j loop2 printSpace: la a0, spa li a7, 4 ecall j loop2 exit: li a7, 10 ecall ``` Rewrite the code: `MIT-01.S` ```cpp= RVTEST_IO_INIT RVTEST_IO_ASSERT_GPR_EQ(x31, x0, 0x00000000) RVTEST_IO_WRITE_STR(x31, "# Test Begin\n") RVTEST_IO_WRITE_STR(x31, "# Test part 1\n"); la x1, test_A1_1_data la x2, test_A1_2_data la x3, test_A1_res # Load testdata lw x8, 0(x1) lw x9, 0(x2) sw x8, 0(x3) sw x9, 4(x3) beqz x9, print beqz x8, assign # use Euclidean algorithm main: add s2, x0, x0 # i =0 add s3, x0, x0 # j = 0 lw s4, length # size = 8 loop1: la a0, newline #print newline li a7, 4 ecall addi s2, s2, 1 #i = i+1 addi t0, s4, 1 #t0 = size+1 blt s2, t0, cond #for(i < size+1) j exit loop2: addi s3, s3, 1 #j = j+1 add t2, s4, s2 #t0=size+i bge s3, t2, loop1 #for(j < size+i ) sub t1, s4, s2 #t1=size-i addi t1, t1, 1 #t1=size-i+1 blt s3, t1, printSpace #if (j < size - i+1) j printStar cond: add s3, x0, x0 # init j j loop2 printStar: la a0, star li a7, 4 ecall j loop2 printSpace: la a0, spa li a7, 4 ecall j loop2 # ---- ---- RVTEST_IO_CHECK() RVTEST_IO_ASSERT_GPR_EQ(x3, x8, 0x00000006) RVTEST_IO_WRITE_STR(x31, "# Test End\n"); RV_COMPLIANCE_HALT # ---- ---- RV_COMPLIANCE_CODE_END # Input data section. .data test_A1_1_data: .word 0x0000004E test_A1_2_data: .word 0x00000078 #Output data section. RV_COMPLIANCE_DATA_BEGIN test_A1_res: .fill 3, 4, -1 RV_COMPLIANCE_DATA_END ``` ## Run with Reindeer TBD... ## GTKwave TBD... ## How RISC-V Compliance Tests works First, by document, >The RISC-V Compliance Testing framework which is used to test a RISC-V device’s compliance to the different RISC-V specifications. The goal is to >Check whether the processor under development meets the open RISC-V standards or not. So, the `Compliance Test` running the RISC-V code on the processor(soft CPU) and that provides results in ==a defined memory area== (the signature). Checking if result_signature are matching with the reference_signature. ### Signature The test signature is defined as reference data written into memory during the execution of the test. It should record values and results of the operation of the Test. It is expected that an implementation, at the end of a test, dumps the signature in to a file such that only 4-bytes are written per line, starting with the most-significant byte on the left. ## How Reindeer works with Verilator ==A C++ testbench(`tb_PulseRain_RV2T.cpp`) will replace the OCD.== 1. The testbench will invoke the toolchain (objdump, readelf) to extract code/data from sections of the .elf file. 2. The testbench will mimic the OCD bus to load the code/data into CPU's memory. And Verilator will converts Verilog from Reindeer to a model in C++ or SystemC. ## 2x2 pipeline design Reindeer's pipeline is composed of 4 stages: 1. IF-Instruction Fetch 2. ID-Instruction Decode 3. IE-Instruction Execution 4. WB/MEM-Register Write Back and Memory Access And 2 x 2 will like this. ![](https://i.imgur.com/9D4PQsB.png) (cite from [Reindeer](https://github.com/PulseRain/Reindeer)) In this way, IF and MEM always happen on different clock cycles, thus to avoid the structural hazard caused by the single port memory. Also, because using single port memory, the Reindeer soft CPU is quite portable and flexible across all FPGA platforms. ## Hold and Load From [Reindeer/README](https://github.com/PulseRain/Reindeer) >To break the status quo, the PulseRain Reindeer takes a different approach called “hold and load”, which brings a hardware based OCD (on-chip debugger) into the fore, as illustrated below: ![](https://i.imgur.com/jIoxyUg.png) So, the `Hold and Load` is for Reindeer to initialize the soft CPU state. 1. When soft CPU be put into a hold state, the memory can be accessd, and new software image can be loaded in.(let OCD to reconfigure the mux and switch the UART TX to OCD side) 2. After loaded, the OCD send singal to active the soft CPU.(the OCD can switch the UART TX back to the CPU side for CPU's output) ### Bootstrapping The traditional approach 1. Making a boot loader in software 2. Store the boot loader in a ROM 3. After power on reset, the boot loader is supposed to be executed first, for which it will move the rest of the code/data into RAM. And the PC will be setted.