RISC-V指令格式与示例分析

LIONEFAN 2022-05-17 23:59:47

RISC-V 

 


RISC-V 指令集架构(ISA),是一个最新的、简约的、清晰的、开源的指令集架构。RISC-V 指令集架构的目标是让它在最小的到最快的所有计算机设备上都能够有效工作。RISC-V 指令集架构强调简洁性来保证它的低成本,同时有着大量的寄存器和透明的指令执行速度,从而帮助编译器和汇编语言程序员将实际的重要问题转换为适当的高效代码。

计算机指令即:汇编指令代码

计算机指令是能够被计算机识别并执行的二进制代码,它规定了计算机能完成的某种操作;

计算机指令通常由两部分操作:操作码和操作数(地址码)。


操作码 opcode

指令中的操作码:指出该指令需要完成操作的类型或性质。例如,取数、加法、减法、输出等不同的操作具有不同的操作码;指令中操作码的二进制位数决定了该种计算机最多能具有的指令条数(即操作种类);


地址码

指令中的地址码:用来描述该指令的操作对象,或者直接给出操作数,或者指出操作数的存储器地址或寄存器地址(即寄存器名)
根据指令中操作码的性质,操作数又分为源操作数和目的操作数;
例如:在一般的加法指令中又3个操作数,其中加法和被加数为源操作数,计算结果为目的操作数;
在大多数指令中,指令中给出的操作数一般是存放数据的地址,而并不是具体数据本身,甚至在有些指令中实际上给出的只能是地址而不是数据;
每一条指令的地址码个数是不同的


RISC-V 指令类型


计算机指令所占的字节数是各不相同的,只占一个字节的指令称为单字节指令,占两个字节的指令称为双字节指令,一般来说,如果指令中的操作码和操作数(即地址码)共占 n 个字节,则称该指令为 n 字节指令,RISC-V指令集的所有的指令都是32位长度,这简化了指令解码的工作。

ARM-32 和 x86-32 都有许多不同的指令格式,这使得解码部件在硬件中实现较昂贵,在中高端处理器设计中容易带来性能挑战。

RISC-V 指令具有六种基本指令格式:

R 类型指令:用于寄存器 - 寄存器操作;
I 类型指令:用于短立即数和访存 load 操作;
S 类型指令:用于访存 store 操作;
B 类型指令:用于条件跳转操作;
U 类型指令:用于长立即数操作;
J 类型指令:用于无条件操作;

在这里插入图片描述

 

RISC-V 指令提供3个寄存器操作数,而不像 x86-32一样,让源操作数和目的操作数共享一个字段。让一个操作天然就需要有三个不同的操作数,但是指令集架构(ISA)只提供了两个操作数时,编译器或者汇编程序员就需要多使用一条 move 指令来保存目的寄存器的值。

在 RISC-V 中对于所有指令,要读写的寄存器的标识符总是在同一个位置,这不仅对解码部件要求降低,而且也意味着在解码指令之前,就可以先开始访问寄存器。

指令格式中的立即数字段总是符号扩展,符号位总是在指令中最高位,这意味着可能成为关键路径的立即数符号扩展,可以在指令解码之前进行。

 

RISC-V环境搭建

先安装编译环境。

sudo apt install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev git libncurses5-dev

riscv toolchain的地址为https://github.com/riscv-collab/riscv-gnu-toolchain ,使用git将项目clone下来。

git clone https://github.com/riscv-collab/riscv-gnu-toolchain

在当前目录下会出现riscv-gnu-toolchain,进入该文件夹,然后执行configure进行配置。

cd riscv-gnu-toolchain
./configure --prefix=/opt/riscv --with-arch=rv64imafdc

configure的参数说明如下。

  • --prefix=/opt/riscv表示工具将会被安装在/opt/riscv文件夹下。
  • --with-arch=rv64imafdc表示安装的是RISC-V 64的ISA,扩展包为i, m, a, f, d, c

默认的configure生成的gdb配置是不会带tui的,需要手动在Makefile中加上--enable-tui=yes,如下所示。

GDB_TARGET_FLAGS := --with-expat=yes --enable-tui=yes $(GDB_TARGET_FLAGS_EXTRA)

然后执行make编译。

sudo make linux -j$(nproc)

编译完成后,会在/opt/riscv/bin文件夹下出现如下工具。

如果希望使用riscv64-unknown-linux-gnu-gcc来编译C代码,需要加上路径/opt/riscv/bin,例如

/opt/riscv/bin/riscv64-unknown-linux-gnu-gcc main.c -o main.out

~/.bashrc的最后添加如下语句,然后保存退出即可可以在调用gcc时不用加上路径

export PATH=/opt/riscv/bin:/opt/qemu/bin:$PATH

重启终端即可生效,使用如下命令可以测试是否成功加入环境变量。

