Lecture 9: RISC-V Decision Making Logic Ops
Decision Making
RV32 So Far…
RV32 迄今为止…
- 加法/减法
add rd, rs1, rs2
执行加法操作,将寄存器rs1
和rs2
的值相加,并将结果存储到目标寄存器rd
中。sub rd, rs1, rs2
执行减法操作,将寄存器rs1
的值减去rs2
的值,并将结果存储到目标寄存器rd
中。
- 加立即数
addi rd, rs1, imm
执行立即数加法操作,将寄存器rs1
的值与立即数imm
相加,并将结果存储到目标寄存器rd
中。
- 加载/存储
lw rd, rs1, imm
从地址R[rs1] + imm
加载一个字(32位)到寄存器rd
中。lw
(Load Word)指令用于从内存中读取数据。lb rd, rs1, imm
从地址R[rs1] + imm
加载一个字节(8位)到寄存器rd
中。lb
(Load Byte)指令用于读取单字节数据。lbu rd, rs1, imm
从地址R[rs1] + imm
加载一个无符号字节(8位)到寄存器rd
中。lbu
(Load Byte Unsigned)指令会将字节值扩展到寄存器中,而不会考虑符号。sw rs1, rs2, imm
将寄存器rs1
的值存储到地址R[rs2] + imm
指向的内存位置。sw
(Store Word)指令用于将数据写入内存。sb rs1, rs2, imm
将寄存器rs1
的一个字节值存储到地址R[rs2] + imm
指向的内存位置。sb
(Store Byte)指令用于存储单字节数据。
Computer Decision Making
计算机决策
-
基于计算结果执行不同的操作
计算机可以通过判断计算结果来决定执行不同的操作,这类似于编程语言中的if
语句。 - 在编程语言中:
if
语句
在大多数编程语言中,if
语句用于根据条件执行特定的代码块。它的基本语法是:if (condition) { // Code to execute if condition is true }
- RISC-V:
if
语句的指令
在RISC-V指令集中,条件分支指令用于根据寄存器的值来改变控制流。例如:beq reg1, reg2, L1
如果寄存器reg1
的值等于reg2
,则跳转到标签L1
指定的位置,执行标记为L1
的指令。- 否则,执行下一条指令。
beq
表示 “branch if equal”(如果相等则分支)
beq
指令用于根据两个寄存器的值是否相等来决定是否跳转到指定标签。
Types of Branches
分支类型
-
分支
分支指令用于改变程序的控制流,使程序能够根据条件跳转到不同的代码位置。 - 条件分支
条件分支指令根据比较结果改变控制流。常见的条件分支指令包括:beq
:如果两个寄存器的值相等,则进行分支。bne
:如果两个寄存器的值不相等,则进行分支。blt
:如果第一个寄存器的值小于第二个寄存器的值,则进行分支。bge
:如果第一个寄存器的值大于或等于第二个寄存器的值,则进行分支。- 无符号版本
bltu
:如果第一个寄存器的值小于第二个寄存器的值(按无符号数比较),则进行分支。bgeu
:如果第一个寄存器的值大于或等于第二个寄存器的值(按无符号数比较),则进行分支。
- 无条件分支
无条件分支指令不依赖于任何条件,始终进行跳转。- RISC-V 指令:
j label
用于跳转到指定的标签label
,不考虑任何条件。
- RISC-V 指令:
Example if Statement
示例 if 语句
假设以下 C 语言代码需要翻译成 RISC-V 指令:
f
->x10
g
->x11
h
->x12
i
->x13
j
->x14
if (i == j)
f = g + h;
对应的 RISC-V 指令:
bne x13, x14, Exit
如果寄存器x13
的值不等于寄存器x14
的值,则跳转到Exit
标签。add x10, x11, x12
如果条件成立,执行加法,将x11
和x12
的值相加,并将结果存储到x10
。
Exit:
标记结束。此处的 Exit
是一个标签,用于指示程序跳转后的继续执行位置。
有时可能需要对分支条件进行取反。此时,可以使用 bne
来处理原本使用 beq
的条件。
Example if-else Statement
if-else 语句示例
假设以下 C 语言代码需要翻译成 RISC-V 指令:
f
->x10
g
->x11
h
->x12
i
->x13
j
->x14
if (i == j)
f = g + h;
else
f = g - h;
对应的 RISC-V 指令:
bne x13, x14, Else
如果寄存器x13
的值不等于寄存器x14
的值,则跳转到Else
标签。add x10, x11, x12
如果条件成立,执行加法,将x11
和x12
的值相加,并将结果存储到x10
。j Exit
跳转到Exit
标签,跳过Else
部分的执行。Else: sub x10, x11, x12
如果条件不成立,执行减法,将x11
的值减去x12
的值,并将结果存储到x10
。Exit:
标记结束,程序继续从这里执行。
Magnitude Compares in RISC-V
RISC-V 中的大小比较
在编程中,测试 <
和 >
操作是非常常见的。RISC-V 提供了多种分支指令来实现这些操作:
- Branch on Less Than (
blt
)- 语法:
blt reg1, reg2, Label
- 含义:如果
reg1 < reg2
,则跳转到Label
- 示例:
blt x5, x6, TargetLabel // 如果 x5 小于 x6,则跳转到 TargetLabel
- 语法:
- Branch on Less Than Unsigned (
bltu
)- 语法:
bltu reg1, reg2, Label
- 含义:如果
reg1 < reg2
(无符号比较),则跳转到Label
- 示例:
bltu x5, x6, TargetLabel // 如果 x5 小于 x6(无符号比较),则跳转到 TargetLabel
- 语法:
此外,还有:
- Branch on Greater or Equal (
bge
)- 语法:
bge reg1, reg2, Label
- 含义:如果
reg1 >= reg2
,则跳转到Label
- 示例:
bge x5, x6, TargetLabel // 如果 x5 大于或等于 x6,则跳转到 TargetLabel
- 语法:
- Branch on Greater or Equal Unsigned (
bgeu
)- 语法:
bgeu reg1, reg2, Label
- 含义:如果
reg1 >= reg2
(无符号比较),则跳转到Label
- 示例:
bgeu x5, x6, TargetLabel // 如果 x5 大于或等于 x6(无符号比较),则跳转到 TargetLabel
- 语法:
注意:RISC-V 中没有 bgt
或 ble
指令,但可以通过取反相应的比较条件来实现这些操作。
Loops in C/Assembly
C/汇编中的循环
C 语言中常见的循环有三种类型:
while
循环- 语法:
while (condition) { /* 循环体 */ }
- 语法:
do ... while
循环- 语法:
do { /* 循环体 */ } while (condition);
- 语法:
for
循环- 语法:
for (initialization; condition; increment) { /* 循环体 */ }
- 语法:
这些循环都可以重写为等效的形式,使用相同的分支指令在 RISC-V 中实现。
关键概念是:在 RISC-V 中,循环的实现依赖于条件分支指令来控制循环的开始和结束。
C Loop Mapped to RISC-V Assembly
将 C 循环映射到 RISC-V 汇编
以下是一个简单的 C 循环及其对应的 RISC-V 汇编实现:
C 代码:
int A[20];
// 填充 A 数据
int sum = 0;
for (int i = 0; i < 20; i++)
sum += A[i];
对应的 RISC-V 汇编指令:
add x9, x8, x0 // x9 = &A[0]
add x10, x0, x0 // sum = 0
add x11, x0, x0 // i = 0
addi x13, x0, 20 // x13 = 20
Loop:
bge x11, x13, Done // 如果 i >= 20,跳转到 Done
lw x12, 0(x9) // x12 = A[i]
add x10, x10, x12 // sum += x12
addi x9, x9, 4 // &A[i+1]
addi x11, x11, 1 // i++
j Loop // 跳转到 Loop
Done:
- 初始化:
add x9, x8, x0
:将数组A
的起始地址存储到寄存器x9
。add x10, x0, x0
:初始化sum
为 0。add x11, x0, x0
:初始化循环计数器i
为 0。addi x13, x0, 20
:将常数 20 存储到寄存器x13
。
- 循环体:
bge x11, x13, Done
:如果i >= 20
,跳转到Done
。lw x12, 0(x9)
:从A[i]
位置加载数据到x12
。add x10, x10, x12
:将A[i]
的值加到sum
中。addi x9, x9, 4
:更新A
的指针,指向下一个元素。addi x11, x11, 1
:递增i
。j Loop
:跳转回循环开始位置。
- 完成:
Done:
标签表示循环结束后的执行位置。
RV32 So Far…
到目前为止的 RV32 指令
- 加法/减法
add rd, rs1, rs2
:将rs1
和rs2
相加,结果存入rd
。sub rd, rs1, rs2
:将rs1
和rs2
相减,结果存入rd
。
- 立即数加法
addi rd, rs1, imm
:将rs1
和立即数imm
相加,结果存入rd
。
- 加载/存储
lw rd, rs1, imm
:从内存地址rs1 + imm
加载字到rd
。lb rd, rs1, imm
:从内存地址rs1 + imm
加载字节到rd
。lbu rd, rs1, imm
:从内存地址rs1 + imm
加载无符号字节到rd
。sw rs1, rs2, imm
:将rs1
存储到内存地址rs2 + imm
。sb rs1, rs2, imm
:将字节rs1
存储到内存地址rs2 + imm
。
- 分支
beq rs1, rs2, Label
:如果rs1 == rs2
,跳转到Label
。bne rs1, rs2, Label
:如果rs1 != rs2
,跳转到Label
。bge rs1, rs2, Label
:如果rs1 >= rs2
,跳转到Label
。blt rs1, rs2, Label
:如果rs1 < rs2
,跳转到Label
。bgeu rs1, rs2, Label
:如果rs1 >= rs2
(无符号),跳转到Label
。bltu rs1, rs2, Label
:如果rs1 < rs2
(无符号),跳转到Label
。j Label
:无条件跳转到Label
。
RISC-V Logical Instructions
RISC-V 逻辑指令
-
逻辑操作适用于对字中的位字段进行操作。这些操作对于处理位级别数据非常有用,如对一个字中的字符(8 位)进行操作。
-
位操作指令
and
:位与操作or
:位或操作xor
:位异或操作sll
:逻辑左移srl
:逻辑右移
逻辑操作 | C 操作符 | Java 操作符 | RISC-V 指令 |
---|---|---|---|
按位与 (AND) | & | & | and |
按位或 (OR) | | | | | or |
按位异或 (XOR) | ^ | ^ | xor |
逻辑左移 | « | « | sll |
逻辑右移 | » | » | srl |
RISC-V Logical Instructions
RISC-V 逻辑指令
RISC-V 提供了多种逻辑指令,用于对位字段进行操作,这些指令包括按位与、按位或、按位异或以及逻辑移位操作。每种指令都有两个变体:寄存器版本和立即数版本。
- 总是有两个变体
- 寄存器版本:
and x5, x6, x7
,表示x5 = x6 & x7
- 立即数版本:
andi x5, x6, 3
,表示x5 = x6 & 3
- 寄存器版本:
- 用于“掩码”操作
andi with 0000 00FF_hex
:隔离最低有效字节andi with FF00 0000_hex
:隔离最高有效字节
这些指令对于处理位级别的数据非常有用,例如,在图像处理、加密算法等领域。
没有逻辑非(NOT)指令
- 没有逻辑 NOT 指令
- 使用
xor
和11111111₂
实现逻辑非 - 例如:
xori x5, x6, -1
,将x6
中的所有位取反,并将结果存储在x5
中
- 使用
x | y | XOR(x, y) |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
在 RISC-V 中,逻辑 NOT 操作可以通过对值进行 XOR 操作并与全 1 进行 XOR 实现。这是由于 RISC-V 追求简洁性,没有直接提供 NOT 指令。
Logical Shifting
逻辑移位
RISC-V 提供了逻辑左移和逻辑右移指令,用于将寄存器中的位向左或向右移动,并在空位中插入零。
-
逻辑左移(sll)和立即数逻辑左移(slli)
slli x11, x12, 2
:将x12
中的值左移 2 位,结果存储到x11
中- 在 C 语言中等效于
<<
- 例如:
- 移位前:
0000 0002ₕ = 0000 0000 0000 0000 0000 0000 0000 0010₂
- 移位后:
0000 0008ₕ = 0000 0000 0000 0000 0000 0000 0000 1000₂
- 移位前:
左移操作在寄存器中将值向左移动,并在右边插入 0。这样的操作在计算机中用于乘法等操作。
-
逻辑右移(srl)
- 逻辑右移的操作与左移相反,在左边插入 0,向右移动位。
Arithmetic Shifting
算术移位
RISC-V 提供了算术右移指令,用于将寄存器中的值向右移动,并在空位中插入符号位,从而保持符号。
-
算术右移(sra, srai)
sra
和srai
将 n 位向右移动,并在空位中插入高位符号位。- 例如,如果寄存器
x10
包含1111 1111 1111 1111 1111 1111 1110 0111₂ = -25₁₀
- 执行
srai x10, x10, 4
后结果是:1111 1111 1111 1111 1111 1111 1111 1110₂ = -2₁₀
算术右移与除以 2 的 n 次方不同,尤其对于奇数负数,这种操作可能不符合期望的结果。在 C 语言中,算术语义要求除法结果向 0 舍入。
Assembler to Machine Code (More Later in Course)
汇编器到机器代码(课程后面会详细讲解)
汇编器的作用是将人类可读的汇编代码转换为指令的位模式,从而生成机器代码目标文件。以下是一个汇编器和链接器的工作流程:
- 汇编源文件(.S 文件)
- 汇编器将其转换为机器代码目标文件(.o 文件)
- 链接器将多个目标文件和预构建的目标文件库(lib.o)链接成一个可执行的机器代码文件(a.out)
How Program is Stored
程序的存储方式
程序在内存中存储为字节:
- 程序代码
- 数据
一个 RISC-V 指令占用 32 位(4 字节)。
Program Execution
程序执行
程序计数器(PC)是处理器内部的一个寄存器,它保存下一个要执行的指令的字节地址。执行过程如下:
- 从内存中获取指令。
- 控制单元使用数据路径和内存系统执行指令,并更新程序计数器。
- 默认情况下,PC 加 4 字节以移动到下一个顺序指令;分支和跳转指令会改变这个默认行为。
Helpful RISC-V Assembler Features
有用的 RISC-V 汇编器功能
- 符号寄存器名称
- 例如,a0-a7 用于函数调用的参数寄存器(x10-x17)。
- 例如,zero 代表 x0 寄存器。
- 伪指令
- 常见汇编惯用语法的简写。
- 例如,
mv rd, rs = addi rd, rs, 0
(移动指令)。 - 例如,
li rd, 13 = addi rd, x0, 13
(加载立即数)。 - 例如,
nop = addi x0, x0, 0
(空操作)。
Example: Translate *x = *y
示例:将 *x = *y 翻译为 RISC-V 指令
我们要将 *x = *y 翻译成 RISC-V 指令,x 和 y 的指针分别存储在 x3 和 x5 寄存器中。以下是逐步指令翻译:
lw x6, 0(x5)
:从 x5 指向的内存地址加载值到 x6。sw x6, 0(x3)
:将 x6 中的值存储到 x3 指向的内存地址。
这些指令将实现从 y 指针指向的内存地址加载值并存储到 x 指针指向的内存地址。
Your Turn. What is in x12?
x12 中的值是多少?
假设 x10 存储了 0x34FF,以下是指令的执行步骤:
slli x12, x10, 0x10
:将 x10 左移 16 位,结果为 0x34FF0000。srli x12, x12, 0x08
:将 x12 右移 8 位,结果为 0x0034FF00。and x12, x12, x10
:将 x12 和 x10 按位与,结果为 0x00003400。
最终,x12 中的值是 0x00003400。