NEMU 添加更多的指令
前言
上一篇博客中介绍了如何添加指令,这篇将会熟悉这个过程,学习以及添加更多的指令。
所有程序
在am-kernels/tests/cpu-tests/tests 目录下有很多测试:
1 |
|
我的目标是,将这些测试全部通过。
add.c
1 |
|
老规矩,直接看反汇编代码,发现是这条指令:
1 |
|
lw
在 RISC-V 指令集中,lw
指令表示“Load Word”。这是一种加载指令,用于将内存中的一个 32 位字(word)读取到寄存器中。lw
是 I-type(立即数类型)指令的一部分,用于从指定的内存地址加载数据到寄存器。
指令格式
lw
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储加载的数据。 - **
offset
**:一个立即数,表示相对于基址寄存器rs1
的偏移量。 - **
rs1
**:基址寄存器,提供内存加载操作的基础地址。
功能描述
当执行 lw
指令时,处理器会执行以下操作:
- 从基址寄存器
rs1
中读取地址。 - 将这个地址与立即数
offset
相加,计算出最终的内存地址。 - 从得到的内存地址读取一个 32 位字(4 字节)。
- 将读取的数据存储到目标寄存器
rd
中。
使用场景
lw
指令在多种编程场景中都非常有用,特别是在需要从内存中读取数据的情况下。它可以用来:
- 从数组或数据结构中获取元素。
- 读取函数调用的参数或局部变量(如果它们被存储在内存中)。
- 在进行系统调用或硬件操作时,从特定的内存映射区域加载设备状态或数据。
示例
假设有一个整数数组,数组的首地址存储在寄存器 a0
中,我们想要加载数组的第一个元素到寄存器 t0
:
1 |
|
这条指令将会从 a0
指定的地址加载一个 32 位的整数到 t0
寄存器中。如果要加载数组的第二个元素,偏移量将是 4(因为每个数组元素占用 4 字节):
1 |
|
因此,指令应该这样解析和执行:
1 |
|
继续编译运行:
1 |
|
00a90533 是:00a90533 add a0,s2,a0。
add
在 RISC-V 指令集中,add
指令是一种算术指令,用于执行两个寄存器的内容之间的整数加法操作,并将结果存储在一个寄存器中。这是一种 R-type(寄存器类型)指令,完全基于寄存器操作,不涉及立即数或内存访问。
指令格式
add
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储加法运算的结果。 - **
rs1
**:第一个源寄存器,提供第一个加数。 - **
rs2
**:第二个源寄存器,提供第二个加数。
功能描述
当执行 add
指令时,处理器会进行以下操作:
- 从寄存器
rs1
中读取第一个操作数。 - 从寄存器
rs2
中读取第二个操作数。 - 将这两个操作数相加。
- 将相加的结果存储到目标寄存器
rd
中。
使用场景
add
指令在几乎所有需要进行算术运算的编程任务中都非常重要。它用于:
- 简单的数值运算。
- 地址计算,如确定数据结构中的元素位置。
- 循环控制和迭代中的计数器操作。
- 数组和指针操作中的索引计算。
示例
假设寄存器 a0
和 a1
分别存储了两个整数,我们想将这两个整数相加,并将结果存储在寄存器 a2
中:
1 |
|
这条指令将会把 a0
和 a1
中的值相加,然后将计算结果存储到 a2
。
这种指令对于实现高级数据结构和复杂算法至关重要,它提供了基本的数值处理能力,是处理器功能的基石之一。
因此,可以这样解析和操作:
1 |
|
继续编译和运行:
1 |
|
可以看到,add 成功解析,接下来解析40f50533:40f50533 sub a0,a0,a5
sub
在 RISC-V 指令集中,sub
指令用于从一个寄存器的值中减去另一个寄存器的值,并将结果存储在一个寄存器中。这也是一个 R-type 指令,与 add
指令相似,但用于执行整数减法运算。
指令格式
sub
指令的基本格式如下:
1 |
|
- **
rd
**(Destination Register):目标寄存器,用于存储运算结果。 - **
rs1
**(Source Register 1):第一个源寄存器,提供被减数。 - **
rs2
**(Source Register 2):第二个源寄存器,提供减数。
功能描述
执行 sub
指令时,处理器将进行以下操作:
- 从寄存器
rs1
中读取被减数。 - 从寄存器
rs2
中读取减数。 - 将
rs1
的值减去rs2
的值。 - 将得到的结果存储到目标寄存器
rd
中。
使用场景
sub
指令在各种需要算术减法的编程场景中都非常有用。它可以用于:
- 算术计算和数据处理。
- 地址或指针计算,如计算偏移量或逆向索引。
- 控制结构中的计数器递减操作。
- 在实现某些算法(如排序、搜索等)时调整循环或条件。
示例
假设寄存器 t0
和 t1
分别存储了两个整数,我们想从 t0
的值中减去 t1
的值,并将结果存储在寄存器 t2
中:
1 |
|
这条指令将计算 t0 - t1
的结果,并把结果保存在 t2
寄存器中。
sub
指令与 add
指令一样,是 RISC-V 指令集中基本的算术操作之一,提供了直接通过寄存器进行整数减法的能力。
因此,sub 指令可以这样解析和实现:
1 |
|
编译运行:
1 |
|
可以看到 sub 解析成功,接下来就是:00153513 seqz a0,a0。
seqz
在 RISC-V 指令集中,seqz
指令表示“Set if Equal to Zero”。这是一种 I-type 指令,用于检查一个寄存器的值是否为零,并据此设置另一个寄存器的值。如果检查的寄存器值为零,则目标寄存器被设置为1;如果不为零,则设置为0。这种指令通常用于条件判断和分支控制逻辑。
指令格式
seqz
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储结果(1 或 0)。 - **
rs
**:源寄存器,其值将被检查是否等于零。
功能描述
执行 seqz
指令时,处理器将进行以下操作:
- 从寄存器
rs
中读取值。 - 检查
rs
的值是否为零:- 如果
rs
的值为零,则将rd
设置为1。 - 如果
rs
的值不为零,则将rd
设置为0。
- 如果
使用场景
seqz
指令在需要根据某些条件控制程序流程的场景中非常有用,例如:
- 判断循环是否应该结束。
- 检查函数的返回值是否表明某种特定状态(例如错误码为零表示无错误)。
- 实现更复杂的逻辑条件判断,通常与其他分支指令结合使用。
示例
假设我们需要检查寄存器 a1
中的值是否为零,并根据这个结果决定接下来的操作。我们可以使用 seqz
指令如下:
1 |
|
如果 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
(Set Less Than Immediate Unsigned):这条指令比较寄存器rs
和立即数1
。如果rs
小于1
(即,rs
为0
,因为无符号比较),rd
将被设置为1
。如果rs
不为0
,rd
将被设置为0
。
因此,seqz(sltiu)可以这样设置:
1 |
|
编译和运行:
1 |
|
继续解析:00050463 beqz a0,80000018。
beqz
在 RISC-V 指令集中,beqz
同样是一种伪指令,用于条件分支。beqz
的全称是 “Branch if Equal to Zero”。当给定的寄存器中的值等于零时,它将导致程序跳转到指定的标签或地址。这是在编程中常用的控制流机制,特别是在循环和条件判断中。
指令格式
beqz
的使用格式如下:
1 |
|
- **
rs
**:源寄存器,其值将被检查是否等于零。 - **
label
**:如果rs
的值为零,则跳转到这个标签指向的代码位置。
功能描述
当执行 beqz
指令时,如果寄存器 rs
的值为零,则程序的执行流将跳转到 label
指定的位置。如果 rs
的值不为零,程序将继续执行下一条指令。
实现细节
作为一种伪指令,beqz rs, label
通常被汇编器翻译为正式的 RISC-V 指令:
1 |
|
- **
beq
**(Branch if Equal):这是一个基础的分支指令,用于比较两个寄存器的值。如果它们相等,则执行跳转。在这里,beqz
使用beq
指令将rs
寄存器与zero
寄存器(永远为 0 的寄存器)比较。
使用场景
beqz
指令在处理循环退出条件或根据函数返回值执行错误处理时非常有用。例如,它可以用来检查函数的返回值是否表示失败(通常用0表示),如果是,则跳转到错误处理代码。
先设置 TYPE-B 的 imm 宏:
1 |
|
因此,beqz(beq)可以这样设置:
1 |
|
编译,运行:
1 |
|
接着继续处理下一跳指令:fe8990e3 bne s3,s0,80000084。
bne
在 RISC-V 指令集中,bne
指令代表 “Branch if Not Equal”。这是一种条件分支指令,用于在两个寄存器的值不相等时,将程序的执行流跳转到指定的标签或地址。bne
是 RISC-V 的基础指令之一,直接支持在硬件级别,非伪指令。
指令格式
bne
指令的基本格式如下:
1 |
|
- **
rs1
**:第一个源寄存器。 - **
rs2
**:第二个源寄存器。 - **
label
**:如果rs1
和rs2
的值不相等,则跳转到这个标签指向的代码位置。
功能描述
执行 bne
指令时的操作如下:
- 比较操作:比较来自
rs1
和rs2
的值。 - 条件跳转:如果
rs1
和rs2
的值不相等,程序的执行流将跳转到label
指定的位置。如果它们相等,则继续执行下一条指令。
使用场景
bne
指令在许多编程任务中都非常有用,特别是在循环、条件判断和错误检测中。它允许程序根据条件执行不同的代码路径,是实现复杂逻辑和控制流的关键工具。
因此可以这样设置:
1 |
|
可以看到,bne 和 beq 指令非常相似。
编译运行:
1 |
|
这样这个 add.c 就算完成了。
add-longlong.c
sltu
在 RISC-V 指令集中,sltu
指令代表“Set Less Than Unsigned”。这是一个 R-type 指令,用于比较两个寄存器中的无符号整数值,并根据比较结果设置目标寄存器的值。
指令格式
sltu
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,将存储比较的结果。 - **
rs1
**:第一个源寄存器,其值作为比较的左操作数。 - **
rs2
**:第二个源寄存器,其值作为比较的右操作数。
功能描述
执行 sltu
指令时,处理器会进行以下操作:
- 比较操作:比较来自
rs1
和rs2
的值,作为无符号整数进行比较。 - 设置结果:
- 如果
rs1
的无符号值小于rs2
的无符号值,则将rd
设置为1
。 - 否则,将
rd
设置为0
。
- 如果
使用场景
sltu
指令在多种编程场景中都非常有用,尤其是涉及无符号数比较的情况。它可以用于:
- 实现条件分支和循环控制,特别是在数据结构如优先队列和二叉搜索树的操作中。
- 数值比较,尤其是在处理大量数据或数组时,进行元素排序和搜索。
- 算法中的决策制定,如决定两个无符号数的大小关系。
示例
假设我们需要根据两个无符号整数的大小关系来执行不同的操作。我们可以使用 sltu
指令如下:
1 |
|
如果 a1
中的无符号值小于 a2
中的值,则 t0
将被设置为 1
;否则,t0
将被设置为 0
。然后,可以使用 t0
的值来控制程序的分支,例如使用条件分支指令跳转到不同的代码段。
sltu
为 RISC-V 提供了一种简洁的方式来比较无符号整数,并根据比较结果设置条件代码或执行分支操作。
1 |
|
xor
在 RISC-V 指令集中,xor
指令用于执行两个寄存器之间的按位异或(XOR)操作。这也是一个 R-type 指令,它处理两个寄存器的位数据,并将结果存储在一个目标寄存器中。
指令格式
xor
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储操作的结果。 - **
rs1
**:第一个源寄存器。 - **
rs2
**:第二个源寄存器。
功能描述
当执行 xor
指令时,处理器将进行以下操作:
- 按位异或操作:从寄存器
rs1
和rs2
中读取数值,对这两个数值进行按位异或操作。按位异或是一个二元操作,其中两位相同结果为 0,不同则为 1。 - 存储结果:将异或操作的结果存储到目标寄存器
rd
中。
使用场景
xor
指令在各种场景中有多种用途,包括:
- 数据掩码和位操作:可以用来切换特定的位或清除特定的位设置。
- 数据校验:在某些错误检测和校正算法中,使用异或操作来生成校验位或校验码。
- 实现简单的逻辑运算:异或操作在某些算法中用于条件逻辑判断。
- 加密和解密:在某些简单的加密算法中,使用异或来对数据进行加密或解密。
示例
假设有两个寄存器 a1
和 a2
,其中存储了两个数值,我们需要获取这两个数值的按位异或结果:
1 |
|
这条指令会将 a1
和 a2
中的每一位进行比较,相同则在 a3
的对应位设置为 0,不同则设置为 1。例如,如果 a1
是 1101
,a2
是 1011
,那么 a3
将被设置为 0110
。
xor
指令是 RISC-V 指令集中基础的位操作指令之一,提供了基本的逻辑功能,是许多更复杂操作和算法的构建块。
1 |
|
or
在 RISC-V 指令集中,or
指令用于执行两个寄存器之间的按位或(OR)操作。这也是一个 R-type 指令,它处理两个寄存器的位数据,并将结果存储在一个目标寄存器中。
指令格式
or
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储操作的结果。 - **
rs1
**:第一个源寄存器。 - **
rs2
**:第二个源寄存器。
功能描述
执行 or
指令时,处理器将进行以下操作:
- 按位或操作:从寄存器
rs1
和rs2
中读取数值,对这两个数值进行按位或操作。按位或是一个二元操作,其中任一位为 1,则结果位也为 1。 - 存储结果:将按位或操作的结果存储到目标寄存器
rd
中。
使用场景
or
指令在多种编程场景中非常有用,包括:
- 设置特定的位:可以用来确保特定的位设置为 1,常用于标志位和控制位的操作。
- 合并位掩码:在需要结合多个条件或标志时,使用按位或来合并掩码。
- 实现简单的逻辑运算:
or
通常用于实现基本的逻辑运算,尤其是在布尔逻辑和条件判断中。
示例
假设寄存器 a1
和 a2
分别存储了两个数值,我们需要合并这两个数值的位,保证任一位为 1 的都设置为 1:
1 |
|
这条指令会将 a1
和 a2
中的每一位进行比较,任何一位为 1,相应的在 a3
的对应位也将设置为 1。例如,如果 a1
是 1101
,a2
是 1011
,那么 a3
将被设置为 1111
。
or
指令是 RISC-V 指令集中基础的位操作指令之一,提供了基本的逻辑功能,是许多更复杂操作和算法的构建块。
1 |
|
bit.c
sh
在 RISC-V 指令集中,sh
指令代表 “Store Halfword”。这是一个 S-type 指令,用于将一个 16位(半字)的整数从寄存器存储到内存中。与其他存储指令如 sb
(Store Byte)和 sw
(Store Word)相似,sh
主要用于将数据写入内存中指定的地址。
指令格式
sh
指令的基本格式如下:
1 |
|
- **
rs2
**:源寄存器,其值(或其中的一部分)将被存储到内存中。 - **
rs1
**:基址寄存器,提供内存地址的基础部分。 - **
offset
**:一个立即数偏移量,与基址寄存器的内容相加,得到最终的内存地址。
功能描述
执行 sh
指令时,处理器将进行以下操作:
- 计算地址:从基址寄存器
rs1
中读取基础地址,并加上偏移量offset
,以计算出内存中的目标地址。 - 存储数据:将寄存器
rs2
的低 16 位(即半字)存储到计算出的内存地址中。
使用场景
sh
指令在需要将较小的数据单元(如 16 位整数)存储到内存的情况下非常有用。它可以用于以下场景:
- 保存数据结构的一部分:如只需要更新结构中的一个字段时。
- 处理数组或列表中的元素:特别是在数组元素为 16 位整数的情况下。
- 资源受限的环境:在内存或带宽受限的嵌入式系统中,可能需要优化数据传输的大小。
示例
假设我们需要将寄存器 t1
中的一个 16 位值存储到由寄存器 a0
指定的内存地址处,地址偏移量为 2:
1 |
|
此指令会将 t1
的低 16 位内容存储到 a0 + 2
指定的内存位置。这种操作是在处理各种数据传输和存储操作时常见的,特别是在与硬件设备交互或在内存中构建数据结构时。
srai
在 RISC-V 指令集中,srai
指令代表 “Shift Right Arithmetic Immediate”。这是一个 I-type 指令,用于将寄存器中的数值向右算术移位一定的位数,位数由立即数指定,移位过程中保持数值的符号位不变。这种指令在处理符号整数时非常有用,因为它保持了数的符号(正或负)。
指令格式
srai
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储操作结果。 - **
rs1
**:源寄存器,其值将被移位。 - **
imm
**:立即数,指定向右移动的位数。
功能描述
执行 srai
指令时,处理器将进行以下操作:
- 读取源寄存器:从寄存器
rs1
中读取数值。 - 算术右移:按照
imm
指定的位数将rs1
的值向右移动。在移动过程中,最高位(符号位)的值将复制到新移出的高位中,这保证了移位后的数值保持原有的符号。 - 存储结果:将移位后的结果存储到目标寄存器
rd
中。
使用场景
srai
指令在多种编程场景中有用,尤其是涉及到处理符号整数的情况。它可以用于:
- 实现快速的除法运算:对于 2 的幂次方的除法,可以使用算术右移来快速完成。
- 调整数值的大小:在不丢失符号的情况下调整数值的范围。
- 信号处理和图形处理:在进行数据标准化或参数调整时经常需要算术右移。
示例
假设寄存器 a1
中存储了一个整数,我们需要将这个数值右移 3 位:
1 |
|
这条指令会将 a1
中的值向右移动 3 位,移动过程中保留符号位,并将最终的结果存储在寄存器 a2
中。如果 a1
中的值为 -8
(在二进制中表示为 11111111111111111111111111111000
),则执行这条指令后 a2
中的值将是 -1
(在二进制中表示为 11111111111111111111111111111111
)。
srai
指令为 RISC-V 提供了有效的方式来处理算术右移,使得基于位的操作可以有效执行,同时保留数值的符号。
andi
在 RISC-V 指令集中,andi
指令代表 “AND Immediate”。这是一种 I-type 指令,用于将寄存器中的数值与一个立即数进行按位与(AND)操作,并将结果存储在目标寄存器中。andi
指令使得处理器可以直接与一个立即数进行逻辑运算,无需另一个寄存器作为第二操作数,这在处理位掩码和清除特定位等操作时非常有用。
指令格式
andi
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储操作结果。 - **
rs1
**:源寄存器,其值将被用于与操作。 - **
imm
**:立即数,与源寄存器的值进行按位与操作。
功能描述
执行 andi
指令时,处理器将进行以下操作:
- 读取源寄存器:从寄存器
rs1
中读取数值。 - 按位与操作:将
rs1
的值与立即数imm
进行按位与操作。此操作对rs1
的每一位与imm
的相应位执行逻辑与,如果两者都是 1,则结果位也是 1;否则结果位为 0。 - 存储结果:将按位与操作的结果存储到目标寄存器
rd
中。
使用场景
andi
指令在多种编程场景中有用,尤其是涉及到处理位级数据的情况。它可以用于:
- 设置特定的位:可以用来清除(置零)特定的位,或确保某些位保持不变。
- 创建掩码:在需要对数值应用特定掩码时使用。
- 条件检查:检查特定的位是否被设置。
示例
假设寄存器 a1
中存储了一个整数,我们需要保留这个数值的低 8 位,其他位清零:
1 |
|
这条指令将 a1
中的值与 255
进行按位与操作,只保留 a1
的低 8 位,其他位都会被清零,最终的结果存储在寄存器 a2
中。
andi
指令为 RISC-V 提供了一种高效的方式来执行与立即数的逻辑运算,它是实现基于位的操作的关键工具。
sll
在 RISC-V 指令集中,sll
指令代表 “Shift Left Logical”。这是一个 R-type 指令,用于将寄存器中的数值向左逻辑移位,移位的位数由另一个寄存器指定。此指令主要用于位操作,如数据的位扩展和快速乘法运算。
指令格式
sll
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储操作的结果。 - **
rs1
**:源寄存器,其值将被移位。 - **
rs2
**:指定移位位数的寄存器。
功能描述
执行 sll
指令时,处理器将进行以下操作:
- 读取源寄存器:从寄存器
rs1
中读取数值。 - 读取位数寄存器:从寄存器
rs2
中读取指定的移位位数。 - 逻辑左移:将
rs1
的值向左移动rs2
中指定的位数。在移动过程中,从右侧移入的位将设置为 0。 - 存储结果:将移位后的结果存储到目标寄存器
rd
中。
使用场景
sll
指令在多种编程场景中有用,尤其是涉及到位操作的情况。它可以用于:
- 位字段操作:可以用来左移位字段,扩大数值范围或为进一步的位操作做准备。
- 快速乘法:对于2的幂次方的乘法,可以使用左移实现快速计算。
- 数据打包:在处理需要组合多个小数据项到一个单一数据块中时使用。
示例
假设寄存器 a1
中存储了一个整数,我们需要将这个数值左移 a2
寄存器中指定的位数:
1 |
|
这条指令会将 a1
中的值向左移动 a2
中指定的位数,移位过程中低位补零,并将最终的结果存储在寄存器 a3
中。
sll
指令为 RISC-V 提供了一种高效的方式来执行逻辑左移,使得基于位的操作可以有效执行,从而扩展数据处理和数值计算的能力。
and
在 RISC-V 指令集中,and
指令代表 “AND”,即逻辑与操作。这是一个 R-type 指令,用于执行两个寄存器中的数值的按位与操作,并将结果存储在目标寄存器中。
指令格式
and
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储操作的结果。 - **
rs1
**:第一个源寄存器。 - **
rs2
**:第二个源寄存器。
功能描述
当执行 and
指令时,处理器将进行以下操作:
- 按位与操作:从寄存器
rs1
和rs2
中读取数值,对这两个数值进行按位与操作。按位与是一个二元操作,其中两位都为1时,结果位才为1;否则,结果位为0。 - 存储结果:将按位与操作的结果存储到目标寄存器
rd
中。
使用场景
and
指令在多种编程场景中非常有用,包括:
- 数据掩码操作:可以用来确保特定的位被清除(置0),或确保某些位保持不变。这对于处理位掩码或进行特定的配置设置非常有用。
- 权限和属性检查:在操作系统和其他系统软件中,常用于检查和设置对象的权限和属性。
- 实现简单的逻辑运算:
and
操作是实现复杂逻辑表达式的基础。
示例
假设寄存器 a1
和 a2
分别存储了两个整数,我们需要获取这两个整数中同时为1的位:
1 |
|
这条指令将 a1
和 a2
中的每一位进行比较,两者对应位都为1时,在 a3
的对应位也将设置为1。例如,如果 a1
是 1101
和 a2
是 1011
,那么 a3
将被设置为 1001
。
and
指令是 RISC-V 指令集中的基础逻辑操作之一,提供了直接通过寄存器进行按位与操作的能力,是处理器功能的基石之一。
xori
在 RISC-V 指令集中,xori
指令代表 “XOR Immediate”。这是一种 I-type 指令,用于执行寄存器中的数值与一个立即数的按位异或(XOR)操作,并将结果存储在目标寄存器中。这种指令允许直接与一个常量进行逻辑运算,非常适合进行快速的位反转或检查位差异等操作。
指令格式
xori
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储操作的结果。 - **
rs1
**:源寄存器,其值将被用于与操作。 - **
imm
**:一个立即数,与源寄存器的值进行按位异或操作。
功能描述
当执行 xori
指令时,处理器将进行以下操作:
- 读取源寄存器:从寄存器
rs1
中读取数值。 - 按位异或操作:将
rs1
的值与立即数imm
进行按位异或操作。按位异或是一个二元操作,其中两位相同结果为 0,不同则为 1。 - 存储结果:将按位异或操作的结果存储到目标寄存器
rd
中。
使用场景
xori
指令在多种编程场景中有用,尤其是涉及到处理位级数据的情况。它可以用于:
- 位反转:可以通过异或一个全1的立即数来反转寄存器中的位。
- 生成特定的位模式:用于快速生成或切换特定的位模式。
- 条件检查:在某些条件或算法逻辑中,使用异或来确定两个值的位级差异。
示例
假设寄存器 a1
中存储了一个整数,我们需要将这个数值中的特定位反转:
1 |
|
这条指令将 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)指令与零寄存器(x0
或 zero
)相结合来模拟这个功能。bge
指令用于判断第一个操作数(寄存器)是否大于等于第二个操作数(寄存器或立即数)。
示例
假设我们想要检查寄存器 a1
的值是否小于或等于零,并根据结果跳转到某个标签 target_label
。可以这样写:
1 |
|
这行代码的逻辑是,如果零(x0
)大于等于 a1
的值,那么就跳转到 target_label
。这实际上检查了 a1
是否小于或等于零。
使用场景
类似 blez
的逻辑在多种编程场景中非常有用,尤其是在需要根据某些计算结果或状态值进行决策控制流的情况下,如:
- 检查计数器或循环变量是否耗尽。
- 根据函数或计算的结果判断是否需要执行错误处理或特殊处理。
- 在算法中基于条件执行不同的代码路径。
通过这种方式,尽管 RISC-V 指令集本身可能不包括某些特定的分支指令,但它的设计允许通过组合现有的指令来实现复杂的逻辑,保持了指令集的整洁性和可扩展性。
crc32.c
lui
在 RISC-V 指令集中,lui
指令代表 “Load Upper Immediate”。这是一种 U-type 指令,用于将一个20位的立即数加载到寄存器的高20位,同时将低12位清零。这种指令非常有用于设置大的常数或者构造高精度地址,尤其是与其他指令组合使用时。
指令格式
lui
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储加载的20位立即数。 - **
imm
**:20位的立即数。
功能描述
执行 lui
指令时,处理器将进行以下操作:
- 加载立即数:取出指令中的20位立即数
imm
。 - 放置到寄存器:将这个20位立即数放置在目标寄存器
rd
的高20位。 - 清零低12位:目标寄存器的低12位被清零。
使用场景
lui
指令在多种编程场景中都非常有用,尤其是在进行内存地址计算或设置大的常数值时。它的主要用途包括:
- 初始化寄存器:用于设置寄存器的初始值,特别是那些用于地址计算或数据操作的寄存器。
- 地址计算:经常与
addi
或其他指令结合使用来形成完整的32位或更大的地址值。 - 常量加载:加载大的常数到寄存器,为后续操作提供参数。
示例
假设我们需要将一个大的常数值加载到寄存器 a1
:
1 |
|
执行后,a1
的值将是 0x12345000
。这种操作是设置高精度数值的快捷方式,也是内存地址设置的基础。
通过 lui
,RISC-V 提供了一种简单而有效的方法来处理高位立即数加载,使得处理器能够快速地设置大范围的数值或地址。这是构建高效且可扩展的软件系统的关键能力之一。
srli
在 RISC-V 指令集中,srli
指令代表 “Shift Right Logical Immediate”。这是一个 I-type 指令,用于将寄存器中的数值向右进行逻辑移位,移位的位数由立即数指定。逻辑移位意味着在右移的过程中,左边移入的位总是设置为0,不管原始的最高位是什么。
指令格式
srli
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储移位后的结果。 - **
rs1
**:源寄存器,其值将被移位。 - **
imm
**:立即数,指定要移位的位数。
功能描述
执行 srli
指令时,处理器将进行以下操作:
- 读取源寄存器:从寄存器
rs1
中读取数值。 - 逻辑右移:按照立即数
imm
指定的位数,将rs1
的值向右逻辑移位。在移位过程中,高位移入的部分全为0。 - 存储结果:将移位后的结果存储到目标寄存器
rd
中。
使用场景
srli
指令在需要进行位级别的操作时非常有用,尤其是处理无符号数或需要明确处理高位为0的场景。它可以用于:
- 无符号除法:快速进行对2的幂次方的除法(无符号数),例如
srli
可以用来计算无符号数的除2、除4等。 - 位处理:在数据处理或编码过程中,需要逻辑右移以处理某些特定位或进行掩码运算。
- 数据打包和解包:在将多个数据项组合到一个寄存器中或者从中解包出来时,可以使用移位操作。
示例
假设寄存器 a1
中存储了一个整数,我们需要将这个数值右移3位:
1 |
|
这条指令会将 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 |
|
- **
rs1
**:第一个源寄存器。 - **
rs2
**:第二个源寄存器。 - **
label
**:如果rs1
的无符号值大于或等于rs2
的无符号值,则跳转到这个标签指向的代码位置。
功能描述
执行 bgeu
指令时,处理器将进行以下操作:
- 比较操作:比较来自
rs1
和rs2
的无符号整数值。 - 条件跳转:如果
rs1
的无符号值大于或等于rs2
的无符号值,程序的执行流将跳转到label
指定的位置。如果它们不满足这个条件,则程序将继续执行下一条指令。
使用场景
bgeu
指令在处理无符号数比较的场景中非常有用,特别是在需要基于数值大小决定程序流向的场合。它可以用于:
- 循环控制:在循环结构中,根据计数器或索引的大小来决定是否继续执行循环。
- 数组或数据结构操作:在处理数组或其他数据结构时,比较索引或边界条件,防止越界。
- 资源管理:在系统资源或内存管理中,根据资源的量或大小进行条件判断和相应操作。
示例
假设寄存器 t0
和 t1
分别存储了两个无符号整数,我们需要根据它们的值大小来决定程序的流程:
1 |
|
在这个示例中,如果 t0
的无符号值大于或等于 t1
的无符号值,程序将跳转到标签 proceed
。这样的逻辑可以用于保护程序不进入不应该执行的代码区域,或者用于循环和条件执行的优化。
bgeu
指令是 RISC-V 指令集中对无符号整数比较提供的直接支持,是实现高效、安全程序控制流的重要工具。
slli
在 RISC-V 指令集中,slli
指令代表 “Shift Left Logical Immediate”。这是一个 I-type 指令,用于将寄存器中的数值向左逻辑移位,移位的位数由一个立即数指定。这种操作通常用于位字段操作,如快速乘以2的幂次,或者将数据字段对齐到特定的位置。
指令格式
slli
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储移位后的结果。 - **
rs1
**:源寄存器,其值将被移位。 - **
imm
**:立即数,指定要移位的位数。
功能描述
执行 slli
指令时,处理器将进行以下操作:
- 读取源寄存器:从寄存器
rs1
中读取数值。 - 逻辑左移:按照立即数
imm
指定的位数,将rs1
的值向左逻辑移位。在移动过程中,从右侧移入的位将设置为 0。 - 存储结果:将移位后的结果存储到目标寄存器
rd
中。
使用场景
slli
指令在多种编程场景中非常有用,尤其是涉及到位操作的情况。它可以用于:
- 位字段操作:可以用来左移位字段,扩大数值范围或为进一步的位操作做准备。
- 快速乘法:对于2的幂次方的乘法,可以使用左移实现快速计算。
- 数据打包和解包:在将多个数据项组合到一个单一数据块中或者从中解包出来时,可以使用移位操作。
示例
假设寄存器 a1
中存储了一个整数,我们需要将这个数值左移 5 位:
1 |
|
这条指令会将 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 |
|
- **
rd
**:目标寄存器,用于存储乘法操作的结果。 - **
rs1
**:第一个源寄存器,提供第一个乘数。 - **
rs2
**:第二个源寄存器,提供第二个乘数。
功能描述
执行 mul
指令时,处理器将进行以下操作:
- 读取源寄存器:从寄存器
rs1
和rs2
中分别读取两个操作数。 - 执行乘法操作:将这两个操作数相乘。
- 存储结果:将乘积的低 32 位存储到目标寄存器
rd
中。如果处理器是 64 位的,mul
仍然只会存储结果的低 32 位,而不考虑高位部分。
使用场景
mul
指令在需要进行整数乘法计算的多种场景中非常有用,特别是在涉及数学运算、算法实现、图形处理以及任何需要乘法计算的地方。它可以用于:
- 算术运算:直接进行两个整数的乘法运算。
- 算法实现:在执行算法,如矩阵乘法、信号处理等,中频繁使用乘法。
- 资源估算:计算资源需求,如内存分配和性能预测等。
示例
假设寄存器 a1
和 a2
分别存储了两个整数,我们需要计算这两个整数的乘积:
1 |
|
这条指令将计算 a1
和 a2
的乘积,并将结果的低 32 位存储在 a3
中。如果是在 32 位 RISC-V 架构上执行,结果是完整的;在 64 位架构上,如果乘积超过 32 位,则只存储低 32 位结果。
mul
指令是 RISC-V 指令集中处理基本乘法操作的关键工具,提供了高效的方式来执行整数乘法。
div
在 RISC-V 指令集中,div
指令代表 “Divide”,用于执行两个寄存器中的数值的除法操作。这是一个 R-type 指令,它处理有符号整数的除法,并将除法的商存储在目标寄存器中。
指令格式
div
指令的基本格式如下:
1 |
|
- **
rd
**:目标寄存器,用于存储除法操作的结果,即商。 - **
rs1
**:第一个源寄存器,提供被除数。 - **
rs2
**:第二个源寄存器,提供除数。
功能描述
执行 div
指令时,处理器将进行以下操作:
- 读取源寄存器:从寄存器
rs1
和rs2
中分别读取被除数和除数。 - 执行除法操作:
- 如果
rs2
(除数)为零,结果根据具体实现可能定义为最大的负数或特定的异常值。 - 如果除法结果超出寄存器可以表示的范围(例如,最小的负数除以
-1
),结果可能被定义为最大的正数或溢出的值。
- 如果
- 存储结果:将除法的商存储到目标寄存器
rd
中。
使用场景
div
指令在需要进行整数除法计算的场景中非常有用,特别是在涉及数学运算和算法实现中。它可以用于:
- 算术运算:直接进行两个整数的除法运算。
- 算法实现:在执行算法,如统计分析、信号处理等,中频繁使用除法。
- 资源分配:计算资源分配的比例或分配单元。
示例
假设寄存器 a1
和 a2
分别存储了两个整数,我们需要计算这两个整数的除法:
1 |
|
这条指令将计算 a1
(被除数)除以 a2
(除数)的商,并将结果存储在 a3
中。如果 a2
为零,处理器的行为将取决于具体的实现,可能会设置 a3
为某个特定的值或引发异常。
div
指令是 RISC-V 指令集中处理基本除法操作的关键工具,提供了一种有效的方式来执行有符号整数的除法运算。
现在,已经可以通过很多的程序了,上面的指令解析如下:
1 |
|