当前位置: 首页 > news >正文

使用 C++23 从零实现 RISC-V 模拟器(3):指令解析

指令解析

这章内容进一解析更多的指令,此外将解析指令的过程拆分为一个单独的类,采用表格驱动的方式,将数据和逻辑分离,降低了 if else 嵌套层数过多。

这部分依旧改动不多,只增加了七个指令。此外代码中细碎的变动没有完全列出来,下面只是主体部分的更新,可以尝试自己动手实现,如果简单抄一遍是没有成长的,总之需要在解决问题中加深印象。可以参考这个分支的代码:https://github.com/weijiew/crvemu/tree/lab3-inst

1. InstructionExecutor

接下来首先将指令解析拆分为一个单独的类 InstructionExecutor ,用来专门解析指令。

class InstructionExecutor {
public:static std::optional<uint64_t> execute(Cpu& cpu, uint32_t inst);
};

1.2 Cpu::execute

将 CPU 中的 execute 方法改为下面的形式:

std::optional<uint64_t>  Cpu::execute(uint32_t inst) {auto exe = InstructionExecutor::execute(*this, inst);if (exe.has_value()) {return exe;}return std::nullopt;
}

此前将所有指令解析都放入了一个 switch 来维护,但是解析指令的个数一增加就难以维护了。

1.3 InstructionExecutor::execute

接下来讲解 InstructionExecutor::execute 如何实现表格驱动的方式来解析指令:

std::optional<uint64_t> executeAddi(Cpu& cpu, uint32_t inst) {uint32_t rd = (inst >> 7) & 0x1f;uint32_t rs1 = (inst >> 15) & 0x1f;int64_t immediate = static_cast<int32_t>(inst & 0xfff00000) >> 20;std::cout << "ADDI: x" << rd << " = x" << rs1 << " + " << immediate << std::endl;cpu.regs[rd] = cpu.regs[rs1] + immediate;return cpu.update_pc();
}std::optional<uint64_t> InstructionExecutor::execute(Cpu& cpu, uint32_t inst) {uint32_t opcode = inst & 0x7f;uint32_t funct3 = (inst >> 12) & 0x7;// x0 is hardwired zerocpu.regs[0] = 0;std::cout << "Executing instruction: 0x" << std::hex << opcode <<", funct3: 0x" << funct3 << std::dec << std::endl;std::unordered_map<std::tuple<uint32_t, uint32_t>,std::function<std::optional<uint64_t>(Cpu&, uint32_t)>> instructionMap = {{std::make_tuple(0x13, 0x0), executeAddi},{std::make_tuple(0x13, 0x1), executeSlli},{std::make_tuple(0x13, 0x2), executeSlti},{std::make_tuple(0x13, 0x3), executeSltiu},{std::make_tuple(0x13, 0x4), executeXori},{std::make_tuple(0x13, 0x5), executefunct70X5},{std::make_tuple(0x13, 0x6), executeOri},{std::make_tuple(0x13, 0x7), executeAndi},{std::make_tuple(0x33, 0x0), executeAdd},};auto it = instructionMap.find({opcode, funct3});if (it != instructionMap.end()) {return it->second(cpu, inst);}// 确保所有可能的执行路径都有明确的返回值
}

其中维护了一张哈希表,key 是有 opcode 和 funct3 组成,value 对应解析指令的函数。

当执行的时候会根据解析出来 opcode 和 funct3 用来进一步跳转到对应的指令。

此外采用 C++17 optional 来控制处理错误,这也是为什么最后一行找不到的时候会返回 return std::nullopt; 。这部分内容可以进一步阅读这篇文章:C++17 optional 其中给出了 optional 出来之前是如何处理的,存在哪些问题,出现之后又是如何处理的。

1.2 funct7

注意 {std::make_tuple(0x13, 0x5), executefunct70X5}, 对应了多个指令。

因为所有的指令都需要 opcode 和 funct3 定位,但有时候需要 funct7 进一步区分。下面的函数就是做了进一步的跳转。

