Skip to content

指令格式设计与扩展操作码

2026 大纲 四(二)指令格式(定长操作码与扩展操作码)。

定长指令字里,多一条指令就得少一个寄存器

指令字长一旦定死(比如 16 位),这 16 位就成了一块必须切开分掉的固定预算。操作码想多占一位,去区分更多种操作,剩给地址码的就少一位——寻址范围直接减半;想多支持一倍的通用寄存器,寄存器号字段就得加一位,同样要从别人身上割。指令格式设计的全部内容,就是在这块预算里做减法。

这件事本身不难,难在两处会翻车的地方。

第一处是减法要做几次。 拿指令字长减去操作码位数,得到的常常不是形式地址的位数——地址码里往往还切着寻址方式位、寄存器号字段。16 位指令、6 位操作码、2 位寻址方式位,形式地址只剩 1662=8 位。指令字里有几个字段,减法就得做几次,漏掉一个,后面所有由位宽推出来的范围都会成倍地错。

第二处是操作码长度可以不固定。 三地址指令用掉三个地址字段,操作码只能短;零地址指令一个地址字段都不用,空出来的位全能拿给操作码。于是就有了扩展操作码:让操作码的长度随地址码个数的减少而增加,把定长指令字的空间榨得更干净。代价是操作码不再自带长度信息,译码器只能从高位边读边判——因此必须有一条铁律:任何一个操作码都不能是另一个更长操作码的前缀,否则读到一半会出现"两种读法都说得通"的局面。

这条铁律不是凭空立的,它就是教材六条设计原则里的第三条——操作码的编码必须有唯一解释。本篇后半部分的所有计算,归根到底都是在这条约束下数"还剩多少编码可以分"。

一、指令格式的六条设计原则

教材列出的六条不是并列的口号,每一条都在回避一个具体的麻烦:

原则它在回避什么
指令应尽量短指令长 程序占用存储空间大,取指访存次数多
要有足够的操作码位数向后兼容会让操作类型只增不减,位数留窄了后续无处可加
操作码的编码必须有唯一解释译码器要么认出一个合法编码,要么判定为非法指令并触发异常
指令长度应是字节的整数倍内存按字节编址,取指与地址计算才对得齐
合理选择地址字段个数地址字段多则指令长(空间开销),少则要靠隐含约定绕(时间开销)
指令应尽量规整长度是否固定、操作码位数是否固定、各字段划分位置是否一致,直接决定硬件实现的复杂度

第三条是本节后半部分的全部依据,先记住它的措辞:唯一解释

二、字段位宽 ↔ 可编码对象数

指令格式题的通用换算只有一条:k 位字段能区分 2k 个对象。对象是什么,由这个字段的角色决定:

字段k 位能表示
操作码2k 种操作
寄存器编号2k 个通用寄存器
形式地址(当作绝对地址)寻址范围 02k1
移位量 shamt移位 02k1

反过来,已知需要区分多少个对象,求字段位宽要向上取整

位宽=log2(对象个数)

29 种操作要 5 位(24=16<2932),多出来的 3 个编码空着不用是正常的——信息冗余是定长编码的固有代价

真实的指令格式里,"地址码"往往不是一整块,而是分成寻址方式位和形式地址:

[ OP  M  D ]
  • M(寻址方式位):告诉 CPU 该怎么解释 D——当立即数、当直接地址,还是当寄存器编号
  • D(形式地址):指令里实际写着的那个数

所以由指令字长推形式地址位数时,字段有几个,减法就要做几次:16 位指令、6 位操作码、2 位寻址方式位,形式地址只有 8 位而不是 10 位。位宽定下来之后还要再问一句它按什么口径读——绝对地址(直接、间接寻址的形式地址)按无符号,范围 02n1;偏移量(相对、基址、变址)按补码,范围 2n12n11,参与地址运算前须符号扩展。同样是 16 位字段,前者能表示 65536 个地址,后者的范围是 32768+32767负侧比正侧多一个

三、定长操作码 vs 变长操作码

选定长还是变长,是时间开销与空间开销之间的权衡

