NEMU 添加更多的指令

前言

上一篇博客中介绍了如何添加指令,这篇将会熟悉这个过程,学习以及添加更多的指令。

所有程序

在am-kernels/tests/cpu-tests/tests 目录下有很多测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
ls -l
total 140
-rw-rw-r-- 1 luyoung luyoung 1050 Aug 15 21:56 add.c
-rw-rw-r-- 1 luyoung luyoung 1562 Aug 15 21:56 add-longlong.c
-rw-rw-r-- 1 luyoung luyoung 954 Aug 15 21:56 bit.c
-rw-rw-r-- 1 luyoung luyoung 512 Aug 15 21:56 bubble-sort.c
-rw-rw-r-- 1 luyoung luyoung 1001 Aug 15 21:56 crc32.c
-rw-rw-r-- 1 luyoung luyoung 322 Aug 15 21:56 div.c
-rw-rw-r-- 1 luyoung luyoung 27 Aug 15 21:56 dummy.c
-rw-rw-r-- 1 luyoung luyoung 315 Aug 15 21:56 fact.c
-rw-rw-r-- 1 luyoung luyoung 461 Aug 15 21:56 fib.c
-rw-rw-r-- 1 luyoung luyoung 379 Aug 15 21:56 goldbach.c
-rw-rw-r-- 1 luyoung luyoung 317 Aug 15 21:56 hello-str.c
-rw-rw-r-- 1 luyoung luyoung 544 Aug 15 21:56 if-else.c
-rw-rw-r-- 1 luyoung luyoung 604 Aug 15 21:56 leap-year.c
-rw-rw-r-- 1 luyoung luyoung 972 Aug 15 21:56 load-store.c
-rw-rw-r-- 1 luyoung luyoung 2020 Aug 15 21:56 matrix-mul.c
-rw-rw-r-- 1 luyoung luyoung 995 Aug 15 21:56 max.c
-rw-rw-r-- 1 luyoung luyoung 667 Aug 15 21:56 mersenne.c
-rw-rw-r-- 1 luyoung luyoung 1146 Aug 15 21:56 min3.c
-rw-rw-r-- 1 luyoung luyoung 292 Aug 15 21:56 mov-c.c
-rw-rw-r-- 1 luyoung luyoung 497 Aug 15 21:56 movsx.c
-rw-rw-r-- 1 luyoung luyoung 668 Aug 15 21:56 mul-longlong.c
-rw-rw-r-- 1 luyoung luyoung 583 Aug 15 21:56 pascal.c
-rw-rw-r-- 1 luyoung luyoung 342 Aug 15 21:56 prime.c
-rw-rw-r-- 1 luyoung luyoung 717 Aug 15 21:56 quick-sort.c
-rw-rw-r-- 1 luyoung luyoung 819 Aug 15 21:56 recursion.c
-rw-rw-r-- 1 luyoung luyoung 518 Aug 15 21:56 select-sort.c
-rw-rw-r-- 1 luyoung luyoung 748 Aug 15 21:56 shift.c
-rw-rw-r-- 1 luyoung luyoung 338 Aug 15 21:56 shuixianhua.c
-rw-rw-r-- 1 luyoung luyoung 574 Aug 15 21:56 string.c
-rw-rw-r-- 1 luyoung luyoung 1562 Aug 15 21:56 sub-longlong.c
-rw-rw-r-- 1 luyoung luyoung 145 Aug 15 21:56 sum.c
-rw-rw-r-- 1 luyoung luyoung 522 Aug 15 21:56 switch.c
-rw-rw-r-- 1 luyoung luyoung 783 Aug 15 21:56 to-lower-case.c
-rw-rw-r-- 1 luyoung luyoung 287 Aug 15 21:56 unalign.c
-rw-rw-r-- 1 luyoung luyoung 275 Aug 15 21:56 wanshu.c

我的目标是,将这些测试全部通过。

add.c

1
2
3
4
5
6
7
8
(nemu) c
invalid opcode(PC = 0x80000078):
03 a9 0a 00 93 04 0b 00 ...
000aa903 000b0493...
There are two cases which will trigger this unexpected exception:
1. The instruction at PC = 0x80000078 is not implemented.
2. Something is implemented incorrectly.
Find this PC(0x80000078) in the disassembling result to distinguish which case it is.

老规矩,直接看反汇编代码,发现是这条指令:

1
000aa903          	lw	s2,0(s5)

lw

在 RISC-V 指令集中,lw 指令表示“Load Word”。这是一种加载指令,用于将内存中的一个 32 位字(word)读取到寄存器中。lw 是 I-type(立即数类型)指令的一部分,用于从指定的内存地址加载数据到寄存器。

指令格式

lw 指令的基本格式如下:

1
lw rd, offset(rs1)
  • **rd**:目标寄存器,用于存储加载的数据。
  • **offset**:一个立即数,表示相对于基址寄存器 rs1 的偏移量。
  • **rs1**:基址寄存器,提供内存加载操作的基础地址。

功能描述

当执行 lw 指令时,处理器会执行以下操作:

  1. 从基址寄存器 rs1 中读取地址。
  2. 将这个地址与立即数 offset 相加,计算出最终的内存地址。
  3. 从得到的内存地址读取一个 32 位字(4 字节)。
  4. 将读取的数据存储到目标寄存器 rd 中。

使用场景

lw 指令在多种编程场景中都非常有用,特别是在需要从内存中读取数据的情况下。它可以用来:

  • 从数组或数据结构中获取元素。
  • 读取函数调用的参数或局部变量(如果它们被存储在内存中)。
  • 在进行系统调用或硬件操作时,从特定的内存映射区域加载设备状态或数据。

示例

假设有一个整数数组,数组的首地址存储在寄存器 a0 中,我们想要加载数组的第一个元素到寄存器 t0

1
lw t0, 0(a0)  # 从内存地址 a0 加载一个字到寄存器 t0

这条指令将会从 a0 指定的地址加载一个 32 位的整数到 t0 寄存器中。如果要加载数组的第二个元素,偏移量将是 4(因为每个数组元素占用 4 字节):

1
lw t0, 4(a0)  # 加载数组的第二个元素

因此,指令应该这样解析和执行:

1
2
INSTPAT("??????? ????? ????? 010 ????? 00000 11", addi, I,
Mw(src1 + imm, 4, R(rd)));

继续编译运行:

1
2
3
4
5
6
7
(nemu) c
invalid opcode(PC = 0x80000090):
33 05 a9 00 33 05 f5 40 ...
00a90533 40f50533...
There are two cases which will trigger this unexpected exception:
1. The instruction at PC = 0x80000090 is not implemented.
2. Something is implemented incorrectly.

