RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记

RISC-V入门


[完结] 循序渐进,学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春


RISC-V 部分作业答案


参考

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第1张图片

RISC-V ISA 基本介绍

历史简介

自由(Free)与开放(Open)

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第2张图片

  • RISC-V 念作 “risk-five”,代表着Berkeley所研发的第五代精简指令集。

  • 该项目2010年始于加州大学伯克利(Berkeley)分校,希望选择一款 ISA 用于科研与教学。经过前期多年的的研究和选型,最终决定放弃使用现成的 X86 和 ARM 等 ISA,而是自己从头研发一款:

    • X86:太复杂,IP问题。
    • ARM:一样的复杂,而且在2010年之前还不支持64位,以及同样的IP问题。
  • 主要研发人员

    • Andrew Waterman,Yunsup Lee,David Patterson,Krste Asanovic

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第3张图片

RISC-V 究竟是什么

  • 一款高质量,免许可证,开放的RISC ISA。

  • 一套由非盈利的RISC-V基金会维护的标准:https://riscv.org/ 。

  • 适用于所有类型的计算机系统:从微控制器到超级计算机。

  • RISC-V不是一家公司,也不是一款 CPU 实现。

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第4张图片

发展现状

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第5张图片

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第6张图片

特点

  • 简单
  • 清晰的分层设计
  • 模块化
  • 稳定
  • 社区化

RISC-V ISA规范一览

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第7张图片

RISC-V ISA

