一、流水线实验回顾
1.1 实验进度
流水线中的组件本质上与单周期类似,主要新增的是各级之间的流水线寄存器(pipeline registers)。基础模块(单周期各组件)一定要测试好——单模块组件测试通过后,后面的连接和流水线级间寄存器就没有太多复杂的事。模块不对就没法调,没有什么能调的余地。
此外,可以尝试用大模型帮助检查基础模块代码。
1.2 实验扩展支持
有同学想做额外的乘法器和除法器——不管有什么自己的想法,只要自己感兴趣,都可以找助教或老师单独沟通实现要求。
1.3 后续实验方向
后续实验还会涉及 Forwarding/ByPass、分支预测、Memory 接口、Cache、CSR 特权指令、原子指令和 Fence。
二、指令集体系结构(ISA)设计
2.1 为什么要设计指令集
指令集(Instruction Set Architecture, ISA)是软件和硬件之间的契约。指令集设计的好坏直接决定了 CPU 实现的难易程度。
指令集就是给 CPU 下命令的语言。如果一门语言设计得不好,CPU 就会很痛苦。新指令集不断冒出来,又有老的不断死掉——都是经过了市场选择。
指令集有两个核心要求——清晰高效。对硬件设计者来说,要让设计电路时简单一点:第一少干点活,第二干的活尽量简单,不要来回折腾。
2.2 自然语言 vs 严谨形式语言的讨论
一个开放性问题:大模型时代,为什么不能用自然语言直接给计算机下指令?
数学之所以要有一套严谨的表达式和公式体系,是因为自然语言不严谨。拿一个不严谨的、开放的语言来让大模型替我们完成严谨的工作,这显然是有问题的。软件工程强调”写文档”——落到纸面上就会慎重,条列清楚才能看懂。
这对 ISA 设计的启示是:需要一套严谨的语言——不要让 CPU 拿到指令后问”这命令到底让我干什么?”
2.3 指令的基本构成
一条指令需要包含以下信息:
- 操作码(Opcode):告诉 CPU “干什么”
- 操作数地址:数据在哪——可能在寄存器、主存、I/O 端口、或指令本身中的立即数
- 下一条指令地址:如何找到下一条指令(默认顺序执行,或通过跳转指定)
数据和指令都存放在同一块主存中。CPU 既要从 Memory 取指令,又要读写数据,这时就会产生冲突,需要仲裁机制。
2.4 指令格式:固定长度 vs 可变长度
| 策略 | 特点 | 优点 | 缺点 |
|---|---|---|---|
| 固定长度 | 每条指令长度相同 | 取指简单,解码规整 | 短指令浪费空间 |
| 可变长度 | 指令长度可变 | 节省空间 | 解码复杂,需判断指令边界 |
指令格式要有足够的操作码位数预留。因为随着业务发展,新指令会不断出现。比如现在 AI 时代,英特尔发现某些 AI 算法中有些操作特别频繁,想加几条指令来支撑 AI 算法——操作码位数不够就完了,指令集就”死了”,竞争对手就抢占 AI 芯片市场了。
三、地址码数量与寻址方式
3.1 地址码的演变
根据指令中显式指定的操作数地址个数,分为:
| 类型 | 地址码数 | 操作数位置 | 典型示例 |
|---|---|---|---|
| 零地址 | 0 | 全部隐式约定 | nop(空操作)、push/pop(栈位置固定) |
| 一地址 | 1 | 另一个操作数默认在累加器 ACC | add R1(ACC + R1 → ACC) |
| 二地址 | 2 | 两个寄存器操作数 | add R1, R2(R1 + R2 → R1) |
| 三地址 | 3 | 两个源 + 一个目标 | add R1, R2, R3(R2 + R3 → R1) |
3.2 历史背景:从苦日子到好日子
早期硬件(电子管时代)非常昂贵——多加一条指令都是要命的。所以指令集设计极度精简,能用软件解决的问题尽量不交给硬件。那时候用零地址、一地址指令将就着过。
现在是 RISC 风格——”有钱了,就别再在那上面省抠了”。指令格式规整、两个或三个寄存器清清楚楚地写好,实现也简单。虽然浪费了点资源,但换来简洁规整的设计,完全值得。
不能现在过了好日子就看不上以前的苦日子——那些看似奇怪的设计背后都有充分的历史原因。
四、硬件 vs 软件的工程权衡
4.1 屁股决定脑袋
硬件和软件工程师之间存在天然的矛盾:
- 硬件工程师:”这硬件设计太难了,指令多了成本会很高。”
- 软件工程师:”这么简单的事都干不了?你硬件工程师不该换个人吗?”
4.2 架构师的权衡
有经验的架构师(”老板”)需要在两者之间做权衡:
| 维度 | 硬件 | 软件 |
|---|---|---|
| 复杂度代价 | 芯片面积增大 → 生产成本高 | 多写代码、熬夜加班 |
| 修改成本 | 流片后无法修改 | 随时可以更新迭代 |
| 核心诉求 | 指令简单、格式固定 | 指令丰富、表达能力强 |
软件工程师永远有活——硬件干完就没什么事了,但软件总在迭代。哪部分该软件辛苦一点熬夜,哪部分该硬件承担——架构师需要根据经验做权衡。因为硬件要生产,面积大就贵;软件熬两个通宵就做好了。
五、Load/Store 架构与 RISC 设计原则
5.1 Load/Store 架构
RISC 的核心约束:只有 load 和 store 两条指令可以访问存储器,其余所有运算指令只在寄存器之间操作。
硬件设计者希望少干点活——”什么时候访问存储?就 load 和 store 两个,其他的咱们都别弄。”指令本身可能会带点数据(立即数或 offset),这是必要的折中。
5.2 RISC 四项核心原则
- 指令尽量短小规整:固定长度,简化取指和解码
- 足够的操作码位数:为未来扩展预留空间
- Load/Store 架构:分离访存和运算
- 简单就是美:指令简单,电路就简单
六、x86 与 RISC-V 的历史对比
6.1 x86:承载辉煌与包袱
x86 是从 8086 一步一步发展起来的。上面承载着辉煌的历史,也承载着沉重的历史包袱。当接手一个老指令集时——”全都有”——想扔掉旧的?现在都 3 纳米了,老的指令还要不要?
就像接手一个老旧的代码库,说把它重构一下扔掉算了——千万小心,一扔也许把公司就扔掉了。因为老客户还在用,兼容性是命根子。
6.2 RISC-V:白手起家
RISC-V 经历了多个版本迭代(I, II, III, IV, V),到第 5 版才获得业界公认。因为是白手起家,没有历史包袱,可以设计得干净利落。
RISC-V 的风格:二地址或三地址指令格式,寄存器-寄存器操作,load/store 单独访存。
6.3 永远保留扩展空间
指令集设计必须为未来留有余地。当 AI 应用出现后,如果某些计算模式特别频繁,能否在指令集中加入专用指令来加速?如果操作码位数用尽,就只能眼睁睁看着竞争对手抢占市场。
七、取指译码流程简述
RISC-V 汇编很简单——一条汇编对应一条机器指令,”手抄手册也能抄出来”。x86 就复杂得多——IO 操作和寄存器操作是不同的指令,取指译码逻辑更复杂。这部分内容会在后续课程中结合流水线和数据通路详细展开。