📑 本页目录
计算机系统 · 第六章配套讲义
从一条指令
走到执行结果第六章 · ARM 指令系统
数值在哪里,地址怎么算,谁被写回,标志怎样变,下一条从哪里执行?按这五个问题读懂代码,再把寻址、计算和分支题独立做出来。
覆盖原讲义 6.1—6.4 全部小节及 6.5 的 52 道习题,包含最后七道星号题。页码按独立第六章讲义:正文与习题第 2—40 页,第 1 页为目录。沿用项目既有教材整理版样式。
编码与条件 → 操作数和地址 → 算术与位运算 → 分支和调用 → 系统、DSP 与浮点
从指令怎样改变状态开始 →把掌握深度说清楚
重点是读对和算对,再按需要查完整指令表
本章指令多,不要求一遍默写全部编码。每项原书内容都讲到,并区分独立做题的核心与需要认识的背景。对具体代码,先检查目标内核和语法,再写出寄存器、内存、标志及控制流变化。
| 内容 | 学到什么程度 | 定位 |
|---|---|---|
| 指令集、编码长度、UAL | 会读格式、判断半字长度;分类表会查 | 01—04 |
| 标志、CMP、条件、IT | 会算 NZCV,选对有/无符号条件,读懂条件块 | 05—07、26、28 |
| 寻址、写回、多传输、栈 | 会算 EA/新基址、每个寄存器地址,解释真实编码边界 | 08—14、16 |
| 传送、算术、逻辑、移位、位域 | 能写基础指令并手算结果,识别符号与位宽 | 15、19—26、30 |
| 调用、零检测与跳转表 | 会追 LR/PC、分清表项地址与目标 | 27—29 |
| 非特权、独占、屏障、低功耗与 SVC | 会解释用途与限制,读懂核心代码 | 17—18、31—33 |
| DSP、打包、FPU 与星号题 | 理解 lane、缩放、符号扩展和浮点步骤;详细变体会查 | 20—21、34—37 |
重点依据原章习题和后续编程依赖,不代表已知你的考试范围。原题含笔误或跨 A32/T32 的例子时,正文和答题卡都会说明;会解释题意与“这条代码能在 M3/M4 上汇编”是两个检查。
- 第一遍按顺序学,先用 SVG 默认例子理解。
- 第二遍先遮住结果,自己写 EA、新基址、NZCV、返回地址和数据位串,再换参数核对。
- 最后做十二道短自测,再逐题核对 52 道原题;星号题也做,但按标注的原理深度准备。
正文、公式、脚本和 SVG 全部在本文件内。图中是教学模型,不操作真实处理器或电脑内存。答案默认折叠;打印显示全部答案和默认图例。外部手册链接仅供核对,离线阅读不依赖网络。
第六章 · 第 01 节
这一章学什么:把指令读成状态变化
6.1,第 2—3 页;习题 6.1、6.2、6.4—6.6、6.25
掌握要求:能区分指令、指令系统、指令字、汇编表示;读代码时写出数据、地址、标志和 PC 的变化。
背景为什么要讲:第五章给出处理器有哪些状态,本章说明一条条指令怎样改变它们;第七章再组织完整程序。
机器指令是处理器能解释并执行的编码;指令字是表示这条指令的二进制位串;指令系统是某处理器支持的指令集合。汇编语言用助记符、寄存器名、常量、标号等表示操作,由汇编器生成机器码。宏和伪指令可能展开成不同条数的机器指令,不能把所有源代码行都看成一行对应一条硬件操作。
一条指令需要表达操作及必要的操作数/地址信息,有些信息是隐含的,例如 PUSH 隐含使用当前 SP,顺序执行时下一条地址由 PC 与指令长度确定。不是每条指令都显式拥有 Rd、Rn、立即数等全部字段。
本章聚焦 Armv7-M/Armv7E-M 的 Cortex-M3/M4 与 T32。A32、A64 都采用 32 位定长指令编码,T32 使用 16/32 位编码;A64 的“64”主要关联其寄存器和执行架构,不表示每条编码 64 位。M3/M4 不执行 A32/A64。原书第 6.1 节的架构/扩展表是历史定位:A 侧常见虚拟内存 VMSA,R/M 常见保护模型 PMSA,具体产品和扩展仍要核对。
原书列出的 M0/M0+/M1、M3/M4/M7、M23/M33 在基本 Thumb、IT、除法、DSP、FPU、TrustZone 等方面支持不同;Y/N/P/O 分别表示支持、不支持、部分支持、可选。表中指令总数、2019 年版本与扩展清单以查表为主,不用硬背,也不能由“都叫 T32”推断任意指令都兼容。
在本章的 32 位 M profile 语境,字节=8 位,半字=16 位,字=32 位,双字=64 位。双字传输常用一对 32 位寄存器;不意味着整数寄存器突然变成 64 位。
读任何指令,都做四个检查
- 操作数是数值,还是地址?是否真正访问内存?
- 写了哪个寄存器或内存单元?基址会不会写回?
- 哪些标志更新,哪些保留?后面的条件判断读哪一次结果?
- PC 顺序前进还是跳转?这条语法在目标内核上是否合法?
下面所有算例均假定相应代码和 RAM 已实现、权限允许;涉及未对齐或不支持的写法会单独指出。重点不是背完所有指令,而是能把这些问题答对。
第六章 · 第 02 节
从第一个半字判断长度,再看编码字段
6.2.1—6.2.2,第 3—5 页;习题 6.3、6.5、6.6
掌握要求:会判断 T32 的 16/32 位长度,知道分类前缀不等于完整合法指令。
背景为什么要讲:原书两张二进制分类表供查读;理解译码入口即可,不必默写每个操作类别的所有位。
T32 指令从半字边界开始。检查当前指令的第一个半字的 bit[15:11]:为 11101、11110、11111 时,表示一条 32 位指令的前半;其他前缀属于 16 位长度的译码入口。识别为 32 位后,还要再取一个半字,不能把第二半字又当新指令起点。
例如 0xE001 的前五位为 11100,按 16 位译码;0xF04F 的前五位为 11110,需要第二半字。判出长度并不证明全部编码合法:其中还有未分配、保留或该内核不支持的组合。
原书 16 位分类入口可以归纳为:00xxxx 常含移位/加减/比较/移动;010000 常规数据处理;010001 特殊数据处理;01001x 为 PC 相对加载;0101xx、011xxx、100xxx 涉及单数据访存;10100x/10101x 生成 PC/SP 相对地址;1011xx 为杂类;11000x/11001x 多存储/多加载;1101xx 条件分支等;11100x 为窄无条件分支。“高六位分类”不是说每条指令都只有固定的六位操作码。还要继续看子字段及特殊保留组合。
32 位分类会结合第一个半字的 op1、op2 及第二半字中的相关位:多寄存器、双字/独占、移位寄存器数据处理、立即数数据处理、分支/杂类、各宽度加载存储、乘法及协处理器类等。原书表 6.5 的作用是展示分层译码,不是要求你靠肉眼完整反汇编所有机器码。
注意字节序:若两个半字按编码顺序为 EA4F、0001,小端内存字节是 4F EA 01 00。不能先把这四字节当一个普通小端 32 位数读成 0x0001EA4F,再去看那个数的最高五位判断第一半字。
第六章 · 第 03 节
汇编语法:逗号、方括号、后缀分别表达什么
6.2.3、6.2.5,第 5—10 页
掌握要求:能识别操作码、操作数、条件、S、.N/.W,区分语法说明符号与真正要输入的符号。
背景为什么要讲:UAL 统一写法便于同一源文件选用不同长度编码;它不让任意参数组合都变合法。
UAL 是 Unified Assembly Language,统一汇编语言。典型写法可读作“助记符及适用后缀 + 操作数列表”,如 ADDS.W R0,R1,R2:加法、更新相应标志、选择宽编码,结果写 R0。
| 符号/名字 | 作用 | 例子 |
|---|---|---|
| Rd / Rt | 目标或传输寄存器占位名 | LDR R8,[R1,#4] 中 R8 为加载目标 |
| Rn / Rm | 源或基址/偏移寄存器占位名 | 含义由指令定义决定 |
# |
立即数前缀 | #10、#0x10 分别是十进制 10、十六进制 16 |
[...] |
在实际访存语句中表示地址表达式 | [R1,#4] 是计算地址去访存 |
! |
相应寻址形式中的基址写回 | [R1,#4]! |
{R1-R4} |
实际代码中的寄存器列表 | 包含 R1、R2、R3、R4 |
; |
本讲义采用的 Arm 风格注释 | GNU 工具需按其注释语法转换 |
手册中的 <Rd>、{S}、[cond] 等是占位或可选语法说明,不全部照字面输入。尤其不能把手册的“方括号表示可选”与实际 LDR 的“方括号表示地址”混淆。
S 表示按该条指令规则更新标志,不是“所有 APSR 位都重写”。.N/.W 选择窄/宽指令编码,不改变访问的数据宽度;LDR.W 仍可取一个 32 位字,LDRB.W 仍只取一个字节。MOVW 的 W 则是助记符的一部分,和 MOV.W 是两件事。
不是所有指令都能任意加 S、.N、.W 或条件后缀。没有对应编码时汇编器应拒绝,不能靠后缀强迫硬件提供能力。不同工具还会选择不同别名或省略形式,本讲义优先给清楚、可核对的写法。GNU 汇编器的 Arm 语法与操作码说明
第六章 · 第 04 节
MOV、MOVS、MOV.W:看懂同一种操作的不同编码
6.2.5,第 7—10 页
掌握要求:能解释功能、长度与标志三个维度,能把两个半字顺序与小端字节写对。
背景为什么要讲:T1/T2/T3 是某条指令说明里的编码编号,不是整个架构通用的三个指令等级。
以不在 IT 块内的 R1→R0 复制为例:
| 汇编 | 编码半字(按取指顺序) | 字节数 | 标志行为 |
|---|---|---|---|
MOV R0,R1 |
4608 | 2 | 不更新算术标志 |
MOVS R0,R1 |
0008 | 2 | 更新 N/Z;此复制形式保留 C/V |
MOV.W R0,R1 |
EA4F 0001 | 4 | 不更新算术标志 |
MOVS.W R0,R1 |
EA5F 0001 | 4 | 此复制形式更新 N/Z,保留 C/V |
16 位 MOV 的寄存器字段和支持的寄存器范围与窄 MOVS 不同;MOVS 的窄复制可看作移位量为 0 的相应移位别名。宽 MOV 留出更多编码空间表达寄存器及 S 等选择。同一数据结果可以有不同编码长度和标志副作用。
原书脚注说 MOV 不可能产生某些宽编码,与同页 MOV.W 示例相矛盾,不应照背。T1/T2/T3 要连同具体指令页读取;不能推出“所有 T1 都不改标志,所有 T2 都改标志”。IT 块中的部分窄指令还存在上下文相关的标志行为,因此要看实际编码与上下文。
做编码题先把字段填入相应模板,再拼成半字,最后按需要写内存字节。读程序结果题则先问有没有 S、它究竟更新哪几位,通常不需要背全部机器码。
第六章 · 第 05 节
N、Z、C、V、Q:特别把减法的 C 讲清楚
6.2.4,第 6—7 页;习题 6.8
掌握要求:会算 32 位加减的 N/Z/C/V,解释有无符号是解释方式而非不同加法电路。
背景为什么要讲:后面比较、分支、多字长运算都依赖这里;标志不能靠“结果看起来像负数”猜。
N=结果 bit31,Z=结果全零,C 由相应算术/移位规则决定,V 表示有符号范围溢出。32 位加法结果保留低 32 位,C 记录无符号加法是否产生第 33 位;减法通常按加补码实现,C=1 表示没有借位,即无符号被减数不小于减数。
| 运算 | 低 32 位结果 | N Z C V | 为什么 |
|---|---|---|---|
| 0xFFFFFFFF+1 | 0x00000000 | 0 1 1 0 | 无符号进位;有符号 −1+1 不溢出 |
| 0x7FFFFFFF+1 | 0x80000000 | 1 0 0 1 | 无无符号进位;正数相加越过有符号上限 |
| 0−1 | 0xFFFFFFFF | 1 0 0 0 | 无符号需要借位,有符号结果 −1 合法 |
| 0x80000000−1 | 0x7FFFFFFF | 0 0 1 1 | 无符号没有借位;有符号越过下限 |
Q 不是普通的“上一条指令是否溢出”:它是相关指令可能置位的黏性饱和/溢出状态,未发生新事件时往往保留旧值;需要按规定清除。M4 的 GE[3:0] 则由部分 SIMD 指令提供逐通道信息,不是普通有符号分支条件 GE 的四位编码。
要读懂“最后一次更新该标志的操作”。LDR/MOV 或无 S 的算术不一定更新;SSAT 常只涉及 Q;MULS 常更新 N/Z 而保留 C/V。不能把所有标志都归给刚执行的最后一条指令。
第六章 · 第 06 节
CMP 后怎样选条件:有符号和无符号不能混
6.2.4、6.4.8—6.4.9;习题 6.7
掌握要求:会用 EQ/NE、HI/LS、CS/CC、GT/LE、GE/LT,能解释 N 与 V 为什么要结合。
背景为什么要讲:条件表要全见过;常用条件必须会选,冷门条件可按标志查表。
CMP R0,R1 按 R0−R1 计算标志,不把差写回 R0。之后比较同样的位串,如果你要按无符号解释,就用 HI/LS、HS/LO 等;如果按补码有符号解释,就用 GT/LE、GE/LT。EQ/NE 仅判断是否相等,两种解释都适用。
| 条件 | 判断式 | 主要意义 |
|---|---|---|
| EQ / NE | Z=1 / Z=0 | 等于 / 不等于 |
| CS(HS) / CC(LO) | C=1 / C=0 | 无符号 ≥ / <;也用于相应进位条件 |
| MI / PL | N=1 / N=0 | 结果最高位为 1 / 0,非完整有符号大小比较 |
| VS / VC | V=1 / V=0 | 有符号溢出 / 未溢出 |
| HI / LS | C=1 且 Z=0 / C=0 或 Z=1 | 无符号 > / ≤ |
| GE / LT | N=V / N≠V | 有符号 ≥ / < |
| GT / LE | Z=0 且 N=V / Z=1 或 N≠V | 有符号 > / ≤ |
| AL | 恒真 | 无条件;一般省略 |
例:R0=0xFFFFFFFF,R1=1,CMP 后 N=1、Z=0、C=1、V=0。无符号是 4294967295>1,HI 成立;有符号是 −1<1,LT 成立。两者都正确,只是在回答不同问题。
为什么不只看 N?0x80000000−1 得 0x7FFFFFFF,N=0,但原来的有符号 −2147483648 明明小于 1。这是减法发生 V=1,需用 N≠V 判断。原书表 6.6 将 LE 的中文写成无符号,是笔误;LE 是有符号 ≤。
第六章 · 第 07 节
IT 块:直线执行中的条件选择
6.2.5、6.4.9 的条件执行,第 9—10、30—31 页;习题 6.7、6.41
掌握要求:会写 IT、ITT、ITE、ITTE/ITTEE 等,并按 T/E 对照条件;分清条件执行与条件跳转。
背景为什么要讲:用于短小分支,未必比跳转总更快;性能要看路径与实现。
M3/M4 的普通条件数据处理通常须放在 IT 块中。IT{x{y{z}}} cond 最多控制后面四条指令,第一条隐含 T,额外字母依次描述第二、三、四条。T 表示与首条件一致,E 表示取相反条件;不是 E 就永远不执行。
CMP R0, #0
ITE EQ
MOVEQ R1, #10
MOVNE R1, #20
若 R0=0,只执行第一条赋值;否则只执行第二条。源代码中的 EQ/NE 要与 IT 模式匹配。不要在不了解上下文时直接单写 MOVEQ 并假设所有汇编器都自动补 IT;自动插入属于工具选项,不是机器保证。
ITTEE EQ 控制四条,条件依次是 EQ、EQ、NE、NE;ITETT NE 则是 NE、EQ、NE、NE。字母数算不清时,把“第一 T + 后缀字母”逐项展开。
IT 块不能随意嵌套;改变 PC 的分支等须遵守只能位于块末等限制,某些指令不允许在块内。中途更新标志会影响后续条件,且部分编码有专门限制。入门例子应避免在块内修改条件依赖的 APSR,而不是背成“架构下绝无任何能更新标志的块内情况”。
BEQ 用标志决定改不改 PC,MOVEQ 用条件决定改不改数据寄存器。前者跳转后从另一处取指;后者保持直线位置前进。IT 不需要把每条普通指令都扩出四位条件字段,这正是它与本章编码格式的联系。
第六章 · 第 08 节
立即数:先问是哪条指令的哪一种编码
6.3.1,第 11—12 页;习题 6.10—6.12
掌握要求:能解释字段有限,区分 imm8、imm12、MOVW/MOVT 与修饰立即数,不把一套规则推广给所有指令。
背景为什么要讲:修饰立即数算法用于读编码,日常可借汇编器判断;常见合法/非法例子必须知道原因。
立即数包含在指令自身,MOVS R0,#66 不去 RAM 取 66。32 位编码还要容纳操作码和寄存器等,不可能让每条指令同时直接携带任意 32 位常量,但不同指令有不同表示办法。
| 形式 | 典型限制/用途 |
|---|---|
| 窄 MOVS 的立即数 | 8 位,目标通常为低寄存器 |
| MOVW / MOVT | 各提供 16 位常量,组合构造 32 位值 |
| ADDW / SUBW | 对相应寄存器加减未移位 imm12,即 0—4095 等编码约束 |
| 某些宽数据处理指令 | 采用 Thumb 修饰立即数(modified immediate) |
| LDR/STR 的地址偏移 | 按该访问的编码、缩放和写回形式限制,不能套数据运算立即数表 |
修饰立即数包含一个字节、字节重复(00ab00ab、ab00ab00、abababab)和特定旋转的位图等。精确算法是在旋转分支构造带最高有效 1 的 8 位模式再进行相应 32 位旋转;并非“任意 8 位数在任意 16 位寄存器里转几圈”。零重复组合等还受编码规则限制。
编码查读补充:若 imm12[11:10]=00,由 [9:8] 选择低 imm8 的直接零扩展或上述三种重复(重复分支的 imm8 不能为 0);否则先构造 0x80 | imm12[6:0],再按 imm12[11:7] 给出的量做 32 位 ROR,该分支旋转量为 8—31。理解“有限字段经规则展开”即可,日常常量是否合法可交汇编器核对,不需穷举记忆全部值。
原书的 0x01FC 可由八位模式扩展旋转形成;0x07FC 的连续 1 超出该类位图的表示能力。但 0x07FC 仍可用于 MOVW 或相应 ADDW 等,不能称为所有指令的非法立即数。原书 ADDS R0,R0,#0x1234 也不能直接因为“十六进制不大”就认定合法,应先用 MOVW 装入另一个寄存器,再做寄存器加法。
任意 32 位常量可用 MOVW+MOVT,或 LDR Rd,=常量 伪指令交给工具选择合适序列。伪指令的加载常量不等于从那个数值表示的地址读数据,详见第 14—15 节。
第六章 · 第 09 节
数值与地址:寄存器直接、间接和移位操作数
6.3 概述、6.3.2—6.3.4,第 11—12 页;习题 6.10、6.12、6.13
掌握要求:会把操作数分为立即数、寄存器值、存储器内容;正确区分读内存与参与地址/算术计算。
背景为什么要讲:源文“ADD 直接带 [R2]”等例子跨了指令集边界,需要在这里纠正。
设 R1=0x20000100,那个地址的内存字为 7:MOV R0,R1 得到的是 0x20000100 这个数值;LDR R0,[R1] 才读出内存中的 7。前者为寄存器直接,后者为寄存器间接。存储器地址本身也是一个整数,区别由指令怎么使用它决定。
M3/M4 采用加载/存储分工,普通 ADD 不能写成 ADD R0,R1,[R2]。要先加载:
LDR R3, [R2]
ADD R0, R1, R3
数据处理的移位寄存器操作数,是把寄存器值先移位再参与运算,如 ADD R0,R1,R2,LSL #2 表示 R1+4×R2,没有内存访问,也不把移位结果写回 R2。这里所谓“寻址”是原书宽泛的操作数形成方式。
独立移位指令可用寄存器给移位量,例如 LSL R0,R2,R1;原书的 MOV R0,R2,LSL R1 在支持这种别名的工具中可转成相应寄存器移位指令,本讲义优先写显式 LSL 便于识别。不能由此推断普通 ADD 等也能任意附带寄存器指定移位量。立即数嵌入移位、寄存器控制的独立移位、访存地址中的缩放各有自己的编码限制,应以目标编码核对。
第六章 · 第 10 节
三种索引:先算本次地址,再单独算写回
6.3.5—6.3.7、6.4.2 的前三类模式;习题 6.14—6.19
掌握要求:能分别写出有效地址 EA 与执行后基址 Rn;遇到连续代码不擅自把基址重置。
背景为什么要讲:这是本章寻址题的核心;改变数据宽度不会改变偏移的字节单位。
设执行前基址 B=0x20000100,立即数偏移 d=8,分别独立执行下列指令:
| 写法 | 本次 EA | 执行后的 R1 | 口诀 |
|---|---|---|---|
LDR R0,[R1,#8] |
B+8 | B | 加偏移访问,不写回 |
LDR R0,[R1,#8]! |
B+8 | B+8 | 加偏移访问,且写回 |
LDR R0,[R1],#8 |
B | B+8 | 用旧基址访问,之后写回 |
每次都先写两列,内存访问地址与执行后的基址是两个结果。! 不是“再读一遍内存”,逗号放在 ] 外也不是排版差异。偏移可以为负,依具体编码范围;如 −4 表示地址退 4 字节,而非退四个字。
寄存器偏移 LDR R0,[R1,R4] 的 EA=R1+R4,一般不改两个源寄存器。M3/M4 的这种寄存器偏移形式不能随意加 !;原题 6.18 的 LDR R0,[R1,R3]! 可先解释其想表达 EA=旧 R1+R3 并写回,但它不是可直接使用的 T32 写法。可改成明确两条 ADD R1,R1,R3、LDR R0,[R1];这两条整体并非一次不可分割的访问。Arm M3 访存指令说明
原题 6.19 的 0xB 怎么看
如果三行分别从相同 B 开始:偏移形式 EA=B+0xB、不写回;前变址 EA=B+0xB、写回 B+0xB;后变址 EA=B、写回 B+0xB。若三行连续执行,第二行已改变 R1,第三行必须使用那个新值,最终 R1=B+0x16。
0xA/0xB 这类偏移还可能让字访问未对齐。本节先求逻辑 EA;实际是否成功,要结合 Normal/Device、指令和非对齐配置,不能仅凭地址计算正确就断言一定读成功。带写回的加载还要避开基址与目标寄存器重叠等受限组合。
第六章 · 第 11 节
缩放寄存器偏移:数组下标怎样变成字节地址
6.3.5、6.4.2 的寄存器移位模式,第 12—13、20 页;习题 6.14、6.17—6.18
掌握要求:会把元素下标乘元素字节数,用 LSL 形成偏移;基址和下标均不因普通偏移形式而写回。
背景为什么要讲:数组、结构体和查表都要用到;这是桶形移位器参与地址计算的具体用途。
若 R1 指向 32 位数组 a 的首元素,R2=i,则 a[i] 的地址为:
LDR R3, [R1,R2,LSL #2] ; 读取 a[i] 的一个字
字节数组无需乘 4,半字数组常乘 2。下标本身不是字节偏移;题目若已经给 R2 为字节偏移,再左移 2 位就多乘了一遍。
M3/M4 的这类单数据访存缩放通常支持 LSL #0—3,源寄存器与具体编码另有约束。不能把数据处理中的 ASR/ROR 或任意寄存器给移位量直接塞进这个访存形式。LDRD/STRD 等也不自动具有这里列出的每种寄存器偏移形式。
原书表 6.15 把同一寻址框架展开为 LDRB/LDRSB/LDRH/LDRSH/LDR 与 STRB/STRH/STR。它们主要变化的是传输宽度与加载扩展:地址公式按所写参数算,结果扩展另按指令算。例如 LDRB R8,[R1,#3] 读一个字节,而不是因为 R8 为 32 位就读四字节。
第六章 · 第 12 节
LDM/STM:寄存器号越小,对应地址越低
6.3.8、6.4.2 的多加载/多存储,第 14—15、20—21 页;习题 6.20、6.21
掌握要求:能独立画 IA/IB/DA/DB 的地址表与写回,明确 M3/M4 支持 IA/DB 的边界。
背景为什么要讲:四模式是经典 Arm 的通用模型;原题同时包含本内核不支持的 IB/DA,也要讲清原理。
LDM=Load Multiple,连续内存字加载到多个寄存器;STM=Store Multiple,多个寄存器写到连续内存字。列表中的寄存器可以不连续,但实际映射遵循最低寄存器号对应最低传输地址,不由你书写列表的先后决定。
设 B 为原基址,n 为寄存器数,每字 4 字节:
| 模式 | 最低传输地址 | 最高传输地址 | 若写回,基址成为 |
|---|---|---|---|
| IA | B | B+4n−4 | B+4n |
| IB | B+4 | B+4n | B+4n |
| DA | B−4n+4 | B | B−4n |
| DB | B−4n | B−4 | B−4n |
例:B=0x20000100,列表 {R1-R4},DB 模式对应 R1→0x200000F0、R2→F4、R3→F8、R4→FC。不是 R1 先去 B−4,然后 R2 再去 B−8。原书“每取一个再递减”的口头说明容易在这里导致逆序,应先算整个区间再按寄存器号填。
! 只决定是否把调整后的基址写回,不决定传输地址有没有递增/递减。原题 STMIA R9,{R1-R4} 无 !,传输照常覆盖四个连续字,但 R9 不变。
T32 的 M3/M4 对应多传输使用 IA、DB;STMIB/STMDA 不应当作它们能直接汇编的指令。原题 6.21 仍可按上表解释经典模型,并说明目标内核限制。寄存器列表大小、SP/PC/LR、基址是否也在列表里等均有编码约束,入门例子避免重叠。
第六章 · 第 13 节
PUSH/POP 与 FD 别名:批量压栈不是逐项反序写入
6.3.9、6.4.2 的压栈/出栈,第 15、21 页;习题 6.22—6.24
掌握要求:会写 FD 栈的存取对应关系,算 SP 与各寄存器地址;理解返回地址为何要保存。
背景为什么要讲:接上第五章的满递减栈,后续函数调用必须用得熟练。
M3/M4 的常用栈是满递减:压栈向低地址生长,弹出后 SP 增大。主要别名关系:
| 含义 | 常见写法 | 相应地址模式 |
|---|---|---|
| 压栈 | PUSH {R1-R4} |
STMDB SP!,{R1-R4};也写 STMFD |
| 弹栈 | POP {R1-R4} |
LDMIA SP!,{R1-R4};也写 LDMFD |
SP 原为 0x20001000,压四个字后 SP=0x20000FF0;从低到高分别保存 R1、R2、R3、R4。一次 PUSH {R1-R4} 与四条“先 R1、再 R2、再 R3、再 R4”的单独 PUSH 布局不同。POP 也按寄存器号与地址映射,不是任意从列表末尾开始。
原题 6.22 的 STMFD SP!,{R1-R8} 与 STMDB 为同一满递减存储含义,n=8、SP 减 32;6.23 的 LTMFD 是拼写错误,应为 LDMFD,与 LDMIA 相应,SP 加 32。
FD、EA 等是“栈的组织名称”,IA、DB 等是“具体多传输地址模式名称”。同样 FD 栈,存时用 DB,取时用 IA。原书 LDMEA/STMEA 作为其他组织的背景,应按“存与取互为恢复”理解,不能只见一个字母就把 LDMDB 当本章标准 POP。
PUSH/POP 的可用寄存器受窄/宽编码限制;涉及 R8 等可能需宽编码。浮点 VPUSH/VPOP 还能保存 S/D 寄存器,64 位 D 视图是传输单位,不因此获得双精度算术。栈区需真实可写、容量足够并维持相应对齐,不能为了少写一条指令破坏调用约定。
第六章 · 第 14 节
PC 相对:地址、该地址的内容和文字池要分三步
6.3.10、6.4.2 的 PC 相对模式,第 15—16、21—22 页;习题 6.24
掌握要求:会用当前指令地址 +4 与相应对齐计算 literal 地址,区分 ADR、LDR 地址形式和 LDR 伪指令。
背景为什么要讲:文字池解决大常量编码问题;下一章才系统学习数据定义和汇编控制。
Thumb 读取 PC 有架构规定的偏移,常见为当前指令地址 A+4;字面量加载和 ADR 等还会使用按字对齐的 PC 基址。不能把源码行号当字节地址,也不能一概把“PC+4”理解为跳过下一条源代码行。
以一条位于 A=0x08001002 的字面量 LDR 为例:可见 PC=A+4=0x08001006,按字对齐得 0x08001004。LDR R0,[PC,#0xC] 从 0x08001010 读字,而非从 A+0xC 读取。具体偏移范围和正负由编码决定。
| 写法 | 得到什么 |
|---|---|
ADR R0,label |
生成附近标号地址,不读取该地址的数据 |
LDR R0,[R1] |
读取 R1 指向的内存字 |
LDR R0,[PC,#offset] |
读取 PC 相对位置的内存字 |
LDR R0,=0x12345678 |
伪指令:使 R0 得常量 0x12345678,由工具选择实现 |
文字池(literal pool,原文也叫文本池)是代码附近存放常量的数据区。若伪指令由 literal LDR 实现,它访问的是池中存常量的地址,不是去 0x12345678 读未知数据。工具也可能把可表示常量转换为其他指令,不保证所有 LDR= 都产生同一序列。
原书 ADR 的 ±4095 等范围对应相关宽编码,窄形式范围、方向和缩放不同;标签距离超界需调整布局。分支的 PC 相对基准与取数据的对齐规则也要分开,见第 27、29 节。
第六章 · 第 15 节
内部传送:MOVW/MOVT、MVN、MRS/MSR
6.4.1,第 16—17 页;习题 6.9、6.35
掌握要求:会构造 32 位常量,知道 MOV 不代替 LDR,特殊 CPU 寄存器不用普通内存访存语法。
背景为什么要讲:指令表每一项都要认识;这几条是系统初始化与常量加载的基础。
MOV 复制寄存器值或装载可编码的立即数,不直接读取任意内存单元。内存到通用寄存器通常用 LDR 系列。因此 MOV R0,[R1] 不能取代 LDR R0,[R1]。
MOVW 写入一个 16 位立即数到目标低半并清零高半;MOVT 修改高 16 位,保留低半。构造 0x12345678:
MOVW R0, #0x5678 ; R0=0x00005678
MOVT R0, #0x1234 ; R0=0x12345678
顺序不能随意交换:先 MOVT 再 MOVW,后者会清掉之前的高半。MVN 则是按位取反,MVN R0,R1 得 ~R1,不等于数学取负 −R1;补码取负通常还要加 1,或用 RSB 从 0 减。
MRS 从特殊寄存器读到一般寄存器,MSR 反向写入,例如 MRS R0,PRIMASK、MSR CONTROL,R0;修改需满足权限与同步要求。SCB/NVIC 的内存映射寄存器则通过地址访问。第五章已区分 CPU 特殊寄存器与 MMIO,此处把对应指令接上。
第六章 · 第 16 节
LDR/STR 的宽度:加载扩展,存储截取
6.4.2、第 17—22 页;习题 6.26—6.28、6.34、6.51、6.52
掌握要求:会根据宽度和有无符号选指令,计算 8/16 位值进入 32 位寄存器后的结果。
背景为什么要讲:原书各寻址模式反复列不同大小,本节用一套规则解释它们,避免机械背重复表。
| 读取指令 | 读取多少 | 高位怎样填 | 相应写入 |
|---|---|---|---|
| LDRB | 8 位 | 零扩展到 32 位 | STRB 只写源寄存器低 8 位 |
| LDRSB | 8 位 | 用 bit7 符号扩展 | 存同样 8 位,不另外分 signed 存储 |
| LDRH | 16 位 | 零扩展到 32 位 | STRH 只写低 16 位 |
| LDRSH | 16 位 | 用 bit15 符号扩展 | 同上 |
| LDR | 32 位 | 已为一个字 | STR 写一个字 |
| LDRD | 64 位 | 写入两个 32 位寄存器 | STRD 从两个寄存器写双字 |
同一个内存字节 0x88:LDRB 得 0x00000088,即 136;LDRSB 得 0xFFFFFF88,即 −120。扩展保留原类型的数值意义,不是把负数一律补 1:正的有符号数也补 0。
读 R1+3 的字节到 R8:LDRB R8,[R1,#3];读 R1+4 半字:LDRH R8,[R1,#4];读 R1+8 字:LDR R8,[R1,#8]。若要求有符号字节/半字,要改 SB/SH,不能只改注释。
加载目标仍是 32 位;STRB/STRH 不先把源寄存器变窄,而只取低部去写,其他内存字节不应被当成一起写掉。对齐、支持的地址形式和基址/目标重叠仍需按指令检查。原书 immediate-offset、前变址、后变址、寄存器缩放表都沿用这些数据规则。
第六章 · 第 17 节
带 T 的非特权访存:内核替用户检查访问
6.4.2 的非特权等级加载/存储,第 22 页;习题 6.45
掌握要求:说明这些指令为什么存在,以及它们不会把调用者整体永久降权或给用户提权。
背景为什么要讲:属于系统编程背景,现阶段不必实现完整用户缓冲区 API,但要懂保护边界。
特权操作系统要替用户读写一段缓冲区,不能只凭自己“权限够大”就把用户本不该访问的数据传出去。LDRT/STRT 等允许特权代码按非特权数据访问规则完成该次访问,从而发现不合法的用户地址。
原书列出 LDRBT、LDRSBT、LDRHT、LDRSHT、LDRT,以及 STRBT、STRHT、STRT;B/H、SB/SH 的宽度与扩展含义和前节一致,T 在这里表示该类非特权访问语义。其立即数偏移等限制须查具体编码。
这不把 Handler 变成非特权模式,也不让用户绕过 MPU。数据曾由特权程序写过,不意味着它必然受保护;真正限制来自区域权限与核心系统规则。访问被拒绝可能产生相应 Fault,系统还需设计错误处理,不能把一条访存当完整、无竞争的安全检查协议。
第六章 · 第 18 节
独占访问:STREX 的第一个寄存器是状态
6.4.2 的排他式访问,第 23 页
掌握要求:读懂 LDREX/STREX 的数据与状态寄存器,知道 0 成功、非零失败以及重试。
背景为什么要讲:第五章讲原理,本节补上字节/半字/字与具体操作数顺序。
LDREXB/H、LDREX 分别按字节、半字、字加载并建立相应独占保留;STREXB/H、STREX 尝试条件存储。STREX R2,R1,[R0] 不是把 R2 写出去,而是尝试写 R1,结果状态放 R2。
retry:
LDREX R1, [R0]
ADD R1, R1, #1
STREX R2, R1, [R0]
CMP R2, #0
BNE retry
成功状态为 0,失败时该次存储不发生,应重新读、重新计算。CLREX 用于清除本地独占保留,例如放弃一条尝试路径;它不清掉软件定义的“锁变量值”。相应操作需匹配地址、大小、对齐、内存类型,并满足寄存器限制。
完整锁/信号量还需软件协议、必要屏障和系统独占监视支持。跨 DMA/多核的竞争能否被正确监控,取决于芯片;本循环不是任意 MMIO 寄存器都能使用的读改写模板。
第六章 · 第 19 节
加减、进位与借位:把 64 位运算接起来
6.4.3,第 23—24 页;习题 6.9、6.50
掌握要求:会写 ADD/ADC、SUB/SBC 的关系,理解 RSB,能追踪两字组成的 64 位加减。
背景为什么要讲:相同位宽加法通常共用电路;跨字运算必须正确传递低字的 C。
ADD 得 A+B;ADC 得 A+B+C。SUB 得 A−B;SBC 得 A−B−(1−C),不能误写成 A−B−C。RSB(Reverse Subtract)交换相减方向,例如 RSB R0,R1,#0 得 −R1 的补码结果。ADDW/SUBW 主要提供相应直接立即数形式,W 不代表 64 位加法。
两对寄存器 {R1,R0}、{R3,R2} 分别为高字、低字,64 位加法:
ADDS R0, R0, R2 ; 先低字,产生 C
ADC R1, R1, R3 ; 高字加上低字进位
64 位减法对应先 SUBS R0,R0,R2,再 SBC R1,R1,R3。例如 0x0000000200000000−0x0000000100000001:低字 0−1 得 FFFFFFFF、C=0;高字 2−1−1=0,结果为 0x00000000FFFFFFFF。
低字与高字之间不要插入会破坏 C 的指令。最终想得到完整 64 位的零/符号等状态,还需按需求组合;只看高字操作的 Z 不足以说明两个字全为零。
第六章 · 第 20 节
MUL、长乘法、MLA/MLS:先决定要留下多少位
6.4.3、6.4.12 的公共乘法表,第 24、36 页;习题 6.9、6.47、6.50
掌握要求:能区分 32 位低结果与 64 位完整结果,读懂 RdLo/RdHi 与累加关系。
背景为什么要讲:乘法是 DSP 的基础,结果位宽和符号解释会决定用哪条指令。
MUL 取 32×32 乘积的低 32 位。对同样的输入位串,有符号与无符号乘法的低 32 位相同,但完整 64 位的高部可能不同;因此不能简单把 MUL 叫成“只会无符号乘法”。有符号结果是否溢出,也不能靠 MUL 不更新 V 来证明没有。
| 指令 | 数学含义 | 结果 |
|---|---|---|
| MUL | Rn×Rm 的低部 | 32 位 |
| MLA | Ra+Rn×Rm 的低部 | 32 位 |
| MLS | Ra−Rn×Rm 的低部 | 32 位 |
| UMULL / SMULL | 无符号 / 有符号完整乘积 | RdHi:RdLo 共 64 位 |
| UMLAL / SMLAL | 64 位原累加值加相应乘积 | 64 位 |
例:位串 FFFFFFFF 与 00000002。UMULL 得 00000001:FFFFFFFE;SMULL 把前者解释为 −1,得 FFFFFFFF:FFFFFFFE。低字一样,高字不同。64 位寄存器对的写法里 RdLo 通常先写在操作数列表,不要因数学显示“高:低”就倒填。
MAC(Multiply-Accumulate,乘并累加)对应点积 ,常见于 FIR 滤波、相关、矩阵乘法。MLA 可减少乘后再加的指令,但要选择足够累加位宽。MULS 的某些窄形式可改 N/Z;这些长乘及 MAC 通常不提供普通 S 后缀,不能一概假定更新 NZCV。
第六章 · 第 21 节
除法与定点缩放:同一位串可以代表不同数
6.4.3 与扩展背景;习题 6.48—6.50
掌握要求:区分 SDIV/UDIV,理解整数与定点共用电路的条件,能做简单 Q15 乘法缩放。
背景为什么要讲:星号题仍然讲到,但不要求现在设计完整乘除法器。
UDIV 按无符号除法;SDIV 按有符号补码,商向零截断。例如 SDIV 的 −7/3=−2;若同样的 0xFFFFFFF9 当无符号,商就完全不同。整数低位乘积可以相同,不代表除法也能不分符号。
没有直接给余数的这一对指令,可用商 q 后 MLS Rrem,Rq,Rden,Rnum 求 n−q×d。除零行为取决于 CCR.DIV_0_TRP 等规定,不能把它想成总会得到数学有效值;普通除法也不自动靠 V 报告一切问题。
定点小数把整数 I 解释成 。若加减两数使用相同缩放,整数加减电路可直接用于定点加减;若缩放不同,先对齐。乘法需调整结果的小数位位置,并考虑舍入、截断和溢出。
这解释了为什么 DSP 需要乘加、算术移位、饱和和符号扩展。浮点则还需处理指数、对阶、规格化与舍入,见第 37 节;不能把定点/浮点的差异说成只换一条注释。
第六章 · 第 22 节
逻辑指令与位掩码:设置、清零、翻转各用什么
6.4.4,第 24 页;习题 6.29—6.33
掌握要求:会用 ORR、BIC、EOR 操作指定 bit,会区分 AND/ORR/ORN/EOR。
背景为什么要讲:寄存器配置、GPIO 和标志集合都依赖掩码;这里处理的是寄存器值,还不自动写回外设。
| 指令 | 按位运算 | 常用目的 |
|---|---|---|
| AND | A & B | 只保留掩码允许的位 |
| ORR | A | B | 掩码为 1 的位置置 1 |
| EOR | A XOR B | 掩码为 1 的位置取反 |
| BIC | A & ~B | 掩码为 1 的位置清 0 |
| ORN | A | ~B | 与“取反后的第二操作数”相或 |
| MVN | ~B | 全位取反 |
本章 bit 编号从 0 开始,bit2 掩码为 :
EOR R0, R0, #4 ; bit2 翻转
ORR R0, R0, #4 ; bit2 置 1
BIC R0, R0, #4 ; bit2 清 0
AND R0, R0, R1 ; R0 与 R1 按位与,结果写 R0
这四行是四个独立要求,不是说应该连续全做一遍。翻转两次会还原;置位和清位重复做则保持同样结果。ORR 与 ORN 的区别是第二操作数是否先取反,ORN 不是 NOR;EOR 也不是 OR。
原书表 6.24 把若干 EOR 行写成 |,应改为 XOR;BIC/ORR/EOR 的真值关系以本表为准。若是在内存映射寄存器上做读改写,还需配合 LDR/STR 并考虑副作用和并发,不能把寄存器内一条运算等同外设原子更新。
第六章 · 第 23 节
LSL、LSR、ASR、ROR、RRX:把移出的位画出来
6.3.4、6.4.5,第 12、25—26 页;习题 6.36—6.38
掌握要求:会算五种移位、移出位与 C,知道算术右移保符号,RRX 使用原 C。
背景为什么要讲:桶形移位器还可参与其他数据处理;零和超过字长的移位属于查规则层,不能忽略。
| 操作 | 空位/回填 | 右移一个位的例子,输入 0x80000001 |
|---|---|---|
| LSL | 低位补 0,高位丢弃 | 左移 1 得 0x00000002 |
| LSR | 高位补 0 | 0x40000000 |
| ASR | 高位复制原符号位 | 0xC0000000 |
| ROR | 移出的低位回到高端 | 0xC0000000 |
| RRX | 只右移 1 位,高端填原 C | C=0 得 0x40000000;C=1 得 0xC0000000 |
这组数恰好使 ASR 与 ROR 的结果相同,不代表二者相同;改用 0x80000000,ASR #1 得 C0000000,ROR #1 得 40000000。RRX 把“原 C + 32 位寄存器”当扩展通道,输出的旧 bit0 可用于更新 C;ROR 不以原 C 作为回填位。
带 S 的对应指令按规则更新 N/Z/C,V 通常保留;不带 S 时 APSR 的 C 不因图中出现一个“移出位”就自动改变。RRX 即使不改标志,仍要读取原 C 来填高位。
移位量的边界别用一种规则包办
立即数编码常见 LSL #0—31、LSR/ASR #1—32、ROR #1—31;寄存器给量则使用相应低 8 位,可出现 0、32、>32。寄存器量为 0 常保留结果与 C;LSL/LSR 为 32 时结果 0,C 分别来自原 bit0/bit31,超过 32 时结果与 C 为 0;ASR≥32 则全填符号位。ROR 非零的 32 倍数结果不变但更新 C 的规则与“移位量为 0”不同。
因此“最多只能移 32 位”不是全部寄存器形式的准确限制。日常做题先明确编码/移位形式,再处理边界;图采用寄存器给量的模型并明确 S 开关。
第六章 · 第 24 节
扩展与字节反转:改变表示宽度不等于数值转换
6.4.6,第 26—27 页;习题 6.34、6.51、6.52
掌握要求:会算 SXTB/SXTH/UXTB/UXTH 和 REV/REV16/REVSH,知道符号位来自截取的数据。
背景为什么要讲:访存加载时可扩展,寄存器内部也可扩展;两者分开认识才能看懂 DSP 数据整理。
SXTB/SXTH 取源低 8/16 位做符号扩展,UXTB/UXTH 取同样的低部补零。R0=0x22AA8765 时:SXTB 和 UXTB 都得到 0x65;SXTH 得 0xFFFF8765;UXTH 得 0x00008765。符号取自被截取部分的 bit7/bit15,不是一律看原寄存器 bit31。
这些指令的某些宽形式还可先 ROR #8/#16/#24,再取低部,方便提取另一个字节/半字。保留数值的前提是解释原数据类型;不同类型使用不同扩展,否则 0x88 可能从 −120 误变 136。
REV 把四字节反序;REV16 在两个半字内部各交换字节;REVSH 只交换低半字的两个字节,再以交换后的 bit15符号扩展。输入 0x12345678:REV 得 0x78563412,REV16 得 0x34127856;输入 0x44558899,REVSH 得 0xFFFF9988。
原书示例 REVH 应写 REV16。这些是字节重排,不是把每个字节的 bit 全部倒序;位序反转用下一节 RBIT。也不是把浮点位串变为其整数数值的转换指令。
第六章 · 第 25 节
位域、CLZ、RBIT:按范围处理一段 bit
6.4.7,第 27—28 页
掌握要求:能根据 lsb 与 width 划范围,手算 BFC/BFI/UBFX/SBFX;知道 CLZ 与 RBIT 的用途。
背景为什么要讲:用于寄存器字段与压缩数据,常比连续掩码移位更直接;不需要背每个二进制操作码。
位域从最低位置 lsb 开始,宽度 width,覆盖 lsb 到 lsb+width−1。必须满足 lsb≥0、width≥1、lsb+width≤32;因此 lsb=0 时可以覆盖 32 位,原书“仅 1—31 位”不完整。
| 指令 | 功能 |
|---|---|
| BFC Rd,#lsb,#width | 把目标该段清 0,其余位保留 |
| BFI Rd,Rn,#lsb,#width | 把 Rn 的低 width 位放进目标该段,其余位保留 |
| UBFX Rd,Rn,#lsb,#width | 提取该段到低部,再零扩展 |
| SBFX Rd,Rn,#lsb,#width | 提取该段到低部,以字段最高位做符号扩展 |
| CLZ Rd,Rn | 从 bit31 向低数连续 0;全 0 返回 32 |
| RBIT Rd,Rn | 反转全部 32 位的位序 |
BFC R0,#4,#8 清 bit4—11。原 R0=0x12345678,清除掩码 0x00000FF0 后得 0x12345008;原书附近文字的 0x1234F00F 与该操作不符。
BFI R1,R0,#8,#16 将 R0 低 16 位插到 R1 的 bit8—23。R0=12345678、R1=AABBCCDD 时,R1 变 AA5678DD,不是把 R0 自己的 bit8—23 原位搬过去。
UBFX/SBFX 若取出 8 位 0xF0,分别得 0x000000F0 和 0xFFFFFFF0。RBIT 输入 1 得 0x80000000;REV 输入 1 得 0x01000000,二者不要混。CLZ 常用于规格化、查找最高有效位等,知道用途即可。
第六章 · 第 26 节
CMP、CMN、TST、TEQ:计算但不保存结果
6.4.8,第 28 页;习题 6.39
掌握要求:能说出隐藏的运算,知道它们更新哪些标志且不覆写源寄存器。
背景为什么要讲:条件逻辑不一定要先产生一个新的数据寄存器结果,这组指令专门留下判断依据。
| 指令 | 内部运算 | 保存什么 |
|---|---|---|
| CMP A,B | A−B | 算术 NZCV,差值不写回 |
| CMN A,B | A+B | 算术 NZCV,和不写回 |
| TST A,B | A AND B | 相应 N/Z/C,逻辑结果不写回,V 保留 |
| TEQ A,B | A XOR B | 相应 N/Z/C,逻辑结果不写回,V 保留 |
CMN 的名字有“negative”,但实际是相加,不是先把 A 取负再比较,也不是直接把 CMP 的所有标志取反。TST 常检测某些 bit 是否为 0;TEQ 可用于位串异或为零的判断。
TST R0, #4
BNE bit2_is_one ; AND 结果非零,说明 bit2 为 1
它们不再另加 S 后缀。TST/TEQ 的 C 来自相应移位/立即数扩展规则,可能保留也可能更新;不能说“所有测试总会改全部标志”,也不能把它们的 C 当作减法无借位结果。第 06 节的有符号/无符号大小条件应与真正 CMP 的上下文配合。
第六章 · 第 27 节
B、BL、BX、BLX:调用要能找到回来的路
6.4.9,第 28—30 页;习题 6.9、6.40
掌握要求:区分改 PC 与同时建立 LR,解释嵌套调用为什么保存 LR;知道寄存器分支的 Thumb 标识。
背景为什么要讲:完整函数约定在第七章,本节先把控制流和返回链条建立起来。
B 跳到标号,不建立新的返回链接;BL 跳到标号并把返回信息放 LR;BX 按寄存器给出的目标分支;BLX 的相应寄存器形式还建立返回链接。M3/M4 仍在 Thumb,BX/BLX 的合法目标 bit0 应标识 Thumb。
若 4 字节 BL 位于 0x08000000,下一条地址为 0x08000004,LR 典型得到 0x08000005,低位表示 Thumb。BX LR 回到下一条实际指令,而不是去奇数字节取一条未对齐指令。
函数自身还调用另一个函数,第二次 BL 会覆盖 LR,必须在合适位置保护原返回链接,例如 PUSH {R4,LR}、调用、POP {R4,PC},并遵守对齐/保存约定。B 并非任何情况下都不能用于函数相关控制流:尾调用等可有意不建立新链接;习题要答的是普通可返回调用为什么用 BL/BLX 更方便。
跳转范围按编码查,不能只认助记符
| 形式 | 相对 A+4 的典型字节位移范围 |
|---|---|
| B.N | −2048 到 +2046 |
| B.W / BL 的宽直接分支 | −2²⁴ 到 +2²⁴−2(约 ±16 MiB) |
| Bcond.N(块外) | −256 到 +254 |
| Bcond.W(块外) | −2²⁰ 到 +2²⁰−2(约 ±1 MiB) |
| IT 中受条件控制的宽 B | 使用相应无条件分支编码的范围,并遵守块末限制 |
| BX/BLX 寄存器 | 目标来自寄存器,但仍须有效、可执行、状态合法 |
原书把普通 B 的范围写成 ±1 MB,混入了宽条件分支范围,应分开。写 PC 的不同指令也有各自规则;第五章 MOV 写 PC 与 BX 的区分仍然适用,不要笼统说“所有 PC 数值必须奇数”。
第六章 · 第 28 节
BEQ、CBZ 与 MOVEQ:同样在判断,走法不同
6.4.9,第 29—31 页;习题 6.41、6.42
掌握要求:会选择标志条件分支与零检测分支,知道 CBZ 不改 APSR、只支持有限的前向范围。
背景为什么要讲:代码变短不总是适用范围变大;要同时检查寄存器、距离和标志依赖。
CMP R0,#0+BEQ label 先更新标志,再根据 Z 跳转;CBZ R0,label 直接检查寄存器是否为 0 并跳转,不修改 APSR。CBNZ 对应非零,原书的 CNBZ 是拼写错误。
CBZ/CBNZ 只允许低寄存器 R0—R7,且为短前向分支;从 PC=A+4 起的位移为 0—126、按 2 字节计,等价于从当前指令 A 看 +4—+130。不能拿它直接做向后的循环回边,也不能放进不允许的 IT 上下文。
普通 BEQ 的目标是标号/相对目标,不是寄存器操作数。原书 BEQ R1 的寄存器跳转解释不成立。若要在 EQ 时跳到 R1 给出的地址,可在合适上下文写 IT EQ 后 BXEQ R1,目标仍须合法,且分支位于块末。
BL 的条件执行在 T32 下也要核对 IT 等规则,不应由 A32 的习惯直接推“任意 BLcond 都自带四位条件字段”。MOVEQ 则通常在 IT 中决定是否写数据,PC 保持顺序推进;二者比较时既要说“条件来自哪里”,也要说“条件满足后改变什么”。
第六章 · 第 29 节
TBB/TBH:查表地址与跳转基准不是一回事
6.4.9 的跳转表,第 31—32 页
掌握要求:能独立完成“算表项地址 → 读取无符号项 → 乘 2 → 加分支 PC”的过程。
背景为什么要讲:用于 switch 等多分支,原书的文字把表基址与跳转基准混在一起,需要明确拆开。
TBB 访问字节表,语法 TBB [Rn,Rm];TBH 访问半字表,语法 TBH [Rn,Rm,LSL #1]。设该条 32 位指令位于 A,表基址 B 在 Rn,下标 i 在 Rm:
表放在哪里由 Rn 决定;跳到哪里从该分支指令后的位置 A+4 起算。表项存的是半字单位的无符号偏移,不是完整 32 位目标地址。原文“相对表首地址跳转”仅在表首恰好也是那个基准时才成立。Arm M4 的 TBB/TBH 指令定义
例:TBB 位于 0x08000100,R0=0x08000200,R1=2,表地址 0x08000202 内的字节是 0x12,则目标=0x08000104+0x24=0x08000128,不是 0x08000224。
TBB 的表项最大 255,对应前向 510 字节;TBH 最大 65535,对应 131070 字节。表项索引距离和目标分支距离是两套量。Rn 可按规则用 PC 指向紧接指令的表,不能用 SP;Rm 不能为 SP/PC,并且要先检查下标范围。它们不修改条件标志;IT 内还须遵守块末等限制。
原书 DCB 用来定义字节数据,具体伪指令到第七章学习。构造表项时用 (目标−分支后基准)/2,再确认偏移非负且放得进相应表项,不能把任意标签差直接塞进去。
第六章 · 第 30 节
SSAT/USAT:输出范围变窄,寄存器仍是 32 位
6.4.10,第 32—33 页;习题 6.43
掌握要求:会按指定宽度计算有符号/无符号饱和值,知道输入解释和 Q 的黏性。
背景为什么要讲:第五章讲削顶原因,本节落实到指令;饱和并不恢复已经丢失的精度。
SSAT 把输入夹到 n 位有符号范围;USAT 把其有符号输入值夹到 n 位无符号输出范围。结果仍放在 32 位寄存器,负的 SSAT 结果会符号扩展。可选移位在饱和前进行,须满足具体编码限制。
| 输入位串(按有符号) | SSAT #16 | USAT #16 |
|---|---|---|
| 00020000 | 00007FFF | 0000FFFF |
| 00008000 | 00007FFF | 00008000 |
| 00007FFF | 00007FFF | 00007FFF |
| 00000000 | 00000000 | 00000000 |
| FFFF8000(−32768) | FFFF8000 | 00000000 |
| FFFF7FFF(−32769) | FFFF8000 | 00000000 |
| FFFFFFFF(−1) | FFFFFFFF | 00000000 |
SSAT 的 n 可按规则为 1—32,USAT 为 0—31;USAT #0 的合法输出只有 0。USAT 不是先把 FFFFFFFF 当 4294967295,再夹到最大值,本例它按 −1 夹到 0。
溢出是数学结果超出表示范围的现象,截断/回绕是一种处理结果;饱和是另一种夹到上下限的处理。不能把“丢掉高位”直接当作溢出的完整定义。SSAT/USAT 发生饱和会置 Q,未饱和时通常不清旧 Q,NZCV 并非一并重算。
若先用普通 32 位加法把真实结果溢出丢掉,再做 SSAT #32,已无法恢复那个数学结果;必须在正确位宽和合适阶段使用饱和或更宽中间结果。信号幅度削顶后仍有失真,只是避免回绕导致的突然反向。
第六章 · 第 31 节
屏障指令:顺序、完成、执行上下文再对照
6.4.11,第 33—34 页;习题 6.44
掌握要求:能分别解释 DMB、DSB、ISB,不把它们视为同一种“清空所有东西”。
背景为什么要讲:第五章已有系统含义,本节要求把名字和实际指令对应起来。
| 指令 | 核心目的 | 本章需要会说的话 |
|---|---|---|
| DMB | 约束相关存储器访问顺序 | 防止需要有序的前后访问被越过 |
| DSB | 等待规定范围的先前操作完成再继续 | 不只排顺序,还要等待完成 |
| ISB | 同步后续指令执行上下文 | 后续按更新后的上下文重新取指/执行 |
M3/M4 即使顺序执行指令,也可能有写缓冲和互连延迟;指令退休、总线完成、外设内部动作完成不是同一时刻。因此不能因为没有通用乱序执行,就删除所有屏障。
原表将 DMB 概括成“前面全部访问完成才执行后面指令”,过于接近 DSB,且省略访问范围。ISB 也不是保证把全部数据 Cache 写回。具体系统控制、休眠、共享数据协议要按手册选择,volatile 不能替代,DSB 也不替代设备状态轮询。
第六章 · 第 32 节
WFI、WFE、SEV:等待中断与等待事件
6.4.11,第 33—34 页
掌握要求:解释 WFI/WFE 区别,知道 WFE 会消费已记录事件,不保证每次执行都真的休眠。
背景为什么要讲:低功耗不是普通算术题重点,但与中断和实时系统有关,需要知道用途。
WFI=Wait For Interrupt,等待中断相关唤醒条件;WFE=Wait For Event,等待事件;SEV=Send Event,发送事件,作用包括本处理器的事件记录,并可由系统传播给其他处理器。
WFE 有一个简化的事件记录模型:记录为 1 时,执行 WFE 清掉记录并直接继续;记录为 0 时,才可能等待后续事件。不是把所有事件按次数排进 FIFO,已经记录时多个事件可能合并。
唤醒以后是否立即进入 ISR,与能否从等待中醒来不是同一判断,还涉及 IRQ 使能、屏蔽、优先级和电源/时钟集成。存在已满足的条件时,WFI 也不保证一定停住。原书“立即进入休眠”应按这种条件理解。
深睡眠、异常退出后睡眠等还与 SCB 的 SCR 和芯片电源控制配合。这里只需认识用途和事件记录行为,暂不要求设计无竞争的完整睡眠协议;不能不查芯片就复制一段指令宣称任何外设都能唤醒。
第六章 · 第 33 节
SVC、CPS、BKPT、NOP:系统类指令各管一层
6.4.11,第 33—34 页
掌握要求:知道 SVC 请求服务、CPS 改屏蔽、BKPT 与调试有关,不能把它们当普通分支或任意提权。
背景为什么要讲:这是本章杂类表的其余内容,详细服务与启动程序在下一章延伸。
SVC #imm 产生编号 11 的 SVCall 异常,常用于用户任务请求可信服务。立即数字段可表示服务编号,但内核还需解释参数并实施权限检查。它不是直接把任意地址当函数入口调用,也不保证任何优先级/屏蔽上下文都能正常服务。若当前状态不允许,可能引出错误升级,不能在 NMI/HardFault 里把 SVC 当普通函数调用。
CPSID i ; 设置 PRIMASK,限制可配置异常激活
CPSIE i ; 清除 PRIMASK
CPSID f ; 设置 FAULTMASK
CPSIE f ; 清除 FAULTMASK
这些操作受特权等规则约束,清某个屏蔽位不等于清所有外设屏蔽和 Pending。临界区应保存恢复原状态,而不是结束一律 CPSIE i;第五章已经给出保存 PRIMASK 的做法。FAULTMASK 的范围与返回清除规则也不能与 PRIMASK 混用。
BKPT #imm 用于断点,按调试配置可能进入停机、调试处理或相应 Fault,没接调试器时不要假定程序会安全暂停等你。NOP 没有所需的数据运算效果,但占指令位置和执行资源,不能凭几个 NOP 得到跨所有时钟配置都准确的延时。
第六章 · 第 34 节
M4 的 SIMD:四个小加法与一个大加法不同
6.4.12 的 SIMD 与表 6.38—6.39,第 35—36 页;习题 6.46
掌握要求:理解 lane 内并行、跨 lane 不传普通加法进位,区分有无符号、饱和和半分系列。
背景为什么要讲:星号题要求理解背景和技术思想,不要求背完数十个变体,但原表各类都要见过。
SIMD=Single Instruction Multiple Data,单指令处理多份数据。图像像素、多个音频采样常用 8/16 位,32 位寄存器可装四字节或两个半字,一条相应指令对各 lane 独立运算。它不等于多核,也不意味着普通 ADD 自动隔断字节之间的进位。
原书 ADD8/SUB8 表示四对字节,ADD16/SUB16 表示两对半字;ASX 把第二源高低半字交换后,高通道做加、低通道做减;SAX 则高通道做减、低通道做加。下面前缀决定具体数值处理:
| 基本操作 | S(有符号) | U(无符号) | Q(有符号饱和) | UQ(无符号饱和) | SH(有符号半分) | UH(无符号半分) |
|---|---|---|---|---|---|---|
| ADD8 | SADD8 | UADD8 | QADD8 | UQADD8 | SHADD8 | UHADD8 |
| SUB8 | SSUB8 | USUB8 | QSUB8 | UQSUB8 | SHSUB8 | UHSUB8 |
| ADD16 | SADD16 | UADD16 | QADD16 | UQADD16 | SHADD16 | UHADD16 |
| SUB16 | SSUB16 | USUB16 | QSUB16 | UQSUB16 | SHSUB16 | UHSUB16 |
| ASX | SASX | UASX | QASX | UQASX | SHASX | UHASX |
| SAX | SSAX | USAX | QSAX | UQSAX | SHSAX | UHSAX |
半分系列在扩展中间运算后按规则除 2/移位,不能先把窄结果截断丢掉进位再平均。例:无符号字节 250+10,正确半分为 130,而不是先回绕成 4 再除 2 得 2。
原书“Q/UQ 前缀的并行饱和运算都更新 Q”不准确。QADD8/QADD16 及相应并行 UQ 系列不改变 Q;SSAT/USAT、标量 QADD 等另按自身规则。S/U 非饱和系列的部分指令更新 GE,判据按有无符号、加减分别规定;不能将 GE 简化为普通“是否负数”位。Arm 作者对并行饱和标志的勘误说明
原表 SUB16 的有符号助记符缺 S,应为 SSUB16。知道命名构成并能解释 lane 独立性即达到本节基本要求,写优化 DSP 程序时再逐条查寄存器、溢出、GE/Q 细节。
第六章 · 第 35 节
M4 的半字乘加:BB、BT、TB、TT 选择哪两半
6.4.12 的乘法/MAC,表 6.40—6.41,第 36—37 页;习题 6.47
掌握要求:按两个后缀字母选择第一、第二源半字,理解有符号乘积与累加位宽。
背景为什么要讲:原书只给部分 DSP 乘加例子,学习命名和适用算法即可,不要求现在实现完整滤波器。
SMULxy 选择两个源的一个 16 位半字做有符号乘法,结果为 32 位;SMLAxy 再加一个 32 位累加数。x 对应第一源,y 对应第二源,B=Bottom(低半字),T=Top(高半字)。
| 后缀 | 第一源 | 第二源 |
|---|---|---|
| BB | 低 16 位 | 低 16 位 |
| BT | 低 16 位 | 高 16 位 |
| TB | 高 16 位 | 低 16 位 |
| TT | 高 16 位 | 高 16 位 |
例如 R1=0x0002FFFD、R2=0x00040005。R1 低半字按有符号是 −3,高半字为 2;R2 低为 5,高为 4。SMULBT 得 −3×4=−12;SMULTB 得 2×5=10。若累加数为 100,SMLABT 得 88。
系数和采样可打包到寄存器里,这些指令减少解包与中间操作,适合点积、滤波、相关等。对可能累加很多项的算法,仍要检查 32 位是否足够,必要时选择更长累加形式;部分 DSP 累加还会按规则影响 Q,不能只看名字就推所有标志。
第六章 · 第 36 节
打包和解包:要抽 byte0、byte2,不是最高字节
6.4.12 的表 6.42,第 37 页;联系习题 6.51、6.52
掌握要求:解释 PKHBT/PKHTB 与扩展组合的功能,能追踪哪部分保留、哪部分换入。
背景为什么要讲:SIMD 之前常要准备 lane,原书列举的几条均保留用途;旋转和扩展细节可查。
| 指令 | 主要结果 |
|---|---|
| PKHBT | 保留 Rn 低半字,与 Rm(可先 LSL)的高半字打包 |
| PKHTB | 保留 Rn 高半字,与 Rm(可先 ASR)的低半字打包 |
| SXTB16 | 取 Rm 的 byte0、byte2,各符号扩展成半字,组成结果 |
| SXTAB16 | 上述两份扩展值分别与 Rn 两个半字相加 |
| UXTAH | Rm(可先 ROR)低半字零扩展,再与 Rn 相加 |
例:Rn=0x11223344,Rm=0xAABBCCDD。PKHBT 不移位时得 AABB3344;Rm 先 LSL #16 后则取其高半字 CCDD,结果 CCDD3344。PKHTB 若 Rm 先 ASR #16,低半字为 AABB,结果 1122AABB。
SXTB16 选 bit[7:0] 与 bit[23:16],不是最低与最高字节。输入 0x118022FF,两个源字节分别 FF、80,扩展后结果 0xFF80FFFF;原书“最高字节”表述应纠正。可选 ROR #8/#16/#24 会先改变源布局,再按相应位置抽取。
“打包”不是压缩算法,也不自动保护数值范围;它是在寄存器位段中摆放数据。要按 lane 类型决定符号/零扩展,否则后面的 SIMD 即使算得完全正确,处理的数也已经不是你想要的那个数。
第六章 · 第 37 节
浮点指令和星号题:值转换与位串搬运不同
6.4.12 的浮点表 6.43,第 37—38 页;习题 6.48、6.49、6.51、6.52
掌握要求:认识 M4F 单精度运算、传送与转换,解释浮点比定点增加了哪些处理步骤。
背景为什么要讲:本章只要求理解表中用途与硬件区别;具体浮点编码、舍入和异常模式可查规范。
带并启用 FPU 的 M4 支持相应 FPv4-SP-D16 指令,普通 M4 不保证有 FPU。S0—S31 是 32 位寄存器视图,D0—D15 为重叠的 64 位视图。可以传送 64 位数据,不等于支持 F64 算术或任意单/双精度转换。
| 原表涉及的指令组 | 意义与正确理解 |
|---|---|
| VADD.F32 / VMUL.F32 / VSQRT.F32 | 单精度加、乘、平方根 |
| VMLA.F32 | 乘并累加,按该指令的中间舍入规则 |
| VFMA.F32 | 融合乘加,组合计算后一次舍入,与 VMLA 不保证逐位相同 |
| VMOV | 传送位串;不自动完成整数到浮点的数值转换 |
| VCVT 等转换 | 按支持的类型做数值/格式转换,如整数与单精度、部分半精度格式 |
| VLDR / VSTR | 浮点寄存器与内存间加载/存储,S/D 指定相应传输视图 |
| VPUSH / VPOP | 浮点寄存器组压栈/出栈 |
| VSTMIA / VSTMDB | 连续多存储,按列表和相应地址模式处理 |
| VMRS | 从 FPSCR 等允许的浮点系统状态读出,按语法选择目标 |
原表写 VMRS.F32 Rt,FPCSR 不规范,常见正确写法为 VMRS R0,FPSCR。浮点比较产生的状态与 CPU 的 APSR 也不是自动完全同一个东西,必要时用规定方式传递;涉及 NaN 的浮点条件不能直接套第 06 节整数比较表。
星号题为什么还要讲浮点电路
定点的比例因子事先约定好,相同尺度的加减可直接用整数电路;浮点的每个数自带符号、指数和有效数。浮点加法需比较指数、对阶、有效数运算、规格化、舍入,再处理零/无穷/NaN 等情况;乘法还需指数运算与结果规格化。差别不仅是“多放一个小数点”。
整数与定点乘积共用电路,仍要按缩放选结果位并处理舍入/溢出;扩展指令则把不同宽度的 signed/unsigned 值送入更宽运算而保持原数值意义。知道这些因果,就能回答原章最后几道星号题,不必在本章手绘完整浮点加法器。
每个原书小节都有去处
完整覆盖表
| 原书 | 内容 | 对应讲解 |
|---|---|---|
| 6.1 | 指令系统、A32/A64/T32、支持表 | 01 |
| 6.2.1 | 16 位编码与长度前缀 | 02 |
| 6.2.2 | 32 位分层编码分类 | 02 |
| 6.2.3 | UAL、操作数与后缀 | 03 |
| 6.2.4 | APSR、条件码 | 05、06 |
| 6.2.5 | MOV 编码和 IT 示例 | 04、07 |
| 6.3.1 | 立即数寻址与限制 | 08 |
| 6.3.2 | 寄存器直接寻址 | 09 |
| 6.3.3 | 寄存器间接寻址 | 09 |
| 6.3.4 | 寄存器移位操作数 | 09、23 |
| 6.3.5 | 基址与三种偏移 | 10、11 |
| 6.3.6 | 前变址/写回 | 10 |
| 6.3.7 | 后变址 | 10 |
| 6.3.8 | 多寄存器 IA/IB/DA/DB | 12 |
| 6.3.9 | SP 与栈别名 | 13 |
| 6.3.10 | PC 相对、ADR、文字池 | 14 |
| 6.4.1 | MOV/MOVW/MOVT/MVN/MRS/MSR | 15 |
| 6.4.2 | 九组访存:宽度、三种索引、缩放、多传输、栈、PC、非特权、独占 | 10、11、12、13、14、16、17、18 |
| 6.4.3 | 算术、MAC、长乘和除法 | 19、20、21 |
| 6.4.4 | 逻辑与位掩码 | 22 |
| 6.4.5 | 五种移位与桶形移位器 | 23 |
| 6.4.6 | 扩展、旋转与字节反转 | 24 |
| 6.4.7 | 位域、CLZ、RBIT | 25 |
| 6.4.8 | 比较/测试 | 26 |
| 6.4.9 | 分支、调用、CBZ、IT、跳转表 | 07、27、28、29 |
| 6.4.10 | SSAT/USAT 与波形削顶 | 30 |
| 6.4.11 | 屏障、休眠、系统异常与调试 | 31、32、33 |
| 6.4.12 | SIMD 变体、额外 MAC、打包和浮点 | 34、35、36、37 |
| 6.5 | 52 道习题,含 6.46—6.52 星号题 | 逐题答题卡 → |
第 6.1 节的历史支持表、二进制分类入口、冷门指令变体、低功耗与浮点背景均保留用途及掌握边界;重复的访存宽度/索引表用统一规则解释。现阶段不要求自行设计完整锁、OS 服务、浮点加法器或高性能 DSP 库,这些背景用于理解后续程序,不省略它们的存在与原因。
先自己算,再展开
十二道综合短自测
- 第一个半字 F04F 与 E001 分别表示几字节指令?还需检查什么?
- 32 位 ADDS 的 FFFFFFFF+1,结果及 N/Z/C/V 分别是什么?
- CMP 的 80000000−1,有符号 LT 和无符号 HI 哪个成立?为什么不能只看 N?
- MOVW R0,#0x5678 后 MOVT R0,#0x1234 得什么?反过来为何不同?
- R1=20000100,三种 LDR [R1,#8]、[R1,#8]!、[R1],#8 分别独立执行,写出 EA/新 R1。
- STMDB R9!,{R2,R4,R7},原 R9=20000100,三个地址和新 R9 是多少?
- 指令在 08001002,LDR R0,[PC,#0xC] 的字面量地址是多少?
- 同一内存字节 88 分别经 LDRB/LDRSB 读入,两个结果及数值意义是什么?
- 输入 80000000,ASR #1 和 ROR #1 分别是什么?RRXS 输入 80000001、旧 C=0 呢?
- R0=12345678,BFC #4,#8 得什么?R1=AABBCCDD,BFI R1,R0,#8,#16 又得什么?两次都使用原 R0。
- TBB 在 08000100,表基址 08000200,下标 2,读得表项 12(十六进制)。目标是多少?
- M4 的 UADD8 输入 00FF00FF 与 00010001 得什么?UQADD8 又得什么?后者会把 Q 置 1 吗?
展开答案与过关要求
- 分别为 4 字节、2 字节;长度识别后还需核对剩余字段/内核支持,不能由前缀证明合法。
- 00000000,N=0、Z=1、C=1、V=0。无符号进位,有符号 −1+1 不溢出。
- LT、HI 都成立,分别回答 signed/unsigned;结果 7FFFFFFF,N=0、V=1、C=1、Z=0。
- 12345678;MOVW 会清高半,顺序反过来最后只剩 00005678。
- (20000108,20000100)、(20000108,20000108)、(20000100,20000108)。
- R2→200000F4,R4→200000F8,R7→200000FC,新 R9=200000F4。按寄存器号对应递增地址。
- 08001010:先 A+4=08001006,再按字对齐为 08001004,最后加 C。
- 00000088=136 与 FFFFFF88=−120,后者按 bit7 扩展。
- C0000000 与 40000000;RRXS 得 40000000,C=1。
- BFC 得 12345008;BFI 得 AA5678DD,插入的是原 R0 低 16 位。
- 08000128:基准为分支地址+4,再加表项×2;不是表基址+偏移。
- UADD8 得 00000000,逐字节进位信息可见 GE=0101;UQADD8 得 00FF00FF,Q 保留,不能因发生并行饱和就断言置 Q。
过关要求:EA/基址、标志、寄存器结果、跳转目标至少能写出中间过程。出错时回对应讲解,不必重新背整张指令表;星号题能用自己的话解释缩放、lane 与数值解释的原因即可。
原题 [6-1]—[6-52]
52 道习题答题卡
保留原题顺序并归纳题意。先独立作答,再展开核对;原题 6.9 完整填表、四种 STM 模式与最后七道星号题均包含。对于跨内核形式或缺少访问条件的题,限定条件也是答案的一部分。
习题 6.1 / 四个基本名词
解释指令、指令系统、汇编语言、指令字。
展开答题要点
机器指令规定处理器执行的操作;指令系统是支持的指令集合;汇编语言用助记符等描述操作并由工具转换;指令字是对应的二进制编码。伪指令/宏不保证一行对应一条硬件指令。
习题 6.2 / Arm 指令集
Arm 有哪些主要指令集?
展开答题要点
本章沿用 A32、A64、T32 名称:A32/A64 主要为 32 位定长编码,T32 混合 16/32 位;具体内核只实现其架构允许部分,M3/M4 不执行 A32/A64。不能把原书历史表当成所有现今产品完整列表。
习题 6.3 / 区分指令长度
T32 怎样区分 16/32 位编码?
展开答题要点
看当前指令第一个半字的 bit[15:11]:11101、11110、11111 为 32 位指令前半,其余走 16 位译码入口。第二半字不再作为新指令头;识别长度不等于全部编码合法。
习题 6.4 / Word 与 Halfword
本章中的字、半字怎样定义?
展开答题要点
在这里的 32 位 M profile 语境:字=32 位=4 字节,半字=16 位=2 字节;字节为 8 位,双字为 64 位。数据宽度不等于每条指令编码长度。
习题 6.5 / 二进制编码格式
解释指令二进制编码格式。
展开答题要点
规定指令位串中哪些位表达操作、寄存器、立即数、地址方式、条件等,以及如何组合/解释;实际字段由具体指令编码决定,不是一套固定字段能覆盖所有指令。
习题 6.6 / 指令的要素
一条指令需要哪些信息?
展开答题要点
至少要能确定操作及必要的操作数/地址、结果位置和控制流等信息;可显式编码也可隐含,例如 PUSH 使用 SP,普通顺序指令的后继由 PC/长度得到。不是每条都显式列所有源目标。
习题 6.7 / 条件执行
解释条件执行指令。
展开答题要点
按 APSR 条件决定是否产生相应效果。M3/M4 普通条件数据处理常由 IT 块控制,块内后缀必须匹配;条件 B 可直接按其编码检查标志。条件执行不一定改变 PC。
习题 6.8 / N Z C V Q
说明这些标志取值。
展开答题要点
N 为结果最高位,Z 为结果全零;加法 C 为无符号进位,减法 C=1 为无借位;V 为有符号溢出;Q 由相关饱和/DSP 指令按规则置位且常为黏性,未饱和不自动清旧 Q,并非所有带 Q 的并行指令都改它。
习题 6.9 / 19 个助记符填表
补全原题的助记符、英文和中文功能表。
展开答题要点
| 助记符 | 英文 | 中文 |
|---|---|---|
| ADD | Add | 加法 |
| ADC | Add with Carry | 带进位加法 |
| SUB | Subtract | 减法 |
| MUL | Multiply, 32-bit result | 乘法,取低 32 位 |
| SDIV | Signed Divide | 有符号除法 |
| MLA | Multiply with Accumulate | 乘并累加 |
| MLS | Multiply and Subtract | 从累加源减去乘积 |
| ASR | Arithmetic Shift Right | 算术右移 |
| LSR | Logical Shift Right | 逻辑右移 |
| MOV | Move | 寄存器/立即数等数据移动 |
| LDR | Load Register with word | 从内存加载字到寄存器 |
| STR | Store Register word | 将寄存器字写内存 |
| LDM | Load Multiple registers | 从连续内存加载多个字 |
| STM | Store Multiple registers | 多个寄存器字写连续内存 |
| B | Branch | 跳转 |
| BL | Branch with Link | 跳转并建立返回链接 |
| BLX | Branch with Link and Exchange | 按相应寄存器目标跳转并建立链接 |
| CMP | Compare | 相减比较,只保留标志 |
| IT | If-Then | 控制后续条件执行块 |
习题 6.10 / 寻址术语
解释寻址、操作数/指令寻址、立即数与几类寻址。
展开答题要点
寻址是按编码信息确定所需操作数或后继指令位置;立即数内含在指令,寄存器直接使用其中数值,寄存器移位先形成变换后的操作数;间接使用寄存器值作为地址读内存。操作数寻址与下一条指令的寻址要分开。
习题 6.11 / 立即数限制
M3 指令的立即数有什么限制?
展开答题要点
受每种编码的字段与扩展算法限制。窄 imm8、MOVW/T 的 imm16、ADDW 等 imm12、Thumb 修饰立即数、访存缩放偏移规则不同。0x07FC 不适用于某种修饰立即数不表示它对 MOVW/ADDW 也非法。大常量可拆成 MOVW/T 或用 LDR= 伪指令。
习题 6.12 / 操作数的位置
操作数可放在哪里?
展开答题要点
指令自身的立即数、一般/特殊寄存器、数据内存或代码附近常量池等。在哪儿与怎么取由指令定义决定;地址本身也可以是一个寄存器数值,不能把它与目标内存内容混淆。
习题 6.13 / 直接与间接
比较寄存器寻址和寄存器间接寻址。
展开答题要点
直接把寄存器值当操作数,如 MOV R0,R1;间接把寄存器值当内存地址,如 LDR R0,[R1]。前者不因此访问数据内存,后者读取目标内容。
习题 6.14 / 偏移量形式
寄存器偏移/基址偏移的 offset 有几种形式?
展开答题要点
原理上常见立即数、寄存器值、缩放寄存器值。M3/M4 的相应普通访存支持特定 LSL #0—3 等形式;正负、宽度和写回限制按编码,不能将全部数据处理移位都挪来。
习题 6.15 / 三种索引对照
比较偏移、前变址、后变址。
展开答题要点
设原基址 B、偏移 d:普通偏移 EA=B+d、基址不变;前变址 EA=B+d、基址写回 B+d;后变址 EA=B、基址写回 B+d。先写本次 EA,再写执行后基址。
习题 6.16 / 感叹号的意义
解释 [Rn,offset]! 中的 !。
展开答题要点
表示该编码支持的基址写回,访问地址 B+offset 还被更新到 Rn。它不表示重复访问、取反或改变数据宽度;M3/M4 的寄存器偏移 LDR 不可随意加 !。
习题 6.17 / 两条 LDR 的地址
解释 LDR R0,[R1,#0xA];LDR R0,[R1,R4]。
展开答题要点
第一条读 Mem32[R1+0xA] 到 R0;第二条读 Mem32[R1+R4] 到 R0。普通偏移都不写回基址/偏移寄存器。0xA 可能导致字未对齐,要另查内存类型与配置,算出 EA 不等于保证访问成功。
习题 6.18 / 寄存器写回形式的边界
解释 LDR R0,[R9,R1];LDR R0,[R1,R3]!。
展开答题要点
第一条 EA=旧 R9+旧 R1,加载到 R0,不写回。第二条想表达 EA=旧 R1+R3 且写回 R1,但该寄存器偏移带写回不是 M3/M4 的对应 T32 编码;可显式 ADD R1,R1,R3 后 LDR R0,[R1],说明两条的语义与非原子性。
习题 6.19 / 三种 0xB 索引
解释 [R1,#0xB]、[R1,#0xB]!、[R1],#0xB 三种 LDR。
展开答题要点
若各自从同一 B 开始:EA/新 R1 依次为 (B+0xB,B)、(B+0xB,B+0xB)、(B,B+0xB)。若三行连续执行,第三行使用第二行已写回的基址,最终 R1=B+0x16。还需核对字未对齐支持。
习题 6.20 / LDM 语法
解释 LDM{addr_mode} Rn{!},{registers}。
展开答题要点
LDM 从连续内存读多个字;addr_mode 决定整个地址区间,Rn 为原基址,! 为相应写回,列表确定寄存器集合与数量。最低寄存器号对应最低地址;M3/M4 使用相应 IA/DB 编码,具体列表和重叠还有限制。
习题 6.21 / 四种 STM 模式
解释 STMIA R9,{R1-R4};STMIB/STMDA/STMDB R9!,{R1-R4}。
展开答题要点
设 B 为原 R9,列表按 R1、R2、R3、R4 对应以下递增地址:
| 写法 | R1—R4 对应地址 | 新 R9 |
|---|---|---|
| IA,无 ! | B、B+4、B+8、B+12 | B |
| IB,有 ! | B+4、B+8、B+12、B+16 | B+16 |
| DA,有 ! | B−12、B−8、B−4、B | B−16 |
| DB,有 ! | B−16、B−12、B−8、B−4 | B−16 |
IB/DA 是经典模型解释,不是 M3/M4 可直接使用的相应 T32 指令。
习题 6.22 / STMFD 与 STMDB
比较 STMFD SP!,{R1-R8} 与 STMDB SP!,{R1-R8}。
展开答题要点
相应满递减存储别名,8 字共 32 字节,最低 R1 在旧 SP−32,最高 R8 在旧 SP−4,新 SP=旧 SP−32。可用寄存器和编码仍须满足目标限制。
习题 6.23 / LDMFD 与 LDMIA
解释原题 LTMFD SP!,{R1-R8} 与 LDMIA SP!,{R1-R8}。
展开答题要点
LTMFD 为笔误,应 LDMFD。满递减栈弹出相应使用 LDMIA:R1 从旧 SP,直到 R8 从旧 SP+28,新 SP=旧 SP+32。不能因为名字 FD 就改用 DB 弹出。
习题 6.24 / 栈与 PC 相对基址
这两类寻址基址分别在哪里?
展开答题要点
栈寻址使用当前 SP(按模式/CONTROL 映射 MSP/PSP);PC 相对用架构规定的 PC 值,Thumb 常为指令地址+4,literal/ADR 等还需按字对齐。不能把当前源码行地址直接当全部相对寻址基准。
习题 6.25 / 指令类别
一般指令集包含哪些类别?
展开答题要点
数据传送、存储器加载/存储、算术、逻辑、移位、格式转换、位域、比较测试、分支调用、系统/异常/屏障/低功耗等;M4 还涉及 DSP/SIMD、打包、额外乘加及可选 FPU。理解每类解决什么问题,无需仅靠数量背目录。
习题 6.26 / 加载字节到 R8
从 R1 数值+3 的地址读取一个字节到 R8。
展开答题要点
LDRB R8,[R1,#3],零扩展。若题目明确要求有符号字节则 LDRSB。这里题面的 [R1] 按寄存器内容理解,不是先读 Mem[R1] 再加 3。
习题 6.27 / 加载半字到 R8
从 R1 数值+4 的地址读取一个半字到 R8。
展开答题要点
LDRH R8,[R1,#4],零扩展;有符号要求则 LDRSH。基址不写回,实际对齐与权限仍检查。
习题 6.28 / 加载字到 R8
从 R1 数值+8 的地址读取一个字到 R8。
展开答题要点
LDR R8,[R1,#8],读取 32 位字,不写回 R1。后缀宽编码与数据宽度不是同一维度。
习题 6.29 / bit2 翻转
把 R0 的第 2 位取反。
展开答题要点
按 bit0 为最低位:EOR R0,R0,#4。掩码=1<<2,不改其他位;重复做两次恢复原值。
习题 6.30 / bit2 置 1
把 R0 的第 2 位置 1。
展开答题要点
ORR R0,R0,#4,掩码对应位为 1 的位置设 1,其余保留。
习题 6.31 / bit2 清 0
把 R0 的第 2 位置 0。
展开答题要点
BIC R0,R0,#4,或用可合法构造的取反掩码做 AND。BIC 不等于把整个 R0 清零。
习题 6.32 / R0 和 R1 按位与
写出 R0 与 R1 按位与指令。
展开答题要点
若结果要求留在 R0:AND R0,R0,R1。若要保留两个源,可用另一目标,如 AND R2,R0,R1;题面未规定结果位置时需说明选择。
习题 6.33 / AND ORR ORN EOR
解释这四条的差别。
展开答题要点
AND=A&B;ORR=A|B;ORN=A|~B;EOR=A XOR B。ORN 不是 ~(A|B),EOR 的公式不能像原表误写成 OR。
习题 6.34 / 符号扩展
什么是符号扩展?
展开答题要点
窄有符号补码转更宽补码时,复制原数据最高符号位填充高位以保持值。8 位 0x88 扩成 32 位为 FFFFFF88(−120);零扩展则 00000088(136)。正的有符号数也补 0。
习题 6.35 / MOV 能否读内存
MOV 可直接从任意内存单元传到寄存器吗?
展开答题要点
本章 M3/M4 普通 MOV 不直接访问任意数据内存;用 LDR 系列。MOV 可搬寄存器/立即数;LDR Rd,=常量又是伪指令,不能把这几种形式混淆。
习题 6.36 / LSR 与 ASR
两种右移有什么区别?
展开答题要点
LSR 高位补 0,适合相应无符号位处理;ASR 高位复制符号位。80000000 右移 1 分别为 40000000、C0000000。ASR 负数移位的舍入也不能随意等同 SDIV 向零截断。
习题 6.37 / LSR 与 ROR
两者的区别?
展开答题要点
LSR 丢掉低端移出位并在高端补 0;ROR 将移出位循环回高端。00000001 右移 1:LSR 得 0,ROR 得 80000000。
习题 6.38 / ROR 与 RRX
两者的区别?
展开答题要点
ROR 在 32 位内部循环,不以原 C 回填;RRX 固定右移 1,原 C 进入 bit31,原 bit0 可成为新 C。是否实际写 APSR 要看 S 等规则。
习题 6.39 / CMP 与 CMN
两者的区别?
展开答题要点
CMP 内部 A−B,CMN 内部 A+B,按相应算术规则更新 NZCV且不保存数据结果。CMN 不是把 CMP 标志逐位取反。
习题 6.40 / 调用为何要 Link
为什么普通函数调用适合 BL/BLX,而普通 B 不方便?
展开答题要点
BL/BLX 除了跳转还建立 LR 返回链接,普通 B 不做这个工作;嵌套调用需保护原 LR。尾调用等场景可以有意使用 B,因此不能绝对说 B 永不能与函数控制流有关。
习题 6.41 / 条件分支与 MOVEQ
比较 B/BL 的条件控制与 MOVEQ。
展开答题要点
条件分支满足后改 PC,BL 相应还建立 LR;MOVEQ 在 IT 等上下文里只条件更新目标数据,PC 顺序推进。T32 的独立 Bcond 与 IT 控制的 BL/普通指令编码不同,不能套 A32 一般化。
习题 6.42 / CBZ 与 CMP+BEQ
CBZ 相比 CMP 后 BEQ 有何异同?
展开答题要点
都可实现寄存器为零则跳;CBZ 不改 APSR、仅 R0—R7、短前向范围且有上下文限制。CMP+BEQ 更新标志且编码选择更灵活,可用于其他距离/方向,但不是所有条件下代码大小时延都相同。
习题 6.43 / 饱和与溢出
解释两者差别。
展开答题要点
溢出是超出表示范围的现象;饱和是把结果夹到上下限的处理方法,截断回绕是另一种结果。饱和仍有精度/波形失真,不能恢复之前已被窄运算丢掉的数学结果。
习题 6.44 / 屏障
定性解释屏障指令作用。
展开答题要点
处理访问顺序、完成和后续执行上下文:DMB 约束相关访问顺序;DSB 等待规定先前操作完成;ISB 同步后续取指执行上下文。顺序执行内核也可能因写缓冲需要它们;volatile 不替代。
习题 6.45 / 非特权访存的用途
为何提供非特权加载/存储?
展开答题要点
让特权系统在替用户访问缓冲区时按用户权限规则执行该次访问,避免仅凭自身高权限泄露/写坏受限区域。LDRT/STRT 等不让用户提权,也不永久改变当前模式;还需完整错误和并发处理。
习题 6.46 / SIMD 背景与思想 *
说明 SIMD 为什么出现、怎样工作。
展开答题要点
音频、图像等包含大量相同操作的短数据,单个大寄存器可容纳多个 lane,一条指令在 lane 内并行处理,减少重复指令与打包开销。它与普通大整数跨字节进位、多核都不同;先准备合适类型与布局。
习题 6.47 / MAC 适合哪些算法 *
乘并累加适用什么算法?
展开答题要点
点积、FIR 滤波、相关、卷积、矩阵乘法等反复求 Σ(xᵢhᵢ) 的算法。注意系数/样本符号、定点缩放和累加宽度,不能因有 MAC 就忽略溢出。
习题 6.48 / 整数与定点共电路 *
什么条件下能用相同电路做整数与定点运算?
展开答题要点
位串运算与缩放解释相容时可共用:相同小数位尺度的加减可直接用整数电路;乘法的结果小数位数相加,须再缩放/舍入;不同尺度需先对齐,并处理有无符号与溢出。
习题 6.49 / 浮点与定点电路 *
浮点运算电路比定点多什么?
展开答题要点
浮点需处理符号、指数与有效数,对阶、规格化、舍入以及特殊值/状态;加法与乘法的指数处理不同。定点比例因子预先约定,但仍需溢出与缩放,不是浮点只多存一个小数点。
习题 6.50 / 有无符号乘除 *
为何常用不同指令处理有无符号乘除?
展开答题要点
同位串解释不同,完整乘积高部与除法商可能不同;低 32 位乘积是模 2³² 的结果,可由同一 MUL 处理,但 UMULL/SMULL 及 UDIV/SDIV 要区分。不能说每种情况下都必须两套不同低位乘法。
习题 6.51 / DSP 为何需要扩展 *
DSP 为什么需要符号扩展和补零?
展开答题要点
短采样/系数搬到宽寄存器计算时要保持原数值。signed 窄值应复制符号位,unsigned 应补零;否则负采样会误作很大的正数或无符号数误作负数,后续 MAC 再准确也处理错了输入。
习题 6.52 / 扩展为何分 signed/unsigned *
为什么数据扩展用不同指令?
展开答题要点
源位串本身不包含独立的类型标签,相同 0x80 可能代表 −128 或 128。符号扩展与零扩展需要不同高位填充规则,指令由程序员/编译器明确选择;不由处理器凭值自动猜类型。
对照原书时别机械照抄
最需要纠正和限定的地方
| 易误解处 | 核对后的理解 | 定位 |
|---|---|---|
| 所有立即数共用一种旋转规则 | 按各指令编码判断,MOVW/T、ADDW 与修饰立即数不同 | 08 |
| ADD 可以直接带 [R2] | 先 LDR,再做寄存器算术 | 09 |
| 寄存器偏移 LDR 随意加 ! | M3/M4 对应编码不支持该组合 | 10 |
| DB 从 R1 对应 B−4 开始逆排 | 先算完整区间,最低寄存器号对应最低地址 | 12 |
| STMIB/STMDA 是 M3/M4 现成指令 | 作为经典模型解释,实际 T32 仅相应 IA/DB | 12 |
| LTMFD、REVH、CNBZ | 应分别核对为 LDMFD、REV16、CBNZ | 13、24、28 |
| C=借位;LE=无符号 ≤ | 减法 C=无借位;LE 是有符号 ≤ | 05、06、19 |
| EOR 用或运算公式;MVN 是数学取负 | EOR 为异或;MVN 为逐位取反 | 15、22 |
| BFC 示例出现冲突数值 | 12345678 清 bit4—11 得 12345008 | 25 |
| B 普通范围 ±1 MB;BEQ 可直接取寄存器目标 | 区分窄/宽/条件编码,寄存器目标用相应 BX | 27、28 |
| TBB 从表基址加目标偏移 | 表地址由 B 定,目标从指令后 A+4 起算 | 29 |
| USAT 把输入先当无符号;所有 Q 前缀改 Q | USAT 输入按有符号;并行 Q/UQ 系列与标量规则分开 | 30、34 |
| SXTB16 抽最低/最高字节 | 抽 byte0 与 byte2,各扩展为半字 | 36 |
| VMRS.F32 / FPCSR;64 位传送等于双精度算术 | 核对 VMRS R0,FPSCR;M4F 数据视图与算术能力分开 | 37 |
学完后应该能独立做到
把五张“结果单”写出来
- 数据结果:传送、扩展、算术、逻辑、移位和位域的最终 32 位值。
- 地址结果:EA 与写回分开,多寄存器按完整区间排列,文字池按正确 PC 基准。
- 标志结果:NZCV、黏性 Q 与 SIMD GE 按具体指令判断,条件码按数值类型选择。
- 控制流结果:条件块执行哪几条,BL 如何建立/覆盖 LR,TBB 怎样形成目标。
- 合法性结果:内核支持、立即数字段、寄存器组合、对齐/权限以及工具别名都核对。
基础题需要不看示范写出中间过程;星号题要能解释并行、缩放、扩展与浮点步骤。完整操作码表和冷门变体以查读为主。下一章把这些操作组织成程序,并接上开发工具、伪指令与 C/汇编调用。