皋陶 发表于 2020-8-23 09:56:01

Risc-V简要概括

本帖最后由 皋陶 于 2021-3-5 21:27 编辑

Risc-V简要概括.
.
1.Risc-V硬件平台术语
.
      一个RiscV硬件平台可以包含一个或多个RiscV兼容的核心、其它非RiscV兼容的核心、固定功能的加速器、各种物理存储器结构、I/O设备以及允许这些部件相互连通的互联结构。比如下面的SiFive Freedom U540平台。就包括4个U54 RiscV RV64GC兼容核心,以及一个E51 RV64IMAC核心,DDR3/DDR4内存控制器接口,各种外设I/O接口,以及内部互联的TileLink协议架构等等。

      如果一个部件包含了一个独立的取指令单元,则该部件被称为核心(core)。一个RiscV兼容的核心能够通过多线程技术(或者说超线程技术)支持多个RiscV兼容硬件线程(harts),harts这儿就是指硬件线程, hardware thread的意思。所谓超线程技术,就是在一个硬件核中,实现多份硬件线程,每个硬件线程都有自己独立的寄存器组等上下文资源,但大多数的运算资源都被所有线程复用,因此面积效率很高。超线程最早出现是在Intel的处理器中,下图左边是使用超线程技术的Intel处理器核,每个核心只有一个PU单元,但是有两个AS单元,所以可以同时支持两个物理线程,但这两个物理线程通过调度共享PU单元。
1)Processing Unit(运算处理单元),简称PU2)Architectual State(架构状态单元),简称AS
PU一般就是执行运算,比如算数运算加减乘除。AS执行一些逻辑和调度方面的操作,比如控制内存访问等。   
      一个RiscV核心可能有额外的专有指令集扩展或者专门增加一个协处理器(coprocessor)去执行一些扩展的指令。
我们使用术语协处理器,指的是一个连接到RiscV核心的单元,它执行的指令来自RiscV核心,它和RiscV核心之间通过总线相连。
协处理器可能会包含一些其它的体系结构设计或者指令集扩展。
相对于主控的RiscV核,协处理器通常都有一定自主处理功能。
比如蜂鸟E200系列中,就有一个协处理器,主要是用RiscV的custome定制指令来做一些矩阵运算。
协处理器核主控核之间通过EAI协议互连,协处理器也有自己的内存读写单元。



      RiscV平台中加速器(accelerator) 通常指的是一个不可编程的固定功能单元,或者是一个可以自主工作,但专门用于某项任务的核心。
在RiscV中,我们预期很多可编程加速器将会是基于RiscV核心的,包括专门的指令集扩展,以及定制化的协处理器。
一类重要的RiscV加速器是I/O加速器,它将I/O处理任务从主控核心转移到I/O加速器。
比如PCI I/O 加速器。


      RiscV硬件平台的系统级组织结构,可以从单个核心的微控制器,到一个拥有共享存储器的众核服务器作为节点的大型集群系统。
即使小型的片上系统也可能是结构化的,包含层次化的多个计算机或者多个处理器,以便能够模块化设计研发或者在不同的子系统之间提供安全隔离。


2. Risc-V软件执行环境和Hart
.
      一个RiscV程序的行为依赖于它运行时的执行环境。
通常,RiscV的执行环境接口(EEI, execution enviorments interface)具有以下功能:定义程序的初始状态;
在特权模式和非特权模式环境中harts的数目以及类型;
内存以及I/O的可访问性以及其它特性;
每个hart中所有合法执行的指令的行为(指令集可以看作是EEI的一部分);
以及程序执行过程中中断和异常的处理等等。
比如ABI(Linux application binary interface,linux应用二进制接口)以及SBI(RiscV-supervior binary interface,riscV管理二进制接口)就是典型的EEI接口例子。
      RiscV EEI的实现可以是纯硬件实现,也可以是纯软件实现,或者软硬结合的实现方式。
例如,程序执行时候遇到一个硬件不支持的操作码,进入操作码自陷处理函数或者软件模拟都可以用来处这个理硬件不支持的功能。

      EEI主要包括以下几种实现方式:

[*]bare mental(裸金属),也就是纯物理硬件实施的方式,所有的harts都是纯物理核线程,指令都能够完全访问所有的物理地址空间。在电源复位时候,硬件平台将定义所有的执行环境。
[*]RiscV操作系统通过复用用户层的harts,把它们映射到可利用的物理核线程中,通过虚拟内存机制来访问系统内存,从而提供多个用户层的执行环境。
[*]RiscV管理程序负责为客户操作系统提供多个监督模式下的执行环境。
[*]RiscV模拟器,这些都是纯软件的实现方式,比如Spike, QEMU, 以及rv8等等,它们都在x86系统中模拟了RiscV的harts实现,提供了在用户模式和监督模式下的执行环境。


      通常的EEI 都是分层实现的,比如最底层用纯硬件实现EEI,而高层定义更加抽象的EEI接口 ,这样顶层的EEI可以对应不同的硬件实施平台。
      在一个给定执行环境中,从软件的观点来看,一个hart就是在执行环境中自动取指以及执行指令。
