# Assignment3: single-cycle RISC-V CPU contributed by < [tinhanho](https://github.com/tinhanho) > ## Goal Create a RISC-V CPU and acheieve four requirements. 1. Implementation in Chisel. 2. RV32I instruction set support. 3. Execution of programs compiled from the C programming language. 4. Successful completion of RISC-V Architectural Tests, also known as riscv-arch-test. ## CA2023-LAB3 Run single Scala file for unit test, ``` sbt "testOnly riscv.singlecycle.XXXTest" ``` Run all test, ``` sbt test ``` We will get bellow message if filling in all the blanks of the lab3, ``` [info] Run completed in 38 seconds, 822 milliseconds. [info] Total number of tests run: 9 [info] Suites: completed 7, aborted 0 [info] Tests: succeeded 9, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. ``` What we shall complete is 3 components- Instruction Fetch, Insturction Decode and Execute. Furthermore, finish CPU.scala to combine these 3 components together. ### Insturction Fetch Notice if instruction needs to jump or not. When it jumps, we update the program counter otherwise just plus 4. Note that we need to use 4.U in scala language, which indicates this "4" is unsigned. ### Instruction Decode I take fun3 as the condition at the beginning. For example, ``` opcode===InstructionsTypeL.lb ... ``` It can pass the InstructionDecodeTest somehow. However, it occurs error when executing `sbt test`. The correct way is taking the InstructionsType L and S, which means the Load and Store. I guess that using fun3 will encounter some problem because other InstructionType shall possess the same fun3. ### Execute Note that the code for ALU can be found in src/main/scala/riscv/core/ALU.scala, and ALUControl can be found in src/main/scala/riscv/core/ALUCtrol.scala as well. Note that aluop1 and aluop2 stand for alu operand 1 and operand 2 instead of opcode. Last stage from Execute stage is Decode stage. As a result, we will find the information of what the operand is in Decode stage. ### CPU Fill in the exe part. There are 7 inputs in Execute.scala, so let the 7 inputs pick up the right wire. ### Waveform While running tests, if you set the environment variable WRITE_VCD to 1, waveform files will be generated. ``` $ WRITE_VCD=1 sbt test ``` Go to the test_run_dir directory and use GTKwave to show the Waveform. #### Instruction Fetch ``` $ gtkwave InstructionFetch.vcd ``` ![image](https://hackmd.io/_uploads/SyCAiGkrT.png) We will see PC simply plus 4 if there is no jump. However, the jump flag is set at 4ps. Therefore, the PC is not PC+4 anymore. #### Instruction Decode ![image](https://hackmd.io/_uploads/H134j71H6.png) During the clock 2ps~4ps. The instruction is 0x00A02223(namely 0000 0000 1010 0000 0010 0010 0010 0011 in binary). Thus, we can find out that it is S type Instruction SW, so we enable the write. ## HW2 on MyCPU ### Setup PATH Do not forget to set up the path at the beginning. ``` Configure $PATH $ cd $HOME/riscv-none-elf-gcc $ echo "export PATH=`pwd`/bin:$PATH" > setenv Once step (1) and (2) are complete, you can simply update $PATH environment variable via: $ cd $HOME $ source riscv-none-elf-gcc/setenv ``` ### Clear All System Call and Cycle Count System call and cycle count will encounter problems and can not be executed well on myCPU. Therefore, we need to clear all the related code unfortunately. ### Append Code In the CPUTest.scala Take Fibonacci Test as reference, ``` c.io.mem_debug_read_address.poke(4.U) -> *((volatile int *) (4)) c.io.mem_debug_read_data.expect(55.U) -> fib(10) ``` Terms in the test, |Function/Declaration|Meaning| |-|-| |c.io.mem_debug_read_address.poke|Read data through address from memory.| |c.io.mem_debug_read_data.expect|Expected data value of the read address.| |volatile int*|Volatile means that it tells the compiler to read data directly from memory without any optimization.| <!--volatile它的作用是告诉编译器volatile变量是随时可能发生变化的,与volatile变量有关的运算,不要自作主张进行编译优化,以免出错,每次读取都从地址中获取。--> Append a class in the CPUTest.scala to test whether the CPU works well with our code. ### Test with C code [find_msb.c](https://github.com/tinhanho/ca2023-lab3/blob/main/csrc/find_msb.c) <!-- I want to test if C code works under myCPU. Therefore, we need to modify some files in order to implement the test. First, put the code into the csrc directory and modify the Makefile to successfully generate .asmbin file. Second, add a class "FindMsbTest" under CPUTest.scala in the src/test/scala/riscv/singlecycle. Note that do not forget to use `make update` to create .asmbin file. --> <!-- chatgpt 👍 --> Step 1: Move C Code to csrc directory Move your C code to the csrc directory. Step 2: Modify Makefile Open the Makefile in the root directory and add the necessary modifications. Ensure you have a rule to compile your C code into a binary file. Step 3: Add a Class in CPUTest.scala Open CPUTest.scala located in src/test/scala/riscv/singlecycle and add the FindMsbTest class. Step 4: Use `make update` to Create .asmbin File If it works, run `sbt "testOnly riscv.singlecycle.FindMsbTest"` and we can see following massage. ``` [info] FindMsbTest: [info] Single Cycle CPU [info] - should Find Most Significant Bit [info] Run completed in 20 seconds, 874 milliseconds. [info] Total number of tests run: 1 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. ``` ### Test with handwritten RISC-V assembly code Through similar way, put assembly code into csrc directory and generate .asmbin file. [find_msb_s.S](https://github.com/tinhanho/ca2023-lab3/blob/main/csrc/find_msb_s.S) ``` [info] FindMsbSTest: [info] Single Cycle CPU [info] - should Find Most Significant Bit [info] Run completed in 20 seconds, 226 milliseconds. [info] Total number of tests run: 1 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. ``` Performance of Handwritten Assembly code(226 milliseconds) is better than C code(874 milliseconds.). <!--Execute your program using Verilator and analyze the signals by examining the waveform diagrams. Describe the variations in key signals of the respective components when different instructions are executed.--> ### Analysis After the first run and every time you modify the Chisel code, you need to execute the following command in the project’s root directory to generate Verilog files: ``` $ make verilator ``` And simulate for 1000 cycles, ``` ./run-verilator.sh -instruction src/main/resources/find_msb_s.asmbin -time 2000 -vcd dump.vcd ``` Then, run `gtkwave dump.vcd` to check its waveform. ![image](https://hackmd.io/_uploads/ryK4EdVra.png) We can find 5 stages in CPU and we can check registers value as well. ![image](https://hackmd.io/_uploads/HylkL_4ra.png) Take the instruction during 138ps~140ps as example. The io_instruction "01E2F2B3" stands for `AND t0, t0, t5`. Therefore, set the flag io_ex_aluop1_source and io_ex_aluop2_source to 0. The io_reg_reg1_read_address is rs1, namely register t0. Just like reg1, io_regs_reg2_read_address is rs2, namely register t5. Next, check these two registers value. It shows that rs1 is 00000000 and rs2 is 55555555. After calculation of ALU, obviously that is should be 00000000. Enable the write and write 00000000 in rd, which should be t0. ## Some Trivial Notes ### Install jdk 11 ``` sudo apt-get install openjdk-11-jdk ``` ### Relationship Between Chisel and Verilog Chisel is generator language. It needs to be compiled into Verilog files and the be synthesized into actual circuits through EDA(Electronic Design Automation) softwate. ### Exercise [Learning Chisel by doing](https://github.com/freechipsproject/chisel-bootcamp) #### Arbiter Arbiter will send data if PE(Processing Elements) is ready to receive data and prioirtize PE0 if both are ready. *Use binary operators. **Ready** neads to wait whether data is **Valid**. Note the Input and Output. #### Scala Language Scala is strongly typed language. ``` cmd4. sc:7: type mismatch; found : Int(1) required: chisel3.UInt val twotwo = 1.U + 1 ^Compilation Failed Compilation Failed ``` |Symbol|Meaning| |-|-| |.U|Unsigned| |.W|Width, stands for bit length| |:=|A Chisel operator that indicates that the right-hand signal drives the left-hand signal. It is a directioned operator.| |+&|Width is maximum of two inputs plus 1.| |Mux<br>(C, x, y)|if C then x else y.| ### Nanorc ``` For Linux: Make nano colorful cd /usr/share/nano/java.nanorc Syntax adds "\.scala$" Color green adds val, object. (Note tha Scala is based on JVM.) ``` ## Full Code ### [Github](https://github.com/tinhanho/ca2023-lab3) Forked from [ca2023-lab3](https://github.com/sysprog21/ca2023-lab3). **remote: Support for password authentication was removed on August 13, 2021.** [solution](https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls) Your Personal Icon>Settings>Developer settings>Personal access tokens>Tokens(classic)>Generate new token>Copy the token> Use token as the password for Yourname @github.com **Store Password** git config --global credential.helper store