计算机原理与嵌入式系统笔记:第三篇
ARM指令
- 采用Load-Store结构
- 固定长度(32bit)指令
- 三操作数指令格式
- 条件执行所有指令
- 一条指令可以装载或存储多个寄存器
- ALU操作支持单周期n-bit移位
Load-Store结构
- 指令集中的数据运算:
- 不能对存储器中的数据直接进行操作
- 仅能处理寄存器中的值,而且总是将处理结果放回到寄存器中
- 对存储器中数据的操作:
- 仅能将存储器的值 加载(load) 到寄存器中
- 或将寄存器的值 存储(store) 到存储器中
ARM指令不支持“存储器-存储器”操作!
汇编格式
- 寄存器传送:
- 比较:
<opcode2>{<cond>} <Rn>, <shifter_operand>
<opcode2> := CMP | CMN | TST | TEQ
- 运算:
<opcode3>{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
<opcode3> := ADD | SUB | RSB | ADC | SBC | RSC | AND | BIC | EOR | ORR
CMP | CMN | TST | TEQ
没有目标寄存器
MOV | MVN
没有第一个源操作数
立即数
必考的
立即数的范围:在32位指令字内编码
<immediate> = immed_8 循环右移(2*rotate_imm)
<immediate>
为32位的立即数immed_8
为8位的立即数rotate_imm
为4位的循环右移值
合法立即数:0xff
、0xff0
、0xff00
非合法立即数:0xff1
、0x1fe
如何判断立即数是否合法?
重点,理解方法。
1 | 0x0001_0000_0001 因立即数是八位循环右移 |
比较
CMP CMN TST TEQ
如上比较操作中的“减,加,与和异或”的结果不存储于任何目标寄存器。
乘法
这是ARM中罕见的四操作数指令——MLA
!
乘法:MUL r4, r3, r2 ; r4 := (r3×r2)[31:0]
乘累加:MLA r4, r3, r2, r1 ; r4 := (r3×r2+r1)[31:0]
考虑指令执行的效率,所有操作数都放在寄存器中。
移位
LSL LSR ASL ASR
- 前两个为逻辑移位(Logical),空出的位用0填充;
- 后两个为算术移位(Arithmetic),如果源操作数是正数,则空出的最高有效位用0填充,反之则用1填充。
ROR ROX
这俩是循环移位。
- ROX:循环右移1~31位,移出的字的最低有效位依次填入空出的最高有效位
- RRX:扩展1位的循环右移,空位(31)是用原来的标志位C填充,操作数右移1位
似乎左移都是0~31
位,但右移是1~32
位。应该是为了区分吧。
控制流指令
控制流指令确定程序下一步执行哪条指令,包含转移指令和条件转移指令。
B与BL为转移指令,带L(Link)的话将转移后下一条指令的地址传送到当前处理器模式下的链接寄存器(r14)。这一般用于实现子程序的调用,在子程序返回时将链接寄存器的内容拷贝回PC。
条件执行
条件域(condition field) 占据32位指令域的高4位。条件域共有16个值,每个值都根据CPSR寄存器中的标志位N、Z、C和V的值来确定指令是执行还是跳过。
ARM寻址
ARM中的寻址方式有九种,常用的有如下几种:
- 立即数寻址
- 寄存器寻址
- 寄存器间接寻址
- 寄存器偏移寻址
- 寄存器基址变址寻址
ARM没有直接寻址。
立即数寻址
立即数寻址就是直接将内存中的数据发给CPU作为操作数。ARM是32位指令集,因此立即数范围在0至28-1之间(八位立即数)。
1 | LDR r0, #254 ; 将254写入r0寄存器 |
寄存器寻址
寄存器寻址就是直接将寄存器中的数值作为操作数。
1 | LDR r1, r0 ; 将r0寄存器中的值写到r0 |
寄存器间接寻址
寄存器间接寻址也用到了寄存器,但是操作数不是寄存器里的值了,而是寄存器内地址所对应的操作数。操作数在内存里。
和寄存器寻址相比,在提供操作数地址的寄存器上加上[ ]
,比如[r0]
。
整体流程就是寄存器→地址→操作数。
1 | MOV r0, #0X54000032 ; 将0X54000032的地址赋给r0 |
寄存器偏移寻址
以寄存器寻址为本,将寄存器中的数移位后作为操作数。
1 | LDR r0, r1, LSL #3 ; 将r1的值逻辑左移3位后写入r0 |
寄存器基址变址寻址
多看
基址变址寻址是基于寄存器间接寻址的,只不过地址不再是寄存器中的值了,而是偏移后的值,这里的偏移值可以理解为地址相加值。
有时候会在第二个操作数后面加上!
,这代表加上自动变址的功能。前变址不会改变,因此有时候要加上!
。
前变址
前变址不改变r1寄存器的值,在有些情况下不方便。
加!
就成为前变址加自动变址(auto-indexed)。
1 | LDR r0, [r1, #3] ; 地址为:r1值+3字节,指令执行完r1不变 |
后变址
后变址允许基地址不加偏移即作为数据传送地址使用,而后再自动变址。
1 | LDR r0, [r1], #4 ; 地址为:r1值,但指令执行完后r1=r1+4 |
前后变址的“前后”是相对于基地址的偏移而言的。
前变址是先变再用,后变址是先用再变(自带自动变址)。
练习
- 在ARM指令系统的各种寻址方式中,获取操作数最快的方式是( )
- 若操作数在内存中的地址包含在指令中,则属于( )方式。
- A、直接寻址
- B、立即数寻址
- C、寄存器寻址
- D、寄存器间接寻址
答案
- B
- D
ADR
ADR:小范围的地址读取伪指令
ADR指令将基于PC相对偏移的地址值读取到寄存器中。在汇编编译源程序时,ADR被编译器替换成一条合适的指令(ADD or SUB)。
- 程序计数器PC(r15)的内容通常接近所需数据地址。
ADR r1, table1
指令被转换成PC(r15)加减一个常数。- 程序计数器相对寻址(PC relative addressing),PC值实际为当前指令地址+8。
PC相对寻址
重点,理解计算的方法!
- 程序计数器相对寻址(PC relative addressing):PC值=当前程序执行位置+8
- ARM7采用取指、译码、执行三级流水线结构。
- 程序计数器PC(r15)总是指向 “正在取指” 的指令,而不是指向“正在译码”的指令或“正在执行”的指令。一般来说,人们习惯性约定将“正在执行的指令作为参考点”,称之为当前第一条指令,因此PC总是指向第三条指令。
- 在ARM状态下,每条指令为4字节长,所以PC始终指向该指令地址加8字节的地址。
例:根据ARM的3级流水线结构,分析以下程序执行完成后 PC寄存器的内容。
1 | Mem[0x4000]: ADD PC, PC, #4 |
答案
因为是三级流水线:
1 | Mem[0x4000]: ADD PC, PC, #4 F D E |
在第一条指令执行时,PC指向取值的指令地址,即4008;在执行完第一条(即译码完第二条)之后,PC变为400C,随后执行完第二条(不再对新的指令进行取指),则此时PC不变。
帧机制
ARM64架构栈帧以及帧指针FP_arm64 帧指针-CSDN博客
汇编语言程序的基本结构
- 顺序结构
- 分支结构
- 循环结构
“程序设计理论已经证明,这三种结构是完备的,用它们可以写出任何功能的程序。”
证明自 Bohm C., Jacopini G. "Flow diagrams, Turing machines and languages with only two formation rules." Communications of the Association for Computing Machinery, Vol.9, pp. 366--371. 1966.
顺序结构
y = a * (b + c);
1 | ADR r4, b ; get address for b |
分支结构
if-else
if (a < b) x = 5; else x = c;
答案
1 | ADR r4, a ; get address for a |
switch
DCD
伪操作用于分配一片连续的字存储单元。
switch (test) { case 0: … ; case 1: … ; ……}
答案
1 | ADR r2, test ; get address for test |
循环结构
1+2+3+...+100
答案
1 | MOV r0, #0 |
那我问你,啊,那我问你,#100
是合法立即数吗?
废话。因为#100
是0x01100100
,小于八位的话可以直接认为是合法立即数。
FIR滤波器
答案
1 | MOV r0, #0 ; use r0 for i |
下次会讲讲多寄存器的L/S指令。