1. 引言
当前中国在信息产业上的重大短板是“缺心少魂”,即缺少市场占有率显著的自主处理器芯片和操作系统,当然情况也在逐步改善,如海思麒麟芯片、龙芯和一些国产操作系统等正在努力扩大市场份额。芯片应用涉及到生态链问题,芯片之上是操作系统,操作系统之上是应用软件,芯片和操作系统是基础,应用软件是对用户产生核心价值的部分,如同冰山浮出水面上的部分。没有应用软件支持,芯片和操作系统等底层部分也会自然消失。从资源投入来说,若芯片开发代价为1,操作系统开发代价为10,则各种应用软件的开发代价在100以上,投入极大。因此某类芯片要成功占领一部分市场,只有在长期积累下,在芯片及操作系统成熟可靠后,各种应用开发者主动适配该平台,逐渐形成软件生态,才能在市场中站稳脚跟。
发展芯片的途径之一是做兼容芯片,即搭上便车,不用考虑操作系统和应用环境的问题,但必须付出可观的专利授权费用才能使用,一旦不能获得授权,就面临卡脖子的问题。因此做兼容芯片能获得一部分市场,但难以成为市场的领头羊。众所周知,指令集是底层硬件和上层软件的接口,如果不用市场主流但费用高昂的指令集,而是采用免费或费用低廉的新指令集,则发展空间更大,但生态链的发展过程较为缓慢。要使新的指令集尽快应用起来,可以利用已有计算环境,先开发基于新指令集的虚拟机,绕过底层硬件开发上层软件,在验证可靠后,再反向完成底层硬件开发,这样初期投入较小,成本较低。只要指令集是稳定的,硬件和软件可独立迭代升级,为生态链的打造赢得更多时间。
在现有市场中,手机处理器和个人电脑处理器都已从32位进化到64位。32位寻址有4G内存限制,不能满足大型应用软件的需要,但64位寻址空间对个人计算设备来说稍嫌太大,实际应用的处理器并未采用,大多只支持40位或48位的寻址空间。而采用40位寻址,管理内存空间能达到1024 G = 1 T,从现有的大多数应用软件看,基本够用。因此,针对手机、平板、笔记本电脑以及一部分个人电脑,采用40位处理器芯片能够满足需要,很大程度上又可能比64位处理器更节省计算资源,如果开发出来,可望在市场中实际采用。
本文参考已有的开源处理器指令架构,如RISC-V指令集等 [1] [2] ,对40位处理器的指令集进行了初步设计,以期提供思路,抛砖引玉,并逐步完善,力争在该指令集上开发的芯片能够在市场中占领一席之地。没有历史包袱,使得新的指令集容易做到精简易用,便于简化硬件设计,提高运行速度,同时考虑发展需要,也保留了较好的扩展性,可根据市场要求进行扩充。该指令集架构使芯片设计从底层开始就与现有产品不同,虽然初期发展困难,但可以避开原有的专利限制,省却昂贵的授权费用,从而打造一条自主可控的产业链,为中国的信息产业发展提供坚实基础。
2. 主流的指令集架构
处理器应用领域可简单分为服务器领域、个人电脑领域和嵌入式领域,随着进一步发展,嵌入式领域也发展成几个不同的子领域。随着智能手机和手持设备的发展,移动领域发展为规模甚至超过个人电脑领域的独立领域。现今服务器领域和个人电脑领域由x86架构占据主导地位,移动领域主要由ARM架构所垄断,其次是实时嵌入式领域,仍然以ARM架构占据大多数市场份额,最后是深嵌入式领域,注重低功耗、低成本和高能效比,对软件生态的依赖性相对较低,目前仍然以ARM架构为主流 [1] 。
指令集架构可以理解为一个抽象层,该抽象层构成处理器底层硬件与运行于其上的软件之间的桥梁和接口,指令集架构是区分不同的CPU的主要标准。指令集架构主要分为复杂指令集CISC和精简指令集RISC,在早期,CISC曾经是主流,因为可以用较少的指令完成更多的操作,但是大量的特殊指令让CPU设计变得极为复杂,因此现代的指令集架构基本选择RISC架构 [3] 。
除了CISC与RISC之分,处理器指令集架构的位数也是一个重要区分。譬如32位架构的处理器,其通用寄存器的宽度为32位,能够寻址的范围为232 B,即4 GB的寻址空间。64位架构的处理器,其通用寄存器的宽度为64位,理论上能够寻址的范围为264 B,但目前使用的64位处理器大多只支持40位或48位的寻址空间。
x86架构是由Intel公司于1978年推出的8086处理器使用的复杂指令集,从最初的16位架构发展到如今的64位架构,逐渐成为个人电脑的标准处理器架构。Intel公司通过内部“微码化”方法,即先用解码器将指令翻译成为内部的简单指令,然后送给流水线执行,处理器内核已变成RISC架构。硬件解码器也带来额外的复杂度和面积开销,这是x86架构作为一种CISC架构不得不付出的代价。x86架构不仅在个人计算机领域取得统治性地位,迄今为止还在服务器市场取得主导地位。
SPARC指令集架构由Sun公司于1985年设计,是一种非常有代表性的高性能RISC架构,赢得了当时高端处理器市场的领先地位 [4] [5] 。其最大的特点是采用寄存器窗口,通过切换不同的寄存器组快速响应函数调用与返回,因此能够带来非常高的性能。后来,Sun公司被Oracle公司收购,在2017年9月,Oracle公司宣布放弃硬件业务,至此SPARC处理器可以说正式退出了历史舞台。
Power架构是IBM公司开发的一种RISC架构指令集,1994年IBM基于此推出PowerPC 604处理器,其强大的性能在当时处于全球领先地位。2013年IBM推出Power8处理器,处理器核心数量达到12个,2017年推出Power9处理器,核心数量为24个,并计划在2020年推出Power10处理器。
MIPS架构是一种简洁、优化的RISC架构,由斯坦福大学开发,曾经是最受欢迎的RISC架构,也为我国龙芯处理器所采用。但由于商业运作的原因,MIPS架构被同属RISC阵营的ARM架构后来居上,2013年MIPS被英国Imagination公司收购,2017年Imagination自身出现危机而寻求整体出售,使MIPS架构日渐式微。
Alpha架构是由DEC公司设计开发的一种64位的RISC架构指令集,我国的申威处理器采用并扩展使用。2001年,康柏收购DEC之后,逐步将64位服务器系列产品转移到intel的安腾处理器架构上。2004年,惠普收购康柏,从此Alpha架构逐渐淡出了人们的视野。
ARM架构由位于英国的处理器设计与软件公司ARM设计,2004年推出ARMv7内核架构时,将架构分为三类:面向性能密集型系统的Cortex-A内核,面向实时应用的Cortex-R内核,面向各类嵌入式应用的Cortex-M内核,其中,Cortex-A内核在移动领域占据统治地位,如苹果公司的iphone手机的处理器A7-A12都是ARM架构,华为公司的海思麒麟系列芯片也是属于ARM架构。在微控制器领域,主流厂商几乎都有使用ARM的Cortex-M内核的产品。
RISC-V架构是处理器架构新秀,由美国加州大学伯克利分校研究人员于2010年提出,是一种简单且开放免费的指令集架构 [1] [2] ,2016年,成立了RISC-V基金会,负责维护标准的RISC-V指令集手册与架构文档,并推动RISC-V架构的发展。印度将RISC-V架构指定为国家标准进行发展,但RISC-V架构的生态还不够强,远没有达到威胁x86和ARM架构的程度,还处于蓬勃发展阶段。
可以看到,处理器芯片竞争的核心是指令集架构的竞争,也是相关生态链的竞争。每一种指令集从提出到成熟,都是在市场竞争中发展壮大的。非市场主导的指令集架构日渐式微,这其中并不完全是技术的原因,而主要与商业运作息息相关。迫于竞争压力,各非主导地位的指令集架构已逐步开源。
3. 指令集设计原则
当前,PC领域和服务器领域主要由x86芯片主导,手机处理器芯片主要由ARM芯片主导,对应的操作系统也不同。从发展来看,手机芯片的性能越来越强,手机领域和PC领域的界限越来越模糊,两者都希望渗透到对方领域。如果有一种处理器能同时覆盖市场的大部分范围,则有可能在市场的间隙地带取得成功。40位处理器直接支持1T的寻址空间,能够满足现今大部分应用软件的需求,且不存在性能过剩和多余设计,够用且经济,正好可以满足市场需要。
考虑指令集架构的发展趋势,采用精简指令集RISC,好处是可以简化设计,提高可靠性,还可以节省芯片面积,提高运行速度。指令集的数目尽量减少,一条指令尽量多用,可以使用伪指令的形式来简化使用。对于一条指令语句来说,通常可由操作码和操作数两个域组成,而其中操作数又分无操作数、有一个操作数、有两个操作数等等。指令如果采用定长的形式,格式规整,译码简单,但是存储空间有所浪费,如果采用不定长形式,存储空间节省,但译码复杂。综合考虑,指令格式采用不定长形式,但限定于有限的几种,便于在译码速度和指令大小之间取得平衡。
寄存器的数目选择也很重要。考虑到遵循“越近越快”原则,CPU处理时应尽量在寄存器中进行,因此应该设计较大的寄存器空间。但寄存器空间过大,又会提高芯片造价,若不能完全利用,运行时则白白增加耗能,故寄存器的数量要适当,与编译器的发展水平有关。x86的8086处理器只有8个通用寄存器,进化到64位后,增加到16个通用寄存器,ARMv8 64位处理器则为31个,RISC-V处理器一般使用32个,SPARC V8架构处理器则可以拥有更多的寄存器 [6] [7] 。从发展看,寄存器的数量在逐渐增加,只要编译器能够挖掘出潜力,设计较多数量的寄存器能够使计算局部化,提高程序运行速度的同时减少运行功耗。综合考虑,设置至少32个通用寄存器,占用的地址编码为5位,考虑到未来需要,采用1个字节来表示通用寄存器的地址编码,则最大可支持256个通用寄存器。
在程序运行时,对子程序的调用和返回是非常频繁的操作,这就要进行“保存现场”和“恢复现场”的操作,一般通过堆栈的方法来完成,但这涉及到内存操作,速度上有影响,能否将“现场”保留在寄存器中,达到快速调用程序的目的呢?SPARC处理器巧妙采用寄存器窗口的方法来实现。SPARC处理器可以有6~32个寄存器窗口,每个窗口包括8个全局寄存器和24个工作寄存器。全局寄存器用来存放整体量,所有窗口共用。工作寄存器逻辑上分为3组:8个输入寄存器,8个输出寄存器,8个局部寄存器。执行函数调用时通过改变当前窗口指针分配给该函数一个窗口。由于寄存器窗口的重叠,调用者的输出寄存器和被调用者的输入寄存器是同一组寄存器,因此省掉了寄存器之间的数据传送,极大地提高了速度。当函数调用嵌套深度超出窗口数而发生上溢时,处理器内部产生一个中断,由软件调整若干窗口到主存中去 [4] 。
对于主要考虑性能的处理器设计,可以参考SPARC处理器,实现寄存器窗口,这实际上扩展了通用寄存器的数量,也可以高效地向子程序传递参数,节省环境切换时的开销,免掉现场保护与恢复等操作。窗口大小仍然可以设置为32个通用寄存器,将高于此地址编号的设置为全局寄存器,便于在程序间传递更多的参数,或者为子程序增加可用的通用寄存器数量。可考虑采用堆栈形式,将所有窗口公用的全局寄存器的第一个位置设置为栈顶指针,每个子程序使用时,改变该指针,划分出独立的不受干扰的空间,返回时,恢复栈顶指针,释放空间即可。这样实际上在寄存器中实现了堆栈,比在内存中实现应该能够提高运行速度。如果全局寄存器也实现窗口特性,则更可以扩展寄存器空间,但一般情况下似无必要。对于不使用寄存器窗口的设计,也即是寄存器窗口只有一个的特殊情况下,子程序调用时只需要将当前寄存器内容保存到内存堆栈中去,返回时恢复即可。因此寄存器窗口采用与否,与底层的硬件实现有关,从指令上看是无缝兼容的,提高了指令集的灵活性和扩展性。
由以上讨论,可定义指令的基本格式为:针对40位处理器,通用寄存器的宽度为40位,操作码为8位,理论上容纳256条指令,通用指令在128编号以内,而128号及以上编号为可选的扩展指令和用户扩展指令。看起来操作码空间十分有限,但由于采用精简指令集,仍然能够满足要求,在特定的情况下,还可采用灵活的扩展方式。操作数的编码也为8位,最大可支持256个通用寄存器,要求实现的最少通用寄存器数目为32个。如果实现寄存器窗口,则编码的低地址(如32以内)为程序使用的局部寄存器,编码的高地址为全局寄存器。
为减少指令占用空间,采用变长指令,但以字节为单位,因此有单字节指令、双字节指令、3字节指令、4字节指令、5字节指令五种类型,但保留有更长字节指令的可能性。为减少转换,采用数据排列顺序与网络数据发送顺序一致,即采用大端模式。典型的指令顺序为1字节操作码,1字节目的寄存器地址,1字节源寄存器1地址,1字节源寄存器2地址,1字节可选操作(源寄存器3地址或长度指示等)。若无特别指定,操作码和每个操作数都为1个字节,但对于特殊情况,操作数可以占多个字节。
4. 基本整数指令
4.1. 单字节指令
该类指令没有操作数,只有操作码,因此只有一个字节,如表1所示。其中全0的操作码用于检查非法指令,这在错误使用数据区作为指令区时就容易出现。WFI指令为休眠指令,此时处理器主动休眠,等待外部中断唤醒,这便于降低功耗。NOP指令为无操作状态,相当于暂停,不进行任何操作,可用于等待,此时功耗较休眠状态高,好处是可立即恢复到工作状态。ECALL指令和ERET指令用于系统中断的调用和返回,所需的参数由系统的一些特殊寄存器给出。EBRK指令用于程序调试,便于主动产生一个调试异常。在正常的指令执行序列下,用中断和异常管理外部突发的事件。中断可用4位定义中断级别,4位定义中断类型,合成1个字节。异常也可称为软中断,也可用1个字节来管理级别和类型。
NXTW指令和BCKW指令用于寄存器窗口调度,窗口的范围用当前位置和末尾位置来标识。如果当前位置增加后与末尾位置相同,则发生上溢,而当前位置减少后与末尾位置相同,则发生下溢,之间的部分即为空闲窗口。通常情况下,NXTW指令将当前寄存器窗口的指针移动到下一个位置即可,如果产生上溢,则需要将已被占用的窗口内的寄存器值保存到内存后,才得到可用的寄存器窗口。BCKW指令将窗口的当前位置回退一个位置,如果发生下溢,则需要将内存中的寄存器值读回,才是正确的当前窗口。由于有内存作为后备,因此寄存器窗口并不需太多,用4位来表示即可,即最多实现16个寄存器窗口。每个窗口实现8个输入寄存器,8个输出寄存器,16个局部寄存器,相邻窗口的输入寄存器和输出寄存器重叠,因此实际每个窗口只占用24个寄存器。在特殊情况下,即只有一个寄存器窗口时,NXTW指令和BCKW指令相当于“保存现场”和“恢复现场”的操作,使用该指令可以简化子程序调用,当然也可自行实现寄存器值的保存和恢复操作,便于更灵活控制需要保存的寄存器数量。局部寄存器窗口的调度方法可同样用于全局寄存器,使得通用寄存器的地址编码虽只有1个字节,但能突破范围限制,这对于一些应用来说也许是必要的。
4.2. 双字节指令
该部分指令只需要两个字节,用来读取一些重要的系统状态,如表2所示。在处理器内部设置一个主频节拍计数器,用于高精度计时,处理器一个频率周期计数一次。用一个40位特殊寄存器来保存节拍计数,在计数一定间隔时产生一个中断,这时可以检查系统中的等待任务,RDCYC指令可读取该计数。另外为准确计时的需要,设置时钟计数器,频率比主频低,用一个40位寄存器来保存计数,每隔一定计数产生一次中断,可处理一些低优先级的中断或异常,RDTIME指令读取该时钟计数。为节省硬件成本,特殊情况下可由主频计数器中断来设置时钟计数器。另外设置程序执行指令计数器,为40位特殊寄存器,可以用RDPC读取当前的指令计数器值。
设置40位的堆栈栈顶指针特殊寄存器,该指针指向内存地址,读取时用指令RDSTK,设置时用指令WRSTK,便于切换不同的堆栈空间。移动栈顶指针用指令INCSTK,一次移动的量为1个字节,为有符号立即数,因此分配或释放的最大值为127字节。使用时可以灵活采用向下堆栈或向上堆栈,分配空间后,采用内存读写指令在内存堆栈和寄存器之间移动数据。
由于递增和递减操作经常使用,故定义了加1指令INC和减1指令DEC。
4.3. 载入存储指令
载入存储指令负责寄存器与内存之间的数据交换,有单字(五字节)、双字(十字节)和多字节的读写指令,如表3所示。要求对某个处理核心,读写顺序严格按照指令的先后顺序执行,即使用按序一致性模型,这样无需使用存储器屏障指令。多核心的并行调度采用特定的存储器读写原子指令来完成。
4.4. 整数加减指令
基本整数指令包含40位整数的加减和复制操作,如表4所示。对40位无符号数,表示范围为0~240−1,即0~1099511627775,对40位有符号数,最高位为符号位,表示范围为−549755813887~549755813887。实现时,根据整数的最高位来判断正负,负整数用补码的形式表示,因此无符号整数和有符号整数的加法形式一样,不需要区分。结合填充立即数指令LUI和立即数加法指令ADDI,可以对40位的整数立即数进行处理。对于程序中循环变量一次增减大于1的情况,用自加操作INCB处理,立即数imm为8位,可正可负,范围为−127~127。利用补码,立即数减法可转化为加法进行。

