实验6:CPU,流水线
截止日期:4月2日星期四晚上11:59:59 PT
设置
您必须在本地机器上完成此实验(不是hive机器)。如果需要重新设置本地机器,请参阅实验0。
在本地机器的labs目录中,拉取您在过去实验中可能做过的任何更改:
仍然在本地机器的labs目录中,使用以下命令拉取本实验的文件:
如果遇到任何git错误,请查看常见错误页面。
与实验5一样,本实验的所有工作都将使用数字逻辑模拟程序Logisim Evolution完成。
开始之前的一些重要警告:
- Logisim是一个GUI程序,因此不能在无头环境(WSL、SSH等)中轻松使用。我们建议在具有GUI、Java 9+和Python 3.6+的本地环境中运行它。如果您的本地系统是macOS或Linux,您可能已经准备好了。如果您使用Windows,请使用
Git Bash,它在Windows上运行并支持GUI。 - 请使用我们分发的Logisim版本,因为它与互联网上的其他版本不同(有bug修复和课程特定内容)
- 不要移动工作人员提供的输入/输出引脚;如果引脚移动,您的电路将无法正确测试。如果您的电路未通过测试但您认为它是正确的,请检查您的电路是否适合
tests/ex#-test.circ中的相应测试框架 - Logisim不会自动保存您的工作。记得在工作时经常保存(并提交)!
练习1:构建立即数
正如我们在讲座中看到的,RISC-V中有五种类型的立即数:I型、S型、B型、U型和J型。在本练习中,您将实现S型立即数生成器。ex1.circ将接收一个32位RISC-V存储指令作为输入。它应该输出该指令的符号扩展32位立即数值。构建立即数值需要使用分割器。您可以在实验5的高级Logisim功能部分找到分割器工作原理的摘要。
本练习对项目3有帮助!
- 打开
ex1.circ。此电路接收32位RISC-V存储指令。 - 修改电路,使其输出指令的32位符号扩展立即数值。您可以假设该指令始终是存储指令。
测试
- 打开本地终端会话并导航到您的
lab06文件夹。 - 运行提供的测试:
python3 test.py。- 您的练习1电路在测试框架电路(
tests/ex1-test.circ)中运行。 - 您的输出位于
tests/out/ex1-test.out - 参考输出位于
tests/out/ex1-test.ref - 在输出文件中,每列对应主电路上的一个输入/输出引脚,每行显示一组输入和电路产生的相应输出。
- 如果您的电路输出不同,您可以将其与参考输出文件进行比较;
diff命令可能会有所帮助。
- 您的练习1电路在测试框架电路(
练习2:构建BrUn控制信号
BrUn控制信号用于告诉分支比较器分支比较是对有符号数还是无符号数进行的。下表总结了BrUn的期望值。
| 指令类型 | BrUn | 备注 |
|---|---|---|
| 无符号分支比较 | 1 | - |
| 有符号分支比较 | 0 | - |
beq 或 bne | 无关 | 确定数字是否相等时不需要数字的符号 |
| 非分支 | 无关 | 非分支指令不使用分支比较器的输出 |
如果值列为"无关",这意味着您可以将BrUn设置为0或1。
记住两个输入要么都是有符号的,要么都是无符号的。不能出现一个数字是有符号的而另一个是无符号的情况。我们的硬件不支持将无符号数与有符号数进行比较。
我们为您提供了以下提示来帮助您实现。
指令的哪个字段标识分支类型?
funct3opcode字段重要吗?
您可以使用opcode和funct3字段,在指令执行无符号分支比较时将BrUn设置为1。
然而,在实现BrUn时,opcode字段并不重要,因为对于所有非分支指令它都是"无关"的。我们可以简单地在funct3字段对应于无符号分支指令的funct3字段时输出1。如果BrUn对于非分支指令为1是可以的,因为非分支指令不使用分支比较器的输出。
根据您的实现,常量和比较器对本练习非常有帮助。常量可以在Wiring库中找到。您可以在左下角窗口的Properties部分选择常量的值。您也可以在Properties部分更改用于表示常量的位数。
比较器有两个输入,用于确定第一个输入是小于、等于还是大于第二个输入。它们可以在Arithmetic库中找到。您可以在左下角窗口的Properties部分选择输入的大小以及比较是无符号还是补码。
本练习对项目3有帮助!
- 打开
ex2.circ。此电路接收32位RISC-V指令作为输入,并输出BrUn控制信号的值。 - 修改电路,使其正确生成
BrUn的值。- 注意:输入指令可能是也可能不是分支指令。
测试
请参阅练习1的测试部分,但输出文件前缀现在是ex2-test而不是ex1-test。
练习3:到处都是低效
对于本练习,我们可以假设寄存器最初携带零值。我们将使用实验文件ex3.circ,它应该有一个名为non_pipelined的子电路,看起来像这样:

这个电路简单地接收两个输入,将它们相乘,然后将结果添加到当前状态值。对于这个电路,假设加法器块的传播延迟为45ns,乘法块的传播延迟为60ns。寄存器的CLK-to-Q延迟为10ns,建立时间为10ns,保持时间为5ns。假设两个输入都从寄存器接收数据(因此输入在上升沿之后CLK-to-Q时间到达)。
本练习要求您在ex3_answers.txt中写下您的答案。
- 问题1:此电路的关键路径长度是多少ns。答案应该是一个没有单位的整数。
练习4:管线化
我们希望提高这个电路的性能,使其以更高的时钟频率运行。为实现这一点,我们希望在我们的流水线中有两个阶段:乘法阶段和加法阶段,按这个顺序。
为了管线化电路,我们需要一个寄存器来保存流水线阶段之间计算的中间值。这是流水线的一个普遍主题。
为了检查您的流水线是否仍然产生正确的输出,我们将认为电路的输出是"正确的",当且仅当它对应于非流水线版本将发出的输出序列,但现在电路将有一个前导零。这个前导零的出现是因为流水线的第二阶段在第一个周期中是"空的"。
要查看电路的输入和每个周期的相应输出,请查看out/ex4-test.ref。周期零的输出是00000000,因为还没有任何东西通过流水线。周期零本质上只是打印电路的起始状态。周期一的输出是00000000,因为流水线的第二阶段在第一个周期中是"空的"。
我们讨论过,如果一条指令依赖于前一条指令的输出,我们需要插入一个流水线"气泡"(或多个)或包含转发逻辑,以确保第一条指令的输出准备好作为第二条指令的输入。提醒一下,气泡故意延迟流水线中的指令。
为什么这种"气泡"对于这个特定电路是不必要的?
对于本练习,一条指令包括加法和乘法。加法和乘法不是两条独立的指令。
相反,它们是流水线中的两个独立阶段,类似于EX和MEM是流水线中的两个独立阶段,它们对同一条指令进行操作,EX先对指令进行操作,然后MEM再对指令进行操作。
在这个特定电路中我们没有任何气泡,因为没有任何单独的指令相互依赖。
本练习要求您在ex4_answers.txt中写下您的答案。问题编号可能与步骤编号不同,请小心!
- 打开
ex4.circ并管线化ex3.circ中的电路- 问题1:这个流水线电路的关键路径是多少ns。答案应该是一个没有单位的整数。
- 问题2:这个流水线电路的最大时钟频率是多少MHz。答案应该是一个没有单位的十进制数。
测试
请参阅练习1的测试部分,但输出文件前缀现在是ex4-test而不是ex1-test。
练习5:调试电路
让我们看一下num_checker电路的一个有bug的实现,给定一个32位数字,它将告诉我们它是奇数、偶数、零、大于十五,还是五或十。这个电路可能不是太令人兴奋,但测试电路的结构将与项目非常相似。
本练习要求您在ex5_answers.txt中写下您的答案。文本文件只有前两个问题的项目符号,所以请在回答问题时添加项目符号(例如3. 、4. 等)。问题编号可能与步骤编号不同,请小心!
-
首先,让我们熟悉一下测试框架。打开练习5的测试电路(
File -> Open -> lab06/tests/ex5-test.circ)。-
项目测试框架使用ROM存储RISC-V指令,这些指令将输入到您的CPU中。对于此电路,带有十六进制数字的大块是我们的ROM,其中包含我们将按顺序输入到
ex5电路中的数字。 -
此ROM将在提供的索引处输出一个32位整数。在本例中,我们按顺序遍历ROM中存储的每个数字。

-
-
从测试框架导航到我们的
num_checker电路:右键单击num_checker,然后点击"View num_checker"。
-
或者,"Simulate"选项卡也可以让您轻松地在电路之间切换。

-
-
在开始调试之前,我们应该做两件事:
-
在
num_checker中构建以下小装置。记住将装置中的寄存器标记为num_cycles。这个小装置类似于计数器,允许我们通过探测寄存器的输出来检查当前时间步。该装置可以单独放置在num_checker电路的任何位置。建议您在项目中调试时构建此装置,以便更轻松地调试。- 要编辑
num_checker,您必须打开其对应的电路文件ex5.circ。您不能通过测试电路进行编辑。编辑后,请重新打开ex5_test.circ以使更改生效。
- 要标记寄存器,请使用选择工具(看起来像鼠标光标的那个),然后双击相应的寄存器。
- 要编辑
-
接下来,在Logisim中打开波形查看器(
Simulate -> Timing Diagram)。窗口弹出后,点击Timing Diagram查看波形。如果窗口太小,可以将其沿屏幕一侧拖出,使其看起来像下面的窗口。
-
点击
Add or Remove Signals按钮。使用中间的箭头从视图中移除信号halt和CYCLE。同时将num_cycles信号添加到视图中。您可以向下滚动电路中可用的信号,点击num_checker下拉菜单找到num_cycles信号。
-
点击
Ok后,您应该能在波形查看器中看到添加的num_cycles信号。确保展开Signal Name列使所有文本可见。然后右键单击num_cycles将其类型改为Signed Decimal。对num信号进行同样的操作。
-
波形查看器允许我们查看大量信号及其每个周期对应的值。建议您在项目中调试时使用波形查看器。
-
-
-
让我们探测(即查看某些电线的值)一些输出,并使用波形查看器精确定位bug!
- 问题1:时间步0时(滴答之前)
bit_zero的值是多少?答案应为单个数位。(提示:您可以在num_checker电路内部找到bit_zero。) - 问题2:时间步0时
is_odd的值是多少?答案应为单个数位。
- 问题1:时间步0时(滴答之前)
-
滴答时钟一个完整周期。电路现在应该在时间步1(您在步骤3第1部分中制作的装置也应反映这一点)。
- 问题3:
bit_zero的值是多少?答案应为单个数位。 - 问题4:
is_odd的输出是什么?答案应为单个数位。
- 问题3:
-
滴答时钟另外两个完整周期。
- 问题5:使用波形查看器,
num的值是多少(十进制)?答案应为整数。 - 问题6:使用波形查看器,输出引脚
greater_than_fifteen的值是多少?答案应为单个数位。
- 问题5:使用波形查看器,
-
滴答时钟再一个完整周期。
- 问题7:使用波形查看器,
num的值是多少(十进制)?答案应为整数。 - 问题8:使用波形查看器,输出引脚
greater_than_fifteen的值是多少?答案应为单个数位。
- 问题7:使用波形查看器,
-
滴答时钟再一个完整周期。
- 问题9:电路现在处于哪个时间步?答案应为整数。
- 问题10:
bits_five的值是多少(二进制)?答案应为3位二进制数,带0b前缀。 - 问题11:输出引脚
five_or_ten的值是多少?答案应为单个数位。
-
现在我们已经熟悉了电路的行为,让我们运行测试(使用
python3 test.py)。 -
要查看输出,请打开
ex5-test.out(电路输出)和ex5-test.ref(期望输出)。两个文件都位于tests/out文件夹中。应该有一些值是不同的。 -
运用您从模拟电路中学到的知识,修复
num_checker电路使其通过测试。请随意再多运行几次模拟器!- 注意:所有模拟都应在
ex5_test.circLogisim窗口内进行。记得导航回num_checker电路块来探测输出。不要在num_checker窗口中模拟(时钟和输入不会在文件之间同步)。
- 注意:所有模拟都应在
练习6:反思和反馈表
我们每周都在努力改进课程——请填写此调查告诉我们您到目前为止在讨论课和实验中的体验!
提交
保存、提交并推送您的工作,然后在Gradescope上提交到实验6作业。