计算机体系结构 00:一次数组求和经过哪些层
计算机体系结构 00:一次数组求和经过哪些层
一个程序从源代码变成结果,中间至少经过四层问题。ISA 规定软件能看见的寄存器、指令和内存效果;微架构决定这些指令怎样在一台具体处理器里重叠执行;实现工艺决定电路能以怎样的延迟、功耗和面积工作;系统软件负责把进程、虚拟地址、异常和设备组织成可用环境。把这四层混在一起,最常见的错误是用“CPU 很快”“缓存慢了”“编译器优化了”这种句子解释一切。
本篇只做入口:沿一个数组求和程序,把源代码、指令、硬件可见状态和结果检查分开标注。证据等级为“手算”“功能执行”。这里不声称得到流水线周期、缓存命中率或真实 CPU 的内部状态;第 07 篇的基础执行器只提交 RV32I 子集的架构状态,后续流水线和缓存章节再加入时序模型。
问题:同一个结果来自哪几种机制
示例工作负载是把 5 个 32 位整数相加:
1 | |
手算结果是 3 + 4 + 5 + 6 + 7 = 25。这个等式说明算法结果,不说明机器怎样得到结果。机器层还要回答:数组元素放在哪里,循环变量放在哪个寄存器,分支怎样回到循环头,最后一次分支为什么不跳转,结果写回哪个内存地址。
本系列采用 RISC-V 作为主线 ISA。RISC-V 官方非特权规范把 RV32I 描述为 32 位基本整数指令集,包含 32 个 32 位整数寄存器,x0 硬连为 0,pc 保存当前指令地址;基本指令格式固定为 32 位,基础 ISA 的指令地址按 4 字节对齐。后续文章会逐项进入编码和 ABI,本篇只使用这些规则解释轨迹。参考资料见文末的 RISC-V 官方规范链接。
模型与边界
本篇的教学程序来自 examples/computer-architecture/base/programs/sum.hex,同名素材目录里保存了 sum.hex.txt。输入格式很窄:.text 每行一个 32 位指令字,.data 每行一个字节地址和 32 位数据。基础执行器按小端字节序装入内存,执行一个有限 RV32I 子集:addi、lw、add、bne、sw 等。它不支持系统调用、特权指令、压缩指令、异常恢复、流水线、缓存和真实计数器。
这条边界很重要。执行器输出的是“功能执行”:每条已提交指令改变了哪些架构寄存器或内存。它不能证明商品 CPU 也用了 29 个周期,不能证明分支预测怎样工作,也不能证明缓存是否命中。后续所有章节都沿这个原则扩展:先把可见语义说清楚,再讨论某个实现如何让它更快。
从源代码到指令轨迹
这个程序的数据放在字节地址 128、132、136、140、144,结果写到 148。指令先设置三个寄存器:
| 寄存器 | 含义 | 初值 |
|---|---|---|
x1 |
当前数组元素地址 | 128 |
x2 |
剩余元素个数 | 5 |
x3 |
累加和 | 0 |
循环体做 5 件事:从 x1 指向的地址 lw 一个 32 位字到 x4;把 x4 加到 x3;把 x1 加 4 指向下一个元素;把 x2 减 1;若 x2 != x0,用 bne 回到循环头。最后一次 x2 变成 0,分支不再跳转,sw 把 x3=25 写到地址 148。
功能轨迹的前几步如下,完整输出在同名素材目录的 sum-trace.jsonl.txt:
1 | |
循环结束处的关键记录是:
1 | |
这两行能支持的结论很有限,但足够清楚:在这个教学 ISA 子集和这份输入上,程序提交 29 条指令后停止,架构寄存器 x3 与内存地址 148 都保存 25。这里没有“可能更快”的判断,只有“可见状态正确”的判断。
同一逻辑也可以用真实 C 程序在本机编译。附件 sum.c 运行结果为 sum-host-output.txt:
1 | |
Apple clang 21.0.0 在本机 arm64 上对 sum5 生成的 -O2 汇编见 sum-arm64-O2.s.txt,核心片段是:
1 | |
这段汇编不是 RISC-V;它是本机 AArch64 产物。它证明同一 C 源程序可以被降到另一套 ISA,且宿主运行结果仍为 25。手写 RV32I 机器码和本机 AArch64 汇编服务于两个不同问题:前者让后续教学执行器有稳定输入,后者让“源代码会变成目标机器指令”这件事可检查。
跨层路径可以这样读:
1 | |
| 层 | 本篇对象 | 可见证据 |
|---|---|---|
| 源代码 | sum.c 的 sum5 循环 |
sum5=25 |
| 目标 ISA | AArch64 汇编、手写 RV32I 机器码 | ldr/addv/add 片段;sum.hex.txt |
| 架构状态 | RV32I pc、x1/x2/x3/x4、内存地址 148 |
sum-trace.jsonl.txt |
| 测量边界 | 只检查结果和功能轨迹 | 不包含周期、缓存、频率、能耗 |
体系结构四层怎样分工
ISA 层回答“这条指令承诺什么”。add 把两个源寄存器的低 XLEN 位结果写到目标寄存器;lw 从地址读一个字;bne 在两个寄存器不等时改变 pc。RISC-V 规范还规定 x0 总是 0,所以执行器每步都把 x0 强制保持为 0。
微架构层回答“怎样实现这些承诺”。一台简单处理器可以顺序取指、译码、执行、访存、写回;一台高性能处理器可能预测分支、乱序执行和重命名寄存器。只要提交到 ISA 可见状态时与顺序语义一致,它们都可以运行同一段程序。第 09–14 篇会逐步解释为什么内部乱序仍要按架构状态收束。
实现工艺层回答“这些电路能跑多快、耗多少电”。同样是 add,不同工艺、频率、电压和布线都会改变延迟与能耗。体系结构课通常不从晶体管物理开始,但必须承认真实机器不是抽象公式。
系统软件层回答“程序怎样获得运行环境”。真实 C 程序还有 ABI、栈、页表、异常、调度和设备。第 04 篇讲函数调用边界,第 20–21 篇讲地址翻译和异常。本篇故意用裸输入避开操作系统,目的是先把 ISA 可见状态固定住。
入口自测
以下五道题用于判断是否需要先补基础。每道题的答案都能从本篇案例推出。
0xfff10113这条addi x2,x2,-1执行前x2=5,执行后是多少?- 5 个 32 位元素一共占多少字节?如果起始地址是 128,最后一个元素地址是多少?
x0如果被写入 123,下一步还能读到 123 吗?- 第 27 步
bne x2,x0没有跳转,说明哪个寄存器变成了什么值? - 1 ns、1 µs、1 ms 各相差多少倍?
mem148=25能不能证明真实 CPU 的缓存命中?
参考答案:1. 4。2. 5 × 4 = 20 字节,地址是 144。3. 不能,x0 在 RV32I 程序员模型中硬连为 0,本执行器也保持这个约束。4. x2=0,与 x0 相等,所以不跳转。5. 1 ms = 1000 µs = 1,000,000 ns;不能证明缓存命中,因为这是功能执行输出,只记录架构内存结果,不记录缓存访问。
验收结果
本批实际运行:
1 | |
结果为 base regression: PASS。同名素材目录保存 base-environment.txt、sum.hex.txt 与 sum-trace.jsonl.txt。本机环境记录为 macOS arm64、Apple clang 21.0.0、Python 3.14.4。
本篇验收成立的部分:数组求和从 C 源码、本机 AArch64 汇编、手写 RV32I 机器码、寄存器轨迹、分支和最终内存写回形成一条可检查路径。保留缺口:没有真实硬件计数器;没有流水线、缓存、页表或分支预测证据。
练习
练习 1:如果把数据改成 {1, 1, 2, 3, 5},在不改变指令的情况下,最终 mem148 应是多少?
答案:12。程序只依赖 5 个连续 32 位字,循环次数仍是 5。
练习 2:如果初始 x2 错设为 4,而数据仍是 {3, 4, 5, 6, 7},最终写回什么?这属于 ISA 错误、程序错误还是微架构错误?
答案:18,只加到前四个元素。这是程序输入或机器码层面的语义错误;只要处理器忠实执行这段指令,就不是微架构错误。
参考资料
- RISC-V International,
RV32I Base Integer Instruction Set, Version 2.1:https://docs.riscv.org/reference/isa/v20260120/unpriv/rv32.html - RISC-V Instruction Set Manual snapshot: https://riscv.github.io/riscv-isa-manual/snapshot/spec/
- 本篇实验附件:sum.c、sum-host-output.txt、sum-arm64-O2.s.txt、sum.hex.txt、sum-trace.jsonl.txt、base-environment.txt、intro-environment.txt





