RISC-V学习笔记(2)

RISC-V学习笔记(2)

作者:夏风喃喃
参考:计算机组成与设计:硬件/软件接口(RISC-V版)

文章目录

第2章 计算机的语言

2.1 引言

RISC-V学习笔记(2)
RISC-V学习笔记(2)

名称 寄存器号 用途 调用时是否保存
x0 0 常数0 不适用
x1(ra) 1 返回赋值(链接寄存器)
x2(sp) 2 栈指针
x3(gp) 3 全局指针
x4(tp) 4 线程指针
x5~x7 5~7 临时
x8~x9 8~9 保存
x10~x17 10~17 参数/结果
x18~x27 18~27 保存
x28~x31 28~31 临时

2.2 计算机硬件的操作

RISC-V一个算术指令只执行一个操作,并且必须总是只有三个变量。

类似于加法的操作一般有三个操作数:两个被加到一起的数和一个放置总和的位置。

add	a, b, c				//The sum of b and c is placed in a

2.3 计算机硬件的操作数(加减指令)

RISC-V算术指令的三个操作数必须从32个64位寄存器(RV64)选择,RISC-V约定寄存器编号为x01-x31。
加法

add	x19, x20, x21				//The sum of x20 and x21 is placed in x19

2.3.1 存储器操作数(存取指令)

内存和寄存器之间传输数据的指令,称为数据传输指令

取双字:(取内存中的A[8]至寄存器x9,字节寻址8×8偏移地址为64)

ld	x9, 64(x22)	//x22存放内存中数组的基址,偏移64,取出的数据A[8]存放至寄存器x9

存双字:(将寄存器x9中数据存至内存的A[12],字节寻址8×12偏移地址为96)

sd	x9, 96(x22) 	//x22存放内存中数组的基址,偏移96,寄存器x9的数据存至A[12]

2.3.2 常数或立即数操作数(立即数指令)

RISC-V算术指令有一个常数操作数的快速加指令称为立即数加
立即数加

addi	x22, x22 4				//x22 = x22 + 4

2.4 有符号数与无符号数

64位无符号数:
( x 63 × 2 63 ) + ( x 62 × 2 62 ) + … … + ( x 1 × 2 1 ) + ( x 0 × 2 0 ) (x_{63}×2^{63})+(x_{62}×2^{62})+……+(x_{1}×2^{1})+(x_{0}×2^{0}) (x63​×263)+(x62​×262)+……+(x1​×21)+(x0​×20)
64位有符号数:
( x 63 × − 2 63 ) + ( x 62 × 2 62 ) + … … + ( x 1 × 2 1 ) + ( x 0 × 2 0 ) (x_{63}×-2^{63})+(x_{62}×2^{62})+……+(x_{1}×2^{1})+(x_{0}×2^{0}) (x63​×−263)+(x62​×262)+……+(x1​×21)+(x0​×20)

2.5 计算机中的指令表示(指令格式)

RISC-V的指令均为32位。

R(寄存器)型格式指令:(用于三个操作数均在寄存器)

funct7 rs2 rs1 funct3 rd opcode
7位 5位 5位 3位 5位 7位
opcode、funct3、funct7:操作码,指令的基本操作。
rd:目的操作数寄存器,用来存放操作结果。
rs1:第一个源操作数寄存器。
rs2:第二个源操作数寄存器。

I(立即数)型格式指令:(用于立即数操作或从内存取数据)

immediate rs1 funct3 rd opcode
12位 5位 3位 5位 7位
opcode、funct3:操作码,指令的基本操作。
rd:目的操作数寄存器,用来存放操作结果。
rs1:源操作数寄存器或基址寄存器。
immediate:立即数源操作数或偏移量。

S(存放)型格式指令:(用于向内存存数据)

immediate[11:5] rs2 rs1 funct3 immediate[4:0] opcode
7位 5位 5位 3位 5位 7位
opcode、funct3:操作码,指令的基本操作。
rs1:基址寄存器。
rs2:源操作数寄存器。
immediate:偏移量。

2.6 逻辑操作(移位,逻辑运算指令)

RISC-V的移位操作通常使用 I 型格式指令,因为64位寄存器数据移位不会大于63位,所以immediate的低6位被使用。

