ASM@RISC-V
CPU寄存器
Register | ABI | Discription | Attribution |
---|---|---|---|
x0 | zero | 硬件常数0 | N/A |
x1 | ra | 返回地址 | Caller |
x2 | sp | 栈指针 | Callee |
x3 | gp | 全局指针 | - |
x4 | tp | 线程指针 | - |
x5-x7 | t0-t2 | 临时变量 | Caller |
x8 | s0/fp | 保存寄存器/帧指针 | Callee |
x9 | s1 | 保存寄存器 | Callee |
x10-x11 | a0-a1 | 函数参数/返回值 | Caller |
x12-x17 | a2-a7 | 函数参数 | Caller |
x18-x27 | s2-s11 | 保存寄存器 | Callee |
x28-x31 | t3-t6 | 临时变量 | Caller |
f0-f7 | ft0-ft7 | 浮点临时变量 | Caller |
f8-f9 | fs0-fs1 | 浮点保存寄存器 | Callee |
f10-f11 | fa0-fa1 | 浮点函数参数/返回值 | Caller |
f12-f17 | fa2-fa7 | 浮点函数参数 | Caller |
f18-f27 | fs2-fs11 | 浮点保存寄存器 | Callee |
f28-f31 | ft8-ft11 | 浮点临时变量 | Caller |
32位RISC-V(RV32)指令集概述
典型汇编语句
label: | opcode | operands | comment |
---|---|---|---|
标签 | 操作码 | 操作数 | 注释 |
- 标签:表示当前指令的位置标记
- 操作码:操作码主要有以下几种形式
- RISC-V标准指令
- 伪指令
- 用户自定义宏
- 操作数:
- 操作码所需的操作,与操作码之间以空格分开
- 符号、常量、或是符号与常量组成的表达式
指令机器码格式
-
I型指令
imm[11:0] rs1 funct3 rd opcode [31:20] [19:15] [14:12] [11:7] [6:0] -
U型指令
imm[31:12] rd opcode [31:12] [11:7] [6:0] -
S型指令
imm[11:5] rs2 rs1 imm[4:0] rd opcode [31:25] [24:20] [19:15] [14:12] [11:7] [6:0] -
R型指令
funct7 rs2 rs1 funct3 rd opcode [31:25] [24:20] [19:15] [14:12] [11:7] [6:0] -
B型指令
-
J型指令
RV32I基本指令集
说明:为了展示指令之间的层次结构,在这里使用了多级无序列表,每个指令后的括号即该指令的英文全称,方便理解相似指令在不同环境下的运用,而大写部分则是指令缩写所提取的字母,另外划有删除线代表本身不存在但具有分支应用的指令;后续也可能会画一个树表作为补充。
算术指令
-
add
ADD: add rd,rs
等效于rd = rd + rs
- addi(add immediate)
ADDI: addi rd,rs1,imm
- addi(add immediate)
-
or
- ori(OR Immediate)
-
xor(eXclusive OR)
- xori(eXclusive OR Immediate)
-
shift- sll(Shift Left Logical)
- slli(Shift Left Logical Immediate)
- sra(Shift Right Arithmetic)
- srai(Shift Right Arithmetic Immediate)
- srl(Shift Right Logical)
- srli(Shift Right Logical Immediate)
- sll(Shift Left Logical)
-
sub(SUBtract)
-
lui(Load Upper Immediate)
将一个16位的立即数填入寄存器的高16位中,低16位则补零 -
auipc(Add Upper Immediate to PC)
-
slt(Set Less Than)
- slti(Set Less Than Immediate)
- sltiu(Set Less Than Immediate Unsigned)
- slti(Set Less Than Immediate)
加载指令
-
load- lb(Load Byte)
- lbu(Load Byte Unsigned)
- lh(Load Halfword)
读16位,符号位拓展后写入寄存器rd中- lhu(Load Halfword Unsigned)
读16位,高位补0,写入寄存器rd中
- lhu(Load Halfword Unsigned)
- lw(Load Word)
从存储器中读回一个32位的数据,写入寄存器rd中
- lb(Load Byte)
-
store- sb(store Byte)
- sh(store Halfword)
- sw(store Word)
条件分支指令
branch- beq(branch equal)
比较两个寄存器内部的值,若相等便跳转至指定标签位置,否则继续顺序执行。 - bne(branch not equal)
比较两个寄存器内部的值,如果结果不相等,则跳转至指定标签位置,反之继续顺序执行。 - bge(Branch Greater than or Equal)
当前操作数大于等于后者时,跳转到分支标签内容,否则继续执行顺序内容。- bgeu(Branch Greater than or Equal Unsigned)
对参数以无符号格式进行比较,其他部分逻辑与bge指令相同
- bgeu(Branch Greater than or Equal Unsigned)
- blt(Branch Less Than)
当前操作数小于(但不等于)后者时,跳转到分支标签内容,不然继续执行顺序内容。- bltu(Branch Less Than Unsigned)
- beq(branch equal)
小于等于、大于判断可以通过颠倒操作数位置来实现,注意逻辑和边际问题即可。
站在芯片运行优化的角度,设计的比较条件应该尽可能倾向于程序顺序执行,以减少在判断跳转之间的时间消耗。
BRANCH:
opcode rs1,rs2,LABEL;
跳转控制指令
- jal(Jump And Link)
- jalr(Jump And Link Register)
寄存器屏障指令
-
fence
-
break
- ebreak
-
call
- ecall
-
csr- csrrc(Control Status Register Read & Clear bit)
- csrrci(Control Status Register Read & Clear bit Immediate)
- csrrs(Control Status Register Read & Set bit)
- csrrsi(Control Status Register Read & Set bit Immediate)
- csrrw(Control Status Register Read & Write bit)
- csrrwi(Control Status Register Read & Write bit Immediate)
- csrrc(Control Status Register Read & Clear bit)
RV32标准拓展指令
RV32M标准拓展指令
- mul
- mulh
- mulhsu
- mulhu
- mulh
- div
- divu
- rem
- remu
RV32A标准拓展指令
- lr.w
- sc.w
amo- amoswap.w
- amoadd.w
- amoxor.w
- amoand.w
- amoor.w
- amomin.w
- amomax.w
- amominu.w
- amomaxu.w
RV32F标准拓展指令
- flw
- fsw
- fmadd.s
- fmsub.s
- fnmsub.s
- snmadd.s
- fadd.s
- fsub.s
- fmul.s
- fdiv.s
- fsqrt.s
- fsgnj.s
- fsgnjn.s
- fsgnjx.s
- fmin.s
- fmax.s
- fcvt.w.s
- fcvt.wu.s
- fmv.x.s
- feq.s
- flt.s
- fclass.s
- fcvt.s.w
- fcvt.s.wu
- fmv.s.x
伪指令
实际上是由基本指令组合而成
加载伪指令
- la(Load Address)
加载地址 - ld(LoaD)
加载全局参数
算术伪指令
- nop
执行一个空操作NOP: nop
- li(Load Immediate)
将imm代表的立即数直接赋值rd寄存器LI: li rd,imm;
- mv(MOV)
- not
- neg
- negw
- sext
- seqz
- snez
- sltz
- sgtz
浮点算术伪指令
- fmv.s
- fabs.s
- fneg.s
- fmv.d
- fabs.d
- feng.d
条件分支伪指令
- beqz
- bnez
- blez
- bgez
- bltz
- bgtz
跳转控制伪指令
- j
- jal
- jr
- jalr
- ret
- tail
启动
-
.global(.globl)
伪操作,用于定义全局符号,使链接过程中能被其他程序文件可见,类似于extern -
.local
伪操作,用于定义局部符号,仅当前程序文件可见 -
.file
伪操作,用于指示汇编器该汇编程序的逻辑文件名opcode oprands .file "filename" -
.section
- .text
- .data
- .rodata
- .bss
-
.type
-
.align
- .balign
-
.zero
-
.byte
- 2byte
- 4byte
- 8byte
-
.float
- .double
-
.word
- .half
- .dword
-
.string
-
.equ
-
.weak
伪操作,同时创建符号并定义为弱属性。如果该符号已定义,则设置此符号为弱属性- 在汇编程序中,符号的默认属性为强(strong)
如果符号的属性为弱,则可无需定义具体内容,常用于预留空符号并通过汇编语法检查。当另外存在一个属性为强的同名符号实体时会自动覆盖并链接
-
.pushsection - .popsection
-
.marco - .endm
宏 -
.comm(.common)
-
.option
注释
以符号 # 或 ; 作为分隔号,当行分隔号以后的部分会被视作注释
代指
- rd(Register Destination)
指目标寄存器,实际代码中可以是t0、t1等
代码格式(C语言嵌套汇编)
在C语言文件中嵌入汇编代码,除对固定地址寄存器并直接进行操作外,需要将变量等参数传入汇编程序中,才能保证操作有效,想要传入参数,需要通过操作符列表来完成
关键字
-
asm
为GCC的关键字,表示进行内联汇编操作,也可以使用前后各带两个下划线的__asm__
,_asm_
是GCC关键字asm的宏定义。 -
volatile
或者_volatile_
、_volatile_
或volatile
,均为宏定义。如果添加了该关键字,则通知编译器避免对后续括号内添加汇编程序进行任何优化,以保持其原状 -
汇编指令列表
即需要嵌入的汇编指令,每条指令必须被双引号括起来(作为字符串),两条指令之前必须以“\n”或者“;”作为分隔符。如果没有添加分隔符,相连的字符串将会被合并成为一个字符串。内部语法与汇编程序一样
标记符号
-
%
后面跟标签则为指定,若为数字则是从操作符列表顺序选择 -
[ ]
方括号中的符号名,用于将内联汇编程序中使用的操作数和此操作符通过名称绑定起来。标签名称随意,但不能重复 -
“ ”
引号中的限制字符串,用于约束此操作数变量的属性,常用的约束如:-
r
代表使用编译器自动分配的寄存器来存储该操作数变量 -
m
字母“m”代表使用内存地址来存储该操作数变量,如果同时指明“rm”则编译器自动选择最优方案 -
=
对于输出操作数而言,等号代表输出变量用作输出,原来的值会被新值替换,此约束不适用于输入操作数 -
+
代表输出变量不仅作为输出,还作为输入,此约束不适用于输入操作数
-
-
( )
圆括号中填入C/C++变量或者表达式
输出操作符之间需使用逗号分割
操作符列表
-
输出资源表
: [ ]"=r"( ), [ ]"=r"( ), ···
-
输入资源表
: [ ]"r"( ), [ ]"r"( ), ···
-
影响资源表
: " ", "", ···
用于告知编译器当前内联汇编语句可能会对某些寄存器或内存进行修改,使得编译器在优化时将其因素考虑进去
- 隐式表
除了使用%[字符]
中明确的符号命名指定之外,还可以使用%数字
的方式进行隐含指定。从0开始,依次表示操作符列表中所有的输出操作数与输入操作数。
范例
int abc = 10,cba = 20;
printf("%d & %d\n",abc,cba);
asm volatile(
"mv %[da],%3\n"
"add %2,%1,%0\n"
"li %3,12\n"
: [da]"=r"(abc), "=r"(cba) // Working
: "r"(abc), [db]"r"(cba) // Working
: "t1", "t2"//, "x7"
);
printf("%d & %d\n",abc,cba);
输出结果为:
10 20 (修改前)
40 12 (修改后)