文章目录
1. CISC与RISC指令集
2. ARM汇编指令
3. 汇编与机器码
4. 汇编指令格式
5. MOV指令
6. BL指令
7. B指令
8. ADD/SUB指令
9. LDR/STR指令
1. CISC与RISC指令集
根据指令的复杂度,所有CPU可以分为两类:
CISC(Complex Instruction Set Computer)
- 描述:复杂指令集计算机,具有较多且复杂的指令。
- 代表:如x86架构的处理器。
- 特点:
- 指令复杂,单条指令可执行多个操作。
- 指令长度不固定,可能会占用多个CPU周期。
- 适合编写复杂的操作,但指令解码复杂,功耗较高。
RISC(Reduced Instruction Set Computing)
- 描述:精简指令集计算机,指令集较少且简单。
- 代表:如ARM架构的处理器。
- 特点:
- 指令简单,每条指令通常只执行一个操作。
- 指令长度固定,通常为32位,执行速度快。
- 易于设计和优化,适合嵌入式系统和移动设备。
加法运算示例
以加法运算 a = a + b
为例,说明CISC和RISC的区别:
CISC指令
使用CISC指令集计算机,如x86提供的加法指令,可以通过一条指令完成如下4步操作:
- 读取a
- 读取b
- 计算a + b
- 把结果写回a
ADD a, b ; 将b加到a并将结果存储在a中
这条指令可能需要多个CPU周期来完成。
RISC指令
RISC指令集不提供“单条指令”完成加法操作,需要调用多条简单指令:
LDR R0, [a] ; 将a的值加载到寄存器R0
LDR R1, [b] ; 将b的值加载到寄存器R1
ADD R0, R0, R1 ; 将R0和R1的值相加,结果存储在R0
STR R0, [a] ; 将结果存储回a
这些指令每条只执行一个操作,执行速度快,指令解码简单。
2. ARM汇编指令
ARMv7-A/R指令集
- ARM指令集:大约150条基础指令。这些指令主要用于32位操作。
- Thumb指令集:约60条指令,用于优化代码密度的16位操作。
- Thumb-2指令集:结合了Thumb和ARM指令的特点,增加了额外的指令,使得Thumb指令集扩展到100多条指令。
ARMv8-A指令集
- A64指令集:用于64位操作,有超过100条指令。
- A32指令集:与ARMv7的ARM指令集向下兼容。
- T32指令集:与ARMv7的Thumb-2指令集向下兼容。
常见指令类别
- 数据处理指令:包括算术运算、逻辑运算、位操作等,例如ADD, SUB, MUL, AND, ORR。
- 加载和存储指令:用于内存和寄存器之间的数据传输,例如LDR, STR。
- 分支指令:用于改变程序执行的顺序,例如B, BL, BX。
- 系统指令:控制系统级操作,例如MRS, MSR。
- SIMD和浮点指令:用于多媒体处理和科学计算,例如VADD, VMUL。
ARM芯片属于精简指令集计算机(RISC),它所用的指令有以下特点:
对内存只有读、写指令
- LDR:加载指令,从内存读取数据到寄存器。
- STR:存储指令,从寄存器写数据到内存。
对于数据的运算在CPU内部实现
- ADD:加法指令,在寄存器之间进行加法运算。
- SUB:减法指令,在寄存器之间进行减法运算。
- MUL:乘法指令,在寄存器之间进行乘法运算。
使用RISC指令的CPU复杂度小
- 指令长度固定:通常为32位,执行速度快。
- 易于设计和优化:指令解码简单,功耗低。
在实际应用中,使用RISC架构的ARM处理器进行编程时,通常使用高级编程语言(如C语言)。编译器会将C语言代码翻译成ARM汇编指令,然后进一步翻译成机器码运行在处理器上。
示例代码:
int a = 0x12;
int b = 0x34;
int result;result = a + b;
编译器生成的汇编指令:
LDR R0, =0x12 ; 将立即数0x12加载到寄存器R0
LDR R1, =0x34 ; 将立即数0x34加载到寄存器R1
ADD R2, R0, R1 ; 将R0和R1的值相加,结果存储在R2
STR R2, [result] ; 将结果存储到变量result中
3. 汇编与机器码
在STM32单片机中,使用ARM Cortex-M系列处理器的汇编指令,可以直接对内存和寄存器进行操作。通过一系列的简单汇编指令,我们可以完成复杂的运算操作。下面我们详细解释各个步骤及对应的汇编指令。
示例:计算乘积
我们以加法运算 a = a + b
为例,详细解析每一步的操作及其对应的汇编指令。
假设我们有两个数 a
和 b
,它们的值分别为 0x12 和 0x34,我们希望计算它们的乘积 c = a * b
,并将结果存储在内存中。
汇编代码示例
; 初始化
mov r3, #addr_a ; 将变量 a 的地址传给 CPU 寄存器 r3
ldr r0, [r3] ; 从 r3 所指的内存把 a 的值读进 CPU 寄存器 r0
mov r4, #addr_b ; 将变量 b 的地址传给 CPU 寄存器 r4
ldr r1, [r4] ; 从 r4 所指的内存把 b 的值读进 CPU 寄存器 r1; 计算乘积
mul r2, r0, r1 ; 将 r0 和 r1 的值相乘,结果存储在 r2; 存储结果
mov r5, #addr_c ; 将变量 c 的地址传给 CPU 寄存器 r5
str r2, [r5] ; 将 r2 的值写入 r5 所指的内存
在上面的示例中,mov
、ldr
、str
等指令都是汇编指令,或称为“助记符”,帮助我们记忆和理解这些操作。这些指令实际上是一个个数字,我们去记这些数字的难度很大,所以使用这些助记符来简化理解。
助记符与机器码的关系
在机器码的指令格式中,操作码(opcode)用来表示不同的指令。例如:
- mov 对应的操作码是
0010
- add 对应的操作码是
0001
每个助记符指令都对应特定的机器码,这些机器码由操作码和操作数组成,通常占用32位。例如:
mov r1, r0 ; 机器码可能为 00100000 00000001 00000000
汇编指令与机器码的转换
汇编指令和机器码的关系是固定的,但不同的编译器可能会对一些指令进行优化或使用不同的格式。在实际编程中,机器码通过汇编器或编译器自动生成。
; 初始化
mov r3, #addr_a ; 机器码: 0xE3A03000 ; 将立即数 addr_a 加载到寄存器 r3
ldr r0, [r3] ; 机器码: 0xE5930000 ; 从内存地址 r3 加载数据到寄存器 r0
mov r4, #addr_b ; 机器码: 0xE3A04000 ; 将立即数 addr_b 加载到寄存器 r4
ldr r1, [r4] ; 机器码: 0xE5941000 ; 从内存地址 r4 加载数据到寄存器 r1; 计算乘积
mul r2, r0, r1 ; 机器码: 0xE0000291 ; 将 r0 和 r1 的值相乘,结果存储在 r2; 存储结果
mov r5, #addr_c ; 机器码: 0xE3A05000 ; 将立即数 addr_c 加载到寄存器 r5
str r2, [r5] ; 机器码: 0xE5852000 ; 将寄存器 r2 的值存储到内存地址 r5
MOV指令
- 汇编指令:
mov r3, #addr_a
- 机器码:
0xE3A03000
- 解释:
E3A0
表示MOV指令,3
表示目标寄存器r3
,0000
表示立即数#addr_a
。
4. 汇编指令格式
在ARM汇编语言中,每条指令都有一个固定的格式,便于CPU解释和执行。这些指令由标签(label)、指令(instruction)和注释(comment)组成,下面我们详细解释各部分的含义和使用方法。
label: instruction @ comment
- label(标签):标签表示代码中的一个位置,通常用于跳转(branch)指令的目标地址。标签以冒号(:)结尾。
- instruction(指令):汇编指令或伪指令,用于操作寄存器、内存和CPU状态。
addnum: mov r0, #0 @ 将 R0 寄存器设置成 0
5. MOV指令
在ARM架构中,MOV指令用于将一个寄存器的值移动到另一个寄存器,或者将一个常数加载到寄存器。
示例指令:
mov r1, #10 @ 将10赋值给寄存器r1,即r1=10
指令执行过程
-
取指:首先,CPU从内存的
addrA
地址取出机器码e3a0100a
(即mov r1, #10
指令)。 -
译码:CPU解释取出的机器码,发现它是一条MOV指令。
-
执行:CPU将立即数10赋值给寄存器R1。
机器码解析
机器码e3a0100a
的各个位的含义如下
- cond(条件码):[31:28]位,值为0xe,表示总是执行,即条件码为
AL
(Always)。 - I:第25位,值为1,表示操作数是立即数。
- OpCode:操作码,表示MOV指令。
- S:第20位,值为0,表示不更新条件标志位。
- Rn:[19:16]位,值为0,MOV指令中无关。
- Rd:[15:12]位,值为1,表示目的寄存器为R1。
- Operand2(操作数2):[11:0]位,立即数0x0a,即10。
对应的二进制:
1110 0011 1010 0000 0001 0000 0000 1010
1110
:条件码,为AL
(Always)。0011 1010
:MOV指令的操作码部分。0000
:Rn字段,不适用。0001
:Rd字段,表示寄存器R1。0000 0000 1010
:立即数10的二进制表示。
6. BL指令
在ARM汇编中,BL
(Branch with Link)指令用于跳转到指定的标签地址,同时将返回地址记录在LR
(Link Register)寄存器里。
示例代码:
1 bl test_tag @ 跳转到test_tag
2 mov r1, #10 @ 将10赋值给r1
3
4 test_tag:
5 mov r3, #0 @ 将0赋值给r3
6 mov pc, lr @ 返回调用点,继续执行mov r1, #10指令
-
取指
- CPU从内存地址
addrA
取出机器码eb000000
(即bl test_tag
指令),执行后PC跳转到test_tag
标签位置,即内存的addrA+8
地址。同时将内存的addrA+4
地址存储在寄存器LR
中。
- CPU从内存地址
-
执行标签位置的指令
- CPU从内存地址
addrA+8
取出机器码e3a03000
(即mov r3, #0
指令),执行后,将寄存器R3
的值设为0。 - CPU从内存地址
addrA+12
取出机器码e1a0f00e
(即mov pc, lr
指令),执行后,PC跳转到内存地址addrA+4
。 - CPU从内存地址
addrA+4
取出机器码e3a0100a
(即mov r1, #10
指令),执行后,将寄存器R1
的值设为10。
- CPU从内存地址
BL指令的机器码
机器码eb000000
:
- cond(条件码):[31:28]位,值为0xe,表示总是执行,即条件码为
AL
(Always)。 - Offset(偏移量):[23:0]位,表示PC与标签的偏移值。
在此例子中,偏移值为0,这是因为ARM采用三级流水线的方式,即取指、译码、执行指令。执行addrA
地址的bl test_tag
指令时,PC已经指向addrA+8
,所以偏移值要设置为0。
偏移值的计算
在ARM架构中,BL
指令的偏移量计算是基于当前指令地址(即PC的值)与目标地址(标签地址)之间的差值,偏移量表示为目标地址与当前地址的差值除以4。
例如:
- 当前指令地址为
addrA
,下一条指令地址为addrA+4
。 - 偏移量
imm24
表示为目标地址与当前指令地址的差值除以4。
因此,当bl test_tag
指令执行时:
- PC值为
addrA+8
,目标地址为addrA+8
(test_tag
标签的地址),所以偏移量为0。
7. B指令
B 指令用于无条件跳转到指定的标签地址。执行该指令时,程序计数器(PC)被更新为目标地址。相比 BL 指令,它不会保存下一条指令的地址到 LR 寄存器。
示例代码:
b test_tag
mov r1, #10
test_tag:
mov r3, #0
指令执行过程:
- 第 1 行,CPU 执行
b test_tag
指令,跳转到test_tag
标签处执行mov r3, #0
指令。 b
指令不保存返回地址,不同于BL
指令。
8. ADD/SUB指令
示例代码:
mov r1, #10 ; 将 10 赋值给寄存器 r1
add r2, r1, #4 ; r2 = r1 + 4
sub r2, r1, #4 ; r2 = r1 - 4
指令执行过程
-
取指:
- CPU 从内存的
addrA
地址取出机器码e3a0100a
(即mov r1, #10
指令),执行后,CPU 内部寄存器 R1 等于 10。
- CPU 从内存的
-
译码、执行:
- CPU 从内存的
addrA+4
地址取出机器码e2812004
(即add r2, r1, #4
指令),执行后,CPU 内部寄存器 R2 等于 14。 - CPU 从内存的
addrA+8
地址取出机器码e2412004
(即sub r2, r1, #4
指令),执行后,CPU 内部寄存器 R2 等于 6。
- CPU 从内存的
机器码:
add r2, r1, #4
- 机器码:
e2812004
- 解析:指令各位的解析如下:
- [31:28] 位是条件码
0xe
; - [19:16] 位是源寄存器 R1,即 0x1;
- [15:12] 位是目标寄存器 R2,即 0x2;
- [11:0] 位是立即数 4,即 0x004。
- [31:28] 位是条件码
指令 | 机器码 | 条件码 | 目标寄存器 | 源寄存器 | 立即数 |
---|---|---|---|---|---|
mov r1, #10 | e3a0100a | 0xe | R1 (0x1) | N/A | 0x00a |
add r2, r1, #4 | e2812004 | 0xe | R2 (0x2) | R1 (0x1) | 0x004 |
sub r2, r1, #4 | e2412004 | 0xe | R2 (0x2) | R1 (0x1) | 0x004 |
9. LDR/STR指令
LDR(Load Register)和STR(Store Register)指令是ARM指令集中用来从内存加载数据到寄存器和从寄存器存储数据到内存的指令。以下是一个示例汇编代码及其执行过程。
代码示例:
mov r0, #0x400 @ 将立即数 0x400 存入寄存器 r0
mov r1, #0xa @ 将立即数 0xa 存入寄存器 r1
str r1, [r0] @ 将寄存器 r1 中的值 0xa 存入寄存器 r0 指向的地址(0x400)
ldr r2, [r0] @ 将寄存器 r0 指向地址 0x400 的值(0xa)加载到寄存器 r2
指令执行过程:
mov r0, #0x400:将立即数0x400加载到寄存器R0。
- CPU从内存地址addrA处取出机器码e3a00b01,执行后,CPU内部寄存器R0等于0x400。
mov r1, #0xa:将立即数0xa加载到寄存器R1。
- CPU从内存地址addrA+4处取出机器码e3a0100a,执行后,CPU内部寄存器R1等于0xa。
str r1, [r0]:将寄存器R1的值存储到寄存器R0指向的内存地址0x400。
- CPU从内存地址addrA+8处取出机器码e5801000,执行后,寄存器R1的0xa值存储到地址0x400。
ldr r2, [r0]:将寄存器R0指向地址0x400的数据加载到寄存器R2。
- CPU从内存地址addrA+12处取出机器码e5902000,执行后,寄存器R0指向地址0x400的数据加载到寄存器R2,R2等于0xa。