Table 4. Integer addition and subtraction instructions
表4. 整数加减指令
4.5. 整数乘除指令
整数的乘除运算指令包含了相乘、相除、求余数等功能,如表5所示。如果希望得到两个40位整数相乘的完整80位结果,可以融合两条指令连续执行“MUL:rd, rs1, rs2;MULH[U]: rd+1, rs1, rs2”,此处将80位结果的小端部分放在前,大端部分放在后,便于40位整数和80位整数的相互转换,无需特别的转换指令。40位整数转换为80位整数时,将后续的40位扩充为大端部分,无符号数和有符号数的正数填充全零,有符号数的负数填充全1 (补码表示)。80位整数转换为40位整数时,必须满足相反条件,抛弃大端部分即可。

Table 5. Integer multiplication and division instructions
表5. 整数乘除指令
4.6. 逻辑操作指令
逻辑操作指令包含了与、或、异或操作,如表6所示,可以是寄存器与寄存器操作,也可以是寄存器和立即数操作,立即数为2个字节长度。

Table 6. Logic operation instructions
表6. 逻辑操作指令
4.7. 移位操作指令
移位操作指令包含了左移、右移、算术右移操作,如表7所示,可以是寄存器与寄存器操作,也可以是寄存器和立即数操作,立即数为1个字节长度。

