├── .clang-format ├── .gitignore ├── .scalafmt.conf ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── build.sbt ├── csrc ├── Makefile ├── fibonacci.c ├── hello.c ├── init.S ├── link.lds ├── mmio.S ├── mmio.h ├── quicksort.c └── sb.S ├── project ├── build.properties └── plugins.sbt ├── run-verilator.sh ├── src ├── main │ ├── resources │ │ ├── fibonacci.asmbin │ │ ├── hello.asmbin │ │ ├── mmio.asmbin │ │ ├── quicksort.asmbin │ │ └── sb.asmbin │ └── scala │ │ ├── board │ │ └── verilator │ │ │ └── Top.scala │ │ ├── peripheral │ │ ├── Dummy.scala │ │ ├── InstructionROM.scala │ │ ├── Memory.scala │ │ └── ROMLoader.scala │ │ └── riscv │ │ ├── CPUBundle.scala │ │ ├── Parameters.scala │ │ └── core │ │ ├── ALU.scala │ │ ├── ALUControl.scala │ │ ├── CPU.scala │ │ ├── Execute.scala │ │ ├── InstructionDecode.scala │ │ ├── InstructionFetch.scala │ │ ├── MemoryAccess.scala │ │ ├── RegisterFile.scala │ │ └── WriteBack.scala └── test │ └── scala │ └── riscv │ ├── TestAnnotations.scala │ └── singlecycle │ ├── CPUTest.scala │ ├── ExecuteTest.scala │ ├── InstructionDecoderTest.scala │ ├── InstructionFetchTest.scala │ └── RegisterFileTest.scala └── verilog └── verilator └── sim_main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | Language: Cpp 3 | MaxEmptyLinesToKeep: 3 4 | IndentCaseLabels: false 5 | AllowShortIfStatementsOnASingleLine: false 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortLoopsOnASingleLine: false 8 | DerivePointerAlignment: false 9 | PointerAlignment: Right 10 | SpaceAfterCStyleCast: true 11 | TabWidth: 4 12 | UseTab: Never 13 | IndentWidth: 4 14 | BreakBeforeBraces: Linux 15 | AccessModifierOffset: -4 16 | ForEachMacros: 17 | - list_for_each_entry 18 | - list_for_each_entry_safe 19 | - hlist_for_each_entry 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project Specific stuff 2 | test_run_dir/* 3 | *.vcd 4 | 5 | # Eclipse Core 6 | .project 7 | 8 | # External tool builders 9 | .externalToolBuilders/ 10 | 11 | # Locally stored "Eclipse launch configurations" 12 | *.launch 13 | 14 | # CDT-specific 15 | .cproject 16 | 17 | # JDT-specific (Eclipse Java Development Tools) 18 | .classpath 19 | 20 | # Java annotation processor (APT) 21 | .factorypath 22 | 23 | # PDT-specific 24 | .buildpath 25 | 26 | # sbteclipse plugin 27 | .target 28 | 29 | # Precompiled Headers 30 | *.gch 31 | *.pch 32 | 33 | *.o 34 | *.elf 35 | csrc/*.asmbin 36 | 37 | # Libraries 38 | *.lib 39 | *.a 40 | *.la 41 | *.lo 42 | 43 | # Shared objects (inc. Windows DLLs) 44 | *.dll 45 | *.so 46 | *.so.* 47 | *.dylib 48 | 49 | # Executables 50 | *.exe 51 | *.out 52 | *.app 53 | *.i*86 54 | *.x86_64 55 | *.hex 56 | 57 | # Debug files 58 | *.dSYM/ 59 | 60 | # SBT template 61 | target/ 62 | lib_managed/ 63 | src_managed/ 64 | project/boot/ 65 | .history 66 | .cache 67 | 68 | # Emacs template 69 | *~ 70 | \#*\# 71 | /.emacs.desktop 72 | /.emacs.desktop.lock 73 | *.elc 74 | auto-save-list 75 | tramp 76 | .\#* 77 | 78 | # Org-mode 79 | .org-id-locations 80 | *_archive 81 | 82 | # flymake-mode 83 | *_flymake.* 84 | 85 | # eshell files 86 | /eshell/history 87 | /eshell/lastdir 88 | 89 | # elpa packages 90 | /elpa/ 91 | 92 | # reftex files 93 | *.rel 94 | 95 | # AUCTeX auto folder 96 | /auto/ 97 | 98 | # cask packages 99 | .cask/ 100 | 101 | # Vim template 102 | [._]*.s[a-w][a-z] 103 | [._]s[a-w][a-z] 104 | *.un~ 105 | Session.vim 106 | .netrwhist 107 | *~ 108 | 109 | # JetBrains template 110 | *.iml 111 | 112 | # Directory-based project format: 113 | .idea/ 114 | 115 | # File-based project format: 116 | *.ipr 117 | *.iws 118 | 119 | # IntelliJ 120 | /out/ 121 | 122 | # mpeltonen/sbt-idea plugin 123 | .idea_modules/ 124 | 125 | # JIRA plugin 126 | atlassian-ide-plugin.xml 127 | 128 | # Crashlytics plugin (for Android Studio and IntelliJ) 129 | com_crashlytics_export_strings.xml 130 | crashlytics.properties 131 | crashlytics-build.properties 132 | 133 | # macOS 134 | .DS_Store 135 | .AppleDouble 136 | .LSOverride 137 | 138 | # Files that might appear in the root of a volume 139 | .DocumentRevisions-V100 140 | .fseventsd 141 | .Spotlight-V100 142 | .TemporaryItems 143 | .Trashes 144 | .VolumeIcon.icns 145 | 146 | # Directories potentially created on remote AFP share 147 | .AppleDB 148 | .AppleDesktop 149 | Network Trash Folder 150 | Temporary Items 151 | .apdisk 152 | 153 | # Build generated 154 | build/ 155 | DerivedData 156 | 157 | # Various settings 158 | *.pbxuser 159 | !default.pbxuser 160 | *.mode1v3 161 | !default.mode1v3 162 | *.mode2v3 163 | !default.mode2v3 164 | *.perspectivev3 165 | !default.perspectivev3 166 | xcuserdata 167 | 168 | # sbt specific 169 | .cache 170 | .history 171 | .lib/ 172 | dist/* 173 | target/ 174 | lib_managed/ 175 | src_managed/ 176 | project/boot/ 177 | project/plugins/project/ 178 | 179 | # Scala-IDE specific 180 | .scala_dependencies 181 | .worksheet 182 | 183 | # Java template 184 | *.class 185 | 186 | # Package Files # 187 | *.jar 188 | *.war 189 | *.ear 190 | 191 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 192 | hs_err_pid* 193 | 194 | verilog/*.txt 195 | verilog/verilator/* 196 | !verilog/verilator/sim_main.cpp 197 | *.jou 198 | *.log 199 | .Xil 200 | .vscode 201 | .metals 202 | .bloop/ 203 | project/project/ 204 | project/metals.sbt 205 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | # ATTENTION: 2 | # Keep this in sync with documentation/.scalafmt.conf 3 | 4 | # Version https://scalameta.org/scalafmt/docs/configuration.html#version 5 | version = 3.7.15 6 | # Dialect https://scalameta.org/scalafmt/docs/configuration.html#scala-dialects 7 | runner.dialect = scala213 8 | 9 | # Top-level preset https://scalameta.org/scalafmt/docs/configuration.html#top-level-presets 10 | preset = default 11 | 12 | # Common https://scalameta.org/scalafmt/docs/configuration.html#most-popular 13 | maxColumn = 120 14 | assumeStandardLibraryStripMargin = true 15 | 16 | # Alignment https://scalameta.org/scalafmt/docs/configuration.html#alignment 17 | align { 18 | preset = more 19 | allowOverflow = true 20 | } 21 | 22 | # Newlines https://scalameta.org/scalafmt/docs/configuration.html#newlines 23 | newlines { 24 | alwaysBeforeMultilineDef = false 25 | implicitParamListModifierPrefer = before 26 | beforeCurlyLambdaParams = multilineWithCaseOnly 27 | inInterpolation = "avoid" 28 | } 29 | 30 | # Comment processing https://scalameta.org/scalafmt/docs/configuration.html#comment-processing 31 | docstrings { 32 | style = Asterisk 33 | wrap = no 34 | } 35 | 36 | # Spaces https://scalameta.org/scalafmt/docs/configuration.html#spaces 37 | spaces { 38 | inImportCurlyBraces = true # more idiomatic to include whitepsace in import x.{ yyy => zzz } 39 | } 40 | 41 | # Project https://scalameta.org/scalafmt/docs/configuration.html#project 42 | project { 43 | git = true 44 | } 45 | 46 | # Rewrite Rules https://scalameta.org/scalafmt/docs/configuration.html#rewrite-rules 47 | rewrite { 48 | rules = [ 49 | AvoidInfix, # https://scalameta.org/scalafmt/docs/configuration.html#avoidinfix 50 | RedundantParens, # https://scalameta.org/scalafmt/docs/configuration.html#redundantparens 51 | SortModifiers, # https://scalameta.org/scalafmt/docs/configuration.html#sortmodifiers 52 | PreferCurlyFors, # https://scalameta.org/scalafmt/docs/configuration.html#prefercurlyfors 53 | Imports, # https://scalameta.org/scalafmt/docs/configuration.html#imports 54 | ] 55 | sortModifiers.order = ["private", "protected", "final", "sealed", "abstract", "implicit", "override", "lazy"] 56 | imports { 57 | expand = true 58 | sort = original 59 | groups = [["java(x)?\\..*"], ["scala\\..*"], ["sbt\\..*"]] 60 | } 61 | trailingCommas.style = keep # https://scalameta.org/scalafmt/docs/configuration.html#trailing-commas 62 | } 63 | 64 | fileOverride { 65 | "glob:**/src/main/scala-3/**" { 66 | runner.dialect = scala3 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | # zip and unzip are required for the sdkman to be installed from script 4 | RUN \ 5 | apt update && \ 6 | DEBIAN_FRONTEND=noninteractive apt-get install -y \ 7 | build-essential \ 8 | verilator \ 9 | curl \ 10 | zip \ 11 | unzip \ 12 | && \ 13 | rm -rf /var/lib/apt/lists/* 14 | 15 | # reference: https://sdkman.io/install 16 | RUN curl -s "https://get.sdkman.io" | bash 17 | 18 | # this SHELL command is needed to allow `source` to work properly 19 | # reference: https://stackoverflow.com/questions/20635472/using-the-run-instruction-in-a-dockerfile-with-source-does-not-work/45087082#45087082 20 | SHELL ["/bin/bash", "-c"] 21 | 22 | RUN source "$HOME/.sdkman/bin/sdkman-init.sh" && \ 23 | sdk install java $(sdk list java | grep -o "\b8\.[0-9]*\.[0-9]*\-tem" | head -1) && \ 24 | sdk install sbt 25 | 26 | WORKDIR "/root" 27 | COPY . . 28 | 29 | ENTRYPOINT ["/bin/bash"] 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 National Cheng Kung University, Taiwan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test verilator 2 | 3 | test: 4 | sbt test 5 | 6 | verilator: 7 | sbt "runMain board.verilator.VerilogGenerator" 8 | cd verilog/verilator && verilator --trace --exe --cc sim_main.cpp Top.v && make -C obj_dir -f VTop.mk 9 | 10 | indent: 11 | find . -name '*.scala' | xargs scalafmt 12 | clang-format -i verilog/verilator/*.cpp 13 | clang-format -i csrc/*.[ch] 14 | 15 | clean: 16 | sbt clean 17 | -$(MAKE) -C csrc clean 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Construct a single-cycle RISC-V CPU with Chisel 2 | 3 | > [!WARNING] 4 | > Please be aware that the Scala code in this repository is not entirely complete, as the instructor has omitted certain sections for students to work on independently. 5 | 6 | ## Development Objectives 7 | 8 | Our goal is to create a RISC-V CPU that prioritizes simplicity while assuming a foundational understanding of digital circuits and the C programming language among its readers. The CPU should strike a balance between simplicity and sophistication, and we intend to maximize its functionality. This project encompasses the following key aspects, which will be prominently featured in the technical report: 9 | * Implementation in Chisel. 10 | * RV32I instruction set support. 11 | * Execution of programs compiled from the C programming language. 12 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt.Keys.libraryDependencies 2 | 3 | ThisBuild / scalaVersion := "2.13.10" 4 | ThisBuild / version := "0.1.0" 5 | ThisBuild / organization := "tw.edu.ncku" 6 | 7 | val chiselVersion = "3.6.0" 8 | 9 | lazy val root = (project in file(".")) 10 | .settings( 11 | name := "mycpu", 12 | libraryDependencies ++= Seq( 13 | "edu.berkeley.cs" %% "chisel3" % chiselVersion, 14 | "edu.berkeley.cs" %% "chiseltest" % "0.6.0" % "test", 15 | ), 16 | scalacOptions ++= Seq( 17 | "-language:reflectiveCalls", 18 | "-deprecation", 19 | "-feature", 20 | "-Xcheckinit", 21 | ), 22 | addCompilerPlugin("edu.berkeley.cs" % "chisel3-plugin" % chiselVersion cross CrossVersion.full), 23 | ) 24 | -------------------------------------------------------------------------------- /csrc/Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE ?= riscv-none-elf- 2 | 3 | ASFLAGS = -march=rv32i_zicsr -mabi=ilp32 4 | CFLAGS = -O0 -Wall -march=rv32i_zicsr -mabi=ilp32 5 | LDFLAGS = --oformat=elf32-littleriscv 6 | 7 | AS := $(CROSS_COMPILE)as 8 | CC := $(CROSS_COMPILE)gcc 9 | LD := $(CROSS_COMPILE)ld 10 | OBJCOPY := $(CROSS_COMPILE)objcopy 11 | 12 | %.o: %.S 13 | $(AS) -R $(ASFLAGS) -o $@ $< 14 | %.elf: %.S 15 | $(AS) -R $(ASFLAGS) -o $(@:.elf=.o) $< 16 | $(CROSS_COMPILE)ld -o $@ -T link.lds $(LDFLAGS) $(@:.elf=.o) 17 | %.elf: %.c init.o 18 | $(CC) $(CFLAGS) -c -o $(@:.elf=.o) $< 19 | $(CROSS_COMPILE)ld -o $@ -T link.lds $(LDFLAGS) $(@:.elf=.o) init.o 20 | 21 | %.asmbin: %.elf 22 | $(OBJCOPY) -O binary -j .text -j .data $< $@ 23 | 24 | BINS = \ 25 | fibonacci.asmbin \ 26 | hello.asmbin \ 27 | mmio.asmbin \ 28 | quicksort.asmbin \ 29 | sb.asmbin 30 | 31 | # Clear the .DEFAULT_GOAL special variable, so that the following turns 32 | # to the first target after .DEFAULT_GOAL is not set. 33 | .DEFAULT_GOAL := 34 | 35 | all: $(BINS) 36 | 37 | update: $(BINS) 38 | cp -f $(BINS) ../src/main/resources 39 | 40 | clean: 41 | $(RM) *.o *.elf *.asmbin 42 | -------------------------------------------------------------------------------- /csrc/fibonacci.c: -------------------------------------------------------------------------------- 1 | static int fib(int a) 2 | { 3 | if (a == 1 || a == 2) 4 | return 1; 5 | return fib(a - 1) + fib(a - 2); 6 | } 7 | 8 | int main() 9 | { 10 | *((volatile int *) (4)) = fib(10); 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /csrc/hello.c: -------------------------------------------------------------------------------- 1 | #include "mmio.h" 2 | 3 | #define MUL80(x) (((x) << 6) + ((x) << 4)) 4 | 5 | struct { 6 | unsigned char row, col; 7 | } screen; 8 | 9 | static void copy_line(int prev, int cur) 10 | { 11 | int *prev_vram_start = ((int *) (MUL80(prev) + VRAM_BASE)); 12 | int *cur_vram_start = ((int *) (MUL80(cur) + VRAM_BASE)); 13 | for (int i = 0; i < 20; ++i) 14 | prev_vram_start[i] = cur_vram_start[i]; 15 | } 16 | 17 | static void write_char(int row, int col, unsigned char ch) 18 | { 19 | VRAM[MUL80(row) + col] = ch; 20 | } 21 | 22 | static void new_line() 23 | { 24 | screen.col = 0; 25 | if (screen.row == 29) { 26 | for (int i = 0; i < 29; ++i) 27 | copy_line(i, i + 1); 28 | int *vram = (int *) (MUL80(29) + VRAM_BASE); 29 | for (int i = 0; i < 20; ++i) 30 | vram[i] = 0x20202020; 31 | } else { 32 | ++screen.row; 33 | } 34 | } 35 | 36 | static void putch(unsigned char ch) 37 | { 38 | if (ch == '\n') { 39 | new_line(); 40 | } else if (ch == '\r') { 41 | screen.col = 0; 42 | } else { 43 | if (screen.col == 79) 44 | new_line(); 45 | write_char(screen.row, screen.col, ch); 46 | ++screen.col; 47 | } 48 | } 49 | 50 | static void clear_screen() 51 | { 52 | screen.row = 0; 53 | screen.col = 0; 54 | int *vram = ((int *) VRAM_BASE); 55 | for (int i = 0; i < 600; ++i) 56 | vram[i] = 0x20202020; 57 | } 58 | 59 | static void print_hex(unsigned int counter) 60 | { 61 | putch('0'); 62 | putch('x'); 63 | for (int i = 7; i >= 0; --i) { 64 | unsigned int num = (counter >> (i << 2)) & 0xF; 65 | if (num < 10) { 66 | putch('0' + num); 67 | } else { 68 | putch('A' + num - 10); 69 | } 70 | } 71 | } 72 | 73 | static void putstr(const char *s) 74 | { 75 | while (*s) 76 | putch(*(s++)); 77 | } 78 | 79 | static void print_timer() 80 | { 81 | putstr("Hardware timer count limit = "); 82 | print_hex(*TIMER_LIMIT); 83 | putstr(", enabled = "); 84 | print_hex(*TIMER_ENABLED); 85 | putch('\n'); 86 | } 87 | 88 | extern void enable_interrupt(); 89 | extern unsigned int get_epc(); 90 | 91 | int main() 92 | { 93 | clear_screen(); 94 | *TIMER_ENABLED = 1; 95 | putstr("MyCPU Demo Program "); 96 | putch(137); 97 | putstr("Hello, world!\n"); 98 | putstr("Last EPC = "); 99 | print_hex(get_epc()); 100 | putch('\n'); 101 | print_timer(); 102 | *((int *) 0x4) = 0xDEADBEEF; 103 | enable_interrupt(); 104 | 105 | for (;;) 106 | ; 107 | } 108 | -------------------------------------------------------------------------------- /csrc/init.S: -------------------------------------------------------------------------------- 1 | # mycpu is freely redistributable under the MIT License. See the file 2 | # "LICENSE" for information on usage and redistribution of this file. 3 | 4 | .section .text.init 5 | .global _start 6 | _start: 7 | li sp, 4096 # Initialize stack pointer 8 | call main # Jump to main function 9 | loop: 10 | j loop # Loop forever 11 | .global enable_interrupt 12 | enable_interrupt: 13 | la t0, __trap_entry 14 | csrrw t1, mtvec, t0 # setup trap vector base 15 | li t0, 0x1888 16 | csrrw t1, mstatus, t0 # enable interrupt 17 | ret 18 | .global get_epc 19 | get_epc: 20 | csrr a0, mepc 21 | ret 22 | .weak trap_handler 23 | tran_handler: 24 | ret 25 | __trap_entry: 26 | csrw mscratch, sp 27 | addi sp, sp, -128 28 | sw ra, 4(sp) 29 | 30 | sw gp, 12(sp) 31 | sw tp, 16(sp) 32 | sw t0, 20(sp) 33 | sw t1, 24(sp) 34 | sw t2, 28(sp) 35 | sw tp, 32(sp) 36 | sw s1, 36(sp) 37 | sw a0, 40(sp) 38 | sw a1, 44(sp) 39 | sw a2, 48(sp) 40 | sw a3, 52(sp) 41 | sw a4, 56(sp) 42 | sw a5, 60(sp) 43 | sw a6, 64(sp) 44 | sw a7, 68(sp) 45 | sw s2, 72(sp) 46 | sw s3, 76(sp) 47 | sw s4, 80(sp) 48 | sw s5, 84(sp) 49 | sw s6, 88(sp) 50 | sw s7, 92(sp) 51 | sw s8, 96(sp) 52 | sw s9, 100(sp) 53 | sw s10, 104(sp) 54 | sw s11, 108(sp) 55 | sw t3, 112(sp) 56 | sw t4, 116(sp) 57 | sw t5, 120(sp) 58 | sw t6, 124(sp) 59 | 60 | csrr a0, mepc 61 | csrr a1, mcause 62 | call trap_handler 63 | 64 | lw ra, 4(sp) 65 | 66 | lw gp, 12(sp) 67 | lw tp, 16(sp) 68 | lw t0, 20(sp) 69 | lw t1, 24(sp) 70 | lw t2, 28(sp) 71 | lw tp, 32(sp) 72 | lw s1, 36(sp) 73 | lw a0, 40(sp) 74 | lw a1, 44(sp) 75 | lw a2, 48(sp) 76 | lw a3, 52(sp) 77 | lw a4, 56(sp) 78 | lw a5, 60(sp) 79 | lw a6, 64(sp) 80 | lw a7, 68(sp) 81 | lw s2, 72(sp) 82 | lw s3, 76(sp) 83 | lw s4, 80(sp) 84 | lw s5, 84(sp) 85 | lw s6, 88(sp) 86 | lw s7, 92(sp) 87 | lw s8, 96(sp) 88 | lw s9, 100(sp) 89 | lw s10, 104(sp) 90 | lw s11, 108(sp) 91 | lw t3, 112(sp) 92 | lw t4, 116(sp) 93 | lw t5, 120(sp) 94 | lw t6, 124(sp) 95 | csrr sp, mscratch 96 | mret 97 | -------------------------------------------------------------------------------- /csrc/link.lds: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH("riscv") 2 | ENTRY(_start) 3 | 4 | SECTIONS 5 | { 6 | . = 0x00001000; 7 | .text : { *(.text.init) *(.text.startup) *(.text) } 8 | .data ALIGN(0x1000) : { *(.data*) *(.rodata*) *(.sdata*) } 9 | .bss : { *(.bss) } 10 | _end = .; 11 | } 12 | -------------------------------------------------------------------------------- /csrc/mmio.S: -------------------------------------------------------------------------------- 1 | # mycpu is freely redistributable under the MIT License. See the file 2 | # "LICENSE" for information on usage and redistribution of this file. 3 | 4 | .global _start 5 | _start: 6 | li a0, 0x80000000 7 | lw t0, 4(a0) 8 | li a1, 0xBEEF 9 | sw a1, 4(a0) 10 | nop 11 | lw t1, 4(a0) 12 | loop: 13 | j loop 14 | -------------------------------------------------------------------------------- /csrc/mmio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mycpu is freely redistributable under the MIT License. See the file 3 | * "LICENSE" for information on usage and redistribution of this file. 4 | */ 5 | 6 | #pragma once 7 | 8 | #define VRAM_BASE 0x20000000 9 | #define VRAM ((volatile unsigned char *) VRAM_BASE) 10 | #define TIMER_BASE 0x80000000 11 | #define TIMER_LIMIT ((volatile unsigned int *) (TIMER_BASE + 4)) 12 | #define TIMER_ENABLED ((volatile unsigned int *) (TIMER_BASE + 8)) 13 | -------------------------------------------------------------------------------- /csrc/quicksort.c: -------------------------------------------------------------------------------- 1 | static void quicksort(int *arr, int l, int r) 2 | { 3 | if (l >= r) 4 | return; 5 | 6 | int pivot = arr[l]; 7 | int i = l, j = r; 8 | while (i < j) { 9 | while (arr[j] >= pivot && i < j) 10 | --j; 11 | arr[i] = arr[j]; 12 | while (arr[i] < pivot && i < j) 13 | ++i; 14 | arr[j] = arr[i]; 15 | } 16 | arr[i] = pivot; 17 | quicksort(arr, l, i - 1); 18 | quicksort(arr, i + 1, r); 19 | } 20 | 21 | int main() 22 | { 23 | int nums[10] = {6, 2, 4, 5, 3, 1, 0, 9, 7, 8}; 24 | 25 | quicksort(nums, 0, 9); 26 | 27 | for (int i = 1; i <= 10; ++i) 28 | *(volatile int *) (i * 4) = nums[i - 1]; 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /csrc/sb.S: -------------------------------------------------------------------------------- 1 | # mycpu is freely redistributable under the MIT License. See the file 2 | # "LICENSE" for information on usage and redistribution of this file. 3 | 4 | .global _start 5 | _start: 6 | li a0, 0x4 7 | li t0, 0xDEADBEEF 8 | sb t0, 0(a0) 9 | lw t1, 0(a0) 10 | li s2, 0x15 11 | sb s2, 1(a0) 12 | lw ra, 0(a0) 13 | loop: 14 | j loop 15 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.9.7 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | -------------------------------------------------------------------------------- /run-verilator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ! -f verilog/verilator/obj_dir/VTop ]; then 4 | echo "Failed to generate Verilog" 5 | exit 1 6 | fi 7 | 8 | verilog/verilator/obj_dir/VTop $* 9 | -------------------------------------------------------------------------------- /src/main/resources/fibonacci.asmbin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sysprog21/ca2023-lab3/8fb6ab7574b8d72fad2b539f92cbf006183a8820/src/main/resources/fibonacci.asmbin -------------------------------------------------------------------------------- /src/main/resources/hello.asmbin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sysprog21/ca2023-lab3/8fb6ab7574b8d72fad2b539f92cbf006183a8820/src/main/resources/hello.asmbin -------------------------------------------------------------------------------- /src/main/resources/mmio.asmbin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sysprog21/ca2023-lab3/8fb6ab7574b8d72fad2b539f92cbf006183a8820/src/main/resources/mmio.asmbin -------------------------------------------------------------------------------- /src/main/resources/quicksort.asmbin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sysprog21/ca2023-lab3/8fb6ab7574b8d72fad2b539f92cbf006183a8820/src/main/resources/quicksort.asmbin -------------------------------------------------------------------------------- /src/main/resources/sb.asmbin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sysprog21/ca2023-lab3/8fb6ab7574b8d72fad2b539f92cbf006183a8820/src/main/resources/sb.asmbin -------------------------------------------------------------------------------- /src/main/scala/board/verilator/Top.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package board.verilator 5 | 6 | import chisel3._ 7 | import chisel3.stage.ChiselGeneratorAnnotation 8 | import chisel3.stage.ChiselStage 9 | import peripheral._ 10 | import riscv.core.CPU 11 | import riscv.CPUBundle 12 | import riscv.Parameters 13 | 14 | class Top extends Module { 15 | val io = IO(new CPUBundle) 16 | 17 | val cpu = Module(new CPU) 18 | 19 | io.deviceSelect := 0.U 20 | cpu.io.debug_read_address := io.debug_read_address 21 | io.debug_read_data := cpu.io.debug_read_data 22 | 23 | io.memory_bundle <> cpu.io.memory_bundle 24 | io.instruction_address := cpu.io.instruction_address 25 | cpu.io.instruction := io.instruction 26 | 27 | cpu.io.instruction_valid := io.instruction_valid 28 | } 29 | 30 | object VerilogGenerator extends App { 31 | (new ChiselStage) 32 | .execute(Array("-X", "verilog", "-td", "verilog/verilator"), Seq(ChiselGeneratorAnnotation(() => new Top()))) 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/peripheral/Dummy.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package peripheral 5 | 6 | import chisel3._ 7 | import riscv.Parameters 8 | 9 | // A dummy master that never initiates reads or writes 10 | class Dummy extends Module { 11 | val io = IO(new Bundle { 12 | val bundle = Flipped(new RAMBundle) 13 | }) 14 | io.bundle.write_strobe := VecInit(Seq.fill(Parameters.WordSize)(false.B)) 15 | io.bundle.write_data := 0.U 16 | io.bundle.write_enable := false.B 17 | io.bundle.address := 0.U 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/peripheral/InstructionROM.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package peripheral 5 | 6 | import java.io.FileWriter 7 | import java.nio.file.Files 8 | import java.nio.file.Paths 9 | import java.nio.ByteBuffer 10 | import java.nio.ByteOrder 11 | 12 | import chisel3._ 13 | import chisel3.experimental.annotate 14 | import chisel3.experimental.ChiselAnnotation 15 | import chisel3.util.experimental.loadMemoryFromFileInline 16 | import firrtl.annotations.MemorySynthInit 17 | import riscv.Parameters 18 | 19 | class InstructionROM(instructionFilename: String) extends Module { 20 | val io = IO(new Bundle { 21 | val address = Input(UInt(Parameters.AddrWidth)) 22 | val data = Output(UInt(Parameters.InstructionWidth)) 23 | }) 24 | 25 | val (instructionsInitFile, capacity) = readAsmBinary(instructionFilename) 26 | val mem = Mem(capacity, UInt(Parameters.InstructionWidth)) 27 | annotate(new ChiselAnnotation { 28 | override def toFirrtl = 29 | MemorySynthInit 30 | }) 31 | loadMemoryFromFileInline(mem, instructionsInitFile.toString.replaceAll("\\\\", "/")) 32 | io.data := mem.read(io.address) 33 | 34 | def readAsmBinary(filename: String) = { 35 | val inputStream = if (Files.exists(Paths.get(filename))) { 36 | Files.newInputStream(Paths.get(filename)) 37 | } else { 38 | getClass.getClassLoader.getResourceAsStream(filename) 39 | } 40 | var instructions = new Array[BigInt](0) 41 | val arr = new Array[Byte](4) 42 | while (inputStream.read(arr) == 4) { 43 | val instBuf = ByteBuffer.wrap(arr) 44 | instBuf.order(ByteOrder.LITTLE_ENDIAN) 45 | val inst = BigInt(instBuf.getInt() & 0xffffffffL) 46 | instructions = instructions :+ inst 47 | } 48 | instructions = instructions :+ BigInt(0x00000013L) 49 | instructions = instructions :+ BigInt(0x00000013L) 50 | instructions = instructions :+ BigInt(0x00000013L) 51 | val currentDir = System.getProperty("user.dir") 52 | val exeTxtPath = Paths.get(currentDir, "verilog", f"${instructionFilename}.txt") 53 | val writer = new FileWriter(exeTxtPath.toString) 54 | for (i <- instructions.indices) { 55 | writer.write(f"@$i%x\n${instructions(i)}%08x\n") 56 | } 57 | writer.close() 58 | (exeTxtPath, instructions.length) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/scala/peripheral/Memory.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package peripheral 5 | 6 | import chisel3._ 7 | import chisel3.util._ 8 | import riscv.Parameters 9 | 10 | class RAMBundle extends Bundle { 11 | val address = Input(UInt(Parameters.AddrWidth)) 12 | val write_data = Input(UInt(Parameters.DataWidth)) 13 | val write_enable = Input(Bool()) 14 | val write_strobe = Input(Vec(Parameters.WordSize, Bool())) 15 | val read_data = Output(UInt(Parameters.DataWidth)) 16 | } 17 | // The purpose of this module is to help the synthesis tool recognize 18 | // our memory as a Block RAM template 19 | class BlockRAM(capacity: Int) extends Module { 20 | val io = IO(new Bundle { 21 | val read_address = Input(UInt(Parameters.AddrWidth)) 22 | val write_address = Input(UInt(Parameters.AddrWidth)) 23 | val write_data = Input(UInt(Parameters.DataWidth)) 24 | val write_enable = Input(Bool()) 25 | val write_strobe = Input(Vec(Parameters.WordSize, Bool())) 26 | 27 | val debug_read_address = Input(UInt(Parameters.AddrWidth)) 28 | 29 | val read_data = Output(UInt(Parameters.DataWidth)) 30 | val debug_read_data = Output(UInt(Parameters.DataWidth)) 31 | }) 32 | val mem = SyncReadMem(capacity, Vec(Parameters.WordSize, UInt(Parameters.ByteWidth))) 33 | when(io.write_enable) { 34 | val write_data_vec = Wire(Vec(Parameters.WordSize, UInt(Parameters.ByteWidth))) 35 | for (i <- 0 until Parameters.WordSize) { 36 | write_data_vec(i) := io.write_data((i + 1) * Parameters.ByteBits - 1, i * Parameters.ByteBits) 37 | } 38 | mem.write((io.write_address >> 2.U).asUInt, write_data_vec, io.write_strobe) 39 | } 40 | io.read_data := mem.read((io.read_address >> 2.U).asUInt, true.B).asUInt 41 | io.debug_read_data := mem.read((io.debug_read_address >> 2.U).asUInt, true.B).asUInt 42 | } 43 | 44 | class Memory(capacity: Int) extends Module { 45 | val io = IO(new Bundle { 46 | val bundle = new RAMBundle 47 | 48 | val instruction = Output(UInt(Parameters.DataWidth)) 49 | val instruction_address = Input(UInt(Parameters.AddrWidth)) 50 | 51 | val debug_read_address = Input(UInt(Parameters.AddrWidth)) 52 | val debug_read_data = Output(UInt(Parameters.DataWidth)) 53 | }) 54 | 55 | val mem = SyncReadMem(capacity, Vec(Parameters.WordSize, UInt(Parameters.ByteWidth))) 56 | when(io.bundle.write_enable) { 57 | val write_data_vec = Wire(Vec(Parameters.WordSize, UInt(Parameters.ByteWidth))) 58 | for (i <- 0 until Parameters.WordSize) { 59 | write_data_vec(i) := io.bundle.write_data((i + 1) * Parameters.ByteBits - 1, i * Parameters.ByteBits) 60 | } 61 | mem.write((io.bundle.address >> 2.U).asUInt, write_data_vec, io.bundle.write_strobe) 62 | } 63 | io.bundle.read_data := mem.read((io.bundle.address >> 2.U).asUInt, true.B).asUInt 64 | io.debug_read_data := mem.read((io.debug_read_address >> 2.U).asUInt, true.B).asUInt 65 | io.instruction := mem.read((io.instruction_address >> 2.U).asUInt, true.B).asUInt 66 | } 67 | -------------------------------------------------------------------------------- /src/main/scala/peripheral/ROMLoader.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package peripheral 5 | 6 | import chisel3._ 7 | import riscv.Parameters 8 | 9 | class ROMLoader(capacity: Int) extends Module { 10 | val io = IO(new Bundle { 11 | val bundle = Flipped(new RAMBundle) 12 | 13 | val rom_address = Output(UInt(Parameters.AddrWidth)) 14 | val rom_data = Input(UInt(Parameters.InstructionWidth)) 15 | 16 | val load_address = Input(UInt(Parameters.AddrWidth)) 17 | val load_finished = Output(Bool()) 18 | }) 19 | 20 | val address = RegInit(0.U(32.W)) 21 | val valid = RegInit(false.B) 22 | 23 | io.bundle.write_strobe := VecInit(Seq.fill(Parameters.WordSize)(false.B)) 24 | io.bundle.address := 0.U 25 | io.bundle.write_data := 0.U 26 | io.bundle.write_enable := false.B 27 | when(address <= (capacity - 1).U) { 28 | io.bundle.write_enable := true.B 29 | io.bundle.write_data := io.rom_data 30 | io.bundle.address := (address << 2.U).asUInt + io.load_address 31 | io.bundle.write_strobe := VecInit(Seq.fill(Parameters.WordSize)(true.B)) 32 | address := address + 1.U 33 | when(address === (capacity - 1).U) { 34 | valid := true.B 35 | } 36 | } 37 | io.load_finished := valid 38 | io.rom_address := address 39 | } 40 | -------------------------------------------------------------------------------- /src/main/scala/riscv/CPUBundle.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv 5 | 6 | import chisel3._ 7 | import peripheral.RAMBundle 8 | 9 | // CPUBundle serves as the communication interface for data exchange between 10 | // the CPU and peripheral devices, such as memory. 11 | class CPUBundle extends Bundle { 12 | val instruction_address = Output(UInt(Parameters.AddrWidth)) 13 | val instruction = Input(UInt(Parameters.DataWidth)) 14 | val memory_bundle = Flipped(new RAMBundle) 15 | val instruction_valid = Input(Bool()) 16 | val deviceSelect = Output(UInt(Parameters.SlaveDeviceCountBits.W)) 17 | val debug_read_address = Input(UInt(Parameters.PhysicalRegisterAddrWidth)) 18 | val debug_read_data = Output(UInt(Parameters.DataWidth)) 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/riscv/Parameters.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv 5 | 6 | import chisel3._ 7 | import chisel3.util._ 8 | 9 | object ImplementationType { 10 | val ThreeStage = 0 11 | val FiveStage = 1 12 | } 13 | 14 | object Parameters { 15 | val AddrBits = 32 16 | val AddrWidth = AddrBits.W 17 | 18 | val InstructionBits = 32 19 | val InstructionWidth = InstructionBits.W 20 | val DataBits = 32 21 | val DataWidth = DataBits.W 22 | val ByteBits = 8 23 | val ByteWidth = ByteBits.W 24 | val WordSize = Math.ceil(DataBits / ByteBits).toInt 25 | 26 | val PhysicalRegisters = 32 27 | val PhysicalRegisterAddrBits = log2Up(PhysicalRegisters) 28 | val PhysicalRegisterAddrWidth = PhysicalRegisterAddrBits.W 29 | 30 | val CSRRegisterAddrBits = 12 31 | val CSRRegisterAddrWidth = CSRRegisterAddrBits.W 32 | 33 | val InterruptFlagBits = 32 34 | val InterruptFlagWidth = InterruptFlagBits.W 35 | 36 | val HoldStateBits = 3 37 | val StallStateWidth = HoldStateBits.W 38 | 39 | val MemorySizeInBytes = 32768 40 | val MemorySizeInWords = MemorySizeInBytes / 4 41 | 42 | val EntryAddress = 0x1000.U(Parameters.AddrWidth) 43 | 44 | val MasterDeviceCount = 1 45 | val SlaveDeviceCount = 8 46 | val SlaveDeviceCountBits = log2Up(Parameters.SlaveDeviceCount) 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/riscv/core/ALU.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.core 5 | 6 | import chisel3._ 7 | import chisel3.experimental.ChiselEnum 8 | import chisel3.util._ 9 | import riscv.Parameters 10 | 11 | object ALUFunctions extends ChiselEnum { 12 | val zero, add, sub, sll, slt, xor, or, and, srl, sra, sltu = Value 13 | } 14 | 15 | class ALU extends Module { 16 | val io = IO(new Bundle { 17 | val func = Input(ALUFunctions()) 18 | 19 | val op1 = Input(UInt(Parameters.DataWidth)) 20 | val op2 = Input(UInt(Parameters.DataWidth)) 21 | 22 | val result = Output(UInt(Parameters.DataWidth)) 23 | }) 24 | 25 | io.result := 0.U 26 | switch(io.func) { 27 | is(ALUFunctions.add) { 28 | io.result := io.op1 + io.op2 29 | } 30 | is(ALUFunctions.sub) { 31 | io.result := io.op1 - io.op2 32 | } 33 | is(ALUFunctions.sll) { 34 | io.result := io.op1 << io.op2(4, 0) 35 | } 36 | is(ALUFunctions.slt) { 37 | io.result := io.op1.asSInt < io.op2.asSInt 38 | } 39 | is(ALUFunctions.xor) { 40 | io.result := io.op1 ^ io.op2 41 | } 42 | is(ALUFunctions.or) { 43 | io.result := io.op1 | io.op2 44 | } 45 | is(ALUFunctions.and) { 46 | io.result := io.op1 & io.op2 47 | } 48 | is(ALUFunctions.srl) { 49 | io.result := io.op1 >> io.op2(4, 0) 50 | } 51 | is(ALUFunctions.sra) { 52 | io.result := (io.op1.asSInt >> io.op2(4, 0)).asUInt 53 | } 54 | is(ALUFunctions.sltu) { 55 | io.result := io.op1 < io.op2 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/scala/riscv/core/ALUControl.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.core 5 | 6 | import chisel3._ 7 | import chisel3.util._ 8 | 9 | class ALUControl extends Module { 10 | val io = IO(new Bundle { 11 | val opcode = Input(UInt(7.W)) 12 | val funct3 = Input(UInt(3.W)) 13 | val funct7 = Input(UInt(7.W)) 14 | 15 | val alu_funct = Output(ALUFunctions()) 16 | }) 17 | 18 | io.alu_funct := ALUFunctions.zero 19 | 20 | switch(io.opcode) { 21 | is(InstructionTypes.I) { 22 | io.alu_funct := MuxLookup( 23 | io.funct3, 24 | ALUFunctions.zero, 25 | IndexedSeq( 26 | InstructionsTypeI.addi -> ALUFunctions.add, 27 | InstructionsTypeI.slli -> ALUFunctions.sll, 28 | InstructionsTypeI.slti -> ALUFunctions.slt, 29 | InstructionsTypeI.sltiu -> ALUFunctions.sltu, 30 | InstructionsTypeI.xori -> ALUFunctions.xor, 31 | InstructionsTypeI.ori -> ALUFunctions.or, 32 | InstructionsTypeI.andi -> ALUFunctions.and, 33 | InstructionsTypeI.sri -> Mux(io.funct7(5), ALUFunctions.sra, ALUFunctions.srl) 34 | ), 35 | ) 36 | } 37 | is(InstructionTypes.RM) { 38 | io.alu_funct := MuxLookup( 39 | io.funct3, 40 | ALUFunctions.zero, 41 | IndexedSeq( 42 | InstructionsTypeR.add_sub -> Mux(io.funct7(5), ALUFunctions.sub, ALUFunctions.add), 43 | InstructionsTypeR.sll -> ALUFunctions.sll, 44 | InstructionsTypeR.slt -> ALUFunctions.slt, 45 | InstructionsTypeR.sltu -> ALUFunctions.sltu, 46 | InstructionsTypeR.xor -> ALUFunctions.xor, 47 | InstructionsTypeR.or -> ALUFunctions.or, 48 | InstructionsTypeR.and -> ALUFunctions.and, 49 | InstructionsTypeR.sr -> Mux(io.funct7(5), ALUFunctions.sra, ALUFunctions.srl) 50 | ), 51 | ) 52 | } 53 | is(InstructionTypes.B) { 54 | io.alu_funct := ALUFunctions.add 55 | } 56 | is(InstructionTypes.L) { 57 | io.alu_funct := ALUFunctions.add 58 | } 59 | is(InstructionTypes.S) { 60 | io.alu_funct := ALUFunctions.add 61 | } 62 | is(Instructions.jal) { 63 | io.alu_funct := ALUFunctions.add 64 | } 65 | is(Instructions.jalr) { 66 | io.alu_funct := ALUFunctions.add 67 | } 68 | is(Instructions.lui) { 69 | io.alu_funct := ALUFunctions.add 70 | } 71 | is(Instructions.auipc) { 72 | io.alu_funct := ALUFunctions.add 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/scala/riscv/core/CPU.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.core 5 | 6 | import chisel3._ 7 | import chisel3.util.Cat 8 | import riscv.CPUBundle 9 | import riscv.Parameters 10 | 11 | class CPU extends Module { 12 | val io = IO(new CPUBundle) 13 | 14 | val regs = Module(new RegisterFile) 15 | val inst_fetch = Module(new InstructionFetch) 16 | val id = Module(new InstructionDecode) 17 | val ex = Module(new Execute) 18 | val mem = Module(new MemoryAccess) 19 | val wb = Module(new WriteBack) 20 | 21 | io.deviceSelect := mem.io.memory_bundle 22 | .address(Parameters.AddrBits - 1, Parameters.AddrBits - Parameters.SlaveDeviceCountBits) 23 | 24 | inst_fetch.io.jump_address_id := ex.io.if_jump_address 25 | inst_fetch.io.jump_flag_id := ex.io.if_jump_flag 26 | inst_fetch.io.instruction_valid := io.instruction_valid 27 | inst_fetch.io.instruction_read_data := io.instruction 28 | io.instruction_address := inst_fetch.io.instruction_address 29 | 30 | regs.io.write_enable := id.io.reg_write_enable 31 | regs.io.write_address := id.io.reg_write_address 32 | regs.io.write_data := wb.io.regs_write_data 33 | regs.io.read_address1 := id.io.regs_reg1_read_address 34 | regs.io.read_address2 := id.io.regs_reg2_read_address 35 | 36 | regs.io.debug_read_address := io.debug_read_address 37 | io.debug_read_data := regs.io.debug_read_data 38 | 39 | id.io.instruction := inst_fetch.io.instruction 40 | 41 | // lab3(cpu) begin 42 | 43 | // lab3(cpu) end 44 | 45 | mem.io.alu_result := ex.io.mem_alu_result 46 | mem.io.reg2_data := regs.io.read_data2 47 | mem.io.memory_read_enable := id.io.memory_read_enable 48 | mem.io.memory_write_enable := id.io.memory_write_enable 49 | mem.io.funct3 := inst_fetch.io.instruction(14, 12) 50 | 51 | io.memory_bundle.address := Cat( 52 | 0.U(Parameters.SlaveDeviceCountBits.W), 53 | mem.io.memory_bundle.address(Parameters.AddrBits - 1 - Parameters.SlaveDeviceCountBits, 0) 54 | ) 55 | io.memory_bundle.write_enable := mem.io.memory_bundle.write_enable 56 | io.memory_bundle.write_data := mem.io.memory_bundle.write_data 57 | io.memory_bundle.write_strobe := mem.io.memory_bundle.write_strobe 58 | mem.io.memory_bundle.read_data := io.memory_bundle.read_data 59 | 60 | wb.io.instruction_address := inst_fetch.io.instruction_address 61 | wb.io.alu_result := ex.io.mem_alu_result 62 | wb.io.memory_read_data := mem.io.wb_memory_read_data 63 | wb.io.regs_write_source := id.io.wb_reg_write_source 64 | } 65 | -------------------------------------------------------------------------------- /src/main/scala/riscv/core/Execute.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.core 5 | 6 | import chisel3._ 7 | import chisel3.util.Cat 8 | import chisel3.util.MuxLookup 9 | import riscv.Parameters 10 | 11 | class Execute extends Module { 12 | val io = IO(new Bundle { 13 | val instruction = Input(UInt(Parameters.InstructionWidth)) 14 | val instruction_address = Input(UInt(Parameters.AddrWidth)) 15 | val reg1_data = Input(UInt(Parameters.DataWidth)) 16 | val reg2_data = Input(UInt(Parameters.DataWidth)) 17 | val immediate = Input(UInt(Parameters.DataWidth)) 18 | val aluop1_source = Input(UInt(1.W)) 19 | val aluop2_source = Input(UInt(1.W)) 20 | 21 | val mem_alu_result = Output(UInt(Parameters.DataWidth)) 22 | val if_jump_flag = Output(Bool()) 23 | val if_jump_address = Output(UInt(Parameters.DataWidth)) 24 | }) 25 | 26 | val opcode = io.instruction(6, 0) 27 | val funct3 = io.instruction(14, 12) 28 | val funct7 = io.instruction(31, 25) 29 | val rd = io.instruction(11, 7) 30 | val uimm = io.instruction(19, 15) 31 | 32 | val alu = Module(new ALU) 33 | val alu_ctrl = Module(new ALUControl) 34 | 35 | alu_ctrl.io.opcode := opcode 36 | alu_ctrl.io.funct3 := funct3 37 | alu_ctrl.io.funct7 := funct7 38 | 39 | // lab3(Execute) begin 40 | 41 | // lab3(Execute) end 42 | 43 | io.mem_alu_result := alu.io.result 44 | io.if_jump_flag := opcode === Instructions.jal || 45 | (opcode === Instructions.jalr) || 46 | (opcode === InstructionTypes.B) && MuxLookup( 47 | funct3, 48 | false.B, 49 | IndexedSeq( 50 | InstructionsTypeB.beq -> (io.reg1_data === io.reg2_data), 51 | InstructionsTypeB.bne -> (io.reg1_data =/= io.reg2_data), 52 | InstructionsTypeB.blt -> (io.reg1_data.asSInt < io.reg2_data.asSInt), 53 | InstructionsTypeB.bge -> (io.reg1_data.asSInt >= io.reg2_data.asSInt), 54 | InstructionsTypeB.bltu -> (io.reg1_data.asUInt < io.reg2_data.asUInt), 55 | InstructionsTypeB.bgeu -> (io.reg1_data.asUInt >= io.reg2_data.asUInt) 56 | ) 57 | ) 58 | io.if_jump_address := io.immediate + Mux(opcode === Instructions.jalr, io.reg1_data, io.instruction_address) 59 | } 60 | -------------------------------------------------------------------------------- /src/main/scala/riscv/core/InstructionDecode.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.core 5 | 6 | import scala.collection.immutable.ArraySeq 7 | 8 | import chisel3._ 9 | import chisel3.util._ 10 | import riscv.Parameters 11 | 12 | object InstructionTypes { 13 | val L = "b0000011".U 14 | val I = "b0010011".U 15 | val S = "b0100011".U 16 | val RM = "b0110011".U 17 | val B = "b1100011".U 18 | } 19 | 20 | object Instructions { 21 | val lui = "b0110111".U 22 | val nop = "b0000001".U 23 | val jal = "b1101111".U 24 | val jalr = "b1100111".U 25 | val auipc = "b0010111".U 26 | val csr = "b1110011".U 27 | val fence = "b0001111".U 28 | } 29 | 30 | object InstructionsTypeL { 31 | val lb = "b000".U 32 | val lh = "b001".U 33 | val lw = "b010".U 34 | val lbu = "b100".U 35 | val lhu = "b101".U 36 | } 37 | 38 | object InstructionsTypeI { 39 | val addi = 0.U 40 | val slli = 1.U 41 | val slti = 2.U 42 | val sltiu = 3.U 43 | val xori = 4.U 44 | val sri = 5.U 45 | val ori = 6.U 46 | val andi = 7.U 47 | } 48 | 49 | object InstructionsTypeS { 50 | val sb = "b000".U 51 | val sh = "b001".U 52 | val sw = "b010".U 53 | } 54 | 55 | object InstructionsTypeR { 56 | val add_sub = 0.U 57 | val sll = 1.U 58 | val slt = 2.U 59 | val sltu = 3.U 60 | val xor = 4.U 61 | val sr = 5.U 62 | val or = 6.U 63 | val and = 7.U 64 | } 65 | 66 | object InstructionsTypeM { 67 | val mul = 0.U 68 | val mulh = 1.U 69 | val mulhsu = 2.U 70 | val mulhum = 3.U 71 | val div = 4.U 72 | val divu = 5.U 73 | val rem = 6.U 74 | val remu = 7.U 75 | } 76 | 77 | object InstructionsTypeB { 78 | val beq = "b000".U 79 | val bne = "b001".U 80 | val blt = "b100".U 81 | val bge = "b101".U 82 | val bltu = "b110".U 83 | val bgeu = "b111".U 84 | } 85 | 86 | object InstructionsTypeCSR { 87 | val csrrw = "b001".U 88 | val csrrs = "b010".U 89 | val csrrc = "b011".U 90 | val csrrwi = "b101".U 91 | val csrrsi = "b110".U 92 | val csrrci = "b111".U 93 | } 94 | 95 | object InstructionsNop { 96 | val nop = 0x00000013L.U(Parameters.DataWidth) 97 | } 98 | 99 | object InstructionsRet { 100 | val mret = 0x30200073L.U(Parameters.DataWidth) 101 | val ret = 0x00008067L.U(Parameters.DataWidth) 102 | } 103 | 104 | object InstructionsEnv { 105 | val ecall = 0x00000073L.U(Parameters.DataWidth) 106 | val ebreak = 0x00100073L.U(Parameters.DataWidth) 107 | } 108 | 109 | object ALUOp1Source { 110 | val Register = 0.U(1.W) 111 | val InstructionAddress = 1.U(1.W) 112 | } 113 | 114 | object ALUOp2Source { 115 | val Register = 0.U(1.W) 116 | val Immediate = 1.U(1.W) 117 | } 118 | 119 | object RegWriteSource { 120 | val ALUResult = 0.U(2.W) 121 | val Memory = 1.U(2.W) 122 | // val CSR = 2.U(2.W) 123 | val NextInstructionAddress = 3.U(2.W) 124 | } 125 | 126 | class InstructionDecode extends Module { 127 | val io = IO(new Bundle { 128 | val instruction = Input(UInt(Parameters.InstructionWidth)) 129 | 130 | val regs_reg1_read_address = Output(UInt(Parameters.PhysicalRegisterAddrWidth)) 131 | val regs_reg2_read_address = Output(UInt(Parameters.PhysicalRegisterAddrWidth)) 132 | val ex_immediate = Output(UInt(Parameters.DataWidth)) 133 | val ex_aluop1_source = Output(UInt(1.W)) 134 | val ex_aluop2_source = Output(UInt(1.W)) 135 | val memory_read_enable = Output(Bool()) 136 | val memory_write_enable = Output(Bool()) 137 | val wb_reg_write_source = Output(UInt(2.W)) 138 | val reg_write_enable = Output(Bool()) 139 | val reg_write_address = Output(UInt(Parameters.PhysicalRegisterAddrWidth)) 140 | }) 141 | val opcode = io.instruction(6, 0) 142 | val funct3 = io.instruction(14, 12) 143 | val funct7 = io.instruction(31, 25) 144 | val rd = io.instruction(11, 7) 145 | val rs1 = io.instruction(19, 15) 146 | val rs2 = io.instruction(24, 20) 147 | 148 | io.regs_reg1_read_address := Mux(opcode === Instructions.lui, 0.U(Parameters.PhysicalRegisterAddrWidth), rs1) 149 | io.regs_reg2_read_address := rs2 150 | val immediate = MuxLookup( 151 | opcode, 152 | Cat(Fill(20, io.instruction(31)), io.instruction(31, 20)), 153 | IndexedSeq( 154 | InstructionTypes.I -> Cat(Fill(21, io.instruction(31)), io.instruction(30, 20)), 155 | InstructionTypes.L -> Cat(Fill(21, io.instruction(31)), io.instruction(30, 20)), 156 | Instructions.jalr -> Cat(Fill(21, io.instruction(31)), io.instruction(30, 20)), 157 | InstructionTypes.S -> Cat(Fill(21, io.instruction(31)), io.instruction(30, 25), io.instruction(11, 7)), 158 | InstructionTypes.B -> Cat( 159 | Fill(20, io.instruction(31)), 160 | io.instruction(7), 161 | io.instruction(30, 25), 162 | io.instruction(11, 8), 163 | 0.U(1.W) 164 | ), 165 | Instructions.lui -> Cat(io.instruction(31, 12), 0.U(12.W)), 166 | Instructions.auipc -> Cat(io.instruction(31, 12), 0.U(12.W)), 167 | // jal's imm represents a multiple of 2 bytes. 168 | Instructions.jal -> Cat( 169 | Fill(12, io.instruction(31)), 170 | io.instruction(19, 12), 171 | io.instruction(20), 172 | io.instruction(30, 21), 173 | 0.U(1.W) 174 | ) 175 | ) 176 | ) 177 | io.ex_immediate := immediate 178 | io.ex_aluop1_source := Mux( 179 | opcode === Instructions.auipc || opcode === InstructionTypes.B || opcode === Instructions.jal, 180 | ALUOp1Source.InstructionAddress, 181 | ALUOp1Source.Register 182 | ) 183 | 184 | // ALU op2 from reg: R-type, 185 | // ALU op2 from imm: L-Type (I-type subtype), 186 | // I-type (nop=addi, jalr, csr-class, fence), 187 | // J-type (jal), 188 | // U-type (lui, auipc), 189 | // S-type (rs2 value sent to MemControl, ALU computes rs1 + imm.) 190 | // B-type (rs2 compares with rs1 in jump judge unit, ALU computes jump address PC+imm.) 191 | io.ex_aluop2_source := Mux( 192 | opcode === InstructionTypes.RM, 193 | ALUOp2Source.Register, 194 | ALUOp2Source.Immediate 195 | ) 196 | 197 | // lab3(InstructionDecode) begin 198 | 199 | // lab3(InstructionDecode) end 200 | 201 | io.wb_reg_write_source := MuxCase( 202 | RegWriteSource.ALUResult, 203 | ArraySeq( 204 | (opcode === InstructionTypes.RM || opcode === InstructionTypes.I || 205 | opcode === Instructions.lui || opcode === Instructions.auipc) -> RegWriteSource.ALUResult, // same as default 206 | (opcode === InstructionTypes.L) -> RegWriteSource.Memory, 207 | (opcode === Instructions.jal || opcode === Instructions.jalr) -> RegWriteSource.NextInstructionAddress 208 | ) 209 | ) 210 | 211 | io.reg_write_enable := (opcode === InstructionTypes.RM) || (opcode === InstructionTypes.I) || 212 | (opcode === InstructionTypes.L) || (opcode === Instructions.auipc) || (opcode === Instructions.lui) || 213 | (opcode === Instructions.jal) || (opcode === Instructions.jalr) 214 | io.reg_write_address := rd 215 | } 216 | -------------------------------------------------------------------------------- /src/main/scala/riscv/core/InstructionFetch.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.core 5 | 6 | import chisel3._ 7 | import riscv.Parameters 8 | 9 | object ProgramCounter { 10 | val EntryAddress = Parameters.EntryAddress 11 | } 12 | 13 | class InstructionFetch extends Module { 14 | val io = IO(new Bundle { 15 | val jump_flag_id = Input(Bool()) 16 | val jump_address_id = Input(UInt(Parameters.AddrWidth)) 17 | val instruction_read_data = Input(UInt(Parameters.DataWidth)) 18 | val instruction_valid = Input(Bool()) 19 | 20 | val instruction_address = Output(UInt(Parameters.AddrWidth)) 21 | val instruction = Output(UInt(Parameters.InstructionWidth)) 22 | }) 23 | val pc = RegInit(ProgramCounter.EntryAddress) 24 | 25 | when(io.instruction_valid) { 26 | io.instruction := io.instruction_read_data 27 | // lab3(InstructionFetch) begin 28 | 29 | // lab3(InstructionFetch) end 30 | 31 | }.otherwise { 32 | pc := pc 33 | io.instruction := 0x00000013.U 34 | } 35 | io.instruction_address := pc 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/riscv/core/MemoryAccess.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.core 5 | 6 | import chisel3._ 7 | import chisel3.util._ 8 | import peripheral.RAMBundle 9 | import riscv.Parameters 10 | 11 | class MemoryAccess extends Module { 12 | val io = IO(new Bundle() { 13 | val alu_result = Input(UInt(Parameters.DataWidth)) 14 | val reg2_data = Input(UInt(Parameters.DataWidth)) 15 | val memory_read_enable = Input(Bool()) 16 | val memory_write_enable = Input(Bool()) 17 | val funct3 = Input(UInt(3.W)) 18 | 19 | val wb_memory_read_data = Output(UInt(Parameters.DataWidth)) 20 | 21 | val memory_bundle = Flipped(new RAMBundle) 22 | }) 23 | val mem_address_index = io.alu_result(log2Up(Parameters.WordSize) - 1, 0).asUInt 24 | 25 | io.memory_bundle.write_enable := false.B 26 | io.memory_bundle.write_data := 0.U 27 | io.memory_bundle.address := io.alu_result 28 | io.memory_bundle.write_strobe := VecInit(Seq.fill(Parameters.WordSize)(false.B)) 29 | io.wb_memory_read_data := 0.U 30 | 31 | when(io.memory_read_enable) { 32 | val data = io.memory_bundle.read_data 33 | io.wb_memory_read_data := MuxLookup( 34 | io.funct3, 35 | 0.U, 36 | IndexedSeq( 37 | InstructionsTypeL.lb -> MuxLookup( 38 | mem_address_index, 39 | Cat(Fill(24, data(31)), data(31, 24)), 40 | IndexedSeq( 41 | 0.U -> Cat(Fill(24, data(7)), data(7, 0)), 42 | 1.U -> Cat(Fill(24, data(15)), data(15, 8)), 43 | 2.U -> Cat(Fill(24, data(23)), data(23, 16)) 44 | ) 45 | ), 46 | InstructionsTypeL.lbu -> MuxLookup( 47 | mem_address_index, 48 | Cat(Fill(24, 0.U), data(31, 24)), 49 | IndexedSeq( 50 | 0.U -> Cat(Fill(24, 0.U), data(7, 0)), 51 | 1.U -> Cat(Fill(24, 0.U), data(15, 8)), 52 | 2.U -> Cat(Fill(24, 0.U), data(23, 16)) 53 | ) 54 | ), 55 | InstructionsTypeL.lh -> Mux( 56 | mem_address_index === 0.U, 57 | Cat(Fill(16, data(15)), data(15, 0)), 58 | Cat(Fill(16, data(31)), data(31, 16)) 59 | ), 60 | InstructionsTypeL.lhu -> Mux( 61 | mem_address_index === 0.U, 62 | Cat(Fill(16, 0.U), data(15, 0)), 63 | Cat(Fill(16, 0.U), data(31, 16)) 64 | ), 65 | InstructionsTypeL.lw -> data 66 | ) 67 | ) 68 | }.elsewhen(io.memory_write_enable) { 69 | io.memory_bundle.write_data := io.reg2_data 70 | io.memory_bundle.write_enable := true.B 71 | io.memory_bundle.write_strobe := VecInit(Seq.fill(Parameters.WordSize)(false.B)) 72 | when(io.funct3 === InstructionsTypeS.sb) { 73 | io.memory_bundle.write_strobe(mem_address_index) := true.B 74 | io.memory_bundle.write_data := io.reg2_data(Parameters.ByteBits, 0) << (mem_address_index << log2Up( 75 | Parameters.ByteBits 76 | ).U) 77 | }.elsewhen(io.funct3 === InstructionsTypeS.sh) { 78 | when(mem_address_index === 0.U) { 79 | for (i <- 0 until Parameters.WordSize / 2) { 80 | io.memory_bundle.write_strobe(i) := true.B 81 | } 82 | io.memory_bundle.write_data := io.reg2_data(Parameters.WordSize / 2 * Parameters.ByteBits, 0) 83 | }.otherwise { 84 | for (i <- Parameters.WordSize / 2 until Parameters.WordSize) { 85 | io.memory_bundle.write_strobe(i) := true.B 86 | } 87 | io.memory_bundle.write_data := io.reg2_data( 88 | Parameters.WordSize / 2 * Parameters.ByteBits, 89 | 0 90 | ) << (Parameters.WordSize / 2 * Parameters.ByteBits) 91 | } 92 | }.elsewhen(io.funct3 === InstructionsTypeS.sw) { 93 | for (i <- 0 until Parameters.WordSize) { 94 | io.memory_bundle.write_strobe(i) := true.B 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/scala/riscv/core/RegisterFile.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.core 5 | 6 | import chisel3._ 7 | import riscv.Parameters 8 | 9 | object Registers extends Enumeration { 10 | type Register = Value 11 | val zero, ra, sp, gp, tp, t0, t1, t2, fp, s1, a0, a1, a2, a3, a4, a5, a6, a7, s2, s3, s4, s5, s6, s7, s8, s9, s10, 12 | s11, t3, t4, t5, t6 = Value 13 | } 14 | 15 | class RegisterFile extends Module { 16 | val io = IO(new Bundle { 17 | val write_enable = Input(Bool()) 18 | val write_address = Input(UInt(Parameters.PhysicalRegisterAddrWidth)) 19 | val write_data = Input(UInt(Parameters.DataWidth)) 20 | 21 | val read_address1 = Input(UInt(Parameters.PhysicalRegisterAddrWidth)) 22 | val read_address2 = Input(UInt(Parameters.PhysicalRegisterAddrWidth)) 23 | val read_data1 = Output(UInt(Parameters.DataWidth)) 24 | val read_data2 = Output(UInt(Parameters.DataWidth)) 25 | 26 | val debug_read_address = Input(UInt(Parameters.PhysicalRegisterAddrWidth)) 27 | val debug_read_data = Output(UInt(Parameters.DataWidth)) 28 | }) 29 | val registers = RegInit(VecInit(Seq.fill(Parameters.PhysicalRegisters)(0.U(Parameters.DataWidth)))) 30 | 31 | when(!reset.asBool) { 32 | when(io.write_enable && io.write_address =/= 0.U) { 33 | registers(io.write_address) := io.write_data 34 | } 35 | } 36 | 37 | io.read_data1 := Mux( 38 | io.read_address1 === 0.U, 39 | 0.U, 40 | registers(io.read_address1) 41 | ) 42 | 43 | io.read_data2 := Mux( 44 | io.read_address2 === 0.U, 45 | 0.U, 46 | registers(io.read_address2) 47 | ) 48 | 49 | io.debug_read_data := Mux( 50 | io.debug_read_address === 0.U, 51 | 0.U, 52 | registers(io.debug_read_address) 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /src/main/scala/riscv/core/WriteBack.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.core 5 | 6 | import chisel3._ 7 | import chisel3.util._ 8 | import riscv.Parameters 9 | 10 | class WriteBack extends Module { 11 | val io = IO(new Bundle() { 12 | val instruction_address = Input(UInt(Parameters.AddrWidth)) 13 | val alu_result = Input(UInt(Parameters.DataWidth)) 14 | val memory_read_data = Input(UInt(Parameters.DataWidth)) 15 | val regs_write_source = Input(UInt(2.W)) 16 | val regs_write_data = Output(UInt(Parameters.DataWidth)) 17 | }) 18 | io.regs_write_data := MuxLookup( 19 | io.regs_write_source, 20 | io.alu_result, 21 | IndexedSeq( 22 | RegWriteSource.Memory -> io.memory_read_data, 23 | RegWriteSource.NextInstructionAddress -> (io.instruction_address + 4.U) 24 | ) 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/test/scala/riscv/TestAnnotations.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv 5 | 6 | import java.nio.file.Files 7 | import java.nio.file.Paths 8 | 9 | import chiseltest.VerilatorBackendAnnotation 10 | import chiseltest.WriteVcdAnnotation 11 | import firrtl.AnnotationSeq 12 | 13 | object VerilatorEnabler { 14 | val annos: AnnotationSeq = if (sys.env.contains("Path")) { 15 | if ( 16 | sys.env 17 | .getOrElse("Path", "") 18 | .split(";") 19 | .exists(path => { 20 | Files.exists(Paths.get(path, "verilator")) 21 | }) 22 | ) { 23 | Seq(VerilatorBackendAnnotation) 24 | } else { 25 | Seq() 26 | } 27 | } else { 28 | if ( 29 | sys.env 30 | .getOrElse("PATH", "") 31 | .split(":") 32 | .exists(path => { 33 | Files.exists(Paths.get(path, "verilator")) 34 | }) 35 | ) { 36 | Seq(VerilatorBackendAnnotation) 37 | } else { 38 | Seq() 39 | } 40 | } 41 | } 42 | 43 | object WriteVcdEnabler { 44 | val annos: AnnotationSeq = if (sys.env.contains("WRITE_VCD")) { 45 | Seq(WriteVcdAnnotation) 46 | } else { 47 | Seq() 48 | } 49 | } 50 | 51 | object TestAnnotations { 52 | val annos = VerilatorEnabler.annos ++ WriteVcdEnabler.annos 53 | } 54 | -------------------------------------------------------------------------------- /src/test/scala/riscv/singlecycle/CPUTest.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.singlecycle 5 | 6 | import java.nio.ByteBuffer 7 | import java.nio.ByteOrder 8 | 9 | import chisel3._ 10 | import chiseltest._ 11 | import org.scalatest.flatspec.AnyFlatSpec 12 | import peripheral.InstructionROM 13 | import peripheral.Memory 14 | import peripheral.ROMLoader 15 | import riscv.core.CPU 16 | import riscv.core.ProgramCounter 17 | import riscv.Parameters 18 | import riscv.TestAnnotations 19 | 20 | class TestTopModule(exeFilename: String) extends Module { 21 | val io = IO(new Bundle { 22 | val mem_debug_read_address = Input(UInt(Parameters.AddrWidth)) 23 | val regs_debug_read_address = Input(UInt(Parameters.PhysicalRegisterAddrWidth)) 24 | val regs_debug_read_data = Output(UInt(Parameters.DataWidth)) 25 | val mem_debug_read_data = Output(UInt(Parameters.DataWidth)) 26 | }) 27 | 28 | val mem = Module(new Memory(8192)) 29 | val instruction_rom = Module(new InstructionROM(exeFilename)) 30 | val rom_loader = Module(new ROMLoader(instruction_rom.capacity)) 31 | 32 | rom_loader.io.rom_data := instruction_rom.io.data 33 | rom_loader.io.load_address := Parameters.EntryAddress 34 | instruction_rom.io.address := rom_loader.io.rom_address 35 | 36 | val CPU_clkdiv = RegInit(UInt(2.W), 0.U) 37 | val CPU_tick = Wire(Bool()) 38 | val CPU_next = Wire(UInt(2.W)) 39 | CPU_next := Mux(CPU_clkdiv === 3.U, 0.U, CPU_clkdiv + 1.U) 40 | CPU_tick := CPU_clkdiv === 0.U 41 | CPU_clkdiv := CPU_next 42 | 43 | withClock(CPU_tick.asClock) { 44 | val cpu = Module(new CPU) 45 | cpu.io.debug_read_address := 0.U 46 | cpu.io.instruction_valid := rom_loader.io.load_finished 47 | mem.io.instruction_address := cpu.io.instruction_address 48 | cpu.io.instruction := mem.io.instruction 49 | 50 | when(!rom_loader.io.load_finished) { 51 | rom_loader.io.bundle <> mem.io.bundle 52 | cpu.io.memory_bundle.read_data := 0.U 53 | }.otherwise { 54 | rom_loader.io.bundle.read_data := 0.U 55 | cpu.io.memory_bundle <> mem.io.bundle 56 | } 57 | 58 | cpu.io.debug_read_address := io.regs_debug_read_address 59 | io.regs_debug_read_data := cpu.io.debug_read_data 60 | } 61 | 62 | mem.io.debug_read_address := io.mem_debug_read_address 63 | io.mem_debug_read_data := mem.io.debug_read_data 64 | } 65 | 66 | class FibonacciTest extends AnyFlatSpec with ChiselScalatestTester { 67 | behavior.of("Single Cycle CPU") 68 | it should "recursively calculate Fibonacci(10)" in { 69 | test(new TestTopModule("fibonacci.asmbin")).withAnnotations(TestAnnotations.annos) { c => 70 | for (i <- 1 to 50) { 71 | c.clock.step(1000) 72 | c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout 73 | } 74 | 75 | c.io.mem_debug_read_address.poke(4.U) 76 | c.clock.step() 77 | c.io.mem_debug_read_data.expect(55.U) 78 | } 79 | } 80 | } 81 | 82 | class QuicksortTest extends AnyFlatSpec with ChiselScalatestTester { 83 | behavior.of("Single Cycle CPU") 84 | it should "perform a quicksort on 10 numbers" in { 85 | test(new TestTopModule("quicksort.asmbin")).withAnnotations(TestAnnotations.annos) { c => 86 | for (i <- 1 to 50) { 87 | c.clock.step(1000) 88 | c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout 89 | } 90 | for (i <- 1 to 10) { 91 | c.io.mem_debug_read_address.poke((4 * i).U) 92 | c.clock.step() 93 | c.io.mem_debug_read_data.expect((i - 1).U) 94 | } 95 | } 96 | } 97 | } 98 | 99 | class ByteAccessTest extends AnyFlatSpec with ChiselScalatestTester { 100 | behavior.of("Single Cycle CPU") 101 | it should "store and load a single byte" in { 102 | test(new TestTopModule("sb.asmbin")).withAnnotations(TestAnnotations.annos) { c => 103 | for (i <- 1 to 500) { 104 | c.clock.step() 105 | c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout 106 | } 107 | c.io.regs_debug_read_address.poke(5.U) // t0 108 | c.io.regs_debug_read_data.expect(0xdeadbeefL.U) 109 | c.io.regs_debug_read_address.poke(6.U) // t1 110 | c.io.regs_debug_read_data.expect(0xef.U) 111 | c.io.regs_debug_read_address.poke(1.U) // ra 112 | c.io.regs_debug_read_data.expect(0x15ef.U) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/scala/riscv/singlecycle/ExecuteTest.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.singlecycle 5 | 6 | import chisel3._ 7 | import chiseltest._ 8 | import org.scalatest.flatspec.AnyFlatSpec 9 | import riscv.core.ALUOp1Source 10 | import riscv.core.ALUOp2Source 11 | import riscv.core.Execute 12 | import riscv.core.InstructionTypes 13 | import riscv.TestAnnotations 14 | 15 | class ExecuteTest extends AnyFlatSpec with ChiselScalatestTester { 16 | behavior.of("Execution of Single Cycle CPU") 17 | it should "execute correctly" in { 18 | test(new Execute).withAnnotations(TestAnnotations.annos) { c => 19 | c.io.instruction.poke(0x001101b3L.U) // x3 = x2 + x1 20 | 21 | var x = 0 22 | for (x <- 0 to 100) { 23 | val op1 = scala.util.Random.nextInt(429496729) 24 | val op2 = scala.util.Random.nextInt(429496729) 25 | val result = op1 + op2 26 | val addr = scala.util.Random.nextInt(32) 27 | 28 | c.io.reg1_data.poke(op1.U) 29 | c.io.reg2_data.poke(op2.U) 30 | 31 | c.clock.step() 32 | c.io.mem_alu_result.expect(result.U) 33 | c.io.if_jump_flag.expect(0.U) 34 | } 35 | 36 | // beq test 37 | c.io.instruction.poke(0x00208163L.U) // pc + 2 if x1 === x2 38 | c.io.instruction_address.poke(2.U) 39 | c.io.immediate.poke(2.U) 40 | c.io.aluop1_source.poke(1.U) 41 | c.io.aluop2_source.poke(1.U) 42 | c.clock.step() 43 | 44 | // equ 45 | c.io.reg1_data.poke(9.U) 46 | c.io.reg2_data.poke(9.U) 47 | c.clock.step() 48 | c.io.if_jump_flag.expect(1.U) 49 | c.io.if_jump_address.expect(4.U) 50 | 51 | // not equ 52 | c.io.reg1_data.poke(9.U) 53 | c.io.reg2_data.poke(19.U) 54 | c.clock.step() 55 | c.io.if_jump_flag.expect(0.U) 56 | c.io.if_jump_address.expect(4.U) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/scala/riscv/singlecycle/InstructionDecoderTest.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.singlecycle 5 | 6 | import chisel3._ 7 | import chiseltest._ 8 | import org.scalatest.flatspec.AnyFlatSpec 9 | import riscv.core.ALUOp1Source 10 | import riscv.core.ALUOp2Source 11 | import riscv.core.InstructionDecode 12 | import riscv.core.InstructionTypes 13 | import riscv.TestAnnotations 14 | 15 | class InstructionDecoderTest extends AnyFlatSpec with ChiselScalatestTester { 16 | behavior.of("InstructionDecoder of Single Cycle CPU") 17 | it should "produce correct control signal" in { 18 | test(new InstructionDecode).withAnnotations(TestAnnotations.annos) { c => 19 | c.io.instruction.poke(0x00a02223L.U) // S-type 20 | c.io.ex_aluop1_source.expect(ALUOp1Source.Register) 21 | c.io.ex_aluop2_source.expect(ALUOp2Source.Immediate) 22 | c.io.regs_reg1_read_address.expect(0.U) 23 | c.io.regs_reg2_read_address.expect(10.U) 24 | c.clock.step() 25 | 26 | c.io.instruction.poke(0x000022b7L.U) // lui 27 | c.io.regs_reg1_read_address.expect(0.U) 28 | c.io.ex_aluop1_source.expect(ALUOp1Source.Register) 29 | c.io.ex_aluop2_source.expect(ALUOp2Source.Immediate) 30 | c.clock.step() 31 | 32 | c.io.instruction.poke(0x002081b3L.U) // add 33 | c.io.ex_aluop1_source.expect(ALUOp1Source.Register) 34 | c.io.ex_aluop2_source.expect(ALUOp2Source.Register) 35 | c.clock.step() 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/scala/riscv/singlecycle/InstructionFetchTest.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.singlecycle 5 | 6 | import scala.math.pow 7 | import scala.util.Random 8 | 9 | import chisel3._ 10 | import chiseltest._ 11 | import org.scalatest.flatspec.AnyFlatSpec 12 | import riscv.core.ALUOp1Source 13 | import riscv.core.ALUOp2Source 14 | import riscv.core.InstructionFetch 15 | import riscv.core.InstructionTypes 16 | import riscv.core.ProgramCounter 17 | import riscv.Parameters 18 | import riscv.TestAnnotations 19 | 20 | class InstructionFetchTest extends AnyFlatSpec with ChiselScalatestTester { 21 | behavior.of("InstructionFetch of Single Cycle CPU") 22 | it should "fetch instruction" in { 23 | test(new InstructionFetch).withAnnotations(TestAnnotations.annos) { c => 24 | val entry = 0x1000 25 | var pre = entry 26 | var cur = pre 27 | c.io.instruction_valid.poke(true.B) 28 | var x = 0 29 | for (x <- 0 to 100) { 30 | Random.nextInt(2) match { 31 | case 0 => // no jump 32 | cur = pre + 4 33 | c.io.jump_flag_id.poke(false.B) 34 | c.clock.step() 35 | c.io.instruction_address.expect(cur) 36 | pre = pre + 4 37 | case 1 => // jump 38 | c.io.jump_flag_id.poke(true.B) 39 | c.io.jump_address_id.poke(entry) 40 | c.clock.step() 41 | c.io.instruction_address.expect(entry) 42 | pre = entry 43 | } 44 | } 45 | 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/scala/riscv/singlecycle/RegisterFileTest.scala: -------------------------------------------------------------------------------- 1 | // mycpu is freely redistributable under the MIT License. See the file 2 | // "LICENSE" for information on usage and redistribution of this file. 3 | 4 | package riscv.singlecycle 5 | 6 | import chisel3._ 7 | import chiseltest._ 8 | import org.scalatest.flatspec.AnyFlatSpec 9 | import riscv.core.RegisterFile 10 | import riscv.TestAnnotations 11 | 12 | class RegisterFileTest extends AnyFlatSpec with ChiselScalatestTester { 13 | behavior.of("Register File of Single Cycle CPU") 14 | it should "read the written content" in { 15 | test(new RegisterFile).withAnnotations(TestAnnotations.annos) { c => 16 | timescope { 17 | c.io.write_enable.poke(true.B) 18 | c.io.write_address.poke(1.U) 19 | c.io.write_data.poke(0xdeadbeefL.U) 20 | c.clock.step() 21 | } 22 | c.io.read_address1.poke(1.U) 23 | c.io.read_data1.expect(0xdeadbeefL.U) 24 | } 25 | } 26 | 27 | it should "x0 always be zero" in { 28 | test(new RegisterFile).withAnnotations(TestAnnotations.annos) { c => 29 | timescope { 30 | c.io.write_enable.poke(true.B) 31 | c.io.write_address.poke(0.U) 32 | c.io.write_data.poke(0xdeadbeefL.U) 33 | c.clock.step() 34 | } 35 | c.io.read_address1.poke(0.U) 36 | c.io.read_data1.expect(0.U) 37 | } 38 | } 39 | 40 | it should "read the writing content" in { 41 | test(new RegisterFile).withAnnotations(TestAnnotations.annos) { c => 42 | timescope { 43 | c.io.read_address1.poke(2.U) 44 | c.io.read_data1.expect(0.U) 45 | c.io.write_enable.poke(true.B) 46 | c.io.write_address.poke(2.U) 47 | c.io.write_data.poke(0xdeadbeefL.U) 48 | c.clock.step() 49 | c.io.read_address1.poke(2.U) 50 | c.io.read_data1.expect(0xdeadbeefL.U) 51 | c.clock.step() 52 | } 53 | c.io.read_address1.poke(2.U) 54 | c.io.read_data1.expect(0xdeadbeefL.U) 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /verilog/verilator/sim_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "VTop.h" // From Verilating "top.v" 12 | 13 | class Memory 14 | { 15 | std::vector memory; 16 | 17 | public: 18 | Memory(size_t size) : memory(size, 0) {} 19 | uint32_t read(size_t address) 20 | { 21 | address = address / 4; 22 | if (address >= memory.size()) 23 | return 0; 24 | 25 | return memory[address]; 26 | } 27 | 28 | uint32_t readInst(size_t address) 29 | { 30 | address = address / 4; 31 | if (address >= memory.size()) 32 | return 0; 33 | 34 | return memory[address]; 35 | } 36 | 37 | void write(size_t address, uint32_t value, bool write_strobe[4]) 38 | { 39 | address = address / 4; 40 | uint32_t write_mask = 0; 41 | if (write_strobe[0]) 42 | write_mask |= 0x000000FF; 43 | if (write_strobe[1]) 44 | write_mask |= 0x0000FF00; 45 | if (write_strobe[2]) 46 | write_mask |= 0x00FF0000; 47 | if (write_strobe[3]) 48 | write_mask |= 0xFF000000; 49 | if (address >= memory.size()) 50 | return; 51 | memory[address] = 52 | (memory[address] & ~write_mask) | (value & write_mask); 53 | } 54 | 55 | void load_binary(std::string const &filename, size_t load_address = 0x1000) 56 | { 57 | std::ifstream file(filename, std::ios::binary); 58 | if (!file) 59 | throw std::runtime_error("Failed to open file " + filename); 60 | file.seekg(0, std::ios::end); 61 | size_t size = file.tellg(); 62 | if (load_address + size > memory.size() * 4) { 63 | throw std::runtime_error( 64 | "File " + filename + " is too large (File is " + 65 | std::to_string(size) + " bytes. Memory is " + 66 | std::to_string(memory.size() * 4 - load_address) + " bytes.)"); 67 | } 68 | file.seekg(0, std::ios::beg); 69 | for (int i = 0; i < size / 4; ++i) 70 | file.read(reinterpret_cast(&memory[i + load_address / 4]), 71 | sizeof(uint32_t)); 72 | } 73 | }; 74 | 75 | class VCDTracer 76 | { 77 | VerilatedVcdC *tfp = nullptr; 78 | 79 | public: 80 | void enable(std::string const &filename, VTop &top) 81 | { 82 | Verilated::traceEverOn(true); 83 | tfp = new VerilatedVcdC; 84 | top.trace(tfp, 99); 85 | tfp->open(filename.c_str()); 86 | tfp->set_time_resolution("1ps"); 87 | tfp->set_time_unit("1ns"); 88 | if (!tfp->isOpen()) 89 | throw std::runtime_error("Failed to open VCD dump file " + 90 | filename); 91 | } 92 | 93 | void dump(vluint64_t time) 94 | { 95 | if (tfp) 96 | tfp->dump(time); 97 | } 98 | 99 | ~VCDTracer() 100 | { 101 | if (tfp) { 102 | tfp->close(); 103 | delete tfp; 104 | } 105 | } 106 | }; 107 | 108 | uint32_t parse_number(std::string const &str) 109 | { 110 | if (str.size() > 2) { 111 | auto &&prefix = str.substr(0, 2); 112 | if (prefix == "0x" || prefix == "0X") 113 | return std::stoul(str.substr(2), nullptr, 16); 114 | } 115 | return std::stoul(str); 116 | } 117 | 118 | class Simulator 119 | { 120 | vluint64_t main_time = 0; 121 | vluint64_t max_sim_time = 10000; 122 | uint32_t halt_address = 0; 123 | size_t memory_words = 1024 * 1024; // 4MiB 124 | bool dump_vcd = false; 125 | std::unique_ptr top; 126 | std::unique_ptr vcd_tracer; 127 | std::unique_ptr memory; 128 | bool dump_signature = false; 129 | unsigned long signature_begin, signature_end; 130 | std::string signature_filename; 131 | std::string instruction_filename; 132 | 133 | public: 134 | void parse_args(std::vector const &args) 135 | { 136 | if (auto it = std::find(args.begin(), args.end(), "-halt"); 137 | it != args.end()) { 138 | halt_address = parse_number(*(it + 1)); 139 | } 140 | 141 | if (auto it = std::find(args.begin(), args.end(), "-memory"); 142 | it != args.end()) { 143 | memory_words = std::stoull(*(it + 1)); 144 | } 145 | 146 | if (auto it = std::find(args.begin(), args.end(), "-time"); 147 | it != args.end()) { 148 | max_sim_time = std::stoull(*(it + 1)); 149 | } 150 | 151 | if (auto it = std::find(args.begin(), args.end(), "-vcd"); 152 | it != args.end()) { 153 | vcd_tracer->enable(*(it + 1), *top); 154 | } 155 | 156 | if (auto it = std::find(args.begin(), args.end(), "-signature"); 157 | it != args.end()) { 158 | dump_signature = true; 159 | signature_begin = parse_number(*(it + 1)); 160 | signature_end = parse_number(*(it + 2)); 161 | signature_filename = *(it + 3); 162 | } 163 | 164 | if (auto it = std::find(args.begin(), args.end(), "-instruction"); 165 | it != args.end()) { 166 | instruction_filename = *(it + 1); 167 | } 168 | } 169 | 170 | Simulator(std::vector const &args) 171 | : top(std::make_unique()), 172 | vcd_tracer(std::make_unique()) 173 | { 174 | parse_args(args); 175 | memory = std::make_unique(memory_words); 176 | if (!instruction_filename.empty()) 177 | memory->load_binary(instruction_filename); 178 | 179 | std::cout << "-time " << max_sim_time << std::endl 180 | << "-memory " << memory_words << std::endl 181 | << "-instruction " << instruction_filename << std::endl; 182 | } 183 | 184 | void run() 185 | { 186 | top->reset = 1; 187 | top->clock = 0; 188 | top->eval(); 189 | vcd_tracer->dump(main_time); 190 | uint32_t data_memory_read_word = 0; 191 | uint32_t inst_memory_read_word = 0; 192 | uint32_t counter = 0; 193 | uint32_t clocktime = 1; 194 | bool memory_write_strobe[4] = {false}; 195 | std::cout << std::endl; 196 | int last_percentage = -1; 197 | while (main_time < max_sim_time && !Verilated::gotFinish()) { 198 | ++main_time; 199 | ++counter; 200 | if (counter > clocktime) { 201 | top->clock = !top->clock; 202 | counter = 0; 203 | } 204 | if (main_time > 2) 205 | top->reset = 0; 206 | 207 | top->io_instruction_valid = 1; 208 | top->io_memory_bundle_read_data = data_memory_read_word; 209 | top->io_instruction = inst_memory_read_word; 210 | top->clock = !top->clock; 211 | top->eval(); 212 | 213 | data_memory_read_word = memory->read(top->io_memory_bundle_address); 214 | 215 | inst_memory_read_word = 216 | memory->readInst(top->io_instruction_address); 217 | 218 | if (top->io_memory_bundle_write_enable) { 219 | memory_write_strobe[0] = top->io_memory_bundle_write_strobe_0; 220 | memory_write_strobe[1] = top->io_memory_bundle_write_strobe_1; 221 | memory_write_strobe[2] = top->io_memory_bundle_write_strobe_2; 222 | memory_write_strobe[3] = top->io_memory_bundle_write_strobe_3; 223 | memory->write(top->io_memory_bundle_address, 224 | top->io_memory_bundle_write_data, 225 | memory_write_strobe); 226 | } 227 | vcd_tracer->dump(main_time); 228 | if (halt_address) { 229 | if (memory->read(halt_address) == 0xBABECAFE) { 230 | std::cout << "halted\n"; 231 | break; 232 | } 233 | } 234 | 235 | /* show progress */ 236 | int percentage = (double) main_time * 100 / max_sim_time; 237 | if (percentage > last_percentage) { 238 | last_percentage = percentage; 239 | std::cout << "\x1b[A[" << std::string(percentage / 5, '-') 240 | << ">" << std::string(20 - percentage / 5, ' ') 241 | << "] " << percentage << "%" << std::endl; 242 | } 243 | } 244 | 245 | if (dump_signature) { 246 | char data[9] = {0}; 247 | std::ofstream signature_file(signature_filename); 248 | for (size_t addr = signature_begin; addr < signature_end; 249 | addr += 4) { 250 | snprintf(data, 9, "%08x", memory->read(addr)); 251 | signature_file << data << std::endl; 252 | } 253 | } 254 | } 255 | 256 | ~Simulator() 257 | { 258 | if (top) 259 | top->final(); 260 | } 261 | }; 262 | 263 | int main(int argc, char **argv) 264 | { 265 | Verilated::commandArgs(argc, argv); 266 | std::vector args(argv, argv + argc); 267 | Simulator simulator(args); 268 | simulator.run(); 269 | return 0; 270 | } 271 | --------------------------------------------------------------------------------