19|RISC-V指令精讲(四):跳转指令实现与调试

这篇具有很好参考价值的文章主要介绍了19|RISC-V指令精讲(四):跳转指令实现与调试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

19|RISC-V指令精讲(四):跳转指令实现与调试

你好,我是LMOS。

前面我们学习了无条件跳转指令,但是在一些代码实现里,我们必须根据条件的判断状态进行跳转。比如高级语言中的if-else 语句,这是一个典型程序流程控制语句,它能根据条件状态执行不同的代码。这种语句落到指令集层,就需要有根据条件状态进行跳转的指令来支持,这类指令我们称为有条件跳转指令。

这节课,我们就来学习这些有条件跳转指令。在RISC-V指令集中,一共有6条有条件跳转指令,分别是beq、bne、blt、bltu、bge、bgeu。

这节课的配套代码,你可以从 这里 下载。

比较数据是否相等:beq和bne指令

我们首先来看看条件相等跳转和条件不等跳转指令,即beq指令和bne指令,它们的汇编代码书写形式如下所示:

beq rs1,rs2,imm
#beq 条件相等跳转指令
#rs1 源寄存器1
#rs2 源寄存器2
#imm 立即数
bne rs1,rs2,imm
#bne 条件不等跳转指令
#rs1 源寄存器1
#rs2 源寄存器2
#imm 立即数

上述代码中,rs1、rs2可以是任何通用寄存器,imm是立即数(也可称为偏移量),占用13位二进制编码。请注意, beq指令和bne指令没有目标寄存器,就不会回写结果。

我们用伪代码描述一下beq指令和bne指令完成的操作。

//beq
if(rs1 == rs2) pc = pc + 符号扩展(imm << 1)
//bne
if(rs1 != rs2) pc = pc + 符号扩展(imm << 1)

你可以这样理解这两个指令。在rs1、rs2寄存器的数据相等时,beq指令就会跳转到标号为imm的地方运行。而rs1、rs2寄存器的数据不相等时,bne指令就会跳转到imm标号处运行。

下面我们一起写代码来验证。在工程目录下,我们需要建立一个beq.S文件,在文件里用汇编写上beq_ins、bne_ins函数,代码如下所示:

.global beq_ins
beq_ins:
    beq a0,a1,imm_l1          #a0==a1,跳转到imm_l1地址处开始运行
    mv a0,zero                 #a0=0
    jr ra                       #函数返回
imm_l1:
    addi a0,zero,1            #a0=1
    jr ra                       #函数返回

.global bne_ins
bne_ins:
    bne a0,a1,imm_l2          #a0!=a1,跳转到imm_l2地址处开始运行
    mv a0,zero                 #a0=0
    jr ra                       #函数返回
imm_l2:
    addi a0,zero,1            #a0=1
    jr ra                       #函数返回

我们先看代码里的 beq_ins函数 完成了什么操作,如果a0和a1相等,则跳转到imm_l1处,将a0置1并返回,否则继续顺序执行,将a0置0并返回。然后,我们再看下 bne_ins函数 的操作,如果a0和a1不相等则跳转到imm_l2处,将a0置1并返回,否则继续顺序执行将a0置0并返回。

我们在main.c文件中声明一下这两个函数并调用它们,然后用VSCode打开工程目录,按下“F5”键来调试,情况如下所示:

beq指令,计算机,risc-v,java,开发语言

上图是执行“beq a0,a1,imm_l1”指令后的状态。由于a0、a1寄存器内容不相等,所以没有跳转到imm_l1处运行,而是继续顺序执行beq后面的下一条指令,最后返回到main函数中。

函数返回结果如下图所示:

beq指令,计算机,risc-v,java,开发语言

从图里我们能看到,首先会由main函数调用beq_ins函数,然后调用printf输出返回的结果,在终端中的输出为0。这个结果在我们的预料之中,也验证了beq指令的效果和我们之前描述的一致。

下面我们继续调试,就会进入bne_ins函数中,如下所示:

beq指令,计算机,risc-v,java,开发语言