00a90533 是:00a90533 add a0,s2,a0。

add

在 RISC-V 指令集中,add 指令是一种算术指令,用于执行两个寄存器的内容之间的整数加法操作,并将结果存储在一个寄存器中。这是一种 R-type(寄存器类型)指令,完全基于寄存器操作,不涉及立即数或内存访问。

指令格式

add 指令的基本格式如下:

1
add rd, rs1, rs2
  • **rd**:目标寄存器,用于存储加法运算的结果。
  • **rs1**:第一个源寄存器,提供第一个加数。
  • **rs2**:第二个源寄存器,提供第二个加数。

功能描述

当执行 add 指令时,处理器会进行以下操作:

  1. 从寄存器 rs1 中读取第一个操作数。
  2. 从寄存器 rs2 中读取第二个操作数。
  3. 将这两个操作数相加。
  4. 将相加的结果存储到目标寄存器 rd 中。

使用场景

add 指令在几乎所有需要进行算术运算的编程任务中都非常重要。它用于:

  • 简单的数值运算。
  • 地址计算,如确定数据结构中的元素位置。
  • 循环控制和迭代中的计数器操作。
  • 数组和指针操作中的索引计算。

示例

假设寄存器 a0a1 分别存储了两个整数,我们想将这两个整数相加,并将结果存储在寄存器 a2 中:

1
add a2, a0, a1  # 将 a0 和 a1 中的数值相加,结果存储到 a2

这条指令将会把 a0a1 中的值相加,然后将计算结果存储到 a2

这种指令对于实现高级数据结构和复杂算法至关重要,它提供了基本的数值处理能力,是处理器功能的基石之一。

因此,可以这样解析和操作:

1
2
3
INSTPAT("0000000 ????? ????? 000 ????? 01100 11", add, R,
R(rd) = src1 + src2);

继续编译和运行:

1
2
3
4
5
6
7
(nemu) c
invalid opcode(PC = 0x80000094):
33 05 f5 40 13 35 15 00 ...
40f50533 00153513...
There are two cases which will trigger this unexpected exception:
1. The instruction at PC = 0x80000094 is not implemented.
2. Something is implemented incorrectly.

可以看到,add 成功解析,接下来解析40f50533:40f50533 sub a0,a0,a5

sub

在 RISC-V 指令集中,sub 指令用于从一个寄存器的值中减去另一个寄存器的值,并将结果存储在一个寄存器中。这也是一个 R-type 指令,与 add 指令相似,但用于执行整数减法运算。

指令格式

sub 指令的基本格式如下:

1
sub rd, rs1, rs2
  • **rd**(Destination Register):目标寄存器,用于存储运算结果。
  • **rs1**(Source Register 1):第一个源寄存器,提供被减数。
  • **rs2**(Source Register 2):第二个源寄存器,提供减数。

功能描述

执行 sub 指令时,处理器将进行以下操作:

  1. 从寄存器 rs1 中读取被减数。
  2. 从寄存器 rs2 中读取减数。
  3. rs1 的值减去 rs2 的值。
  4. 将得到的结果存储到目标寄存器 rd 中。

使用场景

sub 指令在各种需要算术减法的编程场景中都非常有用。它可以用于:

  • 算术计算和数据处理。
  • 地址或指针计算,如计算偏移量或逆向索引。
  • 控制结构中的计数器递减操作。
  • 在实现某些算法(如排序、搜索等)时调整循环或条件。

示例

假设寄存器 t0t1 分别存储了两个整数,我们想从 t0 的值中减去 t1 的值,并将结果存储在寄存器 t2 中:

1
sub t2, t0, t1  # 将 t0 和 t1 中的数值相减,结果存储到 t2

这条指令将计算 t0 - t1 的结果,并把结果保存在 t2 寄存器中。

sub 指令与 add 指令一样,是 RISC-V 指令集中基本的算术操作之一,提供了直接通过寄存器进行整数减法的能力。

因此,sub 指令可以这样解析和实现:

1
2
INSTPAT("0100000 ????? ????? 000 ????? 01100 11", sub, R,
R(rd) = src1 - src2);

编译运行:

1
2
3
4
5
6
7
(nemu) c
invalid opcode(PC = 0x80000098):
13 35 15 00 ef f0 5f f7 ...
00153513 f75ff0ef...
There are two cases which will trigger this unexpected exception:
1. The instruction at PC = 0x80000098 is not implemented.
2. Something is implemented incorrectly.

可以看到 sub 解析成功,接下来就是:00153513 seqz a0,a0。

seqz

在 RISC-V 指令集中,seqz 指令表示“Set if Equal to Zero”。这是一种 I-type 指令,用于检查一个寄存器的值是否为零,并据此设置另一个寄存器的值。如果检查的寄存器值为零,则目标寄存器被设置为1;如果不为零,则设置为0。这种指令通常用于条件判断和分支控制逻辑。

指令格式

seqz 指令的基本格式如下:

1
seqz rd, rs
  • **rd**:目标寄存器,用于存储结果(1 或 0)。
  • **rs**:源寄存器,其值将被检查是否等于零。

功能描述

执行 seqz 指令时,处理器将进行以下操作:

  1. 从寄存器 rs 中读取值。
  2. 检查 rs 的值是否为零:
    • 如果 rs 的值为零,则将 rd 设置为1。
    • 如果 rs 的值不为零,则将 rd 设置为0。

使用场景

seqz 指令在需要根据某些条件控制程序流程的场景中非常有用,例如:

  • 判断循环是否应该结束。
  • 检查函数的返回值是否表明某种特定状态(例如错误码为零表示无错误)。
  • 实现更复杂的逻辑条件判断,通常与其他分支指令结合使用。

示例

假设我们需要检查寄存器 a1 中的值是否为零,并根据这个结果决定接下来的操作。我们可以使用 seqz 指令如下:

1
seqz a2, a1  # 检查 a1 是否为零,将结果存储在 a2 中

如果 a1 的值为零,则 a2 将被设置为1;否则,a2 将被设置为0。然后,可以使用 a2 的值来控制程序的分支,例如使用条件分支指令跳转到不同的代码段。

seqz(Set if Equal to Zero)在 RISC-V 中是一种伪指令,而不是硬件直接支持的正式指令。伪指令是由编译器或汇编器在编译或汇编过程中转换成一种或多种硬件支持的基本指令的。seqz 的作用是测试一个寄存器的值是否为0,并据此设置另一个寄存器的值为1(如果为0)或0(如果不为0)。

如何实现 seqz

