指令集设计

指令集体系结构设计原则与RISC/CISC架构对比

Posted by CloudingYu on March 11, 2026

一、流水线实验回顾

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 指令的基本构成

一条指令需要包含以下信息:

  1. 操作码(Opcode):告诉 CPU “干什么”
  2. 操作数地址:数据在哪——可能在寄存器、主存、I/O 端口、或指令本身中的立即数
  3. 下一条指令地址:如何找到下一条指令(默认顺序执行,或通过跳转指定)

数据和指令都存放在同一块主存中。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 的核心约束:只有 loadstore 两条指令可以访问存储器,其余所有运算指令只在寄存器之间操作。

硬件设计者希望少干点活——”什么时候访问存储?就 load 和 store 两个,其他的咱们都别弄。”指令本身可能会带点数据(立即数或 offset),这是必要的折中。

5.2 RISC 四项核心原则

  1. 指令尽量短小规整:固定长度,简化取指和解码
  2. 足够的操作码位数:为未来扩展预留空间
  3. Load/Store 架构:分离访存和运算
  4. 简单就是美:指令简单,电路就简单

六、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 操作和寄存器操作是不同的指令,取指译码逻辑更复杂。这部分内容会在后续课程中结合流水线和数据通路详细展开。