扩展操作码指令格式
4.1.3 结尾撞的那堵墙是:操作码位数被指令总数绑架——只有 3 条指令需要两个地址,操作码却要按全部指令数来定位数,那几位在每条指令里都得占着。
扩展操作码的想法很直接:地址字段用得多的指令,操作码就短;地址字段用得少的指令,操作码就可以长。 反正指令字长固定,空出来的地址位正好拿去装操作码。
但一旦操作码长度不再固定,一个新问题立刻出现:译码器怎么知道这条指令的操作码到哪一位结束? 本节的全部技术含量都在这个问题上,而且这个问题的答案会一直用到第 5 章的流水线。
机制
唯一的约束:短操作码不能是长操作码的前缀
这就是全部规则。 因为译码器是从最高位开始一位一位看下去的,如果 1111 既是一条三地址指令的完整操作码、又是某条二地址指令操作码的开头四位,译码器看到 1111 时就无法判断该停还是该继续。
满足”任何一个码字都不是另一个码字前缀”的编码叫前缀码,它的性质是唯一可译:一串比特只有一种切分方式,不需要任何分隔符。
扩展操作码 = 操作码用前缀码编码。 这一句把这节和 4.1.3 的差别讲完了。
经典例题:16 位指令字、4 位地址字段
设指令字长 16 位,每个地址码固定 4 位。留窗口的做法是:每一层都拿出最后一个编码(全 1)不作为本层指令,专门留给下一层扩展。
| 层 | 操作码位数 | 编码形式 | 本层指令条数 |
|---|---|---|---|
| 三地址 | 4 | 0000 ~ 1110 + 3×4 位地址 | 15 |
| 二地址 | 8 | 1111 0000 ~ 1111 1110 + 2×4 位地址 | 15 |
| 一地址 | 12 | 1111 1111 0000 ~ 1111 1111 1110 + 1×4 位地址 | 15 |
| 零地址 | 16 | 1111 1111 1111 0000 ~ 1111 1111 1111 1111 | 16 |
graph TD R["4 位编码空间<br/>共 16 个"] --> A["0000~1110<br/>15 条三地址指令"] R --> B["1111<br/>★窗口"] B --> C["1111 0000~1111 1110<br/>15 条二地址指令"] B --> D["1111 1111<br/>★窗口"] D --> E["…0000~…1110<br/>15 条一地址指令"] D --> F["1111 1111 1111<br/>★窗口"] F --> G["…0000~…1111<br/>16 条零地址指令"] classDef win fill:#ffe6cc,stroke:#d79b00,stroke-width:2px classDef ins fill:#d5e8d4,stroke:#82b366 class B,D,F win class A,C,E,G ins
最后一层是 16 条不是 15 条,因为零地址之下没有再要扩展的层了,窗口不用留。这一个数是本题最常见的失分点。
通用计算模板
不要背 15/15/15/16,那只是”每层留一个窗口”这一种特殊分配。通用规则只有一句:
下一层的指令条数上限 = 上一层剩余的编码数 ×
,其中 是本层释放出来的地址字段位数。
写成公式,设地址字段
每一步都是同一个动作:(上层总数 − 上层已用) × 2^a。 会了这一个动作,题目怎么给条件都能往下推,包括反过来问”三地址指令最多几条”。
用上例验算:
边界辨析:一个能当场验算的自检不等式
把每条指令的操作码长度记作
,则一组前缀码存在的充要条件是
取等号时编码空间恰好用尽,一位不剩。这条叫 Kraft 不等式,超出统考大纲,但作为验算工具极好用——它把”够不够、有没有冲突”变成一次加法。
验算上例:
正好等于 1——教材那套分配把 16 位编码空间用得一位不剩。若你算出来
,方案一定冲突;若 ,还有余量可以再塞指令。
设计检查四条
拿到一个扩展操作码方案,按顺序过这四条:
- 前缀条件:每一层用掉的编码里,是否给下一层留了至少一个未用编码作窗口?
- 无重复:不同层的操作码,把短的按窗口展开后是否与长的重叠?
- 位数配平:每条指令的
OP 位数 + 地址位数是否都等于指令字长? - 总量:Kraft 和是否
?
变长指令的长度,藏在哪里
疑问点:变长指令集编码直接乘概率算期望长度,不需要再区分指令长度吗?那怎么知道这条指令到哪里结束
不需要额外的位,因为长度信息已经在操作码里了——而且这不是巧合,正是前缀码性质的直接后果。
先把那道题算完(32 个寄存器 → 寄存器号 5 位;立即数 16 位;142 条指令 → 操作码
位;长度向上取整到 8 的倍数):
类型 占比 字段 原始位数 对齐后 1 输入 + 1 输出 20% 18 24 2 输入 + 1 输出 30% 23 24 1 输入 + 1 输出 + 立即数 25% 34 40 立即数输入 + 1 输出 25% 29 32
- 定长编码:所有指令都得按最长的来 → 40 位
- 变长编码:
位 - 省下
的存储空间 现在回答”怎么知道到哪结束”。取指不是一次取完再译码,而是边取边译:
取回第 1 个字节 ──► 译码器看操作码 ──► "这是类型 3" ──► 长度 = 40 位 └► 于是再取 4 个字节,指令结束操作码是前缀码,所以看到操作码就已经唯一确定了指令类型,而指令类型唯一确定了格式,格式唯一确定了长度。 长度是被推出来的,不是被存储的——所以它一位空间都不占,那个”直接乘概率”的算法完全正确。
换个角度看更清楚:如果真的额外存一个”长度字段”,那才是浪费,因为那个字段的信息量已经被操作码携带了,等于把同一件事编码两遍。
所以”好粗糙啊”这个直觉里,该保留的部分是”这里一定有个机制没讲”,该修正的部分是”那个机制需要额外开销”。它不需要,代价在别处——见下面的边界。
疑问点:直接能隐含指令格式,需要译码
对,而且这句话点中的正是变长指令真正的代价所在。
长度不花存储空间,但花时序:
定长指令 变长指令 第 2 条指令的起始位置 取指时就知道(PC + 常数) 必须先译完第 1 条才知道 取指与译码 可完全并行、可预取任意多条 串行依赖 一拍取多条(超标量) 直接切分 要先做”长度预译码”扫描 代价从空间转移到了时间,而且是转移到了流水线最不能容忍的地方——取指到译码这一段的串行依赖。 这条线索会在三个地方各兑现一次:
- 4.4.1:x86 指令 1~15 字节,长度确定要经过前缀字节、ModRM、SIB 多级判断,这是 CISC 译码复杂的根源
- 4.4.2:RISC 全部定长,取指部件不必等译码
- 5.5 指令流水线(本地资料):为什么”取指”能成为一个独立的、延迟恒定的流水段
本章第一条主线由此立起来:一条指令必须自己携带自己的长度信息;用前缀码携带不花空间,但让取指和译码变成串行的。
边界
扩展操作码不改变指令字长。 教材例题里 16 位始终是 16 位,变的只是这 16 位内部 OP 和地址的分界线。“变长操作码”和”变长指令字”是两件事——真实的 x86 两者兼有,但统考题几乎只考”指令字定长、操作码可扩展”这一种组合。这条已在 4.1.2辨析过,这里是它第二次成为陷阱。
教材例子里”三地址指令用短操作码”的理由不是使用频率。 是因为三地址指令的地址字段吃掉 12 位,只剩 4 位给操作码——位数是被挤出来的,不是被优选的。真实 ISA 里确实也会把短码分给高频指令(这是霍夫曼编码的思想),但这是第二条独立的理由。做题时若问”为什么三地址指令操作码最短”,答地址字段占位最多,不要答”因为常用”。
“最多能有几条”和”若还要留扩展则最多几条”是两个问题。 前者用满,后者留窗口,答案差 1。教材那套 15/15/15/16 是后者的答案。
留出的窗口不必是全 1,也不必只留一个。 全 1 只是惯例。题目若说”以 1101 作为扩展标志”,一切照推,只是编码换了。若说”留 2 个编码作扩展”,则本层指令数减 2、下层容量翻倍。
零地址指令那一层不留窗口,所以是 16 条。 除非题目明说”还要预留将来扩展”。
操作码全 0 通常不作为有效指令。 现实中 0000...0 常被留作非法指令陷阱(读到全 0 的存储区能立刻报错)。统考不考这条,题目给的编码范围以题为准。
对照速查
| 概念 | 一句话 |
|---|---|
| 扩展操作码 | 操作码长度可变,地址字段少的指令操作码长 |
| 前缀码 | 任何码字都不是另一码字的前缀 → 唯一可译 |
| 扩展窗口 | 本层留出的未用编码,供下层继续扩展 |
| Kraft 和 |
| 递推动作 | 公式 |
|---|---|
| 下层最多条数 | (上层总数 − 上层已用) |
| 上层最多条数(反推) |
| 判断 | 对错 |
|---|---|
| 扩展操作码要求短码不是长码的前缀 | ✅ |
| 扩展操作码会改变指令字长 | ❌ |
| 三地址指令操作码最短,因为它最常用 | ❌(因为地址字段占位最多) |
| 16 位指令字 4 位地址,三地址 15 条时二地址最多 15 条 | ❌(最多 16,留窗口才 15) |
| 零地址那一层也要留窗口 | ❌(下面没有层了) |
| 变长指令需要一个额外字段标明长度 | ❌(长度由操作码隐含) |
| 变长指令的代价是存储空间 | ❌(反了,省空间,代价是译码串行) |
考点
- 扩展操作码 = 操作码用前缀码编码;唯一约束是短码不能是长码前缀
- 经典分配 16 位指令字 / 4 位地址:15 / 15 / 15 / 16,最后一层不留窗口
- 通用递推:(上层总数 − 上层已用)×
,正推反推都用它 - Kraft 不等式
可当场验算方案是否冲突或有余量 - 变长指令的长度由操作码隐含推出,不占额外存储位
- 变长指令真正的代价是取指与译码的串行依赖,直接指向 CISC 译码复杂与流水线障碍
- 教材里三地址指令码短,原因是地址字段挤占,不是使用频率
链接
- 🏠 返回总览:计算机组成原理第 4 章:指令系统总览
- ⬅️ 上一节:4.1.3 定长操作码指令格式
- ➡️ 下一节:4.1.5 指令的操作类型
- 🔗 4.4.1 复杂指令系统计算机(CISC)(变长译码的代价在这里结算)
- 🔗 5.5 指令流水线(本地资料)(取指段为什么怕变长)
- 📖 名词库:第 4 章名词库
- 📝 原始提问:#74 / #75(本地资料)