Table 7. Shift operation instructions
表7. 移位操作指令
4.8. 比较操作指令
比较操作指令包含小于、小于等于、等于比较操作,如表8所示,可以是寄存器与寄存器比较,也可以是寄存器与立即数比较,立即数为2个字节长度。

Table 8. Comparison operation instructions
表8. 比较操作指令
4.9. 控制转移指令
控制转移指令用于程序的跳转操作,如表9所示。JAL指令用于近跳转,跳转到与当前位置偏移24位立即数地址,该立即数为有符号整数,因此能跳转到前后8M的地址区间。更远的跳转,可使用JALR指令,跳转偏移为寄存器与立即数的和。在近跳转和远跳转时,将当前指令地址保存到寄存器,这样在返回时,跳转到该地址递增5的位置,即下一指令地址,就结束子程序调用,继续执行原来的指令流。
在程序的内部跳转可以使用BLT、BLE、BEQ、BNE指令,在进行比较操作后进行跳转,通常无需返回,故不用保存当前指令地址,跳转偏移立即数为16位有符号数,因此偏移量范围为±32 KB。

Table 9. Control transfer instructions
表9. 控制转移指令
4.10. 控制状态设置指令
在处理器架构中,需要定义一些控制和状态寄存器,用于配置或记录一些运行的状态,称之为CSR寄存器,使用专有的地址编码空间,便于统一管理。如架构的版本信息、寄存器窗口的控制信息、整数和浮点数的运算状态、中断的控制状态等。CSR寄存器的访问采用CSR指令统一管理,如表10所示,对于一些常用的操作指令还可用伪指令来简化使用。CSR寄存器的编码仍然采用8位,即一个字节,它们与通用寄存器的编码空间互相独立,互不干扰。每个CSR寄存器编码对应1个字节宽度,因此最大管理空间是256个字节,并不充裕,需要节省使用。用其它指令能读取的内容,如前述的时钟计数等可不必纳入编码空间,以保留足够的扩充能力。CSR寄存器可以统一设置(CSRRW指令),也可以按位设置(CSRRS指令)或者按位清除(CSRRC)。该三条指令允许从初始地址开始,进行完整的40位值读取和设置。

