add R5, R1, R2 / sub / and / or / xor,三操作数写成「目标, 源1, 源2」;
装常量用 li R10, 0x1234ABCD(十进制、0x、0b、负数都认,-1 就是 0xFFFFFFFF)。R5、r5 或 $0 都行;# 开头是注释,空行忽略。右边一列是逐行结果预览,写错的行会直接标出来。add R1, R1, R1,rd 和 rs 是同一个寄存器,本拍读到的仍是旧值;
②把目标写成 $0,写入被静默丢弃,R0 永远是 0;③写 li R5, -1 再 add R6, R5, R1,看看无符号回绕成什么样。
| 端口 | 方向 | 触发方式 | 延迟 | 为什么需要 |
|---|---|---|---|---|
| 读口 1(A) | 出 | 电平(组合) | 译码 + 多路选择,~1 个门延迟 | ALU 的源操作数 1 |
| 读口 2(B) | 出 | 电平(组合) | 同上 | ALU 的源操作数 2 |
| 写口(W) | 入 | 上升沿 + WE | 需要一个完整时钟沿 | 把结果存回去 |
同拍读写的语义:读口是「一直盯着某个寄存器的窗户」,写口是「只在沿上开一次的门」。
所以一条 R3 ← R1 + R3 在同一拍内:先(组合地)读出 R1 和 R3 的旧值送进 ALU,算出和,
到这一拍末尾的上升沿才把和写回 R3。本拍读不到本拍写的值 —— 这不是缺陷,而是时序电路的定义。
到了流水线,这条性质就变成了 RAW 数据冒险:写回发生在第 5 级,而下一条指令在第 2 级就要读它,中间差 3 拍,只能靠转发(forwarding)或停顿补上。
$0 为什么免费:R0 不是「存了一个 0 的寄存器」,而是读口上直接接地、写口干脆不接。
这样任何 R0 作为源就白得一个常数 0,不需要额外的「清零」指令,也不需要占用一个真实寄存器;
把结果写进 R0 就等于「算完丢掉」(用来做比较、占位),而且不会报错。代价只是译码器要特殊处理编号 0。
读口有多贵:32 选 1 的 32 位多路器,每位要 31 个 2:1 选择器,32 位就是 992 个;两个读口接近 2000 个 2:1 选择器,
而存储本体 32×32 = 1024 个触发器。读懂的成本和存下的成本同量级 —— 这就是寄存器堆只能有 32 个、
而且编译器要做寄存器分配(尽量把变量留在寄存器里、减少来回搬运)的原因。想加第三个读口,面积几乎再翻一半。
为什么不用内存代替:内存一次只能给一个数据(单端口),取两个操作数要两拍;而且要走地址、访存、等 CAS。
寄存器堆的组合读是「同一个门延迟」,这是它能塞进 CPU 关键路径的前提。