CPU 与指令系统
这一支有两种固定形态:
- 同一台机器连做两题:一题给指令格式,一题给它的数据通路。2015#43 与 #44、2022#43、2024#43 与 #44、2026#43 与 #44 都是这种。
- C 源码加机器级代码清单:先问指令编码和寻址方式,第二题常跨到 Cache 或分页。2014、2017、2019、2023、2024 是这种。
知识框架概要
从一句 C 语句到一串控制信号,中间只有几层固定的翻译。把这几层背成骨架,任何一问都能定位到其中一环。
一、C 语句 → 指令序列只有这几种译法
读代码清单之前先认语法:Intel 格式是 目的, 源,dword ptr 一类前缀交代操作数长度,见 操作数长度后缀、内存寻址格式。
| C 结构 | 编译出来的指令模式 | 见 |
|---|---|---|
| 赋值、读写局部变量 | mov,一端是寄存器、另一端是 [ebp − d] | 数据传送 |
一维数组 a[i] | 三条:下标左移 log₂(元素大小) → 加基址 → load/lw 取数 | 变址寻址 |
二维数组 a[i][j](x86) | 一条 mov [基址寄存器 + 变址×比例 + 首地址], 源 | 内存寻址格式 |
x * 2ᵏ、x / 2ᵏ | 左移 / 算术右移 k 位 | 移位 |
if … else … | cmp + 反条件跳转到 else,then 段末尾 jmp 跳过 else | 选择结构语句 |
while / do-while | 循环体末尾一条条件跳转构成回边 | while 语句、do-while 语句 |
for | 初值 mov +(可能先 jmp 到条件判断)+ 体尾自增 + 条件跳转回边 | for 语句 |
| 条件本身 | 一律 cmp 做减法置标志,再由跳转指令读标志 | 比较、条件跳转 |
| 函数定义与返回 | push ebp 是函数第一条、ret 是最后一条,返回值放 eax | 函数定义和调用 |
| 函数调用与传参 | 实参 push 入栈 → call(相对寻址),被调用方从 [ebp + 8] 起取参数 | 子程序调用、栈操作 |
配套的三条口径每年都要用:
- 变量住哪:局部变量在
[ebp − d]、参数在[ebp + 8]起,栈帧布局见 函数调用时内存结构;也可能全部分配在寄存器里,此时整段代码没有栈帧(2014#44、2024#44)。 - 移位位数和比例因子等于元素大小:
int移 2 位、short移 1 位,2024#44 第 3 问就是把 int 换成 short 改这一位。 - 机器码里的多字节字段按端序排,见 大小端;结构体成员和数组元素的地址还受 数据对齐 约束。
指令长度是否等长顺带就是 CISC / RISC 的判据(2017#44),见 不同位数指令。
二、指令格式
指令 = 操作码 ‖ 寻址方式 ‖ 地址码—— 操作码位数定指令条数上限,见 指令格式- 寻址方式决定「地址码里的数是操作数、是地址、还是相对偏移」,见 寻址方式
- 翻译结果永远是一行
读谁 → 算什么 → 写回谁,见 寄存器传送语言
三、执行这一行靠固定的几个部件
- 取指部件:
PC(下一条地址)、IR(当前指令) - 访存接口:
MAR(地址,宽度 = 地址线)、MDR(数据,宽度 = 数据线) - 运算部件:
ALU及其暂存器 —— 单总线结构下一个输入必经暂存器、输出必经结果寄存器 - 数据仓库:通用寄存器组
GPRs - 指挥者:
CU按Op(IR)逐拍发控制信号
结构见 数据通路。
四、指令周期是一条固定的时间轴
取指 → 译码 → 取操作数 → 执行 → 写回 →(有请求则)响应中断
每一拍上只有两类信号:让谁上总线(Xout)、让谁被写入(Xin),加上 MemR / MemW / ALUop / 多路选择器的选择端,见 指令的控制信号。五个阶段里,取指、译码、写回的写法是死的,只有取操作数和执行随指令变:
| 阶段 | RTL 模板 | 有效控制信号 |
|---|---|---|
| 取指 | MAR ← (PC)MDR ← M(MAR),PC ← (PC)+1IR ← (MDR) | PCout, MARinMemR, MDRinE, PC+1MDRout, IRin |
| 译码 | 指令译码 | 无,由译码器完成 |
| 取操作数 | 只由寻址方式决定,见下表 | |
| 执行 | A ← (Rx)(单总线才需要单独占一拍)AC ← (A) op (Ry) | Rxout, AinRyout, ALUop, ACin |
| 写回寄存器 | Rz ← (AC) | ACout, Rzin |
| 写回主存 | MDR ← (AC)M(MAR) ← (MDR) | ACout, MDRinMDRoutE, MemW |
2009#44 的题干直接给出了上表前三行,要求照这个格式续写执行阶段——格式跟着题干抄,不要自创。
取操作数这一段只有四种写法:
| 寻址方式 | RTL | 访存次数 |
|---|---|---|
| 立即数 | 操作数就在 IR 里,不必取 | 0 |
| 寄存器直接 | A ← (Ri) | 0 |
寄存器间接 (Ri) | MAR ← (Ri);MDR ← M(MAR) | 1 |
| 基址 / 变址(带位移) | 先用 ALU 算有效地址 AC ← (Ri) + 位移,再 MAR ← (AC);MDR ← M(MAR) | 1 |
| 存储器间接 | 上面两拍做两遍:先取出地址,再取出操作数 | 2 |
三点写法上的约定,比记信号名更要紧:
- 暂存器和结果寄存器的名字随题目图变:2009#44 叫
A和AC,2015#43 叫T,教材里常写成Y和Z。照图上的名字写,见 暂存器。 - MDR 有两组进出信号:连内总线的是
MDRin/MDRout,连数据总线(DB)那一侧的是MDRinE/MDRoutE。读主存写MDRinE、写主存写MDRoutE,方向弄反是这类题的常见失分点。 - 多选器型数据通路不写 Xin / Xout:改成对每个 MUX 回答「这一拍它选哪一路」再写 0/1/2,2026#44 就是逐个信号问这个。
五、把这条时间轴切成五段并重叠,就是流水线
IF → ID → EX → MEM → WB,重叠后只多出三类冒险:结构、数据、控制,见 流水线冒险。
解题注意点
两种形态各走一路:给了指令格式和数据通路的,按前四条的次序走;给了 C 源码和机器级代码清单的,直接跳到「代码清单题的固定五问」。
先把字段翻成一句寄存器传送
不要先动二进制。先把每个字段翻成自然语言,再压成一行:
读谁 → 算什么 → 写回谁
语法见 寄存器传送语言。这一步先把操作数方向和数据去向定下来,位串是最后一步的事。
数一遍位宽
| 要求的量 | 由什么决定 |
|---|---|
| 指令条数上限 | 操作码位数 |
| 通用寄存器个数 | 寄存器编号字段的位数 |
| MAR 位数 | 地址线宽度,不是字长 |
| MDR 位数 | 数据线宽度 |
| 主存单元宽度 | 编址单位,按字节编址就是 8 位 |
| 多路选择器的控制信号位数 | ⌈log₂(输入路数)⌉ |
2021#43 和 2026#43 就在 MAR / MDR 和「按字节编址推不出寄存器宽度」上设坑。相关指标见 指令格式 与 计算机性能指标。
读结构图
- 先给 PC、IR、MAR、MDR、ALU、GPRs、暂存器逐个标出输入来自哪、输出去哪;
- 判断是单总线,还是多路选择器加专用通路;
- 单总线:一拍只允许一个部件把数据放上总线,所以 ALU 的一个输入必须先进暂存器,输出也要先落到结果寄存器(两者的名字随题目图变)。2015#43 和 2022#43 直接问「为什么要设置暂存器」;
- 多选器型:每个 MUX 对应一个控制信号,先回答「这一拍它该选哪一路」,再写 0/1。2024#43 和 2026#44 是这类。
结构见 数据通路。
节拍与控制信号
阶段表和信号写法在知识框架第四条里,落笔时按三条走:
- 一拍写一行,先写功能再写信号。功能用
←写成一句寄存器传送,信号从这句话里读出来:数据流入某部件就写 Xin,流出就写 Xout,不要凭记忆背信号名。 - 不冲突的动作合并到同一拍。单总线上一拍只允许一个部件把数据放上总线,除此之外互不干扰的两个动作可以写进一行、用逗号隔开——2009#44 的另一种答案就靠
MDR ← M(MAR), A ← (R0)少用一拍。 - 周期数不能套经验值。「取指 3 拍」这种数字不能直接用,一次访存本身可能占多个周期:2022#43 给的条件是「从发出主存读命令到数据送到 MDR 共需 5 个时钟周期」,取指阶段因此至少 7 个周期。
代码清单题的固定五问
给了 C 源码和机器级代码清单的那五年(2014、2017、2019、2023、2024),小问基本只在这五类里循环。四条式子先写在草稿上:
下条指令地址 = 本条指令地址 + 本条机器码字节数
代码段长度 = 末条指令地址 − 首条指令地址 + 1
转移目标地址 = 下条指令地址 + 偏移量
偏移量 = 目标地址 − 下条指令地址
- 求下条地址或函数长度:数机器码字节数。2017#44 从
push ebp数到ret得 60H = 96B。 - 判寻址方式:跳转和
call一律 相对寻址;0(R4)是寄存器间接(基址寻址);[ebp − 8]是用基址寻址访问栈帧;[ecx + edx×4 + 位移]是基址 + 比例变址 + 位移;指令里写死的常数是立即数寻址。 - 求目标地址或偏移量:基准永远是下一条指令。2023#44 的
jmp是 00401079H + 2 + 09H = 00401084H;2019#45 的call偏移是 00401000H − 0040102AH = FFFFFFD6H。偏移单位不是字节时还要乘一条指令的长度,2014#44 的公式因此是 (PC) + 4 + OFFSET × 4。 - 判端序:把偏移量或位移量字段的字节序和它该表示的数值对照,低字节先出现就是小端——
D6 FF FF FF↔ FFFFFFD6H(2019#45)、00 20 42 00↔ 00422000H(2023#44)。反过来,题目给定小端时用它把内存字节拼成机器数(2024#44)。 - 反推寄存器与变量的对应:谁被「乘元素大小」就是下标变量,谁被当基址相加就是数组首地址,谁在自己身上累加就是 sum。2024#44 第 1 问就是这一步;2023#44 由「一行 64 个 int」反推出 ecx = i × 256。
问到「这段代码取指会不会缺页」「a[i] 在哪一页」就接到 存储系统 那条地址翻译链上,去掉低 log₂(页大小) 位取页号即可。
时间指标与流水线
| 问的是 | 公式 | 陷阱 |
|---|---|---|
| CPU 执行时间 | 指令数 × CPI × 时钟周期 | 时钟周期和主频不要弄反 |
| MIPS | 主频 ÷ (CPI × 10⁶) | |
| 流水线总周期数 | 阶段数 + (指令数 − 1) + 停顿拍数 | 数据冒险的停顿和分支的固定阻塞要单独加 |
按序发射按序完成意味着后一条指令的取指会被前一条的译码连带堵住。2012#44 和 2014#44 都要求指出阻塞位置和原因,见 流水线冒险。
历年题索引
上面这些要点用熟就已经能应付大部分小问,下面这张表只用来查「某一年额外拐了什么弯」。
| 真题 | 主线 | 这一年特别问了什么 |
|---|---|---|
| 2009#44 | 单总线数据通路 | 仿照给出的节拍表列出执行阶段的控制信号 |
| 2010#43 | 指令格式 + 机器码 | 转移目标范围、执行后哪些寄存器和存储单元变化 |
| 2012#44 | 移位指令 + 五段流水线 | 指出两处阻塞原因并画流水线图 |
| 2013#44 | 条件转移 + 标志寄存器 | 无符号数「≤」转移要用哪几个标志位 |
| 2014#44 | 分支指令 + 相对寻址 | 求 OFFSET 并推出目标地址公式 |
| 2015#43 | 单总线 CPU 结构图 | 暂存器 T 的作用、补全数据通路连线 |
| 2015#44 | 机器码 + 控制信号 | 填执行过程中 ①~⑧ 的控制信号 |
| 2017#44 | 机器级代码 + CISC/RISC | 算 cmp 执行后的 CF、能否用移位实现乘 2 |
| 2019#45 | 函数调用 + call 相对寻址 | 算 call 的偏移量并判断端序 |
| 2020#43 | 无乘法指令的机器 | 三种实现方式下执行时间的长短比较 |
| 2021#43 | R/I/J 型指令格式 | 立即数的扩展方式、无条件转移的格式 |
| 2022#43 | 数据通路 + 标志位 | 写 SF 与 OF 的逻辑表达式、取指最少几个周期 |
| 2023#44 | 机器级代码 + 寻址方式 | 两条跳转指令的目标地址计算、大小端 |
| 2024#43 | 控制信号 + ALU 标志 | slli 与 lw 的 Ext 和 ALUctr 各取什么值 |
| 2025#44 | 补码除法器数据通路 | 除法异常的两种情形与异常响应中 CPU 的动作 |
| 2026#43 | 指令格式 + 表达式 | 补全实现 y = 16x − 5 的四条指令编码 |
| 2026#44 | 数据通路 + 扩展器 | EXTOP 至少几位、左移指令周期的控制信号 |
易错清单
- 转移目标地址漏掉 PC 自增和偏移缩放。形如 (PC) + 2 + 2 × OFFSET,两项都不能省,见 相对寻址。
- 偏移量没有先符号扩展。一律先扩展到地址宽度再参与运算。
- 字节序方向写反。
D6 FF FF FF表示的是 FFFFFFD6H,写成 D6FFFFFFH 会连带错掉「端序」和「机器数」两问(2019#45、2024#44)。 - 把加法器的进位直接当成 CF。减法和
cmp的借位标志是进位输出取反,2017#44 由 C = 0 得 CF = 1。 - 以为移位能通吃「乘 2」。整型可以,浮点不行——float 的机器数含阶码和隐含的最高有效位(2017#44)。
- 单总线一拍写了两个源上总线。这是数据通路题最常见的整段失分。
- 溢出被当成自动转异常。要靠运算指令后面的陷阱指令才会转到异常处理程序。
- 开 / 关中断当成普通指令。它们是特权指令,用户态不可用,2021#45 专门问这一点。
异常响应由硬件完成的动作是固定的四句:关中断、保存断点(PC 与 PSW)、切到内核态、按向量转异常处理程序。见 异常。