funct6 immediate rs1 funct3 rd opcode
6位 6位 5位 3位 5位 7位

逻辑左移与右移:(移动的空位用0补充)

sll x11, x19, x10				//reg x11 = reg x19 << reg x10
slli x11, x19, 4				//reg x11 = reg x19 << 4 bits
srl x11, x19, x10				//reg x11 = reg x19 >> reg x10
srli x11, x19, 4				//reg x11 = reg x19 >> 4 bits

算术右移:(移动的空位用符号扩展补充)

sra x11, x19, x10				//reg x11 = reg x19 >> reg x10
srai x11, x19, 4				//reg x11 = reg x19 >> 4 bits

按位与,按位或

and x9, x10, x11				//reg x9 = reg x10 & reg x11
andi x9, x10, 4 				//reg x9 = reg x10 & 4
or x9, x10, x11					//reg x9 = reg x10 | reg x11
ori x9, x10, 4					//reg x9 = reg x10 | 4

按位异或,按位取反:(取反等价于异或111……111)

xor x9, x10, x12				//reg x9 = reg x10 ^ reg x12
xori x9, x10, 4					//reg x9 = reg x10 ^ 4
xor x9, x10, x11				//reg x9 = ~ reg x10,x11 = 111……111

2.7 用于决策的指令(条件判断分支指令)

分支跳转语句常用于 if 语句。

相等则分支

beq rs1, rs2, L1		//if rs1 == rs2, branch to label L1

不相等则分支

bne rs1, rs2, L1		//if rs1 ≠ rs2, branch to label L1

例:( if 条件语句C语言编译为RISC-V代码)

【C】
if (i == j) f = g + h; else f = g - h;
f,g,h,i,j分别对应x19-x23这5个寄存器

【RISC-V】
bne x22, x23, Else		//go to Else if i ≠ j
add x19, x20, x21 		//f = g + h (skipped if i ≠ j)
beq x0, x0, Exit		//if 0 == 0, go to Exit
Else: sub x19, x20, x21 //f = g - h (skipped if i = j)
Exit:

2.7.1 循环(循环判断分支指令)

循环的RISC-V代码也是通过分支跳转实现。

例:( while 循环语句C语言编译为RISC-V代码)

【C】
while (save[i] == k) 
	i += 1;
i,k分别对应x22和x24寄存器,数组的基址保存在x25

【RISC-V】
Loop: slli x10, x22, 3	//Temp reg x10 = i * 8
add x10, x10, x25		//x10 = address of save[i]
ld x9, 0(x10)			//Temp reg x9 = save[i]
bne x9, x24, Exit		//go to Exit if save[i] ≠ k
addi x22, x22, 1		//i = i + 1
beq x0, x0, Loop		//go to Loop
Exit: 

2.7.2 边界检查的简便方法(其他条件判断分支指令)

小于则分支

blt rs1, rs2, L1		//if rs1 < rs2, branch to label L1

大于等于则分支

bge rs1, rs2, L1		//if rs1 ≥ rs2, branch to label L1

无符号小于则分支

bltu rs1, rs2, L1		//if rs1 < rs2, branch to label L1

无符号大于等于则分支

bgeu rs1, rs2, L1		//if rs1 ≥ rs2, branch to label L1

2.7.3 case/switch语句(条件判断跳转指令)

case条件语句可以使用分支地址表高效实现,使用跳转-链接指令(jalr)对寄存器中指定的地址执行无条件跳转。

间接跳转

jalr x0, 0(x1) 	//unconditionally branch to case address

分支地址表:也称作分支表,一种包含了不同指令序列地址的表。

2.8 计算机硬件对过程的支持(函数操作指令)

过程即函数的实现,RISC-V软件为过程调用分配寄存器时遵循约定:x10-x17八个参数寄存器,用于传递参数或返回值,x1一个返回地址寄存器,用于返回到起始点。

跳转-链接

jal x1, ProcedureAddress
//jump to ProcedureAddress and write return address to x1

无条件跳转

jal x0, Label	//unconditionally branch to Label, discard return address

2.8.1 使用更多的寄存器(函数操作寄存器换出指令)