上图中是执行“bne a0,a1,imm_l2”指令之后的状态。同样因为a0、a1寄存器内容不相等,而bne指令是不相等就跳转。这时程序会直接跳转到imm_l2处运行,执行addi a0,zero,1指令,将a0寄存器置为1后,返回到main函数中,如下所示:

beq指令,计算机,risc-v,java,开发语言

上图中第二个printf函数打印出bne_ins函数返回的结果,输出为1。bne指令会因为数据相等而跳转,将a0寄存器置为1,导致返回值为1,这个结果是正确的。

经过上面的调试验证,我们不难发现: 其实bne是beq的相反操作,作为一对指令搭配使用,完成相等和不相等的流程控制。

小于则跳转:blt和bltu指令

有了bqe、bne有条件跳转指令后,就能实现C语言 ==和 != 的比较运算符的功能。但这还不够,除了比较数据的相等和不等,我们还希望实现比较数据的大小这个功能。

这就要说到小于则跳转的指令,即blt指令与bltu指令,bltu指令是blt的无符号数版本。它们的汇编代码书写形式如下:

blt rs1,rs2,imm
#blt 条件小于跳转指令
#rs1 源寄存器1
#rs2 源寄存器2
#imm 立即数
bltu rs1,rs2,imm
#bltu 无符号数条件小于跳转指令
#rs1 源寄存器1
#rs2 源寄存器2
#imm 立即数

和bqe、bne指令一样,上述代码中rs1、rs2可以是任何通用寄存器,imm是立即数(也可称为偏移量),占用13位二进制编码,它们同样没有目标寄存器,不会回写结果。

blt指令和bltu指令所完成的操作,可以用后面的伪代码描述:

//blt
if(rs1 < rs2) pc = pc + 符号扩展(imm << 1)
//bltu
if((无符号)rs1 < (无符号)rs2) pc = pc + 符号扩展(imm << 1)

你可以这样理解这两个指令。当rs1小于rs2时且rs1、rs2中为有符号数据,blt指令就会跳转到imm标号处运行。而当rs1小于rs2时且rs1、rs2中为无符号数据,bltu指令就会跳转到imm标号处运行。

我们同样通过写代码验证一下,加深理解。在beq.S文件中,我们用汇编写上blt_ins、bltu_ins函数,代码如下所示:

.global blt_ins
blt_ins:
    blt a0,a1,imm_l3          #a0<a1,跳转到imm_l3地址处开始运行
    mv a0,zero                 #a0=0
    jr ra                       #函数返回
imm_l3:
    addi a0,zero,1            #a0=1
    jr ra                       #函数返回

.global bltu_ins
bltu_ins:
    bltu a0,a1,imm_l4         #a0<a1,跳转到imm_l4地址处开始运行
    mv a0,zero                 #a0=0
    jr ra                       #函数返回
imm_l4:
    addi a0,zero,1            #a0=1
    jr ra                       #函数返回

blt_ins函数都做了什么呢?如果a0小于a1,则跳转到imm_l3处,将a0置1并返回,否则继续顺序执行将a0置0并返回。

接着我们来看bltu_ins函数的操作,如果a0中的无符号数小于a1中的无符号数,程序就会跳转到imm_l4处,将a0置1并返回,否则继续顺序执行,将a0置0并返回。

我们还是用VSCode打开工程目录,按下“F5”键来调试验证。下图是执行“blt a0,a1,imm_l3”指令之后的状态。

beq指令,计算机,risc-v,java,开发语言

由于a0中的有符号数小于a1中的有符号数,而blt指令是小于就跳转,这时程序会直接跳转到imm_l3处运行,执行addi a0,zero,1指令,将a0寄存器置为1后,返回到main函数中。返回结果如下所示:

beq指令,计算机,risc-v,java,开发语言

对照上图可以发现,main函数先调用了blt_ins函数,然后调用printf在终端上打印返回的结果,输出为1。这个结果同样跟我们预期的一样,也验证了blt指令的功能确实是小于则跳转。

我们再接再厉,继续调试,进入bltu_ins函数中,如下所示:

beq指令,计算机,risc-v,java,开发语言

图里的代码表示执行“bltu a0,a1,imm_l4”指令之后的状态。