定长操作码变长操作码
译码固定取那几位直接查表,一步到位要先判断本条操作码有多长,再解释剩余位
空间位数按最坏情况留足,必然冗余常用指令用短操作码,代码更紧凑
适用追求性能、指令规整的机器字长短、要求代码紧凑的机器

定长的冗余是可以量化的:IBM 360/370 采用 8 位定长操作码,理论上能表示 256 条指令,而指令系统只提供了 183 条,剩下 73 种编码是冗余的。代价换来的是速度:定长操作码的位置固定,译码可以与取操作数并行进行,不必等"这条操作码到底有多长"这个问题先有答案。

变长操作码则相反。它把操作码分散在指令字的不同字段中,能有效压缩操作码的平均长度,在字长较短的机器里被广泛采用;但操作码长度不固定会增加译码和分析的难度,使控制器设计复杂

扩展操作码是变长操作码最规整的一种组织方式:让操作码的长度随地址码个数的减少而增加——地址码少的指令把省下来的位借给操作码用。于是在同一个指令字长下,能容纳的指令种类大幅增加。

编者注(辨析)扩展操作码不等于变长指令。扩展操作码变的是操作码占几位,指令字长通常是固定的——20 位定长指令里操作码可以在 5、10、15、20 位之间变,整条指令始终 20 位。变长指令变的是整条指令有多长,x86 的指令从 1 字节到 15 字节都有。两者可以同时存在,也可以只有其一。

四、前缀不重复:这条约束是怎么来的

定长操作码时,译码器知道"前 k 位就是操作码",直接截取即可。变长操作码没有这个已知条件——指令字里并没有一个字段写着"本条操作码有几位"。译码器只能从最高位开始一位一位往下读,边读边判断"读到这里够不够构成一个完整的操作码"。

假设某台机器同时存在这两条指令:

指令甲:操作码 = 1100          (4 位,后 12 位全是地址码)
指令乙:操作码 = 1100 1010     (8 位,后 8 位是地址码)

译码器读进前 4 位得到 1100,此刻无法决定该停还是该继续:就此停下按甲译码,后面的 1010 会被当成地址码;继续读 4 位按乙译码,1010 是操作码的后半截。同一个 0/1 序列有了两种都说得通的读法——这正好违反第一节那条"唯一解释"。

由此推出"保留编码"的做法

前缀约束反过来就是操作码的分配规则:某一级要留出若干短编码不分配给本级指令,这些留下的编码才是下一级的前缀来源。

以 4 位操作码为例。若把 00001111 全部分给三地址指令,那就一个前缀都不剩,二地址指令无法扩展——任何 8 位操作码的前 4 位必然落在已用的 16 个编码里,一定构成前缀冲突。若只用掉 00001110 共 15 个,留下 1111 一个前缀,则所有以 1111 开头的 8 位编码都还空着。

树的形状把三件事一次说清:

  1. 分配出去的编码是叶子——它们下面不能再长东西,这正是"不能当别人的前缀"。
  2. 留下的编码是内部节点——它继续往下分叉,分叉数就是 2Δ(本例每级多 4 位,故每个前缀分出 24=16 个新编码)。
  3. 每一级的容量 = 上一级剩下的内部节点数 × 分叉数——这就是下一节那条递推式的图形版本。

树上还能直接读出前缀冲突长什么样:若把 1111 也分配给某条三地址指令(变成叶子),它下面那整棵子树就全部作废,二地址指令一条也放不下。

编者注(边界):"每级只保留一个前缀"是一种常见安排,不是规则。保留几个前缀完全取决于本级实际用掉多少条指令——剩下几个就是几个。把"保留 1 个"当成公式,会在本级未用满时算错整整一大截。

前缀约束有两条边界,越界套用就会把本来合法的设计判成非法。其一,它不禁止长度不同的操作码共存1110(4 位)与 1111 0000(8 位)完全可以并列,因为前者不是后者的开头;真正冲突的是 11111111 0000 这一对。其二,它只在同一字段位置上生效:若一条指令的操作码在 [15:12]、另一条指令用 [15:12] 之外的另一个字段(如低位的功能码)再作区分,那么两个字段各自的取值可以相同——另一个字段已经把它们分开了。判断"两种编码能不能同时存在",先问一句:译码器是从同一个位置、按同一条路径读到它们的吗?

