在早期的Arm处理器中,使用了32位指令集,称为Arm指令集,这个指令集具有较高的运行性能。与8位和16位的处理器相比,它提供了更大的程序存储空间;但是,也带来了较高的功耗。
1995年,16位的Thumb-1指令集首先应用于ARM7TDMI处理器,它是Arm指令集的子集。与32位的RISC结构相比,它提供了更好的代码密度,将代码长度减少了30%,但是其性能也降低了20%。通过使用多路复用器,它能与Arm指令集一起使用,如图5.15所示。
图5.15 Thumb指令选择
Thumb-2指令集由32位Thumb指令和最初的16位Thumb指令组成,与32位Arm指令集相比,其代码长度减少了26%,但保持相似的运行性能。
在Cortex-A9处理器中,其指令分为以下4类。
(1)数据处理操作,如ALU操作ADD。
(2)存储器访问,从存储器加载及保存到存储器中。
(3)控制流,如循环、goto、条件代码和其他程序流控制。
(4)系统,如协处理器和模式变化等。
本节介绍指令集的一些基础知识。
1.常数和立即数
前面提到,Arm指令为32位长度,而Thumb指令为16位长度,这就意味着程序员不能在操作码中编码一个任意的32位值。
在Arm指令集中,由于操作码比特位用于标志条件码,指令本身和所使用的寄存器只有12位可用于表示立即数。因此,程序员需要考虑如何使用这12位。不同于用12位指定-2048~+2047的范围,而使用其中的8位作为常数,其余4位作为旋转的值。旋转的值使能8位常数值向右旋转,范围为0~30,步长为2,即0、2、4、6、8等。
这样,就可以有立即数0x23或0xFF。程序员也可以产生其他有用的立即数,如外设或存储器块的地址。例如,通过0x23 ROR 8指令可以产生一个0x23000000常数。但是,对于其他常数,如0x3FF,就不能只使用一条指令。对于这些值,程序员或者使用多条指令,或者从存储器加载它们。程序员不需要专门关心这个,除非汇编器给出相关的错误信息。取而代之的是,程序员可以使用汇编语言伪指令产生所需的常数。
在Thumb指令集中,一条指令中的常数可以被编码为以下几种形式。
(1)对一个8位数向右旋转偶数位。
(2)0x00xy00xy.
(3)0xxy00xy00.
(4)0xxyxyxyxy.
其中,x和y为十六进制字符,范围为0x00~0xFF。
MOVW指令(宽移动)将16位立即数移动到寄存器中,同时将目标寄存器的高16位补0。MOVT指令(向上移)将16位立即数移动到给定寄存器的高16位上,而不改变低16位的值。这允许一个MOV32伪随机指令能够构造任何一个32位常数。这里汇编器可以提供更多的帮助,如前缀“:upper16:”和“:lower16:”用于从32位数中提取出相应的16位数。例如:
尽管要求使用两条指令,但是不要求使用任何额外的空间来保存该常数,也不要求从存储器中读一个数。
用户也可以使用伪指令LDR Rn,=<const>或LDR Rn,=label。这只是用于以前处理器的选项,因为当时没有MOVW和MOVT指令。汇编器将使用最好的顺序在指定的寄存器内产生这些常数(来自一个文字池,MOV、MVN或LDR)。文字池是一个代码段内的常数区域。可以通过汇编指令LTORG(或使用GNU工具的.ltorg)控制文字池的位置。被加载的寄存器可以是程序计数器,会引起分支(跳转)。在进行绝对寻址时,这是非常有用的。很明显,这将导致与位置相关的代码。常数值可以由汇编器或链接器确定。
Arm工具也提供相关的伪指令ADR Rn,=label。这使用PC相关的ADD或SUB,通过一条指令将符号地址放到指定的寄存器中。如果要产生更大的地址,则使用ADRL。这要求使用两条指令,但会给出更大的范围,用于为独立于位置的代码生成地址,但是仅限于在相同的代码段内。
2.有条件执行
Arm指令集的一个特色就是几乎所有的指令都可以是有条件执行的。在大多数其他架构中,只有分支或跳转才是有条件执行的。
下面给出一个例子,在两个值之间找到更小的值(这两个值分别在R0和R1中)并将结果放在R2中,如代码清单5.4所示。
代码清单5.4 分支代码清单(GNU)
对于上面的代码,重新使用有条件的MOV指令来描述,没有分支,如代码清单5.5所示。
代码清单5.5 使用有条件的MOV指令代码
在以前的Arm处理器中,后面的代码不但长度变短,而且执行速度变快。然而,这样的代码运行在Cortex-A9处理器上时速会变慢,因为内部指令的依赖性将使得它比分支有更长的停止时间,分支预测能降低或潜在地消除分支代价。
这种类型的编程依赖一个事实,即一些指令可选择设置标志。如果在代码清单5.5中自动设置标志,那么程序可能不会正确工作。加载和保存指令从不设置标志。然而,对数据处理操作来说,可以有选择。默认在这些操作中设置保护标志。如果指令使用后缀S(如使用MOVS而不是MOV),则指令将设置标志。通过专用的程序状态寄存器操作命令MSR来设置标志。一些指令根据ALU的结果设置进位标志,而其他指令则基于桶形移位寄存器(在一个周期内,将一个指定的数据字移动指定的位数)设置进位标志。
在if-then(IT)指令中引入Thumb-2技术,提供了有条件执行方式,用于最多4条连续的指令。条件可能是相同的,或者是相反的。IT块内的指令必须指定所用的条件码。IT是16位指令,根据ALU标志的值,通过使用条件后缀,几乎所有的Thumb指令都可以有条件执行。指令的语法格式为IT{x{y{z}}},这里的x、y和z为IT块指定条件开关,或者是T(Then),或者是E(Else)(如ITTET),如代码清单5.6所示。
代码清单5.6 ITTET的使用
例如,汇编器自动产生IT指令,而不是人工编写代码。16位指令通常修改条件码的标志,但不在IT块内这样做,除了CMP、CMN和TST,它们只设置标志。在IT块内限制所使用的指令。在IT块内可以发生异常,当前的if-then状态被保存在CPSR中,并被复制到异常入口的SPSR中。这样,当从异常返回时,可以继续正确地执行IT块。
某些指令总是设置标志,但是没有其他作用。这些指令是CMP、CMN、TST和TEQ,它们与SUBS、ADDS、ANDS和EORS类似,但是ALU的计算结果只用于更新标志,而不把结果放在寄存器中。表5.3中的条件码后缀可以用于大多数指令。
表5.3 条件码后缀
3.状态标志和条件码
前面提到,在CPSR中包含4个状态标志,即Z(零)、N(负)、C(进位)和V(溢出),如表5.4所示。
表5.4 CPSR的状态标志
如果一个无符号操作使得32位结果寄存器溢出,则设置C标志。该比特位可以用于通过32位操作来实现64位或更长的算术运算。
V标志和C标志的操作相同,但是对有符号数来说,0x7FFFFFFF表示最大的有符号整数。如果给这个数加2,则结果为0x8000001,为最大的负数。设置V比特位,表示从比特位30到比特位31的上溢或下溢。
算术运算和逻辑操作是Arm核最基本的功能。乘法操作被认为是其中的特殊情况。例如,乘法操作使用略微不同的格式和规则在核内专用的单元上执行。
Arm核只执行寄存器上的数据处理操作,不会直接在寄存器上执行数据处理操作。绝大部分的数据处理操作指令都使用一个目的寄存器和两个源操作数,其基本格式如下:
Cortex-A9处理器提供的数据处理指令如表5.5所示。
表5.5 Cortex-A9处理器提供的数据处理指令
注:比较和测试指令(标志设置指令)只修改CPSR,没有其他作用。
1.操作数和桶形移位寄存器
对所有的数据处理指令来说,第一个操作数总是寄存器;而第二个操作数则比较灵活,可以是立即数(#x),以及一个寄存器(Rm)或被一个立即数移位的移位寄存器,或者寄存器“Rm,shift#x”,或者“Rm,shift Rs”。这里有5种移位操作,即逻辑左移(LSL)、逻辑右移(LSR)、算术右移(ASR)、向右旋转(也称循环右移ROR)、向右旋转扩展(RRX)。
当执行右移操作时,在寄存器的顶端留出空余位置。在这种情况下,需要程序员区分逻辑移位和算术移位。逻辑移位的高位补零;而算术移位则用符号位,即比特位31填充空出来的高位。因此,算术右移操作可用于有符号的值,而逻辑右移操作则用于无符号的值。在执行左移操作时,总是在最低有效位位置插入0。
因此,与很多汇编语言不同的是,Arm汇编语言不要求显式移位指令。取而代之的是MOV指令,可用于移位和旋转。R0=R1>>2用MOV R0,R1,LSR#2等效实现。通常将移位和ADD、SUB或其他指令组合使用。例如,将R0乘以5,可以写成如下形式:
即表示R0=R0+(4×R0)。对右移来说,提供了相应的除法操作。
除提供乘法和除法操作外,Arm汇编语言还使用移位操作的是数组索引查找。考虑下面的例子,R1指向一个32位整型数组的基本元素;R2是索引,它指向该数组中的第 n 个元素。通过一条加载指令,计算R1+(R2×4),程序员可以得到正确的地址。下面给出Op2不同类型的例子,如代码清单5.7所示。
代码清单5.7 Op2不同类型的例子
2.乘法操作
当乘法操作中有常数时,首先需要将其加载到寄存器中,如表5.6所示。
表5.6 乘法操作指令
乘法操作提供了一种方法,将一个32位寄存器和另一个32位寄存器相乘,产生32位结果或64位有符号/无符号结果。在所有情况中,可以选择在结果中累加一个32位或64位的值。乘法操作也增加了额外的乘法指令,包括有符号最高字乘法SMULL、SMMLA和SMMLS,执行32位×32位的乘法,结果是高32位乘积,将低32位结果丢弃。通过使用R后缀,结果可能四舍五入,否则被截断。
3.整数SIMD指令
在Armv6架构中,首先引入单指令多数据流(Single Instruction Multiple Data,SIMD)指令。这些指令能够在32位的寄存器内打包、提取和拆包8位与16位的数,并且能够执行多个算术运算操作,如加法、减法、比较或乘法。
注:不要与更强大、更高级的SIMD操作混淆,SIMD属于Arm的NEON单元。
1)整数寄存器SIMD指令
Armv6 SIMD操作使用了CPSR中的GE(大于或等于)标志,这是与普通条件标志的区别,它对应一个字中4字节的每个位置。普通的数据处理操作产生一个结果,并且设置N、Z、C和V标志;SIMD最多产生4个输出,只设置GE标志,表示溢出。通过MSR和MRS指令,可以直接读取这些标志。
在SIMD指令中,每个寄存器内的子字都是并行工作的(如可以执行4字节的ADDS),并且根据指令的结果设置或清除GE标志。通过使用正确的前缀来指定不同类型的加法和减法操作。例如,QADD16在一个寄存器内执行半字的饱和加法。SADD/UADD8与SSUB/USUB8设置每个GE比特位;而SADD/UADD16与SSUB/USUB16则根据高16位的结果设置GE[3:2]比特位,根据低16位的结果设置GE[1:0]比特位。
此外,还可以用ASX和SAX类的指令,其交换一个操作数的半字,并且并行地加/减或减/加。类似于前面所讲的ADD和减法指令,它们以无符号(UASX/USAX)、有符号(SASX/SSAX)和饱和(QASX/QSAX)版本存在。
SADD16指令如图5.16所示,给出了在一个指令中执行两个独立的加法操作。寄存器R3和R0的高16位相加,结果送到R1寄存器的高16位中;寄存器R3和R0的低16位相加,结果送到R1寄存器的低16位中。根据高16位的结果设置CPSR的GE[3:2]比特位,根据低16位的结果设置CPSR的GE[1:0]比特位。在每种情况下,在指定的比特对内复制溢出信息。
图5.16 SADD16指令
2)整数寄存器SIMD乘法
类似于其他SIMD操作,整数寄存器SIMD操作在寄存器内的子字之间是并行的,指令也包含一个累加选项,指定加法或减法。这些指定加法或减法的指令为SMUAD(SIMD乘和加,不是累加)、SMUSD(SIMD乘和减,没有累加)、SMLAD(乘和加,带有累加)和SMLSD(乘和减,带有累加)。
注:(1)在D之前添加L表示64位累加。
(2)使用X后缀,表示在计算之前对Rm内的半字进行交换。
(3)如果累加溢出,则设置Q标志。
SMUSD指令如图5.17所示。该指令执行两个有符号数的16位乘法(高×高,低×低)操作,并且对这两个结果进行减法操作。当执行带有实部和虚部的复数乘法操作时,这个指令是非常有用的,滤波器算法常用到它。
3)绝对差求和
在通常的视频Codec的运动向量估计中,差的绝对值求和操作是关键操作。USADA8Rd,Rn,Rm,Ra指令如图5.18所示。该指令计算Rn和Rm寄存器中每个字节的差的绝对值的和,并加上保存在寄存器Ra内的值,将结果保存到Rd中。
图5.17 SMUSD指令
图5.18 USADA8 Rd,Rn,Rm,Ra指令
4)数据打包和解包
在很多视频和音频Codec中,将数据打包(数据封装是非常普遍的,视频数据经常被表示为8位像素打包后的数组,音频数据可使用打包的16位采样),在网络协议中也存在。在Armv6架构中添加额外的指令之前,必须先用LDRH和LDRB指令加载数据,或者作为字加载,然后使用移位和位清除操作拆包,这样做效率较低。打包(PKHBT,PKHTB)指令允许从寄存器内的任何位置提取16位或8位的值,打包到另一个寄存器中。解包(UXTH、UXTB,加上一些变形,以及有符号,包含加法)指令能从寄存器内的任意比特位提取8位或16位的值。
这使得通过字或双字加载,就可以高效地加载存储器内打包的数据序列,并解包到独立的寄存器中,执行操作,打包回寄存器用于高效地写到存储器中。
在32位寄存器中打包和解包16位数据如图5.19所示。其中的寄存器R0包含两个独立的16位数据,分别表示为 A 和 B 。程序员可以使用UXTH指令将两个半字解包到寄存器中,用于将来的处理,并使用PKHBT指令将两个寄存器中的半字数据打包到一个寄存器中。
图5.19 在32位寄存器中打包和解包16位数据
在每种情况下,都可以使用MOV或LSR/LSL指令代替解包指令,但是,此时单指令工作于寄存器的某一部分。
5)字节选择
根据CPSR中的GE[3:0]比特位的值,SEL指令用于在第一个或第二个操作数的相应字节中选择结果的每个字节。打包数据的算术操作设置这些位。在提取一部分数据后,可以使用SEL。例如,在每个位置的两个字节中,找到较小的数的语法格式如下:
(1)如果设置GE[0]比特位,则Rn[7:0]→Rd[7:0]。
(2)如果设置GE[1]比特位,则Rn[15:8]→Rd[15:8]。
(3)如果设置GE[2]比特位,则Rn[23:16]→Rd[23:16]。
(4)如果设置GE[3]比特位,则Rn[31:24]→Rd[31:24]。
思考与练习5.1:如何用一条指令将-1的二进制补码加载到寄存器3中?
提示:
思考与练习5.2:只使用两条指令,对一个保存在寄存器内的值求绝对值。
提示:
思考与练习5.3:乘以一个数35,保证在2个CPU周期内执行完。
提示:
Arm核在寄存器中执行ALU操作。Arm核支持的存储器操作包括加载(将存储器中的数据读到寄存器中)或保存(将寄存器中的数据写到存储器中)。LDR和STR指令可以有条件执行。
程序员可以在LDR/STR后面添加B用于字节,添加H用于半字,添加D用于双字(64位),如LDRB。对只加载而言,附加的S用于表示有符号的字节或半字(SB用于有符号的字节,SH用于有符号的半字),这种方式是非常有用的。例如,如果将一个8位或16位的数加载到32位寄存器中,那么程序员必须确定处理高16位的方式。对无符号数来说,为零扩展;而对有符号数来说,将符号位(对于字节是第7位,对于半字是第15位)复制到寄存器的高16位中。
1.寻址模式
对Arm ISA的加载和保存来说,Arm核提供了很多寻址模式,如代码清单5.8所示。
代码清单5.8 寻址模式
(1)寄存器寻址:地址保存在寄存器中(①)。
(2)预索引寻址:在访问存储器之前,将相对于基本寄存器的偏置与基本寄存器的内容相加,其基本形式为LDR Rd,[Rn,Op2]。偏置可以是正数或负数,可以是一个立即数,也可以是另一个带有可选移位的寄存器(②、③)。
(3)带有写回的预索引:在这个指令后添加一个“!”符号标志,发生存储器访问后,通过加上偏移值来更新基本寄存器(④)。
(4)带有写回的索引:在方括号后面写偏置值,来自基本寄存器的值只用于存储器访问。在存储器访问后,将偏置值加到基本寄存器中(⑤)。
思考与练习5.4:假设一个数组有25个字,编译器将y和R1关联,将数组的地址放到R2中。将下面的C语言代码翻译成3条指令:
提示:
2.多个传输
加载和保存多个指令使能从存储器连续读或写到存储器中。对堆栈操作和存储器复制来说,这些指令非常有用。使用这种方法,操作对象只能是字,并且使用字对齐地址边界。
操作数是一个基寄存器(可选的符号“!”用于表示基寄存器写回),在多个寄存器列表中间,用逗号分隔,使用破折号表示范围。加载和保存寄存器的顺序与列表中所指定的顺序无关。取而代之的是,以固定的方式进行处理,即最低标号的寄存器总是映射到最低的地址中。例如:
该指令表示从寄存器R10指向的地址读,由于指定了写回,因此在结束后给R0的内容增加20(5×4)字节。
指令必须说明从基寄存器Rd处理的方法。可能的处理方法包括IA/IB(递增后/递增前)及DA/DB(递减后/递减前)。这些也可以通过使用别名FD、FA、ED和EA来指定,它是来自堆栈的角度,用于说明堆栈指针是否指向一个满或空的堆栈顶部,以及存储器内的堆栈递增还是递减,如LDMFD是LDMDB的同义词。
注:(1)IA/DA,每个传输结束后,对地址进行递增/递减操作。
(2)IB/DB,在每个传输开始前,对地址进行递增/递减操作。
按惯例,只有满递减(FD)选项用于Arm处理器的堆栈。这意味着堆栈指针是堆栈存储器内最后填充的位置,并且当每个新的数据入栈时,递减堆栈指针,如代码清单5.9所示。
代码清单5.9 STM/LDM指令
将两个寄存器入栈,如图5.20所示。在执行STMFD(入栈)指令前,堆栈指针指向堆栈最后一个填充的位置。执行该指令后,堆栈指针递减8(两个字),并且两个寄存器的内容被写到存储器中,最低编号的寄存器中的内容被写到了最低的存储器地址中。
图5.20 入栈操作
Cortex-A9处理器提供了许多不同类型的分支指令。对简单的相对分支(相对于当前地址的偏置)来说,使用B指令。调用子程序需要将返回的地址保存在链接寄存器LR中,使用BL指令。B/BL机器指令的格式如图5.21所示。
图5.21 B/BL机器指令的格式
其中,.w为可选项,表示在Thumb中使用一个32位指令。
使用BL指令实现func1()和func2()两个函数调用与返回的过程如图5.22所示。可以看出,当使用BL指令调用子程序时,将返回地址保存在LR中;当从调用函数中返回时,从LR中将返回地址恢复到PC中。对非叶子函数来说,必须将LR入栈。
图5.22 使用BL指令实现func1()和func2()两个函数调用与返回的过程
如果程序员想改变指令集,如从Arm改到Thumb,或者从Thumb改到Arm,则可以使用BX或BLX指令。
程序员可以将PC作为普通数据处理操作,如ADD/SUB,作为结果的目的寄存器。但是这通常在Thumb中并不能得到支持。一个可实现的额外分支指令类型是带有PC、作为目标的LDR,加载多个LDM,或者带有PC的出栈。
Thumb有比较和分支指令,融合了CMP指令和一个条件分支,但是不改变CPSR的条件码标志。这里有两个操作码,分别为CBZ(比较,当Rn为0时,分支跳转到标号)和CBNZ(比较,当Rn不为0时,分支跳转到标号)。这些分支只能在4~130个字节内进行跳转。格式如下(它们没有Arm或32位的Thumb版本):
Thumb也有TBB(表示分支字节)和TBH(表示分支半字)。这些指令先从一个偏置表中以字节或半字读取一个值,然后执行从表中返回字节/半字值的2倍的一个向前的PC相对分支。这些指令要求在Rn寄存器中说明一个表的基地址,在另一个寄存器Rm中说明索引。这些指令的格式如下:
在视频和音频Codec中,经常使用饱和算术运算。当计算返回比最大正数(负数)更大的数时,能够表示为没有溢出。取而代之的是,将结果设置为最大的正数或负数。Arm指令集中包含大量的指令使能这样的算法。
Arm的饱和算术指令可以对字节、字或半字数据进行操作。例如,QADD8和QSUB8指令表示它们操作字节宽度的值,操作结果将饱和到最大的可能的正数或负数。如果结果已经溢出且饱和,则设置CPSR的Q比特位,一旦设置该标志,就一直保持,直到对CPSR进行一个明确的写操作才能清除该标志。QADD8和QSUB8指令的格式如下:
其中,Rd为目标寄存器,Rm和Rn中保存着操作数。
指令集提供了特殊的指令QSUB和QADD。此外,还提供了QDSUB和QDADD,用于支持Q15或Q31的定点算术运算,其格式如下:
其中,Rd为目标寄存器,Rm和Rn中保存着操作数。这两条指令实现的功能是Rm±饱和(Rn×2)→Rd。
在设置最高有效位之前,前导零计数(Count Leading Zeros,CLZ)指令返回0的个数,其格式如下:
如果Rm全为0,则将数值32加载到寄存器Rd中;如果设置Rm寄存器的第31位,则将数值0加载到寄存器Rd中。
在用于某些除法的量化时,该指令是非常有用的。将一个值饱和到一个指定的比特位位置(有效饱和到2的幂次方),可以使用USAT/SSAT(无符号/有符号)饱和操作。USAT指令的格式如下:
该指令实现无符号饱和,执行移位操作,并将结果饱和到无符号范围[0,2 sat -1]。如果发生饱和,则设置Q标志。
其中,Rd为目的寄存器;sat指定要饱和到的比特位位置,范围为0~31;Rm中保存着操作数;shift是可选的移位个数,可以是ASR#n(在Arm状态下为1~32,在Thumb状态下为1~31),或者LSL#n(范围为0~31)。
SSAT指令的格式如下:
该指令实现有符号饱和,执行移位操作,并将结果饱和到无符号范围[-2 sat-1 ,2 sat-1 -1]。如果发生饱和,则设置Q标志。
其中,Rd为目的寄存器;sat指定要饱和到的比特位位置,范围为1~32;Rm中保存着操作数;shift是可选的移位个数,可以是ASR#n(在Arm状态下为1~32,在Thumb状态下为1~31),或者LSL#n(范围为0~31)。
USAT16和SSAT16允许在一个寄存器内对两个打包的16位半字同时执行饱和操作,格式如下:
本节介绍Cortex-A9处理器提供的杂项指令,包括协处理器指令、SVC、修改PSR、位操作、缓存预加载、字节翻转和其他指令。
1.协处理器指令
Arm指令集中提供了协处理器指令。在Cortex-A9处理器中,可以实现最多16个协处理器,编号为0~15(CP0~CP15)。这些协处理器可能是内部的(内建在处理器中),也可能是外部的,通过专用接口连接。使用外部协处理器在以前的处理器中是不常见的,所有Cortex-A系列处理器均不支持外部协处理器。
(1)CP15是内部处理器,提供对核特性的控制,包括缓存和MMU。
(2)CP14是内部处理器,提供对核硬件调试工具的控制,如断点单元。
(3)CP10和CP11用于访问系统内的浮点与NEON硬件。
如果执行协处理器指令,但是所对应的协处理器并没有出现在系统中,则会发生未定义的指令异常。
在Cortex-A9处理器中,提供了以下5类协处理器指令。
(1)CDP:初始化一个协处理器数据处理操作。格式如下:
其中,coproc为所应用的协处理器的名字,通常的形式为p n , n 为0~15;opcode1为一个4位的协处理器指定的操作码;opcode2为一个可选的3位的协处理器指定的操作码;CRd、CRn、CRm均为协处理器寄存器。
(2)MRC:将协处理器寄存器中的内容复制到Arm寄存器中。格式如下:
其中,Rt为所要用到的Arm寄存器。
(3)MCR:将Arm寄存器中的内容复制到协处理器寄存器中。格式如下:
(4)LDC:将存储器中的内容加载到协处理器寄存器中。格式如下:
其中,L表示有多个寄存器,传输的长度由协处理器决定,但是不能超过16个字;Rn是寄存器,其中保存着用于存储器操作的基地址;offset为4的倍数,范围为0~1020,从Rn中加/减,如果有符号“!”,则表示将包含的地址写回Rn,为字对齐的PC相对的地址标号。
(5)STC:将协处理器寄存器中的内容保存到存储器中。格式如下:
此外,Cortex-A9处理器还提供了以上多个指令的变形。
(1)MRRC:将一个协处理器的值传到一对Arm处理器寄存器中。格式如下:
(2)MCRR:将一对Arm寄存器的值传到一个协处理器中。格式如下:
(3)LDCL:将多个寄存器的值读到一个协处理寄存器中。
(4)STCL:将一个协处理器的值写到多个寄存器中。
2.SVC
SVC为监控程序调用指令。当执行该指令时,会引起监控调用异常。该指令包含一个24位(ARM)或8位(Thumb)的编号,SVC句柄代码会检查这个编号。通过SVC机制,一个操作系统可以指定一套特权操作(系统调用),在用户模式下运行的应用程序可以请求它,其最初被称为SWI(软件中断)。
3.修改PSR
一些指令可以用于读/写PSR。
(1)MRS用于将CPSR或SPSR的值传到通用寄存器中。而MSR则用于将通用寄存器的值传到CPSR/SPSR中。这个状态寄存器或它的一部分都能被更新。在用户模式下,可以读所有的位,但是只能修改条件标志。
MRS的格式如下:
其中,psr可以是APSR、CPSR或SPSR;Rd为目的寄存器;coproc_register是CP14或CP15的名字;DBGDSCR是CP14的名字,它能被复制到APSR中。
MSR的其他格式如下:
其中,flags可以是一个或多个ALU标志,或者是SIMD标志g;Rm为源寄存器;constant为8位模式,被旋转了偶数位;psr可以是APSR、CPSR或SPSR;fields可以是c控制域屏蔽字节PSR[7:0]、x扩展域屏蔽字节PSR[15:8]、s状态域屏蔽字节PSR[23:16]、f标志域屏蔽字节PSR[31:24]。下面给出了用例,如代码清单5.10所示。
代码清单5.10 MSR/MRS的用法
(2)在特权模式下,修改处理器状态指令CPS,可以用于直接修改CPSR内的模式和中断使能/禁止(I和F)比特位。格式如下:
其中,mode是处理器进入模式的编号;CPS的后缀IE用于使能中断或异常终止;CPS的后缀ID用于禁止中断或异常终止,将iflags指定为a时表示异步异常终止,指定为i时表示IRQ,指定为f时表示FIQ。
(3)指令SETEND用于修改CPSR的E(端)比特位。用于系统在大端和小端混合模式之间的切换。格式如下:
4.位操作
下面给出可以在寄存器中实现位操作的指令。
(1)比特域插入指令BFI,将一个寄存器从低端开始的一系列连续的比特位(由宽度值和LSB位置决定)复制到目标寄存器的任意位置。格式如下:
其中,Rd为目的寄存器,Rn为包含将要被复制比特位的寄存器,lsb表示要写到Rd中的最低有效位,width为要复制的比特位的宽度。
(2)比特域清除指令BFC,将一个寄存器内相邻的比特位清零。格式如下:
(3)SBFX和UBFX指令(有符号和无符号比特域抽取),将一个寄存器中相邻的位复制到另两个寄存器的最低有效位上,符号扩展或零扩展到32位。格式如下:
(4)RBIT指令,对一个寄存器内的所有位进行翻转。格式如下:
5.缓存预加载
Cortex-A9处理器提供了缓存预加载指令,即PLD(数据缓存预加载)和PLI(指令缓存预加载)指令。所有指令作为对存储器系统的暗示,表示对指定地址的访问可能很快就会发生。不支持这些操作的实现将预加载看作NOP。但是,Cortex-A系列处理器支持缓存预加载。在PLD指令中,当把任何一个非法地址指定为一个参数时,都不会导致数据异常终止。格式如下:
其中,Rn为保存基地址的寄存器;offset为立即数;Rm保存偏移值,不能是PC(在Thumb状态下为SP)。
6.字节翻转
用于对字节顺序进行翻转的指令非常有用,如数据重新排序或端调整。
(1)REV指令对一个字的字节顺序进行翻转,如图5.23所示。格式如下:
图5.23 REV指令操作
(2)REV16对一个寄存器内半个字的每个字节进行翻转。格式如下:
(3)REVSH先对低端的两个字节进行翻转,然后符号扩展到32位。格式如下:
7.其他指令
此外,Cortex-A9处理器还提供了其他指令。
(1)断点指令BKPT,引起预取异常终止,或者引起核进入待机模式。调试器使用该指令。
(2)等待中断指令WFI,使得CPU进入待机模式。CPU停止执行,直到被一个中断或调试事件唤醒。如果由于执行WFI指令而禁止中断,则一个中断仍能唤醒CPU,但是不会产生中断异常。在WFI后,CPU处理指令。在以前的Arm处理器中,实现WFI作为一个CP15操作。
(3)空操作指令NOP。它并不保证会消耗时间执行该指令,因此不能在代码中通过插入NOP指令实现时间延迟。使用该指令用于填充。
(4)与WFI指令类似,对于等待事件指令WFE,它使CPU进入待机模式。CPU将休眠,直到另一个核执行REV指令,产生一个事件,从而唤醒CPU。一个中断或一个调试事件都可以唤醒CPU。
(5)发送事件指令SEV,用于产生唤醒事件,可以唤醒簇内的其他核。