seqz rd, rs 通常可以通过以下等效的基础指令实现:

1
sltiu rd, rs, 1
  • sltiu(Set Less Than Immediate Unsigned):这条指令比较寄存器 rs 和立即数 1。如果 rs 小于 1(即,rs0,因为无符号比较),rd 将被设置为 1。如果 rs 不为 0rd 将被设置为 0

因此,seqz(sltiu)可以这样设置:

1
2
INSTPAT("??????? ????? ????? 011 ????? 00100 11", sltiu, I,
R(rd) = src1 < 1 ? 1 : 0);

编译和运行:

1
2
3
4
5
6
7
(nemu) c
invalid opcode(PC = 0x80000010):
63 04 05 00 67 80 00 00 ...
00050463 00008067...
There are two cases which will trigger this unexpected exception:
1. The instruction at PC = 0x80000010 is not implemented.
2. Something is implemented incorrectly.

继续解析:00050463 beqz a0,80000018。

beqz

在 RISC-V 指令集中,beqz 同样是一种伪指令,用于条件分支。beqz 的全称是 “Branch if Equal to Zero”。当给定的寄存器中的值等于零时,它将导致程序跳转到指定的标签或地址。这是在编程中常用的控制流机制,特别是在循环和条件判断中。

指令格式

beqz 的使用格式如下:

1
beqz rs, label
  • **rs**:源寄存器,其值将被检查是否等于零。
  • **label**:如果 rs 的值为零,则跳转到这个标签指向的代码位置。

功能描述

当执行 beqz 指令时,如果寄存器 rs 的值为零,则程序的执行流将跳转到 label 指定的位置。如果 rs 的值不为零,程序将继续执行下一条指令。

实现细节

作为一种伪指令,beqz rs, label 通常被汇编器翻译为正式的 RISC-V 指令:

1
beq rs, zero, label
  • **beq**(Branch if Equal):这是一个基础的分支指令,用于比较两个寄存器的值。如果它们相等,则执行跳转。在这里,beqz 使用 beq 指令将 rs 寄存器与 zero 寄存器(永远为 0 的寄存器)比较。

使用场景

beqz 指令在处理循环退出条件或根据函数返回值执行错误处理时非常有用。例如,它可以用来检查函数的返回值是否表示失败(通常用0表示),如果是,则跳转到错误处理代码。

先设置 TYPE-B 的 imm 宏:

1
2
3
4
5
6
7
8
#define immB()                                                          \
do { \
*imm = SEXT(((BITS(i, 31, 31) << 11) | (BITS(i, 30, 25) << 4) | \
BITS(i, 11, 8) | (BITS(i, 7, 7) << 10)) \
<< 1, \
12); \
} while (0)

因此,beqz(beq)可以这样设置:

1
2
INSTPAT("??????? ????? ????? 000 ????? 11000 11", beq, I,
if (src1 == src2) s->dnpc = imm);

编译,运行:

1
2
3
4
5
6
7
(nemu) c
invalid opcode(PC = 0x800000a4):
e3 90 89 fe 13 05 10 00 ...
fe8990e3 00100513...
There are two cases which will trigger this unexpected exception:
1. The instruction at PC = 0x800000a4 is not implemented.
2. Something is implemented incorrectly.

接着继续处理下一跳指令:fe8990e3 bne s3,s0,80000084。

bne

在 RISC-V 指令集中,bne 指令代表 “Branch if Not Equal”。这是一种条件分支指令,用于在两个寄存器的值不相等时,将程序的执行流跳转到指定的标签或地址。bne 是 RISC-V 的基础指令之一,直接支持在硬件级别,非伪指令。

指令格式

bne 指令的基本格式如下:

1
bne rs1, rs2, label
  • **rs1**:第一个源寄存器。
  • **rs2**:第二个源寄存器。
  • **label**:如果 rs1rs2 的值不相等,则跳转到这个标签指向的代码位置。

功能描述

执行 bne 指令时的操作如下:

  1. 比较操作:比较来自 rs1rs2 的值。
  2. 条件跳转:如果 rs1rs2 的值不相等,程序的执行流将跳转到 label 指定的位置。如果它们相等,则继续执行下一条指令。

使用场景

bne 指令在许多编程任务中都非常有用,特别是在循环、条件判断和错误检测中。它允许程序根据条件执行不同的代码路径,是实现复杂逻辑和控制流的关键工具。

因此可以这样设置:

1
2
INSTPAT("??????? ????? ????? 001 ????? 11000 11", bne, B,
if (src1 != src2) s->dnpc = imm);

可以看到,bne 和 beq 指令非常相似。

编译运行:

1
2
(nemu) c
[src/cpu/cpu-exec.c:164 cpu_exec] nemu: HIT GOOD TRAP at pc = 0x80000120

这样这个 add.c 就算完成了。

add-longlong.c

sltu

在 RISC-V 指令集中,sltu 指令代表“Set Less Than Unsigned”。这是一个 R-type 指令,用于比较两个寄存器中的无符号整数值,并根据比较结果设置目标寄存器的值。

指令格式

sltu 指令的基本格式如下:

1
sltu rd, rs1, rs2
  • **rd**:目标寄存器,将存储比较的结果。
  • **rs1**:第一个源寄存器,其值作为比较的左操作数。
  • **rs2**:第二个源寄存器,其值作为比较的右操作数。

功能描述

执行 sltu 指令时,处理器会进行以下操作:

  1. 比较操作:比较来自 rs1rs2 的值,作为无符号整数进行比较。
  2. 设置结果
    • 如果 rs1 的无符号值小于 rs2 的无符号值,则将 rd 设置为 1
    • 否则,将 rd 设置为 0

使用场景

sltu 指令在多种编程场景中都非常有用,尤其是涉及无符号数比较的情况。它可以用于:

  • 实现条件分支和循环控制,特别是在数据结构如优先队列和二叉搜索树的操作中。
  • 数值比较,尤其是在处理大量数据或数组时,进行元素排序和搜索。
  • 算法中的决策制定,如决定两个无符号数的大小关系。

示例

假设我们需要根据两个无符号整数的大小关系来执行不同的操作。我们可以使用 sltu 指令如下:

1
sltu t0, a1, a2  # 检查寄存器 a1 是否小于 a2,将结果存储在 t0

如果 a1 中的无符号值小于 a2 中的值,则 t0 将被设置为 1;否则,t0 将被设置为 0。然后,可以使用 t0 的值来控制程序的分支,例如使用条件分支指令跳转到不同的代码段。

sltu 为 RISC-V 提供了一种简洁的方式来比较无符号整数,并根据比较结果设置条件代码或执行分支操作。

