RISC-V 压缩指令集手册
版本 1.9
警告!这个规范的初稿在成为标准之前,可能会被修改,因此基于此规范
初稿的实现,可能与未来的标准规范并不相符。
(翻译:要你命 3000@EETOP 翻译版本 1.0)
Andrew Waterman, Yunsup Lee, David Patterson, Krste Asanović
CS Division, EECS Department, University of California, Berkeley
{waterman|yunsup|pattrsn|krste}@eecs.berkeley.edu
2015 年 11 月 5 日
该文档同时也是 UCB/EECS-2015-209 技术报告
1.1 介绍
本文档是从 RISC-V 用户级 ISA 规范节录出来的,用于描述 RISC-V 标准压缩指令集扩展
的当前初稿,标准压缩指令集扩展,被命名为“C”,通过对常用操作加入短的 16 位指令编
码,减少了静态和动态代码大小。这个“C”扩展可以添加到任何基本的 ISA 上(RV32、RV64、
RV128),我们使用术语 RVC 来指明这种情形。典型的,程序中大约 50%~60%的 RISC-V 指令
可以被 RVC 指令代替,导致大约 25%~30%代码大小的减少。
我们相信这个初稿将和最终的 RV32C 和 RV64C 设计相接近(看起来现在形成 RV128C 并
不成熟),但我们仍然需要一轮或者多轮的评价,因此定名为 1.9 版本。请将您的评价发送
到 isa-dev@lists.riscv.org 的邮件列表的 isa-dev。
1.2 概述
RVC 使用一种简单的压缩方案,以便在下列情形时,提供更短的 16 位版本的 32 位 RISC-V
指令:
立即数或者地址偏移量较小时
其中一个寄存器是零寄存器(x0)、ABI 链接寄存器(x1)或者 ABI 栈寄存器(x2)
目标寄存器和第一个源寄存器相同
最常见情况下使用了 8 个寄存器
C 扩展与其它所有标准扩展兼容。C 扩展允许 16 位指令可以自由地和 32 位指令混合执
行,并运行 32 位指令可以在任何 16 位边界开始。(译者注:一般情况下,32 位指令必须“天
然地”对齐到32 位存储器地址边界上,否则会导致非对齐存储器访问异常。)
在原来的32 位指令上面去掉32 位对齐约束要求,可以大幅度提高代码密
度。
压缩指令编码在绝大多数情况下,在 RV32C、RV64C 和 RV128C 下都是一样的,但如表 0.3
所示,少数操作码依据基本 ISA 的宽度有不同的用途。例如,更宽地址空间的 RV64C 和 RV128C
变种,需要额外的操作码来完成压缩 load 和 store 64 位整数值,而 RV32C 使用与单精度浮
点值一样的操作码来进行压缩 load 和 store。类似的,RV128C 需要额外的操作码来完成压缩
load 和 store 128 位整数值,而在 RV32C 和 RV64C 中,它们使用与双精度浮点值一样的操作
码来进行压缩 load 和 store。如果要实现标准 C 扩展,必须提供相应的压缩浮点 load 和 store
指令,而不管相关的标准浮点扩展(F 和/或 D 扩展)是否实现。另外,RV32C 包含一条压
缩跳转和链接指令,以压缩短范围的子过程调用,同样的操作码被用于 RV64C 和 RV128C 的
压缩 ADDIW 指令。
双精度load 和store 是静态和动态指令的重要部分,因此想到要把它们加
入到RV32C 和RV64C 编码中。
虽然对于当前支持的ABI 编译出来的基准测试程序(benchmark)来说,
单精度load 和store 并不十分重要,但是对于那些在硬件上仅支持单精度浮点
Copyright ©2010-2015, The Regents of the University of California. All rights reserved.
2
单元、ABI 仅支持单精度浮点数的微控制器来说,在基准程序上,单精度load
和store 的使用频度至少和双精度load 和store 相同。因此,想到在RV32C 中
提供这些指令的压缩支持。
对于微控制器来说,短范围子过程调用在小的二进制代码中非常常见,因
此想到在RV32C 中加入它们。
虽然对不同的基本寄存器宽度、不同的用途,重用操作码会增加一定的文
档复杂度,但是它对实现的影响非常小,即使是对同时支持多个基本ISA 寄存
器宽度也是如此。压缩浮点load 和store 指令使用了与更宽整数load 和store
相同的指令格式、相同的寄存器区分符。
RVC 是在这样的约束下设计的,即每一条 RVC 指令被扩展成在基本 ISA(RV32I/E、RV64I
或者 RV128I)或者 F、D 标准扩展中的一条 32 位指令。采用这条约束,有如下一些好处:
硬件设计可以在译码时简单地扩展 RVC 指令,简化了验证,并使得对现有微体系
结构的改动最小化。
编译器可以不处理 RVC 扩展部分,留到汇编器和链接器来进行代码压缩,虽然一
个压缩敏感的编译器通常可以生成更好代码。
我们感到通过在C 和IFD 指令之间进行简单的一对一映射,得到的多种复
杂度减少,其远远超过通过增加一些仅仅支持C 扩展的指令以获得稍微高一些
的编码密度而带来的收益,也远高于允许将多条IFD 指令编码入一条C 指令而
带来的收益。
值得重视的是,C 扩展并不是作为一个单独的 ISA 而被设计的,意味着它需要与一个基
本 ISA 一块使用。
变长指令集已经被用来提高代码密度使用很长时间了。例如,在1950 后
期研发的IBM Stretch[2]使用了一个具有32 位和64 位指令的ISA,其中有些32
位指令是64 位指令的压缩版本。Stretch 也使用了这样一个概念,就是在一些
较短的指令格式中,限制了可寻址的寄存器集合;具有短分支指令仅能引用索
引寄存器中的一个。后来的IBM 360 体系结构[1]支持一种简单的变长指令编码,
支持16 位、32 位或者48 位指令格式。
在1963 年,CDC 发布了Cray 设计的CDC 6600[3],它是RISC 的前辈,引
入了一个大量寄存器的load-store 体系结构,其指令长度15 位和30 位两种。
后来的Cray-1 设计使用了一种非常相似的指令格式,它采用了16 位和32 位
指令长度。
自1980 以来的早期RISC ISA,都是选择性能而不是代码大小,这在工作站
环境中是情理之中的,但对嵌入式系统并不合理。因此,ARM 和MIPS 在后续
的ISA 版本中,都通过在标准32 位指令集之外提供16 位指令集,来达到更小
的代码大小。压缩过后的 RISC ISA 相对于它们的完全版本,可以减少大约
25%~30%的代码大小,生成的代码远远小于80x86 生成的代码。这个结果令一
些人感到震惊,因为他们认为一个变长CISC ISA 产生的代码应当比仅提供16
位和32 位格式的RISC ISA 产生的代码要小。
由于原来的RISC ISA并没有为这些计划外的压缩指令留有足够的操作码空
间,因此它们被当做一个新的ISA 进行开发。这意味着编译器需要为单独的压
Copyright ©2010-2015, The Regents of the University of California. All rights reserved.
3
缩ISA 使用不同的代码生成器。第一个压缩RISC ISA 扩展(就是ARM Thumb
和MIPS16)仅仅使用了一种固定16 位指令长度,这在静态代码大小上得到了
很好的减少,但是导致动态指令数目的增加,这也导致了与原始固定32 位长
度指令相比,性能的下降。这导致了16 位和32 位指令长度混合的第二代压缩
RISC ISA(就是ARM Thumb2、microMIPS、PowerPC VLE)的研发,于是可以获
得与纯32 位指令相似的性能,同时有巨大的代码大小减少。不幸的是,这些
不同代次的压缩ISA 彼此之间、与原始为压缩ISA 之间,并不兼容,导致了文
档、实现和软件工具上的巨大复杂性。
在通用64 位ISA 中,当前只有PowerPC 和microMIPS 支持压缩指令格式。
令人感到惊讶的是,考虑到静态代码大小和动态取指带宽是重要的指标,当前
移动平台上最为流行的64 位ISA(ARM v8)并没有包含一个压缩指令格式。
虽然对于较大的系统来说,静态代码大小并不是一个主要关心的因素,但是在
服务器运行商业负载(通常具有一个大的指令工作集)的时候,取指带宽可能
会成为一个主要的瓶颈。
得益于25 年以来的经验,RISC-V 被设计成从外部支持压缩指令,为RVC
保留了足够的操作码空间,使得它可以在基本ISA 之上(与其它的标准扩展一
起)作为一个简单的扩展加入。RVC 的理念在于为嵌入式应用程序减小代码大
小,为所有应用程序提高性能和能耗效率,因为可获得更少的指令cache 缺失
(译者注:指令长度缩小,可使得一个Cache line 保存更多的指令,或者意味
着在同样大小的指令Cache 中容纳更多的指令)。Waterman 指出RVC 少取了
25%~30%的指令位,减少了20~25%的指令Cache 缺失,或者等效于指令Cache
大小翻倍同样的性能[4]。
1.3 压缩指令格式
表 0.1:压缩 16 位 RVC 指令格式给出了 8 种压缩指令格式。CR、CI 和 CSS 格式可以使
用任何的 32 个 RVI 寄存器,但是 CIW、CL、CS 和 CB 被限制只能使用所有 32 个寄存器中的
8 个。表 0.2:CIW、CL、CS 和 CB 格式中 rs1’、rs2’和 rd’字段三位指向的寄存器给出了这些
常用的寄存器,对应于 x8 到 x15。注意到有一个单独版本的 load 和 store 指令,将栈指针作
为基地址寄存器使用,这是因为保存到栈和从栈恢复太常见了,并且它们使用 CI 和 CSS 格
式,以便能够访问到所有 32 个数据寄存器。CIW 格式为 ADDI4SPN 指令提供了一个 8 位的
立即数。
RISC-V 的ABI 已经做了修改,将常用寄存器映射到寄存器x8-x15。这通过
提供一个连续的、自然对齐的寄存器编号,将简化解压缩的译码器,并且与
RV32E 子集基本规范相兼容,在那里只有16 个整数寄存器。
压缩的、基于寄存器的浮点 load 和 store 指令也分别使用了 CL 和 CS 格式,将 8 个寄存
器映射到 f8 到 f15。
标准RISC-V 调用规范,将最常用的浮点寄存器映射到f8-f15,这将允许与
整数寄存器编号相同的寄存器解压缩译码。
Copyright ©2010-2015, The Regents of the University of California. All rights reserved.
4
这些格式被设计成将两个源寄存器区分符放在所有指令中的同一个地方,因此可以去掉
目的寄存器字段。如果存在完整的 5 位目的寄存器区分符,它与 32 位 RISC-V 编码中的位置
是一样的。当立即数是符号扩展的时候,符号扩展总是从第 12 位开始。立即数字段被打乱
了,就如同在基本规范中一样,以便减少用于立即数的多路选择器数量(immediate mux)。
指令格式中的立即数字段是被打乱的而不是按顺序存放的,这是为了保证
在每一条指令中尽可能让尽量多的位处在相同的位置,这将简化实现。例如,
立即数的位17-10,总是来自指令的相同位位置。5 个其他的立即数位(5、4、
3、2、1)只有两个指令位的来源,而4 个其他的立即数位(9、7、6、2)有
3 个指令位的来源,而1 个(第8 位)有四个来源。
对于许多 RVC 指令来说,不允许 0 立即数,而且 x0 不是一个有效的 5 位寄存器区分符。
这个限制,为其他需要较少操作数位的指令,释放了编码空间。
格式
CR
含义
寄存器
立即数
CI
CSS 栈相关 store
CIW
宽立即数
Load
Store
分支
跳转
CL
CS
CB
CJ
15 14 13
12
11 10 9 8 7 6 5 4
3 2
1 0
funct4
funct3
funct3
funct3
funct3
funct3
funct3
funct3
imm
rd/rs1
rd/rs1
imm
imm
imm
imm
offset
rs1’
rs1’
rs1’
jump target
表 0.1:压缩 16 位 RVC 指令格式
rs2
imm
rs2
rd’
rd’
rd’
imm
imm
offset
op
op
op
op
op
op
op
op
111
x15
a5
f15
fa5
RVC 寄存器编号
整数寄存器编号
整数寄存器 ABI 名字
浮点寄存器编号
浮点寄存器 ABI 名字
000 001 010 011 100 101 110
x8
s0
f8
x9
s1
f9
x10
x11
x12
x13
x14
a0
f10
a1
f11
a2
f12
a3
f13
a4
f14
fa4
表 0.2:CIW、CL、CS 和 CB 格式中 rs1’、rs2’和 rd’字段三位指向的寄存器
fa3
fa2
fs0
fs1
fa0
fa1
1.4 Load 和 store 指令
为了增加 16 位指令能够访问的范围,使用零扩展立即数的数据传输指令,其数据值的
大小被放大了多倍:对字×4、对双字×8、对四字×16。
RVC 提供了两种类型的 load 和 store。一种使用 ABI 栈指针 x2 作为基址寄存器,并可
定位到任何数据寄存器。另外一种可以引用 8 个基址寄存器之一,并引用 8 个数据寄存器之
一。
Copyright ©2010-2015, The Regents of the University of California. All rights reserved.
5
基于栈指针的 load 和 store
15 13
funct3
3
C.LWSP
C.LDSP
C.LQSP
C.FLWSP
C.FLDSP
12
imm
1
偏移量[5]
偏移量[5]
偏移量[5]
偏移量[5]
偏移量[5]
11 7 6 2 1 0
rd
5
dest≠0
dest≠0
dest≠0
dest
dest
imm
5
偏移量[4:2|7:6]
偏移量[4:3|8:6]
偏移量[4|9:6]
偏移量[4:2|7:6]
偏移量[4:3|8:6]
op
2
C2
C2
C2
C2
C2
这些指令使用 CI 格式。
C.LWSP 指令将一个 32 位数值从存储器读入寄存器 rd 中。其有效地址的计算是通过将
零扩展的偏移量×4,然后加上栈指针 x2 形成的。它被扩展为 lw rd, offset[7:2](x2)指令。
C.LDSP 是一条 RV64C/RV128C 仅有指令,它将一个 64 位数值从存储器读入寄存器 rd 中。
其有效地址的计算是通过将零扩展的偏移量×8,然后加上栈指针 x2 形成的。它被扩展为 ld
rd, offset[8:3](x2)指令。
C.LQSP 指令是一条 RV128C 仅有指令,它将一个 128 位数值从存储器读入寄存器 rd 中。
其有效地址的计算是通过将零扩展的偏移量×16,然后加上栈指针 x2 形成的。它被扩展为
lq rd, offset[9:4](x2)指令。
C.FLWSP 是一条 RV32FC 仅有指令,它将一个单精度浮点数值从存储器读入浮点寄存器
rd 中。其有效地址的计算是通过将零扩展的偏移量×4,然后加上栈指针 x2 形成的。它被
扩展为 flw rd, offset[7:2](x2)指令。
C.FLDSP 是一条 RV32DC/RV64DC 仅有指令,它将一个双精度浮点数值从存储器读入浮点
寄存器 rd 中。其有效地址的计算是通过将零扩展的偏移量×8,然后加上栈指针 x2 形成的。
它被扩展为 fld rd, offset[8:3](x2)指令。
15 13
12 7
6 2 1 0
funct3
3
C.SWSP
C.SDSP
C.SQSP
C.FSWSP
C.FSDSP
imm
6
偏移量[5:2|7:6]
偏移量[5:3|8:6]
偏移量[5:4|9:6]
偏移量[5:2|7:6]
偏移量[5:3|8:6]
rs2
5
src
src
src
src
src
op
2
C2
C2
C2
C2
C2
这些指令使用 CSS 格式。
C.SWSP 指令将寄存器 rs2 中的 32 位值保存到存储器中。其有效地址的计算是通过将零
扩展的偏移量×4,然后加上栈指针 x2 形成的。它被扩展为 sw rs2, offset[7:2](x2)指令。
Copyright ©2010-2015, The Regents of the University of California. All rights reserved.
6
C.SDSP 是一条 RV64C/RV128C 仅有指令,它将寄存器 rs2 中的 64 位值保存到存储器中。
其有效地址的计算是通过将零扩展的偏移量×8,然后加上栈指针 x2 形成的。它被扩展为
sd rs2, offset[8:3](x2)指令。
C.SQSP 是一条 RV128C 仅有指令,它将寄存器 rs2 中的 128 位值保存到存储器中。其
有效地址的计算是通过将零扩展的偏移量×16,然后加上栈指针 x2 形成的。它被扩展为 sq
rs2, offset[9:4](x2)指令。
C.FSWSP 是一条 RV32FC 仅有指令,它将浮点寄存器 rs2 中的单精度浮点数值保存到存
储器中。其有效地址的计算是通过将零扩展的偏移量×4,然后加上栈指针 x2 形成的。它被
扩展为 fsw rs2, offset[7:2](x2)指令。
C.FSDSP 是一条 RV32DC/RV64DC 仅有指令,它将浮点寄存器 rs2 中的双精度浮点数值
保存到存储器中。其有效地址的计算是通过将零扩展的偏移量×8,然后加上栈指针 x2 形成
的。它被扩展为 fsd rs2, offset[8:3](x2)指令。
基于寄存器的 Load 和 store
15 13
12 10
9 7 6 5 4 2 1 0
funct3
3
C.LW
C.LD
C.LQ
C.FLW
C.FLD
imm
3
rs1’
3
imm
2
偏移量[5:3] 基址 偏移量[2|6]
偏移量[7:6]
偏移量[5:3] 基址
偏移量[5|4|8] 基址
偏移量[7:6]
偏移量[5:3] 基址 偏移量[2|6]
偏移量[5:3] 基址
偏移量[7:6]
rd’
3
dest
dest
dest
dest
dest
op
2
C0
C0
C0
C0
C0
这些指令使用 CL 格式。
C.LW 指令将一个 32 位数值从存储器读入寄存器 rd’中。其有效地址的计算是通过将零
扩展的偏移量×4,然后加上寄存器 rs1’中的基址形成的。它被扩展为 lw rd’, offset[6:2](rs1’)
指令。
C.LD 是一条 RV64C/RV128C 仅有指令,它将一个 64 位数值从存储器读入寄存器 rd’中。
其有效地址的计算是通过将零扩展的偏移量×8,然后加上寄存器 rs1’中的基址形成的。它
被扩展为 ld rd’, offset[7:3](rs1’)指令。
C.LQ 是一条 RV128C 仅有指令,它将一个 128 位数值从存储器读入寄存器 rd’中。其有
效地址的计算是通过将零扩展的偏移量×16,然后加上寄存器 rs1’中的基址形成的。它被扩
展为 lq rd’, offset[8:4](rs1’)指令。
C.FLW 是一条 RV32FC 仅有指令,它将一个单精度浮点数值从存储器读入浮点寄存器 rd’
中。其有效地址的计算是通过将零扩展的偏移量×4,然后加上寄存器 rs1’中的基址形成的。
它被扩展为 flw rd’, offset[6:2](rs1’)指令。
C.FLD 是一条 RV32DC/RV64DC 仅有指令,它将一个双精度浮点数值从存储器读入浮点寄
存器 rd’中。其有效地址的计算是通过将零扩展的偏移量×8,然后加上寄存器 rs1’中的基址
形成的。它被扩展为 fld rd’, offset[7:3](rs1’)指令。
Copyright ©2010-2015, The Regents of the University of California. All rights reserved.
7
15 13
12 10
9 7 6 5 4 2 1 0
funct3
3
C.SW
C.SD
C.SQ
C.FSW
C.FSD
imm
3
rs1’
3
2
imm
rs2’
偏移量[5:3] 基址 偏移量[2|6]
偏移量[5:3] 基址
偏移量[7:6]
偏移量[7:6]
偏移量[5|4|8] 基址
偏移量[5:3] 基址 偏移量[2|6]
偏移量[5:3] 基址
偏移量[7:6]
op
2
C0
C0
C0
C0
C0
3
src
src
src
src
src
这些指令使用 CS 格式。
C.SW 指令将寄存器 rs2’中的 32 位值保存到存储器中。其有效地址的计算是通过将零扩
展的偏移量×4,然后加上寄存器 rs1’中的基址形成的。它被扩展为 sw rs2’, offset[6:2](rs1’)
指令。
C.SD 是一条 RV64C/RV128C 仅有指令,它将寄存器 rs2’中的 64 位值保存到存储器中。其
有效地址的计算是通过将零扩展的偏移量×8,然后加上寄存器 rs1’中的基址形成的。它被
扩展为 sd rs2’, offset[7:3](rs1’)指令。
C.SQ 是一条 RV128C 仅有指令,它将寄存器 rs2’中的 128 位值保存到存储器中。其有效
地址的计算是通过将零扩展的偏移量×16,然后加上寄存器 rs1’中的基址形成的。它被扩展
为 sq rs2’, offset[8:4](rs1’)指令。
C.FSW 是一条 RV32FC 仅有指令,它将浮点寄存器 rs2’中的单精度浮点数值保存到存储
器中。其有效地址的计算是通过将零扩展的偏移量×4,然后加上寄存器 rs1’中的基址形成
的。它被扩展为 fsw rs2’, offset[6:2](rs1’)指令。
C.FSD 是一条 RV32DC/RV64DC 仅有指令,它将浮点寄存器 rs2’中的双精度浮点数值保
存到存储器中。其有效地址的计算是通过将零扩展的偏移量×8,然后加上寄存器 rs1’中的
基址形成的。它被扩展为 fsd rs2’, offset[7:3](rs1’)指令。
1.5 控制转移指令
RVC 提供了无条件跳转指令和条件分支指令。如同基本 RVI 指令一样,所有 RVC 控制转
移指令的偏移量都是 2 字节的倍数。
15 13
12 2
1 0
funct3
3
C.J
C.JAL
imm
11
偏移量[11|4|9:8|10|6|7|3:1|5]
偏移量[11|4|9:8|10|6|7|3:1|5]
op
2
C1
C1
这些指令使用 CJ 格式。
C.J 指令执行一个无条件控制转移。偏移量被符号扩展后,与 pc 相加形成跳转目标地址。
C.J 指令因此可以在±2KB 范围内进行跳转。C.J 指令被扩展为 jal x0, offset[11:1]。
Copyright ©2010-2015, The Regents of the University of California. All rights reserved.
8