ISA 命名格式:RV [###] [abc…xyz]

  • RV:用于标识 RISC-V体系架构的前缀,既 RISC-V 的缩写。
  • [###] :{32, 64, 128} 用于标识处理器的字宽,也就是处理器的寄存器的宽度(单位为bit)。
  • [abc…xyz] :标识该处理器支持的指令集模块集合。
  • 例子:RV32IMA,RV64GC

模块化的 ISA

  • 增量式 ISA :计算机体系结构的传统方法,同一个体系架构下的新一代处理器不仅实现了新的 ISA 扩展,还必须实现过去的所有扩展,目的是为了保持向后的二进制兼容性。典型的,以 80X86 为代表。

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第8张图片

  • 模块化 ISA:由 1 个基本整数指令集 + 多个可选的扩展指令集组成。基础指令集是固定的,永远不会改变。

RISC ISA = 1个基本整数指令集 + 多个可选的扩展指令集

  • 基本整数(Integer)指令集

    • 唯一强制要求实现的基础指令集,其他指令集都是可选的扩展模块。

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第9张图片

  • 扩展模块指令集:

    • RISC-V 允许在实现中以可选的形式实现其他标准化和非标准化的指令集扩展。
    • 特定组合“IMAFD”被称为“通用(General)”组合,用英文字母G表示。

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第10张图片

  • 例子:

    • RV32I:最基本的 RISC-V 实现。
    • RV32IMAC:32位实现,支持 Integer + Multiply + Atomic + Compressed 。
    • RV64GC:64位实现,支持 IMAFDC 。

通用寄存器(General Purpose Register)

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第11张图片

  • RISC-V 的 Unprivileged Specification 定义了32个通用寄存器,以及一个PC
    • 对 RV32I/RV64I/RV128I 都一样。
    • 如果实现支持 F/D 扩展则需要额外支持32个 浮点(Float Point)寄存器。
    • RV32E 将32个通用寄存器缩减为16个。
  • 寄存器的宽度由 ISA 指定
    • RV32的寄存器宽度为 32 位,RV64的寄存器宽度为64位,依次类推。
  • 每个寄存器具体编程时有特定的用途以及各自的别名。由RISC-V Application Binary Interface(ABI)定义。

Hart

  • HART = HARdware Thread

    • 一个处理器有多个执行流(硬件线程),跟多核的效果有点像。

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第12张图片

特权级别(Privileged Level)

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第13张图片

  • RISC-V 的 Privileged Specification 定义了三个特权级别(privilege level)

  • Machine 级别是最高的级别,所有的实现都需要支持。

  • 可选的 Debug 级别(用于调试CPU)

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第14张图片

Control and Status Registers(CSR)

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第15张图片

  • 不同的特权级别下时分别对应各自的一套 Register(CSR),用于控制(Control)和获取相应 Level 下的处理器工作状态。
  • 高级别的特权级别下可以访问低级别的 CSR ,譬如Machine Level 下可以访问 Supervisor/User Level 的CSR,以此类推,但反之不可以。
  • RISC-V 定义了专门用于操作 CSR 的指令(【参考1】中定义的 “Zicsr” 扩展)。
  • RISC-V 定义了特定的指令可以用于在不同特权级别之间进行切换(【参考1】中定义的 ECALL/EBREAK)。

内存管理与保护

  • 物理内存保护(Physical Memory Protection,PMP)

    • 允许M模式指定U模式可以访问的内存地址。
    • 支持 R/W/X,以及 Lock。

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第16张图片

  • 虚拟内存(Virtual Memory)

    • 需要支持 Supervisor Level
    • 用于实现高级的操作系统特性(Unix/Linux)
    • 多种映射方式 Sv32/Sv39/Sv48

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第17张图片

异常和中断

  • 异常(Exception):“an unusal condition occurring at run time associated with an instruction in the current RISC-V hart”,执行完异常后,回到出现异常的那条指令,再次运行那条指令,然后继续往下运行。
  • 中断(Interrupe):“an external asynchronous event that may cause a RISC-V hart to experience an unexpected transfer of control”,执行完中断后,回到中断出现的后一条语句,继续往下执行。

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第18张图片

编译与链接

GCC 介绍

简介

GCC(GNU Compiler Collection)

  • https://gcc.gnu.org/
  • 由 GNU 开发的,遵循 GPL 许可证发行的编译器套件。
  • 支持 C/C++、Objective-C、Fortran、Ada 和 Go 语言等多种语言前端,已被移植到多种计算机体系架构上,如 x86、ARM、RISC-V 等。
  • GCC的初衷是为 GNU 操作系统专门编写一款编译器,现以被大多数“Unix-like”操作系统(如Linux、BSD、MacOS等)采纳为标准编译器。

命令格式

gcc [options] [filenames]

常用选项 含义
-E 只做预处理
-c 只编译不链接,生成目标文件“.o”
-S 生成汇编代码
-o file 把输出生成到由 file 指定文件名的文件中
-g 在输出的文件中加入支持调试的信息
-v 显示输出详细的命令执行过程信息

主要执行步骤

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第19张图片

  • 编译(cc1,这里针对 C 语言,不同的语言由自己的编译器):编译器完成“预处理” 和 “编译”。
    • “预处理”指处理源文件中以“#”开头的预处理指令,譬如 #include、#define等;
    • “编译” 则针对预处理的结果进行一系列的词法分析、语法分析、语义分析、优化后生成汇编指令,存放在 .o 为后缀的目标文件中。
  • 汇编(as):汇编器将汇编语言代码转换为机器(CPU)可以执行的指令。
  • 链接(ld):链接器将汇编器生成的目标文件和一些标准库(譬如 libc)文件组合,形成最终可执行的应用程序。

涉及的文件类型

  • .c:C源文件
  • .cc/.cxx/.cpp:C++ 源文件
  • .s/.S:汇编语言源文件,大S包含预处理程序,小s不包含预处理的汇编
  • .h:头(header)文件
  • .o:目标(object)文件
  • .a/.so:编译后的静态库(archive)文件和共享库(shared object)文件
  • a.out:可执行文件

针对多个源文件的处理

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第20张图片

ELF 介绍

简介

  • ELF(Executable Linkble Format)是一种Unix-like系统上的二进制文件格式标准。

  • ELF标准中定义的采用 ELF 格式的文件分为4类:

    ELF文件类型 说明 实例
    可重定位文件(Relocatable File) 内容包含了代码和数据,可以被链接成可执行文件或共享目标文件。 Linux上的 .o 文件
    可执行文件(Executable File) 可以直接执行的程序 Linux上的 a.out
    共享目标文件(Shared Object File) 内容包含了代码和数据,可以作为链接器的输入,在链接阶段和其他的 Relocatable File 或者 Shared Object File 一起链接成新的 Object File;或者在运行阶段,作为动态连接器的输入,和Executable File 结合,作为进程的一部分来运行。 Linux上的 .so
    核心转储文件(Core Dump File) 进程意外终止时,系统可以将该进程的部分内容和终止时的状态信息保存到该文件中以供调试分析。 Linux 上的 core 文件

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第21张图片

文件格式

ELF文件格式提供了两种不同的视角,在汇编器和链接器看来,ELF文件是由Section Header Table描述的一系列Section的集合,而执行一个ELF文件时,在加载器(Loader)看来它是由Program Header Table描述的一系列Segment的集合

ELF文件格式

左边是从汇编器和链接器的视角来看这个文件,开头的ELF Header描述了体系结构和操作系统等基本信息,并指出Section Header Table和Program Header Table在文件中的什么位置,Program Header Table在汇编和链接过程中没有用到,所以是可有可无的,Section Header Table中保存了所有Section的描述信息。右边是从加载器的视角来看这个文件,开头是ELF Header,Program Header Table中保存了所有Segment的描述信息,Section Header Table在加载过程中没有用到,所以是可有可无的。注意Section Header Table和Program Header Table并不是一定要位于文件开头和结尾的,其位置由ELF Header指出,上图这么画只是为了清晰。

文件处理相关工具:Binutils

https://www.gnu.org/software/binutils/

  • ar:归档文件,将多个文件打包成一个大文件。
  • as:被 gcc 调用,输出汇编文件,输出目标文件供应链接收器 ld 连接。
  • ld:GNU 链接器。被 gcc 调用,它把目标文件和各种库文件结合在一起,重定位数据,并链接付好引用。
  • objcopy:执行文件格式转换。
  • objdump:显示 ELF 文件 的信息。
    • -S,反汇编(gcc -g -c 源文件.c --> 生成带调试信息的 .o 文件)
  • readelf:显示更多 ELF 格式文件的信息(包括 DWARF 调试信息)。
    • -SW,显示Section header table
    • -h,显示文件头信息

嵌入式开发

什么是嵌入式开发

嵌入式开发是一种比较综合性的技术,它不单指纯粹的软件开发技术,也不单是一种硬件配置技术;他是在特定的硬件环境下针对某款硬件进行开发,是一种系统级别的与硬件结合比较紧密的软件开发技术。

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第22张图片

交叉编译

  • 参与编译和运行的机器根据其角色可以分成以下三类:

    • 构建(build)系统:生成编译器可执行程序的计算机。
    • 主机(host)系统:运行编译器可执行程序,编译链接应用程序的计算机系统。
    • 目标(target)系统:运行应用程序的计算机系统
  • 根据 build/host/target 的不同组合我们可以得到如下的编译方式分类:

    • 本地(native)编译:build == host == target
    • 交叉(cross)编译:build == host != target
  • 交叉(cross)编译:build == host != target

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第23张图片

  • GNU 交叉编译工具链(Toolchain)

    • 命名格式:arch-vendor-os1-[os2-]XXX
    • 例子:
      • x86_64-linux-gnu-gcc
      • riscv64-unknown-elf-gcc
      • riscv64-unknown-elf-objdump
      • riscv64-unknown-linux-gnu-gcc
      • riscv64-linux-gnu-gcc

调试器 GDB

https://www.gnu.org/software/gdb/

  • GDB(GDB:The GNU Project Debugger),GNU项目调试器,用于查看另一个程序在执行过程中正在执行的操作,或该程序奔溃时正在执行的操作。
  • 被调试的程序可能与 GDB 在同一台计算机上执行,也可能在另一台计算机(远程)上或者在模拟器上执行。
  • GDB支持调试多种语言:譬如:Assembly,C,Go,Rust,…

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第24张图片

GDB 基本调试流程

  • 重新编译程序并在编译选项中加入“-g”
    • $ gcc -g test.c
  • 运行 gdb 和程序
    • $ gdb a.out
  • 设置断点
    • (gdb) b 6
  • 运行程序
    • (gdb) r
  • 程序暂停在断点处,执行查看
    • (gdb) p xxx
  • 继续、单步或者恢复程序运行
    • (gdb) s/n/c

模拟器 QEMU

https://www.qemu.org/

  • QEMU 是一套由(Fabrice Bellard)编写的以 GPL 许可证分发源码的计算机系统模拟软件,在 GNU/Linux 平台上使用广泛。
  • 支持多种体系架构。譬如:IA-32(x86),AMD 64,MIPS 32/64,RISC-V 32/64 等等。
  • QEMU 有两种主要运作模式:
    • User mode:直接运行应用程序。
    • System mode:模拟整个计算机系统,包括中央处理器及其他周围设备。

Qemu 的安装与使用

  • 安装
    • Ubuntu 上 apt install
    • 源码编译安装
  • qemu-system-riscv32 … -kernel ./test.elf
  • qemu-system-riscv32 … -kernel ./test.elf -s -S
    • -s:“-gdb tcp::1234”的缩写,启动 gdbserver 并在 1234端口号上监听客户端。
    • -S:在启动时停止CPU(只有到在客户端键入 ‘c’ 才会开始执行)

项目构建工具 Make

https://www.gnu.org/software/make/

  • make 是什么
    • make是一种自动化工程管理工具。
  • Makefile
    • 配合make,用于描述构建工程过程中所管理的对象以及如何构造工程的过程。
  • make 如何找到 Makefile
    • 隐式查找:当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件
    • 显式查找:-f

MakeFile 的构成

  • MakeFile 由一条或者多条规则(rule)组成。

  • 每条规则由三要素构成:

    • target:目标,可以是 obj 文件也可以是可执行文件。
    • prerequisites:生成 target 所需要的依赖。
    • command:为了生成 target 需要执行的命令,可以有多条。
  • 一个简单的 Makefile 规则如下:

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第25张图片

  • Makfile中其他元素介绍

    • 缺省规则

      RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第26张图片

    • 伪规则

      RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第27张图片

    • 注释:行注释,以“#”开头

make 的运行

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第28张图片

RISC-V汇编

RISC-V汇编语言 入门

汇编语言概念简介

  • 汇编语言(Assembly Language)是一种低级语言
  • 汇编语言的缺点:
    1. 难读
    2. 难写
    3. 难移植
  • 汇编语言的优点
    1. 灵活
    2. 强大
  • 汇编语言的应用场景
    1. 需要直接访问底层硬件的地方
    2. 需要对性能执行极致优化的地方

汇编语言语法介绍(GNU版本)

  • 一个完整的RISC-V汇编程序有多条 语句(statement)组成

  • 一个典型的RSIC-V汇编 语句 由3部分组成:

    ​ [label:] [operation] [comment]

    • label(标号):GNU汇编中,任何以冒号结尾的标识符都被认为是一个标号。

    • operation 可以有以下多种类型:

      • instruction(指令):直接 对应二进制机器指令的字符串。

      • pseudo-instruction(伪指令):为了提高编写代码的效率,可以用一条伪指令指示汇编器产生多条实际的指令(instruction)。

      • directive(指令/伪操作):通过类似指令的形式(以 “ . ”开头),通知汇编器如何控制代码的产生等,不对应具体的指令。

      • macro:采用.macro/.endm自定义的宏。

        .macro do_nothing # 宏的开头,定义了一个名为do_nothing的宏
        	nop		# 宏的内容
        .endm		# 宏的结尾
        
    • comment(注释):常用方法,“#”开始到当前行结束。

汇编常见伪操作(directive)
指示符 作用
.text 代码段,之后跟的符号都在.text内
.data 数据段,之后跟的符号都在.data内
.bss 未初始化数据段,之后跟的符号都在.bss中
.section .foo 自定义段,之后跟的符号都在.foo段中,.foo段名可以做修改
.align n 按2的n次幂字节对齐
.balign n 按n字节对齐
.globl sym 声明sym未全局符号,其它文件可以访问
.string “str” 将字符串str放入内存
.byte b1,…,bn 在内存中连续存储n个单字节
.half w1,…,wn 在内存中连续存储n个半字(2字节)
.word w1,…,wn 在内存中连续存储n个字(4字节)
.dword w1,…,wn 在内存中连续存储n个双字(8字节)
.float f1,…,fn 在内存中连续存储n个单精度浮点数
.double d1,…,dn 在内存中连续存储n个双精度浮点数
.option rvc 使用压缩指令(risc-v c)
.option norvc 不压缩指令
.option relax 允许链接器松弛(linker relaxation,链接时多次扫描代码,尽可能将跳转两条指令替换为一条)
.option norelax 不允许链接松弛
.option pic 与位置无关代码段
.option nopic 与位置有关代码段
.option push 将所有.option设置存入栈
.option pop 从栈中弹出上次存入的.option设置

RISC-V 汇编指令 总览

RISC-V 汇编指令 操作对象

  1. 寄存器:
    1. 32个通用寄存器,x0~x31(注意:本章节课程仅涉及RCV32I的通用寄存器组);
      1. x0只读,读出来永远是0。其他的可读可写;
      2. 另外还有一个pc寄存器,常规下不可访问,用于保存当前指令的地址。
    2. 在RISC-V中,Hart(硬件线程,处理器执行的最小单元)在执行算数逻辑运算是操作的数据必须直接来自寄存器。
  2. 内存:
    1. Hart可以执行在寄存器和内存之间的数据读写操作;
    2. 读写操作使用字节(Byte)为基本单位进行寻址;
    3. RV32可以访问最多2^32个字节的内存空间。

RISC-V 汇编指令 编码格式

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第29张图片

6种指令格式(format)
  • R-type:(Register),每条指令中有3个fields,用于指定3个寄存器参数。
  • I-type:(Immediate),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度12bits)。
  • S-type:(Store),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为12bits,但fields的组织方式不同于I-type)。
  • B-tyep:(Branch),每条指令除了带有两个寄存器参数外,还带有一个立即数参数(宽度为12bits,但取值为2的倍数)。
  • U-type:(Upper),每条指令含有一个寄存器参数再加上一个立即数参数(宽度为20bits,用于表示一个立即数的高20位)。
  • J-type:(Jump),每条指令含有一个寄存器参数再加上一个立即数参数(宽度为20bits)
