计算机体系结构 06:单周期数据通路
单周期数据通路:一条指令怎样走完五件事
中心问题:如果一条指令必须在一个时钟周期内完成,硬件要给它准备哪些路?
单周期 CPU 把取指、译码、执行、访存、写回放进同一个周期。它不表示真实高性能处理器都这样工作;它的价值在于把“指令语义需要的硬件路径”一次摊开。add 要读两个寄存器、进 ALU、写回寄存器;lw 还要访问数据存储器;sw 写内存但不写寄存器;分支既要比较寄存器,又要决定下一个 PC。
本篇覆盖 add/sub/and/or/addi/andi/ori/lw/sw/beq/bne/blt/bge。证据等级是“手算”和“功能执行”。功能执行指 Python 脚本生成控制表、穷举 ALU 断言并保存静态延迟算式;延迟部分不是周期仿真,也不推进阶段事件。不覆盖 jump、CSR、异常、压缩指令、流水线冒险、多周期控制或真实硬件频率。
依据和边界
RISC-V RV32I 文档给出本篇使用的指令语义:整数寄存器-寄存器指令读 rs1/rs2 并写 rd;立即数逻辑和加法读 rs1 加 sign-extended 12 位立即数;load/store 用 rs1 + offset 得到有效地址;条件分支比较两个寄存器,满足条件时把 sign-extended B-immediate 加到分支指令地址形成目标地址。依据见 RV32I Base Integer Instruction Set, Version 2.1。
控制信号表对齐 CS61C 单周期控制材料。该材料把 PCSel、ImmSel、BrUn、ASel、BSel、ALUSel、MemRW、RegWEn、WBSel 作为控制器输出,并指出分支 PCSel 依赖比较器产生的 BrEq/BrLT。依据见 CS61C Single-Cycle CPU Control: Control Logic Design。
时序公式沿用第 05 篇:寄存器边沿后先经过 clk-to-q,组合逻辑传播,下一处寄存器在 setup 窗口前采样。关键路径仍是最长寄存器到寄存器路径。
一张足够小的数据通路
下面是本篇使用的教学数据通路。箭头只表示主要数据方向,控制线没有全部画出:
1 | |
这张图只保留教学所需部件:PC、指令存储器、寄存器堆、立即数生成器、ALU、数据存储器、写回选择器和 PC 选择器。控制器读取指令字段,再决定每个选择器和写使能。
单周期设计的强约束是:每条受支持指令都必须在一个周期内走完自己的最长路径。不同指令不用同样多的部件,但周期长度必须照顾最慢的那一条。
四类指令走四条路
R 型算术逻辑指令的路径:
1 | |
add/sub/and/or 都读两个寄存器,差别只在 ALUSel。它们不访问数据存储器,MemRW = Read 或空闲,RegWEn = 1,WBSel = ALU。
I 型算术逻辑指令的路径:
1 | |
addi/andi/ori 的第二个 ALU 输入来自立即数,BSel = Imm。RISC-V 文档说明普通立即数会 sign-extend;因此立即数生成器必须在 ALU 计算前稳定输出。
load/store 的地址路径:
1 | |
lw 写寄存器,WBSel = Mem。sw 写内存,不写 rd,RegWEn = 0。两者都用 ALU 做地址加法,所以 ALUSel = ADD。
条件分支的路径:
1 | |
分支不写寄存器,也不写内存。它的难点是 PCSel 不是只由 opcode 决定。beq 要看 BrEq,blt 要看 BrLT;比较器输出稳定后,控制器才能选 PC + 4 或分支目标。
控制表是指令语义到线路选择的翻译
本批脚本生成了控制表,附件见 control_table.txt。核心行如下:
1 | |
这张表不是从硬件自动综合出来的,而是把本篇支持的指令语义手工编码为教学控制信号,再由脚本保存和复查。它的验收意义有两个:
第一,能看出部件复用。ALU 既做算术结果,也做 rs1 + imm 地址计算,还做 PC + imm 分支目标计算。单周期教学 CPU 为了少放硬件,让这些计算共享一块 ALU;代价是同一周期内必须等它完成。
第二,能看出“不关心”不是“不存在”。sw 不写回寄存器,所以 WBSel 对最终状态无影响,可以标为 -。但控制器仍必须保证 RegWEn = 0,否则错误写回会污染寄存器状态。
手算一条 lw 路径
用第 05 篇同一份脚本里的延迟假设。lw 的地址路径里,寄存器读数、立即数生成和控制信号会并行到达 ALU 输入选择器,所以脚本先取三者最大到达时间,再加 mux 和 ALU 延迟:
1 | |
lw 的路径是:
1 | |
脚本输出确认 single_cycle_lw 是当前模型最长路径:
1 | |
可复查附件:timing_report.txt、verification.json、logic_cases.py。
这就是单周期 CPU 的核心代价。add 在同一模型中只需要 660 ps,但它也必须使用至少 920 ps 的周期,因为所有指令共享同一个时钟周期。后面的多周期和流水线会把这个浪费拆开:慢指令多用阶段,快指令不必被最慢路径完全拖住。
分支为什么容易成为控制路径问题
beq 的分支目标可以和比较并行计算:
1 | |
但最终 PC 只能选一个。脚本把 target 到达时间、select 到达时间和 PC + 4 到达时间分开计算,最后取最大值,再加 PC mux 和 PC setup。若比较器还没有稳定,PCSel 可能先指向 PC + 4,随后变成 target。只要 PC 寄存器采样前 PCSel 和目标地址都稳定,机器仍能得到正确下一条指令;若稳定得太晚,就会采到错误 PC。
因此控制器不是“译码后马上给出全部答案”的静态表。大多数控制信号只由指令字段决定,分支 PC 选择还要等数据路径的比较结果。CS61C 控制材料也把控制器拆成 ROM/解码和 branch-taken 组合逻辑两部分,本篇采用同样边界。
本篇验收
读完本篇,应能完成三件事:
- 给
add/lw/sw/beq分别画出经过的主要部件。 - 按表解释
RegWEn、MemRW、WBSel、PCSel为什么不同。 - 用声明的延迟假设算出最长单周期路径,并说明这个结果只对本模型有效。
没有完成的内容也必须清楚:本篇没有证明流水线正确性,没有证明真实 CPU 的周期时间,没有处理异常和跳转,也没有让第 07 篇参考执行器变成硬件。
练习
练习一:在本篇控制表里,sw x3, 8(x4) 的 ImmSel、ASel、BSel、ALUSel、MemRW、RegWEn 应该是什么?
答案:ImmSel = S,因为 store 使用 S 型立即数;ASel = Reg,第一个 ALU 输入来自 x4;BSel = Imm,第二个输入是偏移 8;ALUSel = ADD,地址为 x4 + 8;MemRW = Write;RegWEn = 0,因为 store 不写 rd。
练习二:若数据存储器读延迟从 260 ps 降到 150 ps,其余假设不变,lw 路径变成多少?它还一定是最长路径吗?
答案:前半段 addr 仍为 605 ps。新的 lw 路径为 max(605 + 150, 315) + 30 + 25 = 810 ps。脚本原表中 sw 为 825 ps,taken branch 为 620 ps,add 为 660 ps。因此在这个修改后,sw 变成最长路径。这个练习说明关键路径是模型和参数共同决定的,不是某类指令永远固定最长。





