riscv 汇编

RISC-V汇编语言入门

参考:

汇编语言概念简介

  • 汇编语言(Assembly Language)是一种低级的语言。

  • 一个完整的RISC-V汇编程序有多条语句(statement)组成

  • 一条典型的RISC-V汇编语句由三部分组成:[label :] [operation] [comment]

    --方括号表示可选项

    -- label 标号:GNU汇编中, 任何以冒号结尾的标识符被视为标号

    --operatioin

    -- instruction(指令):直接对应二进制机器指令的字符串

    -- pseudo-instruction(伪指令):为了提高编写效率,使用一条伪指令指示汇编器产生多条实际指令(instructions).

    -- dierctive(指示/伪操作) :通过类似指令的形式(以"."开头),通知汇编器如何控制代码的产生,不对应具体的指令

    --macro :采用 .macro/.endm自定义的宏

# 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
​

汇编语言的扩展名含义

  • .S 包含预处理指令

  • .s 纯汇编指令

汇编指令总览

RISC-V汇编指令操作对象

寄存器

  • 32个通用寄存器,x0~x31(RV32I通用寄存器组)

  • 在RISC-V中,Hart在执行算数逻辑运算时操作的数据必须直接来自寄存器

内存

  • Hart 可以执行在寄存器和内存之间的数据读写操作;

  • 读写操作使用字节(Byte)为基本单位进行寻址;

  • RV32 可以访问最多 2^32 个字节的内存空间。

(HART(Hardware Thread)是指硬件线程的缩写,它代表了处理器的一个执行单元。HART用于描述处理器中可以独立执行指令流的最小硬件单元。每个HART可以独立执行指令,可以是一个处理器核心,也可以是同一个核心中的多个执行线程,取决于硬件实现。)

RISC-V汇编指令编码格式

指令格式

  • 指令长度:ILEN1=32bits(RV32I)

  • 指令对齐:IALIGN = 32 bits (RV32I)

  • 32 个 bit 划分成不同的 “域(field)

  • funct3/funct7 和 opcode 一起决定最终的指令类型

  • 指令在内存中按照小端序排列

六种指令格式

rs2,rs1,rd为寄存器代号,rd用于存放计算结果

  • R-type:(Register),每条指令中有三个 fields,用于指定 3 个 寄存器参数

  • I-type: Immediate),每条指令除了带有两个寄存器参数外,还带有一个立即数参数 (宽度为 12 bits)。

  • S-type: (Store),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽 度为 12 bits,但 fields 的组织方式不同于 I-type)

  • B-type: (Branch),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度 为 12 bits,但取值为 2 的倍数)。

  • U-type: (Upper),每条指令含有一个寄存器参数再加上一个立即数参数(宽度为 20 bits,用于表示一个立即数的高 20 位)

  • J-type: (Jump),每条指令含有一个寄存器参数再加上一个立即数参数(宽度为 20 bits)

RISC-V汇编指令详解

算数运算指令

ADD

语法: ADD RD,RS1,RS2
例子: add x5,x6,x7 # x5=x6+x7
  • 编码格式 R-type

  • opcode (7): 0110011(OP)

  • funct3 取值 000;funct7 取值 0000000

  • rs1 (5): 第一个 operand (“source register 1”)

  • rs2 (5): 第二个 operand (“source register 2”)

  • rd (5): “destination register” 用于存放求和的结果

    SUB

语法: SUB RD,RS1,RS2
例子: sub x5,x6,x7 # x5=x6-x7
  • 编码格式R-type

ADDI

语法: ADDI RD,RS1,IMM
例子: addi x5,x6,1 #5=x6+1
  • 编码格式I-type

  • opcode(7):0b0010011 (op-IMM)

  • unct3 (3): 和 opcode 一起决定最终的指令类型

  • rs1 (5): 第一个 operand (“source register 1”)

  • rd (5): “destination register” 用于存放求和的结果

  • imm (12): “immediate”, 立即数,代替了 R-type 的第三个寄存器参数和 func7

  • imm (12): “immediate”, 立即数占 12 位

  • 在参与算术运算前该 immediate 会被 “符号扩展”为一个 32 位的数

  • 这个立即数可以表达的数值范围为:[-2^11 ,+2^11),即[-2048, 2047]

基于算数运算指令实现的其他伪指令

伪指令

语法

等价指令

指令描述

例子

NEG

NEG RD,RS

SUB RD,x0,RS

对 RS 中的值取反并将结果存放在RD中

neg x5, x6

MV

MV RD, RS

ADDI RD, RS, 0

将 RS 中的值拷贝到 RD 中

mv x5, x6

NOP

NOP

ADDI x0, x0, 0

什么也不做

nop

ADDI的局限性

给一个寄存器赋值的数值范围只有:[-2048, 2047]。如果要赋值一个大数 (32 位) 怎么办?

  • 先引入一个新的命令先设置高 20 位,存放在 rs1

  • 然后复用现有的 ADDI 命令补上剩余的低 12 位即可

LUI

语法: LUI,RD,IMM
例子: lui x5,0x12345 #x5= 0x12345 <<12
  • 编码格式U-type

  • opcode (7): 0b0110111(LUI)

  • rd (5): “destination register” 用于存放结果

  • imm (20): “immediate”, 立即数

  • LUI 指令会构造一个 32 bits 的立即数,这个立即数的高 20 位对应指令中的 imm,低 12 位清零。这个立即数作为结果存放在 RD 中。

LI

语法: LI RD,IMM
例子: li x5,0x12345678 #x5=0x12345678
  • LI (Load Immediate)是一个伪指令(pseudo-instruction)

  • 汇编器会根据 IMM 的实际情况自动生成正确的真实指令(instruction)。

AUIPC

语法: AUIPC RD,IMM
例子: auipc x5,0x12345 # x5=0x12345+pc
  • AUIPC 指令采用 U-type

  • 和 LUI 指令类似,AUIPC 指令也会构造一个 32bits 的立即数,这个立即数的高 20 位对应指令中的 imm,低 12 位清零。但和 LUI 不同的是,AUIPC 会先将这个立即数和 PC 值相加,将相加后的结果存放在 RD 中。

  • 多用于构造相对地址

LA

语法: LA
例子: la x5,foo
  • LA是一个伪指令(pseudo-instruction)

  • 具体编程时给出需要加载的 label,编译器会根据实际情况利用 auipc 和其他指令自动生成正确的指令序列。

  • 常用于加载一个函数或者变量的地址。

正在装修中.....