std::optional<uint64_t> executefunct70X5(Cpu& cpu, uint32_t inst) {uint32_t funct7 = (inst & 0xfe000000) >> 25;std::cout << "Executing srli or srai funct7: 0x" << std::hex << funct7 << std::dec << std::endl;switch (funct7) {// srlicase 0x00: {return executeSrli(cpu, inst);}// sraicase 0x20: {return executeSrai(cpu, inst);}default:return std::nullopt;}
}

2.1 指令解析

从下面的维护的哈希表中我们已经能够看到接下来需要进一步解析的指令,此前 addi 和 add 已经解析完成了的。

  std::unordered_map<std::tuple<uint32_t, uint32_t>,std::function<std::optional<uint64_t>(Cpu&, uint32_t)>> instructionMap = {{std::make_tuple(0x13, 0x0), executeAddi},{std::make_tuple(0x13, 0x1), executeSlli},{std::make_tuple(0x13, 0x2), executeSlti},{std::make_tuple(0x13, 0x3), executeSltiu},{std::make_tuple(0x13, 0x4), executeXori},{std::make_tuple(0x13, 0x5), executefunct70X5},{std::make_tuple(0x13, 0x6), executeOri},{std::make_tuple(0x13, 0x7), executeAndi},{std::make_tuple(0x33, 0x0), executeAdd},};

新增加的指令都属于RISC-V指令集中的I(立即数)类型指令和R(寄存器-寄存器)类型指令的一部分,用于进行基本的整数运算和逻辑操作。以下是每个指令的功能和类别:

  1. Slli (Shift Left Logical Immediate)

    • 类型: I 类型指令
    • 功能: 逻辑左移,将寄存器中的数值左移一个指定的位数(由立即数字段指定)。
  2. Slti (Set Less Than Immediate)

    • 类型: I 类型指令
    • 功能: 将寄存器中的数值与立即数进行有符号比较,如果寄存器的值小于立即数,则将目标寄存器设置为1,否则为0。
  3. Sltiu (Set Less Than Immediate Unsigned)

    • 类型: I 类型指令
    • 功能: 与Slti类似,但是进行的是无符号比较。
  4. Xori (XOR Immediate)

    • 类型: I 类型指令
    • 功能: 对寄存器中的数值与立即数进行异或操作。
  5. Ori (OR Immediate)

    • 类型: I 类型指令
    • 功能: 对寄存器中的数值与立即数进行按位或操作。
  6. Andi (AND Immediate)

    • 类型: I 类型指令
    • 功能: 对寄存器中的数值与立即数进行按位与操作。
  7. Srli (Shift Right Logical Immediate)

    • 类型: I 类型指令
    • 功能: 逻辑右移,将寄存器中的数值右移一个指定的位数(由立即数字段指定)。
  8. Srai (Shift Right Arithmetic Immediate)

    • 类型: I 类型指令
    • 功能: 算术右移,将寄存器中的数值右移一个指定的位数(由立即数字段指定),保持符号位不变。

这些指令提供了基本的算术运算和位操作,用于实现诸如加法、减法、逻辑运算等基本操作,是RISC-V指令集中用于处理整数数据的关键部分。

2.2 SLLI 指令格式

RISC-V 指令 SLLI(Shift Left Logical Immediate)用于将寄存器中的值左移指定的位数,然后将结果存储回寄存器。下面是 SLLI 指令的内部组成以及一个文本图形化的表示:

31              20        15     10        6          0
+----------------+---------+-----+---------+----------+
|   imm[11:0]    |  shamt  |  rd |  funct3 |  opcode  |  I-type
+----------------+---------+-----+---------+----------+
  • imm[11:0]: 12 位的立即数,表示左移的位数。
  • shamt: 移位操作数,指定左移的位数,范围为 0 到 31。
  • rd: 目标寄存器,用于存储结果。
  • funct3: 功能字段,对于 SLLI 指令为 001。
  • opcode: 操作码字段,指定指令类型。

例子:

假设有以下 SLLI 指令:

SLLI x1, x2, 4

这表示将寄存器 x2 中的值左移 4 位,并将结果存储回 x1。在文本图形化的内部表示中:

  000000000100  10000  00001  001  0110011imm[11:0]    shamt    rd   funct3   opcode
  • imm[11:0] 是 000000000100,表示左移的位数为 4。
  • shamt 是 10000,也就是 4 的二进制表示。
  • rd 是 00001,表示目标寄存器为 x1
  • funct3 是 001,表示 SLLI 操作。
  • opcode 是 0110011,表示 R-type 操作。

因此,SLLI x1, x2, 4 的二进制表示为 00000000010010000000010010110011

使用场景:

SLLI 指令通常用于位操作,例如在实现算法时需要将某个寄存器中的值左移一定位数,以进行乘法或其他算术运算。这在编写低级别的系统软件或底层硬件控制程序时可能会经常遇到。例如,在实现加密算法或图形处理器中,位操作是常见的操作之一。

2.3 SLTI

slti 是一条有符号立即数比较指令,用于将一个寄存器的值与一个立即数进行比较。下面是 slti 指令的内部组成的文本图形表示:

  [  immediate  ] [  rs1  ] [  funct3  ] [  rd  ] [ opcode ]31          20 19     15 14        12 11    7  6       0
  • opcode:操作码字段,指定指令的类型。
  • rd:目标寄存器,用于存储比较结果。
  • funct3:功能码字段,用于指定具体的比较操作。
  • rs1:源寄存器,包含待比较的值。
  • immediate:立即数,与源寄存器的值进行比较。

具体来说,slti 的操作是将 rs1 中的值与有符号的 immediate 相比较,如果 rs1 的值小于 immediate,则将目标寄存器 rd 设置为 1,否则设置为 0。

以下是一个例子,假设我们有如下 RISC-V 汇编代码:

slti x3, x1, 10

这条指令的意思是将寄存器 x1 中的值与立即数 10 进行比较,如果 x1 的值小于 10,则将寄存器 x3 设置为 1,否则设置为 0。这样,x3 将存储比较的结果,表示 x1 < 10 的情况。

2.4 SRAI

“SRAI” 的完整展开是 “Shift Right Arithmetic Immediate”,其中:

  • “S” 表示 “Shift”,表示进行位移操作。
  • “RA” 表示 “Right Arithmetic”,表示是算术右移,即在右移时保持符号。
  • “I” 表示 “Immediate”,表示使用一个立即数值来指定移动的位数。

因此,“SRAI” 用于对有符号整数执行算术右移操作,移动的位数由一个立即数值指定。

下面是一个 RISC-V 汇编指令的示例:

SRAI x1, x2, 2

这意味着:进行算术右移立即数操作,取寄存器 x2 中的值,将其算术右移 2 位,然后将结果存储在寄存器 x1 中。

3. 测试

因为上一部分已经增加了编译和运行汇编代码的工具函数,接下来可以直接调用:

    TEST(RVTests, TestSlli) {std::string code = start +"addi x2, x0, 5 \n"    // Load 5 into x2"slli x1, x2, 3 \n";   // x1 = x2 << 3Cpu cpu = rv_helper(code, "test_slli", 2);// Verify if x1 has the correct valueEXPECT_EQ(cpu.regs[1], 5 << 3) << "Error: x1 should be the result of SLLI instruction";}// Test slti instructionTEST(RVTests, TestSlti) {std::string code = start +"addi x2, x0, 8 \n"    // 将 8 加载到 x2 中"slti x1, x2, 10 \n";  // x1 = (x2 < 10) ? 1 : 0Cpu cpu = rv_helper(code, "test_slti", 2);// 验证 x1 的值是否正确EXPECT_EQ(cpu.regs[1], 1) << "Error: x1 should be the result of SLTI instruction";}

上面只是一部分内容,变动没有完全列出,需要参考代码来实现。

下一节会解析 load 和 store 相关的指令,此外还会引入更多的现代 C++ 新特性并完善工具类。

👉🏻 文章汇总「从零实现模拟器、操作系统、数据库、编译器…」:https://okaitserrj.feishu.cn/docx/R4tCdkEbsoFGnuxbho4cgW2Yntc

相关文章:

使用 C++23 从零实现 RISC-V 模拟器(3):指令解析

指令解析 这章内容进一解析更多的指令&#xff0c;此外将解析指令的过程拆分为一个单独的类&#xff0c;采用表格驱动的方式&#xff0c;将数据和逻辑分离&#xff0c;降低了 if else 嵌套层数过多。 这部分依旧改动不多&#xff0c;只增加了七个指令。此外代码中细碎的变动没…...

CSS Selector—选择方法,和html自动——异步社区的爬取(动态网页)——爬虫(get和post的区别)

这里先说一下GET请求和POST请求&#xff1a; post我们平时是要加data的也就是信息&#xff0c;你会发现我们平时百度之类的 搜索都是post请求 get我们带的是params&#xff0c;是发送我们指定的内容。 要注意是get和post请求&#xff01;&#xff01;&#xff01; 先说一下异…...

C语言 服务器编程-日志系统

日志系统的实现 引言最简单的日志类 demo按天日志分类和超行日志分类日志信息分级同步和异步两种写入方式 引言 日志系统是通过文件来记录项目的 调试信息&#xff0c;运行状态&#xff0c;访问记录&#xff0c;产生的警告和错误的一个系统&#xff0c;是项目中非常重要的一部…...

HarmonyOS 状态管理装饰器 Observed与ObjectLink 处理嵌套对象/对象数组 结构双向绑定

本文 我们还是来说 两个 harmonyos 状态管理的装饰器 Observed与ObjectLink 他们是用于 嵌套对象 或者 以对象类型为数组元素 的数据结构 做双向同步的 之前 我们说过的 state和link 都无法捕捉到 这两种数据内部结构的变化 这里 我们模拟一个类数据结构 class Person{name:…...

windows中的apache改成手动启动的操作步骤

使用cmd解决安装之后开机自启的问题 services.msc 0. 这个命令是打开本地服务找到apache的服务名称 2 .通过服务名称去查看服务的状态 sc query apacheapache3.附加上关掉和启动的命令&#xff08;换成是你的服务名称&#xff09; 关掉命令 sc stop apacheapache启动命令 …...

Intellij Idea的数据库工具 DataGrip

DataGrip DataGrip&#xff1a; IDEA自带&#xff0c;非常好用。智能提示很强大&#xff0c;快捷键跟IDEA自身一致。 如果下载不了 DataGrip&#xff0c;也可以直接用 IDEA 自带的。 常用的快捷键 alt8&#xff1a; 打开数据库Service ctrlshiftF10&#xff1a;打开常用的数…...

精品springboot疫苗发布和接种预约系统

《[含文档PPT源码等]精品基于springboot疫苗发布和接种预约系统[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#xff1a;…...

Linux快速入门

一. Linux的结构目录 1.1 Linux的目录结构 Linux为免费开源的系统&#xff0c;拥有众多发行版&#xff0c;为规范诸多的使用者对Linux系统目录的使用&#xff0c;Linux基金会发布了FHS标准&#xff08;文件系统层次化标准&#xff09;。多数的Linux发行版都遵循这一规范。 注&…...

【图形图像的C++ 实现 01/20】 2D 和 3D 贝塞尔曲线

目录 一、说明二、贝塞尔曲线特征三、模拟四、全部代码如下​五、资源和下载 一、说明 以下文章介绍了用 C 计算和绘制的贝塞尔曲线&#xff08;2D 和 3D&#xff09;。    贝塞尔曲线具有出色的数学能力来计算路径&#xff08;从起点到目的地点的曲线&#xff09;。曲线的形…...

python+flask+django医院预约挂号病历分时段管理系统snsj0

技术栈 后端&#xff1a;python 前端&#xff1a;vue.jselementui 框架&#xff1a;django/flask Python版本&#xff1a;python3.7 数据库&#xff1a;mysql5.7 数据库工具&#xff1a;Navicat 开发软件&#xff1a;PyCharm . 第一&#xff0c;研究分析python技术&#xff0c…...

《CSS 简易速速上手小册》第9章:CSS 最佳实践(2024 最新版)

文章目录 9.1 维护大型项目的 CSS9.1.1 基础知识9.1.2 重点案例&#xff1a;构建一个可复用的 UI 组件库9.1.3 拓展案例 1&#xff1a;优化现有项目的 CSS 结构9.1.4 拓展案例 2&#xff1a;实现主题切换功能 9.2 BEM、OOCSS 和 SMACSS 方法论9.2.1 基础知识9.2.2 重点案例&…...

Qt QVariant类应用

QVariant类 QVariant类本质为C联合(Union)数据类型&#xff0c;它可以保存很多Qt类型的值&#xff0c;包括 QBrush&#xff0c;QColor&#xff0c;QString等等&#xff0c;也能存放Qt的容器类型的值。 QVariant::StringList 是 Qt 定义的一个 QVariant::type 枚举类型的变量&…...

不到1s生成mesh! 高效文生3D框架AToM

论文题目&#xff1a; AToM: Amortized Text-to-Mesh using 2D Diffusion 论文链接&#xff1a; https://arxiv.org/abs/2402.00867 项目主页&#xff1a; AToM: Amortized Text-to-Mesh using 2D Diffusion 随着AIGC的爆火&#xff0c;生成式人工智能在3D领域也实现了非常显著…...

Mac中管理多版本Jdk

1. 首先下载JDK&#xff0c;以jdk8和17为例 2. 打开.zprofile中添加如下内容 #java config export JAVA_8_HOME/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home export JAVA_17_HOME/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home#default java …...

用C语言列出Linux或Unix上的网络适配器

上代码&#xff1a; 1. #include <sys/socket.h> 2. #include <stdio.h> 3. 4. #include <netdb.h> 5. #include <ifaddrs.h> 6. 7. int main() { 8. struct ifaddrs *addresses; 9. if(getifaddrs(&addresses) -1) { 10. printf("…...

单片机学习笔记---LED点阵屏显示图形动画

目录 LED点阵屏显示图形 LED点阵屏显示动画 最后补充 上一节我们讲了点阵屏的工作原理&#xff0c;这节开始代码演示&#xff01; 前面我们已经说了74HC595模块也提供了8个LED&#xff0c;当我们不使用点阵屏的时候也可以单独使用74HC595&#xff0c;这8个LED可以用来测试7…...

Git分支常用指令

目录 1 git branch 2 git branch xx 3 git checkout xx 4 git checkout -b xx 5 git branch -d xx 6 git branch -D xx 7 git merge xx(含快进模式和冲突解决的讲解) 注意git-log: 1 git branch 作用&#xff1a;查看分支 示例&#xff1a; 2 git branch xx 作用&a…...

3.3 Binance_interface APP U本位合约行情-实时行情

Binance_interface APP U本位合约行情-实时行情 Github地址PyTed量化交易研究院 量化交易研究群(VX) py_ted目录 Binance_interface APP U本位合约行情-实时行情1. APP U本位合约行情-实时行情函数总览2. 模型实例化3. 获取一个产品的最优挂单 get_bookTicker4. 获取全部产品…...

机器学习——流形学习

流形学习是一种在机器学习领域中用于理解和分析数据的技术。它的核心思想是&#xff0c;尽管我们通常将数据表示为高维空间中的向量&#xff0c;但实际上数据可能具有较低维度的内在结构&#xff0c;这种结构被称为流形。流形学习的目标是发现并利用数据的这种潜在结构&#xf…...

离线数仓(一)【数仓概念、需求架构】

前言 今天开始学习数仓的内容&#xff0c;之前花费一年半的时间已经学完了 Hadoop、Hive、Zookeeper、Spark、HBase、Flume、Sqoop、Kafka、Flink 等基础组件。把学过的内容用到实践这是最重要的&#xff0c;相信会有很大的收获。 1、数据仓库概念 1.1、概念 数据仓库&#x…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

回溯算法学习

一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

认识CMake并使用CMake构建自己的第一个项目

1.CMake的作用和优势 跨平台支持&#xff1a;CMake支持多种操作系统和编译器&#xff0c;使用同一份构建配置可以在不同的环境中使用 简化配置&#xff1a;通过CMakeLists.txt文件&#xff0c;用户可以定义项目结构、依赖项、编译选项等&#xff0c;无需手动编写复杂的构建脚本…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)

引言 在嵌入式系统中&#xff0c;用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例&#xff0c;介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单&#xff0c;执行相应操作&#xff0c;并提供平滑的滚动动画效果。 本文设计了一个…...

Java并发编程实战 Day 11:并发设计模式

【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天&#xff0c;今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案&#xff0c;它们不仅提供了优雅的设计思路&#xff0c;还能显著提升系统的性能…...

C++11 constexpr和字面类型:从入门到精通

文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…...

用js实现常见排序算法

以下是几种常见排序算法的 JS实现&#xff0c;包括选择排序、冒泡排序、插入排序、快速排序和归并排序&#xff0c;以及每种算法的特点和复杂度分析 1. 选择排序&#xff08;Selection Sort&#xff09; 核心思想&#xff1a;每次从未排序部分选择最小元素&#xff0c;与未排…...

Linux入门(十五)安装java安装tomcat安装dotnet安装mysql

安装java yum install java-17-openjdk-devel查找安装地址 update-alternatives --config java设置环境变量 vi /etc/profile #在文档后面追加 JAVA_HOME"通过查找安装地址命令显示的路径" #注意一定要加$PATH不然路径就只剩下新加的路径了&#xff0c;系统很多命…...