RV32/64G 的 opcode 分类

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第30张图片

riscv-spec-20191213.ptf 第147页

基础概念
  • 指令长度:ILEN1=32bits(RV32I)
  • 指令对齐:IALIGN=32bits(RV32I)
  • 32个bit划分成不同的 “域(field)”。如,R-type分成了6个域。
  • funct3/funct7 和 opcode 一起决定最终的指令类型
  • 指令在内存中按照 小端序 排列
小端序概念
我的总结
  • 小端序是相对大端序来的,他们共同的特征是:从内存的低地址依次向高地址读取和写入

  • 区别在于,对于0x12345678(下面是4字节对齐):

    • 大端模式,即低地址存放高字节数

      RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第31张图片

    • 小端模式,即低地址存储低字节数:

      RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第32张图片

汪辰老师的讲述
  • 主机字节序(HBO - Host Byte Order)

  • 一个多字节整数在计算机内存中存储的字节顺序称为主机字节序(HBO - Host Byte Order,或者叫本地字节序)

  • 不同类型CPU的HBO不同,这与CPU的设计有关。分为大端序(Bit-Endian)小端序(Little-Endian)。

  • 主机字节序(大端序 vs 小端序)

    RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第33张图片

RISC-V 汇编指令分类

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第34张图片

RISC-V 汇编伪指令一览

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第35张图片

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第36张图片RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第37张图片RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第38张图片

