本帖最后由 andeyqi 于 2023-11-1 23:01 编辑
1 PC 相对寻址指令
1.1 auipc 指令格式
RISC-V 支持以当前PC指针为基地址的相对寻址的方式,对应的汇编指令为auipc指令,auipc 指令格式如下:
auipc rd,imm (rd = PC + Signed (imm << 12))
这条指令将imm 右移12位并带符号扩展至32位后得到一个立即数,这个立即数和PC的值相加存储到rd寄存器中,由于新的立即数表示的是地址的20位部分,并且是个有符号的立即数,因此这条指令能寻址的范围位基于当前PC偏移量±2GB,下图位 auipc 的指令编码格式,从指令介绍可知auipc 是个U-type的指令(长立即数指令)。
图 1.1.1
对于4KB 的寻址需结合其他的12bit 立即数相关处理指令配合完成,例如addi,ld 指令。
1.2 auipc 指令代码示例 从上面的描述可知auipc 指令可以根据当前pc指令±2GB 范围内的寻址,有了这个功能auipc指令就可以用来实现指令的长跳转,如下代码段是objdump 的反汇编的代码段,我们根据反汇编的代码来理解auipc指令。
- 1 2000a97c: 07f00513 li a0,127
- 2 2000a980: dfffb097 auipc ra,0xdfffb
- 3 2000a984: ed8080e7 jalr -296(ra) # 5858 <analog_read_reg8>
- 4 2000a988: 845f990b addigp s2,-1980 # 80044 <g_pm_status_info>
- 5 2000a98c: 0805785b bbc a0,0,2000aa1c <main+0x790>
- 6 2000a990: 5a14fc5b bbs s1,1,2000ab48 <main+0x8bc>
- 7 2000a994: 47c1 c.li a5,16
- 8 2000a996: 00f90123 sb a5,2(s2)
- 9 2000a99a: bc19 c.j 2000a3b0 <main+0x124>
复制代码
上述代码的第二行会根据当前pc地址(2000a980) + signed (0xdfffb<<12) = 0x2000a980 + -(0xffffffff - 0xdfffb000 +1) = 0x2000a980 +(-0x20005000) = 0x5980 更新到ra。 第三行代码会针对ra 做一次offset加载更为ra = 0x5980 +(-296) = 0x5858 jalr 回跳转到对应的0x5858 地址进行运行,jalr 指令我们稍后继续学习,通过objdump反汇编的文件可知0x5858地址对应的代码如下: - 00005858 <analog_read_reg8>:
- 5858: 300026f3 csrr a3,mstatus
- 585c: 8aa1 c.andi a3,8
- 585e: c299 c.beqz a3,5864 <analog_read_reg8+0xc>
- 5860: 300477f3 csrrci a5,mstatus,8
- 5864: 801407b7 lui a5,0x80140
- 5868: 18a78023 sb a0,384(a5) # 80140180 <_DATA_LMA_START+0x60133e28>
- 586c: 00100713 li a4,1
- 5870: 18e781a3 sb a4,387(a5)
- 5874: 04000613 li a2,64
- 5878: 80140737 lui a4,0x80140
- 587c: 18c78123 sb a2,386(a5)
- 5880: 18270783 lb a5,386(a4) # 80140182 <_DATA_LMA_START+0x60133e2a>
- 5884: fe77fe5b bbs a5,7,5880 <analog_read_reg8+0x28>
- 5888: 18470503 lb a0,388(a4)
- 588c: 0ff57513 andi a0,a0,255
- 5890: 00068463 beqz a3,5898 <analog_read_reg8+0x40>
- 5894: 3006a6f3 csrrs a3,mstatus,a3
- 5898: 00008067 ret
复制代码对应的是analog_read_reg8 这个函数,从上面的代码代码可以auipc + jalr指令可以实现函数的调用功能。 2 直接寻址
2.1 lui 指令格式
上面介绍auipc 指令的时候,官方的文档上是把auipc 和 lui 指令一起介绍的,从图1.1.1的指令介绍可以知道lui和auipc指令编码非常像。不同的地方lui指令的opcode为0110111,auipc 的指令opcode为0010111,lui指令的格式如下:
lui rd,imm (rd = Signed (imm << 12))
lui指令:把imm立即数左移12位得到一个新的32位立即数,并且符号扩展到64位存储到rd寄存器中。
- .align 3
- .global load_store_test
- load_store_test:
- li t0, 0x90000
- lw t1,(t0)
- sw t1,4(t0)
- ret
复制代码代码段 2.1 - 2000b580 <load_store_test>:
- 2000b580: 000902b7 lui t0,0x90
- 2000b584: 0002a303 lw t1,0(t0) # 90000 <__global_pointer$+0xf800>
- 2000b588: 0062a223 sw t1,4(t0)
- 2000b58c: 8082 c.jr ra
复制代码
li t0, 0x90000 最终被转换成 lui t0,0x90 t0 = (0x90<<12) = 0x90000,跟我们期待的加载的值也是一致的。
3 跳转指令
3.1 无条件跳转指令
RV32I 指令集提供了2条无条件跳转指令,对应的指令描述如下:
jal rd,imm (PC=PC+offset,rd = PC + 4) jalr rd,imm (PC=PC+offset,rd = PC + 4)
根据如上描述jal(jump and link),jalr(jump and link register)主要说明如下: - JAL(jump and link)指令使用J类型的指令编码,其中操作数offset[20:1]由指令编码的Bit[31:12]构成,它默认是2的倍数,因此它的跳转范围为当前PC值偏移±1 MB(2^20)范围
- JAL和JALR 指令都会把当前PC+4的值更新至rd寄存器,如果把返回地址存储到ra(x1)寄存器中,则可以实现函数返回
- JAL和JALR指令都是相对PC的跳转,可以用来帮助产生位置无关的代码
- JALR可以寻址到高20bit 的地址结合LUI 指令可以的低12bit的地址可以做到芯片内部的32bit的全地址空间的访问
RV32I 指令支持如下伪指令和对应的实际的指令关系如下:
4 开发板验证
4.1 汇编调用C函数验证
根据之前的理解我们可以通过auipc 和 jalr 指令即可实现函数的调用返回,不过需要知道对应的offset,这就要求我们知道我们的程序具体放在哪个位置及要跳转的函数在那个位置,从而计算出offset,这显然对程序开发人员是不友好的,这些地址都是在编译阶段确定的,这时体现伪指令的好处了,我们只要编写伪指令告诉编译器我要加载的符号,编译器自动帮我们转换成合适的指令省去我们手动计算的烦恼,编写如下c代码。
- void printf_test(void)
- {
- printf("TEST \r\n");
- }
复制代码
编写如下汇编函数调用对应的c函数。
- .extern printf_test
- .global call_test
- call_test:
- la t0,printf_test
- jr t0
- ret
复制代码 编写asm_test 2 指令调用call_test 汇编接口确认是否正常输出"TEST",运行结果如下跟预期的一致实现了C函数的调用。
图4.1.1
对应的伪指令被转换成如下的auipc 和 jr 指令完成C函数的调用。
- 200124e2 <call_test>:
- 200124e2: ffffe297 auipc t0,0xffffe
- 200124e6: 39a28293 addi t0,t0,922 # 2001087c <printf_test>
- 200124ea: 8282 c.jr t0
- 200124ec: 8082 c.jr ra
- 200124ee: 00000013 nop
复制代码
|