由于bltu把a0、a1中的数据当成无符号数,所以a0的数据小于a1的数据,而bltu指令是小于就跳转,这时程序就会跳转到imm_l4处运行,执行addi a0,zero,1指令,将a0寄存器置为1后,就会返回到main函数中。

对应的跳转情况,你可以对照一下后面的截图:

beq指令,计算机,risc-v,java,开发语言

我们看到上图中调用bltu_ins函数传递的参数是3和-1,应该返回0才对。然而printf在终端上输出为1,这个结果是不是出乎你的意料呢?

我们来分析一下原因,没错,这是因为bltu_ins函数 会把两个参数都当成无符号数据,把-1当成无符号数是0xffffffff,远大于3。所以这里返回1,反而是bltu指令正确的运算结果。

大于等于则跳转:bge和bgeu指令

有了小于则跳转的指令,我们还是需要大于等于则跳转的指令,这样才可以在C语言中写出类似"a >= b"这种表达式。在RISC-V指令中,为我们提供了bge、bgeu指令,它们分别是有符号数大于等于则跳转的指令和无符号数大于等于则跳转的指令。

这是最后两条有条件跳转指令,它们的汇编代码形式如下:

bge rs1,rs2,imm
#bge 条件大于等于跳转指令
#rs1 源寄存器1
#rs2 源寄存器2
#imm 立即数
bgeu rs1,rs2,imm
#bgeu 无符号数条件大于等于跳转指令
#rs1 源寄存器1
#rs2 源寄存器2
#imm 立即数

代码规范和前面四条指令都相同,这里不再重复。

下面我们用伪代码描述一下bge、bgeu指令,如下所示:

//bge
if(rs1 >= rs2) pc = pc + 符号扩展(imm << 1)
//bgeu
if((无符号)rs1 >= (无符号)rs2) pc = pc + 符号扩展(imm << 1)

我们看完伪代码就能大致理解这两个指令的操作了。当rs1大于等于rs2,且rs1、rs2中为有符号数据时,bge指令就会跳转到imm标号处运行。而当rs1大于等于rs2时且rs1、rs2中为无符号数据,bgeu指令就会跳转到imm标号处运行。

我们继续在beq.S文件中用汇编写上bge_ins、bgeu_ins函数,进行调试验证,代码如下所示:

.global bge_ins
bge_ins:
    bge a0,a1,imm_l5          #a0>=a1,跳转到imm_l5地址处开始运行
    mv a0,zero                 #a0=0
    jr ra                       #函数返回
imm_l5:
    addi a0,zero,1            #a0=1
    jr ra                       #函数返回

.global bgeu_ins
bgeu_ins:
    bgeu a0,a1,imm_l6         #a0>=a1,跳转到imm_l6地址处开始运行
    mv a0,zero                 #a0=0
    jr ra                       #函数返回
imm_l6:
    addi a0,zero,1            #a0=1
    jr ra                       #函数返回

结合上面的代码,我们依次来看看bge_ins函数和bgeu_ins函数都做了什么。先看bge_ins函数,如果a0大于等于a1,则跳转到imm_l5处将a0置1并返回,否则就会继续顺序执行,将a0置0并返回。

而bgeu_ins函数也类似,如果a0中无符号数大于等于a1中的无符号数,则跳转到imm_l6处将a0置1并返回,否则继续顺序执行,将a0置0并返回。

我们用VSCode打开工程目录,按“F5”键调试,情况如下:beq指令,计算机,risc-v,java,开发语言

上图中是执行“bge a0,a1,imm_l5”指令之后的状态,由于a0中的有符号数,大于等于a1中的有符号数。而bge指令是大于等于就跳转,所以这时程序将会直接跳转到imm_l5处运行。执行addi a0,zero,1指令,将a0寄存器置为1后,就会返回到main函数中。

对照下图,可以看到调用bge_ins(4,4)函数后,之后就是调用printf,在终端上打印其返回结果,输出为1。

beq指令,计算机,risc-v,java,开发语言

因为两个数相等,所以返回1,这个结果正确,也验证了bge指令的功能确实是大于等于则跳转。

