计算机体系结构 08:多周期执行
多周期执行:怎样在几拍之间保存一条指令
一条 lw 要取指、读基址、计算地址、读取内存,最后写回寄存器。若这些动作必须在一拍里结束,时钟周期就要覆盖整条路径。把它拆成几拍可以缩短每拍必须完成的组合路径,也允许同一硬件部件在不同拍承担不同任务。代价是指令要占用更多周期,控制器还必须保存中间结果。
中心问题是:拆开的每一拍究竟保存什么,下一拍怎样知道继续做哪一步?本篇用第 07 篇相同的 .hex 求和程序,构建实际推进状态的多周期解释器。证据等级包括“教学时序模型”和带明确题设的“手算”。模型没有门级网表、综合时钟或面积数据,因此不能据此断言某块硬件变快或变小。
保存状态以后,组合逻辑才能跨拍复用
单周期通路中的某个中间值在该拍内一直可见。进入多周期设计后,组合逻辑输入可能在下一拍改变;后续仍要使用的值必须写进寄存器。指令字需要 IR,寄存器堆的读出值需要 A/B,地址或算术结果需要 ALUOut,读取内存的结果需要 MDR。实际实现可以合并或重新命名这些寄存器,所保存的信息不能凭空消失。
Berkeley CS152 的多周期数据通路讲义展示了这些跨周期寄存器及控制状态。讲义使用 MIPS;这里借用的是多周期组织方法,指令语义来自本系列的 RV32I 子集,具体状态数由本篇模型决定。
模型中的 Packet 保存指令、两个操作数、结果、有效地址和下一 PC。multicycle.py 的循环每次只处理一个状态:IF 读取指令字,ID 读取寄存器,EX 运算,MEM 访问数据,WB 写回。它没有读取 C 执行轨迹再按指令名称加周期。C 程序只在事后提供独立的提交结果用于比对。
控制状态决定当前允许改变的对象
本模型的正常执行路径是:
1 | |
分支在 EX 决定下一 PC。store 在 MEM 写数据,随后取下一条指令;load 在 MEM 读出的数据还要经过 WB 才成为目标寄存器的新值。表里的 PC 更新指当前指令完成时接受 next_pc,并不是每一拍都把 PC 加四。
| 状态与指令类别 | 组合动作 | 本拍结束后保留的值 | 允许的架构写入 |
|---|---|---|---|
| IF,所有指令 | 以 PC 读指令字 | IR、原始 PC | 无 |
| ID,所有指令 | 译码,读 rs1/rs2 | A、B、立即数与控制类别 | 无 |
| EX,ALU | A 与 B/立即数运算 | 算术结果、next_pc | 无 |
| EX,load/store | A 加符号扩展偏移 | 有效地址、store 数据 | 无 |
| EX,branch | 比较操作数、选择后继 PC | 分支结果 | PC |
| MEM,load | 以有效地址读取数据 | 读出值 | 无 |
| MEM,store | 以有效地址写入 B | 完成标志 | 内存、PC |
| WB,ALU/load | 选择算术结果或读出值 | 完成标志 | rd(排除 x0)、PC |
IRWrite、A/BWrite、ALUOutWrite、MDRWrite、MemWrite、RegWrite 可以由这张表逐行推导。例如 MEM-load 允许 MDRWrite,禁止 MemWrite 和 RegWrite;下一拍 WB 才允许 RegWrite。控制器不能只输出“下一状态”,也要输出当前状态的写使能。
这里的控制表是一组教学约束,没有为所有加法器指定物理共享关系。主 ALU 可在不同指令中承担算术或地址运算;PC 加四、分支目标加法和比较是否共用 ALU,会影响状态划分。若把这些动作也强制放到同一块 ALU 上,就需要重新检查某一状态是否要求它同时计算两个结果,不能保留原状态数却忽略资源冲突。
同一个求和程序得到 116 个模型周期
实验沿用基址 128、五个输入 3,4,5,6,7,把和写入地址 148。动态执行包含 18 条 ALU 指令、5 条 load、1 条 store 和 5 条分支。指令存储器和数据存储器在本实验中都被当作一次访问一拍。
| 类别 | 动态条数 | 每条周期 | 总周期 |
|---|---|---|---|
| ALU | 18 | 4 | 72 |
| load | 5 | 5 | 25 |
| store | 1 | 4 | 4 |
| branch | 5 | 3 | 15 |
| 合计 | 29 | 平均 4 | 116 |
逐拍输出见 sum-multicycle.txt,控制表见 control-states.txt。每行都有周期、动态指令编号、PC 和状态,可以逐条加总,而不必相信正文里的汇总数字。
最终提交记录包含 x3=25、mem148=25。验证程序对每条指令的 PC、指令字、操作、目标寄存器或内存事件、next_pc 做完整 JSON 对象比较;执行错误和最终 halt 对象也参加比较。C 与 Python 没有共享译码或算术实现,两个执行器读同一输入文件。
下载 pipeline-lab.zip 后,在解压目录运行:
1 | |
第一条命令重新编译 C 参考并运行整个本批回归;第二条只执行多周期求和。需要 Python 3.10+ 与 C11 编译器,没有第三方 Python 包。完整记录见 verification.json:其中 41 个程序在多周期模型中与参考一致,另有三份非法输入在解析阶段被拒绝。执行错误案例也算一次差分,不能把这些数字称为全部正常结束的程序。
周期更短,不保证总时间更少
取一个明确的课堂题设:单周期设计的周期为 800 ps,多周期设计为 220 ps。这两个数字只用于算式,没有来自任何器件或综合报告。在该题设下,相同的 29 条动态指令有:
1 | |
多周期每拍短了很多,总时间仍多出 10%。它的平均 CPI 是 4,周期缩短倍数只有约 3.64。只有时钟缩短足以抵消更多周期,时间才会减少。复用硬件也可能带来面积或功耗方面的收益,但这些收益需要另一组测量证据。
实际周期至少要覆盖当前最慢状态的组合路径,加上寄存器的 clk-to-q、setup 以及所采用的时钟裕量。若增加选择器或让一块 ALU承担更多输入路径,最慢状态也可能变长。先规定状态数,再任意给一个极短时钟,会把物理约束从比较里删掉。
内存未完成时,状态要能够保持
--memory-cycles 4 把每次数据访问的 MEM 占用设为四拍。求和有六次数据访问,原来各占一拍,现在各多三拍,因此总周期应增加 18,得到 134。这是可复跑的模型预测;附带的延迟实验还覆盖了按地址返回不同占用时间的接口。
MEM 等待期间不能重复写 store,也不能提前让 load 进入 WB。模型只在最后一个 MEM 周期执行数据读写,等待拍保留地址与数据。这里用一个整数表示总占用拍数;真实设备可能有请求、接收、返回及重试等不同握手阶段,本模型没有这些接口时序。
练习与验收
把求和的多周期参数改为 --memory-cycles 4,检查状态日志是否有 134 行,并检查最后的 mem148 是否仍为 25。答案来自六次数据访问各增加三拍,不是所有 29 条指令都增加三拍。
再考虑把分支比较与目标地址计算放到唯一的一块 ALU,且两者不能在同一拍完成。应增加或重新安排分支状态,并重新计算分支 CPI;不能只把硬件图中的两个部件画成一个而沿用三拍分支。此题没有唯一微架构答案,验收点是能够指出冲突和需要保留的中间值。
本篇验收要求能从 lw、sw 和分支分别画出状态路径,指出每一拍允许的写入,并用 IC × CPI × 周期长度 比较题设中的执行时间。完成脚本只证明教学状态机及所测输入通过,RTL 综合、实际频率、面积和功耗仍未验证。
阅读衔接
前一篇:计算机体系结构 07:有限 RV32I 参考执行器。
参考资料
- Berkeley CS152 Lecture 9: Multicycle Processors:跨周期寄存器、控制状态与等待状态的教学来源。
- RISC-V RV32I,Version 2.1:算术、load/store、分支的架构语义;不规定本文的控制拍数。
- 本篇附件中的
multicycle.py、rv32i_ref.c与verification.json:本篇具体数字与差分范围的来源。






