Skip to content

【计组】RISC与CISC

Computer Architecture AI Assisted
Contents

一、ISA与RISC、CISC

ISA(instruction set architecture)是软件和硬件之间的接口:编译器把程序翻译成ISA规定的机器码,CPU按ISA执行这些机器码。

RISC和CISC是设计ISA的两种思路,区别在于一条指令做多少事:

  • CISC(complex instruction set computer):一条指令可以完成好几步操作,算术指令可以直接读写内存。例:x86、VAX、Motorola 68000、8051
  • RISC(reduced instruction set computer 精简指令集计算机):一条指令只做一件简单的事,格式统一,只有load、store访问内存。例:MIPS、Arm、RISC-V、PowerPC、AVR

CISC这个名字是RISC出现以后才起的,用来和RISC区分;在那之前的处理器都按这种思路设计。

reduced指的是每条指令的复杂度,不是指令的数量

早期的RISC指令确实少(Berkeley的RISC-I只有32条),但现代的Arm加上浮点、SIMD等扩展后指令也很多,仍然是RISC:每条指令依旧简单、只有load、store访存。

1. 为什么先有CISC

1970年代内存很贵,程序要尽量小。一条指令做的事越多,同一个程序需要的指令就越少,占的内存也越少。

2. RISC的出现

1980年前后,CISC的指令越加越多,硬件越来越复杂,几个项目各自重新设计了ISA:

  • IBM 801(1975~1980):John Cocke领导,一般认为是RISC思路的起点
  • Berkeley RISC:1982年做出RISC-I,RISC这个名字由David Patterson提出
  • Stanford MIPS:1981年开始,后来常用于RISC的教学

这些设计的共同点是指令格式统一、容易做成流水线。

3. RISC普及花了十年

RISC在1980年代初就已提出,过了大约十年才普及:

  • 缺软件:已有的程序都是为CISC编译的,换一种ISA就要重新编译、重新移植
  • x86要兼容老软件:Intel有足够的资源继续改进x86,不需要推倒重来
Important

ISA能不能普及,取决于软件生态:没有能跑的软件,再好的ISA也用不起来。

二、RISC与CISC的区别

同一个程序,用RISC和CISC写出来,指令条数、每条指令花的cycle数、代码占的空间都不一样。这一节逐项比较。

1. 指令数与CPI

a = a + x(a在内存里,x已经在寄存器里)
  • CISC(x86):1条指令
    • add [a], eax:一条指令内部读内存、做加法、再写回内存(eax里是x)
  • RISC(RISC-V):3条指令(a0里是a的地址,t1里是x)
    • lw t0, 0(a0):读a
    • add t0, t0, t1:做加法
    • sw t0, 0(a0):写回a

说明同一件事,CISC一条指令就做完读、加、写三步;RISC每条指令只做一步,要3条。

处理器执行一个程序的时间是:

执行时间 = 指令数 × CPI × 时钟周期

指令数是程序要执行多少条指令,CPI是平均每条指令花几个cycle。RISC和CISC从不同的项入手:

  • CISC减少指令数:一条指令做好几步,代价是每条指令要好几个cycle,CPI高
  • RISC降低CPI:每条指令简单,流水线每一级一个cycle就能做完,代价是指令数多;硬件简单,时钟周期也更容易做短
「RISC每条指令只要1个cycle」不准确

RISC的目标是CPI接近1:流水线每个cycle完成一条指令。单条指令仍要走完好几级流水线,load遇到cache miss要等,乘除法也要好几个cycle;superscalar处理器一个cycle能完成好几条指令,CPI还能小于1。

2. 指令长度与代码大小

  • RISC:固定长度。MIPS、RISC-V的基本指令都是4字节
    • 好取指、好译码:顺序执行时下一条指令一定从PC + 4开始,一次取进来的几条指令可以同时译码,流水线好做
    • 缺点:代码更大。指令数多,每条又都占4字节,程序要占更多内存和I-cache
  • CISC:可变长度。x86的一条指令1~15字节
    • 代码更小:常用的指令编码得短,一条又能做好几步,程序占的内存和I-cache更少
    • 缺点:要先译出这一条有多长,才知道下一条从哪开始,取指和译码更复杂

3. 复杂操作由谁拆开