下面我们继续调试,就会进入bgeu_ins函数之中,如下所示:

beq指令,计算机,risc-v,java,开发语言

上图中是执行“bgeu a0,a1,imm_l6”指令之后的状态。

由于bgeu把a0、a1中的数据当成无符号数,所以a0的数据小于a1的数据。而bgeu指令是大于等于就跳转,这时程序就会就会顺序运行bgeu后面的指令“mv a0,zero”,将a0寄存器置为0后,返回到main函数中。

可以看到,意料外的结果再次出现了。你可能疑惑,下图里调用bgeu_ins函数传递的参数是3和-1,应该返回1才对,然而printf在终端上的输出却是0。

beq指令,计算机,risc-v,java,开发语言

出现这样的情况,跟前面bltu_ins函数情况类似,bgeu_ins函数会把两个参数都当成无符号数据,把-1当成无符号数是0xffffffff,3远小于0xffffffff,所以才会返回0。也就是说,图里的结果恰好验证了bgeu指令是正确的。

到这里,我们已经完成了对beq、bne、blt、bltu、bge、bgeu指令的调试,熟悉了它们的功能细节,现在我们继续一起看看beq_ins、bne_ins、blt_ins、bltu_ins、bge_ins、bgeu_ins函数的二进制数据。

沿用之前查看jal_ins、jalr_ins函数的方法,我们将main.elf文件反汇编成main.ins文件,然后打开这个文件,就会看到这些函数的二进制数据,如下所示:

beq指令,计算机,risc-v,java,开发语言

上图里的反汇编代码中使用了一些伪指令,它们的机器码以及对应的汇编语句、指令类型,我画了张表格来梳理。

beq指令,计算机,risc-v,java,开发语言

有了这些机器码数据,我们同样来拆分一下这些指令各位段的数据,在内存里它们是这样编码的:

beq指令,计算机,risc-v,java,开发语言

看完图片我们可以发现,bqe、bne、blt、bltu、bge、bgeu指令的操作码是相同的,区分指令的是 功能码

这些指令的立即数都是相同的,这和我们编写的代码有关,其数据正常组合起来是0b00000000110,这个二进制数据左移1位等于十六进制数据0xc。看看那些bxxx_ins函数代码,你就明白了,bxxx指令和imm_lxxx标号之间(包含标号)正好间隔3条,一条指令4字节,其 偏移量正好是12,pc+12正好落在imm_lxxx标号处的指令上。

重点回顾

这节课就要结束了,我们做个总结。

RISC-V指令集中的有条件跳转指令一共六条,它们分别是beq、bne、blt、bltu、bge、bgeu。

bne和beq指令,用于比较数据是否相等,它们是一对相反的指令操作,搭配使用就能完成相等和不相等的流程控制。blt、bltu是小于则跳转的指令,bge、bgeu是大于等于则跳转的指令,区别在于有无符号数。这六条跳转指令的共性是, 都会先比较两个源操作数,然后根据比较结果跳转到具体的偏移地址去运行。

这节课的要点我给你准备了导图,供你参考复习。

beq指令,计算机,risc-v,java,开发语言

到这里,我们用两节课的时间掌握了RISC-V指令集的八条跳转指令。正是这些“辛勤劳作”的指令,CPU才获得了顺序执行之外的新技能,进而让工程师在高级语言中,顺利实现了函数调用和流程控制与比较表达式。

下节课我们继续挑战访存指令,敬请期待。

思考题

我们发现在RISC-V指令集中,没有大于指令和小于等于指令,这是为什么呢?

别忘了在留言区记录收获,或者向我提问。如果觉得课程还不错,别忘了推荐给身边的朋友,跟他一起学习进步。文章来源地址https://www.toymoban.com/news/detail-813659.html