这样来说,即使真实的硬件在执行环境中是分时复用的,但一个hart行为也和硬件线程非常相像。
一些EEIs支持附加的hart创建和销毁,例如通过环境调用产生新的harts。


3. Risc-V指令集概括
.Risc-V的基础指令集是整数指令集,在任何架构方案中,必须完整实现基础的整数指令集。
在整数指令集中,用补码表示有符号整数。
主要有四个基础指令集,它们主要通过整数寄存器的长度来区分,比如RV32I,在该指令集方案中,整数寄存器的长度为32位,在RV64I指令集方案中,整数寄存器的长度为64位。
对于RV32I,由于整数寄存器是32位,所以可以提供2^32=4GB的地址访问空间,对于RV64I,整数寄存器是64位,所以可以提供2^64=4194304TB的地址访问空间,这是一个非常大的地址空间。
我们通常用xlen表示整数寄存器位数或者说地址空间位数,所以对于RV32I, xlen=32, 对于RV64I, xlen=64。
在整数指令集的基础上,可以选择实现扩展模块,比如RV32IMAFDC,表示当前实现支持这些模块的组合,其中IMAFD是通用组合,用字母G表示,所以RV32IMAFDC,也可以写作RV32GC。
现在的Risc-V编译工具链,重点会支持RV32G和RV64G。
RiscV的指令集主要包括以下模块:

基础模块:
.RVWMO, V2.0, 批准(Ratified):RiscV内存一致性模型。RV32I, V2.1, 批准(Ratified):基础的32位整数指令集,32位地址空间,寄存器是32位。RV64I,V2.1,批准(Ratified):   基础的64位整数指令集,64位地址空间,寄存器是64位。RV32E, V1.9, 草案(Draft):嵌入式架构,仅有16个整数寄存器。RV128I,V1.7,草案(Draft):基础的的128位整数指令集,支持128位地址空间。

扩展模块:
.ZiFencei,V2.0,批准(Ratified):Instruction-Fetch Fence。Zicsr,V2.0, 批准(Ratified):控制和状体寄存器指令。M,V2.0,批准(Ratified):支持乘法和除法指令。A,V2.0,冻结(Freeze):支持原子操作指令和Load-Reserved/store-Conditional指令。F,V2.2,批准(Ratified):单精度浮点指令。D,V2.2,批准(Ratified):双精度浮点指令。Q,V2.2,批准(Ratified):四精度浮点指令。C,V2.0,批准(Ratified):支持编码长度为16的压缩指令。Ztso, V0.1, 冻结(Freeze):Total Store ordering。Counters, V2.0, 草案(Draft):性能统计CountersL,V0.0, 草案(Draft):十进制浮点数,IEEE754-2008。B,V0.0,草案(Draft):位操作指令。J,V0.0,草案(Draft):支持动态转化语言。T,V0.0,草案(Draft):transactional memory operations。P,V0.2,草案(Draft):Packed-SIMD Instructions。V,V0.2,草案(Draft):向量操作指令。N,V1.1,草案(Draft):用户层的终端和异常指令。Zam,V0.1,草案(Draft):非对齐的原子指令。

