
嵌入式系统第三章ARM指令系统.ppt
57页第三章第三章 ARM指令系统指令系统3.1 数据处理指令数据处理指令3.2 数据传送指令数据传送指令3.3 控制流指令控制流指令3.4 汇编语言程序汇编语言程序汇编语言的源语句行的一般格式是:汇编语言的源语句行的一般格式是:•{label} {instruction | directive|pseudo‑instruction} {;comment} •注意注意: :•即使没有标号,指令、伪指令和命令前面也必须有一个空白,例如一个空格或制表符•源语句行的所有三部分都是可选的可以使用空行来使代码更具可读性•大小写规则大小写规则•指令记忆码、命令和符号寄存器名称可以用大写或小写编写,但不能混合使用大小写•标号标号: :•标号是表示地址的符号在汇编时计算由标号指定的地址汇编器计算标号的地址,该地址是相对于所定义标号所在段的原点的引用相同段内的一个标号时,可以使用程序计数器加上或减去一个偏移量这被称为相对程序的寻址 其它段中的标号地址是在链接时计算的,此时链接程序已在存储器中为每个段分配了具体的位置•注释注释: :•一行中的第一个分号用于标记注释的开始,但不包括出现在一个字符串常数内的分号。
行的末尾就是注释的结束一个注释本身就是一个有效的行汇编程序将忽略所有注释•常数常数: : 常数可以数值、布尔值、字符或字符串•数字 可以接受下列形式的数字常数:十进制数,如 123十六进制数,如, 0x7B•布尔值 布尔常数 TRUE和 FALSE 必须书写为 {TRUE} 和 {FALSE}•字符 字符常数由左、右单引号组成,中间括住单个字符或一个转义字符,采用标准的 C 转义字符•字符串 字符串由左、右双引号括住多个字符或空格而组成如果在一个字符串内使用了双引号或美元字符作为文字文本字符,则必须用一对适当的字符来表示它们例如,如果需要在字符串内使用单个 $,则必须使用 $$在字符串常数内可以使用标准的 C 转义序列•ARM 汇编语言模块的示例汇编语言模块的示例 AREA ARMex, CODE, READONLY ; Name this block of code ARMex ENTRY ; Mark first instruction to execute start MOV r0, #10 ; Set up parameters MOV r1, #3 ADD r0, r0, r1 ; r0 = r0 + r1 stop MOV r0, #0x18 ; angel_SWIreason_ReportException LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit SVC #0x123456 ; ARM semihosting (formerly SWI) END ; Mark end of file 3.1 数据处理指令数据处理指令•ARM的数据处理指令使得程序员能够完成寄存器中数据的算术和逻辑操作。
•其他指令只是传送数据和控制程序的顺序•数据处理指令的典型特征是需要两个操作数,产生单个结果•所有的操作数是32位,或来自寄存器、或是指令中定义的立即数•如果有结果,则结果为32位宽并只能在寄存器中•每一个操作数寄存器和结果寄存器都在指令中独立指定,,也即ARM指令使用3地址模式ADDr0, r1, r2 ; r0 := r1 + r2•注意指令中操作数的顺序,r0是结果寄存器,然后是第一操作数(r1),最后是第二操作数(r2)•当指令执行后,对系统状态而言唯一的变化是目的寄存器r0的值•CPSR中的标志位(N、Z、C、V)也可有选择地变化1、、算术操作算术操作•这类指令对两个32位操作数进行二进制算术操作• ADD r0 , r1 , r2 ; r0 := r1 + r2• ADC r0 , r1 , r2 ; r0 := r1 + r2 + C• SUB r0 , r1 , r2 ; r0 := r1 - r2• SBC r0 , r1 , r2 ; r0 := r1 - r2 + C - 12、、逻辑操作逻辑操作•这类指令对输入操作数的对应位进行指定的布尔逻辑操作。
• ANDr0 , r1 , r2;r0 [i] := r1 [i] and r2 [i]• ORRr0 , r1 , r2;r0 := r1 or r2• EORr0 , r1 , r2;r0 := r1 xor r2• BICr0 , r1 , r2;r0 := r1 and not r23、、寄存器传送操作寄存器传送操作•这些指令无第一操作数(省略),只是简单的将第二操作数传送到结果寄存器• MOV r0 , r2;r0 := r2• MVN r0 , r2;r0 := not r24、、比较操作比较操作•这类指令不产生结果(省略结果寄存器),仅影响CPSR中的相应位(N、Z、C、V)• CMP r1 , r2;r1 - r2 cc• CMN r1 , r2 ;r1 + r2 cc• TST r1 , r2 ;r1 and r2 cc• TEQ r1 , r2 ;r1 xor r2 cc5、立即数操作、立即数操作•因为立即数是在32位指令中编码,所以不可能将32位都作为立即数,只能是一个8位数并按两位数为边界进行的调整。
•立即数=(0->255)x 22n0<=n<=12• ADDr3 , r3 , #1;r3 := r3 + 1• ANDr8 , r7 , #& ff;r8 := r7 [7:0]6、、寄存器移位操作寄存器移位操作•这类指令允许第二个操作数(寄存器)在同第一个操作数(寄存器)运算之前完成移位操作• ADDr3 , r2 , r1 , LSL #3 ;r3 := r2 + r1 x 8•这是一条在单个时钟周期内执行的ARM指令,许多处理器采用独立的移位操作指令•LSL(逻辑左移)、LSR(逻辑右移) •ASL(算术左移)、ASR(算术右移)•ROR(循环右移)7、、设置条件码设置条件码•如果程序员需要,任何数据处理指令都能设置条件码,但必须在指令中显示的提出来(比较指令除外)• ADDSr2 , r2 , r0;r1r0 64位数1,r3r2 64位数2• ADCr3 , r3 , r1;结果在r3r2中3.2 数据传送指令数据传送指令•指在ARM寄存器和存储器、I/O设备之间传送数据•常用的有2种数据传送指令:1、单寄存器的Load和Store指令:LDR、STR2、多寄存器的Load和Store指令:LDRM、STRM•ARM的数据传送指令都是基于寄存器间接寻址及各种变化,如基址偏移寻址、基址变址寻址等(没有直接寻址)。
•寄存器间接寻址:LDR r0 , [r1]、STR r0 , [ r1] LDR r0 , [ r1 ] STR r0 , [ r1 ]•在任何情况下都需要有一个ARM寄存器来寄存地址,其他形式的寻址都是建立在这种形式之上的•寄存器间接寻址利用一个寄存器的值(基址寄存器)作为存储器地址;或在此基础上加上立即数构成基址偏移寻址:LDRr0 , [ r1 , #4 ]•或加上另一个寄存器偏移量构成基址变址寻址:LDRr0 , [ r1 , r2 ]•初始化寄存器——地址指针初始化寄存器——地址指针•要访问一个特定的存储器单元(因ARM是寄存器间接寻址的),必须把一个寄存器初始化,使之包含存储器单元的地址•ADR伪指令COPYADRr1 , TABLE1ADRr2 , TABLE2…TABLE1…TABLE2…1、单寄存器的Load和Store指令:LDR、STR•这些指令提供最灵活的单数据项传送方式,数据项可以是字节、字、半字COPYADRr1 , TABLE1ADRr2 , TABLE2LDRr0 , [ r1 ]STRr0 , [ r2 ]…TABLE1…TABLE2…使用数据处理指令为下一次传送修改基址寄存器,即COPYADRr1 , TABLE1ADRr2 , TABLE2LDRr0 , [ r1 ]STRr0 , [ r2 ]ADDr1 , r1 , #4ADDr2 , r2 , #4…TABLE1…TABLE2…•所有Load和Store指令只能使用这种寄存器间接寻址的简单形式,其他形式均在此基础上变化,其目的是使代码更高效。
基址偏移寻址(修改指针)基址偏移寻址(修改指针)l LDR r0 , [ r1 , #4 ] ;r0 := mem[ r1 + 4 ]这是一个前变址寻址模式,这种模式可以使用一个基址寄存器来访问位于同一区域的多个存储单元l可以使用带有自动变址自动变址的前变址寻址来实现对基址寄存器的修改,这样可以让程序追踪一个数据表• LDR r0 , [ r1 , #4 ] !;r0 := mem[ r1 + 4 ];r1 := r1 + 4l上面程序中的惊叹号表示在开始传送数据后,基址寄存器将更新在ARM中自动变址并不花费额外的时间,但它严格的等效于先执行一条简单的寄存器间接Load指令,再执行一条数据处理指令,避免了额外的指令时间和代码空间开销LDR r0 , [ r1+4 ]ADD r1 , r1 + 4l后变址寻址:LDR r0 , [ r1 ] , #4;r0 := mem[ r1 ];r1 := r1 + 4这是一个后变址寻址模式,它允许基址不加偏移即作为传送地址使用,而后再自动变址它等同于两条指令,但其速度快且占用较少的代码空间COPYADRr1 , TABLE1ADRr2 , TABLE2LOOPLDRr0 , [ r1 ] , #4STRr0 , [ r2 ] , #4?……TABLE1…TABLE2…比较:COPYADRr1 , TABLE1ADRr2 , TABLE2LDRr0 , [ r1 ]STRr0 , [ r2 ]ADDr1 , r1 , #4ADDr2 , r2 , #4…TABLE1…TABLE2…2、多寄存器的Load和Store指令:•为保存和恢复处理器状态以及在存储器中移动数据块提供了一种有效方式。
•它节省代码空间,使操作速度比顺序执行等效的单寄存器Load、Store指令快达4倍•举例:LDMIA r1 , { r0 , r2 , r5 } ;r0 := [ r1 ] ;r2 := [ r1+4 ] ;r5 := [ r1+8 ]•注意:该指令执行完后r1内容不变LDMIA r1! , { r0 , r2 , r5 } ;r0 := [ r1 ] ;r2 := [ r1+4 ] ;r5 := [ r1+8 ]•注意:该指令执行完后r1改变。
堆栈寻址:堆栈寻址:•堆栈经常作为一个线性的数据结构来实现,当加入数据时它就向下增大存贮空间(递减);而数据移走时,它又缩回来•堆栈指针总是保持在当前栈顶的地址,它指向最后压入堆栈的有效数据(满栈),或指向一个数据空位(空栈)•满递减:堆栈随着减小存储器地址而向下增长,基址寄存器指向存储有效数据的最低地址STMFDr13! , { r2 – r9 };将寄存器压入堆栈LDMFDr13! , { r2 – r9 };从堆栈中恢复•注意:在堆栈操作中几乎总是要指定自动变址,以便保证堆栈指针具有一致的行为•ARM体系结构中使用多寄存器指令完成堆栈操作进栈使用STM指令,出栈使用LDM指令•在使用堆栈时,需要确定堆栈在存储器空间是向上生长还是向下生长的,向上称为递增(Ascending),向下称为(Descending)•满堆栈是指SP指向最后一个进栈的有效数据•空堆栈是指SP指向最后一个进栈的有效数据的下一个空地址•LDM和STM指令往往结合下面一些参数实现堆栈操作FD满递减堆栈ED空递减堆栈FA满递增堆栈EA空递增堆栈 ARM制定了ARM-Thumb过程调用标准(ATPCS),在标准中,堆栈被定义为满递减式。
因此,LDMFD和STMFD指令分别用来支持出栈和进栈操作递增A(入栈时)递减D(入栈时)满空满空增值先增STMFALDMED后增STMEALDMFD减值先减LDMEASTMFD后减LDMFASTMED块拷贝块拷贝(多寄存器数据传送寻址模式):(多寄存器数据传送寻址模式):•块拷贝概念基于数据被存储到基址寄存器的地址(指针)以上还是以下,以及地址的增(I)减(D)开始于存储了第一个数据之前(B)还是之后(A)•对于Load、Store操作,这两种概念的映射是不同的(见8086方向标志)增值先增STMIBLDMIB后增STMIALDMIA减值先减LDMDBSTMDB后减LDMDASTMDA•例如:把8个字从r0指向的位置拷贝到r1指向的位置,即LDMIAr0 ! , { r2 – r9 }STMIAr1 , { r2 – r9 }•执行后,r0增加了32这是由于“!”使其自动变址8个字节,而r1没有改变•如果r2-r9含有有用的数据,则可以先把它们压入堆栈,即STMFDr13 ! , { r2 - r9 }; 将寄存器压栈LDMIAr0 ! , { r2 – r9 }STMIAr1 , { r2 – r9 }LDMFDr13 ! , { r2 – r9 };从堆栈中恢复•后缀“FD”表示满递减堆栈寻址模式。
注意,在堆栈操作中几乎总是要指定自动变址,以便保证堆栈指针具有一致的行为•多寄存器Load和Store指令为保存和恢复处理器状态以及在存储器中移动数据块提供了一种有效方式它节省代码空间,并且其执行速度提高了4倍•这个重要的优点说明,值得认真考虑数据在存储器中的组织方式,以便增大使用多寄存器传送指令去访问存储器的潜力递增A(入栈时)递减D(入栈时)满空满空增值先增STMIBSTMFALDMIBLDMED后增STMIASTMEALDMIALDMFD减值先减LDMDBLDMEASTMDBSTMFD后减LDMDALDMFASTMDASTMED3.3 控制流指令控制流指令•控制流指令既不处理数据,也不存取数据,它只是确定下一步执行哪一条指令•转移指令•条件转移•条件执行•转移链接•子程序返回•监控程序调用•跳转表转移指令转移指令((无条件转移)无条件转移)•将程序的执行从一个位置切换到另一个位置最常用的方法是使用转移(branch)指令,BLABEL…LABEL…•或LABEL……BLABEL条件转移条件转移•有时想让微处理器决定是否进行转移MOV r0 , #0;计数器初始化LOOP…ADDr0 , r0, #1;循环计数器加一CMP r0 , #10;与循环的限制比较BNELOOP;如不相等则返回…;否则循环终止转移转移解释解释一般应用一般应用B无条件总是执行转移BEQ相等BNE不等BPL正BMI负BCC BLO无进位、低于无符号数比较BCS BHS有进位、高于或相等无符号数比较BVC无溢出有符号数比较BVS有溢出有符号数比较BGT大于有符号数比较BGE大于或相等有符号数比较BLT小于有符号数比较BLE小于或相等有符号数比较BHI高于无符号数比较BLS低于或相等无符号数比较•列在表中同一行的一对条件(如和)的含义相同,并得到同样的二进制代码。
但两者都是有用的,因在特定的环境中,每一种条件都可能使汇编语言源代码的编译更加容易•当表中提到有符号数和无符号数的比较时,它并不是要选择比较指令本身,而只是支持操作数选择的解释条件执行条件执行•ARM指令集有一条不寻常的特征,就是条件执行不仅应用于转移指令,也应用于所有的ARM指令CMPr0 , #5BEQBYPASS;if ( r0 != 5 ) {ADD r1 , r1 ,r0 ;r1 := r1+ r0 - r2SUB r1 , r1 ,r2 ;}BYPASS…•替代为CMP r0 , #5; if ( r0 != 5 ) {ADDNEr1 , r1 ,r0; r1 := r1+ r0 - r2SUBNEr1 , r1 ,r2; }…•要激活条件执行,须在3字符的操作码之后增加2字符的条件码(条件码应在其它任何修正码之前)if ( ( a == b) && ( c == d ) ) e++;CMPr0 , r1CMPEQr2 , r3ADDEQr4 , r4 , #1•由于第二个比较指令使用了条件执行,从而实现了if语句中的逻辑“与”•使用条件执行的示例使用条件执行的示例 Euclid (欧几里德)的最大公因子 (gcd) 算法的两种实现方法。
它说明了如何能使用条件执行来改进代码密度和执行速度 在 C 语言中,该算法可以表示如下:int gcd(int a, int b) { while (a != b) { if (a > b) a = a ‑ b; else b = b ‑ a; } return a; } •采用下列代码,可以只用带条件跳转来实现 gcd 函数:gcd CMP r0, r1 BEQ end BLT less SUBS r0, r0, r1 ; could be SUB r0, r0, r1 for ARM B gcd less SUBS r1, r1, r0 ; could be SUB r1, r1, r0 for ARM B gcd end •由于跳转数目的原因,该代码的长度是七条指令每次获得一个跳转时,处理器必须重填流水线并从新位置继续执行其它指令和未执行的跳转各使用一个周期•通过使用 ARM 指令集的条件执行功能,可以仅用四个指令实现 gcd 函数:gcd CMP r0, r1 SUBGT r0, r0, r1 SUBLE r1, r1, r0 BNE gcd END•除了减小了代码大小之外,此代码在大多数情况下执行速度更快。
在这种情况下,用条件执行的所有指令来代替跳转后,可节省三个周期当r0 = r1 时两种代码的执行周期数相等在其他情况下,代码的条件执行的执行周期数最少 所有指令为条件执行所有指令为条件执行 R0:aR1:bInstruction Cycles(7)12CMP r0, r1 112SUBGT r0,r0,r1 1 (未执行) 11SUBLT r1,r1,r0 111BNE gcd 311CMP r0,r1 111SUBGT r0,r0,r1 1 (未执行) 11SUBLT r1,r1,r0 1 (未执行) 11BNE gcd 1 (未执行) 总计 = 10 所有指令为条件跳转所有指令为条件跳转 R0:aR1:bInstruction Cycles(7)12CMP r0, r1 112BEQ end 1 (未执行) 12BLT less 312SUB r1, r1, r0 112B gcd 311CMP r0, r1 111BEQ end 3总计 = 13 转移链接指令:转移链接指令:•通常要调用子程序,并且当子程序执行完时能够恢复到原来的代码位置,这就需要把执行转移之前程序计数器r15的值保存下来。
•ARM使用转移链接指令BL来提供这一功能该指令完全像转移指令一样执行转移,并将转移后面紧接的一条指令的地址保存到连接寄存器r14中BLSUBR; 转移到SUBR…;返回到这里…;SUBR…;子程序入口…MOV pc , r14;返回•注意:由于返回地址保存在r14寄存器里,子程序不应嵌套,否则,新的返回地址将覆盖原来的返回地址常规做法是将r14入栈BLSUB1; 转移到SUB1…;返回到这里SUB1STMFD r13! , { r0-r2, r14 };保存现场BLSUB2…SUB2 …子程序返回指令:子程序返回指令:•为了返回调用程序,必须将转移链接指令保存在r14中的程序计数器值拷贝到程序寄存器SUBR…;子程序入口…MOV pc , r14;把r14拷贝到r15来返回•对子程序嵌套返回SUB1STMFDr13 ! , { r0-r2 , r14 }BLSUB2…LDMFDr13 ! , { r0-r2 , pc } AREA subrout, CODE, READONLY ; Name this block of code ENTRY ; Mark first instruction to execute start MOV r0, #10 ; Set up parameters MOV r1, #3 BL doadd ; Call subroutine stop MOV r0, #0x18 ; angel_SWIreason_ReportException LDRr1, =0x20026 ; ADP_Stopped_ApplicationExit SVC #0x123456 ; ARM semihosting (formerly SWI) doadd ADD r0, r0, r1 ; Subroutine code BX lr ; Return from subroutine END ; Mark end of file监控程序调用监控程序调用•监控程序提供了委托访问系统资源的方式,对用户级程序它更像一个专门的子程序入口。
指令集包含一个专门的指令SWI,用来调用访问输入输出这类功能•如:把r0底部字节中的字符送到显示器,SWISWI_WriteC;输出r0[ 7:0 ]•或,把控制从用户程序返回到监控程序,SWISWI_Exit;返回到监控程序跳转表跳转表:•跳转表的思想是程序员有时想调用一系列子程序中的一个,但究竟调用哪一个须由程序的计算值确定(如r0)如传统的,BLJUMPTAB…JUMPTABCMP r0 , #0;假设值在r0BEQSUB0CMP r0 , #1BEQSUB1CMP r0 , #2BEQSUB2…;当子程序列表较长时,该方案非常慢•一个有效的解决方案是利用程序计数器在通用寄存器文件中的可见性来实现的BLJUMPTAB…JUMPTABADRr1 , SUBTABCMPr0 , #SUBMAX;如果OKLDRLSpc , [ r1,r0,LSL #2] ;则进入表中BERROR;否则报警SUBTABDCDSUB0;子程序表入口DCDSUB1DCDSUB2… ENTRY ; mark the first instruction to callstart MOV r0, #1 ; set up the three parameters MOV r1, #3 MOV r2, #2 BL arithfunc ; call the functionstop MOV r0, #0x18 ; angel_SWIreason_ReportException LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit SWI 0x123456 ; ARM semihosting SWIarithfunc ; label the function CMP r0, #num ; Treat function code as unsigned integer MOVHS pc, lr ; If code is >= num then simply return ADR r3, JumpTable ; Load address of jump table LDR pc, [r3,r0,LSL#2] ; Jump to the appropriate routineJumpTable DCD DoAdd DCD DoSubDoAdd ADD r0, r1, r2 ; Operation 0 MOV pc, lr ; ReturnDoSub SUB r0, r1, r2 ; Operation 1 MOV pc,lr ; Return END ; mark the end of this file•DCD指示汇编器保留一个字,并将它们初始化为右边表达式的值——标号的地址。
•不管表中有多少子程序,以及它们使用的频度如何互不相关,这种方法的性能不变•在读跳转表时,若超出了表的末端,结果将是灾难性的,因此,检查越限是必须的或CMPr0 , #SUBMAXBHIEERORLDRpc , [ r1 , r0 , LSL #2 ] …3.4 汇编语言程序汇编语言程序AREAHellow, CODE, READONLY;声明代码区SWI_WriteEQU&0;输出r0中的字符SWI_ExitEQU&11;程序结束ENTRY;代码入口STARTADRr1 , TEXT;指针LOOPLDRBr0 , [ r1 ] , #1;读下一字节CMPr0 , #0;检查文本终点SWINESWI_Write;打印BNELOOP;SWISWI_Exit;执行结束TEXT=“Hellow,World” , &0a , & 0d , 0END;程序结束•这段程序说明了ARM汇编语言和指令集的许多特征:•带有适当属性的代码AREA的声明;•系统调用的定义;•使用ARD伪指令,以便将地址写入基址寄存器;•使用自动变址寻址,以扫描一系列字节;•SWI指令的条件执行,以避免额外的转移•使用字节“0”标记字符串(回车和换行之后)的结束。
只要采用循环结构,就必须保证它有结束的条件AREABlkCpy, CODE,READONLY;块拷贝SWI_WriteEQU&0;输出r0中的字符SWI_ExitEQU&11;程序结束ENTRYADRr1,TABLE1ADRr2,TABLE2ADRr3,T1ENDLOOP1LDRr0,[ r1 ] , #4STRr0,[ r2 ] , #4CMPr1,r3BLTLOOP1ADRr1,TABLE2LOOP2LDRBr0,[ r1 ] , #1CMPr0,#0SWINESWI_WriteBNELOOP2SWI_ExitTABLE1=“This is the right string!”, &0a , &0d , 0;DCBT1ENDALIGN;保证字对齐TABLE2=“This is the wrong string!”, 0ENDAREAHex_Out, CODE, READONLYSWI_WriteEQU&0;输出r0中的字符SWI_ExitEQU&11;程序结束ENTRY;代码入口LDRr1,VALUE ;读取要打印的数据BLHexOue;调用16进制输出SWISWI_Exit;结束VALUEDCD&12345678;测试数据HexOutMOVr2,#8;半字节数LOOPMOVr0,r1 , LSR #28;读取顶半字节CMPr0,#9;0~9还是A~F?ADDGTr0,r0, # ”A”-10;ASCII字母ADDLEr0,r0 , #”0”;ASCII数字SWISWI_Write;打印字符MOVr1,r1 , LSL #4;左移4位SUBSr2,r2 , #1;半字节数减1BNELOOP;若还有MOVpc , r14;返回END。