1
2
3
INSTPAT("0000000 ????? ????? 011 ????? 01100 11", sltu, R,
R(rd) = src1 < src2);

xor

在 RISC-V 指令集中,xor 指令用于执行两个寄存器之间的按位异或(XOR)操作。这也是一个 R-type 指令,它处理两个寄存器的位数据,并将结果存储在一个目标寄存器中。

指令格式

xor 指令的基本格式如下:

1
xor rd, rs1, rs2
  • **rd**:目标寄存器,用于存储操作的结果。
  • **rs1**:第一个源寄存器。
  • **rs2**:第二个源寄存器。

功能描述

当执行 xor 指令时,处理器将进行以下操作:

  1. 按位异或操作:从寄存器 rs1rs2 中读取数值,对这两个数值进行按位异或操作。按位异或是一个二元操作,其中两位相同结果为 0,不同则为 1。
  2. 存储结果:将异或操作的结果存储到目标寄存器 rd 中。

使用场景

xor 指令在各种场景中有多种用途,包括:

  • 数据掩码和位操作:可以用来切换特定的位或清除特定的位设置。
  • 数据校验:在某些错误检测和校正算法中,使用异或操作来生成校验位或校验码。
  • 实现简单的逻辑运算:异或操作在某些算法中用于条件逻辑判断。
  • 加密和解密:在某些简单的加密算法中,使用异或来对数据进行加密或解密。

示例

假设有两个寄存器 a1a2,其中存储了两个数值,我们需要获取这两个数值的按位异或结果:

1
xor a3, a1, a2  # 将 a1 和 a2 中的值进行按位异或,结果存储在 a3

这条指令会将 a1a2 中的每一位进行比较,相同则在 a3 的对应位设置为 0,不同则设置为 1。例如,如果 a11101a21011,那么 a3 将被设置为 0110

xor 指令是 RISC-V 指令集中基础的位操作指令之一,提供了基本的逻辑功能,是许多更复杂操作和算法的构建块。

1
2
INSTPAT("0000000 ????? ????? 100 ????? 01100 11", xor, R,
R(rd) = src1 ^ src2);

or

在 RISC-V 指令集中,or 指令用于执行两个寄存器之间的按位或(OR)操作。这也是一个 R-type 指令,它处理两个寄存器的位数据,并将结果存储在一个目标寄存器中。

指令格式

or 指令的基本格式如下:

1
or rd, rs1, rs2
  • **rd**:目标寄存器,用于存储操作的结果。
  • **rs1**:第一个源寄存器。
  • **rs2**:第二个源寄存器。

功能描述

执行 or 指令时,处理器将进行以下操作:

  1. 按位或操作:从寄存器 rs1rs2 中读取数值,对这两个数值进行按位或操作。按位或是一个二元操作,其中任一位为 1,则结果位也为 1。
  2. 存储结果:将按位或操作的结果存储到目标寄存器 rd 中。

使用场景

or 指令在多种编程场景中非常有用,包括:

  • 设置特定的位:可以用来确保特定的位设置为 1,常用于标志位和控制位的操作。
  • 合并位掩码:在需要结合多个条件或标志时,使用按位或来合并掩码。
  • 实现简单的逻辑运算or 通常用于实现基本的逻辑运算,尤其是在布尔逻辑和条件判断中。

示例

假设寄存器 a1a2 分别存储了两个数值,我们需要合并这两个数值的位,保证任一位为 1 的都设置为 1:

1
or a3, a1, a2  # 将 a1 和 a2 中的值进行按位或,结果存储在 a3

这条指令会将 a1a2 中的每一位进行比较,任何一位为 1,相应的在 a3 的对应位也将设置为 1。例如,如果 a11101a21011,那么 a3 将被设置为 1111

or 指令是 RISC-V 指令集中基础的位操作指令之一,提供了基本的逻辑功能,是许多更复杂操作和算法的构建块。

1
2
INSTPAT("0000000 ????? ????? 110 ????? 01100 11", or, R,
R(rd) = src1 | src2);

bit.c

sh

在 RISC-V 指令集中,sh 指令代表 “Store Halfword”。这是一个 S-type 指令,用于将一个 16位(半字)的整数从寄存器存储到内存中。与其他存储指令如 sb(Store Byte)和 sw(Store Word)相似,sh 主要用于将数据写入内存中指定的地址。

指令格式

sh 指令的基本格式如下:

1
sh rs2, offset(rs1)
  • **rs2**:源寄存器,其值(或其中的一部分)将被存储到内存中。
  • **rs1**:基址寄存器,提供内存地址的基础部分。
  • **offset**:一个立即数偏移量,与基址寄存器的内容相加,得到最终的内存地址。

功能描述

执行 sh 指令时,处理器将进行以下操作:

  1. 计算地址:从基址寄存器 rs1 中读取基础地址,并加上偏移量 offset,以计算出内存中的目标地址。
  2. 存储数据:将寄存器 rs2 的低 16 位(即半字)存储到计算出的内存地址中。

使用场景

sh 指令在需要将较小的数据单元(如 16 位整数)存储到内存的情况下非常有用。它可以用于以下场景:

  • 保存数据结构的一部分:如只需要更新结构中的一个字段时。
  • 处理数组或列表中的元素:特别是在数组元素为 16 位整数的情况下。
  • 资源受限的环境:在内存或带宽受限的嵌入式系统中,可能需要优化数据传输的大小。

示例

假设我们需要将寄存器 t1 中的一个 16 位值存储到由寄存器 a0 指定的内存地址处,地址偏移量为 2:

1
sh t1, 2(a0)  # 将寄存器 t1 的低 16 位存储到由 a0 寄存器加 2 的地址

此指令会将 t1 的低 16 位内容存储到 a0 + 2 指定的内存位置。这种操作是在处理各种数据传输和存储操作时常见的,特别是在与硬件设备交互或在内存中构建数据结构时。

srai

在 RISC-V 指令集中,srai 指令代表 “Shift Right Arithmetic Immediate”。这是一个 I-type 指令,用于将寄存器中的数值向右算术移位一定的位数,位数由立即数指定,移位过程中保持数值的符号位不变。这种指令在处理符号整数时非常有用,因为它保持了数的符号(正或负)。

指令格式

srai 指令的基本格式如下:

1
srai rd, rs1, imm
  • **rd**:目标寄存器,用于存储操作结果。
  • **rs1**:源寄存器,其值将被移位。
  • **imm**:立即数,指定向右移动的位数。

功能描述