Table 10. Control state setting instructions
表10. 控制状态设置指令
CSR寄存器的设置还可以直接利用立即数来处理,立即数为8位,一个字节,可以统一设置(CSRRWI指令),也可以按位设置(CSRRSI指令)或者按位清除(CSRRCI)。此时只使用通用寄存器的低8位和CSR寄存器进行数据交换,高位默认扩展补0。如果立即数的值为0,则不会进行写操作,只进行读取操作。如果结果寄存器的索引值为0,则不会进行读操作。
4.11. 原子存储操作指令
处理器对速度的追求是无止境的,超线程技术能尽量挖掘处理器内部执行部件的能力,也就是用同一套执行机构,不同的寄存器来达到程序快速切换的能力,避免了保存现场和恢复现场的费时操作,这与采用寄存器窗口的思路是一致的。在单个处理器核心充分挖掘达到速度瓶颈后,一般通过多个核心来提高处理能力。但核心之间的并行性必须要协调,由于多个核心一般围绕同一份数据进行处理,因此只要保证数据的一致性,就容易达成多核心的并行性,故多核心的并行性一般通过内存变量的同步来处理。在核心数少时,某个核心读写时可以通知其他核心暂停存储器操作,保证读写共享变量的唯一性。在核心数多时,通过总线来控制更简单一些。此时某个核心锁定总线,只有当前核心能进行读写,其它核心则不能操作存储器,保证了共享变量的正确读写。
原子性存储操作指令为多核心执行读–算–写操作时对共享变量进行同步,即在对存储器执行操作时,不被其它核心干扰。在读出和写回的间隙,存储器的该地址不能被其它的进程或线程访问,这保证了共享变量的操作唯一性,然后借此达成多个核心的同步和并行。这些原子计算存储指令原子性地从rs1地址读取数据值、将这个值写入寄存器rd、在这个值和rs2的原始值上施加一个二进制操作、然后把结果保存到rs1地址的存储器中。支持的操作包括原子比较交换、交换、整数加、逻辑操作等,如表11所示。
这些指令在执行时先尝试获取存储总线,获取后进行读取并计算,存储结果后释放存储总线。如果不能获取总线,一般需要等待,这阻塞了指令执行,对计算速度有所影响。其中原子比较交换指令AMCMPS无需等待,可用来实现加锁,一般用1表示加锁状态,用0表示解锁状态。如果发现为0的解锁状态,则进行加锁,否则退出,可等待片刻后再尝试加锁。解锁时更为简单,只需将解锁信号传递即可,无需比较,可采用原子交换指令AMSWAP。为并行计算的简单性,无需加锁解锁的繁琐过程,可采用原子加、与、或、异或、取最小值指令,这些指令能保证核心操作时的唯一性。

