# Assignment3: single-cycle RISC-V CPU > contributed by [weiweiwei68](https://github.com/weiweiwei68/RISC-V-Assembly-and-Instruction-Pipeline) ## Prerequisites In this assignment, Ubuntu Linux 22.04 is employed. Some necessary packages are installed by the following command. ``` # Ubuntu Linux 22.04 $ sudo apt install build-essential verilator gtkwave $ curl -s "https://get.sdkman.io" | bash $ source "$HOME/.sdkman/bin/sdkman-init.sh" $ sdk install java 11.0.21-tem $ sdk install sbt ``` ## Single-cycle RISC-V CPU In this section, i will follow the instructions outlined in [Lab3](https://hackmd.io/@sysprog/r1mlr3I7p) to complete this assignment. The entire work is based on the repository [ca2023-lab3](https://github.com/sysprog21/ca2023-lab3/tree/main). The Chisel code pertinent to this assignment will be finalized, accompanied by a detailed explanation. To access the repository [ca2023-lab3](https://github.com/sysprog21/ca2023-lab3/tree/main), execute the following commands. ```shell $ git clone https://github.com/sysprog21/ca2023-lab3 $ cd ca2023-lab3 ``` ### Instruction Fetch In this part, i will focus on the IF stage. The primary task involves fetching the instruction memory, based on the current address stored in the ```PC``` register. To determine the correct address, it is necessary to assess whether a jump condition occures. The following block is part of code in ```src/main/scala/riscv/core/InstructionFetch.scala``` ```scala class InstructionFetch extends Module { val io = IO(new Bundle { val jump_flag_id = Input(Bool()) val jump_address_id = Input(UInt(Parameters.AddrWidth)) val instruction_read_data = Input(UInt(Parameters.DataWidth)) val instruction_valid = Input(Bool()) val instruction_address = Output(UInt(Parameters.AddrWidth)) val instruction = Output(UInt(Parameters.InstructionWidth)) }) ``` This module has four inputs and two outputs. The inputs are utilized to find the correct address for the next instruction. ```jump_flag_id``` is a boolean value to check whether jump condition occures or not. If a jump condition is detected, the value stored in ```jump_address_id``` will be fetched into the ```PC``` register. In cases where no jump condition is identified, the ```PC``` register will be incremented by 4, which indicates the immediate address of the next instruction. :::danger :warning: **Refrain from copying and pasting your solution directly into the HackMD note**. Instead, provide a concise summary of the various test cases, outlining the aspects of the CPU they evaluate, the techniques employed for loading test program instructions, and the outcomes of these test cases. ::: In the above code block, we firstly check the validation of the instruction. If the instruction can be operated, the instruction is loaded, and a subsequent check is performed to determine whether a jump is required or not. ``` wei@wei:~/ca2023-lab3$ sbt "testOnly riscv.singlecycle.InstructionFetchTest" [info] welcome to sbt 1.9.7 (Eclipse Adoptium Java 11.0.21) [info] loading settings for project ca2023-lab3-build from plugins.sbt ... [info] loading project definition from /home/wei/ca2023-lab3/project [info] loading settings for project root from build.sbt ... [info] set current project to mycpu (in build file:/home/wei/ca2023-lab3/) [info] compiling 1 Scala source to /home/wei/ca2023-lab3/target/scala-2.13/classes ... [info] InstructionFetchTest: [info] InstructionFetch of Single Cycle CPU [info] - should fetch instruction [info] Run completed in 6 seconds, 859 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. ``` The above output demonstrates the success of IF test. Let's go to the next stage. ### Instruction Decode In this stage, it is crucial to ascertain the opcode of the instruction to select the appropriate technique for instruction processing. The key to completing this task is establishing a mapping relationship between control signals and instructions. Before assign the correct values to the control signals, it is indispensable to identify the opcode of the instruction. For example, for instructions that require reading data from memory, the control signal of ```memory_read_enable``` is set to ```True```. Conversely, if instructions do not have requirment to read data from memory, the control signal is set to ```False```. | Control Signals | Meanings | Possible values | | --------------- | -------- | --------------- | | ```memory_read_enable``` | Memory Read Enable | True / False | | ```memory_write_enable``` | Memory Write Enable | True / False | Selecting inputs is a valuable concept in hardware description, and the ```Mux```, a 2-input selector, proves to be a useful tool in assigning the correct contrl signal. The ```Mux``` allows for the conditional assigmnent of one of two inputs based on a selector signal, enabling efficient control signal management in hardware design. ```scala io.memory_read_enable := Mux ( opcode === InstructionTypes.L, true.B, false.B ) ``` The following output demonstrates the success of ID test. Let's go to the next stage. ``` wei@wei:~/ca2023-lab3$ sbt "testOnly riscv.singlecycle.InstructionDecodeTest" [info] welcome to sbt 1.9.7 (Eclipse Adoptium Java 11.0.21) [info] loading settings for project ca2023-lab3-build from plugins.sbt ... [info] loading project definition from /home/wei/ca2023-lab3/project [info] loading settings for project root from build.sbt ... [info] set current project to mycpu (in build file:/home/wei/ca2023-lab3/) [info] Passed: Total 0, Failed 0, Errors 0, Passed 0 [info] No tests to run for Test / testOnly [success] Total time: 2 s, completed Dec 2, 2023, 8:52:14 PM ``` ### Execution In this part, we need to assign values to the input ports of ```alu```. The following code is described in ```src/main/scala/riscv/core/ALU.scala```. ```scala class ALU extends Module { val io = IO(new Bundle { val func = Input(ALUFunctions()) val op1 = Input(UInt(Parameters.DataWidth)) val op2 = Input(UInt(Parameters.DataWidth)) val result = Output(UInt(Parameters.DataWidth)) }) // ... } ``` Before assigning values to the input ports, let us examine the ```ca2023-lab3/src/test/scala/riscv/singlecycle/ExecuteTest.scala``` file. Following code is part of this file. We can find that ```aluop1_source``` and ```aluop2_source``` are changed for a ```beq test```. This implies that the ALU computation might be bypassed when a value is set to 1. ``` // beq test c.io.instruction.poke(0x00208163L.U) // pc + 2 if x1 === x2 c.io.instruction_address.poke(2.U) c.io.immediate.poke(2.U) c.io.aluop1_source.poke(1.U) c.io.aluop2_source.poke(1.U) c.clock.step() ``` The ALU class has three input ports, necessitating the preparation of an equivalent number of values to be assigned to the corresponding ports. The ```Mux``` used again, providing a means to select the appropriate value for these ports based on certain conditions or control signals. ```scala alu.io.func := alu_ctrl.io.alu_funct alu.io.op1 := Mux ( io.aluop1_source === 0.U, io.reg1_data, 0.U(Parameters.DataWidth) ) alu.io.op2 := Mux ( io.aluop2_source === 0.U, io.reg2_data, io.immediate ) ``` The following output demonstrates the success of Execute test. Let's go to the next stage. ``` wei@wei:~/ca2023-lab3$ sbt "testOnly riscv.singlecycle.ExecuteTest" [info] welcome to sbt 1.9.7 (Eclipse Adoptium Java 11.0.21) [info] loading settings for project ca2023-lab3-build from plugins.sbt ... [info] loading project definition from /home/wei/ca2023-lab3/project [info] loading settings for project root from build.sbt ... [info] set current project to mycpu (in build file:/home/wei/ca2023-lab3/) [info] ExecuteTest: [info] Execution of Single Cycle CPU [info] - should execute correctly [info] Run completed in 7 seconds, 461 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. [success] Total time: 9 s, completed Dec 2, 2023, 10:09:24 PM ``` ### CPU In this section, our focus is on the input ports of the Execute module, and we need to identify the corresponding output ports of other modules. The Execute module has seven input ports , namely ```io.instruction```, ```io.instruction_address```, ```io.reg1_data```, ```io.reg2_data```, ```io.immediate```, ```io.aluop1_source```, and ```io.aluop2_source```. These input ports are associated with the following output ports from other modules: ```inst_fetch.io.instruction```, ```inst_fetch.io.instruction_address```, ```regs.io.read_data1```, ```regs.io.read_data2```, ```id.io.ex_immediate```, ```id.io.ex_aluop1_source```, and ```id.io.ex_aluop2_source```. To visualize the relationships between each module, refer to the following CPU architecture diagram. ![CPU](https://hackmd.io/_uploads/B1pD2p_Hp.png) The successful completion of the CPU test indicates the establishment of a single-cycle CPU architecture. This integration demonstrates the coordination and communication between different modules, ultimately forming a functional and cohesive CPU system. ``` wei@wei:~/ca2023-lab3$ sbt "testOnly riscv.singlecycle.CPUTest" [info] welcome to sbt 1.9.7 (Eclipse Adoptium Java 11.0.21) [info] loading settings for project ca2023-lab3-build from plugins.sbt ... [info] loading project definition from /home/wei/ca2023-lab3/project [info] loading settings for project root from build.sbt ... [info] set current project to mycpu (in build file:/home/wei/ca2023-lab3/) [info] compiling 1 Scala source to /home/wei/ca2023-lab3/target/scala-2.13/classes ... [info] Passed: Total 0, Failed 0, Errors 0, Passed 0 [info] No tests to run for Test / testOnly [success] Total time: 7 s, completed Dec 2, 2023, 11:12:13 PM ``` ## Wavefrom Wavefrom file can be generated by setting the environment variable ```WRITE_VCD``` to 1. ```shell $ WRITE_VCD=1 sbt test ``` The scala files generates ```.vcd``` files under the ```test_run_dir``` directory. To visualize these files, the GTKWave sofrware is employed, allowing for the explicit oberservation of waveforms. ### Instruction Fetch ![Screenshot from 2023-12-04 16-21-43](https://hackmd.io/_uploads/r1BPe7iHp.png) For the time duration spanning from 0 to 4 ps, ```io_jump_flag_id``` is set to 0, signifying the absence of a jump condition. During this interval, ```io_instruction_address``` has the incresment of 4. At 8 ps, however, ```io_jump_flag_id``` is set to 1, and ```io_jump_address_id``` is assigned the value ```0x00001000```. This triggers a jump condition, leading to the replacement of the ```PC``` register with the value ```0x00001000```. The same change is mirrored in ```io_instruction_address``` as well. ### Instruction Decode ![Screenshot from 2023-12-04 19-39-35](https://hackmd.io/_uploads/B10KqVorp.png) At 2ps, ```io_instruction``` is assigned the value ```0x00A02223```. Using an [online decoder](https://luplab.gitlab.io/rvcodecjs/), this hexadecimal value is translated into the RISC-V assembly instruction ```sw x10, 4(x0)```. Given that this instruction is an S-type instruction, ```io_memory_read_enable``` should be set to 1, and ```io_memory_read_enable``` should be set to 0. The value stored in ```io_regs_reg2_read_address``` is ```0x0A```, indicating that the content of ```x10``` register is selected to be saved in memory. The base address is given in ```io_regs_reg1_read_address```, corresponding to ```x0``` register, and the offset is provided by the instruction field ```rd```. By adding the base address and offset, the specific location of memory for the operation can be identified. ### Exection ![Screenshot from 2023-12-04 20-31-23](https://hackmd.io/_uploads/rkajLHsra.png) In this stage, the arithmetic operation is executed. The specific operands for the operation are determined from the instruction. Using an [online decoder](https://luplab.gitlab.io/rvcodecjs/), the ```io_instruction``` can be easily decoded into the instruction ```add x3, x2, x1```. As a result, the data stored in register ```x1``` and register ```x2``` is extracted for further arithmetic operation. ## Reference * [Assignment3: single-cycle RISC-V CPU](https://hackmd.io/@sysprog/2023-arch-homework3) * [Lab3: Construct a single-cycle RISC-V CPU with Chisel](https://hackmd.io/@sysprog/r1mlr3I7p) * [Chisel](https://www.chisel-lang.org/)