跳转至

一条 load/add/store 如何贯穿机器

场景与初始状态

源程序为:

c = a + b;

编译器约定 x10 指向三个连续 32 位整数,生成:

lw  x5, 0(x10)
lw  x6, 4(x10)
add x7, x5, x6
sw  x7, 8(x10)

假设:

采用 RV32I 风格,通用寄存器宽度 XLEN = 32
x10 = 0x1000
a = 20, b = 22
页表映射有效
指令已在 L1 I-cache
目标数据行不在任一级数据 cache,但页面已驻留主存

最终可观察目标是 Mem[0x1008] = 42。下面所有预测、停顿和缓冲都必须保持这个架构结果。

1. 从源代码到机器码

编译器完成类型选择、寄存器分配和指令选择。汇编器把助记符变成机器码,把标签解析为位移;链接器安排代码与数据地址,装载器建立进程虚拟地址空间。

此时 lw 的语义已经固定,但“有几级流水、cache 多大、是否乱序”仍未决定,这正是 ISA 与微体系结构的边界。

2. 取第一条 lw

PC 给出虚拟指令地址。前端查询指令 TLB,得到物理页号后访问 L1 I-cache。因为假设命中,指令位进入译码器,PC 同时准备顺序增加 4。

若有分支预测器,它此时提供后续取指方向;本例没有分支,前端连续取四条指令。

3. 译码与读寄存器

译码器识别 load 格式,提取 rs1=x10rd=x5 和立即数 0。寄存器堆读出:

\[ R[x10]=0x1000. \]

控制器设置 ALU 输入选择为立即数、存储读有效、写回来源为内存、寄存器写有效。写使能还不会立刻提交,它随指令沿流水线向后传递。

4. EX 计算虚拟有效地址

ALU 做普通整数加法:

\[ VA=R[x10]+\operatorname{signext}(0)=0x1000. \]

这一步只得到虚拟地址,不是最终物理位置。还要检查对齐:32 位 load 通常要求地址是 4 的倍数。

5. 地址翻译与 L1 miss

数据 TLB 用 VPN 查到 PPN并验证读权限,组合出物理地址。L1 D-cache 用组索引查 tag,发现 miss:

  1. miss 状态缓冲记录尚未完成的请求;
  2. 请求发往 L2、末级 cache,最终到内存控制器;
  3. DRAM 激活相应 bank/row 并返回整个 cache line;
  4. cache line 填入 L1,目标 4 B 由块内偏移选出。

第一次 miss 的等待可能远长于一次 ALU 加法。若处理器支持非阻塞 cache,其他独立指令可能继续;简单顺序核心会冻结相关流水阶段。

6. 第一条 load 写回

数据 20 沿写回通路进入 x5。若采用精确异常,只有确认翻译、权限和访存都成功后,寄存器更新才可成为架构状态。

第二条 lw 访问 0x1004。因为与 a 常位于同一 cache line,第一次 miss 带回的相邻数据让它很可能 L1 命中。这就是空间局部性带来的实际收益。

7. add 的数据相关

add x7, x5, x6 依赖两条 load。若第二条 load 的数据直到 MEM 末尾才可用,紧随其后的 add 可能出现 load-use 冒险。冒险检测器让 add 停顿,或在值可用时从 MEM/WB 转发到 ALU。

ALU 最终计算:

\[ R[x7]=20+22=42. \]

补码加法器执行模 \(2^{32}\) 运算。本例没有溢出;在 RV32I 中,整数 add 即使溢出也不触发算术异常,只保留结果的低 32 位。其他 ISA 或语言运行时可以规定不同的溢出处理。

8. sw 的地址与数据路径

store 同时需要:

  • x10 与立即数 8 计算地址 0x1008
  • x7 提供数据 42。

x7 可通过 store-data 转发直接送来,不必等它先写回再读。地址翻译检查写权限,D-cache 定位目标行。

若使用写回、写分配 cache,cache 行被更新并置 dirty,下层内存可能暂时仍是旧值。ISA 关注的是之后合法 load 应观察到正确结果,而不是要求 DRAM 立刻写成 42。

9. store buffer 与提交

高性能处理器可在执行阶段先为 store 分配队列或 buffer 项并准备地址、数据;只有在异常检查完成且按程序顺序提交后,它才可向 cache 排空并对外可见。后续对同地址的 load 必须从尚未排空的 store 转发最新值,避免读到旧 cache 数据。

在多核系统中,store 获得该 cache 行所有权并使其他核心副本失效。何时对其他核心可见还受内存一致性模型约束。

10. 从晶体管角度看

上述每个寄存器位由存储单元保持;ALU 加法由门网络传播进位;tag 比较由并行比较器完成;控制信号驱动多路选择器和写使能;时钟边沿让流水寄存器同步采样。更底层的晶体管尺寸、电压和连线延迟共同限制频率与功耗。

组成原理的价值就在这里:不必逐管分析,也能知道某次等待发生在 ALU、翻译、cache、DRAM 还是一致性协议。

失败路径

这组指令还可能遇到:

  • 非法或未对齐地址;
  • TLB miss 或 page fault;
  • cache 替换需要写回 dirty 行;
  • 多核所有权请求等待;
  • ECC 检测到内存错误。

精确异常要求更早指令已完成,出错指令和更晚指令不留下架构副作用。正常路径和失败路径必须共同设计。

自测

  1. 为什么第二个 load 可能受益于第一个 load 的 cache line 填充?
  2. 这四条指令中至少有哪些 RAW 相关?分别可用转发还是停顿处理?
  3. store 已对本核后续 load 可见,是否意味着 DRAM 和其他核心已立刻看到?