Table 11. Atomic storage operation instructions
表11. 原子存储操作指令
5. 单精度浮点操作指令
浮点数操作也是通用处理器实现中必不可少的内容,由于默认的字长是40位,故此处的单精度不是32位的单精度浮点,而是40位单精度浮点数。浮点数格式可参考国际标准ANSI/IEEE-754,为了与双精度浮点数无缝转换,综合考虑,可以用1位存符号,11位存指数,剩下28位存尾数。因此指数的范围为−1022~1023,能表示的大小范围为
,尾数的精度可达到
,即有8位有效数字。对常用工程计算来说,假设损失一半精度,最后结果仍有4位有效数字,基本能满足需要。
5.1. 单精度浮点数运算指令
单精度浮点数运算指令包含了40位浮点数加、减、乘、除、平方根指令,如表12所示,由于很多工程计算中有求乘积后立即相加或相减的运算,故设置了浮点乘加指令和浮点乘减指令。

Table 12. Single-precision floating-point number operation instructions
表12. 单精度浮点数运算指令
5.2. 单精度浮点数比较操作指令
40位浮点数比较操作指令包含判断浮点数类型、小于、小于等于、等于、最小和最大指令,如表13所示,其中浮点数类型包含上溢、下溢等类型,与浮点数定义格式有关,可参考RISC-V开源指令集中的定义 [1] 。