五、可用编码数怎么算

把上面的推理写成可计算的形式。逐级向下时要分清三个量:

含义
容量上限 Cii理论上最多能编多少条
已用 ui该级实际分配出去的指令条数
剩余前缀 pi=Ciui,留给下一级当前缀用
Ci=pi1×2Δi,pi=Ciui

其中 Δi 是本级操作码比上一级多出的位数,起点是 p0=2k1(最短操作码的整个编码空间)。两个乘数各有含义:pi1能用几个前缀2Δi每个前缀后面能接多少种新组合

同样的递推可以倒着用。设短格式操作码 a 位、已用 u 条,长格式操作码比它多 Δ 位、需要 v 条,则必须满足

(2au)×2Δ  v2au  v2Δ
逐级递推完整走一遍:从"每级操作码几位"到"零地址最多几条",以及反向求最小位宽(想核对自己每一步的除数与减数取得对不对时展开)

设定:指令字长 20 位,每个地址码 5 位。需要 20 条三地址指令、300 条二地址指令、2000 条一地址指令,问零地址指令最多能有多少条?

先定各级操作码位数——指令字长固定,地址码少一个,操作码就多 5 位

格式地址码占位操作码位数比上级多 Δ
三地址3×5=152015=5
二地址2×5=102010=105
一地址1×5=5205=155
零地址0205

再逐级递推:

格式操作码位数容量上限 C已用 u剩余前缀 p
三地址525=32203220=12
二地址1012×25=384300384300=84
一地址1584×25=2688200026882000=688
零地址20688×25=22016

零地址指令最多 22016 条。中间那些容量上限(384、2688)是整条链的关节:必须显式算出来再减去已用条数

反向用:短格式已用 20 条,长格式操作码多 4 位、需要 100 条,求 a 的最小值。

10016=72a20+7=27a5

a=5。(a=424=16<27,剩余前缀为负,方案不成立。)除完向上取整的含义是"7 个前缀才装得下 100 条"(6×16=96<100)——凡是"至少需要多少"的量一律向上取整。

六、算完操作码还有一步:指令字长圆整

指令存放在按字节编址的内存里,取指和地址计算都以字节为单位,所以指令字长必须是编址单位的整数倍,算出的位数不是 8 的倍数时要向上圆整:5 位操作码 + 3 个 6 位地址码 =23 位,按字节编址圆整到 24 位(3 字节),末位补足的一位空着不用。

一个直接后果:32 位定长指令按字节编址占 4 个存储单元,所以顺序执行时 PC 每次加 4,而不是加 1。反过来,主存规模也约束地址字段:

单元总数=主存容量编址单位地址位数log2(单元总数)

按字编址时分母是一个字的字节数,按字节编址时分母是 1 字节——这一步一旦按错口径,地址位数会整体偏移。

考点速记

  1. 六条设计原则里"操作码的编码必须有唯一解释"是扩展操作码全部约束的源头:变长操作码没有字段标明自身长度,译码器只能从高位边读边判,所以短操作码不能是长操作码的前缀;但这条约束只在同一字段位置上生效
  2. 扩展操作码 = 操作码长度随地址码个数减少而增加,通常配合定长指令字使用,与"变长指令"不是一回事;分配出去的编码是叶子、保留的前缀是内部节点,每个内部节点分出 2Δ 个新编码。
  3. 逐级递推 Ci=pi1×2Δipi=Ciui剩余前缀由本级实际用量决定、不能默认为 1,容量上限必须显式算出;反向求位宽与前缀数一律向上取整,最后别忘了指令字长要圆整到编址单位的整数倍