执行 srai 指令时,处理器将进行以下操作:

  1. 读取源寄存器:从寄存器 rs1 中读取数值。
  2. 算术右移:按照 imm 指定的位数将 rs1 的值向右移动。在移动过程中,最高位(符号位)的值将复制到新移出的高位中,这保证了移位后的数值保持原有的符号。
  3. 存储结果:将移位后的结果存储到目标寄存器 rd 中。

使用场景

srai 指令在多种编程场景中有用,尤其是涉及到处理符号整数的情况。它可以用于:

  • 实现快速的除法运算:对于 2 的幂次方的除法,可以使用算术右移来快速完成。
  • 调整数值的大小:在不丢失符号的情况下调整数值的范围。
  • 信号处理和图形处理:在进行数据标准化或参数调整时经常需要算术右移。

示例

假设寄存器 a1 中存储了一个整数,我们需要将这个数值右移 3 位:

1
srai a2, a1, 3  # 将寄存器 a1 中的值向右算术移位 3 位,结果存储在 a2

这条指令会将 a1 中的值向右移动 3 位,移动过程中保留符号位,并将最终的结果存储在寄存器 a2 中。如果 a1 中的值为 -8(在二进制中表示为 11111111111111111111111111111000),则执行这条指令后 a2 中的值将是 -1(在二进制中表示为 11111111111111111111111111111111)。

srai 指令为 RISC-V 提供了有效的方式来处理算术右移,使得基于位的操作可以有效执行,同时保留数值的符号。

andi

在 RISC-V 指令集中,andi 指令代表 “AND Immediate”。这是一种 I-type 指令,用于将寄存器中的数值与一个立即数进行按位与(AND)操作,并将结果存储在目标寄存器中。andi 指令使得处理器可以直接与一个立即数进行逻辑运算,无需另一个寄存器作为第二操作数,这在处理位掩码和清除特定位等操作时非常有用。

指令格式

andi 指令的基本格式如下:

1
andi rd, rs1, imm
  • **rd**:目标寄存器,用于存储操作结果。
  • **rs1**:源寄存器,其值将被用于与操作。
  • **imm**:立即数,与源寄存器的值进行按位与操作。

功能描述

执行 andi 指令时,处理器将进行以下操作:

  1. 读取源寄存器:从寄存器 rs1 中读取数值。
  2. 按位与操作:将 rs1 的值与立即数 imm 进行按位与操作。此操作对 rs1 的每一位与 imm 的相应位执行逻辑与,如果两者都是 1,则结果位也是 1;否则结果位为 0。
  3. 存储结果:将按位与操作的结果存储到目标寄存器 rd 中。

使用场景

andi 指令在多种编程场景中有用,尤其是涉及到处理位级数据的情况。它可以用于:

  • 设置特定的位:可以用来清除(置零)特定的位,或确保某些位保持不变。
  • 创建掩码:在需要对数值应用特定掩码时使用。
  • 条件检查:检查特定的位是否被设置。

示例

假设寄存器 a1 中存储了一个整数,我们需要保留这个数值的低 8 位,其他位清零:

1
andi a2, a1, 255  # 将寄存器 a1 中的值与立即数 255 (二进制 11111111) 进行按位与,结果存储在 a2

这条指令将 a1 中的值与 255 进行按位与操作,只保留 a1 的低 8 位,其他位都会被清零,最终的结果存储在寄存器 a2 中。

andi 指令为 RISC-V 提供了一种高效的方式来执行与立即数的逻辑运算,它是实现基于位的操作的关键工具。

sll

在 RISC-V 指令集中,sll 指令代表 “Shift Left Logical”。这是一个 R-type 指令,用于将寄存器中的数值向左逻辑移位,移位的位数由另一个寄存器指定。此指令主要用于位操作,如数据的位扩展和快速乘法运算。

指令格式

sll 指令的基本格式如下:

1
sll rd, rs1, rs2
  • **rd**:目标寄存器,用于存储操作的结果。
  • **rs1**:源寄存器,其值将被移位。
  • **rs2**:指定移位位数的寄存器。

功能描述

执行 sll 指令时,处理器将进行以下操作:

  1. 读取源寄存器:从寄存器 rs1 中读取数值。
  2. 读取位数寄存器:从寄存器 rs2 中读取指定的移位位数。
  3. 逻辑左移:将 rs1 的值向左移动 rs2 中指定的位数。在移动过程中,从右侧移入的位将设置为 0。
  4. 存储结果:将移位后的结果存储到目标寄存器 rd 中。

使用场景

sll 指令在多种编程场景中有用,尤其是涉及到位操作的情况。它可以用于:

  • 位字段操作:可以用来左移位字段,扩大数值范围或为进一步的位操作做准备。
  • 快速乘法:对于2的幂次方的乘法,可以使用左移实现快速计算。
  • 数据打包:在处理需要组合多个小数据项到一个单一数据块中时使用。

示例

假设寄存器 a1 中存储了一个整数,我们需要将这个数值左移 a2 寄存器中指定的位数:

1
sll a3, a1, a2  # 将寄存器 a1 中的值向左移位,移位数由 a2 指定,结果存储在 a3

这条指令会将 a1 中的值向左移动 a2 中指定的位数,移位过程中低位补零,并将最终的结果存储在寄存器 a3 中。

sll 指令为 RISC-V 提供了一种高效的方式来执行逻辑左移,使得基于位的操作可以有效执行,从而扩展数据处理和数值计算的能力。

and

在 RISC-V 指令集中,and 指令代表 “AND”,即逻辑与操作。这是一个 R-type 指令,用于执行两个寄存器中的数值的按位与操作,并将结果存储在目标寄存器中。

指令格式

and 指令的基本格式如下:

1
and rd, rs1, rs2
  • **rd**:目标寄存器,用于存储操作的结果。
  • **rs1**:第一个源寄存器。
  • **rs2**:第二个源寄存器。

功能描述

当执行 and 指令时,处理器将进行以下操作:

  1. 按位与操作:从寄存器 rs1rs2 中读取数值,对这两个数值进行按位与操作。按位与是一个二元操作,其中两位都为1时,结果位才为1;否则,结果位为0。
  2. 存储结果:将按位与操作的结果存储到目标寄存器 rd 中。

使用场景

and 指令在多种编程场景中非常有用,包括:

  • 数据掩码操作:可以用来确保特定的位被清除(置0),或确保某些位保持不变。这对于处理位掩码或进行特定的配置设置非常有用。
  • 权限和属性检查:在操作系统和其他系统软件中,常用于检查和设置对象的权限和属性。
  • 实现简单的逻辑运算and 操作是实现复杂逻辑表达式的基础。