当函数需要更多的参数时,需要更多的寄存器,将寄存器旧值换出至存储器以满足参数传递,函数调用结束后,需要恢复寄存器旧值,换出寄存器的数据结构是栈(stack),栈按照从高到低的地址顺序(高地址在上,低地址在下)增长,栈指针(stack pointer)是寄存器x2,也称为sp。

例:( 没有调用其他函数的C语言函数编译为RISC-V代码)

【C】
long long int leaf_example(long long int g, long long int h, 
long long int i, long long int j)
{
	long long int f;
	f = (g + h) - (i + j);
	return f;
}
g,h,i,j分别对应x10-x13寄存器,f对应于x20

【RISC-V】
leaf_example:
addi sp, sp, -24		//adjust stack to make room for 3 times
sd x5, 16(sp)			//save reg x5 for use afterwards
sd x6, 8(sp)			//save reg x6 for use afterwards
sd x20, 0(sp)			//save reg x20 for use afterwards
add x5, x10, x11		//reg x5 contains g + h
add x6, x12, x13		//reg x6 contains i + j
sub x20, x5, x6			//f = x5 - x6, which is (g + h) - (i + j)
addi x10, x20, 0		//returns f (x10 = x20 + 0)
ld x20, 0(sp)			//restore reg x20 for caller
ld x6, 8(sp)			//restore reg x6 for caller
ld x5, 16(sp)			//restore reg x5 for caller
addi sp, sp, 24			//adjust stack to delete 3 times
jalr x0, 0(x1)			//branch back to calling routine

RISC-V软件将19个寄存器分为两组:x5-x7以及x28-x31是临时寄存器,在过程调用中不被被调用者保存;x8-x9以及x18-x27是保存寄存器,在过程调用中必须被保存。

2.8.2 嵌套过程(函数操作递归指令)

例:( 计算阶乘的C语言递归函数编译为RISC-V代码)

【C】
long long int fact (long long int n)
{
	if (n < 1) return (1);
		else return (n * fact (n - 1))
}
n对应x10参数寄存器

【RISC-V】
fact:
addi sp, sp, -16		//adjust stack to make room for 2 times
sd x1, 8(sp)			//save the return address
sd x10, 0(sp)			//save the argument n
addi x5, x10, -1		//x5 = n - 1
bge x5, x0, L1			//if (n - 1) >= 0, go to L1
addi x10, x0, 1			//return 1
addi sp, sp, 16			//pop 2 items off stack
jalr x0, 0(x1)			//return to caller
L1:
addi x10, x10, -1		//n >= 1: argument gets (n - 1)
jal x1, fact			//call fact with (n - 1)
addi x6, x10, 0			//return from jal: move result of fact 
						  (n - 1) to x6
ld x10, 0(sp)			//restore argument n
ld x1, 8(sp)			//restore the return address
addi sp, sp, 16 		//adjust stack pointer to pop 2 items
mul x10, x10, x6		//return n * fact (n - 1)
jalr x0, 0(x1)			//return to the caller

为了简化C语言中的静态全局变量的访问,RISC-V编译器保留了一个寄存器x3用作全局指针或gp。

例:( 计算累加的C语言递归函数编译为RISC-V代码)

【C】
long long int sum (long long int n, long long int acc)
{
	if (n > 0) 
		return sum (n - 1, acc + n);
	else 
		return acc
}
n,acc对应x10,x11寄存器, 结果放入x12

【RISC-V】
sum:
ble x10, x0, sum_exit		//go to sum_exit if n <= 0
add x11, x11, x10			//add n to acc
addi x10, x10, -1			//subtract 1 from n
jal x0, sum					//jump to sum
sum_exit:
addi x12, x11, 0			//return value acc
jalr x0, 0(x1)				//return to caller

2.9 人机交互(字符串操作指令)

例:( 字符串复制的C语言编译为RISC-V代码)

【C】
void strcpy (char x[], char y[])
{
	size_t i;
	i = 0;
	while ((x[i] = y[i] != '\0') /*copy & test byte*/ ) 
		i += 1;
}
x,y基址对应x10,x11寄存器, i对应x19寄存器

