diff --git a/docs/docs/acknowledgement.md b/docs/docs/acknowledgement.md new file mode 100644 index 0000000..f64d176 --- /dev/null +++ b/docs/docs/acknowledgement.md @@ -0,0 +1,15 @@ +# 致谢 + +本项目感谢以下各位老师和同学的付出: + +感谢陈志广教授提供了 Pynq 实验板以及在他的课堂上推广本项目。 + +感谢刘皓铧同学完成了初期大部分的工作,实现了带中断、总线的三级流水线 CPU,编写了基本的测试程序和 Verilator 主程序代码,提供了自动化烧板脚本,以及移植并运行 RISC-V 合规性测试以及 CoreMark 性能测试。同时,他还参与编写了本文档的环境准备以及参考资料部分。 + +感谢黄灿彬同学在三级流水线 CPU 基础上实现了带转发功能的五级流水线 CPU。 + +感谢[吴坎](https://github.com/wu-kan)同学参与编写文档中环境准备以及实验设计部分,以及 [SYsU](https://github.com/arcsysu/SYsU-lang) 移植。 + +感谢张钧宇同学参与编写了文档中关于操作系统的部分。 + + diff --git a/docs/docs/better-tut/cheatsheets/ide-tips.md b/docs/docs/better-tut/cheatsheets/ide-tips.md new file mode 100644 index 0000000..78cd730 --- /dev/null +++ b/docs/docs/better-tut/cheatsheets/ide-tips.md @@ -0,0 +1,14 @@ +# IDEA 和 VSCode 使用技巧 + +## IDEA 快捷键 + +| 功能 | 快捷键 | +|:-----: |---------| +|按文件名查找文件 | 双击++shift++ 打开命令窗口并输入文件名| + + +## VSCode 快捷键 + +| 功能 | 快捷键 | +|:-----: |---------| +|按文件名查找文件 | ++ctrl+p++ 打开命令窗口并输入文件名| diff --git a/docs/docs/better-tut/theory/images/rv-asm-refcard-page1.png b/docs/docs/better-tut/cheatsheets/images/riscv-asm-refcard-page1.png similarity index 100% rename from docs/docs/better-tut/theory/images/rv-asm-refcard-page1.png rename to docs/docs/better-tut/cheatsheets/images/riscv-asm-refcard-page1.png diff --git a/docs/docs/better-tut/theory/images/rv-refcard-page1.png b/docs/docs/better-tut/cheatsheets/images/riscv-refcard-page1-fmt-32i.png similarity index 100% rename from docs/docs/better-tut/theory/images/rv-refcard-page1.png rename to docs/docs/better-tut/cheatsheets/images/riscv-refcard-page1-fmt-32i.png diff --git a/docs/docs/better-tut/cheatsheets/images/riscv-refcard-page5-pseudo.png b/docs/docs/better-tut/cheatsheets/images/riscv-refcard-page5-pseudo.png new file mode 100644 index 0000000..c2148d8 Binary files /dev/null and b/docs/docs/better-tut/cheatsheets/images/riscv-refcard-page5-pseudo.png differ diff --git a/docs/docs/better-tut/cheatsheets/images/riscv-refcard-page6-reg-convention.png b/docs/docs/better-tut/cheatsheets/images/riscv-refcard-page6-reg-convention.png new file mode 100644 index 0000000..4efe978 Binary files /dev/null and b/docs/docs/better-tut/cheatsheets/images/riscv-refcard-page6-reg-convention.png differ diff --git a/docs/docs/better-tut/cheatsheets/riscv-isa.md b/docs/docs/better-tut/cheatsheets/riscv-isa.md new file mode 100644 index 0000000..14da658 --- /dev/null +++ b/docs/docs/better-tut/cheatsheets/riscv-isa.md @@ -0,0 +1,19 @@ +# RISC-V 指令集格式 + +!!! tips "食用指南" + 点击图片以放大,拖拽图片以在新标签页打开,使用 ++ctrl++ +滚轮 以缩放。 + +## 指令集格式 + +来自 jameslzhu 的 [RISC-V Refcard](https://github.com/jameslzhu/riscv-card),主要包含常见指令及其 opcode,funct 码、其操作逻辑和寄存器命名对应。下图只选了其中关键几页,其余的内容包括 M 乘法扩展、A 原子操作扩展等内容。 + +![](images/riscv-refcard-page1-fmt-32i.png) +![](images/riscv-refcard-page5-pseudo.png) +![](images/riscv-refcard-page6-reg-convention.png) + + +## 32位汇编指令格式 + +汇编指令的书写格式,[University of Cambridge Open RISC-V Refcard](https://www.cl.cam.ac.uk/teaching/1617/ECAD+Arch/files/docs/RISCVGreenCardv8-20151013.pdf) + +![](images/riscv-asm-refcard-page1.png) diff --git a/docs/docs/better-tut/getting-started/docker.md b/docs/docs/better-tut/getting-started/docker.md new file mode 100644 index 0000000..0c08a56 --- /dev/null +++ b/docs/docs/better-tut/getting-started/docker.md @@ -0,0 +1,31 @@ +# Docker 配置指南 + +!!!tips "如果你不了解什么是 Docker" + 如果你不知道什么是 Docker,可以直接跳过这一节,按照下面的 Windows 或 Linux/WSL 配置方法在本机进行配置。 + +该方法适用于 Windows、Linux 和 macOS 系统。 + +首先到 [Docker 官方网站](https://docs.docker.com/engine/install/#supported-platforms) 选择并下载你使用的操作系统所对应的安装包,按照安装指南配置好 Docker。Docker 环境中含有 Scala 开发环境以及 Verilator 仿真器,但不包含 Vivado。如果你不需要烧板,那么使用 Docker 环境就可以完成所有实验以及软件测试了。 + +之后,只需要运行 + +```bash +docker run -it --rm howardlau1999/yatcpu +sbt test +``` + +Docker 会自动下载我们准备好的镜像并运行容器。如果成功执行,你会看到类似这样的输出。 + +``` +[success] Total time: 385 s (06:25), completed Dec 15, 2021, 8:45:25 PM +``` + +Docker 中的 YatCPU 代码可能不是最新版,且容器结束运行之后所有修改都将丢失,如果你需要完成实验,需要先将代码仓库克隆到本机,然后在运行 Docker 容器时挂载本机目录: + +``` +git clone --recursive https://github.com/Tokisakix/2023-fall-yatcpu-repo +docker run -it --rm -v yatcpu:/root/yatcpu howardlau1999/yatcpu +``` + +按照这种方法在容器中所做的修改将保存到本机文件夹,反之同理。 + diff --git a/docs/docs/better-tut/getting-started/images/idea-1-clone-prj.png b/docs/docs/better-tut/getting-started/images/idea-1-clone-prj.png new file mode 100644 index 0000000..b05acf1 Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/idea-1-clone-prj.png differ diff --git a/docs/docs/better-tut/getting-started/images/idea-2-close-and-reopen-lab-as-root.png b/docs/docs/better-tut/getting-started/images/idea-2-close-and-reopen-lab-as-root.png new file mode 100644 index 0000000..087540a Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/idea-2-close-and-reopen-lab-as-root.png differ diff --git a/docs/docs/better-tut/getting-started/images/idea-3-plugin-install.png b/docs/docs/better-tut/getting-started/images/idea-3-plugin-install.png new file mode 100644 index 0000000..50c3e65 Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/idea-3-plugin-install.png differ diff --git a/docs/docs/better-tut/getting-started/images/idea-4-download-jdk.png b/docs/docs/better-tut/getting-started/images/idea-4-download-jdk.png new file mode 100644 index 0000000..9c7be87 Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/idea-4-download-jdk.png differ diff --git a/docs/docs/better-tut/getting-started/images/idea-5-download-scala.png b/docs/docs/better-tut/getting-started/images/idea-5-download-scala.png new file mode 100644 index 0000000..b2e25d5 Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/idea-5-download-scala.png differ diff --git a/docs/docs/better-tut/getting-started/images/idea-6-scala-add.png b/docs/docs/better-tut/getting-started/images/idea-6-scala-add.png new file mode 100644 index 0000000..a3ed9b3 Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/idea-6-scala-add.png differ diff --git a/docs/docs/better-tut/getting-started/images/idea-test-passed.png b/docs/docs/better-tut/getting-started/images/idea-test-passed.png new file mode 100644 index 0000000..d4d506d Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/idea-test-passed.png differ diff --git a/docs/docs/better-tut/getting-started/images/idea-trust-prj.png b/docs/docs/better-tut/getting-started/images/idea-trust-prj.png new file mode 100644 index 0000000..c295d2b Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/idea-trust-prj.png differ diff --git a/docs/docs/better-tut/getting-started/images/vivado-install-1.png b/docs/docs/better-tut/getting-started/images/vivado-install-1.png new file mode 100644 index 0000000..5f1a1c8 Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/vivado-install-1.png differ diff --git a/docs/docs/better-tut/getting-started/images/vivado-install-2.png b/docs/docs/better-tut/getting-started/images/vivado-install-2.png new file mode 100644 index 0000000..f0af74c Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/vivado-install-2.png differ diff --git a/docs/docs/better-tut/getting-started/images/vivado-install-3.png b/docs/docs/better-tut/getting-started/images/vivado-install-3.png new file mode 100644 index 0000000..2952be2 Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/vivado-install-3.png differ diff --git a/docs/docs/better-tut/getting-started/images/vivado-install-4.png b/docs/docs/better-tut/getting-started/images/vivado-install-4.png new file mode 100644 index 0000000..b63b00f Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/vivado-install-4.png differ diff --git a/docs/docs/better-tut/getting-started/images/vivado-install-5.png b/docs/docs/better-tut/getting-started/images/vivado-install-5.png new file mode 100644 index 0000000..6cbae97 Binary files /dev/null and b/docs/docs/better-tut/getting-started/images/vivado-install-5.png differ diff --git a/docs/docs/better-tut/getting-started/index.md b/docs/docs/better-tut/getting-started/index.md new file mode 100644 index 0000000..ad39d00 --- /dev/null +++ b/docs/docs/better-tut/getting-started/index.md @@ -0,0 +1,12 @@ +# 简介 + +本项目经过测试且可用的操作系统为: + +- Windows 10 +- Debian 11 +- Debian 11, Windows Subsystem for Linux (WSL) + +我们提供两种风格的开发环境配置方案: + +1. 对于喜欢使用 IDE 集成环境的同学,推荐使用 Windows 配置方法。该方法适合喜欢图形化界面操作的同学,上手难度最低。 +2. 对于喜欢使用终端+代码编辑器组合的同学,推荐使用 Linux/WSL 配置方法,或使用我们准备好的 Docker 环境。该方法需要一定的动手和问题解决能力。 diff --git a/docs/docs/better-tut/getting-started/linux-wsl.md b/docs/docs/better-tut/getting-started/linux-wsl.md new file mode 100644 index 0000000..fff39e4 --- /dev/null +++ b/docs/docs/better-tut/getting-started/linux-wsl.md @@ -0,0 +1,210 @@ +# Linux/WSL 配置指南 + +By: [:material-github: wu-kan](https://github.com/wu-kan)、[:material-github: howardlau1999](https://github.com/howardlau1999) + + +下面介绍如何在 Linux 或 WSL(Windows Subsystem for Linux) 环境中搭建本实验的开发环境。[这里](https://liuhaohua.com/server-programming-guide/appendix/build-env/)给出搭建相关环境的一个参照。 + +这里假设你使用的 Linux 或 WSL 系统是 Debian 11。对于使用其他 Linux 系统的同学,操作是类似的,相信你有足够的能力参考下面的指令搭建环境。 + +## 安装必要工具 + +本实验将会使用到以下工具,除 sbt 外,可以通过下面提供的命令一键安装: + +```bash +sudo apt install -y git \ + clang \ + make \ + gnupg \ + scala \ + libtinfo5 \ + coreutils \ + cmake \ + llvm \ + lld +``` + +| 名称 | 说明 | +| :---------------------: | ------------------------------------ | +| git | 代码版本管理工具 | +| clang, lld | 用于编译生成 RISC-V 可执行二进制文件 | +| llvm | 用于编辑和查看二进制文件 | +| make | 用于执行 Makefile | +| cmake | 用于执行 CMakeLists.txt | +| gnupg | 签名验证工具 | +| scala | 本项目的语言编译器 | +| sbt | Scala 包管理器 | +| libtinfo5 | Vivado 启动依赖 | +| md5sum | 安装包校验工具 | + +安装完成之后,在任意目录执行 `git clone --recursive https://github.com/Tokisakix/2023-fall-yatcpu-repo` 下载代码仓库。 + +## 安装 sbt 包管理器 + +[sbt](https://www.scala-sbt.org/) 是 Scala 的构建系统及包管理器,可按照 [官方安装指示](https://www.scala-sbt.org/1.x/docs/zh-cn/Installing-sbt-on-Linux.html#Ubuntu%E5%92%8C%E5%85%B6%E4%BB%96%E5%9F%BA%E4%BA%8EDebian%E7%9A%84%E5%8F%91%E8%A1%8C%E7%89%88) 进行安装,该教程可能随着系统及版本更新而变化,以其官网为准。 + +```bash +echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" | sudo tee /etc/apt/sources.list.d/sbt.list +echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | sudo tee /etc/apt/sources.list.d/sbt_old.list +curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo tee /etc/apt/trusted.gpg.d/sbt.asc +sudo apt-get update +sudo apt-get install sbt +# up to 2025-08-19 +``` + + + + + + +## 安装 Verilator + +[Verilator](https://github.com/verilator/verilator) 是一款 Verilog 模拟器,在本实验中用于测试所编写的CPU及硬件,其模拟效率优于 Chisel3 自带模拟器。 +chiseltest 测试会主动在 `PATH` 环境变量中寻找 Verilator 并使用。 + +使用 Verilator 推荐的安装方式,从源码安装: + +```bash +cd $HOME +sudo apt-get install git perl python3 make autoconf g++ \ + flex bison ccache libgoogle-perftools-dev numactl perl-doc +git clone --depth 1 -b stable https://github.com/verilator/verilator +cd verilator +autoconf +./configure +make -j `nproc` +sudo make install +``` + +???+tips "修改安装路径" + 如果不想全局安装,可以在 `./configure` 后加参数 `--prefix=/path/to/install`,指定安装路径,且在后续测试时,需要将安装路径添加到 `PATH` 环境变量,以便测试程序可以找到 Verilator 可执行程序。 + +命令执行完毕后,可以执行命令 `verilator --version` 测试安装是否成功。同时应检查版本号为 4.xxx: + +``` +Verilator 4.219 devel rev UNKNOWN.REV (mod) +``` + +!!! warning "Verilator" + 由于代码仓库仍使用较旧的 [chiseltest](https://github.com/ucb-bar/chiseltest) 进行测试,其仅支持 Verilator 4.x 版本,而不兼容 5.x 版本。 + + 使用 `apt` 直接安装 Verilator 时将默认安装 5.x 版本。 + + +之后,运行仿真测试的时候,测试框架将自动寻找 Verilator 并用来加速。配置完成后,可以用 VSCode 或者 vim 等编辑器打开代码仓库,开始实验了。 + + + + +--- + + +## 安装 Vivado + +!!! warning "预留足够的硬盘空间" + Vivado 仅在最终烧板时使用,编写代码及测试不要求安装。您可完成实验后,准备烧板前再进行安装。若您电脑存储空间不足,也可询问助教使用无需 Vivado 和烧板的备用考核方式。 + + Vivado 2020.1 安装包体积较大,约为 36 GB,且后续安装也要使用大量硬盘空间。请预留好**至少 100 GB 的硬盘空间**。 + +中山大学组成原理实验课使用的 Zynq 实验板型号为 Zybo-10, 需要 Xilinx 的工具进行综合实现以及烧板。这里安装 `Vitis` 即可,不需要许可或激活许可密钥。 +经过测试且可用的 Vivado 版本为 2020.1 和 2022.1。更新或更旧的版本理论上可以使用,但没有经过测试。 + +经过测试且可用的 Vivado 版本为 2020.1 和 2022.1。更新或更旧的版本理论上可以使用,但没有经过测试。 + +中山大学校园网内下载 Vivado 2020.1 安装包,可以使用我们提供的镜像。 +下面是使用 `curl` 下载校园网安装包镜像的命令,你也可以使用其他方式下载。 + +```bash +# 下载 +curl -O https://mirrors.matrix.moe/software/Xilinx/Xilinx_Unified_2020.1_0602_1208.tar.gz +# 也可以在 Xilinx 的官网下载 +# https://china.xilinx.com/support/download/index.html/content/xilinx/zh/downloadNav/vivado-design-tools/archive.html +``` + +由于文件较大,为了校验传输过程中是否发生错误,请在下载完成后验证安装包的 MD5 值。使用以下命令计算文件的 MD5 值: + +```bash +md5sum Xilinx_Unified_2020.1_0602_1208.tar.gz +``` + +命令应当输出以下内容: + +```bash +b018f7b331ab0446137756156ff944d9 Xilinx_Unified_2020.1_0602_1208.tar.gz +``` + +如果不一致,请重新下载。校验通过后,使用下面的命令解压安装包并生成安装配置文件: + +```bash +tar -zxf Xilinx_Unified_2020.1_0602_1208.tar.gz +cd Xilinx_Unified_2020.1_0602_1208 +./xsetup -b ConfigGen +``` + +输入 `1`,会产生一个默认配置文件 `~/.Xilinx/install_config.txt`。 + +随后再次执行下述指令,安装 `Vitis`。 + +```bash +./xsetup -a XilinxEULA,3rdPartyEULA,WebTalkTerms -b Install \ + -c ~/.Xilinx/install_config.txt -l ~/Xilinx +``` + +其中 `~/Xilinx` 是安装目录,可以自行定义。 + + + + + + diff --git a/docs/docs/better-tut/getting-started/macos.md b/docs/docs/better-tut/getting-started/macos.md new file mode 100644 index 0000000..93d7464 --- /dev/null +++ b/docs/docs/better-tut/getting-started/macos.md @@ -0,0 +1,43 @@ +# macOS 配置指南 + +By: [:material-github: wu-kan](https://github.com/wu-kan)、[:material-github: howardlau1999](https://github.com/howardlau1999) + +!!! warning "macOS 无法烧板" + 除了 Vivado 之外,所有工具都可以在 macOS 上运行,也即可以顺利完成软件测试以及波形仿真。但由于 **Vivado 无法在 macOS 上安装**,所以如果你使用的是 macOS 并且**需要进行烧板实验**,请通过虚拟机或使用其他设备等方式使用上述的操作系统进行实验。 + + +打开终端,执行以下命令安装 Homebrew(已经安装的可以跳过): + +```bash +export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.ustc.edu.cn/brew.git" +export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git" +export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles" +export HOMEBREW_API_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles/api" + +/bin/bash -c "$(curl -fsSL https://mirrors.ustc.edu.cn/misc/brew-install.sh)" +``` + +完全退出终端,重新打开终端,执行以下命令安装依赖: + +```bash +brew install llvm cmake verilator sbt +``` + +由于 macOS 系统有可能已经自带了 LLVM 工具链,如果需要使用 Homebrew 安装的 LLVM 工具链,需要设置环境变量: + +```bash +export PATH=$(brew --prefix llvm)/bin:$PATH +``` + +上面也可以添加到 `~/.bashrc` 或 `~/.zshrc` 中,以便每次打开终端时自动设置。 + +执行命令 `verilator --version` 测试安装是否成功。如果安装无误,应当看到如下输出(版本号可能不同): + +``` +Verilator 5.016 2023-09-16 rev UNKNOWN.REV +``` + +之后,运行仿真测试的时候,测试框架将自动寻找 Verilator 并用来加速。 + +上面完成了 macOS 的命令行配置,和 Linux 差不多,如果你更喜欢图形化操作,macOS 上也可以使用 IDEA IDE,配置方法参考 Windows 环境配置一节。 + diff --git a/docs/docs/better-tut/getting-started/windows.md b/docs/docs/better-tut/getting-started/windows.md new file mode 100644 index 0000000..ca77e50 --- /dev/null +++ b/docs/docs/better-tut/getting-started/windows.md @@ -0,0 +1,183 @@ +# Windows 配置指南 + +By: [:material-github: wu-kan](https://github.com/wu-kan)、[:material-github: howardlau1999](https://github.com/howardlau1999) + +!!! tips "善于搜索问题及提问" + TODO + + +## 安装 IDE + +本实验可以使用 Jetbrains 的 IDEA 或 Microsoft 的 vscode 作为开发环境,您可从中**二选一**。 +您将在这些IDE上完成代码并进行测试。 + +- IDEA 更容易上手,可以自动下载所需库,界面功能更清晰,但消耗内存较多 。 +- vscode 有一定的上手难度,但熟悉后可以适用于各种编程语言和工程场景。 + + + +### 安装 Intellij IDEA + + +您可通过先安装 [Jetbrains Toolbox](https://www.jetbrains.com/toolbox-app/) 再从中下载 IDEA,也可在 [Intellij IDEA](https://www.jetbrains.com/idea/download/?section=windows) 处直接下载免费的 Community 版本(其他版本需收费且功能用不上)。 + + +安装完成后打开,您应能看到欢迎界面,此时点击右上角的 Clone Repository 以克隆实验代码仓库,地址为 https://github.com/Tokisakix/2023-fall-yatcpu-repo,并选择项目下载的文件夹,点击 clone 开始下载。 +若由于网络问题克隆失败,可以重新尝试该步骤,或者直接去 GitHub 网页下载压缩包并解压。 +下文均假设项目克隆至文件夹 `2023-fall-yatcpu-repo` 。 + +![](images/idea-1-clone-prj.png) + +稍等片刻后,项目将被打开,此时会提示是否信任该仓库,选择 Trust Project。您可以在左侧文件浏览器一览整个仓库的结构,包括各个实验的项目文件夹 `labx` 。目前我们是将 `2023-fall-yatcpu-repo` 文件夹作为根目录(root),当进行后续实验时,需要以对应实验的 `labx` 作为根目录。如下图,在左上角 File - Close Project 关闭当前项目,并重新打开 `2023-fall-yatcpu-repo` 下面的 `lab1` 。 + +![](images/idea-2-close-and-reopen-lab-as-root.png) + + +随后,您需要安装 Scala 插件。 `双击 `++shift++ 调出快速命令窗口,输入 plugins 并打开插件管理窗口;或通过 ++ctrl+shift+s++ ,或 File - Settings 打开,随后在 marketplace 搜索 scala 插件并安装,如需中文还可搜索 Chinese Language Pack 以使界面汉化。如下图所示。 + +![](images/idea-3-plugin-install.png) + +启用 Scala 插件后,右下角可能弹出窗口提示 load/import sbt build,选择 import 加载项目。 +随后需安装 JDK 及 Scala 库。使用 ++ctrl+shift+alt+s++ 或 `双击` ++shift++ 并打开 Project Structure 窗口,首先在 Project 侧栏中配置 SDK 中的 JDK ,若您电脑已安装有 JDK 17 或 19,直接选择即可。若无,则下载 Java 17 或 19 的 Eclipse Temurin 版本。 + +![](images/idea-4-download-jdk.png) + +随后侧栏中选择 Global Libraries,点击➕添加 Scala SDK ,注意选择 2.13.10 版本,等待下载完成。若遇到网络问题,可以重新操作下载。下载完成后,添加其为使用 Scala SDK。如下面两图所示。 + +![](images/idea-5-download-scala.png) + +![](images/idea-6-scala-add.png) + +完成安装后, `lab1` 项目应该能被正确加载,此时 `双击` ++shift++ 打开 `RegisterFileTest.scala` ,应该能看见测试左侧的绿色箭头,点击运行如果能提示测试通过,则说明环境配置正确。 + +![](images/idea-test-passed.png){width=80%} + + +### 选项2:安装 Visual Studio Code + +使用 vscode 需要手动安装 JDK、Scala,并设置相应系统环境变量,仅建议有相应经验或想要探索的同学使用。若希望快速上手项目开始写码,安装 IDEA 后即可进行后面的配置。 + + +??? note "安装 vscode" + + !!! warning "😦别搞混 vscode 和 vs" + 虽然都是微软公司的软件,但 Visial Studio(VS)专为 Windows 上的 C++、C# 开发设计,而 Visual Studio Code(vscode)则更具普适性,适合各种系统的各种编程语言的开发。 + + 在 [](https://code.visualstudio.com/download) 处下载适合的版本,一般为 Windows x64 版本。vscode 运行 scala 依赖于 Metals 这款插件,配置教程可看:[极客教程](https://geek-docs.com/scala/scala-questions/475_scala_how_to_run_an_existing_scala_project_using_vs_code_and_metals.html) 和 [Metals官方文档](https://scalameta.org/metals/docs/editors/vscode) 。 + + + 参照 [Using the Scala Installer (recommended way)](https://docs.scala-lang.org/getting-started/index.html#using-the-scala-installer-recommended-way) ,在页面处下载 Coursier 工具,并跟随指引安装。Coursier 会自动安装 JDK 和 sbt 。 + + 完成这两项后,打开 vscode,打开 `lab1` 作为根目录。注意 vscode 右下角提示 "Import project" 时,点击导入项目,即可加载。 + +--- + +## 安装 MSYS2 和 Verilator + + +[Verilator](https://github.com/verilator/verilator) 是一款 Verilog 模拟器,在本实验中用于测试所编写的CPU及硬件,其模拟效率优于 Chisel3 自带模拟器。 +chiseltest 测试会主动在 `PATH` 环境变量中寻找 Verilator 并使用。 + +首先 [下载 MSYS2](https://www.msys2.org/),执行安装并将安装目录下的两个文件夹添加至系统 `PATH` 变量,例如您将其安装至 `D:\msys64`,则应添加: + +- `D:\msys64\usr\bin` +- `D:\msys64\mingw64\bin` + +在开始菜单搜索“环境变量”,打开“编辑系统环境变量”,在弹出的对话框点击“环境变量”,双击用户变量中的“Path”,点击“新建”,在最后添加上述路径。 +添加系统变量 `PATH` 的教程见 [path-环境变量及其修改](../practice/envvar-and-cmd.md#path-环境变量及其修改) 。 + +随后,使用 ++win+r++ 运行 `cmd` 打开命令行窗口,输入 + +```cmd +D:\msys64\usr\bin\bash +``` + + + +,进入到 MSYS2 提供的 bash 窗口(注意,若您电脑安装有 WSL,直接输入 `bash` 可能进入的不是 MSYS2 的 bash)。随后,依次输入下列指令,并观察输出是否有 error, failed 等报错: + + + +```bash +# 安装所需包,若网络不好安装失败,则重新执行 +pacman -Sy --noconfirm --needed base-devel mingw-w64-x86_64-toolchain git flex mingw-w64-x86_64-cmake mingw-w64-x86_64-autotools mingw-w64-x86_64-python3 + +# 临时设置 PATH 包含 core perl +export PATH=/mingw64/bin:/usr/local/bin:/usr/bin:/usr/bin/core_perl:$PATH + +# 切换到 ~ 用户目录,并查看当前路径 +cd /home/$USER; pwd + +# 克隆到 ~/verilator,如果您已克隆或复制了包,请略过 +git clone --depth 1 -b v4.226 https://github.com/verilator/verilator; +``` + +!!!warning "Windows 和 Unix 路径分隔符的区别" + MSYS 是在 Windows 上提供了一个兼容 Linux 的环境,在 Linux 里输入的路径应使用 `/` 作为目录分隔符, `\` 会被识别为反义字符(如`\n`换行)。而 Windows 上两种斜杠都可以作为分隔符。 + + +???tips "克隆 Verilator 失败时" + + 若 `git clone` 时网络不佳导致失败,可以重试或在 [Verilator 4.226 Release](https://github.com/verilator/verilator/archive/refs/tags/v4.226.zip) 下载压缩包,并解压到相应目录: + + - 若 `pwd` 显示 `/c/Users/<用户名>` ,则对应 Windows C盘里 `Users/<用户名>/verilator` 文件夹 + - 若 `pwd` 显示 `/home/<用户名>`,则对应 MSYS 安装目录里的文件夹,如 `D:\msys64\home\<用户名>\verilator` + +之后,按下面的步骤构建并安装 Verilator: + + +```bash +# 进入verilator文件夹 +cd ~/verilator + +# 设置 shell 编码,避免安装错误 +export LC_ALL=en_US.UTF-8; export LANG=en_US.UTF-8 + +# 如果成功不会有任何输出 +autoconf + +# verilator 自动设置,需要等待一段时间 +./configure --prefix=/mingw64 + + +# 进行编译安装 +cp /usr/include/FlexLexer.h /mingw64/include; +make -j$(nproc); make install + +``` + +完成上述步骤后,在 `bash` 输入下述指令,若成功输出版本号 4.226 则为成功。 + +```bash +verilator_bin --version +# 若成功会输出 Verilator 4.226 2022-08-31 rev v4.226 +``` + + + +--- + +## 安装 Vivado + +!!! warning "预留足够的硬盘空间" + Vivado 仅在最终烧板时使用,编写代码及测试不要求安装。您可完成实验后,准备烧板前再进行安装。若您电脑存储空间不足,也可询问助教使用无需 Vivado 和烧板的备用考核方式。 + + Vivado 2020.1 安装包体积较大,约为 36 GB,且后续安装也要使用大量硬盘空间。请预留好**至少 100 GB 的硬盘空间**。 + + +中山大学组成原理实验课使用的 Zynq 实验板型号为 Zybo-10, 需要 Xilinx 的工具进行综合实现以及烧板。这里安装 `Vitis` 即可,不需要许可或激活许可密钥。 +经过测试且可用的 Vivado 版本为 2020.1 和 2022.1。更新或更旧的版本理论上可以使用,但没有经过测试。 + +安装包体积较大,**推荐使用带断点续传功能的下载工具如迅雷、Free Download Manager 下载**。 + +如果你在**中山大学校园网内**下载 Vivado 2020.1 安装包,可以使用我们提供的镜像:[点此下载](https://mirrors.matrix.moe/software/Xilinx/Xilinx_Unified_2020.1_0602_1208.tar.gz)。 + +也可以在 Xilinx 的官网下载(可能需要注册AMD账户):[点击跳转](https://china.xilinx.com/support/download/index.html/content/xilinx/zh/downloadNav/vivado-design-tools/archive.html)。 + +![](images/vivado-install-1.png) +![](images/vivado-install-2.png) +![](images/vivado-install-3.png) +![](images/vivado-install-4.png) +![](images/vivado-install-5.png) + +之后耐心等待安装完成即可,这可能耗时数分钟。 diff --git a/docs/docs/better-tut/labs/challenge1/challenge1-running-os.md b/docs/docs/better-tut/labs/challenge1/challenge1-running-os.md new file mode 100644 index 0000000..7ac9072 --- /dev/null +++ b/docs/docs/better-tut/labs/challenge1/challenge1-running-os.md @@ -0,0 +1,3 @@ +# 挑战实验一 运行操作系统 + +尝试在 CPU 上运行 [egos-2000](https://github.com/yhzhang0128/egos-2000),这是一个 2000 行左右实现的教学操作系统,可以在 QEMU 上运行。 \ No newline at end of file diff --git a/docs/docs/better-tut/labs/lab1/lab1-single-cycle-cpu.md b/docs/docs/better-tut/labs/lab1/lab1-single-cycle-cpu.md index 2efa2a0..2275586 100644 --- a/docs/docs/better-tut/labs/lab1/lab1-single-cycle-cpu.md +++ b/docs/docs/better-tut/labs/lab1/lab1-single-cycle-cpu.md @@ -274,9 +274,8 @@ CPUBundle 是 CPU 和内存等外设进行数据交换的通道。 ## 使用自定义应用测试 -TODO: 更新链接 -参考[编译和链接的过程](../tutorial/compile-and-link.md)以及[CMake 入门](../tutorial/cmake.md),编写并编译自己的程序后,修改 `CPUTest` 中的 `InstructionROM` 的指令文件地址,即可运行你自己的程序。 +参考[编译和链接的过程](../../practice/compile-and-link.md)以及[CMake 入门](../../practice/cmake.md),编写并编译自己的程序后,修改 `CPUTest` 中的 `InstructionROM` 的指令文件地址,即可运行你自己的程序。 如果想要烧录自己的程序到 FPGA 板子上,只需要修改 `board/<板子型号>/Top.scala` 中的 `binaryFilename` 为你生成的程序二进制文件名即可。 diff --git a/docs/docs/better-tut/labs/lab4/images/.$device-bus-interact.drawio.bkp b/docs/docs/better-tut/labs/lab4/images/.$device-bus-interact.drawio.bkp new file mode 100644 index 0000000..70cdd7f --- /dev/null +++ b/docs/docs/better-tut/labs/lab4/images/.$device-bus-interact.drawio.bkp @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/better-tut/theory/images/axi-fsm.png b/docs/docs/better-tut/labs/lab4/images/axi-fsm.png similarity index 100% rename from docs/docs/better-tut/theory/images/axi-fsm.png rename to docs/docs/better-tut/labs/lab4/images/axi-fsm.png diff --git a/docs/docs/better-tut/labs/lab4/images/device-bus-interact.drawio b/docs/docs/better-tut/labs/lab4/images/device-bus-interact.drawio new file mode 100644 index 0000000..c0553da --- /dev/null +++ b/docs/docs/better-tut/labs/lab4/images/device-bus-interact.drawio @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/better-tut/labs/lab4/images/device-bus-interact.png b/docs/docs/better-tut/labs/lab4/images/device-bus-interact.png new file mode 100644 index 0000000..45db136 Binary files /dev/null and b/docs/docs/better-tut/labs/lab4/images/device-bus-interact.png differ diff --git a/docs/docs/better-tut/labs/lab4/lab4-bus.md b/docs/docs/better-tut/labs/lab4/lab4-bus.md new file mode 100644 index 0000000..5f1640e --- /dev/null +++ b/docs/docs/better-tut/labs/lab4/lab4-bus.md @@ -0,0 +1,158 @@ +# 实验四 总线 + +CPU 除了可以通过内存控制器访问内存以外,还可以通过总线协议访问外部设备。使用总线的目的是减少电路布线数量以及电路设计复杂度,避免 CPU 和外部设备之间直接连接。通过统一的总线抽象,CPU 使用总线协议访问外部设备,而不需要知道外部设备的硬件细节,具体的硬件操作则进一步抽象为读写硬件设备上的寄存器。 + +在本实验中,你将学习到: + +- AXI4-Lite 协议 +- 使用状态机实现总线协议 + +在开始实现 AXI4-Lite 总线协议前,请先阅读 [总线](../../theory/bus.md) 以了解 AXI4-Lite 协议的基本知识。 + +不管使用 IDE 还是执行命令,根目录是 `lab4` 文件夹。 + +## TODO: + +大纲修正为 + +1. AXI4 接口协议特性、示意图及读写事务 +2. 实现选项:使用状态机转换 + 1. 给予提示,提供自由实现选项 +3. 实现提示:说明时序约束及含义、测试内容、如何查看信号图等 +4. 引入总先后流水线控制变化、结构图、MMIO +5. 提示进行性能对比 +6. 实验报告 +7. + + +## 用状态机实现总线协议 + +我们本次实验的主要内容就是实现 AXI4-Lite 总线协议里面的主从设备间的读写流程的状态转换。本实验使用 AXI4 通信协议的方法如下图所示,主设备(如CPU)要进行读写时,通过 `AXIMasterBundle` 发起读写,接口 `AXI4LiteMaster` 知悉后通过 AXI4 协议在总线上发起读写事务。从设备的接口 `AXI4LiteSlave` 被动地接受事务,并将读写请求转达给从设备(如内存、计时器等)。事务通信过程中,`AXI4LiteMaster` 和 `AXI4LiteSlave` 使用某种机制来记录读写请求并完成 AXI4 协议规定的通信操作。 + + +![](images/device-bus-interact.png) + + +为简单起见,我们使用状态机来实现 AXI4 接口,即 `AXI4LiteMaster` 和 `AXI4LiteSlave` 用状态机实现,在握手完成时进行状态转换,以此逐个完成事务。我们给出一个可参考的状态机,如下图,左图是主设备总线接口的状态机,右图是从设备总线接口的状态机: + +![axi-fsm](images/axi-fsm.png) + + + + +### 进行一次读操作 + +通过上面的资料学习,相信你已经对总线有一个大概的了解,现在我们整理一下 CPU 通过总线取指令的过程。以下给出 `MasterBundle` 的定义,它是 CPU 操纵总线接口进行通信的接口,以及上述状态机实现的 6 个状态: + +```scala +object AXI4LiteStates extends ChiselEnum { + val Idle, ReadAddr, ReadData, WriteAddr, WriteData, WriteResp = Value +} +class AXI4LiteMasterBundle(addrWidth: Int, dataWidth: Int) extends Bundle { + val read = Input(Bool()) // request a read transaction + val write = Input(Bool()) // request a write transaction + val read_data = Output(UInt(dataWidth.W)) + val write_data = Input(UInt(dataWidth.W)) + val write_strobe = Input(Vec(Parameters.WordSize, Bool())) + val address = Input(UInt(addrWidth.W)) + val busy = Output(Bool()) // if busy, master is not ready to accept new transactions + val read_valid = Output(Bool()) // indicates read transaction done successfully and asserts for ONLY 1 cycle. + val write_valid = Output(Bool()) // indicates write transaction done successfully and asserts for ONLY 1 cycle. +} +``` + +首先,我们的取指级(IF)应该发出取指信号,包括将 `AXI4LiteMasterBundle` 的 `read` 置 1,并将 PC 送至 `address`, +这时 CPU 的 AXI 主机(AXI Master) 接口收到 IF 的信号,如果该主机处于空闲状态 (Idle) 则对本次读请求做出响应,主机内部状态由空闲跳转到 读地址状态 (ReadAddr),产生并发送读请求 (`ARVALID`)、读地址 (`ARADDR`)。 + +当内存模块的从机(AXI Slave) 接收到 `ARVALID` 且空闲时,其内部状态跳转至 读地址状态 (ReadAddr),保存 `ARADDR` ,并将 读地址准备(`ARREADY`) 信号置 1 表明读地址已收取,这时就完成了一次读地址的握手。获取读取地址后,从机通过 `AXI4LiteSlaveBundle` 告知内存芯片读取指定位置的数据。 + +当内存芯片返回读出数据时,传回给从机。从机 跳转至 读数据状态 (ReadData) ,将读出数据传入 读数据 (`RDATA`)、读返回请求 (`RVALID`);主机此时也跳变到 读数据状态 (ReadData),并将 `RREADY` 置 1,表明准备好接受读出的数据。 + +当主机发现 `RVALID` 和 `RREADY` 均为 1 时完成握手,其将读出数据 `RDATA` 通过 `AXI4LiteMasterBundle` 的 `read_data` 传回至取值级,同时将 `read_valid` 置 1 持续 1个时钟,表明这次读取操作成功,随后主机跳转回空闲状态。 + +另一边,当从机发现 `RVALID` 和 `RREADY`完成握手时,其也重返空闲状态。 + + +### 如何进行写操作 + +相比于读操作,写操作的流程多一个写反馈的握手,如果你理解了读操作是如何握手的,那么写操作的流程应该不成问题。 + +!!!tips "状态机的具体实现" + 我们提供了灵活的测试方法,主从机的状态转换可以不必按照上面所说的一字不差,只需要能达到测试中要求的读写正确、功能正常即可。你可以观察测试产生的波形图,从而优化实现以减少读写操作的周期数,甚至跳过某些上述的主从机状态。 + + +## 有了总线之后的 MMIO + +CPU 发出访存地址,我们需要通过一个模块来根据这个地址的范围来确定让哪个从机来和主机握手,它的设计思路和没有总线的情况下是一样的,都是通过地址的特定部分译码出控制信号,然后选通对应设备。 + +就是识别CPU发给总线模块的地址,然后通过复用器选择对应的外设即可,不要求实现,代码位于 `src/main/scala/bus/BusSwitch.scala`。 + + + +## 把总线加到你的流水线上 + +从预备知识里面我们知道了需要有总线仲裁这个模块,来协调总线主机响应来自 CPU 哪个阶段的读写请求信号。 + +目前我们的流水线上,无论是三级还是五级,都只存在取指单元和访存单元之间的冲突。这显然也是一种结构冲突(Structural Hazard)。所以可以用 Lab 3 实验中解决冲突的思路,通过阻塞流水线来保证指令流的执行,即如果访存阶段没有占用总线,IF 单元才能够取指。 + + + +## 更多关于总线的知识 + +实际上把总线加到我们原来的 CPU 上后,会发现 IPC 大幅下降,变得很慢。 + +主要原因是总线握手花费了大量的时间。而 AXI4-Lite 协议又是 AXI4 协议的简化版本。为了实现上的简单,所以没有实现突发传输(burst)的功能,所以每次最多读写一个数据总线宽度的数据,即每次都需要重新进行各个读写通道的握手,导致效率很低。 + +解决的一个办法就是加缓存。根据局部性原理,我们可以为取值单元与内存之间加上指令 Cache(I-cache),为访存单元与内存间加上数据 Cache(D-cache),这样就可以加速数据的存取。 + +而这样的实现方案里面 D-cache 和 I-cache 同样是需要通过总线访问内存的。但是由于 Cache 是以 Cache Line 为单位存取的。假设一个 Cache Line 为 128 字节,当我们的总线数据位宽为 4 字节的时候,填充缓存行需要从内存读 128 / 4 = 32 次, +并且这每一次需要重新进行读地址握手、读数据握手。而 AXI4 协议就是在AXI4-Lite的基础上加上了 Burst 的功能,即在读写请求中可以指定传送数据的个数,从指定地址传连续的多个数据给从机, +而读数据握手时,就可以连续获取相应的那么多个数据。这样的协议就是突发传输(burst),为了实现这个协议,主从设备为了握手通信,需要设置更多的寄存器(指定读取数据个数的 RLEN,指示是否为最后一个数据的 RLAST 等),状态机转换也需要更为复杂。 + +这样一来我们从两个方面来加速我们的 CPU,一是 Cache,我们不需要通过总线来获取数据;二是实现完整的 AXI4 协议。而从理论上来说,如果实现了 Cache 而没有实现 AXI4 的 burst 机制,加速效果也不会很明显。所以可以先实现 Cache 然后实现 burst 来体会一下。 + + +--- + +## 实验任务 + + + +主从设备的状态机切换图在预备知识里面给出了,不需要自己去总结,实现所需的寄存器和模块输入输出接口已经给出,你只需要实现状态切换以及相应的握手信号。 + + + +!!! note "实验任务:实现 AXI4-Lite 协议主从机" + 主从设备的代码位于 `src/main/scala/bus/AXI4Lite.scala`,请在标有 `//lab4 (BUS)` 的注释处,实现AXI4Lite主从机的内部逻辑,并: + + - 通过 `src\test\scala\riscv` 下的 `BusTest.scala`,和三级、五级流水线下的 `CPUTest.scala` 中的测试 + - 查看 `BusTest` 中 `FunctionalTest` 输出的读写事务消耗了多少个周期 + - 查看 `CPUTest` 中 `Fibonacci` 和 `Quicksort` 输出的消耗时钟周期 + +!!! tips "如何检测自己实现的 AXI4-Lite 主从机正确性" + 上一版的测试代码要求严格按照给定状态机实现,这一版提供了允许自由实现 AXI4-Lite 协议的测试,并加入测量读写事务耗时的功能。 + + 如果你发现代码无法通过测试,可以查看 `BusTest.scala` 中 `FunctionalTest` 产生的波形图,并查看相应通道的信号,来了解自己实现能否完成最基本的读和写操作。该方法也可以帮助你改进实现,减少读写操作的周期数。 + + + + +## 实验报告 + +1. 简述您的 AXI4-Lite 主从机实现逻辑。如果有,描述您通过什么方法改进了实现及性能提升。 +2. 简要概括`BusTest`中测试的原理,以及测试用例的执行结果。 +3. 【可选】参考[硬件调试](../../practice/hardware-debug.md)一节的内容,用硬件波形的方法捕获程序运行结果。分析 Vivado 是否能正确识别并组合 AXI4 总线协议的传输信号以及过程。 +4. 在完成实验的过程中,遇到的关于实验指导不明确或者其他问题,或者改进的建议。 + + + +## What's next? + +同学们在完成了本实验后,就应该具备根据状态转移图来实现状态机的能力了。如果有兴趣的话,可以尝试一下实现简单的 Cache,然后再实现 burst。包括后面希望实现 MMU,最简单的实现也可以用状态机来实现。所以,你可以放开手脚在 YatCPU 的基础上探索了(当然你也可以自己从头实现)。 + +实现好总线模块的逻辑后,你可以尝试给 Lab 3 的基础上或者是给 Lab 2 的 CPU 接上总线(现有的 YatCPU 的五阶段流水线+总线代码里面的一些逻辑问题在 Lab 3 实验代码中修复了,但是还没有合并到主仓库,Lab 4 用的还是主仓库现有的代码) + +此外同学们也可以思考如何做CPU设计架构的优化,比如现在的 CPU 只有 ID 和 MemoryAccess 阶段对总线有需求,所以总线仲裁比较简单(集成在 `CPU.scala` 里了)。但是如果后面再加上 MMU,那么 CPU 的总线主机模块应该由哪个模块使用,这部分逻辑就会变得更复杂点,而这部分逻辑其实是可以单独拿出来实现的。 + diff --git a/docs/docs/better-tut/practice/chisel-test.md b/docs/docs/better-tut/practice/chisel-test.md new file mode 100644 index 0000000..8737b33 --- /dev/null +++ b/docs/docs/better-tut/practice/chisel-test.md @@ -0,0 +1,44 @@ +# 快速测试 + +By: [:material-github: wu-kan](https://github.com/wu-kan) + +以下两个测试,按照自己使用的工具,完成其中一个即可。 + +## IDE 操作 + +以 Intellij IDEA IDE 为例,打开 `src/test` 目录下任意一个测试,在右边的代码编辑区域,在一些代码行的左边会显示绿色三角形图标,单击即可运行测试。 + +![idea-test-0](images/idea-test-0.png) + +测试结果会显示在窗口下方,如果有测试不通过, + +![idea-test-1](images/idea-test-1.png) + +## 命令行操作 + +在项目目录执行下述指令可以模拟运行本项目的测试,首次运行时需要联网自动下载必要的组件。 + +```bash +sbt test +``` + +如果要运行单个测试,比如只运行 InsturctionDecoderTest,执行下面命令。 + +``` +sbt "testOnly riscv.singlecycle.InstructionDecoderTest" +``` + +???+tips "设置代理" + 如果下载失败或者网络不稳定,可以尝试设置代理。请根据自己的实际情况填写代理地址。 + ```bash + export HTTPS_PROXY=http://127.0.0.1:1080 + export JAVA_OPTS="$JAVA_OPTS -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=1080" + ``` +???+tips "使用 Verilator 加速测试" + 如果测试运行太慢,可以[安装 Verilator 加速测试](../getting-started/windows.md#安装-msys2-和-verilator)。 + +如果成功执行,你会看到类似这样的输出。 + +```bash +[success] Total time: 385 s (06:25), completed Dec 15, 2021, 8:45:25 PM +``` diff --git a/docs/docs/better-tut/practice/cmake.md b/docs/docs/better-tut/practice/cmake.md new file mode 100644 index 0000000..28d4ed9 --- /dev/null +++ b/docs/docs/better-tut/practice/cmake.md @@ -0,0 +1,60 @@ +CMake 是一个支持多平台的构建工具,它可以使用一份配置文件配置源代码的编译和链接方法,就能在不同平台上构建。 + +## 配置编译工具链 + +默认情况下,CMake 会调用系统的默认编译工具链,编译出可以在计算机上运行的可执行文件。但是我们自己的电脑的 CPU 一般是 x86/ARM 指令集,编译出来的二进制不能在我们的 RISC-V CPU 上运行。所以,我们需要通过配置文件来指定自己的编译工具链。 + +### 工具链配置文件 + +在 C 语言程序项目目录下,创建 `toolchain.cmake` 文件,并在文件中配置好我们自己的编译工具链。 + +通过设置 `CMAKE_***_COMPILER` 变量,我们可以指定使用 clang 作为编译器。而 `CMAKE_***_COMPILER_TARGET` 则设置了交叉编译的编译目标三元组。编译目标三元组中,第一个分量代表目标指令集,第二个分量代表目标操作系统,第三个分量代表是可执行程序的格式。`riscv32-unknown-elf` 就表示我们编译的目标是 RISC-V 32 位指令集,在未知操作系统上运行的 ELF 格式的可执行程序。 + +另外,我们还需要指定编译器的初始额外参数,这些参数可以在 `CMAKE_***_FLAGS` 变量中指定。其中重要的是 `-fuse-ld=lld`、`-no-std` 和 `-static`。第一个参数告诉 CMake 使用 lld 作为链接器,而不是默认的 ld。第二个参数告诉 CMake 不使用标准库,而是使用自己的库。最后一个参数告诉编译使用静态链接,而不是动态链接。 + +```cmake +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR riscv32) + +set(triple riscv32-unknown-elf) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER clang) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) +set(CMAKE_AR llvm-ar CACHE FILEPATH "Archiver") +set(CMAKE_OBJCOPY llvm-objcopy) + +set(CMAKE_C_FLAGS_INIT "-mno-relax") +set(CMAKE_CXX_FLAGS_INIT "-mno-relax") +set(CMAKE_ASM_FLAGS_INIT "-mno-relax") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld -nostdlib -static -mno-relax") +set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld -nostdlib -static -mno-relax") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld -nostdlib -static -mno-relax") +``` + +## 使用 CMake 生成项目并编译 + +一个 CMake 文件中最重要的是 `add_library` 和 `add_executable` 指令。`add_library` 指令用于添加一个库目标,`add_executable` 指令用于添加一个可执行文件目标。为了方便测试,初始化程序运行栈的代码和链接脚本已经提供了,后续只需要在 `csrc` 文件夹中添加自己的 C 语言程序,并修改 `CMakeLists.txt` 文件添加可执行程序目标即可。 + +CMake 项目的编译需要两步,第一步是运行 `cmake` 生成平台相关的编译配置文件,在 Linux 下通常是 `Makefile`,在 Windows 下通常是 `Visual Studio` 的解决方案文件,或者微软的 `nmake` 工具的配置文件。第二步是运行 `make` 或者 `nmake` 等工具进行编译。 + +为了避免污染源代码目录,配置和编译通常在单独的文件夹中进行。项目提供的编译脚本 `build.sh`(Linux)或 `build.bat`(Windows)将配置和编译文件放在 `build` 文件夹中。 + +如果想要添加 C 语言程序,只需要在 `CMakeLists.txt` 的开头的: + +```cmake +set(C_PROGRAMS tetris hello fibonacci quicksort) +``` + +添加自己的 C 语言程序文件名,保存后然后重新运行一次编译脚本即可。 + +ELF 格式的文件可以使用 `llvm-objdump` 工具反汇编,在 `build` 文件夹中运行: + +```bash +llvm-objdump -d --arch-name=riscv32 程序名 +``` + +编译出来的 CPU 可执行文件放置在 `src/main/resources` 文件夹中,文件名后缀是 `.asmbin`。 diff --git a/docs/docs/better-tut/practice/compile-and-link.md b/docs/docs/better-tut/practice/compile-and-link.md new file mode 100644 index 0000000..e82cc48 --- /dev/null +++ b/docs/docs/better-tut/practice/compile-and-link.md @@ -0,0 +1,234 @@ +# 编译与链接 + +By: [:material-github: howardlau1999](https://github.com/howardlau1999)、[:material-github: NelsonCheung](https://github.com/NelsonCheung-cn) + +在之前的 C/C++ 课程中你已经学会了程序的入口是 `main()` 函数。但是 CPU 只认识地址,又如何知道一个程序从何处开始执行?我们又要如何编译一个我们自己写的 CPU 可以运行的程序并让它真正地跑起来?下面就将带你揭开 `main()` 函数幕后的秘密,并带你学习如何编译和链接,让我们写的 C 程序可以在我们自己写的 CPU 上运行起来。 + +## 一个简单的例子 + +为了便于阐述 C 程序的编译和链接的过程,我们首先准备一个简单的例子。 + +我们先编写一个 `print.h` 的头文件,声明一个名为 `print_something()` 的函数。 + +```c +#ifndef PRINT_H +#define PRINT_H + +void print_something(); + +#endif +``` + +然后,我们在 `print.c` 中实现这个函数。 + +```c +#include +#include "print.h" + +void print_something() { + printf("Hello YatCPU!\n"); +} +``` + +最后,我们在 `main.c` 中使用这个函数打印并输出。 + +```c +#include "print.h" + +int main() { + print_something(); +} +``` + +接下来,我们要开始编译运行,编译命令如下。 + +```shell +gcc -o main.out main.c +``` + +这条命令是编译 `main.c` ,然后生成可执行文件 `main.out` 。其中, `-o` 指定了生成的可执行文件的名称。但是,直接运行上述命令会出现以下错误。 + +``` +main.c:(.text+0xa): undefined reference to `print_something' +``` + +这是因为 `main.c` 不知道函数 `print_something` 的实现。而函数 `print_something` 的实现在文件 `print.c` 中,我们需要在编译命令中加上它,如下所示。 + +```shell +gcc -o main.out main.c print.c +``` + +上面的命令也可以这样写。 + +```shell +gcc main.c print.c -o main.out +``` + +在本例中,我们使用了 gcc 直接将代码 `main.c print.c print.h` 编译成可执行文件 `main.out` 的。实际上,C/C++ 编译器在编译代码时包含如下几个步骤。 + +- **预处理**。处理宏定义,如 `#include` , `#define` , `#ifndef` 等,生成**预处理文件**,一般以 `.i` 为后缀。 +- **编译**。将预处理文件转换成**汇编代码文件**,一般以 `.S` 为后缀。 +- **汇编**。将汇编代码文件文件转换成**可重定位文件**,一般以 `.o` 为后缀。 +- **链接**。将多个可重定位文件链接生成**可执行文件**,一般以 `.o` 为后缀。 + +下面我们分别来学习这四个过程。 + +## 预处理 + +预处理又被称为预编译,主要处理宏定义,如 `#include` , `#define` , `#ifndef` 等,并删除注释行,还会添加行号和文件名标识。在编译时,编译器会使用上述信息产生调试、警告和编译错误时需要用到的行号信息。 + +经过预编译生成的 `.i` 文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到 `.i` 文件中。在上面的例子中,我们对 `main.c` 进行预处理,生成 `main.i` 预处理文件,命令如下。 + +```shell +gcc -o main.i -E main.c +``` + +生成的内容如下。 + +``` +# 1 "main.c" +# 1 "" +# 1 "" +# 1 "/usr/include/stdc-predef.h" 1 3 4 +# 1 "" 2 +# 1 "main.c" +# 1 "print.h" 1 + + + +void print_something(); +# 2 "main.c" 2 + +int main() { + print_something(); +} +``` + +## 编译 + +编译则是将预处理文件转换成汇编代码文件(`.S` 文件)的过程,具体的步骤主要有:词法分析 -> 语法分析 -> 语义分析及相关的优化 -> 中间代码生成 -> 目标代码生成。 + +我们生成 `main.c` 代码对应的汇编代码。命令如下,其中, `-masm=intel` 是为了生成 Intel 风格的汇编代码,否则默认 AT&T 风格的代码。 + +```shell +gcc -o hello.s -S hello.c -masm=intel +``` + +生成的 `hello.s` 内容如下。 + +```asm + .file "main.c" + .intel_syntax noprefix + .text + .globl main + .type main, @function +main: +.LFB0: + .cfi_startproc + push rbp + .cfi_def_cfa_offset 16 + .cfi_offset 6, -16 + mov rbp, rsp + .cfi_def_cfa_register 6 + mov eax, 0 + call print_something + mov eax, 0 + pop rbp + .cfi_def_cfa 7, 8 + ret + .cfi_endproc +.LFE0: + .size main, .-main + .ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609" + .section .note.GNU-stack,"",@progbits +``` + +## 汇编 + +汇编阶段是将汇编代码编译成可重定位文件(Relocatable file)。汇编器(as)将 `main.S` 翻译成机器语言指令,把这些指令和其他附加的信息打包成可重定位文件( `.o` 文件)。可重定位文件是一个二进制文件,它的字节编码是机器语言指令而不是字符。如果我们在文本编译器中打开可重定位文件,看到的将是一堆乱码,生成命令如下。 + +```shell +gcc -o main.o -c main.c +``` + +在 Linux 下,可重定位文件的格式是ELF文件格式,其包含了ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)等信息。 + +## 链接 + +链接阶段就是把若干个可重定位文件( `.o` 文件)整合成一个可执行文件(Executable file)。上面已经提到,正是因为函数的实现可能分布在多个文件中,所以在链接阶段,我们需要提供函数实现的位置,然后最终生成可执行文件。链接阶段是通过链接器(ld)完成的,链接器将输入的可重定位文件加工后合成一个可执行文件。这些目标文件中往往有相互的数据、函数引用。例如,我们在 `main.c` 的main函数中引用了 `print.c` 中 `print_something` 函数的实现。 + +我们可以使用gcc来进行链接,命令如下。 + +```shell +gcc -o main.o -c main.c +gcc -o print.o -c print.c +gcc -o main.out main.o print.o +``` + +链接完成后,我们使用命令 `./main.out` 即可执行。事实上,上面三条语句等价于 + +```shell +gcc -o main.out main.c print.c +``` + +生成的两个 `main.out` 也是完全一样的。 + +同学们可能会感到奇怪为什么预处理、编译、汇编和链接都是使用gcc,而不是使用as、ld等,这是因为gcc全名叫GNU Compiler Collection,是一个编译工具的集合,我们可以简单理解为gcc内部会自动调用as、ld等。在本例中,我们只需简单使用gcc来完成上述过程即可。 + +链接实际上可以分为静态链接和动态链接两种方式。上面呈现的链接方式是静态链接。在静态链接时,链接器将函数的实现代码从给出的可重定位文件或静态链接库中拷贝到最终的可执行程序中,形成一个文件。由于可执行程序可能链接了多个可重定位文件,而在每一个可重定位文件中,各个函数的地址又是独立的。为了将这些函数放到可执文件中,需要为其重新分配地址。因此,在静态链接时,链接器会做两件事,一是符号解析,把可执行程序中用到的函数的声明和其在可重定位中的实现联系起来;二是重定位,把函数的起始地址和内存地址对应起来,然后修改所有对函数的引用。 + +静态链接会把所有函数的实现都包含进最终的可执行文件中,但这会使得可执行文件的大小变得非常大。注意到可执行文件最终会被加载到内存中执行,有没有一种办法能够让可执行文件中的函数的实现不必包含进文件中,而是在被调用时才加载进内存?这样就能够大大减小可执行文件的大小。这种链接方式被称为动态链接。在编译的链接阶段,动态链接库只提供符号表和其他少量信息用于保证所有符号引用都有定义,保证编译顺利通过。动态链接器(ld.so)在运行过程中根据记录的共享对象的符号定义来动态加载共享库,然后完成重定位。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。 + +两种链接方式各自都有优缺点。 + +静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了 `printf` 函数,则这多个程序中都含有 `printf.o` ,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。 + +动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多份副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。 + + +## 自定义链接脚本 + +上面我们都是使用的默认链接脚本来进行链接的,但是我们也可以自定义链接脚本来进行链接,这样可以更加灵活的控制链接的过程。我们可以通过 `-T` 选项来指定链接脚本,如下所示: + +```bash +$ gcc -T myscript.ld hello.o -o hello +``` + +而链接脚本的内容大致如下: + +``` +OUTPUT_ARCH( "riscv" ) +ENTRY(_start) + +SECTIONS +{ + . = 0x00001000; + .text : { *(.text.init) *(.text.startup) *(.text) } + .data ALIGN(0x1000) : { *(.data*) *(.rodata*) *(.sdata*) } + . = 0x00100000; + .bss : { *(.bss) } + _end = .; +} +``` + +链接脚本的第一行指定了输出的指令格式,也就是 RISC-V,第二行指定了程序的入口地址,也就是 `_start` 函数,第三行开始指定了程序的各个段的位置,其中 `.text` 段的起始地址为 `0x00001000` ,`.data` 段的起始地址在 `.text` 后面对齐 `0x1000` 的地址 ,`.bss` 段的起始地址为 `0x00100000`。最后一行指定了程序的结束地址,也就是 `_end` 变量的地址。 + +## ELF 格式简介 + +ELF(Executable and Linkable Format)是一种可执行文件格式,它是 Linux 系统中最常见的可执行文件格式。对于完成实验来说,ELF 的格式不需要了解具体的细节,只需要了解一些大概的结构即可。 + +在 ELF 中存储了一些程序的元数据,比如程序的入口地址、程序的段信息、程序的符号信息等等。其中,段信息是最重要的,因为它存储了程序的代码和数据,而符号信息则是用来支持调试的。代码段和数据段是程序运行时必须的,而符号信息则是调试时需要的,所以在实验中我们只需要关注代码段和数据段即可。 + +代码段通常命名为 `.text` ,数据段(通常存储字符串、常量等)通常命名为 `.data`。代码段在程序运行的时候,由操作系统加载到内存中,然后让 CPU 跳转到程序的入口地址执行。数据段在程序运行的时候,由操作系统加载到内存中,然后程序就可以访问这些数据了。 + +由于我们的 CPU 还没有操作系统,因此,我们直接在 Chisel 代码中将 CPU 的入口地址(`Parameters.scala` 文件中的 `EntryAddress`)修改为链接时的入口地址,然后将代码段和数据段加载到内存中,就可以直接执行程序了。程序和数据段在 Chisel 3 的综合阶段被作为 ROM 的初始化数据一并烧入 FPGA 逻辑中,`InstructionROM` 模块负责解析节选后的 ELF 文件,并生成对应的 ROM 初始化文件。而在 CPU 启动时加载到内存的功能是通过一个专门的模块 `ROMLoader` 来完成的,它负责从 `InstructionROM` 中读取数据,并将数据写入到内存中。当 `ROMLoader` 完成了拷贝操作后,CPU 就可以正式地开始执行程序了。 + +## CPU 可执行文件的生成 + +我们只需要 ELF 格式中的代码段和数据段,其他的段都暂时不需要。在链接器脚本中,我们将代码段和数据段分配到相邻的地址,简化后续实现。而代码段开始于 0x1000 地址,前面的空间是预留给程序运行的栈。然后,我们需要使用 `objcopy` 工具,单独将文件中的代码段和数据段拷贝到一个文件中,在这个文件里,只有二进制的代码和数据。 + +## 课后练习 + +1. 使用 `gcc` 将 `main.c` , `print.c` 编译成 `main.S` , `print.S` ,然后使用 `as` 将 `main.S` , `print.S` 编译成 `main.o` , `print.o` ,最后使用 `ld` 将 `main.o` , `print.o` 链接成一个可执行文件来执行。 +2. 使用 `gcc` 将 `print.c` 编译成静态链接库,然后使用 `gcc` 将 `main.c` 编译成可执行文件并在编译参数中指定静态链接库的位置。 +3. 使用 `gcc` 将 `print.c` 编译成动态链接库,然后使用 `gcc` 将 `main.c` 编译成可执行文件并在编译参数中指定动态链接库的位置。 \ No newline at end of file diff --git a/docs/docs/better-tut/practice/compliance-test.md b/docs/docs/better-tut/practice/compliance-test.md new file mode 100644 index 0000000..ff3bef3 --- /dev/null +++ b/docs/docs/better-tut/practice/compliance-test.md @@ -0,0 +1,93 @@ +# 配置 RISC-V 合规性测试 + +本节提供新旧两种RISC-V合规性测试的途径,这两种途径的测试效果是相似的。新框架测试(riscof)生成的测试报告更完整美观,旧框架测试的环境配置更简单一些。 + +## 使用 riscof 测试 + +新框架测试目前仅支持在 Linux 环境中运行。 + +### 安装必要组件 + +为了进行新框架的测试,我们需要安装的必要组件如下: + +- Python3.6 以及 pip3 +- RISCOF +- RISCV-GNU Toolchain +- Spike +- Architectual Tests + +首先自行安装 Python 3 以及 pip,然后安装 RISCOF: + +``` +pip3 install git+https://github.com/riscv/riscof.git +``` + +安装 RISCV-GNU Toolchain 以编译测试程序: + +``` +sudo apt install binutils-riscv64-unknown-elf gcc-riscv64-unknown-elf +``` + +编译 Spike 模拟器,作为参考实现: + +``` +sudo apt-get install device-tree-compiler +git clone https://github.com/riscv-software-src/riscv-isa-sim.git +cd riscv-isa-sim +mkdir build +cd build +../configure --prefix=/usr/local +make +sudo make install +``` + +使用下面的命令将 Architectual Tests 克隆到当前文件夹 + +``` +riscof --verbose info arch-test --clone +``` + +### 编译仿真器代码 + +参考[波形仿真一节](simulation.md#%E4%BD%BF%E7%94%A8-verilator-%E4%BB%BF%E7%9C%9F%E7%94%9F%E6%88%90%E6%B3%A2%E5%BD%A2%E6%96%87%E4%BB%B6)先编译出仿真器可执行程序。 + + +### 运行测试 + +``` +cd riscof-target +riscof --verbose info run --config ./config.ini --no-browser --suite YOURPATH/riscv-arch-test/riscv-test-suite/rv32i_m \ +--env YOURPATH/riscv-arch-test/riscv-test-suite/env +``` + +测试通过后会生成一个 HTML 格式的报告。报告位于 `riscof-target/rsicof_work/report.html`。 + +### 关于 riscof + +更多关于新测试框架 riscof 的资料,请参考[官方文档](https://riscof.readthedocs.io/en/latest/index.html)。 + +## 旧框架测试 + +RISC-V 合规性测试仅支持使用 Makefile 与 gcc 编译器运行,如果你使用的是 Windows,那么你应当配置好 mingw 环境,以便运行测试。如果你已经配置好 Verilator,那么合规性测试应当也可以正常运行。 + +### 编译仿真器代码 + +参考[波形仿真一节](simulation.md#%E4%BD%BF%E7%94%A8-verilator-%E4%BB%BF%E7%9C%9F%E7%94%9F%E6%88%90%E6%B3%A2%E5%BD%A2%E6%96%87%E4%BB%B6)先编译出仿真器可执行程序。 + +### 克隆合规性测试仓库 + +合规性测试仓库位于 [https://github.com/riscv-non-isa/riscv-arch-test](https://github.com/riscv-non-isa/riscv-arch-test),旧框架是该仓库的一个分支。在和 yatcpu 同级的目录下(注意不要克隆到 yatcpu 内)运行下面的命令。 + +``` +git clone -b old-framework-2.x https://github.com/riscv-non-isa/riscv-arch-test.git +``` + +克隆完成后,进入合规性测试仓库,配置环境变量,并编译运行测试: + +```bash +cd riscv-arch-test +export TARGET_SIM=~/yatcpu/verilog/verilator/obj_dir/VTop +export TARGETDIR=~/yatcpu/riscv-target +export RISCV_TARGET=yatcpu +make +``` diff --git a/docs/docs/better-tut/practice/coremark-benchmark.md b/docs/docs/better-tut/practice/coremark-benchmark.md new file mode 100644 index 0000000..e219a6d --- /dev/null +++ b/docs/docs/better-tut/practice/coremark-benchmark.md @@ -0,0 +1,26 @@ +# 配置 CoreMark 性能基准测试 + +CoreMark 是一套用于衡量 MCU 和 CPU 等设备的计算性能的基准测试,包含矩阵乘法、链表操作等测试。CoreMark 同样只能使用 Makefile 进行编译,但可以使用 Clang 编译器编译。 + +## 编译波形仿真程序 + +CoreMark 编译出来可以像我们自己写的程序一样加载到 CPU 运行,也可以先在电脑模拟仿真。如果想在电脑模拟仿真,需要先参考[波形仿真一节](simulation.md#波形仿真)先编译出仿真可执行程序。 + +## 克隆 CoreMark 仓库 + +这里同样将 CoreMark 仓库克隆到和我们 CPU 项目同级: + +```bash +git clone https://github.com/eembc/coremark +``` + +克隆完成后,将 CPU 项目内的可移植文件拷贝到 CoreMark 目录中(或创建软连接),然后编译 CoreMark 可执行文件,使用仿真器加载并运行: + +```bash +cp -r ~/yatcpu/coremark/yatcpu ~/coremark +cd ~/coremark +make PORT_DIR=yatcpu +~/yatcpu/verilog/verilator/obj_dir/VTop -instruction coremark.bin.asmbin -time 10000000000 +``` + +运行时间会比较长,大约需要 6 分钟才有输出。 \ No newline at end of file diff --git a/docs/docs/better-tut/practice/envvar-and-cmd.md b/docs/docs/better-tut/practice/envvar-and-cmd.md index 61c4126..ec11207 100644 --- a/docs/docs/better-tut/practice/envvar-and-cmd.md +++ b/docs/docs/better-tut/practice/envvar-and-cmd.md @@ -18,15 +18,15 @@ Writen By: [PurplePower](https://github.com/PurplePower) ???+ abstract "实践:打开一个终端窗口" === "Windows CMD" - ![打开一个Windows CMD 窗口](assets/open-cmd-windows.png){width=70%} + ![打开一个Windows CMD 窗口](images/open-cmd-windows.png){width=70%} 如图,使用 Win+R 快捷键调出运行窗口,输入 cmd 即可打开命令行窗口。 === "Windows Powershell" - ![打开一个 Powershell 终端窗口](assets/open-pwsl-windows.png){width=80%} + ![打开一个 Powershell 终端窗口](images/open-pwsl-windows.png){width=80%} 如图,使用 Win+R 快捷键调出运行窗口,输入 powershell 即可打开命令行窗口。您也可以使用其他终端软件,如 Windows Terminal等。 === "Linux" - ![打开一个 Linux 终端窗口](assets/open-terminal-linux.png){align=left, width=70%} + ![打开一个 Linux 终端窗口](images/open-terminal-linux.png){align=left, width=70%} 如图,如果您使用带 GUI 界面的 Linux,如 Ubuntu,则可以在桌面栏点击 Terminal 图标以打开一个可以输入文字的终端窗口。 @@ -39,21 +39,21 @@ Writen By: [PurplePower](https://github.com/PurplePower) echo %PATH% ``` - ![cmd 输出 PATH 环境变量内容](assets/cmd-echo-path.png) + ![cmd 输出 PATH 环境变量内容](images/cmd-echo-path.png) === "Windows Powershell" ```powershell echo $env:PATH ``` - ![powershell 输出 PATH 环境变量内容](assets/powershell-echo-path.png) + ![powershell 输出 PATH 环境变量内容](images/powershell-echo-path.png) === "Linux" ```bash echo $PATH ``` - ![bash 输出 PATH 环境变量内容](assets/bash-echo-path.png) + ![bash 输出 PATH 环境变量内容](images/bash-echo-path.png) 可以看到,在这几种 shell 中,如何指代环境变量的方法各有不同:Windows 上的 `cmd` 使用百分号来包裹环境变量名 `%%`,`powershell` 则使用 `$env:`,而 Linux 上的 `bash` 则使用 `$`。另外,Windows 和 Linux 的环境变量中多个值的分隔符也不同,Windows 使用分号 `;`,因为引号会作为盘符后的分隔符而存在于路径中,Linux 则使用引号 `:`。此外还有一个需要注意的点是,Windows上的环境变量名不区分大小写,而 Linux 上则是大小写敏感的。 @@ -76,11 +76,11 @@ PATH 环境变量是系统保留的环境变量名,它包含了一系列目录 您可按下图步骤打开系统变量编辑窗口: - ![打开环境变量面板](assets/win-modify-envvar-1.png) + ![打开环境变量面板](images/win-modify-envvar-1.png) 随后,您可以修改系统变量 PATH。例如,当您希望能直接在命令行键入 `vivado` 以正确调用 `E:\Xilinx\Vivado\2020.1\bin\vivado.bat` 时,您应添加该文件所在路径 `E:\Xilinx\Vivado\2020.1\bin`,而非包含文件名的路径。 - ![修改系统变量 PATH](assets/win-modify-envvar-2.png) + ![修改系统变量 PATH](images/win-modify-envvar-2.png) 随后,您需要逐步点击“确定”以保存修改。系统变量修改后,您需要重新打开命令行或 powershell 以使其生效。您可以重新打印 PATH 变量以检查是否正确设置。 diff --git a/docs/docs/better-tut/practice/hardware-debug.md b/docs/docs/better-tut/practice/hardware-debug.md new file mode 100644 index 0000000..61caeb8 --- /dev/null +++ b/docs/docs/better-tut/practice/hardware-debug.md @@ -0,0 +1,53 @@ +# 硬件调试 + +在烧板后,可能会出现硬件运行和软件模拟不一致的情况。这时候可以使用硬件调试的方式来排查问题。硬件调试需要占用一定的板上资源,使用 Vivado 提供的 Integrated Logic Analyzer(ILA)模块来实现。 + +在生成项目文件后,双击打开 `.xpr` 文件,启动 Vivado。在 Vivado 中,点击 "Create Block Design" 按钮,新建一个 Block Design。(Pynq Z1 开发板对应的项目已经有 Block Design,直接点击 "Open Block Design" 按钮即可,也不需要添加顶层模块,脚本已经添加并连接好了) + +![](./images/ila-0.png) + +然后,将 Sources 中的 Top 模块拖拽到 Block Design 中,运行 Run Block Automation 和 Run Connection Automation,将 Top 模块自动连接起来。之后,右键想要观察波形的信号,右键引脚,选择 Debug,之后再运行一次 Run Connection Automation,即可自动添加 ILA 模块并连接。 + +![](./images/ila-1.png) + +信号采样窗口大小受限于硬件资源,并且最大值是在 IP 核中设置的,需要在 Block Design 中,双击 ILA 核: + +![](./images/ila-9.png) + +在弹出的设置窗口中,可以设置采样数量和比较器数量: + +![](./images/ila-10.png) + +窗口的左边是预计消耗的硬件资源,如果消耗过多硬件资源可能会导致生成比特流失败,此时需要减少采样数量或者比较器数量。 + +之后右键 Sources 中的 Block Design,运行 Create HDL Wrapper,创建好对应的顶层模块后,按照正常流程生成比特流并烧入到开发板上。(Pynq Z1 也已经创建好,无需操作) + +![](./images/ila-2.png) + +![](./images/ila-3.png) + +在 Program Device 对话框中,应该可以看到 Debug probe files 中有自动填入的 `.ltx` 文件。 + +![](./images/ila-4.png) + +在烧板后,在 Hardware Manager 中,点击 Refresh Device,可以看到界面中自动检测到了 ILA 调试核,并打开了波形调试界面。 + +![](./images/ila-5.png) + +由于硬件资源有限,ILA 无法保存所有的波形,只能保存一定时间窗口内的若干采样。为了能够捕获我们需要调试的信号,需要先添加触发器,然后在触发器上设置触发条件。例如我们想调试 AXI 总线,可以点击加号,添加我们感兴趣的信号,然后设置触发条件。触发器的数量受限于 IP 核的设置。 + +![](./images/ila-6.png) + +如图,我们设置了当 `AWREADY` 信号为 1 的时候触发。之后,点击一个三角的按钮,即进入触发模式,当条件满足的时候,窗口就会显示触发点前后的时间窗口中的波形数据。 + +![](./images/ila-7.png) + +Vivado 会自动识别一些规范的信号,例如 AXI 总线的信号,并进一步分组分析,可以点击展开来查看原始数据。 + +点击 `>>` 也可以马上采样一次。 + +如果需要调整窗口大小和触发点位于窗口中的位置,可以在 `Settings` 中的 `Capture Mode Settings` 设置: + +![](./images/ila-8.png) + +需要注意采样窗口的大小的最大值受限于 IP 核的设置。 \ No newline at end of file diff --git a/docs/docs/better-tut/practice/assets/bash-echo-path.png b/docs/docs/better-tut/practice/images/bash-echo-path.png similarity index 100% rename from docs/docs/better-tut/practice/assets/bash-echo-path.png rename to docs/docs/better-tut/practice/images/bash-echo-path.png diff --git a/docs/docs/better-tut/practice/images/board.png b/docs/docs/better-tut/practice/images/board.png new file mode 100644 index 0000000..6781be7 Binary files /dev/null and b/docs/docs/better-tut/practice/images/board.png differ diff --git a/docs/docs/better-tut/practice/assets/cheatsheet-page1.png b/docs/docs/better-tut/practice/images/cheatsheet-page1.png similarity index 100% rename from docs/docs/better-tut/practice/assets/cheatsheet-page1.png rename to docs/docs/better-tut/practice/images/cheatsheet-page1.png diff --git a/docs/docs/better-tut/practice/assets/cheatsheet-page2.png b/docs/docs/better-tut/practice/images/cheatsheet-page2.png similarity index 100% rename from docs/docs/better-tut/practice/assets/cheatsheet-page2.png rename to docs/docs/better-tut/practice/images/cheatsheet-page2.png diff --git a/docs/docs/better-tut/practice/assets/cmd-echo-path.png b/docs/docs/better-tut/practice/images/cmd-echo-path.png similarity index 100% rename from docs/docs/better-tut/practice/assets/cmd-echo-path.png rename to docs/docs/better-tut/practice/images/cmd-echo-path.png diff --git a/docs/docs/better-tut/practice/images/download-jdk-0.png b/docs/docs/better-tut/practice/images/download-jdk-0.png new file mode 100644 index 0000000..7725816 Binary files /dev/null and b/docs/docs/better-tut/practice/images/download-jdk-0.png differ diff --git a/docs/docs/better-tut/practice/images/download-jdk-1.png b/docs/docs/better-tut/practice/images/download-jdk-1.png new file mode 100644 index 0000000..9f952da Binary files /dev/null and b/docs/docs/better-tut/practice/images/download-jdk-1.png differ diff --git a/docs/docs/better-tut/practice/images/download-scala-0.png b/docs/docs/better-tut/practice/images/download-scala-0.png new file mode 100644 index 0000000..edf39d9 Binary files /dev/null and b/docs/docs/better-tut/practice/images/download-scala-0.png differ diff --git a/docs/docs/better-tut/practice/images/gtkwave.png b/docs/docs/better-tut/practice/images/gtkwave.png new file mode 100644 index 0000000..a2c4239 Binary files /dev/null and b/docs/docs/better-tut/practice/images/gtkwave.png differ diff --git a/docs/docs/better-tut/practice/images/idea-0.png b/docs/docs/better-tut/practice/images/idea-0.png new file mode 100644 index 0000000..87a1610 Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-0.png differ diff --git a/docs/docs/better-tut/practice/images/idea-1.png b/docs/docs/better-tut/practice/images/idea-1.png new file mode 100644 index 0000000..5e079e6 Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-1.png differ diff --git a/docs/docs/better-tut/practice/images/idea-2.png b/docs/docs/better-tut/practice/images/idea-2.png new file mode 100644 index 0000000..3ea9c81 Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-2.png differ diff --git a/docs/docs/better-tut/practice/images/idea-3.png b/docs/docs/better-tut/practice/images/idea-3.png new file mode 100644 index 0000000..805bc49 Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-3.png differ diff --git a/docs/docs/better-tut/practice/images/idea-4.png b/docs/docs/better-tut/practice/images/idea-4.png new file mode 100644 index 0000000..544da18 Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-4.png differ diff --git a/docs/docs/better-tut/practice/images/idea-5.png b/docs/docs/better-tut/practice/images/idea-5.png new file mode 100644 index 0000000..7fdcd6c Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-5.png differ diff --git a/docs/docs/better-tut/practice/images/idea-6.png b/docs/docs/better-tut/practice/images/idea-6.png new file mode 100644 index 0000000..17143ea Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-6.png differ diff --git a/docs/docs/better-tut/practice/images/idea-7.png b/docs/docs/better-tut/practice/images/idea-7.png new file mode 100644 index 0000000..207ea0d Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-7.png differ diff --git a/docs/docs/better-tut/practice/images/idea-test-0.png b/docs/docs/better-tut/practice/images/idea-test-0.png new file mode 100644 index 0000000..83d7684 Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-test-0.png differ diff --git a/docs/docs/better-tut/practice/images/idea-test-1.png b/docs/docs/better-tut/practice/images/idea-test-1.png new file mode 100644 index 0000000..62028de Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-test-1.png differ diff --git a/docs/docs/better-tut/practice/images/idea-vcs.png b/docs/docs/better-tut/practice/images/idea-vcs.png new file mode 100644 index 0000000..3512408 Binary files /dev/null and b/docs/docs/better-tut/practice/images/idea-vcs.png differ diff --git a/docs/docs/better-tut/practice/images/ila-0.png b/docs/docs/better-tut/practice/images/ila-0.png new file mode 100644 index 0000000..9a05eca Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-0.png differ diff --git a/docs/docs/better-tut/practice/images/ila-1.png b/docs/docs/better-tut/practice/images/ila-1.png new file mode 100644 index 0000000..5185ae5 Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-1.png differ diff --git a/docs/docs/better-tut/practice/images/ila-10.png b/docs/docs/better-tut/practice/images/ila-10.png new file mode 100644 index 0000000..9c62026 Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-10.png differ diff --git a/docs/docs/better-tut/practice/images/ila-2.png b/docs/docs/better-tut/practice/images/ila-2.png new file mode 100644 index 0000000..4b2f3c4 Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-2.png differ diff --git a/docs/docs/better-tut/practice/images/ila-3.png b/docs/docs/better-tut/practice/images/ila-3.png new file mode 100644 index 0000000..25c176f Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-3.png differ diff --git a/docs/docs/better-tut/practice/images/ila-4.png b/docs/docs/better-tut/practice/images/ila-4.png new file mode 100644 index 0000000..4f272db Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-4.png differ diff --git a/docs/docs/better-tut/practice/images/ila-5.png b/docs/docs/better-tut/practice/images/ila-5.png new file mode 100644 index 0000000..316eb97 Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-5.png differ diff --git a/docs/docs/better-tut/practice/images/ila-6.png b/docs/docs/better-tut/practice/images/ila-6.png new file mode 100644 index 0000000..922958f Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-6.png differ diff --git a/docs/docs/better-tut/practice/images/ila-7.png b/docs/docs/better-tut/practice/images/ila-7.png new file mode 100644 index 0000000..735b192 Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-7.png differ diff --git a/docs/docs/better-tut/practice/images/ila-8.png b/docs/docs/better-tut/practice/images/ila-8.png new file mode 100644 index 0000000..d21ea52 Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-8.png differ diff --git a/docs/docs/better-tut/practice/images/ila-9.png b/docs/docs/better-tut/practice/images/ila-9.png new file mode 100644 index 0000000..42b1814 Binary files /dev/null and b/docs/docs/better-tut/practice/images/ila-9.png differ diff --git a/docs/docs/better-tut/practice/assets/open-cmd-windows.png b/docs/docs/better-tut/practice/images/open-cmd-windows.png similarity index 100% rename from docs/docs/better-tut/practice/assets/open-cmd-windows.png rename to docs/docs/better-tut/practice/images/open-cmd-windows.png diff --git a/docs/docs/better-tut/practice/assets/open-pwsl-windows.png b/docs/docs/better-tut/practice/images/open-pwsl-windows.png similarity index 100% rename from docs/docs/better-tut/practice/assets/open-pwsl-windows.png rename to docs/docs/better-tut/practice/images/open-pwsl-windows.png diff --git a/docs/docs/better-tut/practice/assets/open-terminal-linux.png b/docs/docs/better-tut/practice/images/open-terminal-linux.png similarity index 100% rename from docs/docs/better-tut/practice/assets/open-terminal-linux.png rename to docs/docs/better-tut/practice/images/open-terminal-linux.png diff --git a/docs/docs/better-tut/practice/assets/powershell-echo-path.png b/docs/docs/better-tut/practice/images/powershell-echo-path.png similarity index 100% rename from docs/docs/better-tut/practice/assets/powershell-echo-path.png rename to docs/docs/better-tut/practice/images/powershell-echo-path.png diff --git a/docs/docs/better-tut/practice/images/vivado-1.png b/docs/docs/better-tut/practice/images/vivado-1.png new file mode 100644 index 0000000..043e474 Binary files /dev/null and b/docs/docs/better-tut/practice/images/vivado-1.png differ diff --git a/docs/docs/better-tut/practice/images/vivado-2.png b/docs/docs/better-tut/practice/images/vivado-2.png new file mode 100644 index 0000000..a169233 Binary files /dev/null and b/docs/docs/better-tut/practice/images/vivado-2.png differ diff --git a/docs/docs/better-tut/practice/images/vivado-3.png b/docs/docs/better-tut/practice/images/vivado-3.png new file mode 100644 index 0000000..db6d203 Binary files /dev/null and b/docs/docs/better-tut/practice/images/vivado-3.png differ diff --git a/docs/docs/better-tut/practice/images/vivado-4.png b/docs/docs/better-tut/practice/images/vivado-4.png new file mode 100644 index 0000000..8bae16d Binary files /dev/null and b/docs/docs/better-tut/practice/images/vivado-4.png differ diff --git a/docs/docs/better-tut/practice/images/vs-build-tools-installer.png b/docs/docs/better-tut/practice/images/vs-build-tools-installer.png new file mode 100644 index 0000000..962c1fd Binary files /dev/null and b/docs/docs/better-tut/practice/images/vs-build-tools-installer.png differ diff --git a/docs/docs/better-tut/practice/images/vs-installer-clang.png b/docs/docs/better-tut/practice/images/vs-installer-clang.png new file mode 100644 index 0000000..406e92f Binary files /dev/null and b/docs/docs/better-tut/practice/images/vs-installer-clang.png differ diff --git a/docs/docs/better-tut/practice/images/vs-installer.png b/docs/docs/better-tut/practice/images/vs-installer.png new file mode 100644 index 0000000..62f0f75 Binary files /dev/null and b/docs/docs/better-tut/practice/images/vs-installer.png differ diff --git a/docs/docs/better-tut/practice/assets/win-modify-envvar-1.png b/docs/docs/better-tut/practice/images/win-modify-envvar-1.png similarity index 100% rename from docs/docs/better-tut/practice/assets/win-modify-envvar-1.png rename to docs/docs/better-tut/practice/images/win-modify-envvar-1.png diff --git a/docs/docs/better-tut/practice/assets/win-modify-envvar-2.png b/docs/docs/better-tut/practice/images/win-modify-envvar-2.png similarity index 100% rename from docs/docs/better-tut/practice/assets/win-modify-envvar-2.png rename to docs/docs/better-tut/practice/images/win-modify-envvar-2.png diff --git a/docs/docs/better-tut/practice/images/windows-verilator-path.png b/docs/docs/better-tut/practice/images/windows-verilator-path.png new file mode 100644 index 0000000..d0785d0 Binary files /dev/null and b/docs/docs/better-tut/practice/images/windows-verilator-path.png differ diff --git a/docs/docs/better-tut/practice/images/write-vcd-idea-1.png b/docs/docs/better-tut/practice/images/write-vcd-idea-1.png new file mode 100644 index 0000000..4856e7c Binary files /dev/null and b/docs/docs/better-tut/practice/images/write-vcd-idea-1.png differ diff --git a/docs/docs/better-tut/practice/images/write-vcd-idea-2.png b/docs/docs/better-tut/practice/images/write-vcd-idea-2.png new file mode 100644 index 0000000..2f45889 Binary files /dev/null and b/docs/docs/better-tut/practice/images/write-vcd-idea-2.png differ diff --git a/docs/docs/better-tut/practice/images/xshell-0.png b/docs/docs/better-tut/practice/images/xshell-0.png new file mode 100644 index 0000000..b3fe80f Binary files /dev/null and b/docs/docs/better-tut/practice/images/xshell-0.png differ diff --git a/docs/docs/better-tut/practice/images/xshell-1.png b/docs/docs/better-tut/practice/images/xshell-1.png new file mode 100644 index 0000000..6505d5f Binary files /dev/null and b/docs/docs/better-tut/practice/images/xshell-1.png differ diff --git a/docs/docs/better-tut/practice/program-device.md b/docs/docs/better-tut/practice/program-device.md new file mode 100644 index 0000000..e5d645e --- /dev/null +++ b/docs/docs/better-tut/practice/program-device.md @@ -0,0 +1,87 @@ +# 烧板验证 + +By: [:material-github: wu-kan](https://github.com/wu-kan) + + +## 生成 Verilog 文件 + +根据开发板型号,运行相应目录的 `Top.scala` 文件,生成的结果位于 `verilog/开发板名称` 目录下的 `Top.v`。 + +=== "命令行操作" + ```bash + sbt "runMain board.开发板型号.VerilogGenerator" + ``` + +=== "IDEA 操作" + 打开 `src/main/scala/board/开发板型号/Top.scala`,点击 `object VerilogGenerator extends App` 一行左边的绿色三角形运行即可。 + + 或者可以在 "sbt shell" 窗口中,等 sbt 启动完毕后,执行 `runMain board.开发板型号.VerilogGenerator`。 + +## 生成比特流二进制文件 + +下面的教程以 `basys3` 开发板为例,其他开发板可以自行替换。 + +执行下述指令,可以根据 `verilog/basys3/Top.v` 生成二进制文件 `vivado/basys3/riscv-basys3/riscv-basys3.runs/impl_1/Top.bit`。 + +=== "Windows" + 假设你的 Vivado 安装目录是 `C:\Xilinx`(其他目录自行修改): + ```bash + cd vivado\basys3 + C:\Xilinx\Vivado\2020.1\bin\vivado -mode batch -source generate_bitstream.tcl + ``` + +=== "Linux" + 假设你的 Vivado 安装目录是 `~/Xilinx`(其他目录自行修改): + + ```bash + cd vivado/basys3 + ~/Xilinx/Vivado/2020.1/bin/vivado -mode batch \ + -source ./generate_bitstream.tcl + ``` + +## 烧板 + +=== "Windows" + 假设你的 Vivado 安装目录是 `C:\Xilinx`(其他目录自行修改): + ```bash + cd vivado\basys3 + C:\Xilinx\Vivado\2020.1\bin\vivado -mode batch -source program_device.tcl + ``` + +=== "Linux" + 假设你的 Vivado 安装目录是 `~/Xilinx`(其他目录自行修改): + + ```bash + cd vivado/basys3 + ~/Xilinx/Vivado/2020.1/bin/vivado -mode batch \ + -source ./program_device.tcl + ``` + +=== "WSL" + 由于 WSL 下可以调用 `powershell`,我们可以回到 Windows 下烧板。Windows 下可以只装 Vivado Lab,和完整版的 Vivado 相比只有烧板功能,精简很多,安装包体积约为 1 GB。 + + ```bash + # 假设你在本项目的 vivado 目录下,同时 Windows 下 Vivado Lab 2020.1 安装在 C:\Xilinx 目录下 + powershell.exe 'C:\Xilinx\Vivado_Lab\2020.1\bin\vivado_lab.bat -mode batch -source .\program_device.tcl' + ``` + +后续将上述 `program_device.tcl` 换成 `generate_and_program.tcl` 可以将生成比特流和烧板在一个脚本中完成。 + +## 烧板结果 + +如果一切正常,在所有的开关处于下面的位置的时候,板上数码管应当显示 “SYSU” 的字母,如下图所示: + +![SYSU on Board](images/board.png) + +## 串口连接 + +Basys 3 的 USB 端口内置了 UART 转 USB 芯片,可以直接通过 USB 连接电脑。在完成 CPU 的 UART 中断处理后,我们可以使用串口的方式和 CPU 交互。 + +你可以使用任意一种串口工具,这里以 Windows 下的 Xshell 为例: + +1. 打开 Xshell,点击左上角的 “新建” 按钮,新建一个会话。在“协议”中选择串口。![](./images/xshell-0.png) + +2. 在“串口”中选择你的串口设备,波特率选择 115200,数据位 8,停止位 1,校验位 None。端口号在每一台电脑上都不一样,如果选择之后连接不上,可以尝试其他端口。![](./images/xshell-1.png) + +3. 点击“确定”后,选择刚刚新建的会话,点击“连接”按钮,即可连接到 Basys 3。在通讯有数据交换的时候,USB 端口有指示灯会闪烁。 + diff --git a/docs/docs/better-tut/practice/scala-and-chisel.md b/docs/docs/better-tut/practice/scala-and-chisel.md index 41665d0..9552137 100644 --- a/docs/docs/better-tut/practice/scala-and-chisel.md +++ b/docs/docs/better-tut/practice/scala-and-chisel.md @@ -95,9 +95,9 @@ Chisel 3 严格意义上并不是 Verilog 的等价替代,而是一个生成 [free chips project Chisel 3.6.0 Cheat Sheet](https://github.com/freechipsproject/chisel-cheatsheet/releases/download/3.6.0/chisel_cheatsheet.pdf) -![Cheatsheet1](assets/cheatsheet-page1.png) +![Cheatsheet1](images/cheatsheet-page1.png) -![Cheatsheet2](assets/cheatsheet-page2.png) +![Cheatsheet2](images/cheatsheet-page2.png) diff --git a/docs/docs/better-tut/practice/simulation.md b/docs/docs/better-tut/practice/simulation.md new file mode 100644 index 0000000..024b408 --- /dev/null +++ b/docs/docs/better-tut/practice/simulation.md @@ -0,0 +1,146 @@ +# 波形仿真 + +By: [:material-github: howardlau1999](https://github.com/howardlau1999) + +在烧板验证之前,可以使用波形仿真再进行一次测试。你可以使用 Vivado 或者 Verilator 进行波形仿真。使用 Verilator 仿真需要安装 C++ 编译器。 + +## 生成波形文件 + +### 测试时生成波形文件 + +在运行测试时,如果设置环境变量 `WRITE_VCD` 为 `1`,则会生成波形文件。 + +=== "Linux/macOS" + ```bash + WRITE_VCD=1 sbt test + ``` + +=== "Windows" + Powershell: + ```powershell + $Env:WRITE_VCD=1; sbt test + ``` + 或者命令提示符: + ```cmd + set WRITE_VCD=1 + sbt test + ``` + sbt 命令不存在的话可以按照 IDEA 方式运行测试。 + +=== "Intellij IDEA" + 先点击右上角三角形左边的下拉菜单,点 “Edit configurations...”,如果没有就先运行一次测试。 + ![](images/write-vcd-idea-1.png) + 在 “Environment Variables” 选项中,添加一个环境变量,名称为 `WRITE_VCD`,值为 `1`。 + ![](images/write-vcd-idea-2.png) + 之后点 OK 保存即可。 + +之后可以在 `test_run_dir` 目录下的各个子目录中找到 `.vcd` 文件,使用 GTKWave 打开即可,参考[查看波形文件](#查看波形文件)一节。 + +### 使用 Verilator 仿真生成波形文件 + +如果想快速测试自己编写的程序,可以使用 Verilator 进行仿真,仿真的主函数已经写好,位于 `verilog/verilator/sim_main.cpp`。在第一次运行以及每次修改了 Chisel 3 代码后,需要在项目根目录执行命令生成 Verilog 文件: + +=== "命令行操作" + ```bash + sbt "runMain board.verilator.VerilogGenerator" + ``` + +=== "IDEA 操作" + 打开 `src/main/scala/board/verilator/Top.scala`,点击 `object VerilogGenerator extends App` 一行左边的绿色三角形运行即可。 + + 或者可以在 "sbt shell" 窗口中,等 sbt 启动完毕后,执行 `runMain board.verilator.VerilogGenerator`。 + +之后,进入 `verilog/verilator` 目录,执行以下命令生成仿真程序: + +=== "Linux/macOS" + ```bash + verilator --cc --exe --trace --build Top.v sim_main.cpp + ``` + 或 + ```bash + verilator --cc --exe --trace Top.v sim_main.cpp + make -C obj_dir -f VTop.mk + ``` + +=== "Windows" + ```bash + verilator_bin --cc --exe --trace --build Top.v sim_main.cpp + ``` + 或 + ```bash + verilator_bin --cc --exe --trace Top.v sim_main.cpp + make -C obj_dir -f VTop.mk + ``` + +编译完成后,会生成 `obj_dir/VTop` (Windows 下为 `obj_dir/VTop.exe`)的可执行文件。该可执行文件可以传入参数从而运行不同的代码文件。参数以及其用法如下: + +|参数|用法| +|----|-----| +|`-memory`|指定仿真内存的大小,单位为字(4字节)。用例:`-memory 4096`| +|`-instruction`|指定用于初始化仿真内存的 RISC-V 程序。用例:`-instruction ~/yatcpu/src/main/resources/hello.asmbin`| +|`-signature`|指定仿真结束后,需要输出的内存范围以及目的文件。用例:`-signature 0x100 0x200 mem.txt`| +|`-halt`|指定停机标识符地址,往该内存地址写入 `0xBABECAFE` 即停止仿真。用例:`-halt 0x8000`| +|`-vcd`|指定仿真过程波形保存的文件名,不指定则不会生成波形文件。用例:`-vcd dump.vcd`| +|`-time`|指定最大仿真时间,注意时间是周期数的两倍。用例:`-time 1000`| + +例如,如果想加载 `hello.asmbin` 文件,仿真 1000 个周期,并将仿真波形保存到 `dump.vcd` 文件,可以运行: + +```bash +obj_dir/VTop -instruction ~/yatcpu/src/main/resources/hello.asmbin \ +-time 2000 -vcd dump.vcd +``` + +另外,使用 Verilator 仿真时,向内存地址 `0x40000010`(也即 UART 的 MMIO 地址)写入的数据将转换为字符输出到标准输出,可以用来实现 `printf` 等功能,方便调试。 + +### 使用 Vivado 仿真生成波形文件 + +确保你的 PATH 路径中包含 Vivado 的安装目录,然后运行命令: + +=== "Linux" + ```bash + make vivado-sim-basys3 + ``` + +=== "Windows" + ```powershell + cd vivado/basys3 + vivado -mode batch -source run_simulation.tcl + ``` + +将会生成 `vivado/basys3/riscv-basys3/riscv-basys3.sim/sim_1/behav/xsim/dump.vcd` 文件。 + +## 查看波形文件 + +### 使用 GTKWave 查看 VCD 格式波形 + +不同软件生成的 VCD 文件路径不一样: + +- Vivado: `vivado/basys3/riscv-basys3/riscv-basys3.sim/sim_1/behav/xsim/dump.vcd` +- Verilator:`-vcd` 选项指定的文件路径 +- 测试时生成:`test_run_dir` 目录下的各个子目录中的 `.vcd` 文件 + +如果你的操作系统带有 GUI 图形界面,可以使用 GTKWave + +- [Windows 版本](https://sourceforge.net/projects/gtkwave/files/gtkwave-3.3.100-bin-win64/),解压后双击运行 `bin\gtkwave.exe` +- [macOS 版本](https://sourceforge.net/projects/gtkwave/files/gtkwave-3.3.107-osx-app/) +- [Linux 源码](https://sourceforge.net/projects/gtkwave/files/gtkwave-3.3.111/) + +点击 "File->Open New Tab..." 打开对应的文件查看波形。 + +![gtkwave-windows](images/gtkwave.png) + +界面左侧是按照模块组织的树形结构,右键点击在菜单中选择 "Insert" 就可以将信号添加到窗口中了。 + +### 使用 Vivado 查看 WDB 格式波形 + +你也可以打开 Vivado 来查看波形,Vivado 支持的格式为 `.wdb`,路径是 `vivado/basys3/riscv-basys3/riscv-basys3.sim/sim_1/behav/xsim/test_behav.wdb`。在启动 Vivado 后,选择菜单栏的 "Flow->Open Static Simulation...",选择这个文件,然后点击 "Open" 按钮。 + +![vivado-1](images/vivado-1.png) + +![vivado-2](images/vivado-2.png) + +打开后,在左侧的窗口找到你感兴趣的模块,右键,选择 "Add to Wave Window",即可在波形窗口查看波形。 + +![vivado-3](images/vivado-3.png) + +![vivado-4](images/vivado-4.png) \ No newline at end of file diff --git a/docs/docs/better-tut/theory/bus.md b/docs/docs/better-tut/theory/bus.md new file mode 100644 index 0000000..141aaaa --- /dev/null +++ b/docs/docs/better-tut/theory/bus.md @@ -0,0 +1,87 @@ +# 总线 + +By: [:material-github: howardlau1999](https://github.com/howardlau1999) + +## 简介 + +CPU 和其他设备如果想要通信,那么就要和其他设备在物理上连接起来。如果每个设备都像内存一样,需要修改 CPU 的设计和 IO 端口才能连接,那么假如有 m 种 CPU 和 n 种外设,复杂度将成为 O(mn),不仅每个 CPU 都要适配所有的外设,而且还要把所有的外设都适配到所有 CPU 上,这样的复杂度是不现实的,同时也不灵活。这时,我们就得想到计算机科学的一条“金科玉律”: + +> Any problem in computer science can be solved with another level of indirection. [^1] + +[^1]: + +既然直接连接起来不现实,那么我们就通过引入一个通用的中间层,让大家都连接到这个中间层,不就把复杂度变为 O(m + n) 了吗?人们觉得这个想法不错,并给这种技术起了一个形象的名字:总线(Bus)。正如它的英文所展示的一样,数据不再是直接在 CPU 和外设间传递,而是先搭上总线这个“公交车”,到达目的地之后再“下车”。 + +当然,任何的额外的间接层都是有代价的,引入总线有两个问题需要解决: + +1. 数据怎么知道它的目的地是哪里? +2. 好几个设备都想同时发送数据,然而总线只允许一个设备发送,要如何仲裁? + +而且,作为一层通信协议,各家设备厂商都想设计一套最适合自己设备的协议,于是,总线协议也是百花齐放,有 PCI-e、I2C、USB、AXI 等等……不同总线协议也有不同的使用场景,应当支持哪些协议也是一个需要权衡的问题。 + +## AXI 总线通信协议 + +通常而言,总线在传输数据的时候,在保证总线没有被其他设备占用后,发起通信的一方会先发送控制信号,表明接下来传输的数据的设备地址和长度等,目标设备在收到控制信号后,就开始数据的传输过程。 + +AXI 协议是由 ARM 公司提出的总线通信协议,目前最新版本是 AXI4,并且提供了精简的 AXI4-Lite 协议。AXI 协议的主要特性列举如下: + +- 独立的读写通道 +- 允许多个活跃的地址,即主设备可以在前一操作未完成时发起新的读写操作。 +- 数据和地址传输没有严格限制,写入时既可地址再数据,也可反之。 +- 簇发传输(Burst) + + +本实验所用的总线协议是 AXI4-Lite,并实现一个基础的简单版本,上述的高级特性仅供有兴趣的同学实现。 +ARM 提供了 [AXI4 协议的简介](https://developer.arm.com/-/media/Arm%20Developer%20Community/PDF/Learn%20the%20Architecture/102202_0100_01_Introduction_to_AMBA_AXI.pdf?revision=369ad681-f926-47b0-81be-42813d39e132) 可供参考。 + + +AXI4上的读写的流程如下图所示: + +![axi](images/axi.png) + +左图是主设备发起的读流程,右图是主设备发起的写流程。相比起读流程,写流程多了一个写的回复响应,这是因为从设备内部可能有缓冲区,在数据真正写入完成之前,从设备可以不发送响应信号,从而保证数据的完整性。 + +AXI4 使用了 5 个 通道以完成上述地址、数据和响应信号传输,如下图所示: + +![axi-channels](images/axi-channels.png){width=80%} + +这些通道中,AW、W 和 B 通道负责写操作,AR 和 R 负责读操作。每个通道都遵循握手协议以完成传输,**握手** 由 `VALID` 和 `READY` 信号完成。 `VALID` 表示通道上的信号有效,例如主设备将 `AWVALID` 置 1 时,说明此时 `AWADDR` 的值是有效的写地址。`READY` 信号表示准备好接受数据了,例如从设备将 `AWREADY` 置 1 时,说明其准备好接受写入地址。 + +当双方发现 `VALID` 和 `READY` 同时为 1 时,双方握手完成,表明数据成功传达。 +下面展示了两个握手的时序样例: + + +![axi-hs-eg1](images/axi-handshake-eg1.png){width=90%} +/// caption +握手示例1:主设备将信息和 `VALID` 置 1 后,从设备置 1 `READY` 完成握手。 +/// + + +![axi-hs-eg2](images/axi-handshake-eg2.png){width=90%} +/// caption +握手示例2:从设备提前置 1 `READY`,主设备将 `VALID` 置 1 后完成握手。 +/// + +了解了通道的握手机制后,就可以按前述读写流程进行读写操作了。 + +![axi-write](images/axi-write-transaction.png) +/// caption +写操作时序图:主设备先在 AW 写地址通道传输写地址,发起一次写操作。W 通道再将写的数据传输至从设备(实际上 AXI4 协议不要求地址和数据的先后关系,W 也可以在 AW 前先传输)。此时从设备得知写操作的信息,写入完成后通过 B 通道回应。 +/// + + +![axi-read](images/axi-read-transaction.png) +/// caption +读操作时序图:主设备在 AR 读地址通道传输读地址,发起一次读操作。从设备随后在 R 通道将读出数据传回。 +/// + + +## 总线仲裁 + +由于总线是多个设备共享的,如果多个设备同时发起通信,那么信号会产生冲突,导致传输过程无法正常进行。为了避免冲突的发生,所有设备在通信之前,都应该检测总线是否占用。 + +为此,每一个设备则需要增加总线请求线以及总线授权线,连接到总线仲裁器。在设备需要通过总线传输数据前,需要先通过总线请求线请求总线的访问权限。总线仲裁器则通过总线授权线来授予访问权限,从而避免设备之间的冲突。 + +## 总线交换机 + +由于总线仲裁的方法可能会导致一些设备长时间的等待,总线也可以使用类似网络交换机的方式连接,不同对的设备之间可以通过交换机同时进行通信。 \ No newline at end of file diff --git a/docs/docs/better-tut/theory/images/axi-channels.png b/docs/docs/better-tut/theory/images/axi-channels.png new file mode 100644 index 0000000..62894cc Binary files /dev/null and b/docs/docs/better-tut/theory/images/axi-channels.png differ diff --git a/docs/docs/better-tut/theory/images/axi-handshake-eg1.png b/docs/docs/better-tut/theory/images/axi-handshake-eg1.png new file mode 100644 index 0000000..b798b92 Binary files /dev/null and b/docs/docs/better-tut/theory/images/axi-handshake-eg1.png differ diff --git a/docs/docs/better-tut/theory/images/axi-handshake-eg2.png b/docs/docs/better-tut/theory/images/axi-handshake-eg2.png new file mode 100644 index 0000000..8fcfd74 Binary files /dev/null and b/docs/docs/better-tut/theory/images/axi-handshake-eg2.png differ diff --git a/docs/docs/better-tut/theory/images/axi-read-transaction.png b/docs/docs/better-tut/theory/images/axi-read-transaction.png new file mode 100644 index 0000000..5afa1c6 Binary files /dev/null and b/docs/docs/better-tut/theory/images/axi-read-transaction.png differ diff --git a/docs/docs/better-tut/theory/images/axi-write-transaction.png b/docs/docs/better-tut/theory/images/axi-write-transaction.png new file mode 100644 index 0000000..25e7362 Binary files /dev/null and b/docs/docs/better-tut/theory/images/axi-write-transaction.png differ diff --git a/docs/docs/better-tut/theory/riscv-isa.md b/docs/docs/better-tut/theory/riscv-isa.md index 8850983..44946fe 100644 --- a/docs/docs/better-tut/theory/riscv-isa.md +++ b/docs/docs/better-tut/theory/riscv-isa.md @@ -27,9 +27,9 @@ RISC-V 有六种基本的指令类型: [RISC-V Assembly Reference Card](https://www.cl.cam.ac.uk/teaching/1617/ECAD+Arch/files/docs/RISCVGreenCardv8-20151013.pdf) ??? abstract "RISC-V 基础整数指令格式及汇编语法 refcard" - ![](images/rv-refcard-page1.png) + ![](../cheatsheets/images/riscv-refcard-page1-fmt-32i.png) - ![](images/rv-asm-refcard-page1.png) + ![](../cheatsheets/images/riscv-asm-refcard-page1.png) TODO: asm example and godbolt showcase diff --git a/docs/docs/connect_ddr_mem_try.md b/docs/docs/connect_ddr_mem_try.md new file mode 100644 index 0000000..82fbbef --- /dev/null +++ b/docs/docs/connect_ddr_mem_try.md @@ -0,0 +1,23 @@ +# 尝试使用 DDR3 板载内存 + +本文档记录使用 AXI4 接口使用 Zynq 开发板板载 DDR3 内存的尝试过程及经验总结。 + +## 进展 + +- 2025-08-24 基本知悉 通过 Zynq PS AXI_HP 连接 DDR 控制器的方法 + + +## 注意事项 + +- 将 ILA 排除在工具链以外,ILA 无法检测板上 AXI 事务 +- 通过将 LED 接入 wvalid 信号以检测 traffic generator 是否有写出数据 +- read traffic generator 运行若干个事务后会停止 +- PS 上运行程序时,会独占 DDR 控制器,PL 无法使用 +- EMIO 似乎可以使得 PL 信号直达 UART +- 使用 AXI Interconnect 时,Address Editor 似乎无法为其分配地址,但是否影响功能尚未测试 + +## TODO + +- 在 chisel 中编写简单部件,测试仅 PL 能否正确读写 DDR 内存,包括测试 AXI 实现的正确性 +- 将所有 Top 的内存拆除,并引出 AXI4Lite 接口至外部 +- diff --git a/docs/docs/index.md b/docs/docs/index.md index 959fc03..996c0dc 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -4,7 +4,7 @@ Writen By: [Tokisakix](https://github.com/Tokisakix) YatCPU (Yet another toy CPU,逸芯) 是一款开源、开发中的教学用 RISC-V 处理器,基于 Chisel 硬件设计语言实现,并用于中山大学 (Sun Yat-sen University) 计算机学院冯班组成原理实验课程的教学。同样欢迎其他高校相关课程使用! -本仓库由 [Tokisakix](https://github.com/Tokisakix)、[PurplePower](https://github.com/PurplePower)、[Han Huang](https://github.com/HHTheBest) 在 [2022-fall-yatcpu-repo](https://github.com/hrpccs/2022-fall-yatcpu-repo) 的基础上结合 2023 计组教学实情整理而来,有较多原创内容 +本仓库由 [Tokisakix](https://github.com/Tokisakix)、[PurplePower](https://github.com/PurplePower)、[Han Huang](https://github.com/HHTheBest) 在 [2022-fall-yatcpu-repo](https://github.com/hrpccs/2022-fall-yatcpu-repo) 的基础上结合 2023 计组教学实情 及原 YatCPU 教学文档整理而来,改进了实验流程,有较多原创内容 非常感谢 [xy3](https://github.com/xy3xy3) 在此仓库开发期间多次帮我测试代码文件 @@ -23,7 +23,8 @@ YatCPU (Yet another toy CPU,逸芯) 是一款开源、开发中的教学用 RI - 提供基于 Vscode + Dev Container 一键环境配置工具和配套文档 - 已提前用 cmake 编译好测试文件,无须再运行 build.batbuild.sh -- 完全保留了原教学仓库的代码段填空设置和项目架构,代码迁移便利 +- 基本保留了原教学仓库的代码段填空设置和项目架构,代码迁移便利 +- 改进了教程内容,对原教程中同学感到疑惑的部分增加引导和说明 - 支持 Basys3、Pynq、Verilator、ZYBO-Z710 四款开发板的一键烧录,学生可以专心把精力集中在 CPU 的编写 ## 如何使用本整合仓库? @@ -37,4 +38,4 @@ YatCPU (Yet another toy CPU,逸芯) 是一款开源、开发中的教学用 RI - [YatCPU 文档地址](https://yatcpu.sysu.tech) - [YatCPU 的 Dev Container 环境配置](http://tokisakix.cn/2023/11/14/%5BDocker%5D%20YatCPU%20%E7%9A%84%20Dev%20container%20%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/) -- [测试 Tokisakix 的烧板文件](https://blog.skyw.cc/archives/258.html) \ No newline at end of file +- [测试 Tokisakix 的烧板文件](https://blog.skyw.cc/archives/258.html) diff --git a/docs/docs/references.md b/docs/docs/references.md new file mode 100644 index 0000000..e2bfb55 --- /dev/null +++ b/docs/docs/references.md @@ -0,0 +1,40 @@ +# 参考资料 + +## 参考书 + +The RISC-V Reader: An Open Architecture Atlas(RISC-V 手册:一本开源指令集的指南) [[PDF](http://riscvbook.com/chinese/RISC-V-Reader-Chinese-v2p1.pdf)] + +RISC-V Green Card [[PDF](http://riscvbook.com/greencard-20181213.pdf)] + +Processor Microarchitecture: An Implementation Perspective [[PDF](https://cseweb.ucsd.edu/classes/fa14/cse240A-a/pdf/04/Gonzalez_Processor_Microarchitecture_2010_Claypool.pdf)] + +## 手册 + +RISC-V 指令手册卷 I(非特权指令) [[PDF](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf)] + +RISC-V 指令手册卷 II(特权指令) [[PDF](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf)] + +Chisel 语言在线互动教程 [[Web](https://mybinder.org/v2/gh/freechipsproject/chisel-bootcamp/master)] + +Vivado 2020.1 Tcl 脚本指令手册 [[PDF](https://www.xilinx.com/support/documentation/sw_manuals/xilinx2020_1/ug835-vivado-tcl-commands.pdf)] + +## 工具 + +在线 RISC-V 整数指令集解释器 [[Web](https://www.cs.cornell.edu/courses/cs3410/2019sp/riscv/interpreter/)] + + +## 其他开源实现 + +香山处理器 [:material-github: OpenXiangshan/Xiangshan](https://github.com/OpenXiangshan/Xiangshan) + +riscv-sodor [:material-github: ucb-bar/riscv-sodor](https://github.com/ucb-bar/riscv-sodor) + +riscv-mini [:material-github: ucb-bar/riscv-mini](https://github.com/ucb-bar/riscv-mini) + +tinyriscv [liangkangnan/tinyriscv](https://gitee.com/liangkangnan/tinyriscv) + +riscv-boom [:material-github: riscv-boom/riscv-boom](https://github.com/riscv-boom/riscv-boom) + +rocket-chip [:material-github: chipsalliance/rocket-chip](https://github.com/chipsalliance/rocket-chip) + +UC Davis In-order CPU [:material-github: jlpteaching/dinocpu](https://github.com/jlpteaching/dinocpu) \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 49eb4b1..e6ea344 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -4,23 +4,47 @@ copyright: Copyright © 2023 Tokisakix nav: - 前言: index.md -- 如何一键环境配置: env.md -- 如何一键烧板: board.md + + - 最终验收: test.md - better-tut/intro.md +- 实验环境配置: + - better-tut/getting-started/index.md + - better-tut/getting-started/windows.md + - better-tut/getting-started/linux-wsl.md + - better-tut/getting-started/macos.md + # - better-tut/getting-started/docker.md + - 一键配置 docker 环境: env.md + +- 基础工程知识与技能: + - better-tut/practice/envvar-and-cmd.md + - better-tut/practice/scala-and-chisel.md + - better-tut/practice/chisel-test.md + - better-tut/practice/simulation.md + - better-tut/practice/compliance-test.md + - better-tut/practice/coremark-benchmark.md + - better-tut/practice/program-device.md + - better-tut/practice/hardware-debug.md + - better-tut/practice/compile-and-link.md + - better-tut/practice/cmake.md + - 如何一键烧板: board.md + - 理论知识基础: - better-tut/theory/cpu-arch.md - better-tut/theory/riscv-isa.md - better-tut/theory/interrupt-and-exception.md -- 基础工程知识与技能: - - better-tut/practice/envvar-and-cmd.md - - better-tut/practice/scala-and-chisel.md + - better-tut/theory/bus.md - 实验: - better-tut/labs/lab1/lab1-single-cycle-cpu.md - better-tut/labs/lab2/lab2-interrupt.md - better-tut/labs/lab3/lab3-pipelined-cpu.md - - + - better-tut/labs/lab4/lab4-bus.md + - better-tut/labs/challenge1/challenge1-running-os.md +- 快速参考资料: + - better-tut/cheatsheets/riscv-isa.md + - better-tut/cheatsheets/ide-tips.md +- references.md +- acknowledgement.md theme: name: material @@ -34,6 +58,7 @@ markdown_extensions: permalink: "🚁" - pymdownx.superfences - admonition + - footnotes - pymdownx.details - pymdownx.tabbed: alternate_style: true @@ -43,4 +68,10 @@ markdown_extensions: - tables - def_list - pymdownx.tasklist: - custom_checkbox: true \ No newline at end of file + custom_checkbox: true + - pymdownx.keys + +plugins: + - glightbox: + zoomable: true + width: 100% \ No newline at end of file diff --git a/lab4/csrc/CMakeLists.txt b/lab4/csrc/CMakeLists.txt index 1605a1f..5429758 100644 --- a/lab4/csrc/CMakeLists.txt +++ b/lab4/csrc/CMakeLists.txt @@ -3,8 +3,15 @@ cmake_minimum_required(VERSION 3.18) project(yatcpu-programs C CXX ASM) # Setting variables -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 --target=riscv32-unknown-elf -march=rv32i -mabi=ilp32") -set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -O0 --target=riscv32-unknown-elf -march=rv32i -mabi=ilp32") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -march=rv32i -mabi=ilp32") +set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -O0 -march=rv32i -mabi=ilp32") + +if (CMAKE_C_COMPILER EQUAL "clang") + # append --target=riscv32-unknown-elf to flags + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --target=riscv32-unknown-elf") + set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} --target=riscv32-unknown-elf") +endif() + set(C_PROGRAMS tetris hello fibonacci quicksort paging tetris_mmu say_goodbye) set(ASM_PROGRAMS mmio sb) set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/link.lds) @@ -39,5 +46,10 @@ foreach(program IN LISTS C_PROGRAMS ASM_PROGRAMS PROGRAMS) POST_BUILD COMMAND ${CMAKE_OBJCOPY} ARGS ${OBJCOPY_ARGS} $ ${CMAKE_SOURCE_DIR}/${DEST_DIR}/${program}.asmbin ) + add_custom_command( + TARGET ${program} + POST_BUILD + COMMAND ${CMAKE_OBJDUMP} ARGS -d $ > ${CMAKE_SOURCE_DIR}/${DEST_DIR}/${program}.dump + ) endforeach() diff --git a/lab4/csrc/build.bat b/lab4/csrc/build.bat deleted file mode 100644 index b5a522b..0000000 --- a/lab4/csrc/build.bat +++ /dev/null @@ -1,3 +0,0 @@ -rmdir /Q /S build -cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -G"NMake Makefiles" -B build . -cmake --build build \ No newline at end of file diff --git a/lab4/csrc/build.cl.bat b/lab4/csrc/build.cl.bat new file mode 100644 index 0000000..a82eba3 --- /dev/null +++ b/lab4/csrc/build.cl.bat @@ -0,0 +1,3 @@ +rmdir /Q /S build +cmake -DCMAKE_TOOLCHAIN_FILE="./toolchain.cl.cmake" -G"NMake Makefiles" -B build . +cmake --build build \ No newline at end of file diff --git a/lab4/csrc/build.cl.sh b/lab4/csrc/build.cl.sh new file mode 100644 index 0000000..3d7a7a2 --- /dev/null +++ b/lab4/csrc/build.cl.sh @@ -0,0 +1,3 @@ +#!/bin/sh +rm -rf build +cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cl.cmake -B build . && cmake --build build --parallel `nproc` diff --git a/lab4/csrc/build.gnu.bat b/lab4/csrc/build.gnu.bat new file mode 100644 index 0000000..75a5182 --- /dev/null +++ b/lab4/csrc/build.gnu.bat @@ -0,0 +1,3 @@ +rmdir /Q /S build +cmake -DCMAKE_TOOLCHAIN_FILE="./toolchain.riscv-gnu.cmake" -G"Unix Makefiles" -B build . +cmake --build build \ No newline at end of file diff --git a/lab4/csrc/build.gnu.sh b/lab4/csrc/build.gnu.sh new file mode 100644 index 0000000..23dca5d --- /dev/null +++ b/lab4/csrc/build.gnu.sh @@ -0,0 +1,3 @@ +#!/bin/sh +rm -rf build +cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.riscv-gnu.cmake -B build . && cmake --build build --parallel `nproc` diff --git a/lab4/csrc/build.sh b/lab4/csrc/build.sh deleted file mode 100644 index e48a4ba..0000000 --- a/lab4/csrc/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -rm -rf build -cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -B build . && cmake --build build --parallel `nproc` diff --git a/lab4/csrc/fibonacci.c b/lab4/csrc/fibonacci.c index 9905dde..e173fd1 100644 --- a/lab4/csrc/fibonacci.c +++ b/lab4/csrc/fibonacci.c @@ -19,4 +19,6 @@ int fib(int a) { int main() { *(int *)(4) = fib(10); + *(int*)(0) = 0xbabecafe; // anchor for program finished + return 0; } \ No newline at end of file diff --git a/lab4/csrc/say_goodbye.c b/lab4/csrc/say_goodbye.c index 3f551aa..5d4cdd8 100644 --- a/lab4/csrc/say_goodbye.c +++ b/lab4/csrc/say_goodbye.c @@ -15,30 +15,31 @@ void waste_some_time(int cycle) { int main() { - // const char* s = "abcd"; - const char* s = "Never gonna give you up~ Never gonna let you down~\n" - "Never gonna run around and~ desert you~\n"; + // const char* s = "abcd"; + const char* s = "Never gonna give you up~ Never gonna let you down~\n" + "Never gonna run around and~ desert you~\n"; - while (1) { - + while (1) { + - // for (int i = 0; i < ; i++) { - // uart_send_char(s[i]); - // waste_some_time(100); - // } - - const char *p = s; - while (*p != 0) - { - uart_send_char(*p); - p++; - waste_some_time(500); - } + // for (int i = 0; i < ; i++) { + // uart_send_char(s[i]); + // waste_some_time(100); + // } + const char *p = s; + while (*p != 0) + { + uart_send_char(*p); + p++; waste_some_time(500); - - break; // print once, but pressing CPU reset can print again } - return 0; + waste_some_time(500); + + break; // print once, but pressing CPU reset can print again + } + + *(int*)(0) = 0xbabecafe; // anchor for program finished + return 0; } diff --git a/lab4/csrc/toolchain.cmake b/lab4/csrc/toolchain.cl.cmake similarity index 95% rename from lab4/csrc/toolchain.cmake rename to lab4/csrc/toolchain.cl.cmake index 60bc5a9..a15ce50 100644 --- a/lab4/csrc/toolchain.cmake +++ b/lab4/csrc/toolchain.cl.cmake @@ -11,6 +11,7 @@ set(CMAKE_ASM_COMPILER clang) set(CMAKE_ASM_COMPILER_TARGET ${triple}) set(CMAKE_AR llvm-ar CACHE FILEPATH "Archiver") set(CMAKE_OBJCOPY llvm-objcopy) +set(CMAKE_OBJDUMP llvm-objdump) set(CMAKE_C_FLAGS_INIT "-mno-relax") set(CMAKE_CXX_FLAGS_INIT "-mno-relax") diff --git a/lab4/csrc/toolchain.riscv-gnu.cmake b/lab4/csrc/toolchain.riscv-gnu.cmake new file mode 100644 index 0000000..9fa06cf --- /dev/null +++ b/lab4/csrc/toolchain.riscv-gnu.cmake @@ -0,0 +1,21 @@ +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR riscv32) + +set(triple riscv32-unknown-elf) + +set(CMAKE_C_COMPILER riscv64-unknown-elf-gcc) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER riscv64-unknown-elf-g++) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) +set(CMAKE_ASM_COMPILER riscv64-unknown-elf-gcc) +set(CMAKE_ASM_COMPILER_TARGET ${triple}) +# set(CMAKE_AR llvm-ar CACHE FILEPATH "Archiver") +set(CMAKE_OBJCOPY riscv64-unknown-elf-objcopy) +set(CMAKE_OBJDUMP riscv64-unknown-elf-objdump) + +# set(CMAKE_C_FLAGS_INIT "-mno-relax") +# set(CMAKE_CXX_FLAGS_INIT "-mno-relax") +# set(CMAKE_ASM_FLAGS_INIT "-mno-relax") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-nostdlib -static ") +set(CMAKE_MODULE_LINKER_FLAGS_INIT "-nostdlib -static") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "-nostdlib -static") diff --git a/lab4/src/main/resources/fibonacci.asmbin b/lab4/src/main/resources/fibonacci.asmbin index 8a5a1a2..a0935a9 100644 Binary files a/lab4/src/main/resources/fibonacci.asmbin and b/lab4/src/main/resources/fibonacci.asmbin differ diff --git a/lab4/src/main/resources/quicksort.asmbin b/lab4/src/main/resources/quicksort.asmbin index 386fa9e..b58f60d 100644 Binary files a/lab4/src/main/resources/quicksort.asmbin and b/lab4/src/main/resources/quicksort.asmbin differ diff --git a/lab4/src/main/scala/bus/AXI4Lite.scala b/lab4/src/main/scala/bus/AXI4Lite.scala index 4ef120f..b2f75fe 100644 --- a/lab4/src/main/scala/bus/AXI4Lite.scala +++ b/lab4/src/main/scala/bus/AXI4Lite.scala @@ -91,26 +91,26 @@ class AXI4LiteChannels(addrWidth: Int, dataWidth: Int) extends Bundle { } class AXI4LiteSlaveBundle(addrWidth: Int, dataWidth: Int) extends Bundle { - val read = Output(Bool()) - val write = Output(Bool()) - val read_data = Input(UInt(dataWidth.W)) - val read_valid = Input(Bool()) + val read = Output(Bool()) // tell slave device to read + val write = Output(Bool()) // tell slave device to write + val read_data = Input(UInt(dataWidth.W)) // data read from slave device + val read_valid = Input(Bool()) // indicates if read_data is valid val write_data = Output(UInt(dataWidth.W)) val write_strobe = Output(Vec(Parameters.WordSize, Bool())) val address = Output(UInt(addrWidth.W)) } class AXI4LiteMasterBundle(addrWidth: Int, dataWidth: Int) extends Bundle { - val read = Input(Bool()) - val write = Input(Bool()) + val read = Input(Bool()) // request a read transaction + val write = Input(Bool()) // request a write transaction val read_data = Output(UInt(dataWidth.W)) val write_data = Input(UInt(dataWidth.W)) val write_strobe = Input(Vec(Parameters.WordSize, Bool())) val address = Input(UInt(addrWidth.W)) - val busy = Output(Bool()) - val read_valid = Output(Bool()) - val write_valid = Output(Bool()) + val busy = Output(Bool()) // if busy, master is not ready to accept new transactions + val read_valid = Output(Bool()) // indicates read transaction done successfully and asserts for ONLY 1 cycle. + val write_valid = Output(Bool()) // indicates write transaction done successfully and asserts for ONLY 1 cycle. } object AXI4LiteStates extends ChiselEnum { diff --git a/lab4/src/main/scala/peripheral/DummyMaster.scala b/lab4/src/main/scala/peripheral/DummyMaster.scala index ced75f5..2afa3f8 100644 --- a/lab4/src/main/scala/peripheral/DummyMaster.scala +++ b/lab4/src/main/scala/peripheral/DummyMaster.scala @@ -23,11 +23,17 @@ class DummyMaster extends Module { val io = IO(new Bundle { val channels = new AXI4LiteChannels(Parameters.AddrBits, Parameters.DataBits) }) - val master = Module(new AXI4LiteMaster(Parameters.AddrBits, Parameters.DataBits)) - master.io.channels <> io.channels - master.io.bundle.write_strobe := VecInit(Seq.fill(Parameters.WordSize)(false.B)) - master.io.bundle.write_data := 0.U - master.io.bundle.write := false.B - master.io.bundle.read := false.B - master.io.bundle.address := 0.U + // NOTE: not using AXI4LiteMaster to save resources + io.channels.read_address_channel.ARVALID := false.B + io.channels.read_address_channel.ARADDR := 0.U + io.channels.read_address_channel.ARPROT := 0.U + io.channels.read_data_channel.RREADY := false.B + + io.channels.write_address_channel.AWVALID := false.B + io.channels.write_address_channel.AWADDR := 0.U + io.channels.write_address_channel.AWPROT := 0.U + io.channels.write_data_channel.WVALID := false.B + io.channels.write_data_channel.WDATA := 0.U + io.channels.write_data_channel.WSTRB := 0.U + io.channels.write_response_channel.BREADY := false.B } diff --git a/lab4/src/main/scala/peripheral/DummySlave.scala b/lab4/src/main/scala/peripheral/DummySlave.scala index cdd98d3..88e8f7c 100644 --- a/lab4/src/main/scala/peripheral/DummySlave.scala +++ b/lab4/src/main/scala/peripheral/DummySlave.scala @@ -18,15 +18,19 @@ import bus.{AXI4LiteChannels, AXI4LiteSlave} import chisel3._ import riscv.Parameters -// A dummy AXI4 slave that only returns 0 on read -// and ignores all writes +// A dummy AXI4 slave that doesn't respond to anything class DummySlave extends Module { val io = IO(new Bundle { val channels = Flipped(new AXI4LiteChannels(4, Parameters.DataBits)) }) - val slave = Module(new AXI4LiteSlave(Parameters.AddrBits, Parameters.DataBits)) - slave.io.channels <> io.channels - slave.io.bundle.read_valid := true.B - slave.io.bundle.read_data := 0xDEADBEEFL.U + io.channels.read_address_channel.ARREADY := false.B + io.channels.read_data_channel.RVALID := false.B + io.channels.read_data_channel.RDATA := 0.U + io.channels.read_data_channel.RRESP := 0.U + + io.channels.write_address_channel.AWREADY := false.B + io.channels.write_data_channel.WREADY := false.B + io.channels.write_response_channel.BVALID := false.B + io.channels.write_response_channel.BRESP := 0.U } diff --git a/lab4/src/test/scala/riscv/BusTest.scala b/lab4/src/test/scala/riscv/BusTest.scala index 9e9fe1a..44377ab 100644 --- a/lab4/src/test/scala/riscv/BusTest.scala +++ b/lab4/src/test/scala/riscv/BusTest.scala @@ -14,11 +14,43 @@ package riscv -import bus.{AXI4LiteMaster, AXI4LiteMasterBundle, AXI4LiteSlave, AXI4LiteSlaveBundle} +import scala.util.Random +import scala.util.control.Breaks._ import chisel3._ import chiseltest._ import org.scalatest.flatspec.AnyFlatSpec -import peripheral.{Memory, ROMLoader} +import peripheral.{Memory, ROMLoader, DummyMaster, DummySlave} +import bus.{AXI4LiteMaster, AXI4LiteMasterBundle, AXI4LiteSlave, AXI4LiteSlaveBundle} + + +// ======================================= +// Common Functions for Flexible tests +// ======================================= + +object FlexibleTestHelper { + def init_write_transaction(bundle:AXI4LiteMasterBundle, clk: Clock, address: UInt, data: UInt, strobe: UInt) = { + bundle.read.poke(false.B) + bundle.write.poke(true.B) + bundle.address.poke(address) + bundle.write_data.poke(data) + for (i <- 0 until 4) bundle.write_strobe(i).poke(strobe(i)) + clk.step() // this cycle is not counted into transaction + bundle.write.poke(false.B) + bundle.address.poke(0.U) + bundle.write_data.poke(0.U) + } + + def init_read_transaction(bundle:AXI4LiteMasterBundle, clk: Clock, address: UInt) = { + bundle.read.poke(true.B) + bundle.write.poke(false.B) + bundle.address.poke(address) + clk.step() // this cycle is not counted into transaction + bundle.read.poke(false.B) + bundle.address.poke(0.U) + } +} + + class TimerTest extends AnyFlatSpec with ChiselScalatestTester { class TestTimerLimit extends Module { @@ -35,34 +67,54 @@ class TimerTest extends AnyFlatSpec with ChiselScalatestTester { behavior of "Timer" it should "read and write the limit" in { - test(new TestTimerLimit).withAnnotations(TestAnnotations.annos) { - c => - c.io.bundle.read.poke(false.B) - c.io.bundle.write.poke(true.B) - c.io.bundle.address.poke(0x4.U) - c.io.bundle.write_data.poke(0x990315.U) - c.clock.step() + test(new TestTimerLimit).withAnnotations(TestAnnotations.annos) { c => + var (write_success, read_success) = (false, false) + + // ----------------------------- + // initiate a write transaction to timer limit + FlexibleTestHelper.init_write_transaction(c.io.bundle, c.clock, 0x4.U, 0x990315.U, 0xF.U) + + // check correct write + breakable {for (i <- 1 until 20) { + if (c.io.bundle.write_valid.peekBoolean()) { + c.io.limit.expect(0x990315.U) + write_success = true + break() + } c.io.bundle.busy.expect(true.B) - c.io.bundle.write.poke(false.B) - c.io.bundle.address.poke(0x0.U) - c.io.bundle.write_data.poke(0.U) - c.clock.step(8) - c.io.bundle.busy.expect(false.B) - c.io.bundle.write_valid.expect(true.B) - c.io.limit.expect(0x990315.U) - c.io.bundle.read.poke(true.B) - c.io.bundle.address.poke(0x4.U) c.clock.step() + }} + + c.clock.step(2) + c.io.bundle.busy.expect(false.B) // master should be available soon after transaction + if (!write_success) throw new Exception("Timer write test failed") + + // ----------------------------- + // initiate a read transaction to timer limit + FlexibleTestHelper.init_read_transaction(c.io.bundle, c.clock, 0x4.U) + + // check correct read + breakable { for (i <- 1 until 20) { + if (c.io.bundle.read_valid.peekBoolean()) { + c.io.bundle.read_data.expect(0x990315.U) + read_success = true + break() + } c.io.bundle.busy.expect(true.B) - c.clock.step(6) - c.io.bundle.busy.expect(false.B) - c.io.bundle.read_valid.expect(true.B) - c.io.bundle.read_data.expect(0x990315.U) + c.clock.step() + }} + + c.clock.step(2) + c.io.bundle.busy.expect(false.B) // master should be available soon after transaction + if (!read_success) throw new Exception("Timer read test failed") + + println("Timer test passed") } } } -class MemoryTest extends AnyFlatSpec with ChiselScalatestTester { + +class MemoryTestF extends AnyFlatSpec with ChiselScalatestTester { class MemoryTest extends Module { val io = IO(new Bundle { val bundle = new AXI4LiteMasterBundle(Parameters.AddrBits, Parameters.DataBits) @@ -75,90 +127,397 @@ class MemoryTest extends AnyFlatSpec with ChiselScalatestTester { master.io.bundle <> io.bundle master.io.bundle.write_strobe := VecInit(io.write_strobe.asBools) master.io.channels <> memory.io.channels - memory.io.debug_read_address := 0.U } behavior of "Memory" it should "perform read and write" in { test(new MemoryTest).withAnnotations(TestAnnotations.annos) { c => - c.io.bundle.read.poke(false.B) - c.io.bundle.write.poke(true.B) - c.io.write_strobe.poke(0xF.U) - c.io.bundle.address.poke(0x4.U) - c.io.bundle.write_data.poke(0xDEADBEEFL.U) + var (write_success, read_success) = (false, false) + + // ------------------------- + // initiate write transaction + FlexibleTestHelper.init_write_transaction(c.io.bundle, c.clock, 0x4.U, 0xDEADBEEFL.U, 0xF.U) + + breakable { for (i <- 1 until 20) { + if (c.io.bundle.write_valid.peekBoolean()) { + write_success = true + break() + } + c.io.bundle.busy.expect(true.B) + c.clock.step() + }} + + c.clock.step(2) + c.io.bundle.busy.expect(false.B) // master should be available soon after transaction + if (!write_success) throw new Exception("Memory write test failed") + + // ------------------------ + // initialte read transaction + FlexibleTestHelper.init_read_transaction(c.io.bundle, c.clock, 0x4.U) + + breakable { for (i <- 1 until 20) { + if (c.io.bundle.read_valid.peekBoolean()) { + c.io.bundle.read_data.expect(0xDEADBEEFL.U) + read_success = true + break() + } + c.io.bundle.busy.expect(true.B) + c.clock.step() + }} + c.clock.step() - c.io.bundle.busy.expect(true.B) - c.io.bundle.write.poke(false.B) - c.io.bundle.address.poke(0x0.U) - c.io.bundle.write_data.poke(0.U) - c.clock.step(8) c.io.bundle.busy.expect(false.B) - c.io.bundle.write_valid.expect(true.B) - c.io.bundle.read.poke(true.B) - c.io.bundle.address.poke(0x4.U) - c.clock.step() - c.io.bundle.busy.expect(true.B) - c.clock.step(6) - c.io.bundle.busy.expect(false.B) - c.io.bundle.read_valid.expect(true.B) - c.io.bundle.read_data.expect(0xDEADBEEFL.U) + if (!read_success) throw new Exception("Memory read test failed") + + println("Memory test passed") } } } -class ROMLoaderTest extends AnyFlatSpec with ChiselScalatestTester { + +class ROMLoaderTestF extends AnyFlatSpec with ChiselScalatestTester { + /* + + ┌──────────────────────────────────┐ + │ ROMLoader │ ┌───────────┐ + │ │ │ Memory │ + ┌──────────────────┐ ├───────────┐ ┌───────┤ │ │ + │ ◄─────┼─rom_addr │ │ │ AXI ├────────┐ │ + │ Instruction ROM │ │ │ │ master┼─────► slave │ │ + │ ┼─────┼►rom_data ├◄────────────►│ │ │ │ │ + └──────────────────┘ ├───────────┘ └───────┤ ├──┬─────┘ │ + │ │ │ │ │ + │ │ └──┼────────┘ + └──────────────────────────────────┘ │ + ▼ + ROM contents + */ class ROMLoaderTest extends Module { val io = IO(new Bundle { val rom_address = Output(UInt(32.W)) - val rom_data = Input(UInt(32.W)) val load_start = Input(Bool()) val load_address = Input(UInt(32.W)) val load_finished = Output(Bool()) - val bundle = new AXI4LiteSlaveBundle(32, 32) - }) + val slave_bundle = new AXI4LiteSlaveBundle(32, 32) - val rom_loader = Module(new ROMLoader(2)) - rom_loader.io.rom_data := io.rom_data + val init_address = Input(UInt(32.W)) + val init_data = Input(UInt(32.W)) + val init_enable = Input(Bool()) + }) + + val rom = SyncReadMem(32, UInt(Parameters.DataBits.W)) + val rom_loader = Module(new ROMLoader(rom.length.toInt)) + + rom_loader.io.rom_data := rom.read(rom_loader.io.rom_address, true.B) rom_loader.io.load_start := io.load_start rom_loader.io.load_address := io.load_address io.load_finished := rom_loader.io.load_finished io.rom_address := rom_loader.io.rom_address val slave = Module(new AXI4LiteSlave(Parameters.AddrBits, Parameters.DataBits)) - slave.io.bundle <> io.bundle + slave.io.bundle <> io.slave_bundle slave.io.channels <> rom_loader.io.channels slave.io.bundle.read_data := 0.U + + when(io.init_enable) { + rom.write(io.init_address, io.init_data) + } } - + /* + ROMLoader works as a loader to move program from ROM to RAM. + */ behavior of "ROMLoader" - it should "load program" in { + it should "load program through AXI to mem" in { test(new ROMLoaderTest).withAnnotations(TestAnnotations.annos) { c => - c.io.load_address.poke(0x100.U) + + // ---------------------------- + // init ROM with some data + c.io.init_enable.poke(true.B) + c.io.init_address.poke(0.U) + c.io.init_data.poke(0.U) + c.clock.step() + for (i <- 0 until c.rom.length.toInt) { + val data = ((i + 1997) * 23753).U + // val data = (i + 1).U + c.io.init_address.poke(i.U) + c.io.init_data.poke(data) + c.clock.step() + } + c.io.init_enable.poke(false.B) + c.clock.step(2) + + // -------------------------------- + // read from ROMLoader and check the data + c.io.load_address.poke(0x0.U) c.io.load_start.poke(true.B) c.clock.step() c.io.load_start.poke(false.B) - c.io.rom_address.expect(0x0.U) - c.clock.step(8) - c.io.bundle.write.expect(true.B) - c.io.bundle.address.expect(0x100.U) - c.clock.step(4) - c.io.rom_address.expect(0x1.U) - c.clock.step(7) - c.io.rom_address.expect(0x1.U) - c.io.bundle.write.expect(true.B) - c.io.bundle.address.expect(0x104.U) - c.clock.step() - c.io.rom_address.expect(0x1.U) - c.clock.step(3) + + for (i <- 0 until c.rom.length.toInt) { + var word_write_done = false + breakable { for (j <- 0 until 20) { + while (c.io.slave_bundle.write.peekBoolean()) { // loop until `write` is set to low for this word + c.io.slave_bundle.address.expect((i*4).U) + c.io.slave_bundle.write_data.expect(((i + 1997) * 23753).U) + word_write_done = true + c.clock.step() + } + if (word_write_done) break() + c.clock.step() + }} + if (!word_write_done) throw new Exception(s"Write for word $i failed after 20 cycles") + } + + c.clock.step(2) c.io.load_finished.expect(true.B) + c.io.slave_bundle.write.expect(false.B) + + println(s"Successfully tested with ROM of ${c.rom.length.toInt} words") } } } +class FunctionalTest extends AnyFlatSpec with ChiselScalatestTester { + class TestBox extends Module { + val io = IO(new Bundle { + val master = new AXI4LiteMasterBundle(Parameters.AddrBits, Parameters.DataBits) + val slave = new AXI4LiteSlaveBundle(Parameters.AddrBits, Parameters.DataBits) + val readout_data = Input(UInt(Parameters.DataBits.W)) + val detach = Input(Bool()) // detach slave and master connection, used for simulating busy + }) + + val master = Module(new AXI4LiteMaster(Parameters.AddrBits, Parameters.DataBits)) + val slave = Module(new AXI4LiteSlave(Parameters.AddrBits, Parameters.DataBits)) + val dm = Module(new DummyMaster) + val ds = Module(new DummySlave) + + master.io.bundle <> io.master + slave.io.bundle <> io.slave + when (io.detach) { + master.io.channels <> ds.io.channels + slave.io.channels <> dm.io.channels + } + .otherwise { + master.io.channels <> slave.io.channels + dm.io.channels <> ds.io.channels + } + + // slave response is put here since chiseltest will take value poked just now for `expect` and `peek`, + // which may be quite different from wave form. + when (io.slave.read) { // slave device responds to read request + slave.io.bundle.read_data := io.readout_data + slave.io.bundle.read_valid := true.B + } + } + + // this test also benchmarks cycles of write transaction + behavior of "Write Function" + it should "write data with correct response" in { + test(new TestBox).withAnnotations(TestAnnotations.annos) { c => + var address = 0x4.U + var data = 0xDEADBEEFL.U + val strobe = 0xE.U(Parameters.WordSize.W) + + var cycle_passed = 0 + var cycle_arrive_slave = -1 + var cycle_write = -1 + var cycle_next = -1 // cycles taken that master is ready for next transaction + + + // ------------------------ + // start write transaction + FlexibleTestHelper.init_write_transaction(c.io.master, c.clock, address, data, strobe) + + + // ------------------------ + // check correct write at slave + breakable { while (true) { + if (c.io.slave.write.peekBoolean()) { // check when `write` asserted + c.io.slave.address.expect(address) + c.io.slave.write_data.expect(data) + for (i <- 0 until 4) c.io.slave.write_strobe(i).expect(strobe(i)) + cycle_arrive_slave = cycle_passed + } + + if (c.io.master.write_valid.peekBoolean()) { + // master knows write succeeded, make sure `write_valid` is high for only 1 cycle + if (cycle_write == -1) cycle_write = cycle_passed + else throw new Exception("Write test failed: write_valid is high for more than 1 cycle") + } + + if (!c.io.master.busy.peekBoolean()) { + cycle_next = cycle_passed + break() + } + + if (cycle_passed > 20) { + throw new Exception("Write test failed: transaction seems never ends after 20 cycles") + } + + c.io.master.busy.expect(true.B) + c.clock.step() + cycle_passed += 1 + }} + c.clock.step() + + if (cycle_arrive_slave == -1) { + throw new Exception("Write test failed: no data arrives slave") + } + if (cycle_write == -1) { + throw new Exception("Write test failed: no write response arrives master") + } + + println(s"Write transaction cost ${cycle_write} cycles, ${cycle_next} cycles for next transaction") + } + } + + + + behavior of "Read Function" + it should "read correct data" in { + test(new TestBox).withAnnotations(TestAnnotations.annos) { c => + var address = 0x4.U + var data = 0xDEADBEEFL.U + + var cycle_passed = 0 + var cycle_arrive_slave = -1 + var cycle_read = -1 // read out data arrives at master + var cycle_next = -1 // cycles taken that master is ready for next transaction + + + // ---------------------- + // start read transaction + FlexibleTestHelper.init_read_transaction(c.io.master, c.clock, address) + + + // ----------------------- + // check correct read at slave + breakable {while (true) { + if (c.io.slave.read.peekBoolean()) { // check when `read` asserted + c.io.slave.address.expect(address) + cycle_arrive_slave = cycle_passed + } + + if (c.io.master.read_valid.peekBoolean()) { + // master knows read succeeded, make sure `read_valid` is high for only 1 cycle + if (cycle_read == -1) cycle_read = cycle_passed + else throw new Exception("Read test failed: read_valid is high for more than 1 cycle") + } + + if (!c.io.master.busy.peekBoolean()) { + cycle_next = cycle_passed + break() + } + + if (cycle_passed > 20) { + throw new Exception("Read test failed: transaction seems never ends after 20 cycles") + } + + c.io.master.busy.expect(true.B) + c.clock.step() + cycle_passed += 1 + }} + c.clock.step() + + if (cycle_arrive_slave == -1) { + throw new Exception("Read test failed: no data arrives slave") + } + if (cycle_read == -1) { + throw new Exception("Read test failed: no read data arrives master") + } + + println(s"Read transaction cost ${cycle_read} cycles for data read, ${cycle_next} cycles for next transaction") + } + } + + + + behavior of "Bus" + it should "handle continuous transactions" in { + test(new TestBox).withAnnotations(TestAnnotations.annos) { c => + val num_transactions = 1000 + var num_success_transactions = 0 + var total_cycles = 0 + var slave_trans_to_expect = List[Tuple3[String, Long, Long]]() + + // set random seed for benchmark or debug + val seed = 1919810 + Random.setSeed(seed) + + + + def poke_write_transaction() { + val addr = Random.nextLong(0xFFFFFFFFL) + val data = Random.nextLong(0xFFFFFFFFL) + FlexibleTestHelper.init_write_transaction(c.io.master, c.clock, addr.U, data.U, 0xF.U) + c.io.master.busy.expect(true.B) + slave_trans_to_expect :+= ("write", addr, data) + } + + def poke_read_transaction() { + val addr = Random.nextLong(0xFFFFFFFFL) + val data = Random.nextLong(0xFFFFFFFFL) + FlexibleTestHelper.init_read_transaction(c.io.master, c.clock, addr.U) + c.io.readout_data.poke(data.U) + slave_trans_to_expect :+= ("read", addr, data) // record + } + + breakable { while (true) { + + if (!c.io.master.busy.peekBoolean()) { + c.io.detach.poke(true.B) // to simulate that slave is busy and disconnect with master + + // randomly poke read or write transactions + if (Random.nextBoolean()) poke_write_transaction() + else poke_read_transaction() + + c.clock.step(3) // slave busy for this long time, while master waits for it + c.io.detach.poke(false.B) + } + + if (slave_trans_to_expect.length > 0) { + val (trans_type, addr, data) = slave_trans_to_expect.head + + if (trans_type == "write") { + if (c.io.slave.write.peekBoolean()) { + c.io.slave.address.expect(addr.U) + c.io.slave.write_data.expect(data.U) + for (i <- 0 until 4) c.io.slave.write_strobe(i).expect(1.U) + + println(s"Write transaction to address 0x${addr.toHexString} with data 0x${data.toHexString} success") + num_success_transactions += 1 + slave_trans_to_expect = slave_trans_to_expect.tail // pop the head + } + } + else { + if (c.io.slave.read.peekBoolean()) { + c.io.slave.address.expect(addr.U) + c.io.readout_data.expect(data.U) + + println(s"Read transaction from address 0x${addr.toHexString} with expected data 0x${data.toHexString} success") + num_success_transactions += 1 + slave_trans_to_expect = slave_trans_to_expect.tail // pop the head + } + } + } + + if (num_success_transactions >= num_transactions) { + println(s"All $num_transactions transactions success in $total_cycles cycles with seed $seed") + break() + } + + c.clock.step() + total_cycles += 1 + }} + + } + } +} + diff --git a/lab4/src/test/scala/riscv/fivestage/CPUTest.scala b/lab4/src/test/scala/riscv/fivestage/CPUTest.scala index 6ba6625..e8054d0 100644 --- a/lab4/src/test/scala/riscv/fivestage/CPUTest.scala +++ b/lab4/src/test/scala/riscv/fivestage/CPUTest.scala @@ -14,12 +14,13 @@ package riscv.fivestage -import board.basys3.BootStates -import bus.BusSwitch +import scala.util.control.Breaks._ import chisel3._ import chisel3.util.{is, switch} import chiseltest._ import org.scalatest.flatspec.AnyFlatSpec +import board.basys3.BootStates +import bus.BusSwitch import peripheral.{DummySlave, Memory, ROMLoader} import riscv.core.fivestage.{CPU, ProgramCounter} import riscv.{Parameters, TestAnnotations} @@ -113,36 +114,57 @@ class TestTopModule(exeFilename: String) extends Module { class FibonacciTest extends AnyFlatSpec with ChiselScalatestTester { - behavior of "Five Stage CPU" + behavior of "CPU" it should "calculate recursively fibonacci(10)" in { test(new TestTopModule("fibonacci.asmbin")).withAnnotations(TestAnnotations.annos) { c => + c.clock.setTimeout(100 * 1000) c.io.interrupt.poke(0.U) - for (i <- 1 to 100) { - c.clock.step(1000) - c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout - } + c.io.mem_debug_read_address.poke(0.U) + + var cycle_passed = 0 + breakable { while (true) { + c.clock.step() + cycle_passed += 1 + if (c.io.mem_debug_read_data.peek().litValue == 0xbabecafeL) { + c.clock.step(100) + break() + } + }} c.io.mem_debug_read_address.poke(4.U) c.clock.step() c.io.mem_debug_read_data.expect(55.U) + + print(s"Cycles passed: $cycle_passed") } } } class QuicksortTest extends AnyFlatSpec with ChiselScalatestTester { - behavior of "Five Stage CPU" + behavior of "CPU" it should "quicksort 10 numbers" in { test(new TestTopModule("quicksort.asmbin")).withAnnotations(TestAnnotations.annos) { c => + c.clock.setTimeout(50 * 1000) c.io.interrupt.poke(0.U) - for (i <- 1 to 50) { - c.clock.step(1000) - c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout - } + c.io.mem_debug_read_address.poke(0.U) + + var cycle_passed = 0 + breakable { while (true) { + c.clock.step() + cycle_passed += 1 + if (c.io.mem_debug_read_data.peek().litValue == 0xbabecafeL) { + c.clock.step(100) + break() + } + }} + for (i <- 1 to 10) { c.io.mem_debug_read_address.poke((4 * i).U) c.clock.step() c.io.mem_debug_read_data.expect((i - 1).U) } + + print(s"Cycles passed: $cycle_passed") } } } diff --git a/lab4/src/test/scala/riscv/threestage/CPUTest.scala b/lab4/src/test/scala/riscv/threestage/CPUTest.scala index 0b83f0d..00ee8c4 100644 --- a/lab4/src/test/scala/riscv/threestage/CPUTest.scala +++ b/lab4/src/test/scala/riscv/threestage/CPUTest.scala @@ -14,12 +14,13 @@ package riscv.threestage -import board.basys3.BootStates -import bus.BusSwitch +import scala.util.control.Breaks._ import chisel3._ import chisel3.util.{is, switch} import chiseltest._ import org.scalatest.flatspec.AnyFlatSpec +import board.basys3.BootStates +import bus.BusSwitch import peripheral.{DummySlave, Memory, ROMLoader} import riscv.core.threestage.{CPU, ProgramCounter} import riscv.{Parameters, TestAnnotations} @@ -116,15 +117,25 @@ class FibonacciTest extends AnyFlatSpec with ChiselScalatestTester { behavior of "CPU" it should "calculate recursively fibonacci(10)" in { test(new TestTopModule("fibonacci.asmbin")).withAnnotations(TestAnnotations.annos) { c => + c.clock.setTimeout(100 * 1000) c.io.interrupt.poke(0.U) - for (i <- 1 to 100) { - c.clock.step(1000) - c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout - } + c.io.mem_debug_read_address.poke(0.U) + + var cycle_passed = 0 + breakable { while (true) { + c.clock.step() + cycle_passed += 1 + if (c.io.mem_debug_read_data.peek().litValue == 0xbabecafeL) { + c.clock.step(100) + break() + } + }} c.io.mem_debug_read_address.poke(4.U) c.clock.step() c.io.mem_debug_read_data.expect(55.U) + + print(s"Cycles passed: $cycle_passed") } } } @@ -133,16 +144,27 @@ class QuicksortTest extends AnyFlatSpec with ChiselScalatestTester { behavior of "CPU" it should "quicksort 10 numbers" in { test(new TestTopModule("quicksort.asmbin")).withAnnotations(TestAnnotations.annos) { c => + c.clock.setTimeout(50 * 1000) c.io.interrupt.poke(0.U) - for (i <- 1 to 50) { - c.clock.step(1000) - c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout - } + c.io.mem_debug_read_address.poke(0.U) + + var cycle_passed = 0 + breakable { while (true) { + c.clock.step() + cycle_passed += 1 + if (c.io.mem_debug_read_data.peek().litValue == 0xbabecafeL) { + c.clock.step(100) + break() + } + }} + for (i <- 1 to 10) { c.io.mem_debug_read_address.poke((4 * i).U) c.clock.step() c.io.mem_debug_read_data.expect((i - 1).U) } + + print(s"Cycles passed: $cycle_passed") } } }