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

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

指令解析

这章内容进一解析更多的指令,此外将解析指令的过程拆分为一个单独的类,采用表格驱动的方式,将数据和逻辑分离,降低了 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++ 新特性并完善工具类。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/680675.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32 寄存器操作 GPIO 与中断

一、如何使用stm32寄存器点灯&#xff1f; 1.1 寄存器映射表 寄存器本质就是一个开关&#xff0c;当我们把芯片寄存器配置指定的状态时即可使用芯片的硬件能力。 寄存器映射表则是开关的地址说明。对于我们希望点亮 GPIO_B 的一个灯来说&#xff0c;需要关注以下的两个寄存器…

【C语言期末项目-通讯录】-升级可动态申请内存版(手把手详细过程,期末评分A+的项目,答辩辅助神博文,建议三连点赞收藏)

目录 ​编辑 前言&#xff1a; 1.项目功能需求分析 2.文件框架说明 3.程序主框架实现 4.创建联系人结构体类型和通讯录结构体类型 4.1创建通讯录 5.程序功能实现--封装功能函数实现不同功能 5.1通讯录初始化 5.2增加联系人 5.3显示所有联系人的信息 5.4删除指定…

【Java EE初阶十二】网络编程TCP/IP协议(二)

1. 关于TCP 1.1 TCP 的socket api tcp的socket api和U大片的socket api差异很大&#xff0c;但是和前面所讲的文件操作很密切的联系 下面主要讲解两个关键的类&#xff1a; 1、ServerSocket&#xff1a;给服务器使用的类&#xff0c;使用这个类来绑定端口号 2、Socket&#xf…

【图论】【树形dp】【深度优先搜索】2538. 最大价值和与最小价值和的差值

作者推荐 【深度优先搜索】【树】【图论】2973. 树中每个节点放置的金币数目 本文涉及知识点 深度优先搜索 LeetCode2538. 最大价值和与最小价值和的差值 给你一个 n 个节点的无向无根图&#xff0c;节点编号为 0 到 n - 1 。给你一个整数 n 和一个长度为 n - 1 的二维整数…

GEE土地分类——如何利用多年的ESRI_Global-LULC_10m将研究区的指定区域重分类分为两类数据(将多类土地分类转化为草地和非草地区域)

简介 本教程主要的目的是利用自己上传的多年土地分类应先过来实现指定区域的土地分类,而且只提取 ESRI_Global-LULC_10m ESRI_Global-LULC_10m数据集是由ESRI(环境系统研究研究所)开发的一个全球级别的土地利用/土地覆盖数据集。该数据集使用10米的空间分辨率,并提供了详…

使用LoRA和QLoRA微调LLMs:数百次实验的见解

前言 翻译文章《Finetuning LLMs with LoRA and QLoRA: Insights from Hundreds of Experiments》原文地址因译者水平有限&#xff0c;翻译过程中有错误请在评论区指出 提要 LoRA是用于训练自定义LLM的最广泛使用、参数效率最高的微调技术之一。从使用QLoRA节省内存到选择最…

iTop-4412 裸机程序(十九)- 按键中断

目录 0.源码1.异常向量表1.1 原理1.2 异常种类1.3 ARMv7 规定的异常向量表 2. 中断2.1 iTop-4412 中使用的中断相关寄存器 上篇博文介绍了按键的轮询处理方式&#xff0c;本篇介绍按键的中断方式。 0.源码 GitHub&#xff1a;https://github.com/Kilento/4412NoOS 1.异常向量…

常见范数介绍

在线性代数中&#xff0c;符号 ( ||x|| ) 表示向量 ( x ) 的范数&#xff08;Norm&#xff09;。范数是一个将向量映射到非负值的函数&#xff0c;它衡量了向量的大小或长度。范数可以是多种类型&#xff0c;其中最常见的有&#xff1a; 欧几里得范数&#xff08;L2范数&#x…

力扣题目训练(8)

2024年2月1日力扣题目训练 2024年2月1日力扣题目训练404. 左叶子之和405. 数字转换为十六进制数409. 最长回文串116. 填充每个节点的下一个右侧节点指针120. 三角形最小路径和60. 排列序列 2024年2月1日力扣题目训练 2024年2月1日第八天编程训练&#xff0c;今天主要是进行一些…

