实验报告检查

This commit is contained in:
2025-12-21 23:11:21 +08:00
parent 078b2c0b37
commit 52ecd7743b
85 changed files with 8477 additions and 16 deletions

View File

@@ -0,0 +1,365 @@
\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}

View File

@@ -0,0 +1,44 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\providecommand*\HyPL@Entry[1]{}
\HyPL@Entry{0<</S/D>>}
\@writefile{toc}{\contentsline {section}{\numberline {1}实验目的}{1}{section.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {2}实验环境}{1}{section.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {3}流水线结构与功能划分}{1}{section.3}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {3.1}三级流水线结构}{1}{subsection.3.1}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {3.2}五级流水线结构}{2}{subsection.3.2}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {3.3}缩短分支延迟的五级流水线}{2}{subsection.3.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {4}模块实现与分析}{3}{section.4}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {4.1}流水线寄存器PipelineRegister}{3}{subsection.4.1}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.1.1}功能}{3}{subsubsection.4.1.1}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.1.2}代码实现}{3}{subsubsection.4.1.2}\protected@file@percent }
\newlabel{lst:pipeline_reg}{{1}{3}{流水线寄存器实现}{lstlisting.1}{}}
\@writefile{lol}{\contentsline {lstlisting}{\numberline {1}流水线寄存器实现}{3}{lstlisting.1}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.1.3}设计要点}{4}{subsubsection.4.1.3}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {4.2}控制单元Control}{4}{subsection.4.2}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.2.1}功能}{4}{subsubsection.4.2.1}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.2.2}代码实现(最终版本)}{4}{subsubsection.4.2.2}\protected@file@percent }
\newlabel{lst:control_final}{{2}{4}{控制单元实现(缩短分支延迟版本)}{lstlisting.2}{}}
\@writefile{lol}{\contentsline {lstlisting}{\numberline {2}控制单元实现(缩短分支延迟版本)}{4}{lstlisting.2}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.2.3}设计要点}{5}{subsubsection.4.2.3}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {4.3}转发单元Forwarding}{6}{subsection.4.3}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.3.1}功能}{6}{subsubsection.4.3.1}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.3.2}代码实现(最终版本)}{6}{subsubsection.4.3.2}\protected@file@percent }
\newlabel{lst:forward_final}{{3}{6}{转发单元实现(缩短分支延迟版本)}{lstlisting.3}{}}
\@writefile{lol}{\contentsline {lstlisting}{\numberline {3}转发单元实现(缩短分支延迟版本)}{6}{lstlisting.3}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.3.3}设计要点}{8}{subsubsection.4.3.3}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {4.4}译码单元InstructionDecode}{8}{subsection.4.4}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.4.1}关键代码}{9}{subsubsection.4.4.1}\protected@file@percent }
\newlabel{lst:id_jump}{{4}{9}{ID 段跳转逻辑}{lstlisting.4}{}}
\@writefile{lol}{\contentsline {lstlisting}{\numberline {4}ID 段跳转逻辑}{9}{lstlisting.4}\protected@file@percent }
\@writefile{toc}{\contentsline {subsubsection}{\numberline {4.4.2}设计要点}{10}{subsubsection.4.4.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {5}CSR 指令的冒险分析}{10}{section.5}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {5.1}数据冒险}{10}{subsection.5.1}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {5.2}控制冒险}{11}{subsection.5.2}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {5.3}结论}{11}{subsection.5.3}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {6}测试结果}{11}{section.6}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {7}遇到的问题与改进建议}{12}{section.7}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {7.1}遇到的问题}{12}{subsection.7.1}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\numberline {7.2}改进建议}{12}{subsection.7.2}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\numberline {8}实验结论}{13}{section.8}\protected@file@percent }
\gdef \@abspage@last{13}

Binary file not shown.

View File