4. 内存(memory)
.一个字节(byte)是8bits,一个字(word)是32bits,4个字节长度。相应的,半字(halfword)是16bits,双字(double workd)是64bits,四字(quadword)是128bits。
一个RiscV hart可以访问2^xlen字节地址空间。
通常,内存地址空间是环形的,所以地址空间0和2^xlen-1是邻接的,硬件将忽略地址溢出,而是将访问地址模一个2^xlen,得到最终的访问地址 。
比如访问地址2^xlen+1,实际上访问就是地址1。
执行环境决定硬件资源如何映射到hart的地址空间。
hart地址空间中不同的地址范围对应不同硬件资源,比如主存,I/O设备,也有可能是没有对应硬件资源的地址范围,访问这些范围地址,可能会引起异常。
比如蜂鸟E200 RiscV核,是32bit的地址空间,hart可以访问4GB的地址范围,0x0-0xFFFF_FFFF,但实际上对应的硬件资源只使用一部分的地址空间:
ITCM 64K,地址范围:0x8000_0000-0x8000_FFFFDTCM 64K,地址范围:0x9000_0000-0x9000_FFFFPPI 地址范围: 0x1000_0000 – 0x1FFF_FFFFCLINT 地址范围: 0x0200_0000 – 0x0200_FFFF
PLIC地址范围: 0x0C00_0000 – 0x0CFF_FFFFFIO地址范围: 0xF000_FFFF – 0xFFFF_FFFF
当RiscV平台有多个harts时候,每个hart的地址空间可能是完全相同的,也可能是完全不同的,或者部分不同,但共享一些映射到相同或不同地址的硬件。
在纯硬件实现的环境中,所有harts的地址空间是相同的,都是完全访问的物理地址空间。
但执行环境包括一个支持虚拟地址操作系统的时候,通常每个hart访问的地址都是大范围虚拟地址,但该地址最终会被地址转化模块转化为真正的物理地址,不同的虚拟地址可能会映射到相同的物理地址。
RiscV指令分为显式内存访问和隐式内存访问。
在主存和RiscV核心之间,可能存在一级或者多级cache,所以有些指令,比如load和store指令读写内存时候,可能会cache hit,这个时候不需要内存访问,如果cache miss的化,则需要内存访问。
有时候,即使cache hit,虽然没有直接访问内存,但可能隐含了虚拟地址到物理地址的转化,则需要访问内存中的PTE表,所以是隐式的内存访问。
由于RiscV指令可以乱序执行,有时候为了保证读写顺序,比如确保store指令写某个内存地址执行完成后,再用load指令读该地址,则可以在两条指令之间,增加一条fence指令来进行同步操作,确保RAW(read after write)。
RiscV实施的内存模型是Weak Memory Ordering (RVWMO), 后面我们会详细解释这种模型的细节。


5.Risv-V的指令编码规则:

基础的RiscV指令集,比如RV32I,RV64I中,所有指令长度都是固定的32bits,在这些RiscV实施方案中,指令访问必须32bit地址对齐。
但是标准的RiscV指令编码方案是支持其它指令长度的,只要指令长度是16bit的倍数,这时候,指令访问是16bit地址对齐的。比如标准的压缩指令RVC,指令长度就是16bit的。
在嵌入式系统中,使用16bit的编码可以提高代码密度,减少功耗。我们用IALIGN来表示指令地址必须对齐的位数。
在标准指令集中,IALIGN=32,RVC中,IALIGN=16,当然在扩展指令中,你也可以实施16倍数的其它指令编码,比如IALIGN=64等等。
我们用术语ILEN表示RiscV实施中最大的指令长度,它总是IALIGN的倍数。
如果RiscV仅实施了基础指令集,那么ILEN=32,如果实施了更长的指令长度方案,则ILEN有更大的值。
所有的32bit指令,低两位都是11。
对于16位压缩指令,它的低两位不等于11,可以是00,01,10。对于RV32,它的=11,不等于111。
对于64位指令,它的低7位为0111111。
低16位是全0或者全1的编码都是不合法的,会导致异常。
注意:RiscV中仅支持小头格式(little-edian),就是高位存在高地址,低位存在低地址,比如32bit数0x12345678,存在地址0x0-03,则mem=78,mem=56,mem=34,mem=12。



6. 异常(Exceptions),自陷(Trap)和中断(Interrupts)

异常:运行时出现了一个与当前RiscV线程中的一条指令相关的非正常的情况。
自陷:在一个RiscV线程中出现了一个异常或者中断,导致将控制同步传输到自陷处理函数。
自陷处理函数通常是在一个更高特权环境中执行的(注意,浮点异常不会自陷)。
中断:当前RiscV线程外异步出现一个事件。
自陷如何被处理以及是否对hart上运行的软件可见都依赖于执行环境。
从执行环境中运行的软件的观点来看,一个hart在运行时的自陷有以下几种类型。
Contained Trap: 自陷对执行环境中的软件可见,并被软件处理。
比如执行环境接口给hart提供了监督程序和用户模式,一个用户模式的hart执行ecall指令,将会导致把控制权传输给相同hart上的监督模式处理程序。
相似的,在相同环境中,一个hart被中断,中断处理程序将会运行系统hart的监督模式
Requested Trap: 自陷是外部调用产生的异步异常,它通过在执行环境中对软件发出一个请求产生。比如系统调用,这时执行环境可能不可恢复,hart也许就被终止执行。
Invisible Trap: 自陷对执行环境是透明的,自陷服务程序执行完毕后,hart继续执行。比如模拟执行不支持的指令;请求虚拟物理地址转化时,页表在外部内存中;多个程序运行机器中,处理设备中断等等情况。在这些情况下,执行环境中运行的软件并不知道自陷处理的存在。
Fatal Trap: 自陷表示重要的失败,会引起终止执行环境。比如虚拟地址页表转换失败,监督的定时器过期等等。每个EEI都应该定义什么情况下终止执行环境,并把出错信息报告给外部环境。
下面的表格中例举了各种自陷的特征:


