回答

收藏

[评测分享] 【Telink B91通用开发套件】(六)RISC-V 寻址与跳转指令学习

#板卡评测 #板卡评测 1712 人阅读 | 0 人回复 | 2023-10-31

本帖最后由 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. 1   2000a97c:        07f00513                  li        a0,127
  2. 2   2000a980:        dfffb097                  auipc        ra,0xdfffb
  3. 3   2000a984:        ed8080e7                  jalr        -296(ra) # 5858 <analog_read_reg8>
  4. 4   2000a988:        845f990b                  addigp        s2,-1980 # 80044 <g_pm_status_info>
  5. 5   2000a98c:        0805785b                  bbc        a0,0,2000aa1c <main+0x790>
  6. 6   2000a990:        5a14fc5b                  bbs        s1,1,2000ab48 <main+0x8bc>
  7. 7   2000a994:        47c1                        c.li        a5,16
  8. 8   2000a996:        00f90123                  sb        a5,2(s2)
  9. 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地址对应的代码如下:
  1. 00005858 <analog_read_reg8>:
  2.     5858:        300026f3                  csrr        a3,mstatus
  3.     585c:        8aa1                        c.andi        a3,8
  4.     585e:        c299                        c.beqz        a3,5864 <analog_read_reg8+0xc>
  5.     5860:        300477f3                  csrrci        a5,mstatus,8
  6.     5864:        801407b7                  lui        a5,0x80140
  7.     5868:        18a78023                  sb        a0,384(a5) # 80140180 <_DATA_LMA_START+0x60133e28>
  8.     586c:        00100713                  li        a4,1
  9.     5870:        18e781a3                  sb        a4,387(a5)
  10.     5874:        04000613                  li        a2,64
  11.     5878:        80140737                  lui        a4,0x80140
  12.     587c:        18c78123                  sb        a2,386(a5)
  13.     5880:        18270783                  lb        a5,386(a4) # 80140182 <_DATA_LMA_START+0x60133e2a>
  14.     5884:        fe77fe5b                  bbs        a5,7,5880 <analog_read_reg8+0x28>
  15.     5888:        18470503                  lb        a0,388(a4)
  16.     588c:        0ff57513                  andi        a0,a0,255
  17.     5890:        00068463                  beqz        a3,5898 <analog_read_reg8+0x40>
  18.     5894:        3006a6f3                  csrrs        a3,mstatus,a3
  19.     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寄存器中。

我们之前在【Telink B91通用开发套件】(五)RISC-V 存储加载指令学习 的如下测试测试代码中使用了li t0, 0x90000 指令,li 是个伪指令这里面的0x90000 实际可以通过0x90 >> 12位转换,实际代码也是转变成了lui指令来寻址更新,实际反汇编的代码段参见代码段 2.2。
  1. .align 3

  2. .global load_store_test
  3. load_store_test:
  4.         li t0, 0x90000
  5.         lw t1,(t0)
  6.         sw t1,4(t0)

  7.         ret
复制代码
代码段 2.1
  1. 2000b580 <load_store_test>:
  2. 2000b580:        000902b7                  lui        t0,0x90
  3. 2000b584:        0002a303                  lw        t1,0(t0) # 90000 <__global_pointer$+0xf800>
  4. 2000b588:        0062a223                  sw        t1,4(t0)
  5. 2000b58c:        8082                        c.jr        ra
复制代码
代码段 2.2

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


图 3.1

根据如上描述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 指令支持如下伪指令和对应的实际的指令关系如下:
图 3.2

4 开发板验证

4.1 汇编调用C函数验证


根据之前的理解我们可以通过auipc 和 jalr 指令即可实现函数的调用返回,不过需要知道对应的offset,这就要求我们知道我们的程序具体放在哪个位置及要跳转的函数在那个位置,从而计算出offset,这显然对程序开发人员是不友好的,这些地址都是在编译阶段确定的,这时体现伪指令的好处了,我们只要编写伪指令告诉编译器我要加载的符号,编译器自动帮我们转换成合适的指令省去我们手动计算的烦恼,编写如下c代码。
  1. void printf_test(void)
  2. {
  3.         printf("TEST \r\n");
  4. }
复制代码

编写如下汇编函数调用对应的c函数。
  1. .extern printf_test
  2. .global call_test
  3. call_test:
  4.         la t0,printf_test
  5.         jr t0

  6.         ret
复制代码
编写asm_test 2 指令调用call_test 汇编接口确认是否正常输出"TEST",运行结果如下跟预期的一致实现了C函数的调用。

图4.1.1

对应的伪指令被转换成如下的auipc 和 jr 指令完成C函数的调用。


  1. 200124e2 <call_test>:
  2. 200124e2:        ffffe297                  auipc        t0,0xffffe
  3. 200124e6:        39a28293                  addi        t0,t0,922 # 2001087c <printf_test>
  4. 200124ea:        8282                        c.jr        t0
  5. 200124ec:        8082                        c.jr        ra
  6. 200124ee:        00000013                  nop
复制代码



分享到:
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条