RISCV asm内嵌汇编

发布时间 2024-01-04 11:59:33作者: HiDark

引用自 使用asm函数嵌套汇编 - 知乎 (zhihu.com)

在ysyx和处理器学习中,经常遇到类似这样的语句asm volatile("li a5, -1; ecall");或蜂鸟中

    asm volatile (
       ".insn r 0x7b, 2, 1, x0, %1, x0"
           :"=r"(zero)
           :"r"(addr)
     );

这些都是通过asm嵌套汇编实现的,这篇文章就来梳理一下。

asm格式

asm ("汇编指令":"输出部分":"输入部分":"破坏描述部分")

​ 主要有四部分组成,每个部分用双引号包起来,用冒号做间隔,除了汇编指令部分是必须的,其他可以不用。但是如果中间部分缺省,则也用冒号区分并填充空格,如用到第一部分和第四部分:

asm volatile ("sleep" : : : "memory")

​ 一般还有volatile,以避免被优化。

汇编指令

​ 汇编指令部分有汇编指令语句组成。如果使用多条,用 ; 隔开。操作数可以用占位符引用C语言中的变量,最多10个,比如 %0 ,%1 , %2 ...%9.

​ 命令中使用占位符表示的操作数,总被视为long型,但对其施加的操作根据指令可以是字或者字节,当把操作数当作字或者字节使用时,默认为低字或者低字节。对字节操作可以显式的指明是低字节还是高字节。方法是在%和序号之间插入一个字母,"b"代表低字节,"h"代表高字节,例如:%h1。

输出部分

​ 格式为"输出修饰符、限定符"(变量)

  • 输出修饰符有三种:
输出修饰符名称 意义
= 只写
+ 可读可写
& 指定寄存器
  • 限定符
限定符名称 意义
r 将变量放到通用寄存器
m 直接操作内存
n 立即数
g 使用任意一个寄存器,由gcc在所有的可以使用的寄存器中选取一个gcc认为合适的。
f 浮点寄存器
  • 变量

    变量一般就是寄存器名或立即数值或内存。

输入部分

输入部分与输出部分类似,但没有输出修饰符,格式为 "限定符"(变量)

破坏描述部分(暂时没用到)

​ 破坏描述符用于通知编译器我们使用了哪些寄存器或内存,由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名以及"memory"。例如:"%R0","%R1","memory"等。

例子解析

解释三个例子,前两个是开头提到的。

  • asm volatile("li a5, -1; ecall");

    这个只有汇编指令部分。

    1. 先执行li a5,-1,即令a5寄存器为-1;

    2. 然后执行ecall,结束。

  • 蜂鸟例子

    这个例子包括汇编指令、输出部分和输入部分。

    .insn r 0x7b是自定义指令,表示为R型指令,0x7b是RISCV为自定义指令留的操作码(custom_3,inst[6:0])。其余部分按.insn r opcode, func3, func7, rd, rs1, rs2。由于这里不是asm重点,不详细解析。

    输出部分为"=r"(zero) ,其中 = 是输出修饰符,表示只写,r表示放入通用寄存器,zero表示变量名。

    输入部分,把addr变量值赋给rs1寄存器。

asm volatile (
   ".insn r 0x7b, 2, 1, x0, %1, x0"
       :"=r"(zero)
       :"r"(addr)
 );
  • asm volatile("csrw mtvec, %0" : : "r"(__am_asm_trap));

    这个包括汇编指令和输入部分。

    csrw格式如下图,%0是输入变量__am_asm_trap的占位符。

    image-20231226210416089