如何制作一款3D FPS游戏

制作一款3D FPS游戏是一个复杂的过程&#xff0c;需要涵盖多个方面&#xff0c;包括游戏设计、游戏引擎选择、模型制作、音效制作、关卡设计等。下面是一个关于如何制作一款3D FPS游戏的超长文章。 游戏设计 首先&#xff0c;你需要确定游戏的整体设计和核心玩法。这包括游戏的…

人工智能能产生情绪吗?

此图片来源于网络 一、人情绪的本质是什么&#xff1f; 人的情绪本质是一个复杂的现象&#xff0c;涉及到生理、心理和社会的多个层面。以下是关于情绪本质的几种观点&#xff1a; 情绪的本质是生命能量的表达。情绪被认为是生命能量的一种体现&#xff0c;通过情绪的体验和…

web前端(第一天HTML)

前端是什么&#xff1f; 网页&#xff1f; 将数据以各种方式&#xff08;如&#xff1a;表格、饼图、柱状图等&#xff09;呈现给用户&#xff0c;我们就可以称之为前端。 做前端所需要的工具&#xff1f; notepad 、 editplus 、 notepad 、 vscode 、 webstorm 等&#x…

搜索+哈希/平衡树,LeetCode 987. 二叉树的垂序遍历

目录 一、题目 1、题目描述 2、接口描述 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 给你二叉树的根结点 root &#xff0c;请你设计算法计算二叉树的 垂序遍历 序列。 对位于 (row, col) 的每个结点而言&#xff0c;其左右子结…

Netty应用——通过WebSocket编程实现服务器和客户端长连接(十八)

Http协议是无状态的&#xff0c;浏览器和服务器间的请求响应一次&#xff0c;下一次会重新创建连接要求:实现基于webSocket的长连接的全双工的交互改变Http协议多次请求的约束&#xff0c;实现长连接了&#xff0c; 服务器可以发送消息给浏览器客户端浏览器和服务器端会相互感知…

【python】Fraction类详解及生成分数四则运算“试卷”

文章目录 一、前言实验所需的库终端指令Fraction类1. Fraction(numerator, denominator)&#xff1a;2. Fraction(numerator)3. Fraction()4. 分数作参数5. 负分数作参数6. 字符串作参数7. 小数作参数8. 科学计数法9. 浮点数作参数10. 浮点数精度问题11. Decimal对象作参数 二、…

新年快乐(水)

首先&#xff0c;祝大家新年快乐&#xff0c;龙年大吉! 然后我来大概解释一下春晚上两个魔术的原理吧。切&#xff0c;要不是我电脑坏了才不看春晚呢。 第一个魔术根数学没多大关系&#xff0c;主要就是换了一副牌&#xff0c;然后假洗。至于计时器吗&#xff0c;那基本就是后…

【Java程序设计】【C00264】基于Springboot的原创歌曲分享平台(有论文)

基于Springboot的原创歌曲分享平台&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的原创歌曲分享平台 本系统分为平台功能模块、管理员功能模块以及用户功能模块。 平台功能模块&#xff1a;在平台首页可以查看首…

Linux系统安全——iptables相关总结

在使用iptables时注意要先关闭firewalld&#xff08;systemctl stop firewalld.service&#xff09; 1.查看iptables规则 iptables -vnL 选项含义-v查看时显示更多详细信息-n所有字段以数字形式显示-L查看规则列表 例&#xff0c;拒绝来自192.168.241.22的源地址 直接丢弃 …

可视化工具:将多种数据格式转化为交互式图形展示的利器

引言 在数据驱动的时代&#xff0c;数据的分析和理解对于决策过程至关重要。然而&#xff0c;不同的数据格式和结构使得数据的解读变得复杂和困难。为了解决这个问题&#xff0c;一种强大的可视化工具应运而生。这个工具具有将多种数据格式&#xff08;包括JSON、YAML、XML、C…

Swift Combine 用 Future 来封装异步请求 从入门到精通十一

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三Swift Combine 发布者publisher的生命周期 从入门到精通四Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五Swift Com…