示例

假设寄存器 a1a2 分别存储了两个整数,我们需要获取这两个整数中同时为1的位:

1
and a3, a1, a2  # 将寄存器 a1 和 a2 中的值进行按位与,结果存储在 a3

这条指令将 a1a2 中的每一位进行比较,两者对应位都为1时,在 a3 的对应位也将设置为1。例如,如果 a11101a21011,那么 a3 将被设置为 1001

and 指令是 RISC-V 指令集中的基础逻辑操作之一,提供了直接通过寄存器进行按位与操作的能力,是处理器功能的基石之一。

xori

在 RISC-V 指令集中,xori 指令代表 “XOR Immediate”。这是一种 I-type 指令,用于执行寄存器中的数值与一个立即数的按位异或(XOR)操作,并将结果存储在目标寄存器中。这种指令允许直接与一个常量进行逻辑运算,非常适合进行快速的位反转或检查位差异等操作。

指令格式

xori 指令的基本格式如下:

1
xori rd, rs1, imm
  • **rd**:目标寄存器,用于存储操作的结果。
  • **rs1**:源寄存器,其值将被用于与操作。
  • **imm**:一个立即数,与源寄存器的值进行按位异或操作。

功能描述

当执行 xori 指令时,处理器将进行以下操作:

  1. 读取源寄存器:从寄存器 rs1 中读取数值。
  2. 按位异或操作:将 rs1 的值与立即数 imm 进行按位异或操作。按位异或是一个二元操作,其中两位相同结果为 0,不同则为 1。
  3. 存储结果:将按位异或操作的结果存储到目标寄存器 rd 中。

使用场景

xori 指令在多种编程场景中有用,尤其是涉及到处理位级数据的情况。它可以用于:

  • 位反转:可以通过异或一个全1的立即数来反转寄存器中的位。
  • 生成特定的位模式:用于快速生成或切换特定的位模式。
  • 条件检查:在某些条件或算法逻辑中,使用异或来确定两个值的位级差异。

示例

假设寄存器 a1 中存储了一个整数,我们需要将这个数值中的特定位反转:

1
xori a2, a1, 0x0F  # 将寄存器 a1 中的值与立即数 0x0F (二进制 00001111) 进行按位异或,结果存储在 a2

这条指令将 a1 中的值与 0x0F 进行按位异或操作,特别是低4位将被反转,而高位保持不变。如果 a1 的值为 1101 1010(二进制形式),则 a2 将被设置为 1101 1101

xori 指令为 RISC-V 提供了一种高效的方式来执行逻辑异或操作,使得基于位的操作可以有效执行,从而扩展数据处理和数值计算的能力。

bubble-sort.c

blez

在 RISC-V 指令集中,blez(Branch if Less than or Equal to Zero)并不是一个官方的单独指令。然而,许多指令集提供类似的功能,用于在某个寄存器的值小于或等于零时进行条件分支。在 RISC-V 中,这样的条件分支可以通过组合其他指令来实现。

RISC-V 中实现类似 blez 的功能

尽管 RISC-V 没有直接提供 blez 指令,但可以通过使用 bge(Branch if Greater or Equal)指令与零寄存器(x0zero)相结合来模拟这个功能。bge 指令用于判断第一个操作数(寄存器)是否大于等于第二个操作数(寄存器或立即数)。

示例

假设我们想要检查寄存器 a1 的值是否小于或等于零,并根据结果跳转到某个标签 target_label。可以这样写:

1
bge x0, a1, target_label

这行代码的逻辑是,如果零(x0)大于等于 a1 的值,那么就跳转到 target_label。这实际上检查了 a1 是否小于或等于零。

使用场景

类似 blez 的逻辑在多种编程场景中非常有用,尤其是在需要根据某些计算结果或状态值进行决策控制流的情况下,如:

  • 检查计数器或循环变量是否耗尽。
  • 根据函数或计算的结果判断是否需要执行错误处理或特殊处理。
  • 在算法中基于条件执行不同的代码路径。

通过这种方式,尽管 RISC-V 指令集本身可能不包括某些特定的分支指令,但它的设计允许通过组合现有的指令来实现复杂的逻辑,保持了指令集的整洁性和可扩展性。

crc32.c

lui

在 RISC-V 指令集中,lui 指令代表 “Load Upper Immediate”。这是一种 U-type 指令,用于将一个20位的立即数加载到寄存器的高20位,同时将低12位清零。这种指令非常有用于设置大的常数或者构造高精度地址,尤其是与其他指令组合使用时。

指令格式

lui 指令的基本格式如下:

1
lui rd, imm
  • **rd**:目标寄存器,用于存储加载的20位立即数。
  • **imm**:20位的立即数。

功能描述

执行 lui 指令时,处理器将进行以下操作:

  1. 加载立即数:取出指令中的20位立即数 imm
  2. 放置到寄存器:将这个20位立即数放置在目标寄存器 rd 的高20位。
  3. 清零低12位:目标寄存器的低12位被清零。

使用场景

lui 指令在多种编程场景中都非常有用,尤其是在进行内存地址计算或设置大的常数值时。它的主要用途包括:

  • 初始化寄存器:用于设置寄存器的初始值,特别是那些用于地址计算或数据操作的寄存器。
  • 地址计算:经常与 addi 或其他指令结合使用来形成完整的32位或更大的地址值。
  • 常量加载:加载大的常数到寄存器,为后续操作提供参数。

示例

假设我们需要将一个大的常数值加载到寄存器 a1

1
lui a1, 0x12345  # 将立即数 0x12345 加载到寄存器 a1 的高20位,低12位清零

执行后,a1 的值将是 0x12345000。这种操作是设置高精度数值的快捷方式,也是内存地址设置的基础。

通过 lui,RISC-V 提供了一种简单而有效的方法来处理高位立即数加载,使得处理器能够快速地设置大范围的数值或地址。这是构建高效且可扩展的软件系统的关键能力之一。

srli

在 RISC-V 指令集中,srli 指令代表 “Shift Right Logical Immediate”。这是一个 I-type 指令,用于将寄存器中的数值向右进行逻辑移位,移位的位数由立即数指定。逻辑移位意味着在右移的过程中,左边移入的位总是设置为0,不管原始的最高位是什么。

指令格式

srli 指令的基本格式如下:

1
srli rd, rs1, imm
  • **rd**:目标寄存器,用于存储移位后的结果。
  • **rs1**:源寄存器,其值将被移位。
  • **imm**:立即数,指定要移位的位数。

功能描述

