嵌入式软件工程师&&太极拳
全部博文(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