Appearance
指令格式设计与扩展操作码
2026 大纲 四(二)指令格式(定长操作码与扩展操作码)。
定长指令字里,多一条指令就得少一个寄存器
指令字长一旦定死(比如 16 位),这 16 位就成了一块必须切开分掉的固定预算。操作码想多占一位,去区分更多种操作,剩给地址码的就少一位——寻址范围直接减半;想多支持一倍的通用寄存器,寄存器号字段就得加一位,同样要从别人身上割。指令格式设计的全部内容,就是在这块预算里做减法。
这件事本身不难,难在两处会翻车的地方。
第一处是减法要做几次。 拿指令字长减去操作码位数,得到的常常不是形式地址的位数——地址码里往往还切着寻址方式位、寄存器号字段。16 位指令、6 位操作码、2 位寻址方式位,形式地址只剩
第二处是操作码长度可以不固定。 三地址指令用掉三个地址字段,操作码只能短;零地址指令一个地址字段都不用,空出来的位全能拿给操作码。于是就有了扩展操作码:让操作码的长度随地址码个数的减少而增加,把定长指令字的空间榨得更干净。代价是操作码不再自带长度信息,译码器只能从高位边读边判——因此必须有一条铁律:任何一个操作码都不能是另一个更长操作码的前缀,否则读到一半会出现"两种读法都说得通"的局面。
这条铁律不是凭空立的,它就是教材六条设计原则里的第三条——操作码的编码必须有唯一解释。本篇后半部分的所有计算,归根到底都是在这条约束下数"还剩多少编码可以分"。
一、指令格式的六条设计原则
教材列出的六条不是并列的口号,每一条都在回避一个具体的麻烦:
| 原则 | 它在回避什么 |
|---|---|
| 指令应尽量短 | 指令长 |
| 要有足够的操作码位数 | 向后兼容会让操作类型只增不减,位数留窄了后续无处可加 |
| 操作码的编码必须有唯一解释 | 译码器要么认出一个合法编码,要么判定为非法指令并触发异常 |
| 指令长度应是字节的整数倍 | 内存按字节编址,取指与地址计算才对得齐 |
| 合理选择地址字段个数 | 地址字段多则指令长(空间开销),少则要靠隐含约定绕(时间开销) |
| 指令应尽量规整 | 长度是否固定、操作码位数是否固定、各字段划分位置是否一致,直接决定硬件实现的复杂度 |
第三条是本节后半部分的全部依据,先记住它的措辞:唯一解释。
二、字段位宽 ↔ 可编码对象数
指令格式题的通用换算只有一条:
| 字段 | |
|---|---|
| 操作码 | |
| 寄存器编号 | |
| 形式地址(当作绝对地址) | 寻址范围 |
| 移位量 shamt | 移位 |
反过来,已知需要区分多少个对象,求字段位宽要向上取整:
29 种操作要 5 位(
真实的指令格式里,"地址码"往往不是一整块,而是分成寻址方式位和形式地址:
- M(寻址方式位):告诉 CPU 该怎么解释 D——当立即数、当直接地址,还是当寄存器编号
- D(形式地址):指令里实际写着的那个数
所以由指令字长推形式地址位数时,字段有几个,减法就要做几次:16 位指令、6 位操作码、2 位寻址方式位,形式地址只有 8 位而不是 10 位。位宽定下来之后还要再问一句它按什么口径读——绝对地址(直接、间接寻址的形式地址)按无符号,范围
三、定长操作码 vs 变长操作码
选定长还是变长,是时间开销与空间开销之间的权衡:
| 定长操作码 | 变长操作码 | |
|---|---|---|
| 译码 | 固定取那几位直接查表,一步到位 | 要先判断本条操作码有多长,再解释剩余位 |
| 空间 | 位数按最坏情况留足,必然冗余 | 常用指令用短操作码,代码更紧凑 |
| 适用 | 追求性能、指令规整的机器 | 字长短、要求代码紧凑的机器 |
定长的冗余是可以量化的:IBM 360/370 采用 8 位定长操作码,理论上能表示 256 条指令,而指令系统只提供了 183 条,剩下 73 种编码是冗余的。代价换来的是速度:定长操作码的位置固定,译码可以与取操作数并行进行,不必等"这条操作码到底有多长"这个问题先有答案。
变长操作码则相反。它把操作码分散在指令字的不同字段中,能有效压缩操作码的平均长度,在字长较短的机器里被广泛采用;但操作码长度不固定会增加译码和分析的难度,使控制器设计复杂。
扩展操作码是变长操作码最规整的一种组织方式:让操作码的长度随地址码个数的减少而增加——地址码少的指令把省下来的位借给操作码用。于是在同一个指令字长下,能容纳的指令种类大幅增加。
编者注(辨析):扩展操作码不等于变长指令。扩展操作码变的是操作码占几位,指令字长通常是固定的——20 位定长指令里操作码可以在 5、10、15、20 位之间变,整条指令始终 20 位。变长指令变的是整条指令有多长,x86 的指令从 1 字节到 15 字节都有。两者可以同时存在,也可以只有其一。
四、前缀不重复:这条约束是怎么来的
定长操作码时,译码器知道"前
假设某台机器同时存在这两条指令:
指令甲:操作码 = 1100 (4 位,后 12 位全是地址码)
指令乙:操作码 = 1100 1010 (8 位,后 8 位是地址码)译码器读进前 4 位得到 1100,此刻无法决定该停还是该继续:就此停下按甲译码,后面的 1010 会被当成地址码;继续读 4 位按乙译码,1010 是操作码的后半截。同一个 0/1 序列有了两种都说得通的读法——这正好违反第一节那条"唯一解释"。
由此推出"保留编码"的做法
前缀约束反过来就是操作码的分配规则:某一级要留出若干短编码不分配给本级指令,这些留下的编码才是下一级的前缀来源。
以 4 位操作码为例。若把 0000~1111 全部分给三地址指令,那就一个前缀都不剩,二地址指令无法扩展——任何 8 位操作码的前 4 位必然落在已用的 16 个编码里,一定构成前缀冲突。若只用掉 0000~1110 共 15 个,留下 1111 一个前缀,则所有以 1111 开头的 8 位编码都还空着。
树的形状把三件事一次说清:
- 分配出去的编码是叶子——它们下面不能再长东西,这正是"不能当别人的前缀"。
- 留下的编码是内部节点——它继续往下分叉,分叉数就是
(本例每级多 4 位,故每个前缀分出 个新编码)。 - 每一级的容量
上一级剩下的内部节点数 分叉数——这就是下一节那条递推式的图形版本。
树上还能直接读出前缀冲突长什么样:若把 1111 也分配给某条三地址指令(变成叶子),它下面那整棵子树就全部作废,二地址指令一条也放不下。
编者注(边界):"每级只保留一个前缀"是一种常见安排,不是规则。保留几个前缀完全取决于本级实际用掉多少条指令——剩下几个就是几个。把"保留 1 个"当成公式,会在本级未用满时算错整整一大截。
前缀约束有两条边界,越界套用就会把本来合法的设计判成非法。其一,它不禁止长度不同的操作码共存:1110(4 位)与 1111 0000(8 位)完全可以并列,因为前者不是后者的开头;真正冲突的是 1111 与 1111 0000 这一对。其二,它只在同一字段位置上生效:若一条指令的操作码在
五、可用编码数怎么算
把上面的推理写成可计算的形式。逐级向下时要分清三个量:
| 量 | 含义 |
|---|---|
| 容量上限 | 第 |
| 已用 | 该级实际分配出去的指令条数 |
| 剩余前缀 |
其中
同样的递推可以倒着用。设短格式操作码
逐级递推完整走一遍:从"每级操作码几位"到"零地址最多几条",以及反向求最小位宽(想核对自己每一步的除数与减数取得对不对时展开)
设定:指令字长 20 位,每个地址码 5 位。需要 20 条三地址指令、300 条二地址指令、2000 条一地址指令,问零地址指令最多能有多少条?
先定各级操作码位数——指令字长固定,地址码少一个,操作码就多 5 位:
| 格式 | 地址码占位 | 操作码位数 | 比上级多 |
|---|---|---|---|
| 三地址 | — | ||
| 二地址 | 5 | ||
| 一地址 | 5 | ||
| 零地址 | 0 | 5 |
再逐级递推:
| 格式 | 操作码位数 | 容量上限 | 已用 | 剩余前缀 |
|---|---|---|---|---|
| 三地址 | 5 | 20 | ||
| 二地址 | 10 | 300 | ||
| 一地址 | 15 | 2000 | ||
| 零地址 | 20 | — | — |
零地址指令最多 22016 条。中间那些容量上限(384、2688)是整条链的关节:必须显式算出来再减去已用条数。
反向用:短格式已用 20 条,长格式操作码多 4 位、需要 100 条,求
取
六、算完操作码还有一步:指令字长圆整
指令存放在按字节编址的内存里,取指和地址计算都以字节为单位,所以指令字长必须是编址单位的整数倍,算出的位数不是 8 的倍数时要向上圆整:5 位操作码
一个直接后果:32 位定长指令按字节编址占 4 个存储单元,所以顺序执行时 PC 每次加 4,而不是加 1。反过来,主存规模也约束地址字段:
按字编址时分母是一个字的字节数,按字节编址时分母是 1 字节——这一步一旦按错口径,地址位数会整体偏移。
考点速记
- 六条设计原则里"操作码的编码必须有唯一解释"是扩展操作码全部约束的源头:变长操作码没有字段标明自身长度,译码器只能从高位边读边判,所以短操作码不能是长操作码的前缀;但这条约束只在同一字段位置上生效。
- 扩展操作码 = 操作码长度随地址码个数减少而增加,通常配合定长指令字使用,与"变长指令"不是一回事;分配出去的编码是叶子、保留的前缀是内部节点,每个内部节点分出
个新编码。 - 逐级递推
、 ,剩余前缀由本级实际用量决定、不能默认为 1,容量上限必须显式算出;反向求位宽与前缀数一律向上取整,最后别忘了指令字长要圆整到编址单位的整数倍。
这一节在真题里被考过的形式:
- 正着切字段,求形式地址的范围(2020-16、2014-17)。这两道是同一条流水线上的两站。2020-16:16 位定长、48 条指令
操作码 位、4 种寻址方式 寻址方式位 2 位,单地址指令的地址码 位,直接寻址按无符号读,范围 。2014-17 多切两刀:32 位定长、8 位操作码(题面已声明含寻址方式位,不必再减)、源操作数寄存器直接 4 位、基址寄存器可用任一通用寄存器故再占 4 位,偏移量 位,且题面说明用补码,范围 。两道题的分水岭都在"减法一共要做几次",以及末尾那一步"这个字段该按无符号还是按补码读"。 - 反着求最小指令字长,最后一步是圆整(2017-16)。三地址 29 条、二地址 107 条、地址字段 6 位、按字节编址。设三地址操作码
位,用掉 29 个后剩 个前缀,扩展到二地址时每个前缀分出 个,需 ,即 (除法向上取整,前缀给不出半个),故 、 ,指令字长 位。到这里还没完——按字节编址要求指令字长是 8 的整数倍,向上圆整得 24 位。漏掉圆整这一步正好落进"23 位"的思维定式,而选项里 26、28、32 也全是圆整方向上的干扰项。 - 逐级递推算零地址指令最多几条(2022-19)。16 位定长、地址码 6 位。二地址操作码
位,用 12 条,剩 个前缀(不是 1 个);一地址操作码 位,容量 ,用 254 条,剩 2 个;零地址操作码 16 位,容量 。整道题的分量全压在"剩余前缀由本级实际用量决定"这一句上——若两级都默认剩 1 个,算出来是 64,恰好就是错项 C。 - 由多种指令格式反推可定义的操作数与寄存器数(2021-43、2026-43)。2021-43 的 R 型用
的 op1 区分,故 R 型最多 种操作;I 型与 J 型都用 这 6 位,而 已被 R 型占去,两者合计最多 种;rs/rt/rd 各 2 位,故通用寄存器最多 4 个。同一字段位置上的编码空间是共享的、要扣掉已占用的值,这是这类题最常丢分的一步。 - 问两个操作码字段的编码能不能相同(2026-43)。这道题直接考本篇那条边界。op1 在
、op2 在 ,位置不同,而 R 型已用 把自己与 I 型区分开,所以 op1 与 op2 可以相同;op2 与 op3 都在 ,是同一字段位置,译码器从同一处读它们,必须互不相同。判据就是那一问:译码器是不是从同一个位置读到它们的。
易错:拿指令字长直接减操作码位数当形式地址。中间常常还夹着寻址方式位、寄存器号字段。
易错:默认"每级保留 1 个前缀"。剩几个由本级实际用了多少条决定,本级没用满时差距是数量级的。
易错:反向求前缀数时把除法向下取整。前缀只能整个地给,一律向上取整。
易错:算出位数就交卷,忘了指令字长要圆整到编址单位的整数倍。
易错:把"不同字段的编码不能相同"当成前缀约束的推论。前缀约束只管同一字段位置。
教材出处
- 袁春风《计算机组成与系统结构(第 3 版)》§4.1.2 指令格式设计原则:六条原则,其中"操作码的编码必须有唯一的解释""指令长度应是字节的整数倍"(印刷页 p92)
- 同上 §4.2.5 操作码编码:定长与变长的时空权衡、IBM 360/370 的 8 位定长操作码与 73 种冗余编码、扩展操作码编码的等长/不等长扩展法(印刷页 p97~98)
- 唐朔飞《计算机组成原理(第 3 版)》§7.1.1 指令格式:操作码长度不固定会增加译码难度、扩展操作码使操作码长度随地址数减少而增加,图 7.2 扩展操作码的安排示意图(印刷页 p301)