riscv-spec-20191213.ptf 第157页

RISC-V汇编 指令 详解

算数运算指令

基本概念
无符号数 v.s. 有符号数
  • 有符号数在计算机中表示:二进制补码(two’s complement),表示一个负数,取反加一(反过来也是取反加一)
  • 符号扩展(Sign extension) v.s. 零扩展(Zero extension)
部分指令介绍
  • ADD

    语法 ADD RD, RS1, RS2
    例子 add x5, x6, x7 x5 = x6 + x7
    • 编码格式:R-type
      • opcode(7): 0110011 (OP)
      • funct3取值000; funct7取值0000000
      • rs1(5): 第一个 operand(“source register 1”)
      • rs2(6): 第二个 operand(“source register 2”)
      • rd(5): “destination register” 用于存放求和的结果
  • SUB(Substract)

    语法 SUB RD, RS1, RS2
    例子 sub x5, x6, x7 x5 = x6 - x7
    • 编码格式:R-type
      • opcode(7): 0110011 (OP)
      • funct3取值000; funct7取值0100000
      • rs1(5): 第一个 operand(“source register 1”)
      • rs2(6): 第二个 operand(“source register 2”)
      • rd(5): “destination register” 用于存放相减的结果
  • ADDI(ADD Immediate)

    语法 addi RD, RS1, IMM
    例子 addi x5, x6, -2 x5 = x6 + (-2)
    • 编码格式:I-type

      • opcode(7): 0010011 (OP-IMM)。
      • funct3: 000,和 opcode 一起决定最终的指令类型。
      • rs1(5): 第一个 operand(“source register 1”)。
      • imm(12): “immediate”,立即数,代替了 R-type 的第三个寄存器参数和 func7。
        • 立即数占12位。
        • 在参与算数运算前该 立即数 会被“符号扩展”为一个32位的数。
        • 这个立即数可以表达的数值范围为:[-2^11, +2^11),既[-2048, 2047)。
      • rd(5): “destination register” 用于存放求和的结果 。
    • 注意:RISC-V ISA并没有提供 SUBI 指令

      • 因为可以用 加上 一个负的立即数 表示
      • x5 = x6 + (-4)
    • ADDI的局限性

      给一个寄存器赋值的数值范围只有:[-2048, 2047)。如果要赋值一个大数(32位)怎么办?

      √ 解决思路:自己构造一个。具体做法:

      • 先引入一个新的命令(LUI)设置高20位,存放 rs1
      • 然后符用现有的 ADDI 命令补上剩余的低 12 位即可
  • LUI(Load Upper Immediate)

    语法 LUI RD, IMM
    例子 lui x5, 0x12345 x5 = 0x12345 << 12
    • LUI 指令采用 U-type:

      • opcode(7): 0110111 (LUI)。
      • rd(5): “destination register” 用于存放结果。
      • imm(20): “immediate”,立即数。
    • LUI 指令会构造一个 32bits 的立即数,这个立即数的高 20 位对应指令中的 imm ,低 12 位清零。这个立即数作为结果存放在 RD 中。

    • 练习:

      利用 LUI + ADDI 来为寄存器加载一个大数

      0x12345678

      lui x1, 0x12345		# x1 = 0x12345000
      addi x1, x1, 0x678	# x1 = x1 + 0x678
      

      0x12345fff

      # 错误写法
      # addi 中的 0xfff 
      # 会被符号扩展成一个32位的数字 0xffffffff
      lui x1, 0x12345		# x1 = 0x12345000
      addi x1, x1, 0xfff	# x1 = x1 + 0xfff
      
      
      # 正确写法
      # 借位写法 给0xfff加上1给到x1,
      # 再给x1减去1
      # 相当于借的那一位1位 0x1000 - 1 = 0xfff
      lui x1, 0x12346
      addi x1, x1, -1
      
      

      上面的写法虽然可以实现,但是还是过于麻烦,而且对于0x12345fff 可以用借位的写法,但是对于0xffff ffff就借不了位了,这个时候我们可以用 li 伪指令解决。

  • LI(Load Immediate)

    语法 LI RD, IMM
    例子 li x5, 0x12345678 x5 = 0x12345678
    • LI 是一个伪指令(pseudo-instruction)
    • 汇编器会根据 IMM 的实际情况自动生成正确的真实指令(instruction)
  • AUIPC

    语法 AUIPC RD, IMM
    例子 auipc x5, 0x12345 x5 = 0x12345 << 12 + pc
    • AUIPC 指令采用 U-type
    • 和 LUI 指令类似, AUIPC 指令也会构造一个 32 bits 的立即数,这个立即数的高 20 位对应指令中的 imm,低12位清零。但和 LUI 不同的是,AUIPC 会将这个立即数和 PC 值相加,将相加的结果存放在 RD 中。
  • LA(Load Address)

    语法 LA RD, LABEL
    例子 la x5, foo
    • LA 是一个伪指令(pseudo-instruction)。
    • 具体编程时给出需要加载的 label,编译器会根据实际情况利用 auipc 和其他指令自动生成正确的指令序列。
    • 常用于加载一个函数或者变量的地址。
