\documentclass[12pt]{ctexart} % 使用 ctexart 文档类支持中文,12pt 字号 \usepackage[utf8]{inputenc} % 输入编码,保持兼容性 \usepackage[margin=2.5cm]{geometry} % 设置页边距 \usepackage{graphicx} % 导入图片 \usepackage{amsmath} % 支持数学公式 \usepackage{listings} % 代码块高亮 \usepackage{xcolor} % 用于代码高亮颜色 \usepackage{hyperref} % 目录、交叉引用可点击,生成PDF书签 \hypersetup{ colorlinks=true, % 这是关键,它会让链接文本以颜色显示,而不是边框 linkcolor=black, % 内部链接(如目录、章节引用)的颜色设为黑色 citecolor=green, % 引用文献的颜色(如果用不到可以忽略或设为黑色) urlcolor=blue, % URL链接的颜色(如果用不到可以忽略或设为黑色) filecolor=magenta, % 文件链接的颜色(如果用不到可以忽略或设为黑色) % 可以添加更多其他 PDF 元数据,让 PDF 文件信息更完整 pdftitle={实验一:单周期 RISC-V CPU 设计与实现}, pdfauthor={朱梓涵}, pdfsubject={RISC-V CPU 设计与实现报告}, pdfkeywords={RISC-V, CPU, Chisel, 单周期, 实验报告} } % 目录、交叉引用可点击,生成PDF书签 \usepackage{fancyhdr} % 自定义页眉页脚 \usepackage{enumitem} % 列表项自定义 \usepackage{ifthen} % 条件判断(用于图片占位) % --- 图片缺失占位宏 --- \newcommand{\includegraphicsorplaceholder}[2][]{% \IfFileExists{#2}{\includegraphics[#1]{#2}}{\fbox{\parbox[c][0.2\textheight][c]{0.9\textwidth}{\centering Missing image: #2}}}% } % --- 页眉页脚设置 --- \pagestyle{fancy} \fancyhf{} % 清除所有页眉页脚字段 \fancyhead[L]{\MakeUppercase{实验一:单周期 RISC-V CPU 设计与实现}} % 左侧页眉:大写实验名称 \fancyfoot[C]{\thepage} % 居中页脚:页码 \renewcommand{\headrulewidth}{0.4pt} % 页眉下方的横线粗细 \renewcommand{\footrulewidth}{0.4pt} % 页脚上方的横线粗细 % 解决 fancyhdr 提示的 \headheight 偏小问题 \setlength{\headheight}{15pt} % --- 标题信息 --- \title{\vspace{-2cm}\textbf{实验一:单周期 RISC-V CPU 设计与实现}} % 标题,垂直间距调整 \author{朱梓涵 \\ 学号:24325356} % 作者信息 \date{\today} % 显示当前日期 % --- 代码高亮风格定义 (Solarized-light) --- \definecolor{sol-base03}{HTML}{002b36} \definecolor{sol-base02}{HTML}{073642} \definecolor{sol-base01}{HTML}{586e75} \definecolor{sol-base00}{HTML}{657b83} \definecolor{sol-base0}{HTML}{839496} \definecolor{sol-base1}{HTML}{93a1a1} \definecolor{sol-base2}{HTML}{eee8d5} \definecolor{sol-base3}{HTML}{fdf6e3} \definecolor{sol-yellow}{HTML}{b58900} \definecolor{sol-orange}{HTML}{cb4b16} \definecolor{sol-red}{HTML}{dc322f} \definecolor{sol-magenta}{HTML}{d33682} \definecolor{sol-violet}{HTML}{6c71c4} \definecolor{sol-blue}{HTML}{268bd2} \definecolor{sol-cyan}{HTML}{2aa198} \definecolor{sol-green}{HTML}{859900} \lstdefinestyle{ScalaChiselStyle}{ commentstyle=\color{sol-base01}\itshape, keywordstyle=\color{sol-green}\bfseries, stringstyle=\color{sol-cyan}, basicstyle=\ttfamily\small, breakatwhitespace=false, breaklines=true, captionpos=b, keepspaces=true, numbers=none, showspaces=false, showstringspaces=false, showtabs=false, tabsize=2, frame=single, rulecolor=\color{black}, % 添加 Chisel 相关关键字 morekeywords={when, Mux, MuxLookup, IndexedSeq, U, io, :=, object, val, def, class, override, package, import, extends, with, Bits, UInt, SInt}, literate={:}{{\color{sol-base02}:}}1 } \lstdefinestyle{CStyle}{ commentstyle=\color{sol-base01}\itshape, keywordstyle=\color{sol-orange}\bfseries, stringstyle=\color{sol-cyan}, basicstyle=\ttfamily\small, breakatwhitespace=false, breaklines=true, captionpos=b, keepspaces=true, numbers=none, showspaces=false, showstringspaces=false, showtabs=false, tabsize=2, frame=single, rulecolor=\color{black}, } \lstset{style=ScalaChiselStyle} % 默认代码风格为 Scala/Chisel % --- 图片计数器与章节联动 --- \counterwithin{figure}{section} \counterwithin{table}{section} % --- 文档开始 --- \begin{document} \maketitle % 生成标题 \thispagestyle{empty} % 标题页无页码 % --- 正文开始 --- \section{实验目的} 本实验旨在深入理解 RISC-V 指令集架构 (ISA) 与单周期 CPU 的工作原理。实验目标包括: \begin{enumerate}[label=\arabic*.] % 使用 enumerate 环境创建编号列表 \item 设计并实现一个支持部分 RV32I 指令集的单周期 CPU。 \item 掌握 CPU 经典五阶段(取指、译码、执行、访存、写回)在单周期模型下的数据通路和控制逻辑设计。 \item 学习使用 Chisel 硬件描述语言进行模块化硬件设计。 \item 通过编写单元测试和集成测试,利用 \texttt{chiseltest} 和 GTKWave 波形图验证 CPU 各模块及整体功能的正确性。 \end{enumerate} \section{实验环境} \begin{itemize} \item \textbf{操作系统}: Windows 11 \item \textbf{开发工具}: IntelliJ IDEA \item \textbf{构建工具}: SBT \item \textbf{仿真与测试}: Verilator, GTKWave \end{itemize} \section{阶段功能划分} 本单周期 RISC-V CPU 依照经典的五级流水线概念进行设计,虽然是单周期实现,但其内部模块划分仍遵循五阶段的逻辑,确保数据通路和控制逻辑清晰。 \begin{itemize} \item \textbf{取指 (Instruction Fetch, IF)}: 程序计数器 (PC) 提供指令地址,指令存储器根据该地址输出 32 位指令。PC 在每个时钟周期默认加 4,若遇跳转或分支成功,则更新为目标地址。 \item \textbf{译码 (Instruction Decode, ID)}: 译码单元解析指令,提取操作数(寄存器地址 \texttt{rs1}, \texttt{rs2})、目标寄存器地址 (\texttt{rd}) 和立即数。主控制器根据指令操作码生成后续阶段所需的所有控制信号。 \item \textbf{执行 (Execute, EX)}: 算术逻辑单元 (ALU) 根据译码阶段生成的控制信号,对来自寄存器文件或立即数生成器的操作数进行计算。同时,分支跳转的地址计算和条件判断也在此阶段完成。 \item \textbf{访存 (Memory Access, MEM)}: 根据译码阶段生成的访存控制信号,与数据存储器进行交互。Load 指令从此阶段读取数据,Store 指令则向此阶段写入数据。 \item \textbf{写回 (Write Back, WB)}: 将执行结果或从数据存储器中读取的数据写回寄存器文件。写回的数据来源由控制信号决定。 \end{itemize} \section{模块实现与分析} \subsection{取指} \begin{itemize} \item \textbf{功能}: 根据 \texttt{PC} 的当前值从指令存储器中取出指令,并计算下一周期的 \texttt{PC} 值。 \item \textbf{实现要点}: \texttt{PC} 的更新逻辑是核心。它由跳转标志 \texttt{jump\_flag\_id} 控制:若跳转发生,\texttt{PC} 更新为 \texttt{jump\_address\_id};否则,顺序执行,\texttt{PC} 更新为 \texttt{PC + 4}。 \end{itemize} \subsubsection{代码实现} \begin{lstlisting}[caption={取指模块PC更新逻辑}, label={lst:if_pc}] // lab1(InstructionFetch) when(io.jump_flag_id) { pc := io.jump_address_id }.otherwise { pc := pc + 4.U } // lab1(InstructionFetch) end \end{lstlisting} \subsubsection{波形图} \begin{figure}[htbp] \centering \includegraphicsorplaceholder[width=0.9\textwidth]{b1.png} \caption{取指模块 (\texttt{InstructionFetch}) 波形图} \label{fig:if_waveform} \end{figure} \subsection{译码} \begin{itemize} \item \textbf{功能}: 解析指令,生成立即数,并为后续阶段提供控制信号。 \item \textbf{实现要点}: 控制信号的生成逻辑是关键。例如,\texttt{memory\_read\_enable} 仅在 L-type 指令时有效,\texttt{wb\_reg\_write\_source} 根据指令类型选择写回数据是来自 ALU、数据存储器还是 PC+4。 \end{itemize} \subsubsection{代码实现} \begin{lstlisting}[caption={译码模块控制信号生成逻辑}, label={lst:id_control}] // lab1(InstructionDecode) io.ex_aluop2_source := Mux(opcode === InstructionTypes.RM, ALUOp2Source.Register, ALUOp2Source.Immediate ) io.memory_read_enable := opcode === InstructionTypes.L io.memory_write_enable := opcode === InstructionTypes.S io.wb_reg_write_source := MuxLookup( opcode, RegWriteSource.ALUResult )( IndexedSeq( InstructionTypes.L -> RegWriteSource.Memory, Instructions.jal -> RegWriteSource.NextInstructionAddress, Instructions.jalr -> RegWriteSource.NextInstructionAddress ) ) // lab1(InstructionDecode) end \end{lstlisting} \subsubsection{波形图} \begin{figure}[htbp] \centering \includegraphicsorplaceholder[width=0.9\textwidth]{b2.png} \caption{译码模块 (\texttt{InstructionDecode}) 波形图} \label{fig:id_waveform} \end{figure} \subsection{执行} \begin{itemize} \item \textbf{功能}: 执行 ALU 运算,并处理分支和跳转逻辑。 \item \textbf{实现要点}: ALU 的两个操作数 \texttt{op1} 和 \texttt{op2} 的来源由译码模块生成的 \texttt{aluop*\_source} 信号决定,实现了操作数的灵活选择。 \end{itemize} \subsubsection{波形图} \begin{figure}[htbp] \centering \includegraphicsorplaceholder[width=0.9\textwidth]{b3.png} \caption{执行模块 (\texttt{Execute}) 波形图} \label{fig:ex_waveform} \end{figure} \subsubsection{代码实现} \begin{lstlisting}[caption={执行模块ALU操作数选择逻辑}, label={lst:ex_aluops}] // lab1(Execute) alu.io.func := alu_ctrl.io.alu_funct alu.io.op1 := Mux( io.aluop1_source === ALUOp1Source.Register, io.reg1_data, io.instruction_address ) alu.io.op2 := Mux( io.aluop2_source === ALUOp2Source.Register, io.reg2_data, io.immediate ) // lab1(Execute) end \end{lstlisting} \subsection{CPU 模块} \begin{itemize} \item \textbf{功能}: 实例化取指、译码、执行等所有子模块,并根据数据通路图将它们正确连接起来。 \item \textbf{实现要点}: 确保数据流和控制流在模块间的正确传递是顶层连接的关键。 \end{itemize} \subsubsection{代码实现} \begin{lstlisting}[caption={CPU模块部分连接逻辑}, label={lst:cpu_top}] // lab1(cpu) ex.io.instruction := inst_fetch.io.instruction ex.io.instruction_address := inst_fetch.io.instruction_address ex.io.reg1_data := regs.io.read_data1 ex.io.reg2_data := regs.io.read_data2 ex.io.immediate := id.io.ex_immediate ex.io.aluop1_source := id.io.ex_aluop1_source ex.io.aluop2_source := id.io.ex_aluop2_source // lab1(cpu) end \end{lstlisting} \section{测试与结果分析} \subsection{单元测试:InstructionDecoderTest 分析} \subsubsection{L-Type 指令测试分析} 测试用例首先向译码模块的 \texttt{io.instruction} 端口输入一条 L-Type 指令。随后,测试代码使用 \texttt{.expect()} 方法断言模块的各个输出端口信号值是否与预设的期望值一致。 对于 \texttt{lw} 指令,其核心功能是从内存读取数据并写回寄存器。因此: \begin{itemize} \item \texttt{memory\_read\_enable} 必须为 \texttt{true},以启动访存阶段的数据读取。 \item \texttt{reg\_write\_enable} 必须为 \texttt{true},以允许最终结果写入寄存器。 \item \texttt{wb\_reg\_write\_source} 必须是 \texttt{RegWriteSource.Memory},因为写回的数据源于数据存储器。 \item \texttt{ex\_immediate} 必须是指令编码中包含的偏移量(此例中为 32),用于地址计算 \texttt{rs1 + imm}。 \end{itemize} 这些 \texttt{expect} 值精确地定义了 L-Type 指令在译码阶段应生成的正确行为模式,确保了控制通路的正确性。 \subsubsection{波形图分析} \begin{figure}[htbp] \centering \includegraphicsorplaceholder[width=0.9\textwidth]{b2.png} \caption{\texttt{L-Type} 指令 \texttt{lw x3, 0(x8)} 的译码波形图} \label{fig:lw_decode_waveform} \end{figure} 如图所示,波形图展示了指令 lw x3, 0(x8)(机器码 0x00404183)的译码过程。在时钟上升沿之后: \begin{itemize} \item \verb|id_io_instruction| 端口稳定为 \texttt{0x00404183},对应 I-type 的 Load 指令 \texttt{lw x3, 0(x8)}。 \item \verb|id_io_memory_read_enable| 和 \verb|id_io_reg_write_enable| 信号被置为高电平(1),表示该指令需要从存储器读取数据并写回寄存器。 \item \verb|id_io_wb_reg_write_source| 信号值为 \texttt{01} (二进制),表示写回数据的来源是 Memory。 \item \verb|id_io_ex_immediate| 的值为 \texttt{0x00000000},即偏移量 0。 \end{itemize} 波形验证了译码模块对 I-type (Load)指令的控制信号输出与 \texttt{InstructionDecoderTest.scala} 中的 \texttt{expect} 语句一致,说明译码单元能够正确识别 Load 指令并产生相应控制信号。 \subsection{整体测试:CPUTest (以 \texttt{FibonacciTest} 为例)} \subsubsection{\texttt{fibonacci.c} 程序分析} \begin{itemize} \item \textbf{程序功能}: 该 C 语言程序定义了一个递归函数 \texttt{fib(int a)},用于计算斐波那契数列的第 \texttt{a} 项。其逻辑为:如果 \texttt{a} 等于 1 或 2,则返回 1;否则,返回 \texttt{fib(a-1)} 与 \texttt{fib(a-2)} 之和。\texttt{main} 函数是程序的入口点,它调用 \texttt{fib(10)} 来计算斐波那契数列的第 10 项,并将最终结果(即 55)存储到内存地址 \texttt{0x4}。 \begin{lstlisting}[language=C, caption={Fibonacci 递归程序 \texttt{fibonacci.c}}, label={lst:fib_c}, style=CStyle] int fib(int a) { if (a == 1 || a == 2) return 1; return fib(a - 1) + fib(a - 2); } int main() { *(int *)(4) = fib(10); // 将 fib(10) 的结果写入内存地址 0x4 } \end{lstlisting} \item \textbf{Chisel 测试检查方式}: 测试框架首先将 \texttt{fibonacci.asmbin} 文件加载到 CPU 的指令存储器和数据存储器中。然后,它驱动 CPU 执行足够多的时钟周期(通过 \texttt{c.clock.step(1000)} 大循环),以确保递归计算和内存写入操作全部完成。在仿真结束时,测试代码通过读取数据存储器的调试端口: \begin{lstlisting}[caption={Chisel 测试代码片段}, label={lst:chisel_test}] c.io.mem_debug_read_address.poke(4.U) // 将调试地址设置为 0x4 c.clock.step() // 步进一个时钟周期 c.io.mem_debug_read_data.expect(55.U) // 期望从地址 0x4 读出的数据为 55 \end{lstlisting} 上述语句首先将调试端口的读取地址 \texttt{mem\_debug\_read\_address} 设置为 \texttt{0x4},然后步进一个时钟周期以便数据稳定,最后断言 \texttt{mem\_debug\_read\_data} 的值是否等于预期的 \texttt{55}。如果相等,则测试通过,验证了 CPU 能够正确执行递归计算并将结果写入指定内存。 \end{itemize} \subsubsection{波形图分析} \begin{figure}[htbp] \centering \includegraphicsorplaceholder[width=0.9\textwidth]{b41.png} \caption{Fibonacci 程序执行末期及结果写入内存的波形图} \label{fig:fibonacci_waveform} \end{figure} 基于 VCD 文件分析,斐波那契程序执行过程中的关键信号包括: \begin{itemize} \item \verb|cpu_io_instruction_address|:程序计数器(PC),显示指令的执行流程; \item \verb|mem_io_bundle_write_enable|:内存写使能信号,用于标识数据写入操作; \item \verb|mem_io_bundle_address|:内存访问地址; \item \verb|mem_io_bundle_write_data|:写入内存的数据; \item \verb|io_mem_debug_read_data|:调试接口读取的内存数据,用于结果验证。 \end{itemize} 所选测试程序为斐波那契数列求解程序,其功能是计算第 10 项的值(即 55),并在程序结束时将结果写入数据存储器地址 \texttt{0x00000004}。在 \texttt{CPUTest.scala} 的测试中,系统通过监测访存信号或从调试接口读取该地址的值,验证处理器输出是否正确。 在程序执行的最后阶段,可以在波形中观察到以下现象: \begin{enumerate} \item \verb|mem_io_bundle_write_enable| 信号置高,表示 CPU 正在执行 \verb|sw| 指令; \item \verb|mem_io_bundle_address| 显示为 \verb|0x00000004|,即目标内存地址为 4; \item \verb|mem_io_bundle_write_data| 显示为 \verb|0x00000037|(十进制 55),即 \verb|fib(10)| 的计算结果; \item 随后,调试接口 \verb|io_mem_debug_read_data| 读出并稳定为相同的值 \verb|0x00000037|。 \end{enumerate} 由此可知,CPU 成功将计算结果 55 写入目标地址,测试检查结果正确,验证了数据通路及访存阶段的功能实现。 \section{遇到的问题与改进建议} \begin{enumerate}[label=\arabic*.] \item \textbf{问题:立即数生成规则复杂,指导文档未详细展开。} 在实现译码模块时,RISC-V 不同指令格式(I/S/B/U/J)的立即数拼接方式各不相同,实验指导对此描述较为简略,导致初期实现困难。 \begin{itemize} \item \textbf{建议}:在实验指导中为每种指令格式提供一个具体的立即数拼接图示或示例代码,能极大帮助理解。 \end{itemize} \item \textbf{问题:缺少对波形图调试的引导案例。} 初次使用 GTKWave 进行硬件调试时,面对海量的信号线,难以快速定位关键信号。 \begin{itemize} \item \textbf{建议}:实验文档中可以附加一个简单的调试案例,例如追踪一条 \texttt{add} 指令的数据流,展示如何从取指 PC 开始,逐步添加 `instruction`, `reg\_read\_data`, `alu\_result`, `reg\_write\_data` 等信号,并解释它们在波形图上的时序关系。 \end{itemize} \end{enumerate} \section{实验结论} 通过本次实验,我成功设计并实现了一个功能基本完备的 RISC-V 单周期 CPU。在实现过程中,我深入理解了数据通路与控制信号在指令执行过程中的协同作用,并掌握了模块化的硬件设计思想。通过编写和分析单元测试与集成测试,我学会了如何利用波形图对硬件设计进行验证和调试。本次实验极大地加深了我对计算机组成原理中理论知识的实践理解,为后续更复杂的处理器设计打下了坚实的基础。 \end{document}