到了这里,关于19|RISC-V指令精讲(四):跳转指令实现与调试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【RISC-V 指令集】RISC-V 向量V扩展指令集介绍(六)- 向量内存一致性模型

    以下是《riscv-v-spec-1.0.pdf》文档的关键内容: 这是一份关于向量扩展的详细技术文档,内容覆盖了向量指令集的多个关键方面,如向量寄存器状态映射、向量指令格式、向量加载和存储操作、向量内存对齐约束、向量内存一致性模型、向量算术指令格式、向量整数和浮点算术

    2024年04月09日
    浏览(33)
  • RISC-V汇编指令

    写在最前面:这一篇是UC Berkeley的CS61C的笔记,根据我自己的理解进行学习记录,其中贴的一些图片来自于课程PPT。 了解汇编之前,我们需要先了解为什么需要汇编?以下是我的理解: 机器执行的命令都是些二进制的机器码,我们需要对机器进行编程需要记住这些机器码,这

    2024年02月15日
    浏览(41)
  • RISC-V 指令集介绍

            指令集从本质上可以分为复杂指令集(Complex Instruction Set Computer, CISC )和精简指令集(Reduced Instruction Set Computer, RISC )两种。复杂指令集的特点是能够在一条指令内完成很多事情。         指令架构(Instruction Set Architecture, 缩写为ISA),是软件和硬件的接口

    2024年02月14日
    浏览(29)
  • 【RISC-V】 li指令

    在RISC-V中有这样一条伪指令: 可以将任意的32位数据或者地址加载到指定的寄存器中 在 RV32I中,它扩展到 lui 和/或 addi li 何时扩展为 lui 或者 addi呢?又何时扩展为lui 和 addi呢? 我们观察lui 和 addi 的指令码即可得出结果 由上图可知, lui 加载的立即数为高20位, addi 加载的立即

    2023年04月08日
    浏览(30)
  • RISCV 6 RISC-V加载存储指令

    RISCV - 1 RV32/64G指令集清单 RISCV - 2 “Zicsr“, CSR Instructions RISCV -3 RV32I/RV64I基本整型指令集 RISCV - 4 ISA 扩展名命名约定 RISCV 5 RISC-V调用规则 RV32I is a load-store architecture, where only load and store instructions access memory and arithmetic instructions only operate on CPU registers. RV32I provides a 32-bit address s

    2024年02月10日
    浏览(29)
  • RISC-V基础之浮点指令(包含实例)

    RISC-V体系结构定义了可选的浮点扩展,分别称为RVF、RVD和RVQ,用于操作单精度、双精度和四倍精度的浮点数。RVF/D/Q定义了32个浮点寄存器,f0到f31,它们的宽度分别为32位、64位或128位。当一个处理器实现了多个浮点扩展时,它使用浮点寄存器的低位部分来执行低精度的指令。

    2024年02月13日
    浏览(33)
  • RISC-V(2)——特权级及特权指令集

    目录 1.  特权级 2. 控制和状态寄存器(CSR) 2.1 分类  2.2 分析               一个 RISC-V 硬件线程( hart )是运行在某个特权级上的,这个特权级被编码到一个或者多个 CSR (control and status register, 控制和状态寄存器 )中的一种模式。         当前定义了四种特权级

    2024年02月10日
    浏览(32)
  • 从CISC到RISC-V:揭开指令集的面纱

    对于大多数同学来说,计算机或智能手机的运行似乎就像魔法一样神奇。你可能知道它们内部都是一些复杂的电子组件,比如CPU、内存等等,但这些组件是如何协同工作,让我们可以在电脑上打字,或者在手机上看视频呢?实际上,这一切都归功于一种名为“指令集”的神秘

    2024年01月21日
    浏览(26)
  • RISC-V/ARM mcu OpenOCD 调试架构解析

    最近有使用到risc-v的单片机,所以了解了下risc-v单片机的编译与调试环境的搭建,面试时问到risc-v的调试可参看以下内容。 risc-v根据官方的推荐,调试器服务是选择OpenOCD,DopenOCD(开放片上调试器)是一个开源的片上调试器,旨在提供针对嵌入式设备的调试、系统编程和边界扫

    2024年04月28日
    浏览(30)
  • 基于硬件隔离增强risc-v调试安全2_安全提议

    安全之安全(security²)博客目录导读 2023 RISC-V中国峰会 安全相关议题汇总 说明:本文参考RISC-V 2023中国峰会如下议题,版权归原作者所有。

    2024年02月10日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包