算数指令总结
指令 语法 描述 例子
ADD ADD RD, RS1, RS2 RS1和rS2的值相加,结果保存到RD add x5, x6, x7
SUB SUB RD, RS1, RS2 RS1的值减去RS2的值,结果保存到RD sub x5, x6, x7
ADDI ADDI RD, RS1, IMM RS1的值和IMM相加,结果保存到RD addi x5, x6, 100
LUI LUI RD, IMM 构造一个32位的数,高20位存放IMM,低12位清零。结果保存到RD lui x5, 0x12345
AUIPC AUIPC RD, IMM 构造一个32位的数,高20位保存到IMM,低12位清零。结果和PC相加后保存到RD auipc x5, 0x12345
基于算数运算指令实现的其他伪指令
伪指令 语法 等价指令 指令描述 例子
LI LI RD, IMM LUI和ADDI的组合 将立即数 IMM 加载到 RD 中 li x5, 0x12345678
LA LA RD, LABEL AUIPC 和 ADDI 的组合 为RD加载一个地址值 la x5, label
NEG NEG RD, RS SUB RD, x0, RS 对RS中的值取反并将结果存放在RD中 neg x5, x6
MV MV RD, RS ADDI RD, RS, 0 将RS中的值拷贝到RD中 mv x5, x6
NOP NOP ADDI x0, x0, 0 什么也不做 nop

