RISCV asm内嵌汇编
在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
的占位符。