一起学RISC-V汇编第7讲之自定义指令

一起学RISC-V汇编第7讲之自定义指令

为了方便用户对RISC-V进行扩展,RISC-V支持自定义指令。

自定义指令通常需要硬件与软件协同开发。

1 硬件实现

确定了自定义指令的功能后,需要设计指令编码,然后硬件逻辑实现。

1.1 确定opcode

RV32指令架构中定义了4种custom指令类型,opcode需使用表格custom-0/custom-1/custom-2/custom-3中的一种:

1.2 确定指令类型

需要结合指令的功能来进行选择,有6种指令指令格式,分别为R/I/S/B/U/J类型,第4讲已经讲过,这里摘抄过来:

  • R型用于寄存器-寄存器间的操作
  • I型用于短立即数和访存(Load)操作
  • S型用于访存Store操作
  • B型用于条件跳转
  • U型用于长立即数
  • J型用于无条件跳转

1.3 确定指令编码

根据opcode以及指令类型,还需要确定其它字段的编码,比如R-type中,需要确定func3/func7字段的编码。指令编码确定后,便可以由硬件实现逻辑。

1.4 实现硬件逻辑

在硬件层面上实现自定义指令的逻辑功能。涉及到修改处理器的微架构,添加相应的电路和逻辑来执行自定义指令的操作。

2 编译器支持

自定义指令,一个前提条件是硬件支持这条指令;二是编译器的支持,可以编译成可执行文件;三是软件怎么用这条指令,可以使用汇编或者内嵌汇编,关于汇编和内嵌汇编的内容后续几讲会专门讲到。

编译器支持自定义指令有两种方式:

  1. 利用Kito Cheng提供的.insn模板进行开发
  2. 修改binutils的方法

下面分别进行讲述,内容主要见参考1:

2.1 使用.insn模板

例如自定义一条计算立方的指令cube,功能是算术运算,所以指令类型为可以选择R-type或I-type

R-type格式如下:

所以.insn模板为:

.insn r opcode, func3, func7, rd, rs1, rs2

opcode需使用上述RISC-V base opcode map表格custom-0/custom-1/custom-2/custom-3中的一种,func3/func7字段可以自定义,注意不要超过了位宽限制。

硬件实现了这条自定义指令后,接下来就是软件上使用了。

汇编语句:

.insn r 0x7b, 6, 6, a0, a1, x0

内嵌汇编:

asm volatile(".insn r 0x7b,  6,  6,  %0,  %1, x0" : "=r"(res) : "r"(var));

这两种写法是等价的,都是计算cube,编译得到机器码后交给实际的硬件进行解析,实现自定义的指令功能。

测试:

static int custom_cube(int var)
{
    int res;
    asm volatile (
       ".insn r 0x7b, 6, 6, %0, %1, x0"
       :"=r"(res)
       :"r"(var)
     );
    return res; 
}

反汇编得到:

a0002c74 <custom_cube>:
a0002c74: 7179               addi sp,sp,-48
a0002c76: d622               sw s0,44(sp)
a0002c78: 1800               addi s0,sp,48
a0002c7a: fca42e23           sw a0,-36(s0)
a0002c7e: fdc42783           lw a5,-36(s0)
a0002c82: 0c07e7fb           0xc07e7fb
a0002c86: fef42623           sw a5,-20(s0)
a0002c8a: fec42783           lw a5,-20(s0)
a0002c8e: 853e               mv a0,a5
a0002c90: 5432               lw s0,44(sp)
a0002c92: 6145               addi sp,sp,48
a0002c94: 8082               ret

其中0xc07e7fb机器码,交给硬件解析,即可完成所需功能。

2.2 修改binutils的方法

使用.insn模板优点是不用修改编译器,比较简单,但阅读起来不友好,临时用还OK,如果是长期维护就会很头疼,为了解决这个问题,还可以通过修改binutils来解决。

  • 下载riscv-opcodes

    $ git clone https://github.com/riscv/riscv-opcodes
    
  • 新建一个custom_opcode文件,添加如下内容,这些内容定义了指令的样式以及各字段的编码

    cube rd rs1 rs2 31..25=0x0c 14..12=0x6 6..2=0x1e 1..0=3
    
  • 使用官方脚本生成指令模版,按照git仓库的README操作,或者如下操作(注意:parse.py要写成完整路径)

    python3 /path/to/parse.py -c custom_opcode
    

    可见生成了encoding.out.h文件,文件中有自定义指令相关内容:

    #define MATCH_CUBE 0x1800607b
    #define MASK_CUBE 0xfe00707f
    
    DECLARE_INSN(cube, MATCH_CUBE, MASK_CUBE)
    
  • 修改binutils

    riscv-gnu-toolchain/riscv-binutils中,修改

    include/opcode/riscv-opc.h
    

    上述riscv-opcodes生成的三条宏定义放到该文件中,然后修改opcodes/riscv-opc.c中的指令定义。

    {"cube",       0, INSN_CLASS_I, "d,s,t",  MATCH_CUBE, MASK_CUBE, match_opcode, 0 },
    

    修改完成后,这样就添加完成了。

  • 编译binutils工具链,最终得到支持自定义指令的工具链。

使用编译得到的工具链测试:

static int custom_cube(int var)
{
    int res;
    asm volatile (
       "cube %0, %1, x0"
       :"=r"(res)
       :"r"(var)
     );
    return res; 
}

反汇编为:

a0002c74 <custom_cube>:
a0002c74: 7179              addi sp,sp,-48
a0002c76: d622              sw s0,44(sp)
a0002c78: 1800              addi s0,sp,48
a0002c7a: fca42e23          sw a0,-36(s0)
a0002c7e: fdc42783          lw a5,-36(s0)
a0002c82: 1807e7fb          cube a5,a5,zero
a0002c86: fef42623          sw a5,-20(s0)
a0002c8a: fec42783          lw a5,-20(s0)
a0002c8e: 853e              mv a0,a5
a0002c90: 5432              lw s0,44(sp)
a0002c92: 6145              addi sp,sp,48
a0002c94: 8082              ret

可见gcc已经可以识别到cube指令了。

参考:

  1. riscv gcc中添加custom自定义指令
  2. https://sourceware.org/binutils/docs/as/RISC_002dV_002dFormats.html
posted @ 2024-09-08 15:23  sureZ_ok  阅读(159)  评论(0编辑  收藏  举报