逻辑运算指令(Logical instruction)

指令 格式 语法 描述 例子
AND R-type AND RD, RS1, RS2 RD = RS1 & RS2 and x5, x6, x7
OR R-type OR RD, RS1, RS2 RD = RS1 | RS2 or x5, x6, x7
XOR R-type XOR RD, RS1, RS2 RD = RS1 ^ RS2 xor x5, x6, x7
ANDI I-type ANDI RD, RS1, IMM RD = RS1 & IMM andi x5, x6, 20
ORI I-type ORI RD, RS1, IMM RD = RS1 | IMM or x5, x6, 20
XORI I-type XORI RD, RS1, IMM RD = RS1 ^ IMM xor x5, x6, 20
  • 所有的逻辑指令都是按位操作
  • XOR(eXclusive OR,“异或”):两个 bit 值 不同(异)则取值为 1(达到类似取 1为OR的效果);如果 两个 bit相同则取值为 0。
  • 取反就异或上二进制表示全是 1 的值
伪指令 语法 等价指令 描述 例子
NOT NOT RD, RS XORI RD, RS, -1 对 RS 的值按位取反,结果存放在 RD 中 not x5, x6

移位运算指令(Shifting Instruction)

逻辑移位
指令 格式 语法 描述 例子
SLL R-type SLL RD, RS1, RS2 逻辑左移(Shift Left Logical)RD = RS1 << RS2 sll x5, x6, x7
SRL R-type SRL RD, RS1, RS2 逻辑右移(Shift Right Logical)RD = RS1 >> RS2 srl x5, x6, x7
SLLI I-type SLLI RD, RS1, IMM 逻辑左移立即数(Shift Left Logical Immediate)RD = RS1 << IMM slli x5, x6, 3
SRLI I-type SRLI RD, RS1, IMM 逻辑右移立即数(Shift Right Logical Immediate)RD = RS1 >> IMM srli x5, x6, 3

无论是逻辑左移还是逻辑右移,补足的都是 0

算数移位
指令 格式 语法 描述 例子
SRA R-type SRA RD, RS1, RS2 算数右移(Shift Right Arithmetic) sra x5, x6, x7
SRAI I-type SRAI RD, RS1, RS2 算数右移立即数(Shift Right Arithmetic Immediate) srai x5, x6, 3
  • 算数右移按照符号位值补足。
  • 对于算数移位,为什么没有算数左移呢,因为 左移符号位都被移走了,符号位没有意义。

内存读写指令(Load and Store Instruction)

  • 内存读指令:Load,将数据从内存读入寄存器
  • 内存写指令:Store,将数据从寄存器写出到内存

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第39张图片

内存读(Load)
指令 格式 语法 描述 例子
LB I-type LB RD, IMM(RS1) Load Byte,从内存中读取一个8bits的数据到RD中,内存地址 = RS1 + IMM,数据在保存到RD之前会执行sign-extended。 lb x5, 40(x6)
LBU I-type LBU RD, IMM(RS1) Load Byte Unsigned,从内存中读取一个8bits的数据到RD中,内存地址 = RS1 + IMM,数据在保存到RD之前会执行zero-extended。 lbu x5, 40(x6)
LH I-type LH RD, IMM(RS1) Load Halfword,从内存中读取一个16bits的数据到RD中,内存地址 = RS1 + IMM,数据在保存到RD之前会执行sign-extended。 lh x5, 40(x6)
LHU I-type LHU RD, IMM(RS1) Load Halfword Unsigned,从内存中读取一个16bits的数据到RD中,内存地址 = RS1 + IMM,数据在保存到RD之前会执行zero-extended。 lhu x5, 40(x6)
LW I-type LW RD, IMM(RS1) Load Word,从内存中读取一个32bits的数据到RD中,内存地址 = RS1 + IMM。 lw x5, 40(x6)

注意:IMM给出的偏移量范围是[-2048, 2047]。

内存写(Store)
指令 格式 语法 描述 例子
SB S-type SB RS2, IMM(RS1) Store Byte,将RS2寄存器中低8bits的数据写出到内存,内存地址 = RS1 + IMM。 sb x5, 40(x6)
SH S-type SH RS2, IMM(RS1) Store Halfword,将RS2寄存器中低16bits的数据写出到内存,内存地址 = RS1 + IMM。 sh x5, 40(x6)
SW S-type SW RS2, IMM(RS1) Store Word,将RS2寄存器中32bits的数据写出到内存,内存地址 = RS1 + IMM。 sw x5, 40(x6)

注意:IMM给出的偏移量范围是[-2048, 2047]。

  • 为上面对于load要区分无符号方式和有符号方式,而store不区分?

条件分支指令