执行 srli 指令时,处理器将进行以下操作:

  1. 读取源寄存器:从寄存器 rs1 中读取数值。
  2. 逻辑右移:按照立即数 imm 指定的位数,将 rs1 的值向右逻辑移位。在移位过程中,高位移入的部分全为0。
  3. 存储结果:将移位后的结果存储到目标寄存器 rd 中。

使用场景

srli 指令在需要进行位级别的操作时非常有用,尤其是处理无符号数或需要明确处理高位为0的场景。它可以用于:

  • 无符号除法:快速进行对2的幂次方的除法(无符号数),例如 srli 可以用来计算无符号数的除2、除4等。
  • 位处理:在数据处理或编码过程中,需要逻辑右移以处理某些特定位或进行掩码运算。
  • 数据打包和解包:在将多个数据项组合到一个寄存器中或者从中解包出来时,可以使用移位操作。

示例

假设寄存器 a1 中存储了一个整数,我们需要将这个数值右移3位:

1
srli a2, a1, 3  # 将寄存器 a1 中的值向右逻辑移位3位,结果存储在 a2

这条指令会将 a1 中的值向右移动3位,左边移入的位全为0,最终的结果存储在寄存器 a2 中。例如,如果 a1 的值是 0b10011000,执行 srli 后,a2 的值将是 0b00010011

srli 指令为 RISC-V 提供了一个基础的逻辑移位功能,使得在处理无符号数和进行位操作时更加高效。

bgeu

在 RISC-V 指令集中,bgeu 指令代表 “Branch if Greater or Equal Unsigned”。这是一种 B-type 指令,用于比较两个寄存器中的无符号整数值。如果第一个寄存器的值大于或等于第二个寄存器的值(以无符号整数的方式比较),则程序将执行到指定的标签或地址处。

指令格式

bgeu 指令的基本格式如下:

1
bgeu rs1, rs2, label
  • **rs1**:第一个源寄存器。
  • **rs2**:第二个源寄存器。
  • **label**:如果 rs1 的无符号值大于或等于 rs2 的无符号值,则跳转到这个标签指向的代码位置。

功能描述

执行 bgeu 指令时,处理器将进行以下操作:

  1. 比较操作:比较来自 rs1rs2 的无符号整数值。
  2. 条件跳转:如果 rs1 的无符号值大于或等于 rs2 的无符号值,程序的执行流将跳转到 label 指定的位置。如果它们不满足这个条件,则程序将继续执行下一条指令。

使用场景

bgeu 指令在处理无符号数比较的场景中非常有用,特别是在需要基于数值大小决定程序流向的场合。它可以用于:

  • 循环控制:在循环结构中,根据计数器或索引的大小来决定是否继续执行循环。
  • 数组或数据结构操作:在处理数组或其他数据结构时,比较索引或边界条件,防止越界。
  • 资源管理:在系统资源或内存管理中,根据资源的量或大小进行条件判断和相应操作。

示例

假设寄存器 t0t1 分别存储了两个无符号整数,我们需要根据它们的值大小来决定程序的流程:

1
2
3
4
bgeu t0, t1, proceed
# 其他指令
proceed:
# 如果 t0 大于或等于 t1,执行此处的代码

在这个示例中,如果 t0 的无符号值大于或等于 t1 的无符号值,程序将跳转到标签 proceed。这样的逻辑可以用于保护程序不进入不应该执行的代码区域,或者用于循环和条件执行的优化。

bgeu 指令是 RISC-V 指令集中对无符号整数比较提供的直接支持,是实现高效、安全程序控制流的重要工具。

slli

在 RISC-V 指令集中,slli 指令代表 “Shift Left Logical Immediate”。这是一个 I-type 指令,用于将寄存器中的数值向左逻辑移位,移位的位数由一个立即数指定。这种操作通常用于位字段操作,如快速乘以2的幂次,或者将数据字段对齐到特定的位置。

指令格式

slli 指令的基本格式如下:

1
slli rd, rs1, imm
  • **rd**:目标寄存器,用于存储移位后的结果。
  • **rs1**:源寄存器,其值将被移位。
  • **imm**:立即数,指定要移位的位数。

功能描述

执行 slli 指令时,处理器将进行以下操作:

  1. 读取源寄存器:从寄存器 rs1 中读取数值。
  2. 逻辑左移:按照立即数 imm 指定的位数,将 rs1 的值向左逻辑移位。在移动过程中,从右侧移入的位将设置为 0。
  3. 存储结果:将移位后的结果存储到目标寄存器 rd 中。

使用场景

slli 指令在多种编程场景中非常有用,尤其是涉及到位操作的情况。它可以用于:

  • 位字段操作:可以用来左移位字段,扩大数值范围或为进一步的位操作做准备。
  • 快速乘法:对于2的幂次方的乘法,可以使用左移实现快速计算。
  • 数据打包和解包:在将多个数据项组合到一个单一数据块中或者从中解包出来时,可以使用移位操作。

示例

假设寄存器 a1 中存储了一个整数,我们需要将这个数值左移 5 位:

1
slli a2, a1, 5  # 将寄存器 a1 中的值向左逻辑移位 5 位,结果存储在 a2

这条指令会将 a1 中的值向左移动 5 位,左边移入的位全为0,并将最终的结果存储在寄存器 a2 中。例如,如果 a1 的值是 0b0001(二进制),执行 slli 后,a2 的值将是 0b100000(二进制)。

slli 指令为 RISC-V 提供了一种高效的方式来执行逻辑左移,使得基于位的操作可以有效执行,从而扩展数据处理和数值计算的能力。

div.c

mul

在 RISC-V 指令集中,mul 指令代表 “Multiply”,用于执行两个寄存器中的数值的乘法操作,并将结果的低 32 位存储在目标寄存器中。这是一个 R-type 指令,是 RISC-V 指令集中标准整数乘法指令的一部分。

指令格式

mul 指令的基本格式如下:

1
mul rd, rs1, rs2
  • **rd**:目标寄存器,用于存储乘法操作的结果。
  • **rs1**:第一个源寄存器,提供第一个乘数。
  • **rs2**:第二个源寄存器,提供第二个乘数。

功能描述

执行 mul 指令时,处理器将进行以下操作:

  1. 读取源寄存器:从寄存器 rs1rs2 中分别读取两个操作数。
  2. 执行乘法操作:将这两个操作数相乘。
  3. 存储结果:将乘积的低 32 位存储到目标寄存器 rd 中。如果处理器是 64 位的,mul 仍然只会存储结果的低 32 位,而不考虑高位部分。

使用场景

