RISC-V架构P扩展指令集的研究与实现(一)

本专栏旨在介绍RISC-V指令集架构中P指令集扩展的相关内容,包括指令集的主要特点、应用场景,译码、执行的实现方式,以及如何提升CPU内核的性能,并以阿里巴巴旗下半导体公司平头哥推出的玄铁E906开源核为例,讲解将已实现的P指令集集成到CPU上的步骤与细节。

一、指令集简述

要想控制计算机硬件,就需要使用它们的语言。而计算机语言中的“单词”称为指令,其“词汇表”称为指令集,或者指令系统。简单来说,指令是指示计算机硬件(主要为CPU)完成某种功能的命令,指令集是一簇指令的集合,包括指令格式、寻址方式和数据形式。CPU的指令集反映了该CPU的全部功能,拥有不同指令集的CPU能够实现的功能不同,对于同一功能的实现所消耗的资源和所需要的时间也不同。例如,一个含有加法指令但不含乘法指令的CPU依然能够实现乘法功能,只需将乘法转换成若干个加法,使用若干条加法指令即可实现,但花费的时间一定比相同主频并含有乘法指令的CPU所花费的多,效率更低。

既然指令是计算机的语言,而现代计算机只能够识别0和1,因此指令最直接的形式就是若干个0和1组成的位串。例如

00000000001100010000000010110011

是RISC-V架构32位基础整数指令集中的一条加法指令。最早的程序员是直接使用上述二进制指令与计算机进行通信的,这就需要程序员们记住这些杂乱无章、规律性弱的二进制位串所对应的含义及功能。可想而知,这是一项极其困难与乏味的工作,于是人们发明了助记符,以符合人类的思维方式,这种助记符形式的指令称为汇编指令。进一步的,设计人员开发了一种软件,可以将汇编指令自动翻译成对应的二进制指令供计算机识别,这种软件即为汇编器。例如,上述那条加法指令可写成

add  x1, x2, x3

该指令告诉计算机将寄存器x2和x3中的数据相加,并将运算结果存放在x1寄存器中。由上可见,汇编指令显然比二进制形式的指令更具有可读性,编写程序更容易。

虽然这是一个非常大的突破,但还存在一个比较大的问题,那就是程序相对于计算机的独立性不高。换句话说,程序非常依赖于支持其运行的CPU,不同指令集架构的CPU,其汇编指令的语法不同,可能会导致在某个CPU上能够运行的程序无法在其他架构的CPU上运行,这是人们不愿意看到的,于是高级编程语言应运而生,像我们比较熟悉的C、Java、Python等。高级编程语言不仅可以使程序员用更自然的思维方式来思考,还一定程度上消除了程序对于计算机的依赖性,极大地提高了人们的编程效率。与此同时,人们还配套地开发出一种称为编译器的软件,用来将高级编程语言自动地翻译成汇编语言(有的编译器能够直接将高级语言翻译成二进制指令),这个过程称为编译。这个翻译过程是相当复杂的,在这里不作深入介绍,本频道其他专栏有关于编译的详细讲解,感兴趣的朋友可移步查看。

二、RISC-V的模块化指令集

RISC-V指令集架构除了开源之外,最突出的特点当属模块化设计。RISC-V作为一款基于精简指令集计算原理建立的指令集架构,不仅短小精悍,而且其不同的部分能够以模块化的方式组织在一起,从而通过一套统一的架构满足各种不同的应用场景,使之能够适应包括从低功耗的嵌入式控制器,到高性能计算机等各种规模的处理器。我们设想一种场景,假如你花了一笔钱买了一台手机,但发现手机百分之六十以上的功能你都是用不上的,这时你会不会觉得这笔钱花的很不划算?如果手机公司推出一款手机,手机的功能可以由用户自己选择,对应不同的价钱,用户选择的功能少,需要支付的钱就少,反之亦然,这样的手机是不是非常地受欢迎。RISC-V指令集架构就是这么一款“手机”,它允许用户自由地选择架构下的标准指令集与标准扩展指令集,从而完成自己定制化设备的需要,并实现对芯片面积地裁剪以及对功耗地把控(这里的芯片面积和功耗对应上述例子中的手机价格)。

上述提到RISC-V的指令集是使用模块化的方式进行组织,而每一个模块使用一个英文字母来表示,其中最基本也是唯一强制要求实现的子集是由字母I表示的基础整数指令集。基础指令集根据地址总线位宽分为RV32I、RV64I、RV128I、RV32E,其中RV32E是RV32I的子集,仅支持16个通用整数寄存器,适用于小面积、低功耗的嵌入式场景。其余的指令子集均为可选的扩展指令集,比较具有代表性的模块有M、A、F、D、C,其中M包含8条整数乘法与除法指令;A包含11条存储器原子操作指令与Load-Reserved/Store-Conditional指令;F包含26条单精度(32比特)浮点指令;D包含26条双精度(64比特)浮点指令,且必须支持F扩展指令;C包含46条压缩指令,指令长度为16位。除了上述指令子集之外,还有如B、H、J、L、N、P、Q、V、T等诸多扩展子集,目前这些扩展很多还在不断完善和定义中,尚未确定最终spec,在此不做详细介绍。

三、P扩展指令集简介

