3.2 指令集介绍
本节介绍的是ARM920T的指令集,包括ARM指令集和Thumb指令集.首先介绍ARM指令的基本格式及灵活的操作数,然后介绍条件码,再把ARM指令集、Thumb指令集按类别分别说明.
在介绍ARM指令集之前,我们先看一个简单的ARM汇编程序,通过这一段程序读者可以了解ARM汇编指令格式、程序结构及基本风格,完整代码如程序清单3-1所示.
程序清单3-1 寄存器相加
第1、2、3行为程序说明,使用“;”进行注释,“;”号后面至行结束均为注释内容.
第4行声明一个代码段,ARM汇编程序至少要声明一个代码段.
第5行标识程序入口,在仿真调试时会从指定入口处开始运行程序.
第6行声明32位ARM指令,ARM920T复位后是ARM状态.
第7~14行为实际代码,标号要顶格书写(如START、LOOP、ADD_SUB),而指令不能顶格书写.BL为调用子程序指令,它会把返回地址(即下一条指令的地址)存到LR,然后跳转到子程序ADD_SUB.子程序ADD_SUB处理结束后,将LR的值装入PC即可返回.
第11、15行为空行,目的在于增强程序的可读性.
第16行用于指示汇编源文件结束,每一个ARM汇编文件均要用END声明结束.
3.2.1 ARM指令集
指令格式ARM指令的基本格式如下:
<opcode>{<cond>}{S} <Rd>,<Rn>{,<operand2>}
其中,<>号内的项是必需的,{}号内的项是可选的.如<opcode>是指令助记符,这是必须书写的,而{<cond>}为指令执行条件,是可选项.若不书写则使用默认条件AL(无条件执行).
opcode 指令助记符,如LDR、STR等.
cond 执行条件,如EQ、NE等.
S 是否影响CPSR寄存器的值,书写时影响CPSR.
Rd 目标寄存器.
Rn 第1个操作数的寄存器.
operand2 第2个操作数.
指令格式举例如下:
第2个操作数:
在ARM指令中,灵活地使用第2个操作数能够提高代码效率.第2个操作数的形式如下:
(1)#immed_8r——常数表达式.
该常数必须对应8位位图(pattern),即常数是由一个8位的常数循环移位偶数位得到.合法常量:0x3FC(0xFF<<2)、0、0xF0000000(0xF0<<24)、200(0xC8)、0xF0000001(0x1F<<28).
非法常量:0x1FE、511、0xFFFF、0x1010、0xF0000010.
常数表达式应用举例:
MOV R0,#1 R0=1
AND R1,R2,#0x0F R2与0x0F,结果保存在R1
LDR R0,[R1], #-4读取R1地址上的存储器单元内容,且R1=R1-4
(2)Rm—寄存器方式.
在寄存器方式下,操作数即为寄存器的数值.寄存器方式应用举例:
SUB R1,R1,R2 R1-R2=>R1
MOV PC,R0 PC=R0,程序跳转到指定地址
LDR R0,[R1],-R2 读取R1地址上的存储器单元内容并存入R0,且R1=R1-R2
(3)Rm,shift——寄存器移位方式.
将寄存器的移位结果作为操作数,但Rm值保存不变,移位方法如下:
ASR #n算术右移n位(1≤n≤32).
LSL #n逻辑左移n位(1≤n≤31).
LSR #n逻辑右移n位(1≤n≤32).
ROR #n循环右移n位(1≤n≤31).
RRX 带扩展的循环右移1位.
Type Rs 其中,type为ASR、LSL、LSR和ROR中的一种;Rs偏移量寄存器,低8位有效.若其值大于或等于32,则第2个操作数的结果为0(ASR、ROR例外).
寄存器偏移方式应用举例:
ADD R1,R1,R1,LSL#3 ;R1=R1X9
SUB R1,R1,R2,LSR#2 ;R1=R1-R2/4
R15为处理器的程序计数器PC,一般不要对其进行操作,而且有些指令是不允许使用R15的,如UMULL指令.
3.2.2 条件码
使用指令条件码可实现高效的逻辑操作,提高代码效率.指令条件码表如表3-1所示.
表3-1 指令条件码表
续表
对于Thumb指令集,只有B指令具有条件码执行功能.此指令的条件码同表3-1.但如果为无条件执行时,条件码助记符AL不能在指令中书写.
条件码应用举例如下:
比较两个值大小,并进行相应加1处理,C代码为:
if(a>b) a++;
else b++;
对应的ARM指令如下(其中R0为a,R1为b):
CMP R0,R1 R0与R1比较
ADDHI R0,R0,#1 若R0>R1,则R0=R0+1
ADDLS R1,R1,#1 若R0≤1,则R1=R1+1
若两个条件均成立,则将这两个数值相加,C代码为:
if((a!=10)&&(b!=20))a=a+b;
对应的ARM指令如下(其中R0为a,R1为b):
CMP R0,#10 比较R0是否为10
CMPNE R1,#20 若R0不为10,则比较R1是否为20
ADDNE R0,R0,R1 若R0不为10且R1不为20,指令执行,R0=R0+R1
3.2.3 ARM存储器访问指令
ARM处理器是加载/存储体系结构的典型的RISC处理器,对存储器的访问只能使用加载和存储指令实现.ARM的加载/存储指令实现字、半字、无符号/有符号字节操作;多寄存器加载/存储指令可实现一条指令加载/存储多个寄存器的内容,大大提高效率;SWP指令是一条寄存器和存储器内容交换的指令,可用于信号量操作等.ARM处理器是冯·诺依曼存储结构,程序空间、RAM空间及I/O映射空间统一编址,除对RAM操作以外,对外围IO、程序数据的访问均要通过加载/存储指令进行.
ARM存储器访问指令见表3-2.
表3-2 ARM存储器访问指令
(1)LDR和STR——加载存储指令加载/存储字和无符号字节指令.
使用STR指令将单一字节或字存储到内存,使用LDR指令从内存中加载单一字节或字到寄存器.LDR指令用于从内存中读取数据放入寄存器中;STR指令用于将寄存器中的数据保存到内存.指令格式如下:
LDR{cond}{T}Rd,<地址> 加载指定地址上的数据(字),放入Rd中.
STR{cond}{T}Rd,<地址> 存储数据(字)到指定地址的存储单元,要存储的数据在Rd中.
LDR{cond}B{T}Rd,<地址> 加载字节数据,放入Rd中,即Rd最低字节有效,高24位清零.
STR{cond}B{T}Rd,<地址> 存储字节数据,要存储的数据在Rd,最低字节有效.
其中,T为可选后缀.若指令有T,那么即使处理器是在特权模式下,存储系统也将访问看成是处理器是在用户模式下.T在用户模式下无效,不能与前索引偏移一起使用T.指令编码格式:
I,P,U,W 用于区别不同的地址模式(偏移量).I为0时,偏移量为12位立即数,I为1时,偏移量为寄存器移位.P表示前/后变址,U表示加/减,W表示回写.L用于区别加载(L为1)或存储(L为0).B用于区别字节访问(B为1)或字访问(B为0).Rn基址寄存器.Rd源/目标寄存器.LDR/STR指令寻址是非常灵活的,由两部分组成,一部分为一个基址寄存器,可以为任一个通用寄存器;另一部分为一个地址偏移量.地址偏移量有以下3种格式:
①立即数.立即数可以是一个无符号的数值.这个数据可以加到基址寄存器,也可以从基址寄存器中减去这个数值.指令举例如下:
LDR R1,[R0,#0x12];将R0+0x12地址处的数据读出,保存到R1中;(R0的值不变)
LDR R1,[R0,#-0x12];将R0-0x12地址处的数据读出,保存到R1中;(R0的值不变)
LDR R1,[R0];将R0地址处的数据读出,保存到R1中(零偏移)
②寄存器.寄存器中的数值可以加到基址寄存器,也可以从基址寄存器中减去这个数值.指令举例如下:
LDR R1,[R0,R2];将R0+R2地址处的数据读出,保存到R1中(R0的值不变)
LDR R1,[R0,-R2];将R0-R2地址处的数据读出,保存到R1中(R0的值不变)
③寄存器及移位常数.寄存器移位后的值可以加到基址寄存器,也可以从基址寄存器中减去这个数值.指令举例如下:
LDR R1,[R0,R2,LSL#2];将R0+R2×4地址处的数据读出,保存到R1中;(R0、R2的值不变)
LDR R1,[R0,-R2,LSL#2];将R0-R2×4地址处的数据读出,保存到R1中;(R0、R2的值不变)
从寻址方式的地址计算方法分,加载/存储指令有以下4种形式:
①零偏移.Rn的值作为传送数据的地址,即地址偏移量为0.指令举例如下:
LDR Rd,[Rn]
②前索引偏移.在数据传送之前,将偏移量加到Rn中,其结果作为传送数据的存储地址.若使用后缀“!”,则结果写回到Rn中,且Rn的值不允许为R15.指令举例如下:
LDR Rd,[Rn,#0x04]!
LDR Rd,[Rn,#-0x04]
③程序相对偏移.程序相对偏移是前索引形式的另一个版本.汇编器由PC寄存器计算偏移量,并将PC寄存器作为Rn生成前索引指令.不能使用后缀“!”.指令举例如下:
LDR Rd,label;label为程序标号,label必须是在当前指令的±4KB范围内后
④索引偏移.Rn的值用做传送数据的存储地址.在数据传送后,将偏移量与Rn相加,结果写回到Rn中.Rn不允许是R15.指令举例如下:
LDR Rd,[Rn],#0x04
地址对齐——大多数情况下,必须保证用于32位传送的地址是32位对齐的.加载/存储字和无符号字节指令举例如下:
LDR R2,[R5];加载R5指定地址上的数据(字),放入R2中
STR R1,[R0,#0x04];将R1的数据存储到R0+0x04的存储单元,R0值不变
LDRB R3,[R2],#1;读取R2地址上的一字节数据,并保存到R3中,R2=R2+1
STRB R6,[R7];将R6的数据保存到R7指定的地址中,只存储一字节数据加载/存储半字和有符号字节
这类LDR/STR指令可加载有符号字节、加载有符号半字、加载/存储无符号半字.偏移量格式、寻址方式与加载/存储字和无符号字节指令相同.指令格式如下:
LDR{cond}SB Rd,<地址>;加载指定地址上的数据(有符号字节),放入Rd中
LDR{cond}SH Rd,<地址>;加载指定地址上的数据(有符号半字),放入Rd中
LDR{cond}H Rd,<地址>;加载半字数据,放入Rd中,即Rd最低16位有效,高16位清零
STR{cond}H Rd,<地址>;存储半字数据,要存储的数据在Rd,最低16位有效
说明:有符号位半字/字节加载是指用符号位加载扩展到32位;无符号位半字加载是指零扩展到32位.指令编码格式:
I,P,U,W 用于区别不同的地址模式(偏移量).I为0时,偏移量为8位立即数;I为1时,偏移量为寄存器偏移.P表示前/后变址,U表示加/减,W表示回写.
L 用于区别加载(L为1)或存储(L为0).
S 用于区别有符号访问(S为1)和无符号访问(S为0).
H 用于区别半字访问(H为1)或字节访问(H为0).
Rn 基址寄存器.
Rd 源/目标寄存器.
地址对齐——对半字传送的地址必须为偶数.非半字对齐的半字加载将使Rd内容不可靠;非半字对齐的半字存储将使指定地址的2字节存储内容不可靠.
加载/存储半字和有符号字节指令举例如下:
LDR SB R1,[R0,R3];将R0+R3地址上的字节数据读出到R1,高24位用符号位扩展
LDR SH R1,[R9] ;将R9地址上的半字数据读出到R1,高16位用符号位扩展
LDRH R6,[R2],#2 ;将R2地址上的半字数据读出到R6,高16位用零扩展,;R2=R2+2
STRH R1,[R0,#2]!将R1的数据保存到R0+2地址中,只存储低2字节数据,R0=R0+2LDR/STR指令用于对内存变量的访问、内存缓冲区数据的访问、查表、外围部件的控制操作等.若使用LDR指令加载数据到PC寄存器,则实现程序跳转功能,这样也就实现了程序散转.
(2)LDM和STM——多寄存器加载/存储指令.
多寄存器加载/存储指令可以实现在一组寄存器和一块连续的内存单元之间传输数据.LDM为加载多个寄存器;STM为存储多个寄存器.允许一条指令传送16个寄存器的任何子集或所有寄存器.指令格式如下:
LDM{cond}<模式>Rn{!},reglist{^}
STM{cond}<模式>Rn{!},reglist{^}
指令编码格式:
register list 寄存器列表,b0位与R0对应,b15位与R15对应.
P,U,W 用于区别不同的地址模式.P表示前/后变址,U表示加/减,W表示回写.
S 恢复CPSR和强制用户位.当PC寄存器包含在LDM指令的reglist中,且S为1时,则当前模式的SPSR将被拷贝到CPSR,成为一个原子的返回和恢复状态指令.若reglist不包含PC寄存器,S为1,则加载/存储的是用户模式的寄存器.
L 用于区别加载(L为1)或存储(L为0).
Rn 基址寄存器.
LDM/STM的主要用途是现场保护、数据复制、参数传送等.其模式有如下8种(前面4种用于数据块的传输,后面4种是堆栈操作):
①IA:每次传送后地址加4;
②IB:每次传送前地址加4;
③DA:每次传送后地址减4;
④DB:每次传送前地址减4;
⑤FD:满递减堆栈;
⑥ED:空递减堆栈;
⑦FA:满递增堆栈;
⑧EA:空递增堆栈.
指令格式中,寄存器Rn为基址寄存器,装有传送数据的初始地址,Rn不允许为R15;后缀“!”表示最后的地址写回到Rn中.寄存器列表reglist可包含多于一个寄存器或包含寄存器范围,使用“,”分开,如{R1,R2,R6-R9},寄存器由小到大排列;“^”后缀不允许在用户模式或系统模式下使用,若在LDM指令且寄存器列表中包含有PC时使用,那么除了正常的多寄存器传送外,将SPSR也拷贝到CPSR中,这可用于异常处理返回.使用“^”后缀进行数据传送且寄存器列表不包含PC时,加载/存储的是用户模式的寄存器,而不是当前模式的寄存器.当Rn在寄存器列表中且使用后缀“!”时,对于STM指令,若Rn为寄存器列表中的最低数字的寄存器,则会将Rn的初值保存;其他情况下Rn的加载值和存储值不可预知.
地址对齐——这些指令忽略地址的位[1∶0].
多寄存器加载/存储指令举例如下:
LDMIA R0!,{R3-R9};加载R0指向的地址上的多字数据,保存到R3~R9中,;R0值更新
STMIA R1!,{R3-R9} ;将R3~R9的数据存储到R1指向的地址上,R1值更新
STMFD SP!,{R0-R7,LR};现场保存,将R0~R7、LR入栈
LDMFD SP!,{R0-R7,PC}^;恢复现场,异常处理返回
在进行数据复制时,先设置好源数据指针和目标指针,然后使用块拷贝寻址指令LDMIA/STMIA、LDMIB/STMIB、LDMDA/STMDA、LDMDB/STMDB进行读取和存储.而进行堆栈操作时,则要先设置堆栈指针,一般使用SP,然后使用堆栈寻址指令STMFD/LDMFD、STMED/LDMED、STMFA/LDMFA和STMEA/LDMEA实现堆栈操作.
多寄存器传送指令示意图如图3-2所示,其中R1为指令执行前的基址寄存器,R1’则为指令执行完后的基址寄存器.
图3-2 多寄存器传送指令示意图
使用多寄存器传送指令时,基址寄存器的地址是向上增长还是向下增长,地址是在加载/存储数据之前还是之后增加/减少,其对应关系如表3-3所示.
表3-3多寄存器传送指令映射
(3)SWP——寄存器和存储器交换指令.
SWP指令用于将一个内存单元(该单元地址放在寄存器Rn中)的内容读取到一个寄存器Rd中,同时将另一个寄存器Rm的内容写入到该内存单元中.使用SWP可实现信号量操作.指令格式如下:
SWP{cond}{B} Rd,Rm,[Rn]
其中,B为可选后缀,若有B,则交换字节,否则交换32位字;Rd为数据从存储器加载到的寄存器;Rm的数据用于存储到存储器中,若Rm与Rn相同,则为寄存器与存储器内容进行交换;Rn为要进行数据交换的存储器地址,Rn不能与Rd和Rm相同.
指令编码格式:
B 用于区别无符号字节(B为1)或字(B为0).
Rm 源寄存器.
Rd 目标寄存器.
Rn 基址寄存器.
WP指令举例如下:
SWP ;R1,R1,[R0]将R1的内容与R0指向的存储单元的内容进行交换
SWPB R1,R2,[R0];将R0指向的存储单元内的容读取一字节数据到R1中(高24;位清零),并将R2的内容写入到该内存单元中(最低字节有效)
3.2.4 ARM数据处理指令
数据处理指令大致可分为3类:数据传送指令(如MOV、MVN)、算术逻辑运算指令(如ADD、SUB、AND),比较指令(如CMP、TST).数据处理指令只能对寄存器的内容进行操作.所有ARM数据处理指令均可选择使用S后缀,并影响状态标志.比较指令CMP、CMN、TST和TEQ不需要后缀S,它们会直接影响状态标志.
ARM数据处理指令见表3-4.
表3-4 ARM数据处理指令
ARM数据处理指令编码格式:
opcode 数据处理指令操作码.
I 用于区别立即数(I为1)或寄存器移位(I为0).
S 设置条件码.
Rn 第一操作数寄存器.
Rd 目标寄存器.
operand2 第二个操作数.
若指令不需要全部的可用操作数时(如MOV指令的Rn),不用的寄存器域应设置为0(由编译器自动完成).对于比较指令,b20位固定为1.ARM数据处理指令操作码见表3-5.
表3-5 ARM数据处理指令操作码
续表
(1)数据传送指令.
①MOV——数据传送指令.
MOV将8位图(pattern)立即数或寄存器(operand2)传送到目标寄存器(Rd),可用于移位运算等操作.指令格式如下:
MOV{cond}{S}Rd,operand2
MOV指令举例如下:
MOV R1,#0x10 ;R1=0x10
MOV R0,R1 ;R0=R1
MOVS R3,R1,LSL#2;R3=R1<<2,并影响标志位
MOV PC,LR ;PC=LR,子程序返回
②MVN——数据非传送指令MVN.
指令将8位图(pattern)立即数或寄存器(operand2)按位取反后传送到目标寄存器(Rd),因为其具有取反功能,所以可以装载范围更广的立即数.指令格式如下:
MVN{cond}{S}Rd,operand2
MVN指令举例如下:
MVN R1,#0xFF ;R1=0xFFFFFF00
MVN R1,R2;将R2取反,结果存到R1
(2)算术逻辑运算指令.
①ADD——加法运算指令.
ADD指令将operan d2的值与Rn的值相加,结果保存到Rd寄存器.指令格式如下:
ADD{cond}{S}Rd,Rn,operand2
ADD指令举例如下:
ADDS R1,R1,#1 ;R1=R1+1
ADD R1,R1,R2 ;R1=R1+R2
ADDS R3,R1,R2,LSL#2;R3=R1+R2<<2
②SUB——减法运算指令.
SUB指令用寄存器Rn减去operand2,结果保存到Rd中.指令格式如下:
SUB{cond}{S}Rd,Rn,operand2
SUB指令举例如下:
SUBS R0,R0,#1 ;R0=R0-1
SUBS R2,R1,R2;R2=R1-R2
SUB R6,R7,#0x10 ;R6=R7-0x10
③RSB——逆向减法指令.
RSB指令将operand2的值减去Rn,结果保存到Rd中.指令格式如下:
RSB{cond}{S}Rd,Rn,operand2
RSB指令举例如下:
RSB R3,R1,#0xFF00;R3=0xFF00-R1
RSBS R1,R2,R2,LSL#2;R1=R2<<2-R2=R2×3
RSB R0,R1,#0 ;R0=-R1
④ADC——带进位加法指令.
将operand2的值与Rn的值相加,再加上CPSR中的C条件标志位,结果保存到Rd寄存器.指令格式如下:
ADC{cond}{S}Rd,Rn,operand2
ADC指令举例如下:
ADDS R0,R0,R2
ADC R1,R1,R3;使用ADC实现64位加法,(R1、R0)=(R1、R0)+(R3、R2)
⑤SBC——带进位减法指令.
SBC指令用寄存器Rn减去operand2,再减去CPSR中的C条件标志位的非(即若C标志清零,则结果减去1),结果保存到Rd中.指令格式如下:
SBC{cond}{S}Rd,Rn,operand2
SBC指令举例如下:
SUBS R0,R0,R2
SBC R1,R1,R3;使用SBC实现64位减法,(R1、R0)=(R1、R0)-(R3、R2)
⑥RSC——带进位逆向减法指令.
RSC指令用寄存器operand2减去Rn,再减去CPSR中的C条件标志位,结果保存到Rd中.指令格式如下:
RSC{cond}{S}Rd,Rn,operand2
RSC指令举例如下:
RSBS R2,R0,#0
RSC R3,R1,#0;使用RSC指令实现求64位数值的负数
⑦AND——逻辑“与”操作指令.
AND指令将operand2的值与寄存器Rn的值按位作逻辑“与”操作,结果保存到Rd中.指令格式如下:
AND{cond}{S}Rd,Rn,operand2
AND指令举例如下:
ANDS R0,R0,#0x01;R0=R0&0x01,取出最低位数据
AND R2,R1,R3;R2=R1&R3
⑧ORR——逻辑“或”操作指令.
ORR指令将operand2的值与寄存器Rn的值按位作逻辑“或”操作,结果保存到Rd中.指令格式如下:
ORR R0,R0,#0x0F ;将R0的低4位置1
MOV R1,R2,LSR#24
ORR R3,R1,R3,LSL#8;使用ORR指令将R2的高8位数据移入到R3低8位中
⑨EOR——逻辑“异或”操作指令.
EOR指令将operand2的值与寄存器Rn的值按位作逻辑“异或”操作,结果保存到Rd中.指令格式如下:
EOR{cond}{S}Rd,Rn,operand2
EOR指令举例如下:
EOR R1,R1,#0x0F;将R1的低4位取反
EOR R2,R1,R0;R2=R1^R0
EORS R0,R5,#0x01;将R5和0x01进行逻辑“异或”,结果保存到R0,并影响标志位
⑩BIC——位清除指令.
BIC指令将寄存器Rn的值与operand2的值的反码按位作逻辑“与”操作,结果保存到Rd中.指令格式如下:
BIC{cond}{S}Rd,Rn,operand2
BIC指令举例如下:
BIC R1,R1,#0x0F;将R1的低4位清零,其他位不变
BIC R1,R2,R3 ;将R3的反码和R2相逻辑“与”,结果保存到R1中比较指令
(3)比较指令.
①CMP——比较指令.
CMP指令将寄存器Rn的值减去operand2的值,根据操作的结果更新CPSR中的相应条件标志位,以便后面的指令根据相应的条件标志来判断是否执行.指令格式如下:
CMP{cond}Rn,operand2
CMP指令举例如下:
CMP R1,#10;R1与10比较,设置相关标志位
CMP R1,R2;R1与R2比较,设置相关标志位
CMP指令与SUBS指令的区别在于CMP指令不保存运算结果.在进行两个数据的大小判断时,常用CMP指令及相应的条件码来操作.
②CMN——负数比较指令.
CMN指令使用寄存器Rn的值加上operand2的值,根据操作的结果更新CPSR中的相应条件标志位,以便后面的指令根据相应的条件标志来判断是否执行.指令格式如下:
CMN{cond}Rn,operand2
CMN指令举例如下:
CMN R0,#1;R0+1,判断R0是否为1的补码.若是,则Z置位为1
CMN指令与ADDS指令的区别在于CMN指令不保存运算结果.CMN指令可用于负数比较,比如CMNR0,#1指令则表示R0与-1比较,若R0为-1(即1的补码),则Z置位;否则Z复位.
③TST——位测试指令.
TST指令将寄存器Rn的值与operand2的值按位作逻辑“与”操作,根据操作的结果更新CPSR中的相应条件标志位,以便后面的指令根据相应的条件标志来判断是否执行.指令格式如下:
TST{cond}Rn,operand2
TST指令举例如下:
TST R0,#0x01;判断R0的最低位是否为0
TST R1,#0x0F;判断R1的低4位是否为0
TST指令与ANDS指令的区别在于TST指令不保存运算结果.TST指令通常与EQ、NE条件码配合使用,当所有测试位均为0时,EQ有效,而只要有一个测试位不为0,则NE有效.
④TEQ——相等测试指令.
TEQ指令将寄存器Rn的值与operand2的值按位作逻辑“异或”操作,根据操作的结果更新CPSR中的相应条件标志位,以便后面的指令根据相应的条件标志来判断是否执行.指令格式如下:
TEQ{cond} Rn,operand2
TEQ指令举例如下:
TEQ R0,R1;比较R0与R1是否相等(不影响V位和C位)
TEQ指令与EORS指令的区别在于TEQ指令不保存运算结果.使用TEQ进行相等测试时,常与EQ、NE条件码配合使用.当两个数据相等时,EQ有效;否则NE有效.
3.2.5 乘法指令与乘加指令
ARM920T具有32×32乘法指令,32×32乘加指令,32×32结果为64位的乘/乘加指令.ARM乘法指令见表3-6.
表3-6 ARM乘法指令
ARM乘法指令编码格式:
opcode 乘法指令操作码.
S 设置条件码.
Rm 被乘数寄存器.
Rs 乘数的寄存器.
Rn/RdLo MLA指令相加的寄存器或64位乘法指令的目标寄存器(低32位).
Rd/RdHi 目标寄存器或64位乘法指令的目标寄存器(高32位).若指令不需要全部的可用操作数时(如MUL指令的Rn),不用的寄存器域应设置为0(由编译器自动完成).ARM乘法指令操作码见表3-7.
表3-7 ARM乘法指令操作码
(1)MUL——32位乘法指令.
MUL指令将Rm和Rs中的值相乘,结果的低32位保存到Rd中.指令格式如下:
MUL{cond}{S}Rd,Rm,Rs
MUL指令举例如下:
MUL R1,R2,R3;R1=R2×R3
MULS R0,R3,R7;R0=R3×R7,同时设置CPSR中的N位和Z位
(2)MLA——32位乘加指令.
MLA指令将Rm和Rs中的值相乘,再将乘积加上第3个操作数,结果的低32位保存到Rd中.指令格式如下:
MLA{cond}{S}Rd,Rm,Rs,Rn
MLA指令举例如下:
MLA R1,R2,R3,R0;R1=R2×R3+R0
(3)UMULL——64位无符号乘法指令.
UMULL指令将Rm和Rs中的值作无符号数相乘,结果的低32位保存到RdLo中,而高32位保存到RdHi中.指令格式如下:
UMULL{cond}{S} RdLo,RdHi,Rm,Rs
UMULL指令举例如下:
UMULL R0,R1,R5,R8;(R1、R0)=R5×R8
(4)UMLAL——64位无符号乘加指令.
UMLAL指令将Rm和Rs中的值作无符号数相乘,64位乘积与RdHi、RdLo相加,结果的低32位保存到RdLo中,而高32位保存到RdHi中.指令格式如下:
UMLAL{cond}{S} RdLo,RdHi,Rm,Rs
UMLAL指令举例如下:
UMLAL R0,R1,R5,R8;(R1、R0)=R5×R8+(R1、R0)
(5)SMULL——64位有符号乘法指令.
SMULL指令将Rm和Rs中的值作有符号数相乘,结果的低32位保存到RdLo中,而高32位保存到RdHi中.指令格式如下:
SMULL{cond}{S} RdLo,RdHi,Rm,Rs
SMULL指令举例如下:
SMULL R2,R3,R7,R6;(R3、R2)=R7×R6
(6)SMLAL——64位有符号乘加指令.
SMLAL指令将Rm和Rs中的值作有符号数相乘,64位乘积与RdHi、RdLo相加,结果的低32位保存到RdLo中,而高32位保存到RdHi中.指令格式如下:
SMLAL{cond}{S} RdLo,RdHi,Rm,Rs
SMLAL指令举例如下:
SMLAL R2,R3,R7,R6;(R3、R2)=R7×R6+(R3、R2)
3.2.6 ARM分支指令
在ARM中有两种方式可以实现程序的跳转,一种是使用分支指令直接跳转,另一种则是直接向PC寄存器赋值实现跳转.分支指令有分支指令B、带链接的分支指令BL、带状态切换的分支指令BX.
ARM分支指令见表3-8.
表3-8 ARM分支指令
(1)B——分支指令.
B指令跳转到指定的地址执行程序.指令格式如下:
B{cond}label
指令编码格式:
signed_immed_24 24位有符号立即数(偏移量).实际跳转的目的地址通过以下方法计算出来:(1)将该24位有符号数的补码通过符号扩展方式扩展到32位数;(2)将结果左移2位;(3)将左移后的结果加上PC的值.
分支指令B举例如下:
B WAITA; 跳转到WAITA标号处
B 0x1234 ; 跳转到绝对地址0x1234处
分支指令B限制在当前指令的±32M字节地址范围内(ARM指令为字对齐,最低2位地址固定为0).
(2)BL——带连接的分支指令.
BL指令先将下一条指令的地址拷贝到R14(即LR)连接寄存器中,然后跳转到指定地址运行程序.指令格式如下:
BL{cond}label
指令编码格式:
signed_immed_24 24位有符号立即数(偏移量).实际跳转的目的地址通过以下方法计算出来:
①将该24位有符号数的补码通过符号扩展方式扩展到32位数.
②将结果左移2位.
③将左移后的结果加上PC的值,再加上8.
带连接的分支指令BL举例如下:
BL DELAY
分支指令BL限制在当前指令的±32M字节地址范围内.BL指令用于子程序调用.
(3)BX——带状态切换的分支指令.
BX指令跳转到Rm指定的地址执行程序,若Rm的位[0]为1,则跳转时自动将CPSR中的标志T置位,即把目标地址的代码解释为Thumb代码;若Rm的位[0]为0,则跳转时自动将CPSR中的标志T复位,即把目标地址的代码解释为ARM代码.指令格式如下:
BX{cond} Rm
指令编码格式:
Rm目标地址寄存器.带状态切换的分支指令BX举例如下:
ADRL R0,ThumbFun+1
BX R0 ;跳转到R0指定的地址,并根据R0的最低位来切换处;理器状态
3.2.7 ARM协处理器指令
ARM支持协处理器操作,协处理器的控制要通过协处理器命令实现.
ARM协处理器指令见表3-9.
表3-9 ARM协处理器指令
续表
(1)CDP——协处理器数据操作指令.
ARM处理器通过CDP指令通知ARM协处理器执行特定的操作.该操作由协处理器完成,即对命令的参数的解释与协处理器有关,指令的使用取决于协处理器.若协处理器不能成功地执行该操作,将产生未定义指令异常中断.指令格式如下:
CDP{cond} coproc,opcode1,CRd,CRn,CRm{,opcode2}
其中: coproc 指令操作的协处理器名.标准名为pn,n为0~15.
opcode1 协处理器的特定操作码.
CRd 作为目标寄存的协处理器寄存器.
CRn 存放第1个操作数的协处理器寄存器.
CRm 存放第2个操作数的协处理器寄存器.Opcode2可选的协处理器特定操作码.
指令编码格式:
cp_num 为协处理器编号.
CDP指令举例如下:
CDP p7,0,c0,c2,c3,0;协处理器7操作,操作码为0,可选操作码为0
CDP p6,1,c3,c4,c5 ;协处理器6操作,操作码为1
(2)LDC——协处理器数据读取指令.
LDC指令从某一连续的内存单元将数据读取到协处理器的寄存器中.进行协处理器数据的传送,由协处理器来控制传送的字数.若协处理器不能成功地执行该操作,将产生未定义指令异常中断.指令格式如下:
LDC{cond}{L}coproc,CRd,<地址>
其中:L可选后缀,指明是长整数传送.coproc指令操作的协处理器名.标准名为pn,n为0~15.CRd作为目标寄存的协处理器寄存器.<地址>指定的内存地址.
指令编码格式:
cp_num 为协处理器编号.
8_bit_word_offset 8位立即数偏移.
P,U,W 用于区别不同的地址模式.P表示前/后变址,U表示加/减,W表示回写.
N 数据大小(依赖于协处理器).
LDC 指令举例如下:
LDC p5,c2,[R2,#4] 读取R2+4指向的内存单元的数据,传送到协处理器p5的c2寄存器中
LDC p6,c2,[R1] 读取R1指向的内存单元的数据,传送到协处理器p6的c2寄存器中
(3)STC——协处理器数据写入指令.
STC指令将协处理器的寄存器数据写入到某一连续的内存单元中.进行协处理器数据的数据传送,由协处理器来控制传送的字数.若协处理器不能成功地执行该操作,将产生未定义指令异常中断.指令格式如下:
STC{cond}{L}coproc,CRd,<地址>
其中: L可选后缀,指明是长整数传送.
coproc指令操作的协处理器名.标准名为pn,n为0~15.
CRd作为目标寄存的协处理器寄存器.
<地址>指定的内存地址.
指令编码格式:
cp_num 为协处理器编号.
8_bit_word_offset 8位立即数偏移.
P,U,W 用于区别不同的地址模式.P表示前/后变址,U表示加/减,W表示回写.
N 数据大小(依赖于协处理器).
STC指令举例如下:
STC p5,c1,[R0]
STC p5,c1,[R0,#-0x04]
(4)MRC——协处理器寄存器到ARM寄存器到的数据传送指令.
MRC指令将协处理器寄存器中的数据传送到ARM处理器的寄存器中.若协处理器不能成功地执行该操作,将产生未定义指令异常中断.指令格式如下:
MCR{cond}coproc,opcode1,Rd,CRn,CRm{,opcode2}
其中:coproc 指令操作的协处理器名.标准名为pn,n为0~15.
opcode1 协处理器的特定操作码.
Rd 作为目标寄存的协处理器寄存器.
CRn 存放第1个操作数的协处理器寄存器.
CRm 存放第2个操作数的协处理器寄存器.
opcode2 可选的协处理器特定操作码.
指令编码格式:
cp_num为协处理器编号.
MCR指令举例如下:
MCR p6,2,R7,c1,c2
MCR p7,0,R1,c3,c2,1
指令编码格式:
cp_num 为协处理器编号.
MRC 指令举例如下:
MRC p5,2,R2,c3,c2
MRC p7,0,R0,c1,c2,1
3.2.8 ARM杂项指令
杂项指令见表3-10.
表3-10 ARM杂项指令
(1)SWI——软中断指令.
SWI指令用于产生软中断,从而实现在从用户模式变换到管理模式,CPSR保存到管理模式的SPSR中,执行转移到SWI向量.在其他模式下也可使用SWI指令,处理器同样地切换到管理模式.指令格式如下:
SWI{cond}immed_24
其中:immed_24 24位立即数,值为0~16777215之间的整数.指令编码格式:
SWI指令举例如下:
SWI 0;软中断,中断立即数为0
SWI 0x123456;软中断,中断立即数为0x123456
使用SWI指令时,通常使用以下两种方法进行传递参数,SWI异常中断处理程序就可以提供相关的服务,这两种方法均是用户软件协定.SWI异常中断处理程序要通过读取引起软中断的SWI指令,以取得24位立即数.
①指令中24位的立即数指定了用户请求的服务类型,参数通过通用寄存器传递.
MOV R0,#34;设置子功能号为34
SWI 12 ;调用12号软中断
②指令中的24位立即数被忽略,用户请求的服务类型由寄存器R0的值决定,参数通过其他的通用寄存器传递.
MOV R0,#12 ;调用12号软中断
MOV R1,#34 ;设置子功能号为34
SWI 0
在SWI异常中断处理程序中,取出SWI立即数的步骤为:首先确定引起软中断的SWI指令是ARM指令还是Thumb指令,这可通过对SPSR访问得到;然后要取得该SWI指令的地址,这可通过访问LR寄存器得到;接着读出指令,分解出立即数.如程序清单3-2所示.
程序清单3-2 读取SWI立即数
(2)MRS——读状态寄存器指令.
在ARM处理器中,只有MRS指令可以将状态寄存器CPSR或SPSR读出到通用寄存器中.指令格式如下:
MRS{cond} Rd,psr
其中:Rd目标寄存器.Rd不允许为R15.
psr CPSR或SPSR.
指令编码格式:
R用于区别CPSR(R为0)或SPSR(R为1).
MRS指令举例如下:
MRS R1,CPSR;将CPSR状态寄存器读取,保存到R1中
MRS R2,SPSR;将SPSR状态寄存器读取,保存到R2中
MRS指令读取CPSR,可用来判断ALU的状态标志,或IRQ、FIQ中断是否允许等.在异常处理程序中,读SPSR可知道进行异常前的处理器状态等.MRS与MSR配合使用,实现CPSR或SPSR寄存器的读—修改—写操作,可用来进行处理器模式切换、允许/禁止IRQ/FIQ中断等设置,如程序清单3-3、程序清单3-4所示.另外,进程切换或允许异常中断嵌套时,也需要使用MRS指令读取SPSR状态值,并将其保存起来.
程序清单3-3 IRQ中断
程序清单3-4 禁能IRQ中断
(3)MSR——写状态寄存器指令.
在ARM处理器中,只有MSR指令可以直接设置状态寄存器CPSR或SPSR.指令格式如下:
MSR{cond} psr_fields,#immed_8r
MSR{cond} psr_fields,Rm
其中:psr CPSR或SPSR.
Fields 指定传送的区域.fields可以是以下的一种或多种(字母必须为小写):
c控制域屏蔽字节(psr[7…0]);
x扩展域屏蔽字节(psr[15…8]);
s状态域屏蔽字节(psr[23…16]);
f标志域屏蔽字节(psr[31…24]).
immed_8r 要传送到状态寄存器指定域的立即数,8位.
Rm要传送到状态寄存器指定域的数据的源寄存器.指令编码格式(操作数为立即数):
指令编码格式(操作数为寄存器):
R 用于区别CPSR(R为0)或SPSR(R为1).
field_mask 域屏蔽.
rotate_imm 立即数对齐.
8_bit_immediate 8位立即数.
Rm 操作数寄存器.MSR指令举例如下:
MSR CPSR_c,#0xD3 ;CPSR[7…0]=0xD3,即切换到管理模式
MSR CPSR_cxsf,R3 ;CPSR=R3
只有在特权模式下才能修改状态寄存器.
程序中不能通过MSR指令直接修改CPSR中的T控制位来实现ARM状态/Thumb状态的切换,必须使用BX指令完成处理器状态的切换(因为BX指令属分支指令,它会打断流水线状态,实现处理器状态切换).MRS与MSR配合使用,实现CPSR或SPSR寄存器的读—修改—写操作,可用来进行处理器模式切换、允许/禁止IRQ/FIQ中断等设置,如程序清单3-5所示.
程序清单3-5 堆栈指令初始化
3.2.9 ARM伪指令
ARM伪指令不是ARM指令集中的指令,只是为了编程方便编译器定义了伪指令,使用时可以像其他ARM指令一样使用,但在编译时这些指令将被等效的ARM指令代替.ARM伪指令有四条,分别为ADR伪指令、ADRL伪指令、LDR伪指令、NOP伪指令.
(1)ADR——小范围的地址读取伪指令.
ADR指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中.在汇编编译源程序时,ADR伪指令被编译器替换成一条合适的指令.通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现,则产生错误,编译失败.ADR伪指令格式如下:
ADR{cond} register,expr
其中:register加载的目标寄存器.
expr地址表达式.
当地址值是非字对齐时,取值范围-255~255字节之间;当地址值是字对齐时,取值范围-1020~1020字节之间.对于基于PC相对偏移的地址值时,给定范围是相对当前指令地址后两个字处(因为ARM7TDMI为三级流水线).
ADR伪指令举例如下:
LOOP MOV R1,#0xF0
…
ADR R2,LOOP ;将LOOP的地址放入R2
ADR R3,LOOP+4
可以用ADR加载地址,实现查表,如程序清单3-6所示.
程序清单3-6 小范围地址的加载
(2)ADRL——中等范围的地址读取伪指令.
ADRL指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中,可以读取比ADR伪指令更大范围的地址.在汇编编译源程序时,ADRL伪指令被编译器替换成两条合适的指令.若不能用两条指令实现ADRL伪指令功能,则产生错误,编译失败.ADRL伪指令格式如下:
ADRL{cond} register,expr
其中:register加载的目标寄存器.
expr地址表达式.
当地址值是非字对齐时,取值范围-64~64KB之间;当地址值是字对齐时,取值范围-256~256KB之间.
ADRL伪指令举例如下:
ADRL R0,DATA_BUF
…
ADRL R1,DATA_BUF+80
…
DATA_BUF
SPACE100;定义100字节缓冲区
可以用ADRL加载地址,实现程序跳转,如程序清单3-7所示.
程序清单3-7 中等范围地址的加载
(3)LDR——大范围的地址读取伪指令.
LDR伪指令用于加载32位的立即数或一个地址值到指定寄存器.在汇编编译源程序时,LDR伪指令被编译器替换成一条合适的指令.若加载的常数未超出MOV或MVN的范围,则使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入文字池,并使用一条程序相对偏移的LDR指令从文字池读出常量.LDR伪指令格式如下:
LDR{cond} register,=expr/label-expr
其中:register加载的目标寄存器.
expr 32位立即数.
label-expr基于PC的地址表达式或外部表达式.
LADR伪指令举例如下:
LDR R0,=0x12345678 ;加载32位立即数0x12345678
LDR R0,=DATA_BUF+60;加载DATA_BUF地址+60
…
LTORG ;声明文字池
…
伪指令LDR常用于加载芯片外围功能部件的寄存器地址(32位立即数),以实现各种控制操作,如程序清单3-8所示.
程序清单3-8 加载32位立即数
从PC到文字池的偏移量必须小于4KB.
与ARM指令的LDR相比,伪指令的LDR的参数有“=”号.
(4)NOP——空操作伪指令.
NOP伪指令在汇编时将会被代替成ARM中的空操作,比如可能为“MOV R0,R0”指令等.NOP伪指令格式如下:
NOP
NOP可用于延时操作,如程序清单3-9所示.
程序清单3-9 软件延时
思考与练习
1.基础知识
(1)ARM920TDMI有几种寻址方式?LDR R1,[R0,#0x08]属于哪种寻址方式?
(2)ARM指令的条件码有多少个?默认条件码是什么?
(3)ARM指令中第二个操作数有哪几种形式?列举5个8位图立即数.
(4)LDR/STR指令的偏移形式有哪4种?LDRB和LDRSB有何区别?
(5)请指出MOV指令与LDR加载指令的区别及用途.
(6)CMP指令的操作是什么?写一程序,判断R1的值是否大于0x30,是则将R1减去0x30.
(7)调用子程序是用B还是用BL指令?请写出返回子程序的指令.
(8)请指出LDR伪指令的用法.指令格式与LDR加载指令的区别是什么?
(9)ARM状态与Thumb状态的切换指令是什么?请举例说明.
2.有符号和无符号加法
下面给出A和B的值,可先手动计算A+B,并预测N、Z、V和C标志位的值.然后修改程序清单4.1中R0、R1的值,将这两个值装载到这两个寄存器中(使用LDR伪指令,如LDRR0,=0xFFFF0000),使其执行两个寄存器的加法操作.调试程序,每执行一次加法操作就将标志位的状态记录下来,并将所得结果与预先计算得出的结果相比较.如果两个操作数看做是有符号数,如何解释所得标志位的状态?同样,如果这两个操作数看做是无符号数,所得标志位又当如何理解?
0xFFFF000F 0x7FFFFFFF 67654321(A)+0x0000FFF1+ 0x02345678 + 23110000(B)
结果:( )( )( )
3.数据访问
把下面的C代码转换成汇编代码.数组a和b分别存放在以0x4000和0x5000为起始地址的存储区内,类型为long(即32位).把编写的汇编语言进行编译连接,并进行调试.
for(i=0;i<8;i++)
{a[i]=b[7-i];
}
4.阶乘计算
计算一个数n的阶乘,即n!=n×(n-1)×(n-2)×…×3×2×1.
给定n的值后,整个算法就是不断使当前值与前一次乘数减一所得值相乘,这里所说的当前值即是乘法运算的结果.程序不断循环执行乘法操作,每次循环先将乘数减一,若所得值为0则循环结束.在程序中,使用条件执行的思想来做乘法.在编写含有循环和转移指令的程序时,由于可以用Z标志来迅速判断是否到达循环次数,很多编程者通常使用一个非零数向下计数而不是向上计数的方法来启动程序.
请填充下面的代码段,并加入相应的段声明信息,然后调试程序的正确性.并在设定n的值为10时,说明程序执行结果,并观察程序运行之前和之后寄存器的内容.
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。