这一节在真题里被考过的形式

  • 正着切字段,求形式地址的范围(2020-16、2014-17)。这两道是同一条流水线上的两站。2020-16:16 位定长、48 条指令 操作码 log248=6 位、4 种寻址方式 寻址方式位 2 位,单地址指令的地址码 =1662=8 位,直接寻址按无符号读,范围 0255。2014-17 多切两刀:32 位定长、8 位操作码(题面已声明含寻址方式位,不必再减)、源操作数寄存器直接 4 位、基址寄存器可用任一通用寄存器故再占 4 位,偏移量 =32844=16 位,且题面说明用补码,范围 32768+32767。两道题的分水岭都在"减法一共要做几次",以及末尾那一步"这个字段该按无符号还是按补码读"。
  • 反着求最小指令字长,最后一步是圆整(2017-16)。三地址 29 条、二地址 107 条、地址字段 6 位、按字节编址。设三地址操作码 a 位,用掉 29 个后剩 2a29 个前缀,扩展到二地址时每个前缀分出 26=64 个,需 (2a29)×64107,即 2a29107/64=2除法向上取整,前缀给不出半个),故 2a31a5,指令字长 5+18=23 位。到这里还没完——按字节编址要求指令字长是 8 的整数倍,向上圆整得 24 位。漏掉圆整这一步正好落进"23 位"的思维定式,而选项里 26、28、32 也全是圆整方向上的干扰项。
  • 逐级递推算零地址指令最多几条(2022-19)。16 位定长、地址码 6 位。二地址操作码 1612=4 位,用 12 条,1612=4 个前缀(不是 1 个);一地址操作码 4+6=10 位,容量 4×26=256,用 254 条,剩 2 个;零地址操作码 16 位,容量 2×26=128。整道题的分量全压在"剩余前缀由本级实际用量决定"这一句上——若两级都默认剩 1 个,算出来是 64,恰好就是错项 C。
  • 由多种指令格式反推可定义的操作数与寄存器数(2021-43、2026-43)。2021-43 的 R 型用 [3:0] 的 op1 区分,故 R 型最多 24=16 种操作;I 型与 J 型都用 [15:10] 这 6 位,而 000000 已被 R 型占去,两者合计最多 261=63 种;rs/rt/rd 各 2 位,故通用寄存器最多 4 个。同一字段位置上的编码空间是共享的、要扣掉已占用的值,这是这类题最常丢分的一步。
  • 问两个操作码字段的编码能不能相同(2026-43)。这道题直接考本篇那条边界。op1 在 [3:0]、op2 在 [15:12]位置不同,而 R 型已用 [15:12]=0000 把自己与 I 型区分开,所以 op1 与 op2 可以相同;op2 与 op3 都在 [15:12]是同一字段位置,译码器从同一处读它们,必须互不相同。判据就是那一问:译码器是不是从同一个位置读到它们的。

易错:拿指令字长直接减操作码位数当形式地址。中间常常还夹着寻址方式位、寄存器号字段。

易错:默认"每级保留 1 个前缀"。剩几个由本级实际用了多少条决定,本级没用满时差距是数量级的。

易错:反向求前缀数时把除法向下取整。前缀只能整个地给,一律向上取整。

易错:算出位数就交卷,忘了指令字长要圆整到编址单位的整数倍。

易错:把"不同字段的编码不能相同"当成前缀约束的推论。前缀约束只管同一字段位置。

教材出处
  • 袁春风《计算机组成与系统结构(第 3 版)》§4.1.2 指令格式设计原则:六条原则,其中"操作码的编码必须有唯一的解释""指令长度应是字节的整数倍"(印刷页 p92)
  • 同上 §4.2.5 操作码编码:定长与变长的时空权衡、IBM 360/370 的 8 位定长操作码与 73 种冗余编码、扩展操作码编码的等长/不等长扩展法(印刷页 p97~98)
  • 唐朔飞《计算机组成原理(第 3 版)》§7.1.1 指令格式:操作码长度不固定会增加译码难度、扩展操作码使操作码长度随地址数减少而增加,图 7.2 扩展操作码的安排示意图(印刷页 p301)

相关知识

指令系统的基本概念数据寻址方式有效地址计算

真题练习