【RISC-V】
strcpy:
addi sp, sp, -8			//adjust stack for 1 more item
sd x19, 0(sp)			//save x19
add x19, x0, x0			//i = 0 + 0
L1:
add x5, x19, x11		//address of x[i] in x5
lbu x6, 0(x5)			//x[6] = y[i]
add x7, x19, x10		//address of x[i] in x7
sb x6, 0(x7)			//x[i] = y[i]
beq x6, x0, L2
addi x19, x19, 1		//i = i + 1
jal x0, L1				//go to L1
L2:
ld x19, 0(sp)			//restore old x19
addi sp, sp, 8			//pop 1 doubleword off stack
jalr x0, 0(x1)			//return

加载无符号字节

lbu x12, 0(x10)	//Read byte from source

加载无符号字

lwu x12, 0(x10)	//Read byte from source

2.10 对大立即数的RISC-V编址和寻址

2.10.1 大立即数(大立即数加载指令)

将64位常量加载到寄存器,需要使用lui(load upper immediate)指令,用于将20位常数加载到寄存器的第31位到第12位。将31位的值复制填充到最左边32位,最右边的12位用0填充。lui使用U型格式指令。

例:( 64位常量加载到寄存器x19编译为RISC-V代码)

00000000 00000000 00000000 00000000 00000000 00111101 00000101 00000000

【RISC-V】
lui x19, 976			//976(decimal) = 0000 0000 0011 1101 0000
addi x19, x19, 1280		//1280(decimal) = 00000101 00000000

2.10.2 分支中的寻址

RISC-V分支指令格式为SB型。