Zifencei扩展
.fence 指令对外部可见的访存请求,如设备 I / O 访问,内存访问等进行串行化。
外部可见是指对处理器的其他核心、线程,外部设备或协处理器可见。
fence.i 指令同步指令和数据流。
在执行 fence.i 指令之前,对于同一个硬件线程(hart), RISC-V 不保证用存储指令写到指令存储区的数据可以被取指指令取到。
Zifencei扩展目前仅包括FENCE.I指令。
该指令提供了同一个hart中写指令内存空间和读指令内存空间之间的显式同步, 就是说读取的指令的总是最新写入的指令。
该指令目前是确保指令内存存储和读取都对hart可见的唯一标准机制。
fence.i指令可以有各种实现方法,一种简单的实现就是在执行fence.i指令的时候,冲刷(flush)指令缓存(Icache, instruction cache)和指令管线(instruction pipeline)。
冲刷icache和管线的作用是确保指令缓存中的内容和指令内存空间中的数据一致,以及所有写指令缓存的动作完成(icache通常是只读的,但自修改指令可能会需要写的动作)。
这样确保后续的指令读取操作正确。
更复杂的实现可能会在每个数据(指令)高速缓存未命中时窥探指令(数据)高速缓存,或者使用统一专用L2高速缓存,L2缓存是全局缓存,所有的riscv核都接在上面,当然riscv核本身有icache和dcache,也就是L1 cache, 如果L2 cache足够大,对指令数据并没有一致性问题。
对L2的store指令,就去回看L1对应的cacheline是否有效,如果数据有效,就invalidate它。
如果指令和数据高速缓存以这种方式保持一致,或者如果存储器系统仅由未缓存的RAM组成,那么只需要在FENCE.I处冲刷管线。
FENCE.I指令以前是基本指令集RV32I/RV64I的一部分。现在把它移到扩展指令集Zifencei, 这样做主要有两个原因:
首先,在某些系统上,实现FENCE.I代价将是昂贵的,RiscV基金会存储器模型工作组中正在讨论替代机制。
特别是,对于具有不一致指令高速缓存和不一致数据高速缓存的设计,或者指令高速缓存不能监视(snoop)数据高速缓存一致性的时候,当遇到FENCE.I指令时,两个高速缓存必须完全冲刷管线。
当在主存和riscv系统全局统一cache的上层(更接近core),有多级的icache和dcache时候,这个问题会更加严重。
其次,该指令的功能不足以在类Unix操作系统环境中的用户级别使用。
FENCE.I仅同步本地hart,操作系统可以在FENCE.I之后将用户hart重新分配到不同的物理hart。
这将要求操作系统执行额外的FENCE.I来进行每个上下文切换。
出于这个原因,标准Linux ABI已经从用户级删除了FENCE.I,现在需要系统调用来维持指令一致性,这允许操作系统最小化在当前系统上执行FENCE.I的数量,对于将来改进的读取指令一致性机制,也可以保持向前兼容。
一些新的取指令一致性机制仍在讨论中,将来会提供fence.i更多的版本。
比如在rs1中指定地址,仅fence指定的rs1地址的访问。
fence.i 指令同步指令和数据流。
在执行 fence.i 指令之前,对于同一个硬件线程(hart), RISC-V 不保证用存储指令写到内存指令区的数据可以被取指令取到。
使用fence.i指令后,对同一hart,可以确保指令读取是最近写到内存指令区域的数据。
但是,fence.i将不保证别的riscv hart的指令读取也能够满足读写一致性。
如果要使写指令内存空间对所有的hart都满足一致性要求,需要执行fence指令。


fence.i
.
fence.i    //Fence(Store, Fetch)
同步指令流(Fence Instruction Stream). I-type, RV32I and RV64I.
使对内存指令区域的读写,对后续取指令可见。


immrs1func3rdopcode
nametype313029282726252423222120191817161514131211109876543210
fence.iI00000000000000000001000000001111


例子:0:    0ff0000f            fence    iorw,iorw
4:    0000100f            fence.i
fence.i指令用于同步指令和数据流。
如果程序中添加一个fence.i,则该指令能够保证fence.i之前所有指令的访存结果能被fence.i之后的所有指令访问到。
通常说来,处理器的微架构硬件实现时,一旦遇到一条fence.i指令,便会先等到之前的所有访存指令执行完,然后冲刷流水线,包括Icache,使其后的所有指令,能够重新取指,从而得到最新的值。
注意:fence.i只能保证同一个hart(硬件线程)执行的指令流和数据流顺序,不能保证多个hart之间的指令流和数据流访问。
本篇完,感谢关注:RISC-V单片机中文网


页: [1]
查看完整版本: Risc-V简要概括