指令 格式 语法 描述 例子
BEQ B-type BEQ RS1, RS2, IMM Branch if EQual。比较RS1和RS2的值,如果相等,则执行路径跳转到一个新的地址。 beq x5, x6, 100
BNE B-type BNE RS1, RS2, IMM Branch if Not EQual。比较RS1和RS2的值,如果不相等,则执行路径跳转到一个新的地址。 bne x5, x6, 100
BLT B-type BLT RS1, RS2, IMM Branch if Less Than。按照有符号方式比较RS1和RS2的值,如果RS1 < RS2,则执行路径跳转到一个新的地址。 blt x5, x6, 100
BLTU B-type BLTU RS1, RS2, IMM Branch if Less Than(Unsigned)。按照无符号方式比较RS1和RS2的值,如果RS1 < RS2,则执行路径跳转到一个新的地址。 bltu x5, x6, 100
BGE B-type BGE RS1, RS2, IMM Branch if Greater Than。按照有符号方式比较RS1和RS2的值,如果RS1 >= RS2,则执行路径跳转到一个新的地址。 bge x5, x6, 100
BGEU B-type BGEU RS1, RS2, IMM Branch if Greater Than(Unsigned)。按照无符号方式比较RS1和RS2的值,如果RS1 >= RS2,则执行路径跳转到一个新的地址。 bgeu x5, x6, 100
  • 跳转的目标地址计算方法:先将 IMM x 2,符号扩展后和PC值相加得到最终的目标地址,所以跳转范围是以PC为基准,+/- 4KB 左右([-4096, 4094])
  • 具体编程时,不会直接写 IMM,而是用标号代替,交由连接器来最终决定IMM的值。
伪指令 语法 等价指令 描述
BLE BLE RS, RT, OFFSET BGE RT, RS, OFFSET Branch if Less & EQual,有符号方式比较,如果 RS <= RT,跳转到 OFFSTET
BLEU BLEU RS, RT, OFFSET BGEU RT, RS, OFFSET Branch if Less or EQual Unsigned,无符号方式比较,如果 RS <= RT,跳转到 OFFSTET
BGT BGT RS, RT, OFFSET BLT RT, RS, OFFSET Branch if Greater Than,有符号方式比较,如果 RS > RT,跳转到 OFFSTET
BGTU BGTU RS, RT, OFFSET BLTU RT, RS, OFFSET Branch if Greater Than Unsigned,无符号方式比较,如果 RS > RT,跳转到 OFFSTET
BEQZ BEQZ RS, OFFSET BEQ RS, x0, OFFSET Branch if EQual Zero,如果 RS == 0,跳转到OFFSET
BNEZ BNEZ RS, OFFSET BNE RS, x0, OFFSET Branch if Not EQual Zero,如果 RS != 0,跳转到OFFSET
BLTZ BLTZ RS, OFFSET BLT RS, x0, OFFSET Branch if Less Than Zero,如果 RS < 0,跳转到OFFSET
BLEZ BLEZ RS, OFFSET BGE x0, RS, OFFSET Branch if Less or EQual Zero,如果 RS <= 0,跳转到OFFSET
BGTZ BGTZ RS, OFFSET BLT x0, RS, OFFSET Branch if Greater Than Zero,如果 RS > 0,跳转到OFFSET
BGEZ BGEZ RS, OFFSET BGE RS, x0, OFFSET Branch if Greater or EQual Zero,如果 RS <= 0,跳转到OFFSET

无条件跳转指令(Unconditional Jump Instruction)

  • JAL(Jump And Link)

    语法 JAL RD, LABEL
    例子 jal x1, label
    • JAL指令使用J-type编码格式。

    • JAL指令用于调用子过程(subroutine/function)。

    • 子过程的地址计算方式:首先对20bits宽的IMM x 2后进行sign-extended,然后将符号扩展后的值和PC的值相加。因此该函数跳转的范围是以PC为基准,上下~+/- 1MB。

    • JAL指令的下一条指令的地址写入RD,保存返回地址。

    • 实际编程时,用label给出跳转的目标,具体IMM值由编译器和链接器最终负责生成。

    • 该函数跳转的范围是以PC为基准,上下~+/- 1MB。

      • 如何解决更远距离的跳转?

        AUIPC x6, IMM-20
        JALR x1, x6, IMM-12
        
  • JALR(Jump And Link Register)

    语法 JALR RD, IMM(RS1)
    例子 jalr x0, 0(x5)
    • JALR指令使用I-type编码格式。
    • JALR指令用于调用子过程(subroutine/function)。
    • 子过程的地址计算方法:首先对12bits宽的IMM进行sign-extended,然后将符号扩展后的值和RS1的值相加,得到最终的结果后将其最低位设置为0(确保地址按2字节对齐)。因此该函数跳转地址的范围是以RS1为基准,上下 ~+/- 2KB。
    • JALR指令的下一条指令的地址写入RD,保存为返回地址。

    如果跳转不需要返回,可以利用 x0 代题 JAL 和 JALR 中的 RD。

  • 伪指令

    伪指令 语法 等价指令 例子
    J J OFFSET JAL x0, OFFSET j leap
    JR JR RS JARL x0, 0(RS) jr x2

RISC-V指令寻址模式总结

所谓寻址模式指的是指令中定位操作数(oprand)或者地址的方式。

寻址模式 解释 例子
立即数寻址 操作数是指令本身的一部分。 addi x5, x6, 20
寄存器寻址 操作数存放在寄存器中,指令中指定访问的寄存器从而获取该操作数。 add x5, x6, x7
基址寻址 操作数在内存中,指令通过指定寄存器(基址base)和立即数(偏移offset),通过base+offset的方式获得操作数在内存中的地址从而获取该操作数。 sw x5, 40(x6)
PC 相对地址 在指令中通过PC和指令中的立即数相加获得目的地址的值。 beq x5, x6, 100