同一个复杂操作(如上面的a = a + x),CISC和RISC在不同的时候、由不同的一方拆成简单的步骤:

  • CISC:运行时由硬件拆。复杂指令进到CPU后由硬件分步执行
    • 早期的CISC(如Motorola 68000)用microcode实现:CPU里有一块ROM,每条复杂指令对应一段microcode,执行时一步步读出来,控制读内存、做加法、写回内存
    • 代价是晶体管多:68000约三分之一的晶体管用在microcode上
  • RISC:编译时由编译器拆。编译器直接生成lw、add、sw三条指令,硬件只执行简单指令,控制信号由译码电路直接产生(hardwired),没有microcode这一层
RISC不需要专门的编译器

选用哪几条指令、把常用的变量留在寄存器里、排好指令的顺序,是所有编译器都要做的事,x86的编译器也一样。RISC-V用的就是标准的GCC和LLVM。

RISC更依赖的是编译器优化得好不好:指令数本来就多,寄存器分配、指令调度做得差,性能下降得更明显。

4. 寻址方式

寻址方式是指令里算内存地址的办法:

  • CISC的寻址方式多:x86一条指令就能按base + index × scale + offset算出地址,直接取到数组元素a[i]
  • RISC的寻址方式少:RISC-V的load、store只有base + offset一种,取a[i]要先用shift、add算出地址,再load

5. 对比小结

方面CISCRISC
访存算术指令可直接访存只有load、store访存
指令数与CPI指令少,CPI高指令多,CPI接近1
指令长度可变固定
代码大小小大
复杂操作由谁拆硬件(microcode)编译器
寻址方式多少

三、现代处理器:两种思路的结合

现代处理器不再是纯粹的RISC或CISC,两边都借用了对方的长处。

1. x86:对外CISC,内部micro-op

从1995年的Pentium Pro开始,x86 CPU的译码器把每条x86指令拆成一条或几条micro-op(微操作)。micro-op和RISC指令一样,每个只做一件简单的事:

  • 例:add [a], eax被拆成读内存、做加法、写回内存几个micro-op,和「指令数与CPI」里RISC-V的lw、add、sw是同样几步
  • 很复杂的指令(如字符串拷贝)仍由microcode给出micro-op序列(microcode见「复杂操作由谁拆开」)
  • 后面的流水线、乱序执行只处理micro-op

这样x86对外保持CISC的ISA,老软件照样能跑,代码依然紧凑;内部又能像RISC一样做流水线。代价是译码器更复杂。

2. RISC:加入16位压缩指令

固定长度的缺点是代码大(「指令长度与代码大小」),RISC用压缩指令弥补:

  • Arm的Thumb:把常用的32位指令再编一套16位的短格式,在流水线里还原成对应的32位指令再执行
    • 只能访问8个通用寄存器:16位编码里寄存器号只有3位
    • 用于内存紧张的嵌入式系统:程序更小,占的内存、带宽和I-cache更少
  • RISC-V也有同样思路的压缩指令,见「压缩指令(C扩展)」
Important

RISC和CISC的界线已经模糊:x86内部按RISC的方式执行,RISC也加回了短指令。一项在真实的Arm和x86处理器上做的测量研究(Blem et al., HPCA 2013)的结论是:两者只是为不同性能目标做的不同设计,没有哪一类ISA天生更省电,ISA是RISC还是CISC基本无关。

四、RISC-V的优势与缺点

RISC-V是UC Berkeley从2010年开始设计的RISC ISA,现在由瑞士的非营利组织RISC-V International维护。既然ISA是RISC还是CISC已经不决定性能,RISC-V和Arm、x86的区别主要在授权方式和ISA本身的设计上。

1. 开放、免费

  • ISA开放:spec公开,任何人都能按spec设计RISC-V处理器,不用向谁申请授权,也不用付授权费
  • 不依赖某一家公司:ISA不归哪家公司所有,用它的厂商不会受某一家公司授权政策的影响
  • Arm、x86要授权:用Arm要向Arm买授权,可以用Arm设计好的core,或者买architecture license自己设计core;x86只有Intel、AMD等少数几家有授权
ISA免费不等于core免费

直接买别人做好的RISC-V core(如SiFive的IP),仍要付这份IP的授权费;免费的只是ISA本身。

2. 模块化:base加扩展