SB(分支)型格式指令:(如bne x10, x11, 2000

imm[12] imm[10:5] rs2 rs1 funct3 imm[4:1] imm[11] opcode
1位 6位 5位 5位 3位 4位 1位 7位

无条件跳转-链接指令(jal)是唯一使用UJ型格式的指令。

UJ(无条件跳转)型格式指令:(如jal x0, 2000 )

imm[20] imm[10:1] imm[11] imm[19:12] rd opcode
1位 10位 1位 8位 5位 7位

分支中的寻址使用PC相对寻址,它的地址是PC和指令中的常量之和。

2.10.3 RISC-V寻址模式总结

RISC-V有4种寻址模式:

  1. 立即数寻址:操作数是指令本身的常量。
  2. 寄存器寻址:操作数在寄存器中。
  3. 基址寻址:操作数于内存中,其地址是寄存器和指令中的常量之和。
  4. PC相对寻址:分支地址是PC和指令中常量之和。

2.10.4 机器语言译码

从机器语言到汇编语言的译码需要编码表和指令格式表来查找。

RISC-V指令的编码:

格式 指令 操作码 funct3 funct6/7
R add 0110011 000 0000000
R sub 0110011 000 0100000
R sll 0110011 001 0000000
R xor 0110011 100 0000000
R srl 0110011 101 0000000
R sra 0110011 101 0000000
R or 0110011 110 0000000
R and 0110011 111 0000000
R lr.d 0110011 011 0001000
R sc.d 0110011 011 0001100
I lb 0000011 000 n.a.
I lh 0000011 001 n.a.
I lw 0000011 010 n.a.
I ld 0000011 011 n.a.
I lbu 0000011 100 n.a.
I lhu 0000011 101 n.a.
I lwu 0000011 110 n.a.
I addi 0010011 000 n.a.
I slli 0010011 001 000000
I xori 0010011 100 n.a.
I srli 0010011 101 000000
I srai 0010011 101 010000
I ori 0010011 110 n.a.
I andi 0010011 111 n.a.
I jalr 1100111 000 n.a.
S sb 0100011 000 n.a.
S sh 0100011 001 n.a.
S sw 0100011 010 n.a.
S sd 0100011 111 n.a.
SB beq 1100111 000 n.a.
SB bne 1100111 001 n.a.
SB blt 1100111 100 n.a.
SB bge 1100111 101 n.a.
SB bltu 1100111 110 n.a.
SB bgeu 1100111 111 n.a.
U lui 0110111 n.a. n.a.
UJ 1101111 n.a. n.a.

RISC-V指令格式:

名称
(字段大小)
字段 备注
7位 5位 5位 3位 5位 7位
R型 funct7 rs2 rs1 funct3 rd opcode 算术指令格式
I型 immediate[11:0] rs1 funct3 rd opcode 加载&立即数算术
S型 immediate[11:5] rs2 rs1 funct3 immediate[4:0] opcode 存储
SB型 immediate[12,10:5] rs2 rs1 funct3 immediate[4:1,11] opcode 条件分支格式
UJ型 immediate[20,10:1,11,19:12] rd opcode 无条件跳转
U型 immediate[31:12] rd opcode 大立即数格式

2.11 指令与并行性:同步

当来自两个线程的访存请求出现数据竞争时,通常需要以原子交换原语形式构建基本同步原语。它是构建同步机制的典型操作,它将寄存器中的值与存储器中的值进行交换。

假设构建一个简单的锁变量,其中值0表示锁变量可用,值1表示锁变量被占用。处理器尝试通过将寄存器中的1与该锁变量对应的内存地址中的值进行交换来加锁。如果其他处理器已访问该锁变量,则交换指令返回值1,表明该锁已被占用,否则为0,表示加锁成功,且锁变量被交换为1,以防止其他处理器也加锁成功。

在RISC-V中,使用指令对保留加载(load-reserved)双字(lr.d)和条件存储(store-conditional)双字(sc.d)实现原子交换。

例:(在寄存器x20中指定的内存位置上实现原子交换编译为RISC-V代码)

【RISC-V】
addi x12, x0, 1			//copy locked value
again:			
lr.d x10, (x20)			//load-reserved to read lock
bne x10, x0, again		//check if it is 0 yet
sc.d x11, x12, (x20)	//attempt to store new value
bne x11, x0, again		//branch if store fails
sd x0, 0(x20)			//free lock by writting 0

2.12 翻译并启动程序

C程序转换为计算机上可运行程序需要四个步骤:

编译器 汇编器 链接器 链接器 加载器 C程序 汇编语言程序 目标模块-机器语言模块 可执行代码-机器语言程序 目标库-库例程机器语言 存储器

文件名后缀:

文件 UNIX MS-DOS
C源文件 x.c .C
汇编文件 x.s .ASM
目标文件 x.o .OBJ
静态链接库 x.a .LIB
动态链接库 x.so .DLL
可执行文件 a.out .EXE

2.12.1 编译器

编译器将C程序转换为机器能理解的符号形式——汇编语言程序(assembly language program)。

2.12.2 汇编器

汇编器将汇编语言转换为目标文件,该目标文件是机器指令、数据和将指令正确放入内存所需信息的组合。

汇编器还可以处理机器指令的常见变体,这类指令称为伪指令。如li x9, 123 //load immediate value 123 into register x9汇编器将其转化为addi x9, x0, 123mv x10, x11 //reg x10 gets reg x11汇编器将其转化为addi x9, x10, 0and x9, x10, 15汇编器将其转化为andi x9, x10, 15

2.12.3 链接器

链接器将所有独立汇编的机器语言程序“缝合”在一起。由于独立编译和汇编每个过程,因此更改一行代码只需要编译和汇编一个过程,链接器有用的原因是修正代码要比重新编译和重新汇编快得多。

2.12.4 加载器

将可执行程序放在主存中以准备执行的系统程序。

2.12.5 动态链接库

动态链接库(DLL)是在执行期间链接到程序的库例程。DLL需要额外的空间来存储动态链接所需的信息,但不要求复制或链接整个库。它们在第一次调用历程时会付出大量的开销,但此后只需一个间接跳转。

2.13 以C排序程序为例的汇总整理

2.13.1 swap过程

例:( swap过程的C语言编译为RISC-V代码)

【C】
void swap (long long int v[], size_t k)
{
	long long int temp;
	temp = v[k];
	v[k] = v[k+1];
	v[k+1] = temp;
}
v,k对应x10,x11寄存器
	
【RISC-V】
swap:
slli x6, x11, 3			//reg x6 = k * 8
add x6, x10, x6			//reg x6 = v + (k * 8)
ld x5, 0(x6)			//reg x5 (temp) = v[k]
ld x7, 8(x6)			//reg x7 = v[k + 1]
sd x7, 0(x6)			//v[k] = reg x7
sd x5, 8(x6)			//v[k + 1] = reg x5 (temp)
jalr x0, 0(x1)			//return to calling routine

2.13.2 sort过程

例:( 冒泡sort过程的C语言编译为RISC-V代码)

【C】
void sort (long long int v[], size_t int k)
{
	size_t i,j;
	for (i = 0; i < n; i += 1){
		for (j = 1 - 1; j >= 0 && v[j] > v[j + 1]; j -= 1){
			swap(v,j);
		}
	}
}
v,n对应x10,x11寄存器,i,j对应x19,x20寄存器
	
【RISC-V】
*******************保存寄存器************************
sort:
addi sp, sp, -40		//make room on stack for 5 registers
sd x1, 32(sp)			//save return address on stack
sd x22, 24(sp)			//save x22 on stack
sd x21, 16(sp)			//save x21 on stack
sd x20, 8(sp)			//save x20 on stack
sd x19, 0(sp)			//save x19 on stack
*********************过程体**************************
//移动参数
mv x21, x10				//copy parameter x10 into x21
mv x22, x11				//copy parameter x10 into x22
//外循环
li x19, 0				//i = 0
for1tst:
bge x19, x22, exit1 	//go to exit1 if i >= n
//内循环
addi x20, x19, -1		//j = i - 1
for2tst:
blt x20, x0, exit2		//go to exit2 if j < 0
slli x5, x20, 3			//x5 = j * 8
add x5, x21, 3			//x5 = v + (j * 8)
ld x6, 0(x5)			//x6 = v[j]
ld x7, 8(x5)			//x7 = v[j + 1]
ble x6, x7, exit2		//go to exit2 if x6 < x7
//参数传递和调用
mv x10, x21				//first swap parameter is v
mv x11, x20				//second swap parameter is j
jal x1, swap			//call swap
//内循环
addi x20, x20, -1		//j = j - 1
j for2tst				//go to for2tst
//外循环
exit2:
addi x19, x19, 1		//i += 1
j for1tst				//go to for1tst
*******************恢复寄存器********************
exit1:
ld x19, 0(sp)			//restore x19 from stack
ld x20, 8(sp)			//restore x20 from stack
ld x21, 16(sp)			//restore x21 from stack
ld x22, 24(sp)			//restore x22 from stack
ld x1, 32(sp)			//restore return address from stack
addi sp, sp, 40			//restore stack pointer
********************过程返回*********************
jalr x0, 0(x1)			//return to calling routine

2.14 数组与指针

例:( 清除内存中一个双字序列的C语言编译为RISC-V代码)

【C】
//数组实现
clear1 (long long int array[], size_t int size)
{
	size_t i;
	for (i = 0; i < size; i += 1)
		array[i] = 0;
}
//指针实现
clear2 (long long int *array, size_t int size)
{
	long long int *p;
	for (p = &array[0]; p < &array[size]; p = p + 1)
		*p = 0;
}

2.14.1 用数组实现clear

【RISC-V】
li x5, 0				//i = 0
loop1:
slli x6, x5, 3			//x6 = i * 8
add x7, x10, x6			//x7 = address of array[i]
sd x0, 0(x7)			//array[i] = 0
addi x5, x5, 1			//i = i + 1
blt x5, x11, loop1		//if (i < size) go to loop1

2.14.2 用指针实现clear

【RISC-V】
mv x5, x10				//p = address of array[0]
slli x6, x11, 3			//x6 = size * 8
add x7, x10, x6			//x7 = address of array[size]
loop2:
sd x0, 0(x5)			//memory[p] = 0
addi x5, x5, 8			//p = p + 8
bltu x5, x7, loop2		//if (p < &array[size]) go to loop2

2.18 实例:RISC-V指令系统的剩余部分

RISC-V指令系统体系结构分为基本ISA(称作I)以及五个标准扩展:M、A、F、D、C。

助记符 描述 指令数
I 基本体系结构 51
M 整数乘法/除法 13
A 原子操作 22
F 单精度浮点 30
D 双精度浮点 32
C 压缩指令 36

RISC-V的基本体系结构及其扩展共有184条指令及13条系统指令。

上一篇:Spring Boot 学习(一) Condition


下一篇:sqlserver的备份和恢复 命令非计划任务