mul 指令在需要进行整数乘法计算的多种场景中非常有用,特别是在涉及数学运算、算法实现、图形处理以及任何需要乘法计算的地方。它可以用于:

  • 算术运算:直接进行两个整数的乘法运算。
  • 算法实现:在执行算法,如矩阵乘法、信号处理等,中频繁使用乘法。
  • 资源估算:计算资源需求,如内存分配和性能预测等。

示例

假设寄存器 a1a2 分别存储了两个整数,我们需要计算这两个整数的乘积:

1
mul a3, a1, a2  # 将寄存器 a1 和 a2 中的值相乘,结果存储在 a3

这条指令将计算 a1a2 的乘积,并将结果的低 32 位存储在 a3 中。如果是在 32 位 RISC-V 架构上执行,结果是完整的;在 64 位架构上,如果乘积超过 32 位,则只存储低 32 位结果。

mul 指令是 RISC-V 指令集中处理基本乘法操作的关键工具,提供了高效的方式来执行整数乘法。

div

在 RISC-V 指令集中,div 指令代表 “Divide”,用于执行两个寄存器中的数值的除法操作。这是一个 R-type 指令,它处理有符号整数的除法,并将除法的商存储在目标寄存器中。

指令格式

div 指令的基本格式如下:

1
div rd, rs1, rs2
  • **rd**:目标寄存器,用于存储除法操作的结果,即商。
  • **rs1**:第一个源寄存器,提供被除数。
  • **rs2**:第二个源寄存器,提供除数。

功能描述

执行 div 指令时,处理器将进行以下操作:

  1. 读取源寄存器:从寄存器 rs1rs2 中分别读取被除数和除数。
  2. 执行除法操作
    • 如果 rs2(除数)为零,结果根据具体实现可能定义为最大的负数或特定的异常值。
    • 如果除法结果超出寄存器可以表示的范围(例如,最小的负数除以 -1),结果可能被定义为最大的正数或溢出的值。
  3. 存储结果:将除法的商存储到目标寄存器 rd 中。

使用场景

div 指令在需要进行整数除法计算的场景中非常有用,特别是在涉及数学运算和算法实现中。它可以用于:

  • 算术运算:直接进行两个整数的除法运算。
  • 算法实现:在执行算法,如统计分析、信号处理等,中频繁使用除法。
  • 资源分配:计算资源分配的比例或分配单元。

示例

假设寄存器 a1a2 分别存储了两个整数,我们需要计算这两个整数的除法:

1
div a3, a1, a2  # 将寄存器 a1 和 a2 中的值进行除法运算,结果存储在 a3

这条指令将计算 a1(被除数)除以 a2(除数)的商,并将结果存储在 a3 中。如果 a2 为零,处理器的行为将取决于具体的实现,可能会设置 a3 为某个特定的值或引发异常。

div 指令是 RISC-V 指令集中处理基本除法操作的关键工具,提供了一种有效的方式来执行有符号整数的除法运算。

现在,已经可以通过很多的程序了,上面的指令解析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// add.c
INSTPAT("??????? ????? ????? 010 ????? 00000 11", lw, I,
R(rd) = SEXT(Mr(src1 + imm, 4), 32));

INSTPAT("0000000 ????? ????? 000 ????? 01100 11", add, R,
R(rd) = src1 + src2);

INSTPAT("0100000 ????? ????? 000 ????? 01100 11", sub, R,
R(rd) = src1 - src2);
INSTPAT("??????? ????? ????? 011 ????? 00100 11", sltiu, I,
R(rd) = (word_t)src1 < (word_t)imm);

INSTPAT("??????? ????? ????? 000 ????? 11000 11", beq, B,
if (src1 == src2) s->dnpc = s->pc + imm);

INSTPAT("??????? ????? ????? 001 ????? 11000 11", bne, B,
if (src1 != src2) s->dnpc = s->pc + imm);
// add-longlong.c
INSTPAT("0000000 ????? ????? 011 ????? 01100 11", sltu, R,
R(rd) = (word_t)src1 < (word_t)src2);

INSTPAT("0000000 ????? ????? 100 ????? 01100 11", xor, R,
R(rd) = src1 ^ src2);

INSTPAT("0000000 ????? ????? 110 ????? 01100 11", or, R,
R(rd) = src1 | src2);
// bit.c
INSTPAT("??????? ????? ????? 001 ????? 01000 11", sh, S,
Mw(src1 + imm, 2, src2));
INSTPAT(
"0100000 ????? ????? 101 ????? 00100 11", srai, I,
R(rd) =
(sword_t)src1 >>
BITS(imm, 5,
0)); // 数学右移,保留符号位;丢弃 I-TYPE imm 前面的0100000
INSTPAT("??????? ????? ????? 111 ????? 00100 11", andi, I,
R(rd) = src1 & imm);
INSTPAT("0000000 ????? ????? 001 ????? 01100 11", sll, R,
R(rd) = src1 << src2);

INSTPAT("0000000 ????? ????? 111 ????? 01100 11", and, R,
R(rd) = src1 & src2);

INSTPAT("??????? ????? ????? 100 ????? 00100 11", xori, I,
R(rd) = src1 ^ imm);

// bubble-sort.c
INSTPAT("??????? ????? ????? 101 ????? 11000 11", bge, B,
if (src1 >= src2) s->dnpc = s->pc + imm);
// crc32.c
INSTPAT("??????? ????? ????? ??? ????? 01101 11", lui, U,
R(rd) = imm); // TYPE-U 已经将数左移 12 位了,这里不需要再继续左移

INSTPAT(
"0000000 ????? ????? 101 ????? 00100 11", srli, I,
R(rd) =
(word_t)src1 >>
BITS(imm, 5,
0)); // 逻辑右移,不保留符号位;丢弃 I-TYPE imm 前面的0000000

INSTPAT("??????? ????? ????? 111 ????? 11000 11", bgeu, B,
if ((word_t)src1 >= (word_t)src2) s->dnpc = s->pc + imm);

INSTPAT(
"0000000 ????? ????? 001 ????? 00100 11", slli, I,
R(rd) =
(word_t)src1 << BITS(
imm, 5,
0)); // 逻辑右移,不保留符号位;丢弃 I-TYPE imm 前面的0000000

// div.c
INSTPAT("0000001 ????? ????? 000 ????? 01100 11", mul, R,
R(rd) = src1 * src2);
INSTPAT("0000001 ????? ????? 100 ????? 01100 11", div, R,
R(rd) = src1 / src2);

NEMU 添加更多的指令
http://blog.luliang.online/2024/08/21/NEMU添加更多指令/
作者
Luyoung
发布于
2024年8月21日
许可协议