Table 13. Single-precision floating-point number comparison operation instructions
表13. 单精度浮点数比较操作指令
5.3. 浮点数转换和符号操作指令
浮点数转换指令包含40位浮点数和40位有符号整数和无符号整数之间的转换操作指令,符号注入指令包含符号位的提取、取反和异或操作,如表14所示。由于符号位放置在最高位,操作位置相同,故也适用于双精度浮点数操作。

Table 14. Floating-point number conversion operation and sign injection instructions
表14. 浮点数转换和符号注入指令
指令编号73、74、75、76、77、78、79为预留的4字节指令。
6. 扩展指令
对于高精度计算来说,40位整数和40位浮点数可能不足,故可定义80位双精度的整数和浮点操作。这部分指令不是所有40位芯片都必须实现的,故作为可选的扩展指令。扩展指令从128号指令开始,即操作码的最高位为1,除了此处定义的双精度操作指令,预留的指令空间较多,需要实现特殊功能的芯片可以方便地扩展指令集。
实现双精度计算的简单方法是将通用寄存器的位宽加倍,从40位扩展到80位,原有的基本指令只使用其中40位,故不受影响,这个方法的缺点是要增加硬件成本。另一个较为经济的做法是通用寄存器的位宽不变,只是将相邻的两个寄存器合并起来达到80位宽度,这个方法能降低硬件制造成本,缺点是双精度操作所用的通用寄存器数量减少。综合考虑,如果双精度操作使用不多,可维持原有寄存器设计,以尽量经济,如果双精度操作十分密集,则应将通用寄存器宽度加倍,以获得更好性能。
寄存器和存储器之间的载入和存储操作采用已有指令,即载入十字节的指令LT: rd, rs1,imm,和存储十字节的指令ST: rs2, rs1,imm,转移的数据可以是整数,也可以是浮点数,因为占用的空间大小一样,为方便使用,也可改写为伪指令。
80位双精度浮点数参考前述的40位浮点数格式,可以用1位存符号,11位存指数,剩下68位存尾数。指数的范围为−1022~1023,能表示的大小范围为
,尾数的精度可达到
,即有20位有效数字,对高精度计算十分有用。
6.1. 双精度浮点数运算指令
双精度浮点数运算指令包含了80位浮点数加、减、乘、除、平方根指令,如表15所示,由于很多工程计算中有求乘积后立即相加或相减的运算,故设置双精度浮点乘加指令和双精度浮点乘减指令。