DSP(数字信号处理)已经成为现代电子系统中的一项重要技术,许多领域会使用DSP算法来解决其中的问题,包括但不限于音频与视频的编解码、医学成像、计算机视觉、嵌入式控制、人机交互以及图像处理等领域。基于上述社会生产需求,P扩展指令集应运而生,其能够有效地提高CPU的DSP算法处理能力。随着P指令集的增加,RISC-V架构CPU现在可以以更低的功耗和更高的性能运行这些不同的DSP算法。这里可能会有朋友存在疑问,DSP算法无非是一系列的加减乘运算,那么基础指令集I与扩展指令集M的组合也能够实现DSP算法,为什么还需要额外添加P指令集占用芯片面积呢?这其实涉及到一个效率的问题,上文也提到过一个例子,一个含有加法指令但不含乘法指令的CPU依然能够实现乘法功能,只需将乘法转换成若干个加法,使用若干条加法指令即可实现,但花费的时间一定比相同主频并含有乘法指令的CPU所花费的多,效率更低。这也解释了为什么在我们需要乘法运算的场景时,并且有了基础指令集I的情况下还需要添加扩展指令集M。同理,P扩展指令集中有许多的SIMD(单指令多数据)指令,一条指令可以同时处理多个数据,提供更小数据类型上的并行计算,这些都是指令集I与M无法实现的。

四、P扩展指令集的译码

P扩展指令集包含328条DSP指令,其中RV32/RV64均支持的DSP指令共有247条,仅RV64支持的指令有81条,如此庞大的一个指令子集,CPU是如何“分辨”不同的指令,并完成其对应的功能实现?RISC-V指令的执行通常包含5个步骤:

1.从存储器中取出指令;

2.读寄存器并译码指令;

3.执行操作或计算地址;

4.访问数据存储器中的操作数(如有必要);

5.将结果写入寄存器(如有必要)。

CPU从存储器(通常是内存)中取到将要执行的二进制指令后,会去判断所取到的是哪一条指令,同时提取该指令的一些信息并传递给执行级,这个过程称为指令的译码。在介绍译码的原理前,首先介绍一下RISC-V指令格式,除了扩展指令集C中的压缩指令是16位之外,其余的RISC-V指令长度均为32位,而所有32位指令的格式有R型、I型、S型、SB型、UJ型、U型6种,每一种类型的具体字段如下图所示:

RISC-V架构P扩展指令集的研究与实现(一)_第1张图片

上图中,opcode、funct3、funct7均为操作码字段,用于表示指令操作和指令格式;rd为目的操作数寄存器,用来存放操作结果;rs1为第一个源操作数寄存器;rs2为第二个源操作数寄存器;immediate为立即数字段,通常为补码值。我们发现rd、rs1、rs2、immediate字段会随着需要被处理的数据变化而变化,换句话说,即使是同一种指令在不同时间被取出时,这四个字段也有可能不同,因此其不能被用来区分指令(译码)。而opcode、funct3、funct7三个操作码字段与被处理的数据无关,可以作为指令的唯一标识,并且6种指令格式的三个操作码字段(如有)均在相同的位置(opcode字段位于[6:0],funct3字段位于[14:12],funct7字段位于[31:25]),这使得CPU只需要对指令的这三个区段共17位二进制数进行识别即可,也解释了为什么在S型、SB型指令中的立即数被拆成两截,放置在两个不连续的区段,目的是为了保证三个操作码字段在指令中的位置不变,来降低译码级的复杂度。

译码级除了识别出是哪一条指令之外,同时需要提取出该指令所包含的一些信息并传递给执行级,例如rd、rs1、rs2三个寄存器是否有效(S型、SB型不需要用到rd寄存器,UJ型、U型不需要用到rs1和rs2寄存器,I型不需要用到rs2寄存器),是否存在立即数以及立即数的位宽,部分指令还需要第三个源操作数寄存器rs3,原因是这部分指令需要rd寄存器中原有的数据作为输入,因此这些指令中rd既是目的操作数寄存器,又是第三个源操作数寄存器。此外,P指令集中立即数位宽有3比特、4比特、5比特三种,占据指令中rs2的区段,并且低位对齐。我们以一条指令为例,展示如何实现上述译码功能。例如

casez({decd_inst[31:25],decd_inst[14:12],decd_inst[6:0]})

17'b11100100001110111:

begin

decd_func[`FUNC_WIDTH-1:0] = `FUNC_SCLIP32;

decd_rs1_vld = 1'b1;

decd_rs2_vld = 1'b0;

decd_rs3_vld = 1'b0;

decd_rd_vld = 1'b1;

dsp_imm3_vld = 1'b0;

dsp_imm4_vld = 1'b0;

dsp_imm5_vld = 1'b1;

end

……

endcase

以上是对sclip32指令的译码示例,其中`FUNC_SCLIP32为宏定义,是给指令的一个标识,目的是让执行级得知指令类型并进行相应操作,由于该指令只用到了一个源操作数寄存器与目的操作数寄存器,且有立即数且位宽为5,因此上述代码段中rs1、rd、imm5的标识均有效,其余无效,至此sclip32指令所包含的信息均被提取出来,其他的指令译码同理。最后将上述信息以及各寄存器编号与立即数补码值传递给执行级即可。

以上是这篇帖子的全部内容,在该专栏下一篇中会介绍P扩展指令集的执行、集成以及验证工作。

你可能感兴趣的:(网络,算法,java)