├── verilog └── .gitkeep ├── c-tests-results └── .gitkeep ├── riscv-tests-results └── .gitkeep ├── rust-tests-results └── .gitkeep ├── rust ├── .gitignore ├── Cargo.lock ├── .cargo │ └── config ├── README.md ├── src │ └── fib │ │ └── main.rs └── Cargo.toml ├── project ├── build.properties └── plugins.sbt ├── .gitmodules ├── c ├── link.ld ├── crt0.s ├── fib.c ├── ctest.c ├── data_hazard_ex.c ├── data_hazard_wb.s ├── br_hazard.c ├── vsetvli_e32_m1.c ├── vsetvli_e32_m2.c ├── vsetvli_e64_m1.c └── README.md ├── patch └── start_addr.patch ├── src ├── test │ └── scala │ │ ├── CTests.scala │ │ ├── RustTests.scala │ │ └── RiscvTests.scala └── main │ └── scala │ ├── Top.scala │ ├── Main.scala │ ├── Memory.scala │ ├── Instructions.scala │ ├── Consts.scala │ └── Core.scala ├── .github └── workflows │ └── ci.yaml ├── LICENSE.txt ├── Dockerfile ├── Makefile ├── README.md └── .gitignore /verilog/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /c-tests-results/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /riscv-tests-results/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rust-tests-results/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rust/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.4.9 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "riscv-tests"] 2 | path = riscv-tests 3 | url = https://github.com/riscv/riscv-tests.git 4 | -------------------------------------------------------------------------------- /c/link.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | 3 | SECTIONS 4 | { 5 | . = 0x00000000; 6 | .text : { 7 | *(.init) 8 | *(.text) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /c/crt0.s: -------------------------------------------------------------------------------- 1 | .section .init 2 | 3 | # TODO: Setup an exception handler 4 | 5 | .global _start 6 | _start: 7 | call main 8 | 1: 9 | j 1b 10 | -------------------------------------------------------------------------------- /c/fib.c: -------------------------------------------------------------------------------- 1 | int fib(int i) { 2 | if (i <= 1) { 3 | return 1; 4 | } 5 | return fib(i - 1) + fib(i - 2); 6 | } 7 | 8 | int main() { 9 | return fib(6); 10 | } 11 | -------------------------------------------------------------------------------- /rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "rust-tests" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /c/ctest.c: -------------------------------------------------------------------------------- 1 | int main() { 2 | const unsigned int x = 1; 3 | const unsigned int y = 2; 4 | unsigned int z = x + y; 5 | if (z == 1) { 6 | z = z + 1; 7 | } else { 8 | z = z + 2; 9 | } 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /c/data_hazard_ex.c: -------------------------------------------------------------------------------- 1 | // data hazard between ID and EX 2 | 3 | int main() { 4 | asm volatile("addi a0, x0, 1"); 5 | asm volatile("add a1, a0, a0 "); // addi is at EX and add is at ID 6 | 7 | asm volatile("nop"); 8 | asm volatile("nop"); 9 | asm volatile("nop"); 10 | 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /rust/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "riscv32i-unknown-none-elf" 3 | 4 | [target.riscv32i-unknown-none-elf] 5 | rustflags = [ 6 | "-C", "debuginfo=0", 7 | "-C", "opt-level=2", 8 | "-C", "linker=riscv64-unknown-elf-ld", 9 | "-C", "link-args=-b elf32-littleriscv -T ../c/link.ld ../c/crt0.o" 10 | ] 11 | -------------------------------------------------------------------------------- /rust/README.md: -------------------------------------------------------------------------------- 1 | This is a test project to check a binary compiled from Rust sources can be run on our CPU. 2 | 3 | Each directory under `src/` should be an implementation of each binary. Binary names are defined in `[bin]` section of `Cargo.toml`. 4 | 5 | How to run the tests is described in [the project README file](../README.md). 6 | -------------------------------------------------------------------------------- /patch/start_addr.patch: -------------------------------------------------------------------------------- 1 | diff --git a/p/link.ld b/p/link.ld 2 | index b3e315e..d32c627 100644 3 | --- a/env/p/link.ld 4 | +++ b/env/p/link.ld 5 | @@ -3,7 +3,7 @@ ENTRY(_start) 6 | 7 | SECTIONS 8 | { 9 | - . = 0x80000000; 10 | + . = 0x00000000; 11 | .text.init : { *(.text.init) } 12 | . = ALIGN(0x1000); 13 | .tohost : { *(.tohost) } 14 | -------------------------------------------------------------------------------- /rust/src/fib/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::panic::PanicInfo; 5 | 6 | #[panic_handler] 7 | fn panic(_: &PanicInfo) -> ! { 8 | loop {} 9 | } 10 | 11 | fn fib(i: i32) -> i32 { 12 | if i <= 1 { 13 | 1 14 | } else { 15 | fib(i - 1) + fib(i - 2) 16 | } 17 | } 18 | 19 | #[no_mangle] 20 | pub extern "C" fn main() -> i32 { 21 | fib(6) 22 | } 23 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-tests" 3 | version = "0.1.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [[bin]] 8 | name = "fib" 9 | path = "src/fib/main.rs" 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [dependencies] 20 | -------------------------------------------------------------------------------- /c/data_hazard_wb.s: -------------------------------------------------------------------------------- 1 | # GCC did not emit expected code when compiling sample code at p.203 2 | # Instead I hand-assemble the code here 3 | .file "data_hazard_wb.c" 4 | .option nopic 5 | .option checkconstraints 6 | .attribute arch, "rv32i2p0" 7 | .attribute unaligned_access, 0 8 | .attribute stack_align, 16 9 | .text 10 | .align 2 11 | .globl main 12 | .type main, @function 13 | main: 14 | li a0, 1 15 | nop 16 | nop 17 | add a1, a0, a0 18 | nop 19 | nop 20 | nop 21 | unimp 22 | li a5,0 23 | mv a0,a5 24 | lw s0,12(sp) 25 | addi sp,sp,16 26 | jr ra 27 | .size main, .-main 28 | .ident "GCC: (GNU) 9.2.0" 29 | -------------------------------------------------------------------------------- /src/test/scala/CTests.scala: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import java.io.File 4 | import chisel3._ 5 | import org.scalatest._ 6 | import chiseltest._ 7 | import Consts._ 8 | 9 | class CTests extends FlatSpec with ChiselScalatestTester { 10 | behavior of "mycpu" 11 | 12 | for (f <- new File("./c").listFiles.filter(f => f.isFile && f.getName.endsWith(".hex"))) { 13 | val p = f.getPath 14 | it should p in { 15 | test(new Top(p)) { c => 16 | // c is an instance of Top 17 | while (!c.io.exit.peek().litToBoolean) { 18 | c.clock.step(1) 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/scala/RustTests.scala: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import java.io.File 4 | import chisel3._ 5 | import org.scalatest._ 6 | import chiseltest._ 7 | import Consts._ 8 | 9 | class RustTests extends FlatSpec with ChiselScalatestTester { 10 | behavior of "mycpu" 11 | 12 | for (f <- new File("./rust").listFiles.filter(f => f.isFile && f.getName.endsWith(".hex"))) { 13 | val p = f.getPath 14 | it should p in { 15 | test(new Top(p)) { c => 16 | // c is an instance of Top 17 | while (!c.io.exit.peek().litToBoolean) { 18 | c.clock.step(1) 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/Top.scala: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import Consts._ 6 | 7 | class Top(hexMemoryPath: String) extends Module { 8 | val io = IO(new Bundle { 9 | val exit = Output(Bool()) 10 | val gp = Output(UInt(WORD_LEN.W)) 11 | val pc = Output(UInt(WORD_LEN.W)) 12 | }) 13 | 14 | val core = Module(new Core()) 15 | val memory = Module(new Memory(hexMemoryPath)) 16 | 17 | // Connect ports between core and memory 18 | core.io.imem <> memory.io.imem 19 | core.io.dmem <> memory.io.dmem 20 | 21 | // Connect signals inside core 22 | io.exit := core.io.exit 23 | io.gp := core.io.gp 24 | io.pc := core.io.pc 25 | } 26 | -------------------------------------------------------------------------------- /c/br_hazard.c: -------------------------------------------------------------------------------- 1 | int main() { 2 | asm volatile("addi a0, x0, 1"); 3 | asm volatile("addi a1, x0, 2"); 4 | asm volatile("jal x0, jump"); 5 | 6 | // These instructions should not be executed 7 | asm volatile("addi a0, x0, 2"); 8 | asm volatile("addi a1, x0, 3"); 9 | 10 | // Jump to here 11 | asm volatile("jump:"); 12 | 13 | asm volatile("nop"); // These NOPs runs the instruction until the end of pipeline 14 | asm volatile("nop"); 15 | asm volatile("nop"); 16 | asm volatile("nop"); 17 | asm volatile("add a2, a0, a1"); 18 | asm volatile("nop"); 19 | asm volatile("nop"); 20 | asm volatile("nop"); 21 | asm volatile("nop"); 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | submodules: recursive 11 | - name: Run riscv-tests and c-tests on Docker 12 | run: 13 | docker run --rm -v "$(pwd):/app" --workdir /app -t rhysd/riscv-cpu-chisel:latest make ci 14 | lint: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Check workflow files 19 | run: | 20 | bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 21 | ./actionlint -color 22 | shell: bash 23 | -------------------------------------------------------------------------------- /src/test/scala/RiscvTests.scala: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import java.io.File 4 | import chisel3._ 5 | import org.scalatest._ 6 | import chiseltest._ 7 | 8 | class RiscvTests extends FlatSpec with ChiselScalatestTester { 9 | behavior of "mycpu" 10 | 11 | for (f <- new File("./riscv-tests-results").listFiles.filter(f => f.isFile && f.getName.endsWith(".hex"))) { 12 | val p = f.getPath 13 | it should p in { 14 | test(new Top(p)) { c => 15 | // riscv-tests test case finishes when program counter is at 0x44 16 | while (c.io.pc.peek().litValue != 0x44) { 17 | c.clock.step(1) 18 | } 19 | // riscv-tests sets 1 to gp when the test passed otherwise gp represents which test case failed 20 | c.io.gp.expect(1.U) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /c/vsetvli_e32_m1.c: -------------------------------------------------------------------------------- 1 | // Test SEW=e32, LMUL=m1 2 | 3 | int main() { 4 | unsigned int size = 5; // Number of elements to compute 5 | unsigned int vl; 6 | 7 | // Expected behavior: 8 | // 1st loop: AVL=5, VL=4, size=1 9 | // 2nd loop: AVL=1, VL=1, size=0 10 | 11 | // Loop until all elements are computed. 12 | while (size > 0) { 13 | // `size` might be larger than VL. Store how many elements will be calculated in `vl`. 14 | asm volatile("vsetvli %0, %1, e32, m1" 15 | : "=r"(vl) 16 | : "r"(size)); 17 | 18 | // Avoid data hazard 19 | asm volatile("nop"); 20 | asm volatile("nop"); 21 | asm volatile("nop"); 22 | 23 | size -= vl; 24 | 25 | // Some vector computation goes here 26 | } 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /c/vsetvli_e32_m2.c: -------------------------------------------------------------------------------- 1 | // Test SEW=e32, LMUL=m2 2 | 3 | int main() { 4 | unsigned int size = 10; // Number of elements to compute 5 | unsigned int vl; 6 | 7 | // Expected behavior: 8 | // 1st loop: AVL=10, VL=8, size=2 9 | // 2nd loop: AVL=2, VL=2, size=0 10 | 11 | // Loop until all elements are computed. 12 | while (size > 0) { 13 | // Avoid data hazard 14 | asm volatile("nop"); 15 | asm volatile("nop"); 16 | asm volatile("nop"); 17 | 18 | // `size` might be larger than VL. Store how many elements will be calculated in `vl`. 19 | asm volatile("vsetvli %0, %1, e32, m2" 20 | : "=r"(vl) 21 | : "r"(size)); 22 | 23 | size -= vl; 24 | 25 | // Some vector computation goes here 26 | } 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import java.io.File 4 | import chisel3._ 5 | import chisel3.stage.ChiselStage 6 | 7 | object MyCpuDriver extends App { 8 | def getMemoryHexFilePath(args: Array[String]): (String, Array[String]) = { 9 | for (i <- 1 until args.length) { 10 | if (args(i-1) == "--memoryHexFile") { 11 | val a = args(i) 12 | if (!a.isEmpty) { 13 | if (!new File(a).isFile) { 14 | throw new IllegalArgumentException(a ++ " is not a file at --memoryHexFile argument") 15 | } 16 | return (a, args.take(i-1) ++ args.drop(i + 1)) 17 | } 18 | } 19 | } 20 | throw new IllegalArgumentException("--memoryHexFile {path} must specify non-empty file path") 21 | } 22 | val (p, a) = getMemoryHexFilePath(args) 23 | (new ChiselStage).emitVerilog(new Top(p), a) 24 | } 25 | -------------------------------------------------------------------------------- /c/vsetvli_e64_m1.c: -------------------------------------------------------------------------------- 1 | // Test SEW=e64, LMUL=m1 2 | 3 | int main() { 4 | unsigned int size = 5; // Number of elements to compute 5 | unsigned int vl; 6 | 7 | // Expected behavior: 8 | // 1st loop: AVL=5, VL=2, size=3 9 | // 2nd loop: AVL=3, VL=2, size=1 10 | // 3rd loop: AVL=1, VL=1, size=0 11 | 12 | // Loop until all elements are computed. 13 | while (size > 0) { 14 | // Avoid data hazard 15 | asm volatile("nop"); 16 | asm volatile("nop"); 17 | asm volatile("nop"); 18 | 19 | // `size` might be larger than VL. Store how many elements will be calculated in `vl`. 20 | asm volatile("vsetvli %0, %1, e64, m1" 21 | : "=r"(vl) 22 | : "r"(size)); 23 | 24 | size -= vl; 25 | 26 | // Some vector computation goes here 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | the MIT License 2 | 3 | Copyright (c) 2021 rhysd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 20 | THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # https://github.com/chadyuu/riscv-chisel-book/blob/master/dockerfile 2 | FROM ubuntu:18.04 3 | 4 | ENV RISCV=/opt/riscv 5 | ENV PATH=$RISCV/bin:/root/.cargo/bin:$PATH 6 | WORKDIR $RISCV 7 | 8 | # Install apt packages 9 | RUN apt-get update && \ 10 | apt-get install -y --no-install-recommends autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev pkg-config git libusb-1.0-0-dev device-tree-compiler default-jdk gnupg && \ 11 | echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" | tee -a /etc/apt/sources.list.d/sbt.list && \ 12 | echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | tee /etc/apt/sources.list.d/sbt_old.list && \ 13 | curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | apt-key add && \ 14 | apt-get update && \ 15 | apt-get install -y --no-install-recommends sbt && \ 16 | apt-get clean && rm -rf /var/lib/apt/lists/* 17 | 18 | # Install RISC-V GNU toolchain 19 | # Note: Docker crashes with `make -j`. 20 | RUN git clone -b rvv-0.9.x --single-branch https://github.com/riscv/riscv-gnu-toolchain.git && \ 21 | cd riscv-gnu-toolchain && git checkout 5842fde8ee5bb3371643b60ed34906eff7a5fa31 && \ 22 | git submodule update --init --recursive && \ 23 | mkdir build && cd build && ../configure --prefix=${RISCV} --enable-multilib && make -j4 && \ 24 | cd ${RISCV} && rm -rf ${RISCV}/riscv-gnu-toolchain 25 | 26 | # Install Rust toolchain 27 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /init-rustup.sh && \ 28 | sh /init-rustup.sh -y --default-toolchain stable --profile minimal --target riscv32i-unknown-none-elf && \ 29 | ~/.cargo/bin/rustup show && \ 30 | rm /init-rustup.sh 31 | -------------------------------------------------------------------------------- /c/README.md: -------------------------------------------------------------------------------- 1 | ## Register names 2 | 3 | | Register | ABI | Description | 4 | |----------|---------------|------------------------------------| 5 | | 0 | `zero` | Constant `0` | 6 | | 1 | `ra` | Return address | 7 | | 2 | `sp` | Stack pointer | 8 | | 3 | `gp` | Global pointer | 9 | | 4 | `tp` | Thread pointer | 10 | | 5 to 7 | `t0` to `t2` | Temporary register | 11 | | 8 | `s0`/`fp` | Save register, frame pointer | 12 | | 9 | `s1` | Save register | 13 | | 10 to 17 | `a0` to `a7` | Function arguments or return value | 14 | | 18 to 27 | `s2` to `s11` | Save register | 15 | | 28 to 31 | `t3` to `t6` | Temporary register | 16 | 17 | [Assembly Manual](https://github.com/riscv/riscv-asm-manual/blob/master/riscv-asm.md) 18 | 19 | ## SEW ("V" Spec 6.1) 20 | 21 | | Assembly | Bits | SEW | `vsew` | 22 | |----------|---------|---------|--------| 23 | | `e8` | 000 (0) | 8bit | 0 | 24 | | `e16` | 001 (1) | 16bit | 1 | 25 | | `e32` | 010 (2) | 32bit | 2 | 26 | | `e64` | 011 (3) | 64bit | 3 | 27 | | `e128` | 100 (4) | 128bit | 4 | 28 | | `e256` | 101 (5) | 256bit | 5 | 29 | | `e512` | 110 (6) | 512bit | 6 | 30 | | `e1024` | 111 (7) | 1024bit | 7 | 31 | 32 | ## LMUL ("V" Spec 6.1) 33 | 34 | | Assembly | `vlmul` | LMUL | 35 | |----------|----------|------| 36 | | `m1` | 000 (0) | 1 | 37 | | `m2` | 001 (1) | 2 | 38 | | `m4` | 010 (2) | 4 | 39 | | `m8` | 011 (3) | 8 | 40 | | `mf8` | 101 (-3) | 1/8 | 41 | | `mf4` | 110 (-2) | 1/4 | 42 | | `mf2` | 111 (-1) | 1/2 | 43 | -------------------------------------------------------------------------------- /src/main/scala/Memory.scala: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import chisel3.util.experimental.loadMemoryFromFile 6 | import Consts._ 7 | 8 | // Group input/output between CPU core and memory (see p.77) 9 | // 'I' stands for 'Instruction'. 10 | class ImemPortIo extends Bundle { 11 | // input port for memory address 12 | val addr = Input(UInt(WORD_LEN.W)) 13 | // output port for fetching instruction 14 | val inst = Output(UInt(WORD_LEN.W)) 15 | } 16 | 17 | class DmemPortIo extends Bundle { 18 | val addr = Input(UInt(WORD_LEN.W)) 19 | val rdata = Output(UInt(WORD_LEN.W)) // r stands for read 20 | val wen = Input(Bool()) // Indicate when data should be written 21 | val wdata = Input(UInt(WORD_LEN.W)) // w stands for write 22 | } 23 | 24 | // Group input/output between CPU core and memory (p.92) 25 | // 'D' stands for 'data'. 26 | class Memory(hexMemoryPath: String) extends Module { 27 | val io = IO(new Bundle { 28 | val imem = new ImemPortIo() 29 | val dmem = new DmemPortIo() 30 | }) 31 | 32 | // As underlying implementation, memory consists of 8bit * 0x4000 registers (16KB). The register 33 | // size is 8bits because PC is counted up by 4bytes. 34 | val mem = Mem(0x4000, UInt(8.W)) 35 | 36 | loadMemoryFromFile(mem, hexMemoryPath) 37 | 38 | io.imem.inst := Cat( 39 | mem(io.imem.addr + 3.U(WORD_LEN.W)), 40 | mem(io.imem.addr + 2.U(WORD_LEN.W)), 41 | mem(io.imem.addr + 1.U(WORD_LEN.W)), 42 | mem(io.imem.addr) 43 | ) 44 | 45 | io.dmem.rdata := Cat( 46 | mem(io.dmem.addr + 3.U(WORD_LEN.W)), 47 | mem(io.dmem.addr + 2.U(WORD_LEN.W)), 48 | mem(io.dmem.addr + 1.U(WORD_LEN.W)), 49 | mem(io.dmem.addr) 50 | ) 51 | 52 | when(io.dmem.wen) { 53 | mem(io.dmem.addr + 3.U(WORD_LEN.W)) := io.dmem.wdata(31, 24) 54 | mem(io.dmem.addr + 2.U(WORD_LEN.W)) := io.dmem.wdata(23, 16) 55 | mem(io.dmem.addr + 1.U(WORD_LEN.W)) := io.dmem.wdata(15, 8) 56 | mem(io.dmem.addr) := io.dmem.wdata(7, 0) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/scala/Instructions.scala: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | 6 | object Instructions { 7 | // Load/Store 8 | val LW = BitPat("b?????????????????010?????0000011") 9 | val SW = BitPat("b?????????????????010?????0100011") 10 | 11 | // Add 12 | val ADD = BitPat("b0000000??????????000?????0110011") 13 | val ADDI = BitPat("b?????????????????000?????0010011") 14 | 15 | // Sub 16 | val SUB = BitPat("b0100000??????????000?????0110011") 17 | 18 | // Logical operations 19 | val AND = BitPat("b0000000??????????111?????0110011") 20 | val OR = BitPat("b0000000??????????110?????0110011") 21 | val XOR = BitPat("b0000000??????????100?????0110011") 22 | val ANDI = BitPat("b?????????????????111?????0010011") 23 | val ORI = BitPat("b?????????????????110?????0010011") 24 | val XORI = BitPat("b?????????????????100?????0010011") 25 | 26 | // Shift 27 | val SLL = BitPat("b0000000??????????001?????0110011") 28 | val SRL = BitPat("b0000000??????????101?????0110011") 29 | val SRA = BitPat("b0100000??????????101?????0110011") 30 | val SLLI = BitPat("b0000000??????????001?????0010011") 31 | val SRLI = BitPat("b0000000??????????101?????0010011") 32 | val SRAI = BitPat("b0100000??????????101?????0010011") 33 | 34 | // Compare 35 | val SLT = BitPat("b0000000??????????010?????0110011") 36 | val SLTU = BitPat("b0000000??????????011?????0110011") 37 | val SLTI = BitPat("b?????????????????010?????0010011") 38 | val SLTIU = BitPat("b?????????????????011?????0010011") 39 | 40 | // Branch 41 | val BEQ = BitPat("b?????????????????000?????1100011") 42 | val BNE = BitPat("b?????????????????001?????1100011") 43 | val BLT = BitPat("b?????????????????100?????1100011") 44 | val BGE = BitPat("b?????????????????101?????1100011") 45 | val BLTU = BitPat("b?????????????????110?????1100011") 46 | val BGEU = BitPat("b?????????????????111?????1100011") 47 | 48 | // Jump 49 | val JAL = BitPat("b?????????????????????????1101111") 50 | val JALR = BitPat("b?????????????????000?????1100111") 51 | 52 | // Load immediate 53 | val LUI = BitPat("b?????????????????????????0110111") 54 | val AUIPC = BitPat("b?????????????????????????0010111") 55 | 56 | // CSR 57 | val CSRRW = BitPat("b?????????????????001?????1110011") 58 | val CSRRWI = BitPat("b?????????????????101?????1110011") 59 | val CSRRS = BitPat("b?????????????????010?????1110011") 60 | val CSRRSI = BitPat("b?????????????????110?????1110011") 61 | val CSRRC = BitPat("b?????????????????011?????1110011") 62 | val CSRRCI = BitPat("b?????????????????111?????1110011") 63 | 64 | // Exception 65 | val ECALL = BitPat("b00000000000000000000000001110011") 66 | 67 | // Vector 68 | val VSETVLI = BitPat("b?????????????????111?????1010111") 69 | val VLE = BitPat("b000000100000?????????????0000111") 70 | val VSE = BitPat("b000000100000?????????????0100111") 71 | val VADDVV = BitPat("b0000001??????????000?????1010111") 72 | 73 | // Custom 74 | val PCNT = BitPat("b000000000000?????110?????0001011") 75 | } 76 | -------------------------------------------------------------------------------- /src/main/scala/Consts.scala: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | 6 | object Consts { 7 | val WORD_LEN = 32 8 | val START_ADDR = 0.U(WORD_LEN.W) 9 | val BUBBLE = 0x00000013.U(WORD_LEN.W) // [ADDI x0,x0,0] = NOP = BUBBLE (2.4) 10 | val UNIMP = "x_c0001073".U(WORD_LEN.W) // [CSRRW x0, cycle, x0] p.159 11 | val ADDR_LEN = 5 // rs1,rs2,wb 12 | val CSR_ADDR_LEN = 12 13 | val VLEN = 128 14 | val LMUL_LEN = 2 15 | val SEW_LEN = 11 16 | val VL_ADDR = 0xC20 17 | val VTYPE_ADDR = 0xC21 18 | 19 | val EXE_FUN_LEN = 5 20 | val ALU_NONE = 0.U(EXE_FUN_LEN.W) 21 | val ALU_ADD = 1.U(EXE_FUN_LEN.W) 22 | val ALU_SUB = 2.U(EXE_FUN_LEN.W) 23 | val ALU_AND = 3.U(EXE_FUN_LEN.W) 24 | val ALU_OR = 4.U(EXE_FUN_LEN.W) 25 | val ALU_XOR = 5.U(EXE_FUN_LEN.W) 26 | val ALU_SLL = 6.U(EXE_FUN_LEN.W) 27 | val ALU_SRL = 7.U(EXE_FUN_LEN.W) 28 | val ALU_SRA = 8.U(EXE_FUN_LEN.W) 29 | val ALU_SLT = 9.U(EXE_FUN_LEN.W) 30 | val ALU_SLTU = 10.U(EXE_FUN_LEN.W) 31 | val BR_BEQ = 11.U(EXE_FUN_LEN.W) 32 | val BR_BNE = 12.U(EXE_FUN_LEN.W) 33 | val BR_BLT = 13.U(EXE_FUN_LEN.W) 34 | val BR_BGE = 14.U(EXE_FUN_LEN.W) 35 | val BR_BLTU = 15.U(EXE_FUN_LEN.W) 36 | val BR_BGEU = 16.U(EXE_FUN_LEN.W) 37 | val ALU_JALR = 17.U(EXE_FUN_LEN.W) 38 | val ALU_RS1 = 18.U(EXE_FUN_LEN.W) // Copy RS1 39 | val ALU_VADDVV = 19.U(EXE_FUN_LEN.W) 40 | val VSET = 20.U(EXE_FUN_LEN.W) 41 | val ALU_PCNT = 21.U(EXE_FUN_LEN.W) 42 | 43 | val OP1_LEN = 2 44 | val OP1_RS1 = 0.U(OP1_LEN.W) 45 | val OP1_PC = 1.U(OP1_LEN.W) 46 | val OP1_NONE = 2.U(OP1_LEN.W) 47 | val OP1_IMZ = 3.U(OP1_LEN.W) 48 | 49 | val OP2_LEN = 3 50 | val OP2_NONE = 0.U(OP2_LEN.W) 51 | val OP2_RS2 = 1.U(OP2_LEN.W) 52 | val OP2_IMI = 2.U(OP2_LEN.W) 53 | val OP2_IMS = 3.U(OP2_LEN.W) 54 | val OP2_IMJ = 4.U(OP2_LEN.W) 55 | val OP2_IMU = 5.U(OP2_LEN.W) 56 | 57 | val MEN_LEN = 2 58 | val MEN_NONE = 0.U(MEN_LEN.W) 59 | val MEN_SCALAR = 1.U(MEN_LEN.W) // Scalar 60 | val MEN_VECTOR = 2.U(MEN_LEN.W) // Vector 61 | 62 | val REN_LEN = 2 63 | val REN_NONE = 0.U(REN_LEN.W) 64 | val REN_SCALAR = 1.U(REN_LEN.W) // Scalar 65 | val REN_VECTOR = 2.U(REN_LEN.W) // Vector 66 | 67 | val WB_SEL_LEN = 3 68 | val WB_NONE = 0.U(WB_SEL_LEN.W) 69 | val WB_ALU = 0.U(WB_SEL_LEN.W) 70 | val WB_MEM = 1.U(WB_SEL_LEN.W) 71 | val WB_PC = 2.U(WB_SEL_LEN.W) 72 | val WB_CSR = 3.U(WB_SEL_LEN.W) 73 | val WB_MEM_V = 4.U(WB_SEL_LEN.W) 74 | val WB_ALU_V = 5.U(WB_SEL_LEN.W) 75 | val WB_VL = 6.U(WB_SEL_LEN.W) 76 | 77 | val MW_LEN = 3 78 | val MW_X = 0.U(MW_LEN.W) 79 | val MW_W = 1.U(MW_LEN.W) 80 | val MW_H = 2.U(MW_LEN.W) 81 | val MW_B = 3.U(MW_LEN.W) 82 | val MW_HU = 4.U(MW_LEN.W) 83 | val MW_BU = 5.U(MW_LEN.W) 84 | 85 | val CSR_LEN = 3 86 | val CSR_NONE = 0.U(CSR_LEN.W) 87 | val CSR_W = 1.U(CSR_LEN.W) // Write 88 | val CSR_S = 2.U(CSR_LEN.W) // Set bits 89 | val CSR_C = 3.U(CSR_LEN.W) // Clear bits 90 | val CSR_E = 4.U(CSR_LEN.W) // Exception (ECALL) 91 | val CSR_V = 5.U(CSR_LEN.W) 92 | } 93 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Use bash for -o option 2 | SHELL=/bin/bash 3 | .SHELLFLAGS = -o pipefail -c 4 | 5 | UI_INSTS := sw lw add addi sub and andi or ori xor xori sll srl sra slli srli srai slt sltu slti sltiu beq bne blt bge bltu bgeu jal jalr lui auipc 6 | MI_INSTS := csr scall 7 | 8 | RISCV_ELF := $(patsubst %,target/share/riscv-tests/isa/rv32ui-p-%,$(UI_INSTS)) $(patsubst %,target/share/riscv-tests/isa/rv32mi-p-%,$(MI_INSTS)) 9 | RISCV_HEX := $(patsubst %,riscv-tests-results/%.hex,$(notdir $(RISCV_ELF))) 10 | RISCV_OUT := $(patsubst %.hex,%.out,$(RISCV_HEX)) 11 | C_HEX := $(patsubst %.c,%.hex,$(wildcard c/*.c)) $(patsubst %.s,%.hex,$(filter-out c/crt0.s,$(wildcard c/*.s))) 12 | C_OUT := $(patsubst %.hex,c-tests-results/%.out,$(notdir $(C_HEX))) 13 | RUST_TESTS := fib 14 | RUST_HEX := $(patsubst %,rust/%.hex,$(RUST_TESTS)) 15 | RUST_OUT := $(patsubst %,rust-tests-results/%.out,$(RUST_TESTS)) 16 | 17 | SRC := $(wildcard src/main/scala/*.scala) 18 | VERILOG_SRC := verilog/Top.v verilog/Top.Memory.mem.v 19 | 20 | target/share/riscv-tests: 21 | cd ./riscv-tests && \ 22 | patch -p1 < ../patch/start_addr.patch && \ 23 | autoconf && \ 24 | ./configure "--prefix=$(shell pwd)/target" && \ 25 | make -j && \ 26 | make install 27 | 28 | %.hex: %.bin 29 | od -An -tx1 -w1 -v $< > $@ 30 | 31 | riscv-tests-results/%.bin: target/share/riscv-tests 32 | riscv64-unknown-elf-objcopy -O binary target/share/riscv-tests/isa/$(basename $(notdir $@)) $@ 33 | 34 | riscv-tests-results/%.out: riscv-tests-results/%.hex $(SRC) src/test/scala/RiscvTests.scala 35 | sbt "testOnly cpu.RiscvTests -- -z $<" | tee "$@" 36 | 37 | riscv-tests: $(RISCV_OUT) 38 | 39 | c/%.o: c/%.s 40 | riscv64-unknown-elf-gcc -O2 -march=rv32iv -mabi=ilp32 -c -o $@ $< 41 | 42 | c/%.o: c/%.c 43 | riscv64-unknown-elf-gcc -O2 -march=rv32iv -mabi=ilp32 -c -o $@ $< 44 | 45 | c/%.s: c/%.c 46 | riscv64-unknown-elf-gcc -O2 -march=rv32iv -mabi=ilp32 -S -o $@ $< 47 | 48 | c/%.elf: c/%.o c/crt0.o c/link.ld 49 | riscv64-unknown-elf-ld -b elf32-littleriscv $< -T ./c/link.ld -o $@ ./c/crt0.o 50 | 51 | %.bin: %.elf 52 | riscv64-unknown-elf-objcopy -O binary $< $@ 53 | 54 | %.dump: %.elf 55 | riscv64-unknown-elf-objdump -b elf32-littleriscv -D $< > $@ 56 | 57 | c-tests-results/%.out: c/%.hex $(SRC) src/test/scala/CTests.scala 58 | sbt "testOnly cpu.CTests -- -z $<" | tee "$@" 59 | 60 | c-tests: $(C_OUT) 61 | 62 | rust/fib.elf: 63 | cd ./rust && cargo build 64 | cp ./rust/target/riscv32i-unknown-none-elf/debug/fib ./rust/fib.elf 65 | 66 | rust-tests-results/%.out: rust/%.hex $(SRC) src/test/scala/RustTests.scala 67 | sbt "testOnly cpu.RustTests -- -z $<" | tee "$@" 68 | 69 | rust-tests: $(RUST_OUT) 70 | 71 | # Use `cat` not to connect output to tty. This prevents sbt shows prompt line on each stdout output line on CI. 72 | ci: $(RISCV_HEX) $(C_HEX) $(RUST_HEX) 73 | sbt test | cat 74 | 75 | $(VERILOG_SRC): $(SRC) 76 | sbt "run --target-dir ./verilog --memoryHexFile $(MEMORY_HEX_FILE_PATH)" 77 | 78 | verilog: $(VERILOG_SRC) 79 | 80 | clean: 81 | rm -f ./riscv-tests-results/*.out ./riscv-tests-results/*.hex ./riscv-tests-results/*.bin 82 | rm -f ./c/*.elf ./c/*.hex ./c/*.dump 83 | rm -f ./c-tests-results/*.out 84 | rm -f ./rust/*.elf ./rust/*.dump ./rust/*.hex 85 | rm -rf ./rust/target 86 | rm -f ./rust-tests-results/*.out 87 | rm -f ./verilog/*.v ./verilog/*.json ./verilog/*.f ./verilog/*.fir 88 | 89 | .PHONY: clean riscv-tests c-tests rust-tests verilog ci 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RISC-V 32bit CPU written in Chisel 2 | ================================== 3 | [![CI][ci-badge]][ci] 4 | 5 | [RISC-V][riscv] 32bit CPU written in [Chisel][chisel]. This project is for my learning purpose to understand how to design/implement 6 | hardware with Chisel and what RISC-V architecture is. Working in progress. 7 | 8 | ## References 9 | 10 | - RISC-VとChiselで学ぶ はじめてのCPU自作: https://gihyo.jp/book/2021/978-4-297-12305-5 11 | - 'Introduction to making your own CPU with RISC-V and Chisel' Japanese book 12 | - Chisel API Document: https://www.chisel-lang.org/api/latest/chisel3/index.html 13 | - RISC-V Spec: https://riscv.org/technical/specifications/ 14 | - https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf 15 | - Assembly Manual: https://github.com/riscv/riscv-asm-manual/blob/master/riscv-asm.md 16 | - Reference simulator: https://github.com/riscv/riscv-isa-sim 17 | - Proxy kernel: https://github.com/riscv/riscv-pk 18 | - "V" vector extension: https://github.com/riscv/riscv-v-spec 19 | - https://github.com/riscv/riscv-v-spec/releases/download/v1.0-rc1/riscv-v-spec-1.0-rc1.pdf 20 | - GNU toolchain: https://github.com/riscv/riscv-gnu-toolchain 21 | - **TODO:** This repository currently refers to `rvv-0.9.x` branch but it should refer to `rvv-intrinsic` branch for RVV v1.0-rc. 22 | 23 | This repository was imported from [chisel-template@f5f33c6](https://github.com/freechipsproject/chisel-template/tree/f5f33c69f04a64531cbdb31581e09b95583fba91). 24 | 25 | ## Install 26 | 27 | Clone this repository: 28 | 29 | ```sh 30 | git clone --recursive https://github.com/rhysd/riscv32-cpu-chisel.git 31 | ``` 32 | 33 | Build Docker image for RISC-V GNU toolchain and Scala toolchain: 34 | 35 | ```sh 36 | docker build . -t riscv/mycpu 37 | ``` 38 | 39 | Start an interactive shell with mounting this repository: 40 | 41 | ```sh 42 | docker run -it -v $(pwd):/app riscv/mycpu 43 | ``` 44 | 45 | ## Generate Verilog sources 46 | 47 | Verilog sources can be generated from Chisel sources via `sbt run`: 48 | 49 | ```sh 50 | make ./c/fib.hex # Make hex dump of memory image of program to run 51 | make verilog MEMORY_HEX_FILE_PATH=./c/fib.hex # Generate Verilog sources 52 | cat ./verilog/Top.v 53 | ``` 54 | 55 | ## Test 56 | 57 | ### riscv-tests 58 | 59 | To run all tests in [riscv-tests](https://github.com/riscv/riscv-tests): 60 | 61 | ```sh 62 | make riscv-tests 63 | ``` 64 | 65 | Outputs of tests are stored in `riscv-tests-results` directory. 66 | 67 | To run a specific test case in riscv-tests (when running `rv32ui-p-addi` test case): 68 | 69 | ```sh 70 | make ./riscv-tests-results/rv32ui-p-addi.out 71 | ``` 72 | 73 | ### C tests 74 | 75 | To run all tests with C sources in [`c/` directory](./c/): 76 | 77 | ```sh 78 | make c-tests 79 | ``` 80 | 81 | Outputs of tests are stored in `c-tests-results` directory. 82 | 83 | To run a specific test case in c-tests (when running [`fib.c`](./c/fib.c) test case): 84 | 85 | ```sh 86 | make ./c-tests-results/fib.out 87 | ``` 88 | 89 | ### Rust tests 90 | 91 | To run all tests with Rust sources in [`rust/` directory](./rust/): 92 | 93 | ```sh 94 | make rust-tests 95 | ``` 96 | 97 | Outputs of tests are stored in `rust-tests-results` directory. 98 | 99 | To run a specific test case in rust-tests (when running [`fib`](./rust/src/fib) test case): 100 | 101 | ```sh 102 | make ./rust-tests-results/fib.out 103 | ``` 104 | 105 | ### Run tests with the uploaded Docker image 106 | 107 | Run `docker run` with [the uploaded Docker image][docker]. 108 | 109 | ```sh 110 | docker run --rm -v $(pwd):/app --workdir /app -t rhysd/riscv-cpu-chisel:latest make riscv-tests 111 | ``` 112 | 113 | ## License 114 | 115 | Distributed under [the MIT license](./LICENSE.txt). 116 | 117 | [ci-badge]: https://github.com/rhysd/riscv32-cpu-chisel/actions/workflows/ci.yaml/badge.svg 118 | [ci]: https://github.com/rhysd/riscv32-cpu-chisel/actions/workflows/ci.yaml 119 | [riscv]: https://riscv.org/ 120 | [chisel]: https://www.chisel-lang.org/ 121 | [docker]: https://hub.docker.com/r/rhysd/riscv-cpu-chisel 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Project Specific stuff 2 | test_run_dir/* 3 | ### XilinxISE template 4 | # intermediate build files 5 | *.bgn 6 | *.bit 7 | *.bld 8 | *.cmd_log 9 | *.drc 10 | *.ll 11 | *.lso 12 | *.msd 13 | *.msk 14 | *.ncd 15 | *.ngc 16 | *.ngd 17 | *.ngr 18 | *.pad 19 | *.par 20 | *.pcf 21 | *.prj 22 | *.ptwx 23 | *.rbb 24 | *.rbd 25 | *.stx 26 | *.syr 27 | *.twr 28 | *.twx 29 | *.unroutes 30 | *.ut 31 | *.xpi 32 | *.xst 33 | *_bitgen.xwbt 34 | *_envsettings.html 35 | *_map.map 36 | *_map.mrp 37 | *_map.ngm 38 | *_map.xrpt 39 | *_ngdbuild.xrpt 40 | *_pad.csv 41 | *_pad.txt 42 | *_par.xrpt 43 | *_summary.html 44 | *_summary.xml 45 | *_usage.xml 46 | *_xst.xrpt 47 | 48 | # project-wide generated files 49 | *.gise 50 | par_usage_statistics.html 51 | usage_statistics_webtalk.html 52 | webtalk.log 53 | webtalk_pn.xml 54 | 55 | # generated folders 56 | iseconfig/ 57 | xlnx_auto_0_xdb/ 58 | xst/ 59 | _ngo/ 60 | _xmsgs/ 61 | ### Eclipse template 62 | *.pydevproject 63 | .metadata 64 | .gradle 65 | bin/ 66 | tmp/ 67 | *.tmp 68 | *.bak 69 | *.swp 70 | *~.nib 71 | local.properties 72 | .settings/ 73 | .loadpath 74 | 75 | # Eclipse Core 76 | .project 77 | 78 | # External tool builders 79 | .externalToolBuilders/ 80 | 81 | # Locally stored "Eclipse launch configurations" 82 | *.launch 83 | 84 | # CDT-specific 85 | .cproject 86 | 87 | # JDT-specific (Eclipse Java Development Tools) 88 | .classpath 89 | 90 | # Java annotation processor (APT) 91 | .factorypath 92 | 93 | # PDT-specific 94 | .buildpath 95 | 96 | # sbteclipse plugin 97 | .target 98 | 99 | # TeXlipse plugin 100 | .texlipse 101 | ### C template 102 | # Object files 103 | *.o 104 | *.ko 105 | *.obj 106 | *.elf 107 | 108 | # Precompiled Headers 109 | *.gch 110 | *.pch 111 | 112 | # Libraries 113 | *.lib 114 | *.a 115 | *.la 116 | *.lo 117 | 118 | # Shared objects (inc. Windows DLLs) 119 | *.dll 120 | *.so 121 | *.so.* 122 | *.dylib 123 | 124 | # Executables 125 | *.exe 126 | *.out 127 | *.app 128 | *.i*86 129 | *.x86_64 130 | *.hex 131 | 132 | # Debug files 133 | *.dSYM/ 134 | ### SBT template 135 | # Simple Build Tool 136 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control 137 | 138 | target/ 139 | lib_managed/ 140 | src_managed/ 141 | project/boot/ 142 | .history 143 | .cache 144 | ### Emacs template 145 | # -*- mode: gitignore; -*- 146 | *~ 147 | \#*\# 148 | /.emacs.desktop 149 | /.emacs.desktop.lock 150 | *.elc 151 | auto-save-list 152 | tramp 153 | .\#* 154 | 155 | # Org-mode 156 | .org-id-locations 157 | *_archive 158 | 159 | # flymake-mode 160 | *_flymake.* 161 | 162 | # eshell files 163 | /eshell/history 164 | /eshell/lastdir 165 | 166 | # elpa packages 167 | /elpa/ 168 | 169 | # reftex files 170 | *.rel 171 | 172 | # AUCTeX auto folder 173 | /auto/ 174 | 175 | # cask packages 176 | .cask/ 177 | ### Vim template 178 | [._]*.s[a-w][a-z] 179 | [._]s[a-w][a-z] 180 | *.un~ 181 | Session.vim 182 | .netrwhist 183 | *~ 184 | ### JetBrains template 185 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 186 | 187 | *.iml 188 | 189 | ## Directory-based project format: 190 | .idea/ 191 | # if you remove the above rule, at least ignore the following: 192 | 193 | # User-specific stuff: 194 | # .idea/workspace.xml 195 | # .idea/tasks.xml 196 | # .idea/dictionaries 197 | 198 | # Sensitive or high-churn files: 199 | # .idea/dataSources.ids 200 | # .idea/dataSources.xml 201 | # .idea/sqlDataSources.xml 202 | # .idea/dynamic.xml 203 | # .idea/uiDesigner.xml 204 | 205 | # Gradle: 206 | # .idea/gradle.xml 207 | # .idea/libraries 208 | 209 | # Mongo Explorer plugin: 210 | # .idea/mongoSettings.xml 211 | 212 | ## File-based project format: 213 | *.ipr 214 | *.iws 215 | 216 | ## Plugin-specific files: 217 | 218 | # IntelliJ 219 | /out/ 220 | 221 | # mpeltonen/sbt-idea plugin 222 | .idea_modules/ 223 | 224 | # JIRA plugin 225 | atlassian-ide-plugin.xml 226 | 227 | # Crashlytics plugin (for Android Studio and IntelliJ) 228 | com_crashlytics_export_strings.xml 229 | crashlytics.properties 230 | crashlytics-build.properties 231 | ### C++ template 232 | # Compiled Object files 233 | *.slo 234 | *.lo 235 | *.o 236 | *.obj 237 | 238 | # Precompiled Headers 239 | *.gch 240 | *.pch 241 | 242 | # Compiled Dynamic libraries 243 | *.so 244 | *.dylib 245 | *.dll 246 | 247 | # Fortran module files 248 | *.mod 249 | 250 | # Compiled Static libraries 251 | *.lai 252 | *.la 253 | *.a 254 | *.lib 255 | 256 | # Executables 257 | *.exe 258 | *.out 259 | *.app 260 | ### OSX template 261 | .DS_Store 262 | .AppleDouble 263 | .LSOverride 264 | 265 | # Icon must end with two \r 266 | Icon 267 | 268 | # Thumbnails 269 | ._* 270 | 271 | # Files that might appear in the root of a volume 272 | .DocumentRevisions-V100 273 | .fseventsd 274 | .Spotlight-V100 275 | .TemporaryItems 276 | .Trashes 277 | .VolumeIcon.icns 278 | 279 | # Directories potentially created on remote AFP share 280 | .AppleDB 281 | .AppleDesktop 282 | Network Trash Folder 283 | Temporary Items 284 | .apdisk 285 | ### Xcode template 286 | # Xcode 287 | # 288 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 289 | 290 | ## Build generated 291 | build/ 292 | DerivedData 293 | 294 | ## Various settings 295 | *.pbxuser 296 | !default.pbxuser 297 | *.mode1v3 298 | !default.mode1v3 299 | *.mode2v3 300 | !default.mode2v3 301 | *.perspectivev3 302 | !default.perspectivev3 303 | xcuserdata 304 | 305 | ## Other 306 | *.xccheckout 307 | *.moved-aside 308 | *.xcuserstate 309 | ### Scala template 310 | *.class 311 | *.log 312 | /.bsp 313 | 314 | # sbt specific 315 | .cache 316 | .history 317 | .lib/ 318 | dist/* 319 | target/ 320 | lib_managed/ 321 | src_managed/ 322 | project/boot/ 323 | project/plugins/project/ 324 | 325 | # Scala-IDE specific 326 | .scala_dependencies 327 | .worksheet 328 | ### Java template 329 | *.class 330 | 331 | # Mobile Tools for Java (J2ME) 332 | .mtj.tmp/ 333 | 334 | # Package Files # 335 | *.jar 336 | *.war 337 | *.ear 338 | 339 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 340 | hs_err_pid* 341 | 342 | 343 | /riscv-tests-results/*.out 344 | /c-tests-results/*.out 345 | /c/*.o 346 | /c/*.elf 347 | /c/*.dump 348 | /rust/*.elf 349 | /rust/*.dump 350 | /rust-tests-results/*.out 351 | /verilog/* 352 | /verilog/!.gitkeep 353 | -------------------------------------------------------------------------------- /src/main/scala/Core.scala: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | import Consts._ 6 | import Instructions._ 7 | 8 | // ┌─┐ ┌─┐ ┌─┐ ┌─┐ 9 | // ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │ │ ┌─────┐ │ │ ┌────┐ 10 | // │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 11 | // │ IF ├─►│ ├─►│ ID ├─►│ ├─►│ EX ├─►│ ├─►│ MEM ├─►│ ├─►│ WB │ 12 | // │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 13 | // └────┘ │ │ └────┘ │ │ └────┘ │ │ └─────┘ │ │ └────┘ 14 | // └─┘ └─┘ └─┘ └─┘ 15 | // IF/ID IF/ID IF/MEM MEM/ID 16 | // registers registers registers registers 17 | 18 | // IF 19 | class FetchStage { 20 | val stall_flag = Wire(Bool()) // True when data hazard occurs at EX stage 21 | // IF/ID pipeline registers 22 | val reg_pc = RegInit(0.U(WORD_LEN.W)) 23 | val reg_inst = RegInit(0.U(WORD_LEN.W)) 24 | 25 | def connect(imem: ImemPortIo, exe: ExecuteStage, csr: Mem[UInt]) = { 26 | val inst = imem.inst 27 | 28 | // Uninitialized ports for branch instructions. They will be connected in EX stage 29 | 30 | // Program counter register. It counts up per 4 bytes since size of instruction is 32bits, but 31 | // memory address is byte oriented. 32 | val pc = RegInit(START_ADDR) 33 | pc := MuxCase(pc + 4.U(WORD_LEN.W), Seq( 34 | exe.br_flag -> exe.br_target, // Branch instructions write back br_target address to program counter 35 | exe.jmp_flag -> exe.alu_out, // Jump instructions calculate the jump address by ALU 36 | // CSRs[0x305] is mtvec (trap_vector). The process on exception (syscall) is written at the 37 | // trap_vector address. Note that there is no OS on this CPU. 38 | (inst === ECALL) -> csr(0x305), 39 | // Keep current pc due to stall by data hazard 40 | stall_flag -> pc, 41 | )) 42 | 43 | // Connect program counter to address output. This output is connected to memory to fetch the 44 | // address as instruction 45 | imem.addr := pc 46 | 47 | // Save IF states for next stage 48 | reg_pc := Mux(stall_flag, reg_pc, pc) // Keep current pc due to stall by data hazard 49 | 50 | // On branch hazard: 51 | // 52 | // Jump instructions cause pipeline branch hazard. Replace the instruction being fetched with NOP 53 | // not to execute it. 54 | // 55 | // IF ID EX MEM 56 | // ┌──────┬──────┐ 57 | // │INST A│ JUMP │ CYCLE = C 58 | // │ (X+4)│ (X) │ 59 | // └──────┴──────┘ 60 | // ┌──────┬──────┬──────┐ 61 | // │INST B│INST A│JUMP X│ CYCLE = C+1 62 | // │ (X+8)│ (X+4)│(X->Y)│ 63 | // └──┬───┴──┬───┴──────┘ 64 | // ▼ ▼ 65 | // ┌──────┬──────┬──────┐ 66 | // │ NOP │ NOP │JUMP X│ CYCLE = C+1 67 | // │ │ │(X->Y)│ 68 | // └──────┴──────┴──────┘ 69 | // ┌──────┬──────┬──────┬──────┐ 70 | // │INST P│ NOP │ NOP │JUMP X│ CYCLE = C+2 71 | // │ (Y) │ │ │(X->Y)│ 72 | // └──────┴──────┴──────┴──────┘ 73 | // 74 | // 75 | // On data hazard: 76 | // 77 | // To fetch the register data which is now being calculated at EX stage in IF stage, IF and ID 78 | // must wait for the data being written back to the register. The data will be forwarded to IF 79 | // and ID at MEM. 80 | // 81 | // IF ID EX MEM 82 | // ┌──────┬──────┬──────┐ 83 | // │INST B│INST A│INST C│ CYCLE = C 84 | // │ (X+8)│ (X+4)│ (X) │ 85 | // └┬─────┴┬─────┴──────┘ 86 | // │keep │keep 87 | // ┌▼─────┐▼─────┬──────┬──────┐ 88 | // │INST B│INST A│ NOP │INST C│ CYCLE = C+1 89 | // │ (X+8)│ (X+4)│ │ (X) │ 90 | // └──────┴──▲───┴──────┴──┬───┘ 91 | // └─────────────┘ 92 | // forward 93 | // 94 | reg_inst := MuxCase(inst, Seq( 95 | (exe.br_flag || exe.jmp_flag) -> BUBBLE, // Prioritize branch hazard over data hazard 96 | stall_flag -> reg_inst, 97 | )) 98 | 99 | printf(p"IF: pc=0x${Hexadecimal(pc)}\n") 100 | } 101 | } 102 | 103 | // ID 104 | class DecodeStage { 105 | val inst = Wire(UInt(WORD_LEN.W)) 106 | // ID/EX pipeline registers 107 | val reg_pc = RegInit(0.U(WORD_LEN.W)) 108 | val reg_wb_addr = RegInit(0.U(ADDR_LEN.W)) 109 | val reg_op1_data = RegInit(0.U(WORD_LEN.W)) 110 | val reg_op2_data = RegInit(0.U(WORD_LEN.W)) 111 | val reg_rs1_data = RegInit(0.U(WORD_LEN.W)) 112 | val reg_rs2_data = RegInit(0.U(WORD_LEN.W)) 113 | val reg_exe_fun = RegInit(0.U(EXE_FUN_LEN.W)) 114 | val reg_mem_wen = RegInit(0.U(MEN_LEN.W)) 115 | val reg_rf_wen = RegInit(0.U(REN_LEN.W)) 116 | val reg_wb_sel = RegInit(0.U(WB_SEL_LEN.W)) 117 | val reg_csr_addr = RegInit(0.U(CSR_ADDR_LEN.W)) 118 | val reg_csr_cmd = RegInit(0.U(CSR_LEN.W)) 119 | val reg_imm_i_sext = RegInit(0.U(WORD_LEN.W)) 120 | val reg_imm_s_sext = RegInit(0.U(WORD_LEN.W)) 121 | val reg_imm_b_sext = RegInit(0.U(WORD_LEN.W)) 122 | val reg_imm_u_shifted = RegInit(0.U(WORD_LEN.W)) 123 | val reg_imm_z_uext = RegInit(0.U(WORD_LEN.W)) 124 | 125 | def connectStallFlag(prev: FetchStage) = { 126 | // rs1_addr and rs2_addr in connect() are not available because they are connected from inst and 127 | // inst wire is connected to stall_flag wire. Separate wires are necessary. 128 | val rs1_addr = prev.reg_inst(19, 15) 129 | val rs2_addr = prev.reg_inst(24, 20) 130 | 131 | // stall_flag outputs true signal when data hazard occurs in RS1 or RS2 at EX stage. 132 | prev.stall_flag := ( 133 | reg_rf_wen === REN_SCALAR && 134 | rs1_addr =/= 0.U && 135 | rs1_addr === reg_wb_addr 136 | ) || ( 137 | reg_rf_wen === REN_SCALAR && 138 | rs2_addr =/= 0.U && 139 | rs2_addr === reg_wb_addr 140 | ) 141 | } 142 | 143 | def connect(prev: FetchStage, exe: ExecuteStage, mem: MemStage, gr: Mem[UInt]) = { 144 | connectStallFlag(prev) 145 | 146 | // Spec 2.2 Base Instruction Formats 147 | // 148 | // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 149 | // ┌─────────────────────────┬──────────────────┬──────────────┬────────┬─────────────────┬─────────────┐ 150 | // │ funct7 │ rs2 │ rs1 │ funct3 │ rd │ opcode │ R-type 151 | // ├─────────────────────────┴──────────────────┼──────────────┼────────┼─────────────────┼─────────────┤ 152 | // │ imm[11:0] │ rs1 │ funct3 │ rd │ opcode │ I-type 153 | // ├─────────────────────────┬──────────────────┼──────────────┼────────┼─────────────────┼─────────────┤ 154 | // │ imm[11:5] │ rs2 │ rs1 │ funct3 │ imm[4:0] │ opcode │ S-type 155 | // ├───────┬─────────────────┼──────────────────┼──────────────┼────────┼─────────┬───────┼─────────────┤ 156 | // │imm[12]│ imm[10:5] │ rs2 │ rs1 │ funct3 │imm[4:1] │imm[11]│ opcode │ B-type 157 | // ├───────┴─────────────────┴──────────────────┴──────────────┴────────┼─────────┴───────┼─────────────┤ 158 | // │ imm[31:12] │ rd │ opcode │ U-type 159 | // ├───────┬────────────────────────────┬───────┬───────────────────────┼─────────────────┼─────────────┤ 160 | // │imm[20]│ imm[10:1] │imm[11]│ imm[19:12] │ rd │ opcode │ J-type 161 | // └───────┴────────────────────────────┴───────┴───────────────────────┴─────────────────┴─────────────┘ 162 | 163 | // Jump instructions cause pipeline branch hazard. Replace the instruction being fetched with NOP 164 | // not to execute it. 165 | inst := Mux(exe.br_flag || exe.jmp_flag || prev.stall_flag, BUBBLE, prev.reg_inst) 166 | 167 | val rs1_addr = inst(19, 15) 168 | val rs2_addr = inst(24, 20) 169 | val wb_addr = inst(11, 7) // rd 170 | 171 | val rs1_data = MuxCase(gr(rs1_addr), Seq( 172 | // The value of register #0 is always zero 173 | (rs1_addr === 0.U) -> 0.U(WORD_LEN.W), 174 | // Forward data from MEM stage to avoid data hazard. In this case, the data is not written back 175 | // to the register. To fix this, ID stage needs to wait for the data is written back (stall). 176 | // To avoid the stall, directly read the data being written back to the register at MEM stage. 177 | (rs1_addr === exe.reg_wb_addr && exe.reg_rf_wen === REN_SCALAR) -> mem.wb_data, 178 | // Forward data from WB stage to avoid data hazard. The same as above. 179 | (rs1_addr === mem.reg_wb_addr && mem.reg_rf_wen === REN_SCALAR) -> mem.reg_wb_data, 180 | )) 181 | val rs2_data = MuxCase(gr(rs2_addr), Seq( 182 | // The value of register #0 is always zero 183 | (rs2_addr === 0.U) -> 0.U(WORD_LEN.W), 184 | // Forward data from MEM stage to avoid data hazard. The same as RS1 above. 185 | (rs2_addr === exe.reg_wb_addr && exe.reg_rf_wen === REN_SCALAR) -> mem.wb_data, 186 | // Forward data from WB stage to avoid data hazard. The same as above. 187 | (rs2_addr === mem.reg_wb_addr && mem.reg_rf_wen === REN_SCALAR) -> mem.reg_wb_data, 188 | )) 189 | 190 | // Spec 2.6: The effective address is obtained by adding register rs1 to the sign-extended 12-bit offset. 191 | // sext 12bit value to 32bit value. 192 | val imm_i = inst(31, 20) // imm for I-type 193 | val imm_i_sext = Cat(Fill(20, imm_i(11)), imm_i) 194 | val imm_s = Cat(inst(31, 25), inst(11, 7)) // imm for S-type 195 | val imm_s_sext = Cat(Fill(20, imm_s(11)), imm_s) 196 | 197 | // Decode imm of B-type instruction 198 | val imm_b = Cat(inst(31), inst(7), inst(30, 25), inst(11, 8)) 199 | // imm[0] does not exist in B-type instruction. This is because the first bit of program counter 200 | // is always zero (p.126). Size of instruction is 32bit or 16bit, so instruction pointer (pc) 201 | // address always points an even address. 202 | val imm_b_sext = Cat(Fill(19, imm_b(11)), imm_b, 0.U(1.U)) 203 | 204 | // Decode imm of J-type instruction 205 | val imm_j = Cat(inst(31), inst(19, 12), inst(20), inst(30, 21)) 206 | val imm_j_sext = Cat(Fill(11, imm_j(19)), imm_j, 0.U(1.U)) // Set LSB to zero 207 | 208 | // Decode imm of U-type instruction 209 | val imm_u = inst(31, 12) 210 | val imm_u_shifted = Cat(imm_u, Fill(12, 0.U)) // for LUI and AUIPC 211 | 212 | // Decode imm of I-type instruction 213 | val imm_z = inst(19, 15) 214 | val imm_z_uext = Cat(Fill(27, 0.U), imm_z) // for CSR instructions 215 | 216 | // Decode operand sources and memory/register write back behavior 217 | val List(exe_fun, op1_sel, op2_sel, mem_wen, rf_wen, wb_sel, csr_cmd) = ListLookup( 218 | inst, 219 | List(ALU_NONE, OP1_RS1, OP2_RS2, MEN_NONE, REN_NONE, WB_NONE, CSR_NONE), 220 | Array( 221 | // 2.6 Load and Store Instructions 222 | LW -> List(ALU_ADD, OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_MEM, CSR_NONE), // x[rs1] + sext(imm_i) 223 | SW -> List(ALU_ADD, OP1_RS1, OP2_IMS, MEN_SCALAR, REN_NONE, WB_NONE, CSR_NONE), // x[rs1] + sext(imm_s) 224 | // 2.4 Integer Computational Instructions 225 | ADD -> List(ALU_ADD, OP1_RS1, OP2_RS2, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] + x[rs2] 226 | ADDI -> List(ALU_ADD, OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] + sext(imm_i) 227 | SUB -> List(ALU_SUB, OP1_RS1, OP2_RS2, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] - x[rs2] 228 | AND -> List(ALU_AND, OP1_RS1, OP2_RS2, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] & x[rs2] 229 | OR -> List(ALU_OR, OP1_RS1, OP2_RS2, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] | x[rs2] 230 | XOR -> List(ALU_XOR, OP1_RS1, OP2_RS2, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] ^ x[rs2] 231 | ANDI -> List(ALU_AND, OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] & sext(imm_i) 232 | ORI -> List(ALU_OR , OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] | sext(imm_i) 233 | XORI -> List(ALU_XOR, OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] ^ sext(imm_i) 234 | SLL -> List(ALU_SLL, OP1_RS1, OP2_RS2, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] << x[rs2](4,0) 235 | SRL -> List(ALU_SRL, OP1_RS1, OP2_RS2, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] >>u x[rs2](4,0) 236 | SRA -> List(ALU_SRA, OP1_RS1, OP2_RS2, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] >>s x[rs2](4,0) 237 | SLLI -> List(ALU_SLL, OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] << imm_i_sext(4,0) 238 | SRLI -> List(ALU_SRL, OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] >>u imm_i_sext(4,0) 239 | SRAI -> List(ALU_SRA, OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] >>s imm_i_sext(4,0) 240 | SLT -> List(ALU_SLT, OP1_RS1, OP2_RS2, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] List(ALU_SLTU, OP1_RS1, OP2_RS2, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] List(ALU_SLT, OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] List(ALU_SLTU, OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // x[rs1] List(ALU_ADD, OP1_NONE, OP2_IMU, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // sext(imm_u[31:12] << 12) 245 | AUIPC -> List(ALU_ADD, OP1_PC, OP2_IMU, MEN_NONE, REN_SCALAR, WB_ALU, CSR_NONE), // PC + sext(imm_u[31:12] << 12) 246 | // 2.5 Control Transfer Instructions 247 | BEQ -> List(BR_BEQ, OP1_RS1, OP2_RS2, MEN_NONE, REN_NONE, WB_NONE, CSR_NONE), // x[rs1] === x[rs2] then PC+sext(imm_b) 248 | BNE -> List(BR_BNE, OP1_RS1, OP2_RS2, MEN_NONE, REN_NONE, WB_NONE, CSR_NONE), // x[rs1] =/= x[rs2] then PC+sext(imm_b) 249 | BGE -> List(BR_BGE, OP1_RS1, OP2_RS2, MEN_NONE, REN_NONE, WB_NONE, CSR_NONE), // x[rs1] >=s x[rs2] then PC+sext(imm_b) 250 | BGEU -> List(BR_BGEU, OP1_RS1, OP2_RS2, MEN_NONE, REN_NONE, WB_NONE, CSR_NONE), // x[rs1] >=u x[rs2] then PC+sext(imm_b) 251 | BLT -> List(BR_BLT, OP1_RS1, OP2_RS2, MEN_NONE, REN_NONE, WB_NONE, CSR_NONE), // x[rs1] List(BR_BLTU, OP1_RS1, OP2_RS2, MEN_NONE, REN_NONE, WB_NONE, CSR_NONE), // x[rs1] List(ALU_ADD, OP1_PC, OP2_IMJ, MEN_NONE, REN_SCALAR, WB_PC, CSR_NONE), // x[rd] <- PC+4 and PC+sext(imm_j) 254 | JALR -> List(ALU_JALR, OP1_RS1, OP2_IMI, MEN_NONE, REN_SCALAR, WB_PC, CSR_NONE), // x[rd] <- PC+4 and (x[rs1]+sext(imm_i))&~1 255 | // 9.1 "Zicsr", Control and Status Register (CSR) Instructions 256 | CSRRW -> List(ALU_RS1, OP1_RS1, OP2_NONE, MEN_NONE, REN_SCALAR, WB_CSR, CSR_W), // CSRs[csr] <- x[rs1] 257 | CSRRWI -> List(ALU_RS1, OP1_IMZ, OP2_NONE, MEN_NONE, REN_SCALAR, WB_CSR, CSR_W), // CSRs[csr] <- uext(imm_z) 258 | CSRRS -> List(ALU_RS1, OP1_RS1, OP2_NONE, MEN_NONE, REN_SCALAR, WB_CSR, CSR_S), // CSRs[csr] <- CSRs[csr] | x[rs1] 259 | CSRRSI -> List(ALU_RS1, OP1_IMZ, OP2_NONE, MEN_NONE, REN_SCALAR, WB_CSR, CSR_S), // CSRs[csr] <- CSRs[csr] | uext(imm_z) 260 | CSRRC -> List(ALU_RS1, OP1_RS1, OP2_NONE, MEN_NONE, REN_SCALAR, WB_CSR, CSR_C), // CSRs[csr] <- CSRs[csr]&~x[rs1] 261 | CSRRCI -> List(ALU_RS1, OP1_IMZ, OP2_NONE, MEN_NONE, REN_SCALAR, WB_CSR, CSR_C), // CSRs[csr] <- CSRs[csr]&~uext(imm_z) 262 | // 2.8 Environment Call and Breakpoints 263 | ECALL -> List(ALU_NONE, OP1_NONE, OP2_NONE, MEN_NONE, REN_NONE, WB_NONE, CSR_E), 264 | // "V" Vector Extension 265 | VSETVLI -> List(ALU_NONE, OP1_NONE, OP2_NONE, MEN_NONE, REN_SCALAR, WB_VL, CSR_V), 266 | VLE -> List(ALU_RS1, OP1_RS1, OP2_NONE, MEN_NONE, REN_VECTOR, WB_MEM_V, CSR_NONE), 267 | ), 268 | ) 269 | 270 | // Determine 1st operand data signal 271 | val op1_data = MuxCase(0.U(WORD_LEN.W), Seq( 272 | (op1_sel === OP1_RS1) -> rs1_data, 273 | (op1_sel === OP1_PC) -> prev.reg_pc, 274 | (op1_sel === OP1_IMZ) -> imm_z_uext, 275 | )) 276 | 277 | // Determine 2nd operand data signal 278 | val op2_data = MuxCase(0.U(WORD_LEN.W), Seq( 279 | (op2_sel === OP2_RS2) -> rs2_data, 280 | (op2_sel === OP2_IMI) -> imm_i_sext, 281 | (op2_sel === OP2_IMS) -> imm_s_sext, 282 | (op2_sel === OP2_IMJ) -> imm_j_sext, 283 | (op2_sel === OP2_IMU) -> imm_u_shifted, // for LUI and AUIPC 284 | )) 285 | 286 | // Decode CSR instructions 287 | val csr_addr = Mux( 288 | csr_cmd === CSR_E, 289 | // CSRs[0x342] is mcause register which describes where the ecall is executed from. 290 | // 8: from use mode 291 | // 9: from supervisor mode 292 | // 10: from hypervisor mode 293 | // 11: from machine mode 294 | 0x342.U(CSR_ADDR_LEN.W), 295 | inst(31, 20), // I-type imm value 296 | ) 297 | 298 | // Save ID states for next stage 299 | reg_pc := prev.reg_pc 300 | reg_op1_data := op1_data 301 | reg_op2_data := op2_data 302 | reg_rs1_data := rs1_data 303 | reg_rs2_data := rs2_data 304 | reg_wb_addr := wb_addr 305 | reg_rf_wen := rf_wen 306 | reg_exe_fun := exe_fun 307 | reg_wb_sel := wb_sel 308 | reg_imm_i_sext := imm_i_sext 309 | reg_imm_s_sext := imm_s_sext 310 | reg_imm_b_sext := imm_b_sext 311 | reg_imm_u_shifted := imm_u_shifted 312 | reg_imm_z_uext := imm_z_uext 313 | reg_csr_addr := csr_addr 314 | reg_csr_cmd := csr_cmd 315 | reg_mem_wen := mem_wen 316 | 317 | printf(p"ID: pc=0x${Hexadecimal(prev.reg_pc)} inst=0x${Hexadecimal(inst)} rs1=${rs1_data} rs2=${rs2_data} stall=${prev.stall_flag}\n") 318 | } 319 | } 320 | 321 | // EX 322 | class ExecuteStage { 323 | val br_flag = Wire(Bool()) 324 | val br_target = Wire(UInt(WORD_LEN.W)) 325 | val jmp_flag = Wire(Bool()) 326 | val alu_out = Wire(UInt(WORD_LEN.W)) 327 | 328 | // EX/MEM pipeline registers 329 | val reg_pc = RegInit(0.U(WORD_LEN.W)) 330 | val reg_wb_addr = RegInit(0.U(ADDR_LEN.W)) 331 | val reg_op1_data = RegInit(0.U(WORD_LEN.W)) 332 | val reg_rs1_data = RegInit(0.U(WORD_LEN.W)) 333 | val reg_rs2_data = RegInit(0.U(WORD_LEN.W)) 334 | val reg_mem_wen = RegInit(0.U(MEN_LEN.W)) 335 | val reg_rf_wen = RegInit(0.U(REN_LEN.W)) 336 | val reg_wb_sel = RegInit(0.U(WB_SEL_LEN.W)) 337 | val reg_csr_addr = RegInit(0.U(CSR_ADDR_LEN.W)) 338 | val reg_csr_cmd = RegInit(0.U(CSR_LEN.W)) 339 | val reg_imm_i_sext = RegInit(0.U(WORD_LEN.W)) 340 | val reg_imm_z_uext = RegInit(0.U(WORD_LEN.W)) 341 | val reg_alu_out = RegInit(0.U(WORD_LEN.W)) 342 | 343 | def connect(prev: DecodeStage) = { 344 | // Arithmetic Logic Unit process arithmetic/logical calculations for each instruction. 345 | alu_out := MuxCase(0.U(WORD_LEN.W), Seq( 346 | (prev.reg_exe_fun === ALU_ADD) -> (prev.reg_op1_data + prev.reg_op2_data), 347 | (prev.reg_exe_fun === ALU_SUB) -> (prev.reg_op1_data - prev.reg_op2_data), 348 | (prev.reg_exe_fun === ALU_AND) -> (prev.reg_op1_data & prev.reg_op2_data), 349 | (prev.reg_exe_fun === ALU_OR) -> (prev.reg_op1_data | prev.reg_op2_data), 350 | (prev.reg_exe_fun === ALU_XOR) -> (prev.reg_op1_data ^ prev.reg_op2_data), 351 | // Note: (31, 0) is necessary because << extends bits of the result value 352 | // Note: (4, 0) is necessary for I instructions (imm[4:0]) 353 | (prev.reg_exe_fun === ALU_SLL) -> (prev.reg_op1_data << prev.reg_op2_data(4, 0))(31, 0), 354 | (prev.reg_exe_fun === ALU_SRL) -> (prev.reg_op1_data >> prev.reg_op2_data(4, 0)).asUInt(), 355 | (prev.reg_exe_fun === ALU_SRA) -> (prev.reg_op1_data.asSInt() >> prev.reg_op2_data(4, 0)).asUInt(), 356 | // Compare as signed integers 357 | (prev.reg_exe_fun === ALU_SLT) -> (prev.reg_op1_data.asSInt() < prev.reg_op2_data.asSInt()).asUInt(), 358 | (prev.reg_exe_fun === ALU_SLTU) -> (prev.reg_op1_data < prev.reg_op2_data).asUInt(), 359 | // &~1 sets the LSB to zero (& 0b1111..1110) for jump instructions 360 | (prev.reg_exe_fun === ALU_JALR) -> ((prev.reg_op1_data + prev.reg_op2_data) & ~1.U(WORD_LEN.W)), 361 | (prev.reg_exe_fun === ALU_RS1) -> prev.reg_op1_data, 362 | )) 363 | 364 | // Branch instructions 365 | br_flag := MuxCase(false.B, Seq( 366 | (prev.reg_exe_fun === BR_BEQ) -> (prev.reg_op1_data === prev.reg_op2_data), 367 | (prev.reg_exe_fun === BR_BNE) -> !(prev.reg_op1_data === prev.reg_op2_data), 368 | (prev.reg_exe_fun === BR_BLT) -> (prev.reg_op1_data.asSInt() < prev.reg_op2_data.asSInt()), 369 | (prev.reg_exe_fun === BR_BGE) -> !(prev.reg_op1_data.asSInt() < prev.reg_op2_data.asSInt()), 370 | (prev.reg_exe_fun === BR_BLTU) -> (prev.reg_op1_data < prev.reg_op2_data), 371 | (prev.reg_exe_fun === BR_BGEU) -> !(prev.reg_op1_data < prev.reg_op2_data), 372 | )) 373 | br_target := prev.reg_pc + prev.reg_imm_b_sext 374 | 375 | jmp_flag := prev.reg_wb_sel === WB_PC 376 | 377 | // Save EX states for next stage 378 | reg_pc := prev.reg_pc 379 | reg_op1_data := prev.reg_op1_data 380 | reg_rs1_data := prev.reg_rs1_data 381 | reg_rs2_data := prev.reg_rs2_data 382 | reg_wb_addr := prev.reg_wb_addr 383 | reg_alu_out := alu_out 384 | reg_rf_wen := prev.reg_rf_wen 385 | reg_wb_sel := prev.reg_wb_sel 386 | reg_csr_addr := prev.reg_csr_addr 387 | reg_csr_cmd := prev.reg_csr_cmd 388 | reg_imm_i_sext := prev.reg_imm_i_sext 389 | reg_imm_z_uext := prev.reg_imm_z_uext 390 | reg_mem_wen := prev.reg_mem_wen 391 | 392 | printf(p"EX: pc=0x${Hexadecimal(prev.reg_pc)} wb_addr=${prev.reg_wb_addr} op1=0x${Hexadecimal(prev.reg_op1_data)} op2=0x${Hexadecimal(prev.reg_op2_data)} alu_out=0x${Hexadecimal(alu_out)} jmp=${jmp_flag}\n") 393 | } 394 | } 395 | 396 | // MEM 397 | class MemStage { 398 | val wb_data = Wire(UInt(WORD_LEN.W)) // Declare wire for forwarding (p.200) 399 | // MEM/WB pipeline registers 400 | val reg_wb_addr = RegInit(0.U(ADDR_LEN.W)) 401 | val reg_rf_wen = RegInit(0.U(REN_LEN.W)) 402 | val reg_wb_data = RegInit(0.U(WORD_LEN.W)) 403 | 404 | def connect(dmem: DmemPortIo, prev: ExecuteStage, decode: DecodeStage, csr: Mem[UInt]) = { 405 | dmem.addr := prev.reg_alu_out // Always output data to memory regardless of instruction 406 | dmem.wen := prev.reg_mem_wen // mem_wen is integer and here it is implicitly converted to bool 407 | dmem.wdata := prev.reg_rs2_data 408 | 409 | // Handle CSR instructions 410 | val csr_rdata = csr(prev.reg_csr_addr) // Read CSRs[csr] 411 | val csr_wdata = MuxCase(0.U(WORD_LEN.W), Seq( 412 | (prev.reg_csr_cmd === CSR_W) -> prev.reg_op1_data, // Write 413 | (prev.reg_csr_cmd === CSR_S) -> (csr_rdata | prev.reg_op1_data), // Read and Set Bits 414 | (prev.reg_csr_cmd === CSR_C) -> (csr_rdata & ~prev.reg_op1_data), // Read and Clear Bits 415 | // This CPU only implements the machine mode. 11 (machine mode) is always written to 416 | // CSRs[0x342] (mcause). 417 | (prev.reg_csr_cmd === CSR_E) -> 11.U(WORD_LEN.W), 418 | )) 419 | 420 | when(prev.reg_csr_cmd =/= CSR_NONE) { 421 | csr(prev.reg_csr_addr) := csr_wdata 422 | } 423 | 424 | // "V" 6: Configuration-Setting Instructions (I-type) 425 | // 426 | // 31 30 29 25 24 20 19 15 14 12 11 7 6 0 427 | // ┌──┬──────────────────┬─────────┬─────┬─────────┬───────┐ 428 | // │0 │ zimm[10:0] │ rs1 │ 111 │ rd │1010111│ vsetvli 429 | // └──┴──────────────────┴─────────┴─────┴─────────┴───────┘ 430 | // ┌──┬──┬───────────────┬─────────┬─────┬─────────┬───────┐ 431 | // │1 │1 │ zimm[9:0] │uimm[4:0]│ 111 │ rd │1010111│ vsetivli 432 | // └──┴──┴───────────────┴─────────┴─────┴─────────┴───────┘ 433 | // ┌──┬──┬─────┬─────────┬─────────┬─────┬─────────┬───────┐ 434 | // │1 │0 │00000│ rs2 │ rs1 │ 111 │ rd │1010111│ vsetvl 435 | // └──┴──┴─────┴─────────┴─────────┴─────┴─────────┴───────┘ 436 | // 437 | // Note: VLEN is 128bit. 438 | // TODO: Implement vsetivli and vsetvl 439 | // TODO: Current implementation is limited: e8 <= SEW <= e64 and m1 <= LMUL <= m8 440 | 441 | // "V" 6.1: vtype encoding (p.226) 442 | // 443 | // 31 30 8 7 6 5 3 2 0 444 | // ┌────┬──────────────────┬───┬───┬─────────┬──────────┐ 445 | // │vill│ reserved │vma│vta│vsew[2:0]│vlmul[2:0]│ 446 | // └────┴──────────────────┴───┴───┴─────────┴──────────┘ 447 | // 448 | // vtype setting is encoded in imm of vsetvli/vsetivli and rs2 of vsetvl. 449 | val vtype = prev.reg_imm_i_sext 450 | // Note: vtype(2, 0) is correct range of vsew, but we only supports range of m1~m8 for lmul 451 | // (mf8~mf2 are unsupported). The MSB is discarded here. 452 | val vlmul = vtype(1, 0) 453 | val vsew = vtype(4, 2) 454 | // sew = 2^(vsew+3) 455 | // lmul = 2^vlmul 456 | // vlmax = (vlen * lmul) / sew = (vlen * 2^vlmul) / 2^(vsew+3) = (vlen << vlmul) >> (vsew+3) 457 | val vlmax = ((VLEN.U << vlmul) >> (vsew + 3.U(3.W))).asUInt() 458 | val avl = prev.reg_rs1_data 459 | // Condition is AVL/2 <= VL <= VLMAX (See "V" 6.3). It means AVL <= VLMAX*2. When AVL is larger 460 | // than VLMAX, it is separate into 2 computations. Compute VLMAX elements at first, then compute 461 | // (VLMAX - AVL) elements. 462 | val vl = MuxCase(0.U(WORD_LEN.W), Seq( 463 | (avl <= vlmax) -> avl, 464 | (avl > vlmax) -> vlmax, 465 | )) 466 | when(prev.reg_csr_cmd === CSR_V) { 467 | csr(VL_ADDR) := vl 468 | csr(VTYPE_ADDR) := vtype 469 | } 470 | 471 | // By default, write back the ALU result to register (wb_sel == WB_ALU) 472 | wb_data := MuxCase(prev.reg_alu_out, Seq( 473 | (prev.reg_wb_sel === WB_MEM) -> dmem.rdata, // Loaded data from memory 474 | (prev.reg_wb_sel === WB_PC) -> (prev.reg_pc + 4.U(WORD_LEN.W)), // Jump instruction stores the next pc (pc+4) to x[rd] 475 | (prev.reg_wb_sel === WB_CSR) -> csr_rdata, // CSR instruction write back CSRs[csr] 476 | (prev.reg_wb_sel === WB_VL) -> vl, // vsetvli, vsetivli, vsetvl 477 | )) 478 | 479 | // Save MEM states for next stage 480 | reg_wb_addr := prev.reg_wb_addr 481 | reg_rf_wen := prev.reg_rf_wen 482 | reg_wb_data := wb_data 483 | 484 | printf(p"MEM: pc=0x${Hexadecimal(prev.reg_pc)} wb_data=0x${Hexadecimal(wb_data)} rs1=0x${Hexadecimal(prev.reg_rs1_data)} rs2=0x${Hexadecimal(prev.reg_rs2_data)}\n") 485 | } 486 | } 487 | 488 | // WB 489 | class WriteBackStage { 490 | def connectVle(prev: MemStage, vr: Mem[UInt]) = { 491 | // "V" 7.1: Vector Load Instruction Encoding 492 | // 493 | // 31 29 28 27 26 25 24 20 19 15 14 12 11 7 6 0 494 | // ┌─────┬───┬─────┬──┬───────┬───────┬──────┬──────┬───────┐ 495 | // │ nf │mew│ mop │vm│ lumop │ rs1 │width │ vd │0000111│ VL* 496 | // └─────┴───┴─────┴──┴───────┴───────┴──────┴──────┴───────┘ 497 | // unit-stride -> vd: destination of load, rs1: base address 498 | // 499 | // 31 29 28 27 26 25 24 20 19 15 14 12 11 7 6 0 500 | // ┌─────┬───┬─────┬──┬───────┬───────┬──────┬──────┬───────┐ 501 | // │ nf │mew│ mop │vm│ rs2 │ rs1 │width │ vd │0000111│ VLS* 502 | // └─────┴───┴─────┴──┴───────┴───────┴──────┴──────┴───────┘ 503 | // strided -> vd: destination of load, rs1: base address, rs2: stride 504 | // 505 | // 31 29 28 27 26 25 24 20 19 15 14 12 11 7 6 0 506 | // ┌─────┬───┬─────┬──┬───────┬───────┬──────┬──────┬───────┐ 507 | // │ nf │mew│ mop │vm│ vs2 │ rs1 │width │ vd │0000111│ VLX* 508 | // └─────┴───┴─────┴──┴───────┴───────┴──────┴──────┴───────┘ 509 | // indexed -> vd: destination of load, rs1: base address, vs2: address offsets 510 | 511 | // TODO 512 | } 513 | 514 | def connect(prev: MemStage, gr: Mem[UInt], vr: Mem[UInt]) = { 515 | when(prev.reg_rf_wen === REN_SCALAR) { 516 | gr(prev.reg_wb_addr) := prev.reg_wb_data // Write back to the register specified by rd 517 | }.elsewhen(prev.reg_rf_wen === REN_VECTOR) { 518 | connectVle(prev, vr) 519 | } 520 | 521 | printf(p"WB: wb_data=0x${Hexadecimal(prev.reg_wb_data)}\n") 522 | } 523 | } 524 | 525 | class Core extends Module { 526 | val io = IO(new Bundle { 527 | // Flip input and output to connect to memory 528 | val imem = Flipped(new ImemPortIo()) 529 | val dmem = Flipped(new DmemPortIo()) 530 | 531 | // This port signal will be `true` when a program finished 532 | val exit = Output(Bool()) 533 | val gp = Output(UInt(WORD_LEN.W)) // for riscv-tests 534 | val pc = Output(UInt(WORD_LEN.W)) // for riscv-tests 535 | }) 536 | 537 | // RISC-V has 32 registers. Size is 32bits (32bits * 32regs). 538 | val regfile = Mem(32, UInt(WORD_LEN.W)) 539 | // Control and Status Registers 540 | val csr_regfile = Mem(4096, UInt(WORD_LEN.W)) 541 | // 128bit Vector Registers 542 | val vec_regfile = Mem(32, UInt(VLEN.W)) 543 | 544 | val fetch = new FetchStage() 545 | val decode = new DecodeStage() 546 | val execute = new ExecuteStage() 547 | val mem = new MemStage() 548 | val wb = new WriteBackStage() 549 | 550 | fetch.connect(io.imem, execute, csr_regfile) 551 | decode.connect(fetch, execute, mem, regfile) 552 | execute.connect(decode) 553 | mem.connect(io.dmem, execute, decode, csr_regfile) 554 | wb.connect(mem, regfile, vec_regfile) 555 | 556 | // We can know that a program is exiting when it is jumping to the current address. This never 557 | // happens in C source since C does not allow an infinite loop without any side effect. The 558 | // infinite loop is put in start.s. 559 | // 560 | // 00000008 <_loop>: 561 | // 8: 0000006f j 8 <_loop> 562 | // 563 | // This seems a normal way to represent a program exits. GCC generates a similar code in _exit 564 | // function (eventually called when a program exists). 565 | // 566 | // 0000000000010402 <_exit>: 567 | // ... 568 | // 10410: 00000073 ecall 569 | // 10414: 00054363 bltz a0,1041a <_exit+0x18> 570 | // 10418: a001 j 10418 <_exit+0x16> 571 | // ... 572 | // 10426: 008000ef jal ra,1042e <__errno> 573 | // 1042a: c100 sw s0,0(a0) 574 | // 1042c: a001 j 1042c <_exit+0x2a> 575 | // 576 | io.exit := execute.jmp_flag && (decode.reg_pc === execute.alu_out) 577 | 578 | io.gp := regfile(3) 579 | io.pc := execute.reg_pc 580 | 581 | printf(p"dmem: addr=${io.dmem.addr} wen=${io.dmem.wen} wdata=0x${Hexadecimal(io.dmem.wdata)}\n") // memory address loaded by LW 582 | 583 | when(io.exit) { 584 | printf(p"returned from main with ${regfile(10)}\n") // x10 = a0 = return value or function argument 0 585 | } 586 | printf("----------------\n") 587 | } 588 | --------------------------------------------------------------------------------