RISC-V的ISA由一个必须实现的base,加上按需选的扩展组成:

  • base:整数指令集,如RV32I(32位)、RV64I(64位)。RV32I只有40条指令,已经够编译器生成程序、够操作系统运行
  • 标准扩展:如M(乘除法)、A(原子操作)、F和D(单、双精度浮点)、C(压缩指令)、V(向量)。芯片支持哪些扩展就写在名字里,如RV32IMC、RV64GC(G是IMAFD再加Zicsr、Zifencei的简写)

这样做的好处:

  • 一套ISA覆盖从小到大:小的MCU只做RV32IMC,省面积、省功耗;应用处理器做RV64GC再加V。软件按目标芯片支持的扩展编译
  • 批准的部分不再改:base和已批准的扩展冻结,按它们编译的程序以后一直能跑
同一行C代码a * b(a、b是float),按两种芯片编译
  • RV32IMFC(有F扩展):1条浮点乘法指令fmul.s
  • RV32IMC(没有F扩展):调用库函数__mulsf3。它只用整数指令算浮点乘法:拆出符号、指数、尾数,尾数相乘、指数相加,再规格化、舍入;函数本身有171条指令(LLVM的实现)

说明芯片缺了某个扩展,同一段程序照样能编译、能跑:编译器从编译选项-march知道没有F扩展,就改用整数指令实现浮点运算,这叫软浮点(soft-float),代价是慢很多。

3. 可以加自定义指令

  • spec留了位置:opcode里的custom-0、custom-1两组专门留给厂商自定义的指令,以后的标准扩展不会占用
  • 用途:厂商可以给自己的应用加专用指令,比如加速AI或加密的运算,不会和标准指令冲突
  • 代价:标准的编译器不认识这些指令,要自己扩展编译器,或者在代码里用内联汇编调用(标准指令则不需要,见「复杂操作由谁拆开」的note)

4. 编码简单

RISC-V是从零设计的,没有要兼容的老指令,编码可以完全按方便硬件来排。以RV32I的三种格式为例:

格式[31:25][24:20][19:15][14:12][11:7][6:0]
R(如add)funct7rs2rs1funct3rdopcode
I(如lw)imm[11:5]imm[4:0]rs1funct3rdopcode
S(如sw)imm[11:5]rs2rs1funct3imm[4:0]opcode
  • 寄存器字段的位置固定:rs1、rs2、rd在所有格式里都在同样的位,还没判断出是哪种格式,就能先拿这几位去读寄存器堆。代价是立即数要拆开放:S型的imm[4:0]挪到了rd的位置
  • 立即数的符号位总在第31位:符号扩展可以和译码同时进行
  • 没有branch delay slot:MIPS规定跳转指令后面那一条无论跳不跳都执行,这是为早期5级流水线定的规矩,流水线一变就成了负担;RISC-V没有这条规定
  • 没有condition code(标志位):x86、Arm的条件分支一般要先算出condition code,再按它跳;RISC-V的分支指令(beq、blt等)直接比较两个寄存器,一条指令完成,CPU里也少了一份要维护的condition code

5. 压缩指令(C扩展)

C扩展是RISC-V对「RISC:加入16位压缩指令」的做法:

  • 16位、32位指令自由混放:常用的指令有16位的编码,可以和32位指令混在一起,不像早期的Thumb那样要切换模式
  • 看最低2位就知道长度:最低2位是11就是32位指令,否则是16位(标准指令只有这两种长度)。取指后只看2位就能把指令分开,比x86的可变长度(「指令长度与代码大小」)简单得多
  • 每条16位指令都对应一条32位指令:译码时展开成对应的32位指令,后面的流水线不用改
  • 效果:一般50%~60%的指令能换成16位,代码小25%~30%
  • 缺点:32位指令只需按2字节对齐,可能横跨两条cacheline,取指要处理这种情况

6. RISC-V的缺点

ECE 411课件引用了Tirias Research对几种授权方式的比较(Forbes),开放ISA的短处在于:

  • 现成的软件少:比x86、Arm少很多,正是「RISC普及花了十年」里说的软件生态问题
  • 自己设计core的成本高:用开放ISA自己设计core,设计、验证都要自己做,研发成本高、周期长;用Arm则可以直接买授权的core
  • 碎片化:可选扩展和自定义指令多,不同芯片支持的组合不一样,为一颗芯片编译的程序不一定能在另一颗上跑
    • RISC-V International用profile来约束:如2024年批准的RVA23,规定了64位应用处理器必须支持的扩展组合

参考

Discussion

Loading comments…

Checking sign-in…