Table 15. Double-precision floating-point number operation instructions
表15. 双精度浮点数运算指令
6.2. 双精度浮点数比较操作指令
双精度浮点数比较操作指令包含判断双精度浮点数类型、小于、小于等于、等于、最小和最大指令,如表16所示,其中双精度浮点数类型与单精度浮点数类型的判断是相似的。

Table 16. Double-precision floating-point number comparison operation instructions
表16. 双精度浮点数比较操作指令
6.3. 双精度浮点数转换指令
双精度浮点数转换指令包含80位浮点数和80位有符号长整数和无符号长整数之间的转换操作指令,如表17所示。不提供40位整数和80位双精度浮点数之间的直接转换操作,因为精度损失太大。要实现40位整数和双精度浮点之间的转换,可以先将40位整数扩展为80位整数,再转换为双精度浮点数。由于单精度浮点数和双精度浮点数的指数位宽度一样,所以它们之间的转换是无缝的,即取前40位为单精度浮点数,取完整的80位为双精度浮点数。双精度浮点数的符号操作与单精度浮点数相同,都是对最高位即符号位的处理,故无需另外指令。

Table 17. Double-precision floating-point number conversion operation instructions
表17. 双精度浮点数转换操作指令
6.4. 长整数运算指令
长整数为80位二进制整数,分为有符号和无符号两类。对80位无符号整数,表示范围为0~280−1,对80位有符号数,最高位区分符号,表示范围为−279+1~279−1。长整数的运算指令包含加减、乘除、求余等功能,如表18所示。如果希望得到两个80位整数相乘的完整160位结果,可以融合两条指令连续执行“LMUL: rd, rs1, rs2; LMULH[U]: rd+2, rs1, rs2”,此处将160位结果的小端部分放在前,大端部分放在后,便于和80位整数无缝转换。另提供复制80位内容的操作指令LMOV和长整数自加操作指令LINCB。