riscv64-unknown-linux-gnu-gcc --version

上述命令并没有编译qemu,进入riscv-gnu-toolchain中的qemu文件夹,使用如下命令来编译。

sudo apt install ninja-build libglib2.0-dev libpixman-1-dev libcairo2-dev libpango1.0-dev libjpeg8-dev libgif-dev
./configure --target-list=riscv64-linux-user,riscv64-softmmu --prefix=/opt/qemu --enable-debug
sudo make CROSS_COMPILE=riscv64-unknown-linux-gnu- qemu-riscv64_smode_defconfig
sudo make CROSS_COMPILE=riscv64-unknown-linux-gnu- -j$(nproc)

/opt/qemu/bin加入环境变量后,使用如下命令可以检查qemu是否安装成功。

qemu-system-riscv64 --version

编写代码并编译

#include <stdio.h>

int g(int x)
{
    return x + 3;
}

int f(int x)
{
    return g(x);
}

int main()
{
    return f(8) + 1;
}

编译成汇编代码,使用命令:

riscv64-unknown-linux-gnu-gcc -S riscv-test.c -o riscv-test.s

最后的编译结果,并进行分析如下:

	.file	"riscv-test.c"
	.option nopic		
	.attribute arch, "rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0"
	.attribute unaligned_access, 0	
	.attribute stack_align, 16	
	.text				
	.align	1			
	.globl	g			
	.type	g, @function 
g:
	addi	sp,sp,-32	# 修改栈指针sp=sp-32,push
	sd	s0,24(sp)		# 把s0保存到sp+24,s0表示帧指针fp
	addi	s0,sp,32	# 栈底保存到s0,s0=sp+32
	mv	a5,a0			# 将a0存放的参数值存入a5
	sw	a5,-20(s0)		# 将a5中的字保存到s0-20
	lw	a5,-20(s0)		# 将s0-20的字存入a5
	addiw	a5,a5,3		# a5=a5+3
	sext.w	a5,a5		
	mv	a0,a5			# 将a5的值返回a0
	ld	s0,24(sp)		# 恢复s0
	addi	sp,sp,32	# 恢复sp
	jr	ra				# 跳转到ra地址返回
	.size	g, .-g		
	.align	1			
	.globl	f			
	.type	f, @function 
f:
	addi	sp,sp,-32	# 修改栈指针sp=sp-32,push
	sd	ra,24(sp)		# 将ra值保存到sp+24
	sd	s0,16(sp)		# 将s0值保存到sp+16
	addi	s0,sp,32	# 栈底保存到s0,s0=sp+32
	mv	a5,a0			# 将a0存放的参数值存入a5
	sw	a5,-20(s0)		# 将a5中的字保存到s0-20
	lw	a5,-20(s0)		# 将s0-20的字存入a5
	mv	a0,a5			# 将a5的值返回a0
	call	g			# 调用函数g
	mv	a5,a0			# 将返回来的值a0移入a5中
	mv	a0,a5			# 将a5中的值移入返回值a0中
	ld	ra,24(sp)		# 恢复ra
	ld	s0,16(sp)		# 恢复s0
	addi	sp,sp,32	# 恢复sp
	jr	ra				# 跳转到ra地址返回
	.size	f, .-f		
	.align	1			# 对齐
	.globl	main		# 声明main为全局符号
	.type	main, @function	 # 将main定义为一个函数
main:
	addi	sp,sp,-16	# 修改栈指针sp=sp-16
	sd	ra,8(sp)		# 将ra值保存到sp+8所指向的位置
	sd	s0,0(sp)		# 将s0值保存到sp所指向的位置
	addi	s0,sp,16	# 栈底保存到s0,s0=sp+16
	li	a0,8			# a0 = 8
	call	f			# 调用函数f
	mv	a5,a0			# 将a0移入a5中
	addiw	a5,a5,1		# a5=a5+1
	sext.w	a5,a5		
	mv	a0,a5			# a0 = a5
	ld	ra,8(sp)		# 恢复ra
	ld	s0,0(sp)		# 恢复s0
	addi	sp,sp,16	# 恢复sp
	jr	ra				# 跳转到ra地址返回
	.size	main, .-main 	.ident	"GCC: () 10.2.0"	

 

作者285

参考文献:

RISC-V 指令格式_ViatorSun的博客-CSDN博客_riscv指令格式

RISC-V交叉编译工具链安装指南 - Nelson's Blog (nelson-cheung.cn)

分析C代码编译成RISC-V汇编后如何执行_phantom_of_shadow的博客-CSDN博客

...全文
812 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

571

社区成员

发帖
与我相关
我的任务
社区描述
软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitee.com/mengning997/se
软件工程 高校
社区管理员
  • 码农孟宁
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