一、RISCV汇编入门
参考资料:
缺点:
难读,难写,难移植。
优点:
灵活 ,强大。
特点:
适合参与直接操作硬件的场景。
需要对性能极致优化的地方。
1.risc汇编规则(gnu版本)
不同的汇编器可能规则不同。我们使用GNU工具链。
一个汇编程序(.s/.S)由多条语句组成(statement)。
一条语句由三部分组成:
[label:] [operation] [comment]
标签,操作,注释。
# First RISC-V Assemble Sample
.macro do_nothing # directive
nop # pseudo-instruction
nop # pseudo-instruction
.endm # directive
.text # directive
.global _start # directive
_start: # Label
li x6, 5 # pseudo-instruction
li x7, 4 # pseudo-instruction
add x5, x6, x7 # instruction
do_nothing # Calling macro
stop: j stop # statement in one line
.end # End of file
label就是类似于一种变量,或者说地址,这也是中文意思:标签。
operation是操作指令,可以分为几类:
instruction指令
pseudo instruction伪指令
directive指示
macro(宏)
有的指令对应机器码(机器指令),例如 add x1,x2, x4
伪指令就是不对应原生的机器指令,是为了提高写代码效率,类似与多条指令组合,会被分解成真正的指令再翻译成机器语言。
directive的作用是通过.开头的的类似指令的形式,来控制代码生成的一些特殊处理。事实上是给汇编器看的东西不属于riscV标准。比如.end就是告诉汇编器到这结束了,后面的就不会再看了。.text就是告诉汇编器把代码存到text这个section内。在编译和链接章节内有提到。.global 类似与设置全局变量。start又是一个地址,把这个地址变为全局变量。不对应具体指令:只能在汇编器手册里看到。
宏的定义就是类似c语言的宏定义,比如代码内的do nothing。先定义后调用的形式大家应该能发现。变成两条nop,又被翻译。
注释:#,//,;,都可以,常用的是#。
2.汇编指令总览:操作对象
寄存器
32个通用寄存器,注意这里指的是我们文章仅涉及RV32I的寄存器组。每个寄存器都有别名。大家要仔细研究手册。
手册机翻的中文版我也给大家放在开头了。
在riscv中hart的操作数据必须来自寄存器。
内存
HART可以执行在寄存器和内存之间的数据读写操作。
读写操作使用字节作为基本单位进行寻址。
RV32最多访问2^32个内存地址。
3.汇编指令总览:指令编码格式
第一节也讲了类似的概念。
那么这里来讲真实的。
指令长度:ILEN1=32bit(RV32I)
指令对齐:IALIGN=32bit(RV32I)事实上指的是在内存里面,地址一定是4个字节的倍数,这就是对齐。当引入16bit指令时,对对齐的检查放宽到2字节(16bit)。
32个bit划分成不同的域。
不同的划分域的方式就是不同类型的指令。
手册内:
opcode并不能直接决定指令类型,而是结合funct3(3bit)和funct7(7bit)部分才能确定。
riscv isa把rs1与rs2(两个源)以及rd(目的)放在相同的位置以方便解码。
- 用于寄存器-寄存器操作的 R 类型指令,
- 用于短立即数和访存 load 操作的 I 型指令,
- 用于访存 store 操作的 S 型指令,
- 用于条件跳转操作的 B 类型指令,
- 用于长立即数的 U 型指令,
- 用于无条件跳转的 J 型指令
第二十四章有这么一张指令opcode对照表。 这是满足imafd的G指令集。如果不知道我现在在说什么,请务必回去看之前的isa介绍那一篇文章。图中我们可以看到我们的opcode map给出第0位和第1位永远是11。那么还剩下5位,从第二位到第六位。那么这个表格行标题是第二位到第四位,列标题是第五位到第六位。
下面则是更详细的总表。
比如add指令:0110011,
0110011:-11是操作码的缺省位
0110011:查表行01
0110011:查表列100
得到op类型。
另外指令在内存中为小端序排列(低位字节排在内存的低地址。)
不了解这个可以自行查阅百度等。概念不难不在这赘述。
4.指令类型
- 用于寄存器-寄存器操作的 R 类型指令,
- 用于短立即数和访存 load 操作的 I 型指令,
- 用于访存 store 操作的 S 型指令,
- 用于条件跳转操作的 B 类型指令,
- 用于长立即数的 U 型指令,
- 用于无条件跳转的 J 型指令
具体的格式与他们分类有关。
R:register | 每条指令中有三个 fields,用于指定 3 个 寄存器参数 |
I:immediate | 每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为 12 bits)。 |
S:store | 每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为 12 bits,但 fields 的组织方式不同于 I-type) |
B:branch | 每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为 12 bits,但取值为 2 的倍数)。 |
U:upper | 每条指令含有一个寄存器参数再加上一个立即数参数(宽度为 20 bits,用于表示一个立即数的高 20 位) |
J:jump | 每条指令含有一个寄存器参数再加上一个立即数参数(宽度为 20 bits) |
寄存器一般都是5bit,为什么?========因为一共32个寄存器。
汇编指令分类还有更具体的,比如算数运算有哪几个,内存读写有哪几个等等。这里也不多谈。
第二十五章则是给出汇编的常见伪指令。
那么简介就到这里。下一站是汇编指令的详解。和我一起做好准备。