Table 18. Long integer operation instructions
表18. 长整数运算指令
6.5. 长整数原子存储操作指令
长整数原子存储指令对80位的长整数进行原子性操作,便于多个核心并行处理长整数,支持的操作包括原子比较交换、交换、加减和逻辑操作,如表19所示。

Table 19. Long integer atomic storage operation instructions
表19. 长整数原子存储操作指令
7. 结语
在手机、平板、笔记本电脑等应用领域,x86芯片和ARM芯片正在互相渗透,而40位处理器能够满足主流应用要求且经济性可能更好,可望在市场缝隙中获得一线生机。借鉴并杂合各类处理器指令集的优点,如x86、ARM、RISC-V、SPARC等,本文尝试对40位处理器的指令集架构进行了初步设计,以便于抛砖引玉,为中国芯片产业发展提供一些思路。定义的40位处理器指令集是以字节为单位的可变长度精简指令集,解码规则简单,便于硬件实现,可用性和扩展性好。该指令集定义了从0号到99号共100条基本指令,其中已定义指令93条,预留7条四字节指令,另提供为双精度计算服务的扩展指令34条,编号从128号到161号,全部为已定义指令。总的已定义指令共127条,还留有较大的指令扩充空间。为推广使用,该指令集免费开源,不妨命名为熊猫处理器精简指令集,以突出中国特色。
指令集作为硬件和软件之间的接口,既是相对稳定的,又不是一成不变的,必须适应软硬件的发展而扩充。本文提出的40位处理器的指令集没有历史包袱,便于全新设计,但能否满足信息处理的现代需求和未来发展,还需要实践来回答。该指令集架构设计为了适应硬件体系的实现要求,还有许多细节问题尚待讨论和细化,如寄存器窗口如何设计更为优化,中断系统的定义和控制,甚至浮点数格式的定义和控制等等,都可以有不同实现方案。要使得定义的指令集能够实际运作,还需要各方有识之士共同努力,将这一叶新苗培育成长,扎根于广阔的芯片市场,为中国和世界的信息产业服务。