嵌入式软件工程师&&太极拳
全部博文(548)
分类: LINUX
2011-02-20 19:56:32
ARM指令
八、乘法指令
1、 mul乘法指令
格式: mul{}{s} Rd,Rm,Rs
代码: if(cond == 1)
{
Rd = Rm * Rs;
}
2、 mla乘累加指令
格式: mla{}{s} Rd,Rm,Rs,Rn
代码: if(cond == 1)
{
Rd = Rm * Rs + Rn;
}
3、 smull有符号长乘指令
格式: smull{}{s} RdLo,RdHi,Rm,Rs
代码: if(cond == 1)
{
RdHi:RdLo = Rm * Rs;
}
4、smlal有符号长乘累加指令
格式: smlal{}{s} RdLo,RdHi,Rm,Rs
代码: if(cond == 1)
{
RdHi:RdLo = Rm * Rs + RdHi:RdLo;
}
5、 umull无符号长乘指令
格式: umull{}{s} RdLo,RdHi,Rm,Rs
代码: if(cond == 1)
{
RdHi:RdLo = Rm * Rs;
}
6、 umlal无符号长乘累加指令
格式: umlal{}{s} RdLo,RdHi,Rm,Rs
代码: if(cond == 1)
{
RdHi:RdLo = Rm * Rs + RdHi:RdLo;
}
九、乘法指令位图
1、 32位
xxxx 0000 00as dddd nnnn ssss 1001 mmmm
Rm
if(a == 0)
乘法;
else
乘累加;
2、 64位
xxxx 0000 1uas hhhh llll ssss 1001 mmmm
if(u == 0)
无符号;
else
有符号;
十、分支指令
1、 b分支指令
格式: b{l}{} label
代码: if(cond == 1)
{
if(l == 1)
{
lr = pc + 4 - 8; //保存下一条指令,当把lr的值赋给pc时可实现返回(为什么要减去 8 请见第十二节 第7部分 )
}
pc = pc + offset - 8;
// pc = pc + offset + 8; offset是当前指令到lebel的偏移量,offset是正数 -8,负数 +8
}
2、b分支指令位图
xxxx 101L jjjj jjjj jjjj jjjj jjjj jjjj
if(L == 0)
指令 = b;
else
指令 = bl;
24 * j 用来保存地址,只能访问 0 - 16 M 的内存空间
指令是按每 4 个字节对齐的,所以先右移 2 位保存,取出值后再左移 2 位,可以访问 0 - 64 M
但是label的值可以达到 4 G,64 M - 4 G无法访问
如果 24 位来表示当前指令的地址到label的偏移量,正数表示向前偏移,负数表示向后偏移
这样就可以访问当前指令的前后 32 M了(pc的值就是当前指令的地址)
3、 bx分支切换指令
格式: bx{} Rm
代码: if(cond == 1)
{
pc = Rm & 0xfffffffc; //pc = Rm & 0xfffffffe;T = Rm[0];
}
注: bx分arm版本和thumb版本,thumb版本不能条件执行,blx要在ARMv5T及以上版本才有
十一、单寄存器load-store指令
1、 ldr 32 位数据读取指令
格式: ldr{} Rd,addr //addr用法请见第十二节内存寻址方式
代码: if(cond == 1)
{
val = *(int *)addr;
if(Rd == pc)
pc = val & 0xfffffffc; //pc = val & 0xfffffffe;T = val[0];
else
Rd = val;
}
2、 ldr 16 位数据读取指令
格式: ldr{}h Rd,addr
代码: if(cond == 1)
{
Rd = *(short *)addr & 0x0000ffff;
}
3、 ldr 16 位有符号数据读取指令
格式: ldr{}sh Rd,addr
代码: if(cond == 1)
{
val = *(short *)addr & 0x0000ffff;
if(val[15] == 1)
{
Rd = 0xffff0000;
Rd |= val;
}
else
{
Rd = val;
}
}
4、 ldr 8 位数据读取指令
格式: ldr{}b Rd,addr
代码: if(cond == 1)
{
val = *(char *)addr;
Rd = val & 0x000000ff;
}
5、 ldr 8 位有符号数据读取指令
格式: ldr{}b Rd,addr
代码: if(cond == 1)
{
val = *(char *)addr & 0x000000ff;
if(val[7] == 1)
{
Rd = 0xffffff00;
Rd |= val;
}
else
{
Rd = val;
}
}
6、 str 32 位数据写入指令
格式: str{} Rd,addr
代码: if(cond == 1)
{
*(int *)addr = Rd;
}
7、 str 16 位数据写入指令
格式: str{}h Rd,addr
代码: if(cond == 1)
{
*(short *)addr = Rd[15:0];
}
8、 str 8 位数据写入指令
格式: str{}b Rd,addr
代码: if(cond == 1)
{
*(char *)addr = Rd[7:0];
}
注: 没有str有符号数据写入指令
十二、内存寻址方式
1、 label
举例: ldr r0,label //ldr r0,label + 0
lebel:
.word 0x12345
代码: r0 = *(int *)label; //r0 == 0x12345
2、 前变址
格式: [Rn,op]
代码: addr = Rn + op;
注: addr只是地址,还没有取值
举例: ldr R0,[R1,#8]
代码: addr = R1 + 8;
R0 = *(int *)addr;
3、 回写前变址
格式: [Rn,op]!
代码: addr = Rn + op;
Rn = Rn + op;
4、 后变址
格式: [Rn],op
代码: addr = Rn;
Rn = Rn + op;
5、 ldr、ldrb、str、strb指令的op
立即数: #+/-offset_12
寄存器: +/-Rm
寄存器移位: +/-Rm,<移位符> #number
注: #+/-offset_12 = +/-(offset_12[7:0] ror offset_12[11:8])(与前面的立即数构造方法一样)
number最大为 0x1f,没有 +/-Rm,<移位符> Rs方式的寄存器移位
+号可以省略
立即数等于 0 可以省略
6、 ldrh、ldrsh、ldrsb、strh指令的op
立即数: #+/-offset_8
寄存器: +/-Rm
注: offset_8最大为255,没有寄存器移位
7、Rn == pc
说明: 当Rn == pc时,addr = 当前指令的地址 + 8 + op
这是流水线的问题,因为执行当前这条指令时pc已经指向下两条指令了,所有我们计算op时应该减去 8
举例: 0 ldr r0,[pc,#-4]
4 .word 0x12345
8
这两行指令等于 mov r0,#0x12345,但是 0x12345 不能构造成立即数,所以可以通过这种方式来赋值
我们不能写成 ldr r0,[pc,#4],因为执行这条指令时pc已经执行地址 8 的指令,应该减去 4 而不是加 4
本来按道理应该加 4 的现在变成减 4,所以等于减去了 8
十三、多寄存器方式
1、 ldm / stm
格式: {}{ia|ib|da|db} Rn{!},R_list //{r0 - r15}^ / {r0,r5,r9,pc},如果顺序不对,编译器会自动按从R0-R15调整
代码: if(cond == 1) // ^表示cpsr --> spsr或spsr -->cpsr
{
if(ia == 1) //执行后增加, 如果寻址模式为空,默认是ia
addr = Rn;
else if(ib == 1) //执行前增加
addr = Rn + 4;
else if(da == 1) //执行后减少
addr = Rn - sizeof(R_list) + 4; //sizeof(R_list) == 寄存器数 * 4
else //db == 1 //执行前减少
addr = Rn - sizeof(R_list);
if(ldm == 1)
ldm_func(addr,R_list);
else //stm == 1
stm_func(addr,R_list);
if(! == 1)
{
if(ia == 1 || ib == 1)
Rn += sizeof(R_list);
else //da == 1 || db == 1
Rn -= sizeof(R_list);
}
}
void ldm_func(addr,R_list)
{
for(i = 0;i <= 14;i++)
{
if(R_list[i] == 1) //R_list[i] 表示是否有该寄存器
R_list[i] = *(int *)addr; //R_list[i] 表示该寄存器的值
addr += 4;
}
if(R_list[15] == 1) //R_list有pc
{
pc = *(int *)addr & 0xfffffffc; //pc = *(int *)addr & 0xfffffffe;T = *(int *)addr[0];
}
if(^ == 1)
{
cpsr = spsr;
}
}
void stm_func(addr,R_list)
{
for(i = 0;i <= 15;i++)
{
if(R_list[i] == 1)
*(int *)addr = R_list[i];
addr += 4;
}
if(^ == 1)
{
spsr = cpsr;
}
}
2 、更新基地址的指令对,可实现栈的push和pop操作
stmia <=======> ldmdb
stmib <=======> ldmda
stmda <=======> ldmib
stmdb <=======> ldmia
举例: stmdb sp!,{r1,r2,r3}
ldmia sp!,{r1,r2,r3}
执行后 sp,r1,r2,r3 的值保持不变
3、 栈操作
栈向高地址扩展的为递增栈(A,ascending),向低地址扩展的为递减栈(D,descending)
栈指针(sp)指向的是有效数据称为满栈(F,full),指向的是无效数据称为空栈(E,empty)
在ARM-Thumb过程调用标准(ATPCS)中,栈被定义为递减式满栈
寻址方式 说明 push =stm pop =ldm
FA 递增满 stmfa stmib ldmfa ldmda
// FD 递减满 stmfd stmdb ldmfd ldmia //arm汇编的push和pop操作指令
EA 递增空 stmea stmia ldmea ldmdb
ED 递减空 stmed stmda ldmed ldmib
十四、交换指令
格式: swp{}{b} Rd,Rm,[Rn] //[Rn]不能加立即数和其它方式
代码: if(cond == 1)
{
if(b == 1)
{
Rd = *(int *)Rn;
*(int *)addr = Rm;
}
else // b == 0
{
Rd = *(char *)Rn & 0x000000ff;
*(char *)Rn = Rm & 0x000000ff;
}
}
注: 交换数据是在一条指令内完成的,可以保证在交换的过程中不会被任何中断打断
十五、swi(software interrupt)软件中断指令
格式: swi{} SWI_number
代码: if(cond == 1)
{
lr_svc = pc + 4 - 8; //当前指令的下一条指令
spsr_svc = cpsr; //保存cpsr
cpsr[4:0] = 0b10011; //进入管理模式
cpsr[5] = 0; //执行arm状态
cpsr[7] = 1; //屏蔽irq中断
pc = 0x00000008 //跳转中断向量表的swi
}
SWI_number是中断号,用 24 位表示,最大为 #0xffffff,#号可以省略,直接把中断号存在指令里,不用按立即数方式构造
arm-linux的中断号见内核源码include/arch/arm/include/asm/unistd.h //一般从0x900000开始编号
x86-linux的中断号见系统目录/usr/include/asm/unistd.h //一般从0x0开始编号
十六、程序状态寄存器指令
1、 mrs
格式: mrs{} Rd,
2、 msr
格式: msr{} {_fields},
fields可以是f、x、s、f的任意组合,表示只对该域操作,默认全部
f 表示标志域 psr[31:24]
s 表示状态域 psr[23:16]
x 表示扩展域 psr[15:8]
c 表示控制域 psr[7:0]
十七、协处理器指令
ldp
mcr
mrc
ldc
stc