RISC-V汇编 函数 调用约定

函数调用过程概述

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第40张图片

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第41张图片
RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第42张图片

汇编编程时为何需要制定函数调用约定

  • 函数跳转时使用的寄存器,不同的人可能用不一样的,自己编程时不会去在意用哪个寄存器,但是多人协作时,别人不知道你的调用函数用的寄存器,这会导致一些不便,于是约定一个共同的函数调用寄存器,可以更好的有助于团队,以及其他人容易的调用你的代码。

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第43张图片

函数调用过程中的编程约定

有关寄存器的编程约定
寄存器 ABI名(编程用名) 用途约定 谁负责在函数调用过程中维护这些寄存器
x0 zero 读取时总为0,写入时不起任何效果 N/A
x1 ra 存放函数返回地址的值(return address) Caller
x2 sp 存放栈指针(stack pointer) Callee
x5-x7, x28-x31 t0-t2, t3-t6 临时(temporaries)寄存器,Callee可能会使用这些寄存器,所以Callee不保证这些寄存器中的值在函数调用过程中保持不变,这意味着对于Caller来说,如果需要的话,Caller需要自己在调用Callee之前保存临时寄存器中的值。 Caller
x8, x9, x18-x27 s0, s1, s2-s11 保存(saved)寄存器,Callee需要保证这些寄存器的值在函数返回后仍然维持函数调用之前的原值,所以一旦Callee在自己的函数中会用到这些寄存器,则需要在栈中备份并在退出函数时进行恢复。 Callee
x10, x11 a0, a1 参数(argument)寄存器,用于在函数调用过程中保存第一个和第二个参数,以及在函数返回时传递返回值。 Caller
x12-x17 a2-a7 参数(argument)寄存器,如果函数调用时需要传递更多的参数,则可以用这些寄存器,但注意用于传递参数的寄存器最多只有8个(a0-a7),如果还有更多的参数则需要利用栈。 Caller
函数跳转和返回指令的编程约定
伪指令 等价指令 描述 例子
jal offset jal x1, offset 跳转 offset 制定位置,返回地址保存在 x1(ra) jal foo
jalr rs jalr x1, 0(rs) 跳转到 rs 中值所指定的位置,返回地址保存在 x1(ra) jarl s1
j offset jal x0, offset 跳转到 offset 指定位置,不保存返回地址。 j loop
jr rs jalr x0, 0(rs) 跳转到 rs 中值所指定的位置,不保存返回地址。 jr s1
call offset auipc x1, offset[31:12]+offset[11] jalr x1, oofset[11:0](x1) 长跳转调用函数 call foo
tail offset auipc x6, offset[31:12]+offset[11] jalr x0, offset[11:0](x6) 长跳转尾调用 tail foo
ret jalr x0, 0(x1) 从 Callee 返回 ret
实现被调用函数的编程约定
函数起始部分(Prologue)
减少 sp 的值,根据本函数中使用 saved 寄存器的情况以及 local 变量的多少开辟栈空间。
将 saved 寄存器的值保存到栈中。
如果函数中还会调用其他的函数,则将 ra 寄存器的值保存到栈中。
函数退出部分(Epilogue)
从栈中恢复 saved 寄存器
如果需要的话,从栈中恢复 ra 寄存器
增加 sp 的值,恢复到进入函数之前的状态
调用 ret 返回

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第44张图片

RISC-V汇编 与 C 混合编程

RISC-V 汇编调用 C 函数

  • 遵守ABI(Abstract Binary Interface)的规定
    • 数据类型的大小,布局和对齐
    • 函数调用约定(Calling Convention)
    • 系统调用约定
  • RISC-V 函数调用约定规定:
    • 函数参数采用寄存器 a0~a7 传递
    • 函数返回值采用寄存器 a0 和 a1 传递

C函数中嵌入 RISC-V 汇编

asm [volatile](

​ “汇编指令”

​ : 输出操作数列表(可选)

​ : 输入操作数列表(可选)

​ : 可能影响的寄存器或者存储器(可选)

)

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第45张图片

  • 汇编指令用双引号括起来,多条指令至今啊用 “ ; ” 或者 “ \n ” 分隔
  • “输出的操作数列表”和“输入的操作数列表”用于将需要操作的C变量和汇编指令的操作数对应起来,多个操作数之间用 “ , ” 分隔。
  • “可能影响的寄存器或者存储器” 用于告知编译器当前嵌入的汇编语句可能修改的寄存器或者内存,方便编译器执行优化。
  • [volatile]:加了这个表示里面的代码不需要优化了,可以省略不写。

例子:

  • 这里的 [sum]“r”(a),意思是 sum 绑定变量 a,以 r(寄存器)的形式,若是 m,则是堆栈的形式。
  • 第二个例子中,0,1,2是按顺序绑定的。

RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第46张图片RISC-V入门(基础概念+汇编部分) 基于 汪辰老师的视频笔记_第47张图片

更多见【参考三】

你可能感兴趣的:(RISC-V,C语言,linux,risc-v,c语言,汇编)