@@ -0,0 +1,507 @@
\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={实验三:流水线 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{实验三:流水线 CPU 设计与实现}} % 左侧页眉:大写实验名称
\fancyfoot[C]{\thepage} % 居中页脚:页码
\renewcommand{\headrulewidth}{0.4pt} % 页眉下方的横线粗细
\renewcommand{\footrulewidth}{0.4pt} % 页脚上方的横线粗细
% 解决 fancyhdr 提示的 \headheight 偏小问题
\setlength{\headheight}{15pt}
% --- 标题信息 ---
\title{\vspace{-2cm}\textbf{实验三:流水线 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, elsewhen, otherwise, Wire, Bool, RegInit, asSInt, asUInt},
literate={:}{{\color{sol-base02}:}}1
}
\lstset{style=ScalaChiselStyle} % 默认代码风格为 Scala/Chisel
% --- 图片计数器与章节联动 ---
\counterwithin{figure}{section}
\counterwithin{table}{section}
% --- 文档开始 ---
\begin{document}
\maketitle % 生成标题
\thispagestyle{empty} % 标题页无页码
% --- 正文开始 ---
\section{实验目的}
本实验旨在深入理解流水线技术在 CPU 设计中的应用,通过实现三级和五级流水线 CPU掌握流水线中竞争冒险的处理方法。实验目标包括
\begin{enumerate}[label=\arabic*.] % 使用 enumerate 环境创建编号列表
\item 理解流水线寄存器的作用,并实现支持阻塞和清空功能的流水线寄存器。
\item 设计并实现三级流水线 CPU掌握控制冒险的处理方法。
\item 设计并实现五级流水线 CPU学习使用阻塞和转发技术处理数据冒险。
\item 将分支跳转提前到译码阶段,进一步缩短分支延迟,优化流水线性能。
\item 通过编写测试用例,验证流水线 CPU 各模块及整体功能的正确性。
\end{enumerate}
\section{实验环境}
\begin{itemize}
\item \textbf{操作系统}: Windows 11
\item \textbf{开发工具}: IntelliJ IDEA
\item \textbf{构建工具}: SBT
\item \textbf{仿真与测试}: Verilator, chiseltest
\end{itemize}
\section{流水线结构与功能划分}
\subsection{三级流水线结构}
三级流水线将单周期 CPU 的组合逻辑切分为三个阶段:
\begin{itemize}
\item \textbf{取指 (IF)}: 根据 PC 从指令存储器取出指令。
\item \textbf{译码 (ID)}: 解码指令,生成控制信号,从寄存器组读取操作数。
\item \textbf{执行 (EX)}: 执行 ALU 运算、访存和写回操作。
\end{itemize}
在三级流水线中,主要需要处理控制冒险。当 EX 段执行跳转或分支指令时IF 和 ID 段的两条指令需要被清空。
\subsection{五级流水线结构}
五级流水线在三级流水线的基础上,将 EX 段进一步细分:
\begin{itemize}
\item \textbf{取指 (IF)}: 根据 PC 从指令存储器取出指令。
\item \textbf{译码 (ID)}: 解码指令,生成控制信号,从寄存器组读取操作数。
\item \textbf{执行 (EX)}: 执行 ALU 运算。
\item \textbf{访存 (MEM)}: 访问数据存储器。
\item \textbf{写回 (WB)}: 将结果写回寄存器组。
\end{itemize}
五级流水线引入了更复杂的数据冒险,需要使用阻塞和转发技术进行处理。
\subsection{缩短分支延迟的五级流水线}
在最终版本的五级流水线中,将分支和跳转指令的执行从 EX 段提前到 ID 段:
\begin{itemize}
\item 在 ID 段增加加法器,用于计算跳转目标地址。
\item 在 ID 段进行分支条件判断,使用转发逻辑从 MEM 和 WB 段获取操作数。
\item 如果依赖的数据还未产生,则进行阻塞。
\end{itemize}
这样做可以将分支延迟从两个时钟周期减少到一个时钟周期。
\section{模块实现与分析}
\subsection{流水线寄存器PipelineRegister}
\subsubsection{功能}
流水线寄存器是流水线 CPU 的核心组件,用于在相邻流水段之间缓存数据和控制信号。它支持三种操作:
\begin{itemize}
\item \textbf{清空 (flush)}: 将寄存器值重置为默认值,用于清除错误路径上的指令。
\item \textbf{阻塞 (stall)}: 保持当前寄存器值不变,用于暂停流水线。
\item \textbf{正常更新}: 在时钟上升沿将输入值写入寄存器。
\end{itemize}
\subsubsection{代码实现}
\begin{lstlisting}[caption={流水线寄存器实现}, label={lst:pipeline_reg}]
class PipelineRegister(width: Int = Parameters.DataBits, defaultValue: UInt = 0.U) extends Module {
val io = IO(new Bundle {
val stall = Input(Bool())
val flush = Input(Bool())
val in = Input(UInt(width.W))
val out = Output(UInt(width.W))
})
// Lab3(PipelineRegister)
val register = RegInit(defaultValue)
when(io.flush) {
register := defaultValue
}.elsewhen(io.stall) {
}.otherwise {
register := io.in
}
io.out := register
// Lab3(PipelineRegister) End
}
\end{lstlisting}
\subsubsection{设计要点}
\begin{itemize}
\item 优先级:\texttt{flush} 信号优先级最高,其次是 \texttt{stall},最后是正常更新。
\item\texttt{flush} 为高时,寄存器被清空为默认值(通常是 NOP 指令或 0
\item\texttt{stall} 为高时,寄存器保持当前值不变。
\item 其他情况下,寄存器在时钟上升沿更新为输入值。
\end{itemize}
\subsection{控制单元Control}
\subsubsection{功能}
控制单元负责检测流水线中的冒险,并生成相应的控制信号:
\begin{itemize}
\item 检测数据冒险,生成阻塞信号。
\item 检测控制冒险,生成清空信号。
\end{itemize}
\subsubsection{代码实现(最终版本)}
\begin{lstlisting}[caption={控制单元实现(缩短分支延迟版本)}, label={lst:control_final}]
class Control extends Module {
val io = IO(new Bundle {
val jump_flag = Input(Bool())
val jump_instruction_id = Input(Bool())
val rs1_id = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val rs2_id = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val memory_read_enable_ex = Input(Bool())
val rd_ex = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val memory_read_enable_mem = Input(Bool())
val rd_mem = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val if2id_flush = Output(Bool())
val id2ex_flush = Output(Bool())
val pc_stall = Output(Bool())
val if2id_stall = Output(Bool())
})
// Lab3(Final)
val stall = Wire(Bool())
val load_use_hazard = io.memory_read_enable_ex && io.rd_ex =/= 0.U &&
(io.rd_ex === io.rs1_id || io.rd_ex === io.rs2_id)
val id_jump_needs_ex_alu = io.jump_instruction_id && io.rd_ex =/= 0.U &&
!io.memory_read_enable_ex &&
(io.rd_ex === io.rs1_id || io.rd_ex === io.rs2_id)
val id_jump_needs_mem_load = io.jump_instruction_id && io.memory_read_enable_mem && io.rd_mem =/= 0.U &&
(io.rd_mem === io.rs1_id || io.rd_mem === io.rs2_id)
stall := load_use_hazard || id_jump_needs_ex_alu || id_jump_needs_mem_load
val flush = io.jump_flag && !stall
io.pc_stall := stall
io.if2id_stall := stall
io.if2id_flush := flush
io.id2ex_flush := stall
// Lab3(Final) End
}
\end{lstlisting}
\subsubsection{设计要点}
在缩短分支延迟的版本中,由于分支和跳转指令在 ID 段执行,控制单元需要处理以下几种情况:
\begin{enumerate}
\item \textbf{Load-use 冒险}: 当 ID 段的指令需要使用 EX 段 load 指令的结果时,必须阻塞一个周期,等待数据从 MEM 段产生后通过转发获取。
\item \textbf{跳转指令依赖 EX 段 ALU 结果}: 当 ID 段的跳转/分支指令需要使用 EX 段的 ALU 计算结果时,需要阻塞一个周期。虽然 EX 段的结果会在下个周期进入 MEM 段,可以通过转发提供给 ID 段,但由于跳转判断需要在 ID 段完成,因此必须等待一个周期。
\item \textbf{跳转指令依赖 MEM 段 load 结果}: 当 ID 段的跳转/分支指令需要使用 MEM 段 load 指令的结果时,需要阻塞一个周期,等待数据进入 WB 段后通过转发获取。
\item \textbf{控制冒险}: 当跳转确实发生时(\texttt{jump\_flag} 为真且无阻塞),需要清空 IF2ID 流水线寄存器,丢弃已取出的错误路径指令。
\item \textbf{阻塞时插入气泡}: 当发生阻塞时,需要清空 ID2EX 流水线寄存器,在 EX 段插入一条 NOP 指令(气泡),防止 ID 段的指令被重复执行。
\end{enumerate}
\subsection{转发单元Forwarding}
\subsubsection{功能}
转发单元负责检测数据冒险,并生成转发控制信号,使得 EX 或 ID 段可以直接从流水线寄存器中获取所需数据,而不必等待数据写回寄存器组,从而减少流水线阻塞。
\subsubsection{代码实现(最终版本)}
\begin{lstlisting}[caption={转发单元实现(缩短分支延迟版本)}, label={lst:forward_final}]
object ForwardingType {
val NoForward = 0.U(2.W)
val ForwardFromMEM = 1.U(2.W)
val ForwardFromWB = 2.U(2.W)
}
class Forwarding extends Module {
val io = IO(new Bundle() {
val rs1_id = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val rs2_id = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val rs1_ex = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val rs2_ex = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val rd_mem = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val reg_write_enable_mem = Input(Bool())
val rd_wb = Input(UInt(Parameters.PhysicalRegisterAddrWidth))
val reg_write_enable_wb = Input(Bool())
val reg1_forward_id = Output(UInt(2.W))
val reg2_forward_id = Output(UInt(2.W))
val reg1_forward_ex = Output(UInt(2.W))
val reg2_forward_ex = Output(UInt(2.W))
})
// Lab3(Final)
val ex_mem_hazard_rs1 = io.reg_write_enable_mem && io.rd_mem =/= 0.U && (io.rd_mem === io.rs1_ex)
val ex_mem_hazard_rs2 = io.reg_write_enable_mem && io.rd_mem =/= 0.U && (io.rd_mem === io.rs2_ex)
val ex_wb_hazard_rs1 = io.reg_write_enable_wb && io.rd_wb =/= 0.U && (io.rd_wb === io.rs1_ex)
val ex_wb_hazard_rs2 = io.reg_write_enable_wb && io.rd_wb =/= 0.U && (io.rd_wb === io.rs2_ex)
io.reg1_forward_ex := Mux(ex_mem_hazard_rs1, ForwardingType.ForwardFromMEM,
Mux(ex_wb_hazard_rs1, ForwardingType.ForwardFromWB, ForwardingType.NoForward))
io.reg2_forward_ex := Mux(ex_mem_hazard_rs2, ForwardingType.ForwardFromMEM,
Mux(ex_wb_hazard_rs2, ForwardingType.ForwardFromWB, ForwardingType.NoForward))
val id_mem_hazard_rs1 = io.reg_write_enable_mem && io.rd_mem =/= 0.U && (io.rd_mem === io.rs1_id)
val id_mem_hazard_rs2 = io.reg_write_enable_mem && io.rd_mem =/= 0.U && (io.rd_mem === io.rs2_id)
val id_wb_hazard_rs1 = io.reg_write_enable_wb && io.rd_wb =/= 0.U && !id_mem_hazard_rs1 && (io.rd_wb === io.rs1_id)
val id_wb_hazard_rs2 = io.reg_write_enable_wb && io.rd_wb =/= 0.U && !id_mem_hazard_rs2 && (io.rd_wb === io.rs2_id)
io.reg1_forward_id := Mux(id_mem_hazard_rs1, ForwardingType.ForwardFromMEM,
Mux(id_wb_hazard_rs1, ForwardingType.ForwardFromWB, ForwardingType.NoForward))
io.reg2_forward_id := Mux(id_mem_hazard_rs2, ForwardingType.ForwardFromMEM,
Mux(id_wb_hazard_rs2, ForwardingType.ForwardFromWB, ForwardingType.NoForward))
// Lab3(Final) End
}
\end{lstlisting}
\subsubsection{设计要点}
转发单元需要同时处理到 EX 段和到 ID 段的转发:
\begin{enumerate}
\item \textbf{EX 段转发}:
\begin{itemize}
\item 如果 EX 段的源寄存器与 MEM 段的目标寄存器相同,从 MEM 段转发。
\item 如果 EX 段的源寄存器与 WB 段的目标寄存器相同,从 WB 段转发。
\item MEM 段转发优先级高于 WB 段转发(保证获取最新的值)。
\end{itemize}
\item \textbf{ID 段转发}:
\begin{itemize}
\item 由于跳转指令在 ID 段执行,需要将 MEM 和 WB 段的结果转发到 ID 段。
\item 如果 ID 段的源寄存器与 MEM 段的目标寄存器相同,从 MEM 段转发。
\item 如果 ID 段的源寄存器与 WB 段的目标寄存器相同(且不与 MEM 段冲突),从 WB 段转发。
\item MEM 段转发优先级同样高于 WB 段转发。
\end{itemize}
\item \textbf{寄存器 x0 的特殊处理}:
\begin{itemize}
\item RISC-V 中寄存器 x0 恒为 0写入 x0 的结果会被丢弃。
\item 因此,转发逻辑中需要检查目标寄存器是否为 0\texttt{rd =/= 0.U}),避免不必要的转发。
\end{itemize}
\end{enumerate}
\subsection{译码单元InstructionDecode}
在缩短分支延迟的版本中,译码单元需要在 ID 段完成分支和跳转指令的执行。
\subsubsection{关键代码}
\begin{lstlisting}[caption={ID 段跳转逻辑}, label={lst:id_jump}]
val reg1_data = MuxLookup(io.reg1_forward, io.reg1_data)(
Seq(
ForwardingType.ForwardFromMEM -> io.forward_from_mem,
ForwardingType.ForwardFromWB -> io.forward_from_wb
)
)
val reg2_data = MuxLookup(io.reg2_forward, io.reg2_data)(
Seq(
ForwardingType.ForwardFromMEM -> io.forward_from_mem,
ForwardingType.ForwardFromWB -> io.forward_from_wb
)
)
val is_jump_instruction = opcode === Instructions.jal || opcode === Instructions.jalr || opcode === InstructionTypes.B
io.ctrl_jump_instruction := is_jump_instruction
val jump_condition_met =
(opcode === Instructions.jal) ||
(opcode === Instructions.jalr) ||
(opcode === InstructionTypes.B) && MuxLookup(
funct3,
false.B,
IndexedSeq(
InstructionsTypeB.beq -> (reg1_data === reg2_data),
InstructionsTypeB.bne -> (reg1_data =/= reg2_data),
InstructionsTypeB.blt -> (reg1_data.asSInt < reg2_data.asSInt),
InstructionsTypeB.bge -> (reg1_data.asSInt >= reg2_data.asSInt),
InstructionsTypeB.bltu -> (reg1_data < reg2_data),
InstructionsTypeB.bgeu -> (reg1_data >= reg2_data)
)
)
val jump_address = Mux(
opcode === Instructions.jalr,
(reg1_data.asSInt + io.ex_immediate.asSInt).asUInt,
(io.instruction_address.asSInt + io.ex_immediate.asSInt).asUInt
) & (~1.U(Parameters.DataWidth)).asUInt
io.if_jump_flag := jump_condition_met || io.interrupt_assert
io.if_jump_address := Mux(
io.interrupt_assert,
io.interrupt_handler_address,
jump_address
)
\end{lstlisting}
\subsubsection{设计要点}
\begin{itemize}
\item 在 ID 段使用转发逻辑获取寄存器数据,确保使用的是最新的值。
\item 根据指令类型jal/jalr/分支)判断跳转条件是否满足。
\item 在 ID 段计算跳转目标地址,无需等到 EX 段。
\item 跳转地址需要与 \texttt{\textasciitilde 1} 进行与运算确保地址为偶数RISC-V 要求)。
\end{itemize}
\section{CSR 指令的冒险分析}
\subsection{数据冒险}
CSRControl and Status Register指令用于读写控制和状态寄存器。在本实验的实现中CSR 指令可能产生以下数据冒险:
\begin{enumerate}
\item \textbf{RAWRead After Write冒险}:
\begin{itemize}
\item 当后续指令需要读取 CSR 指令写入的通用寄存器时,会发生 RAW 冒险。
\item 例如:\texttt{csrrw x1, mstatus, x2} 后跟 \texttt{add x3, x1, x4}
\item 解决方法与普通指令相同通过转发或阻塞解决。CSR 指令在 WB 段将结果写回通用寄存器,可以通过 MEM-EX 和 WB-EX 转发路径提供数据。
\end{itemize}
\item \textbf{CSR 寄存器的 RAW 冒险}:
\begin{itemize}
\item 当连续的 CSR 指令访问同一个 CSR 寄存器时,后续指令可能读取到过时的值。
\item 例如:\texttt{csrrw x1, mstatus, x2} 后跟 \texttt{csrrs x3, mstatus, x4}
\item 本实验未专门处理 CSR 寄存器间的冒险。在实际实现中,可以通过以下方式处理:
\begin{itemize}
\item 检测 CSR 地址冲突,插入阻塞。
\item 为 CSR 单元添加转发逻辑。
\item 简化方案CSR 指令较少出现连续访问,可以通过编译器重排指令避免。
\end{itemize}
\end{itemize}
\end{enumerate}
\subsection{控制冒险}
CSR 指令本身不产生控制冒险(它们不是跳转或分支指令)。但是,某些 CSR 指令(如 \texttt{mret})会改变程序执行流:
\begin{itemize}
\item \texttt{mret} 指令用于从异常处理返回,会跳转到 \texttt{mepc} 寄存器指定的地址。
\item 这类指令在本实验中被当作特殊的跳转指令处理,会触发流水线清空。
\end{itemize}
\subsection{结论}
\begin{itemize}
\item CSR 指令写入通用寄存器的数据冒险可以通过现有的转发和阻塞机制解决。
\item CSR 寄存器间的 RAW 冒险在本实验中未专门处理,实际应用中需要额外的检测和阻塞逻辑。
\item \texttt{mret} 等特殊 CSR 指令产生的控制冒险可以通过流水线清空解决。
\end{itemize}
\section{测试结果}
本实验通过了所有测试用例,包括:
\begin{itemize}
\item \textbf{流水线寄存器测试}: 验证了流水线寄存器的阻塞和清空功能。
\item \textbf{三级流水线 CPU 测试}: 验证了控制冒险的处理,包括递归计算斐波那契数列、快速排序、单字节加载存储等。
\item \textbf{五级流水线阻塞CPU 测试}: 验证了使用阻塞解决数据冒险的正确性。
\item \textbf{五级流水线转发CPU 测试}: 验证了使用转发减少阻塞的优化效果。
\item \textbf{五级流水线缩短分支延迟CPU 测试}: 验证了将跳转提前到 ID 段的优化。
\end{itemize}
所有测试均在 60 秒内完成,共 20 个测试用例全部通过,验证了流水线 CPU 各阶段实现的正确性。
\section{遇到的问题与改进建议}
\subsection{遇到的问题}
\begin{enumerate}[label=\arabic*.]
\item \textbf{问题:转发逻辑的优先级容易混淆。}
在实现转发单元时,需要处理 MEM 段和 WB 段同时满足转发条件的情况。根据流水线原理,应优先使用 MEM 段的数据(更新),但在编写代码时容易写反优先级。
\begin{itemize}
\item \textbf{解决方法}:仔细分析数据流,绘制流水线状态图,明确各阶段数据的新旧关系。使用嵌套的 \texttt{Mux} 语句时,外层 \texttt{Mux} 判断优先级更高的条件。
\end{itemize}
\item \textbf{问题:跳转指令提前到 ID 段后,冒险情况增多。}
将跳转判断从 EX 段提前到 ID 段后,需要考虑 ID 段的跳转指令与 EX、MEM 段指令的依赖关系,冒险检测逻辑变得更加复杂。
\begin{itemize}
\item \textbf{解决方法}:按照实验指导的提示,列出所有可能的冒险组合表格,逐一分析每种情况,确保覆盖所有冒险场景。
\end{itemize}
\item \textbf{问题:调试困难,波形图信号繁多。}
五级流水线 CPU 信号众多,在 GTKWave 中查看波形时很难快速定位错误信号。
\begin{itemize}
\item \textbf{解决方法}:使用 Chisel 的 \texttt{printf} 调试功能,在关键部件(如寄存器文件、控制单元)添加打印语句,输出关键信号的值。结合简单的测试程序(如 \texttt{sb.S}),逐步排查错误。
\end{itemize}
\end{enumerate}
\subsection{改进建议}
\begin{enumerate}[label=\arabic*.]
\item \textbf{建议:提供更多调试案例和方法指导。}
实验指导中的调试部分较为简略,建议增加具体的调试案例,例如如何追踪一条指令在流水线中的完整执行过程,如何分析波形图定位冒险问题等。
\item \textbf{建议:增加可视化工具。}
流水线状态可视化对理解和调试非常有帮助。建议提供或推荐一些工具,能够将流水线各阶段的指令和数据流以图形化方式展示。
\item \textbf{建议:补充 CSR 指令冒险处理的说明。}
实验指导中未详细讨论 CSR 指令的冒险处理,建议在后续版本中补充相关内容,或明确说明本实验中 CSR 指令的简化假设。
\end{enumerate}
\section{实验结论}
通过本次实验,我成功实现了三级和五级流水线 CPU深入理解了流水线技术和竞争冒险的处理方法。在实现过程中我掌握了
\begin{itemize}
\item 流水线寄存器的设计与实现。
\item 控制冒险的检测与清空机制。
\item 数据冒险的阻塞和转发解决方案。
\item 分支延迟优化技术(将跳转提前到 ID 段)。
\end{itemize}
通过编写和分析测试用例,我学会了如何验证流水线 CPU 的正确性,并掌握了使用打印和波形图进行调试的方法。本次实验使我对计算机组成原理中的流水线技术有了更深入的实践理解,为后续更复杂的处理器设计奠定了坚实的基础。
\end{document}