Reisen1969 / Reisen1969.github.io.back

1 stars 0 forks source link

333 #3

Open Reisen1969 opened 2 years ago

Reisen1969 commented 2 years ago

【翻译】 RISC-V 非特权规范文档 第3章:"Zifencei"取指屏障 ver2.0 Ratified // 版本:20191214-draft

VN Vortex

时间会冲刷一切。 【翻译】 RISC-V 非特权规范文档 第3章:"Zifencei"取指屏障 ver2.0 Ratified // 版本:20191214-draft 7 人赞同了该文章 原文:The RISC-V Instruction Set Manual Volume I: Unprivileged ISA Chapter 3 "Zifencei" Instruction-Fetch Fence, Version 2.0 (Document Version 20191214-draft) November 19, 2021

虽然是翻译但其实本质上还是个人笔记... 所以一切请以最新的官方标准文档为准。其实之前也有大佬翻译过,但是后来版本更新了,所以我想在加深理解RISC-V spec的同时顺便翻译翻译~ 如果能帮到在看的你就更好了!!

当然... 碍于个人水准,出现疏漏肯定在所难免。如果发现了还请务必在评论区指出,我会非常感谢>_<!!!!!!!

如果注释了大段原文,则说明这段原文被严重地重新表述了,建议在阅读本文的同时打开原版文档,并与原文相互对照。

请务必指出的包括:

翻译错误,和原文对不上,或者没有表达出原文要表达的东西/语气;或者术语/单词翻译错误。 错字错句(由于拼音输入法笨笨的,肯定有很多词打错了)。 读起来不顺畅。比如某个句子特别特别长,以至于看到了尾巴又忘了开头,这种肯定是没翻译好的,习惯上,我会通过用逗号/空格来拆分这种句子,或者彻底调整句子结构。 更好的翻译,如果读者发现某句话可以翻译地更加优雅,也请务必指出。 需要加注原文。首先术语肯定是要加注原词;其次就是某些句子,比较重要或者原文读起来更舒服,也需要加注原句。 我也不知道为什么反正就是觉得怪怪的:这种情况也要指出! 其它想指出的东西。 以下为正文:

本章定义“Zifencei”扩展,其包含FENCE.I指令,该指令用于在(同一hart上的)指令内存写入(writes to instruction memory)与取指(instruction fetches)间提供显式同步。目前该指令是唯一能确保Store可见于hart的同时也可见于该hart的取指的标准机制。

我们考虑过“存指令字(store instruction word)”指令(如MAJC[21]),但并未将其纳入标准。JIT编译器可能会在单个FENCE.I指令前生成一个较大的指令踪迹(trace of instructions)[1],并将转换后的指令写至已知不在I-Cache中的内存区域上,以摊销指令缓存监听/无效化(instruction cache snooping/invalidation)的开销。 FENCE.I的设计能用于支持多种具体实现。简单实现可以在执行FENCE.I时冲刷(flush)本地指令缓存 和 指令流水线。 更复杂的实现可以在每次数据(指令)缓存缺失时监听指令(数据)缓存,或者采用一个包含性的统一私有L2缓存(inclusive unified private L2 cache),使得在它们被本地store指令写入时会让主缓存中的行无效化[2]。若指令与数据缓存以这种方式保持一致(coherent),又或者内存系统仅由未缓存的RAMs(uncached RAMs)组成的话,则FENCE.I只需冲刷fetch pipeline即可。 FENCE.I指令曾是base I指令集的一部分。但有两个主要原因促使其从base中分离了出来。尽管直至目前其仍是维持取指一致性(coherence)的唯一标准方法。 首先我们注意到,在某些系统上FENCE.I的实现成本会很高(目前内存模型任务小组(memory model task group)正在讨论替代机制)。特别是对那些指令和数据缓存没有一致性(incoherent I-Cache and incoherent D-Cache),又或者在重填指令缓存时不监听相关(coherent)数据缓存的设计。这种情况下,一旦遇到FENCE.I指令就必须彻底冲刷这两个缓存。并且在统一缓存(unified cache)或外部内存系统前面存在多级指令和数据缓存时,这个问题还会加剧[3]。 其次,这个指令还不足以在unix-like OS环境中的user-level上提供使用。FENCE.I只能同步本地hart,但OS可能在FENCE.I后将user hart重新调度到不同的physical hart上,从而导致OS在每次执行上下文切换时(context migration)需要执行额外的FENCE.I。出于该原因,Linux标准ABI已经在user-level中移除了FENCE.I,目前取指一致性(coherence)需要通过系统调用来维持,这样能让OS最大程度地减少当前系统所需执行FENCE.I的指令数,并对未来所改进的取指一致性机制(coherence)保持前向兼容。 目前关于未来取指一致性的在讨论方案包括提供受限版的FENCE.I,其仅以一个特定地址(由rs1提供)为目标,以及和/或允许软件使用有关machine-mode缓存维护操作的ABI[4]。

FENCE.I指令用于在指令和数据流之间进行同步[5]。RISC-V本身并不保证取指一定能看见对指令存储器的Store操作,直至hart执行了一个FENCE.I指令。FENCE.I指令保证RISC-V hart上随后的取指可看见任何在此之前的Store操作。 FENCE.I并不保证多处理器系统中其它RISC-V hart上的取指能看见本地hart的Store。要让所有RISC-V hart都能看见对指令内存的Store,除要求所有远程RISC-V harts(remote RISC-V harts)执行一个FENCE.I外,执行该写入的hart还必须在此之前执行一个数据FENCE[6]。 FENCE.I指令中的未使用字段imm[11:0], rs1, rd保留给后续扩展中的细粒度FENCE。为保持前向兼容,基础实现(base implementations)应当忽略这些字段,标准软件则应将这些字段归零。

FENCE.I仅会对hart本身的取指 排序Store,因此应用程序代码应该仅在应用程序线程不会被迁至不同hart时才依赖FENCE.I。EEI可提供有效的多处理器指令流同步机制。

参考 ^指令跟踪(instruction trace) ^A more complex implementation might snoop the instruction (data) cache on every data (instruction) cache miss, or use an inclusive unified private L2 cache to invalidate lines from the primary instruction cache when they are being written by a local store instruction. ^First, it has been recognized that on some systems, FENCE.I will be expensive to implement and alternate mechanisms are being discussed in the memory model task group. In particular, for designs that have an incoherent instruction cache and an incoherent data cache, or where the instruction cache refill does not snoop a coherent data cache, both caches must be completely flushed when a FENCE.I instruction is encountered. This problem is exacerbated when there are multiple levels of I and D cache in front of a unified cache or outer memory system. ^Future approaches to instruction-fetch coherence under discussion include providing more restricted versions of FENCE.I that only target a given address specified in rs1, and/or allowing software to use an ABI that relies on machine-mode cache-maintenance operations ^The FENCE.I instruction is used to synchronize the instruction and data streams ^the writing hart also has to execute a data FENCE before requesting that all remote RISC-V harts execute a FENCE.I. 来自专栏 一些计算机科学书的目录与序言翻译

VN Vortex · 32 篇内容 【翻译】可综合SystemVerilog(2) / Synthesizing SystemVerilog 文章 · 5 赞同 · 0 评论 申请转载 评论 1

写下你的评论... icon

伊卡洛斯

码一下先,每次看设计文档都能学到东西 2021-11-23 ​

查看全部评论​

Reisen1969 commented 2 years ago

【翻译】 RISC-V 非特权规范文档 第2章:RV32I ver2.1 Ratified // 版本:20191214-draft

VN Vortex

时间会冲刷一切。 【翻译】 RISC-V 非特权规范文档 第2章:RV32I ver2.1 Ratified // 版本:20191214-draft 6 人赞同了该文章 原文:The RISC-V Instruction Set Manual Volume I: Unprivileged ISA Chapter 2 (Document Version 20191214-draft) November 19, 2021

虽然是翻译但其实本质上还是个人笔记... 所以一切请以最新的官方标准文档为准。其实之前也有大佬翻译过,但是后来版本更新了,所以我想在加深理解RISC-V spec的同时顺便翻译翻译~ 如果能帮到在看的你就更好了!!

当然... 碍于个人水准,出现疏漏肯定在所难免。如果发现了还请务必在评论区指出,我会非常感谢>_<!!!!!!!

如果注释了大段原文,则说明这段原文被严重地重新表述了,建议在阅读本文的同时打开原版文档,并与原文相互对照。

请务必指出的包括:

翻译错误,和原文对不上,或者没有表达出原文要表达的东西/语气;或者术语/单词翻译错误。 错字错句(由于拼音输入法笨笨的,肯定有很多词打错了)。 读起来不顺畅。比如某个句子特别特别长,以至于看到了尾巴又忘了开头,这种肯定是没翻译好的,习惯上,我会通过用逗号/空格来拆分这种句子,或者彻底调整句子结构。 更好的翻译,如果读者发现某句话可以翻译地更加优雅,也请务必指出。 需要加注原文。首先术语肯定是要加注原词;其次就是某些句子,比较重要或者原文读起来更舒服,也需要加注原句。 我也不知道为什么反正就是觉得怪怪的:这种情况也要指出! 其它想指出的东西。 第二章目录:

2.1 基础整数ISA的编程模型(Programmers' Model for Base Integer ISA) 2.2 基础指令格式(Base Instruction Formats) 2.3 立即数编码变体 2.4 整数计算指令 2.5 控制转移指令 2.6 Load和Store指令 2.7 内存排序指令 2.8 环境调用与断点 2.9 HINT指令 以下为正文:

本章描述RV32I基础整数指令集(RV32I base integer instruction set)。

RV32I能够作为编译器目标,并支持现代操作系统环境。此外,ISA的设计还旨在通过最小化实现来减少所需硬件。 RV32I共包含40条单独的指令,但对于简单的实现,也可以选择用一条总是造成trap的系统指令来代替其中的ECALL/EBRAK指令;或者以及,将其中的FENCE指令实现为NOP,从而将指令数压缩至38条。 可以仅通过RV32I来模拟几乎任何其它的ISA扩展(但A扩展除外,因为原子性需要额外的硬件支持)。 在实践中,含有machine-mode特权架构的实现还会需要6条CSR指令。 基础整数ISA的子集可能用于教学目的,但除不对齐访存(misaligned memory access)支持的省略,以及将所有系统指令(SYSTEM instruction)视为单一的trap这两个简化外,应该不太会有人想在真实硬件中仅实现RV32I的子集,我们也没有考虑这样的设计[1]。

RISC-V的标准汇编语法在Assembly Programmer's Manual [1]中说明。

(大部分关于RV32I的评注也同样适用于RV64I基础指令集) 2.1 基础整数ISA的编程模型

Fig2.1:RISC-V 非特权的基础整数寄存器状态 Fig2.1展示了非特权的基础整数ISA状态。在RV32I中,32个x寄存器都是32bits宽的,即XLEN=32。

寄存器x0的值永远为0。通用寄存器x1~x31所保存的值可能被指令理解为:

布尔值(Boolean values), 或者(2的补码)有符号二进制整数(2's complement signed binary integers), 或者无符号二进制整数(unsigned binary integers) 此外还有一个非权限寄存器,即程序计数器pc,它将保存当前指令的地址。

基础整数ISA中不包含专用的堆栈指针寄存器(stack pointer)或者子程序返回地址链接寄存器(subroutine return address link register),任何x寄存器都可用于这些目的。 但标准软件调用(standard software calling)会约定使用x1来保存调用返回地址,x5可作为备用的链接寄存器;然后x2作为堆栈指针。 硬件可能会选择加速x1或x5上的函数调用和返回。请参考JAL和JALR指令的说明。 可选的16bit压缩指令格式将假设x1为返回地址寄存器,x2为堆栈指针。采用其它约定的软件也能正常运行,但可能代码尺寸更大。

可用体系结构寄存器(architectural registers)的数量对代码大小、性能和能耗都有很大的影响。 16个寄存器对于编译得到的整数ISA代码来说是足够的,但我们不太能在16bit的指令格式中为16个寄存器实现3地址格式。 尽管可以选择2地址格式,但这将增加指令数并降低效率。并且同时我们又想避免一些中间的指令长度(如Xtensa的24bit指令),以简化基础硬件实现。

一旦采用32bit的指令长度,就可以顺理成章地直接支持32个整数寄存器。 大量的整数寄存器也有助于提升高性能代码的性能,可以在其中大量地应用循环展开、软件流水线、缓存分片(cache tiling)等技术。 出于以上这些原因,我们将base ISA整数寄存器常的规数量选定为了32个。 寄存器的使用活动情况往往主要来自几个频繁访问的寄存器,在regfile的实现中也可针对这几个寄存器优化访问能耗 [22]。

可选的16bit压缩指令格式[2]一般只能访问8个寄存器,从而做到更密集的指令编码。同时,额外的指令集扩展也可以支持更大的寄存器空间(扁平(flat)或者分层(hierarchical)的[3])。 我们还针对资源受限的嵌入式应用定义了RV32E子集,它仅含有16个寄存器(Chapter 5)。 2.2 基础指令格式

Fig2.2:RISC-V基础指令格式。其中每个立即数子域都标注了它在对应所生成立即数值中的bit位置[imm[x]]。 如Fig2.2所示,基础RV32I ISA有4种核心的指令格式(R/I/S/U)。它们都是32bits定长的,且在内存中必须对齐到每4 Byte的边界。

如果目标地址不是4 Byte对齐的,则会在所执行的分支或无条件跳转上,生成指令地址未对齐异常(instruction-address-misaligned exception)。这个异常是在这个分支或跳转指令上报告的,而非在目标指令上。对于未执行的条件分支,则不会生成指令地址未对齐异常。

如果添加了16bit长度的指令扩展,或者16*n bit(n为奇数)长度的其它指令扩展,则基础ISA指令的对齐约束会放宽至2Byte边界(即IALIGN=16)。 指令地址未对齐异常是在会造成指令不对齐的分支或跳转上报告的,以帮助调试(debug),并简化采用IALIGN=32的系统的硬件设计,它们是唯一可能导致未对齐发生的地方。 保留指令(reserved instruction)在解码过程上的行为是未定义的(unspecified)。

一些平台可能会要求,在标准使用中被保留的操作码(opcodes reserved for standard use)将引发非法指令异常(illeagal-instruction exception)。另一些平台可能允许将保留的操作码空间用于非标准(non-conforming)的扩展。 RISC-V ISA将所有格式中的源寄存器(rs1,rs2)和目寄存器(rd)都维持在了相同的(指令编码)位置,从而简化解码(decoding)。 除了CSR指令(Chapter 10)中所使用的5bit立即数之外,其它的立即数始终都是符号扩展(sign-extended)的,并且通常被封装在指令中最左边的可用位,以尽可能减少硬件复杂度。 特别注意的是,所有立即数指令的符号位始终都是指令的第31bit,以此来方便加速符号扩展。 寄存器说明符(register specifier)的解码过程通常位于关键路径,因此我们决定让指令格式中的寄存器说明符都保持在同一位置,不过代价是,在跨格式时必须移动立即数的bit位(这是个跟RISC-IV共有的特性,RISC-IV又叫SPUR [12])。 实践中,大多数用到的立即数要么很短,要么就是XLEN bit长。这里我们选了个非对称的立即数分割(常规指令中的12bit,加上LUI指令中的20bit),以腾出操作码(opcode)空间。 立即数是符号扩展的,因为我们希望ISA尽可能简单,况且我们也并没观察到,在MIPS ISA中对某些立即数采用零扩展(zero-extension)带来了什么好处。 2.3 立即数编码变体 还有另外两个用于立即数处理的指令格式(B/J),如Fig2.3所示。

Fig2.3:RISC-V基础指令格式,包括立即数变体。 S和B格式间的唯一区别在于,B中的12bits立即数被乘以了2[4],以用于编码分支偏移量(branch offset)。 在硬件上,S和B中立即数中间的bit(imm[10:1]这段),以及符号位都保持在了固定位置上,为此B格式中imm[11]被挪到了右边(即inst[7])。 类似地,U和J格式间的唯一区别在于,U的立即数被左移了12bits,而J的立即数只被左移了1bit。U和J中立即数bit的位置也都尽可能与其它格式重叠[5]。

Fig2.4:RISC-V指令中所生成立即数的类型。每段域都标记了用于构造其值的指令bit位置。符号扩展总是使用inst[31]进行扩展。 符号扩展是立即数指令中最关键的操作之一(尤其是对于XLEN>32),在RISC-V中,所有立即数指令的符号位都将始终固定在指令的第31bit位置上,从而允许符号扩展与指令解码能够同时(parallel)进行。 在更复杂的实现中,可能会采用单独的加法器来进行分支/跳转计算,此时在不同类型的指令中保持立即数位置不变可能并不能带来好处。 但我们还是希望尽可能降低最小实现的硬件成本。通过轮换B和J中立即数的编码位置(而不是通过硬件MUX动态地将立即数乘以2),我们将指令信号的扇出和立即数MUX的开销减少了大约2倍。 对于静态/提前编译,立即数编码的轮换倒转所带来的额外时间开销是忽略不计的。对于动态指令生成,可能会有一些额外的小开销,但最常见的前向短分支(short forward branch)都有一段连续的立即数编码。 2.4 整数计算指令 大多数整数计算指令都是对整数寄存器文件进行XLEN bits操作的。

整数计算指令要么是I-type的寄存器-立即数操作,要么是R-type的寄存器-寄存器操作,它们的目标寄存器都是rd。

所有整数计算指令都不会造成算术异常。

我们并没有在基础指令集中支持整数算术指令的溢出检查,因为许多溢出检查都能通过RISC-V分支低成本地实现。 对于无符号加法,它后面另加一条分支指令就能检查溢出了:add t0, t1, t2; bltu t0, t1, overflow 。 对于有符号加法,包括立即数加法,如果其中一个操作数符号已知,则只需在加法后加个分支:addi t0, t1, +imm; blt t0, t1, overflow。 一般的有符号加法需要在加法后面加上3条附加指令[6]: add t0, t1, t2 slti t3, t2, 0 slt t4, t0, t1 bne t3, t4, overflow 在RV64I中,通过对比对相同操作数所执行ADD和ADDW的结果,还能进一步优化关于有符号加法的检查。 整数 寄存器-立即数 指令

ADDI将符号扩展后的12bit立即数加到寄存器rs1上。算术溢出会被忽略,最终结果就是计算结果的低XLEN位[7]。(ADDI rd, rs1, 0 可用于实现伪指令MV rd,rs1 ) SLTI(set less than immediate):如果寄存器rs1小于符号扩展后的立即数,则将值1写入寄存器rd,否则将0写入寄存器rd。 SLTIU类似,但将被比较的值看作是无符号数(即,立即数首先会被进行符号扩展至XLEN bit,然后被作为无符号数进行处理)。(SLTIU rd, rs1, 1 可用于实现伪指令SEQZ rd,rs,即当且仅当rs1等于时将rd设为1,否则设为0) ANDI,ORI,XORI都是逻辑操作,分别将rs1与符号扩展(至XLEN bits)后的12bit立即数按位进行与、或和异或操作,结果放入rd。(XORI rd, rs1, -1 可用于实现伪指令NOT rd, rs,即rs1按位取反并将结果写至rd)

常量移位(shifts by a constant)被特别地编码为了I-type格式。被移位的操作数来自rs1,移位量来自I-immediate的低5bit,指令的第30bit编码了右移类型(如上图SRAI)。 SLLI是逻辑左移(左移后低位用0填充),SRLI是逻辑右移(右移位后高位用0填充),SRAI是一种算术右移(右移后用高位原先的符号位填充)。

LUI(load upper immediate)用于构造32bit常量,格式为U-type。LUI将32bit的U-immediate值送入目寄存器rd,其低12bits用0填充。 AUIPC(add upper immediate to pc)用于构造相对pc地址(pc-relative address),格式也为U-type。 AUIPC首先会生成一个32bit的偏移量(offset),其低12bits用0填充,然后将该偏移量与该指令的地址相加,所得结果放至寄存器rd。 lui和auipc的汇编语法并不会表示U-immediate的低12bits,它们总为0。 基于AUIPC,可通过1个2指令序列(2-instruction sequence)[8]来获得PC当前值加上任意偏移量后的地址,以用于改变控制流或者访问数据。 通过AUIPC与JALR中12bit立即数的组合,可以将控制转移至任何32bit的相对PC地址(PC-relative address)。

可通过将U-immediate设为0来获取当前的PC值。尽管JAL +4 指令也同样可用于获取(JAL的后续指令的)本地(local)PC值,但它可能在简单的微架构中造成流水线中断(pipeline break),或者在复杂的微架构中污染BTB结构。 整数 寄存器-寄存器 操作

RV32I定义了几个R-type的算术操作。所有操作都将rs1和rs2寄存器作为源操作数读取,并把结果写入寄存器rd。funct7和funct3字段用于选择操作类型。

ADD就是rs1与rs2相加,SUB就是rs1减去rs2。忽略溢出然后将结果的低XLEN bits写入rd。 SLT和SLTU分别执行有符号和无符号比较,如果rs1<rs2,则将1写入rd,否则写入0。(SLTU rd, x0, rs2 可用于实现伪指令SNEZ rd, rs ,即如果rs2不等于0则将rd设为1,否则设为0) AND、OR、XOR都是按位逻辑操作。 SLL、SRL和SRA分别对寄存器rs1执行逻辑左移、逻辑右移和算术右移,移位量来自寄存器rs2中的最低5bits。 NOP 指令

NOP指令不会改变任何可见的体系结构状态,除了增加pc,以及任何其它的可用性能计数器。NOP的编码为ADDI x0, x0, 0。 NOP可用于帮助将代码段(code segment)与微架构的有效地址边界(significant address boundaries)对齐,或为内联代码(inline code)的修改保留空间。 尽管存在许多编码NOP的办法,但我们还是在这里定义了NOP的规范编码,以允许微架构进行相关优化,以及提高反汇编的可读性。其它的NOP编码都可用于HINT指令(Section 2.9)。

我们选择将ADDI作为NOP编码,因为它在各种系统中可能是执行占用资源最少的了(如果不在解码(decode)中优化的话)。特别是,这个指令只会读取1个寄存器。此外,在超标量设计中,专门的ADDI功能单元也是很常见的,因为加法是最常见的基础操作,而且ADDI也可以通过地址生产单元(AGU[9])来完成计算(通过AGU中计算地址base+offset的器件),而 寄存器-寄存器 的ADD或者逻辑以及位移操作则需要额外的硬件。 2.5 控制转移指令 RV32I提供了2种类型的控制转移指令:无条件跳转(unconditional jumps)和条件分支(conditional branches)。RV32I没有体系结构上可见的延迟槽(delay slot)。

如果跳转或选中的分支(taken branch)的目标(target)上发生了指令访问错误异常(access-fault exception)或指令页错误异常(instruction page-fault exception),则该异常将在目标指令上报告,而不是跳转或分支指令上。

无条件跳转

JAL(jump and link)指令采用J-type格式,其中J-immediate编码了一个有符号偏移量(imm[20:10]),它是2Byte的倍数。 偏移量经过符号扩展后,会与这个跳转指令的地址相加以形成跳转目标地址(jump target address)。从而可以在 ±1 MiB 的范围内进行跳转。 JAL还会将跳转指令的后续指令地址(pc+4)存储到rd中。标准软件调用(standard software calling)约定将x1作为返回地址寄存器,x5作为备用链接(link)寄存器。 (普通的无条件跳转(伪指令J)可通过在JAL中设置rd=x0来实现)

备用链接寄存器可以在支持调用毫码子程序(millicode routine)(例如,那些用于在压缩代码(compressed code)中保存(save)与恢复(restore)寄存器的)的同时又保留常规的返回地址寄存器。 x5被选为备用链路寄存器,因为它在标准调用约定中是一个临时寄存器,并且其编码与常规链路寄存器(regular link register)仅有1bit之差。

间接跳转指令JALR(jump and link register)采用I-type格式。目标地址的获取方式是,将符号位扩展后的12位I-immediate与寄存器rs1相加,然后将结果中的最低位设为。 跳转指令的后续指令地址(pc+4)将被写入寄存器rd,如果不需要结果可以将rd设为x0。 无条件跳转指令都是通过相对PC寻址(PC-relative addressing)来帮助实现位置无关代码(position independent code)[10]的。 通过JALR,可以用1个2指令序列在32bit的绝对地址范围内进行任意跳转:首先用LUI将目标地址的高20bit加载至rs1,然后剩下的低位bit可以用JALR加载。 类似地,搭配AUIPC和JALR,就能在32bit的相对PC地址范围内进行任意跳转。

要注意的是,与条件分支指令不同,JALR指令并不会将12bit的立即数视为2Byte的倍数,从而避免在硬件中需要更多的立即数格式。

在实践中,大多数JALR要么将立即数设置为0,要么与LUI或者AUIPC配对,因此范围的轻微减少并不会造成明细影响。 在计算JALR目标地址时,最低有效位(least-significant bit)的清除既能轻微简化硬件,又可以利用函数指针的低位来储存辅助信息。尽管这种情况下可能会有轻微的错误检查丢失(loss of error checking),但在实践中一旦跳转到错误的指令地址,通常很快就会引发异常。 通过将base设置为rs1=x0,JALR可以用于在地址空间的任意位置上对最低2 KiB或最高2 KiB的地址区域进行单指令的子程序调用,可用于实现对小型运行时库(runtime library)的快速调用。或者,ABI也可以专门用一个通用寄存器来指向地址空间中其它位置上的库。 如果目标地址未与4 Byte的边界对齐,JAL和JALR指令将造成地址未对齐异常(instruction-address-misaligned exception)。

在支持16bit对齐指令的扩展(如压缩指令集扩展C)的计算机上,指令地址未对齐异常是不可能发生的。 返回地址预测栈(Return-address prediction stacks)是高性能取指单元的一个常见功能特性,但是需要准确检测出那些用于过程调用和返回的指令,才能使得这项技术有效(effective)。在RISC-V中,相关的指令提示是通过寄存器编号来隐式实现的:只有当 rd=x1/x5 时,JAL才会将返回地址push到返回地址栈(RAS, return-address stack)中。JALR指令对RAS的push/pop规则如Table2.1所示。

Table 2.1: 在JALR指令寄存器操作数中所编码的,关于返回地址栈的提示(hint)

有些ISA会在它的间接跳转指令中添加显式的提示位,来指导返回地址栈的操作。这里我们通过关于寄存器编码的隐式提示与调用约定来节约这些用于提示的编码空间。 当两个不同的链路寄存器(link register)(x1和x5)被指定为rs1和rd时,RAS将会同时被pop和push,以支持协程(coroutines)。如果rs1和rd是相同的链路寄存器(x1或x5),则RAS将只会被push,以允许进行序列的宏操作融合(macro-op fusion): lui ra, imm20; jalr ra, imm12(ra) 和 auipc ra, imm20; jalr ra, imm12(ra) 条件分支

所有的分支(branch)指令都是B-type指令格式的。有符号偏移量(signed offset)是用12bit的B-immediate来编码的,它是2Bytes的倍数。

偏移量经符号扩展后将加到分支指令的地址上,以生成目标地址。条件分支的范围是 ±4 KiB。

分支指令对两个寄存器进行比较,如果 rs1 等于 (不等于) rs1 ,则 BEQ (BNE) 将进行分支。 如果rs1小于rs2,则BLT和BLTU将进行分支,BLT和BLTU分别使用有符号和无符号比较。 如果rs1大等于rs2,则BGE和BGEU将进行分支,BGE和BGEU分别使用有符号和无符号比较。 (注意,只要分别对换BLT、BLTU、BGE和BGEU中的操作数,就能生成(伪指令)BGT、BGTU、BLE和BLEU)

可以用单个BLTU指令来检查有符号的数组边界(signed array bound),因为任何负索引(index)都将大于任何非负边界。 软件应进行优化,以确保顺序代码路径(sequential code path)就是最常见的路径,不常用的代码路径则放在行(line)外。软件还应假设,后向分支(backward branches)将被预测为已执行,而前向分支(forward branches)则将被预测为未执行,至少在第一次遇到它们时是如此。分支预测器应当快速学习(learn)任何可预测的分支行为。

与某些体系结构不同的是,无条件分支始终都会通过RISC-V的跳转指令(JAL with rd=x0)实现,而不是带有总为true的条件的条件分支指令。因为RISC-V的跳转(jump)也同样是相对于PC的,并且偏移范围也更宽,还不会污染条件分支预测表。

在设计上,我们选择将条件分支设计成了包含 寄存器-寄存器算术比较操作 的指令(就如PA-RISC、Xtensa和MIPS R6那样)。而不是采用条件码(condition code)(x86、ARM、SPARC、PowerPC),或者让单个寄存器与 0 比较(Alpha、MIPS)。

这个设计的动机是,我们观察到,比较-分支指令(combined compare-and-branch)更适合常规流水线,可以避免额外的条件码状态或者临时寄存器,以及减少静态代码大小和动态代码的取指通信量(fetch traffic)。 此外的另一点是,与 0 进行比较将涉及到non-trivial的电路延迟,和算术的开销是差不多的。 比较-分支指令的另一个优点是,分支能够在前端指令流中更早地被观察到,从而更早地预测。如果多个分支都具有相同的条件码,则基于条件码的设计可能会更具优势,但我们认为这种情况相对少见。

我们考虑过指令编码中的静态分支提示[11],但并没有采用。指令编码中的静态分支提示能够减轻动态预测器的压力,但需要用到更多的编码空间,也需要进行更多的软件剖析(software profiling)才能得到最佳结果,如果剖析运行(profiling runs)与生成结果的运行(production runs)并不相匹,反而会导致性能下降。

我们也考虑过条件移动(conditional moves)和谓词指令(predicated instructions)[12],但也同样没有采用。在这两者中条件移动比较简单,但它很难跟 可能导致异常(访存和浮点操作)的条件码一起使用。谓词给系统引入了额外的标志状态,以及设置/清除标志的额外指令,还有在每条指令上的额外编码开销。 条件移动指令和谓词指令都增加了乱序微架构的复杂度, 因为在谓词为false时,需要将目标体系结构寄存器的原值(original value)拷贝至重命名后的目标物理寄存器,从而引入隐式的第3个源操作数[13]。 此外,在静态编译时,通过谓词(而不是分支)来进行决策,也可能导致编译的输出结果关于编译器训练集中未包含的输入出现性能下降,特别是考虑到不可预测的分支并不多见,而且也在随着分支预测的改进进一步变少。

我们注意到,有很多可用于将 不可预测的前向短分支 转为 内部谓词编码 来避免 分支预测错误后冲刷流水线开销 的微架构技术 [7, 11, 10],包括在商用处理器中也有实例 [19]。 最简单的技术就是,只冲刷分支阴影(branch shadow)中的分支,而不是整个fetch流水线;或者通过 宽取指(wide instruction fetch)/空转取值槽(idle instruction fetch slot) 来同时从两边取指,以减小前向短分支预测错误后从中恢复的开销。 针对乱序核心的一个更复杂技术是,给分支阴影中的指令添加内部谓词,这些内部谓词由分支指令写入,从而允许分支以及后续指令的乱序推测执行 [19]。 如果目标地址(target address)未与4Bytes边界对齐,且分支条件计算结果为true,则条件分支指令将产生指令地址未对齐异常(instruction-address-misaligned exception)。如果分支条件的计算结果为false,则不会引发该异常。

在支持16bit对齐指令的扩展(如压缩指令集扩展C)的计算机上,指令地址未对齐异常是不可能发生的。 2.6 Load和Store指令 RV32I是种load-store架构,只有Load和Store指令会访问内存(memory),算术指令只会对寄存器进行操作。

RV32I提供了一个字节寻址(byte-addressed)的32bit地址空间。EEI会定义哪些指令可以合法访问哪些指令(例如,某些地址可能是只读,或者只支持字访问(word access)的)。

以x0为目标(destination)的Load必须引发某个异常(exception),并造成副作用(side effects),即便所读取的值已被抛弃[14]。

EEI会定义内存系统(memory system)是小端序还是大端序的。在RISC-V中,端序与字节地址无关(byte-address invariant)。

在端序与字节地址无关(endianness is byte-address invariant)的系统中,以下性质成立:假设某个字节以任意端序存储在内存的某个地址上,则无论如何,从该地址所读出(byte-sized load)的字节数据都将返回原先所存储的值。 在小端序配置下,多字节store(multibyte store)会将寄存器中的低位字节写到内存中对应的低位字节地址。类似地,Load会将内存中的低位字节传输到寄存器中的低位字节位置上。 在大端序配置下,多字节store会将寄存器中的高位字节写入到内存中对应的低位字节地址。类似地,Load会将内存中的高位字节传输到寄存器中的低位字节上。

Load和Store指令在寄存器与内存(memory)间传输值,Load为I-type指令;Store为S-type。有效地址(effective address)是通过 将rs1中的值加给符号扩展后的12bit偏移量(offset) 来得到的。 Load将值从内存拷贝到寄存器rd。Store将寄存器rs2的值拷贝到内存。 LW指令将32bit的值从内存加载到rd。LH从内存加载16bit的值,然后再符号扩展到32bit,再存储到rd。LHU从内存加载16bit的值,然后再零扩展到32bit,然后再存储到rd。LB和LBU的定义类似,不过加载的是8bit的值。 SW、SH和SB指令分别将寄存器rs2的低32bit、16bit、和8bit存储到内存中。 无论EEI如何,在Load和Store中,自然对齐(naturally aligned)的有效地址都不会引发地址未对齐异常(address-misaligned exception)。当有效地址未与所引用的数据类型(referenced datatype)自然对齐时(即,有效地址不能被访问字节大小整除[15]),Load和Store的行为将取决于EEI。

EEI可以保证完全支持非对齐的Load和Store,从而执行环境中的运行软件将永远不会遇到包含或者致命的地址非对齐trap(contained or fatal address-misaligned trap)。这种情况下可以直接在硬件中处理未对齐的Load或Store,或者通过一个不可见的trap陷入执行环境来实现,或者也可以根据地址来选择 不可见trap 或者是 硬件[16]。

EEI也可能并不保证未对齐的Load和Store能够透明地(invisibly)被处理(handle)。在这种情况下,未自然对齐的Load和Store可能会成功完成执行,也可能引发异常。所引发的异常可能会是地址未对齐异常(address-misaligned exception),或者访问错误异常(access-fault exception)。对于一个除非对齐外其它情况都能正常完成的内存访问,在可引发异常时,如果这个访问不应被模拟成非对齐访问,则应当引发访问错误异常(access-fault exception),而不是地址未对齐异常,例如:如果所访问的内存区域有副作用(side effect)[17][18]。如果EEI并不保证未对齐的Load和Store能透明地处理,则其必须定义好 地址未对齐(address misalignment) 所导致的异常是一个包含陷阱(contained trap)(允许执行环境中运行的软件处理(handle)该trap)还是一个致命陷阱(fatal trap)(终止执行(terminating execution))。

在移植代码时,可能偶尔要用到非对齐访问;并且,在外部的打包(packed)数据结构或是使用任何形式的packed-SIMD扩展时,支持非对齐访问也有助于提高应用性能。我们允许EEIs可选择支持常规Load和Store指令的非对齐访问的根本理由是,这能简化非对齐硬件支持的添加。 我们也想过在base ISA中禁止非对齐访问,然后分立出单独的ISA用于支持非对齐访问(让分立ISA提供用于帮助软件处理非对齐访问的特殊指令,或者为非对齐访问提供新的硬件寻址模式),但最终驳回了: 特殊指令很难用,并且会复杂化ISA,还经常引入额外的处理器状态(如SPARC VIS align address offset register)或者使对现有处理器状态的访问更复杂(如MIPS LWL/LWR partial register writes)。另外,对于面向循环(loop-oriented)的packed-SIMD代码,操作数非对齐的额外开销会促使软件针对操作数的对齐情况提供多种形式的循环,这会让代码生成更复杂,且增加循环启动的开销。 为非对齐访问提供的新硬件寻址模式会在指令编码中相当一部分的编码空间,除非寻址模式非常简单(例如,仅寄存器间接寻址[19])[20]。 就算能完成非对齐的Load或Store访问操作,这些访问也可能会执行地非常缓慢(取决于实现方式,例如通过不可见陷阱实现)。此外,与自然对齐的Load和Store不同,非对齐的Load和Store也并不一定能保证它们的操作是原子的(atomic),因此还需要额外同步来确保原子性。

我们并不要求非对齐访问必须有原子性,从而允许执行环境可以用不可见的机器trap,和软件handler来处理部分,甚至所有的非对齐访问。如果硬件支持非对齐访问,则软件可以直接通过常规的Load和Store指令来利用这一特性。然后也能根据运行时的地址是否对齐来自动优化访存。 2.7 内存排序指令

FENCE指令用于对设备I/O和访存操作进行排序(这里所排的是任何其它RISC-V Hart, 以及外部设备或者协处理器所见的顺序)。设备输入(I),设备输出(O),内存读取(R),内存写入(W)的任意组合都可被排序[21]。可以非正式地说,任何其它RISC-V hart或者外部设备,都不能看到FENCE前身集(predcessor set)中的任何操作发生在FENCE后继集(successor set)中任何操作的后面[22]。Chapter 16提供了关于RISC-V内存一致性模型(consistency model)的精确描述。

FENCE指令也会对hart所进行的内存读写进行排序,就如同外设读写所观察到(observed)的那样[23]。不过,FENCE并不会对采用任何其它信号机制(signaling mechanism)的外设所观察到的事件进行排序。

设备可能会通过一些外部通信机制来观察某内存位置上的访问,例如: 映射在内存上的控制寄存器将一个中断信号驱动给中断控制器。这种通信并不在FENCE的排序机制范围内,因而FENCE指令不能确保中断信号改变时中断控制器何时能看到。一些特殊设备可能会为了减少软件开销而提供额外的保序能力,但这超出了RISC-V内存模型的范围。 EEI将定义哪些I/O操作可能发生,尤其是哪些内存地址在Load和Store访问时所进行的是设备读写,而不是内存读写操作。例如,映射到内存的I/O设备(memory mapped I/O devices)一般是用未缓存(uncached)的Load和Store来访问的,并且这些Load和Store在FENCE中用的也是I, O来指定以进行排序,而不是R, W。指令扩展可能会描述新的I/O指令,它们在FENCE中也同样用I, O来指定以进行排序。

Table 2.2: Fence的模式编码 fence的模式字段(fence mode field) fm 定义了FENCE的语义,如果 fm=0000,则该FENCE会把所有前身集(predecessor set)中的操作排到后继集(successor set)前面。

另一个指令是FENCE.TSO,它是编码为fm=1000, predecessor=RW, successor=RW的FENCE。FENCE.TSO会将前置集中的所有Load操作排到后继集中所有的Store操作之前,并将前置集中的所有Store操作排到Load操作前面。至于剩下的,FENCE.TSO前身集中的非AMO Store[24]以及后继集中的非AMO Load则并没有被排序。

FENCE RW. RW所施加的序是FENCE.TSO的超集,因此直接忽略fm字段并将FENCE.TSO作为FENCE RW, RW实现也是正确可行的。 FENCE中的字段rs1和rd目前暂未使用,将保留给未来扩展中粒度更细的fence。为做到向前兼容,基础实现(base implementation)应当忽略这些字段,然后标准软件应当将这些字段设0;基础实现应将所有这些带保留配置且fm=0000的FENCE视为正常的FENCE,然后软件则应仅使用那些非保留的配置[25]。

我们选择了一个松散的内存模型(relaxed memory model),以利于简单的机器实现,以及将来可能出现的协处理器或加速器扩展获得更高的性能。 我们将I/O的排序从内存的R/W排序中分离了出来,使得device-driver hart能够避免不必要的序列化(serialization),并且支持用另外(alternative)的非内存路径(non-memory paths)来控制外加的协处理器或I/O设备。 简单实现中还可以选择忽略predecessor和successor字段,并始终对所有的操作进行保守隔离(conservative fence) 2.8 环境调用与断点 系统指令(SYSTEM instructions)用于访问那些可能需要权限才能访问的系统功能,它们都是I-type编码格式的指令。可以将这些指令分为两大类:对控制与状态寄存器(CSR, Control and status registers)进行原子读-写-修改(atomically read-modify-write)的指令;以及所有其它潜在的特权指令(privileged instructions)。CSR指令会在Chapter 10中介绍,下面介绍的是基础非特权指令(base unprivileged instructions)。

系统指令的定义允许简单实现(simpler implementation)的trap总是只需陷入到单一的软件trap handler上[26]。复杂的 实现可能会在硬件中执行更多各种系统指令。

这两条指令都会对执行环境(the supporting execution environment)造成一个精确请求陷阱(precise requested trap)。

ECALL指令用于向执行环境发出服务请求(service request),服务请求的参数该如何传递将由EEI定义,这些参数通常位于整数寄存器文件中所定义的位置上。 EBREAK用于将控制返回到调试环境(debugging environment)。 ECALL和EBREAK的曾用名分别是SCALL和SBREAK,它们(如ECALL和SCALL)的功能和编码都没变,但新名称反映了它们的使用并不局限于调用supervisor-level OS或者调试器(debugger)。

EBREAK主要用于debugger,使执行暂停并退回到debugger中[27]。 在标准gcc编译器中, EBREAK也用于标记那些不该执行的代码路径。 EBREAK也可用于支持“半主机(semihosting)”,其执行环境中包括一个debugger,该debugger能通过围绕EBREAK指令所构建的备用系统调用接口提供服务[28]。 由于RISC-V base ISAs并不提供多条EBREAK指令,RISC-V半主机需要用下面一段这样的特殊指令序列来区分 由debugger插入的EBREAK 和 半主机EBREAK: slli x0, x0, 0x1f # Entry NOP ebreak # Break to debugger srai x0, x0, 7 # NOP encoding the semihosting call number 7 要注意的是,这三条必须都是 32bit 宽的指令,就是说它们不可以是Chapter 17中所描述的 16bit 压缩指令。 shift NOP 指令仍可用作 HINTs。 半主机是服务调用(service call)的一种形式,在现有ABI下,将它编码为ECALL可能更自然,但这将要求debugger能够拦截ECALLs(这也是最近debug标准中新增的内容)。我们打算转而使用标准ABI下的ECALLs,从而半主机能与现有标准共享一个服务ABI(service ABI)。 我们注意到,在ARM较新的处理器设计中,也开始使用了SVC,而不是通过BKPT来进行半主机调用。 2.9 HINT指令 RV32I 为 HINT指令保留了很大一片编码空间,HINT指令(提示指令)通常用于向微架构传达性能提示,并且就像NOP一样,除推动pc以及任何可用性能计数器外,并不改变任何体系结构可见的状态。具体实现可以选择忽略这些提示编码(encoded hints)[29]。

大多RV32I HINTs都被编码为了 rd=x0 的整数计算指令,除此之外,predecessor set或successor set为空且 fm=0 的 FENCE指令 也被编码为了 RV32I HINTs。

这些HINT的编码在选择上是为了方便让简单的实现能够完全忽略HINT:将HINT作为一条普通且不会改变体系结构状态的指令来执行。例如目标寄存器为x0的ADD就是一个HINT;其中5bits的rs1和rs2字段则用于编码HINT的参数。简单实现可以直接将其执行为一个向x0写入rs1与rs2之和的ADD,该指令不会造成架构上可见的影响。 另一个HINT的例子是pred字段和fm字段皆为0的FENCE,其使用succ,rs1和rd字段来编码HINT参数。而简单的实现就可以直接将其作为一个FENCE执行,由于其predecessor set和successor set交集为空,因此不会强加任何内存排序,从而同样在体系结构上没有可见影响。 Table 2.3 列出了所有的RV32I HINT 编码位点(code points)。91%的HINT空间都被保留给了标准HINTs(standard HINTs),但目前它们都还没被定义。剩余的HINT空间则保留给定制HINTs:标准HINTs永远不会定义在该子空间上。

我们预期的标准hints包括:关于内存系统时间和空间局域性的提示(memory-system spatial and temporal locality hints),分支预测提示,线程调度提示,安全性标签(security tags),以及用于模拟/仿真的仪表标志(instrumentation flags for simulation/emulation)。

Table 2.3 RV32I HINT指令

参考 ^Subsets of the base integer ISA might be useful for pedagogical purposes, but the base has been defined such that there should be little incentive to subset a real hardware implementation beyond omitting support for misaligned memory accesses and treating all SYSTEM instructions as a single trap. ^指C扩展指令集中的16bits指令格式 ^flat/hierarchical,指的是regfile的微架构结构 ^变成了imm[12:1]而不是imm[11:0] ^就是说跟其他格式中立即数的格式编码位置都一样,例如最高位都是符号位 ^原理是,注意到当且仅当另一个操作数为负时,加法结果应小于其中的一个操作数 ^Arithmetic overflow is ignored and the result is simply the low XLEN bits of the result ^就是含有2条指令的指令序列 ^Address Generation (functional) Units ^位置无关代码即,代码的行为和它在内存中的位置无关。这里是说,跳转指令的跳转地址是相对(于PC)而不是绝对的。 ^static branch hints in the instruction encoding ^谓词指令(predicated instruction)就是,谓词指令会指定一个源操作数(通常是boolean)作为它的谓词:如果谓词为真,则执行该指令的操作,否则不执行(NOP) ^第一次看这句话的时候直接绕晕了。我的理解是,在乱序微架构下,发射的谓词指令需要分谓词为true/false两种情况,因此这个指令的目寄存器值有新/旧值两种情况。从而,后续指令如果需要引用这个寄存器值,就可能给后续指令引入“第三个隐式的源操作数” ^Loads with a destination of x0 must still raise any exceptions and cause any other side effects even though the load value is discarded ^the effective address is not divisible by the size of the access in bytes ^or possibly a combination of hardware and invisible trap depending on address. ^For a memory access that would otherwise be able to complete except for the misalignment, an access exception can be raised instead of an address-misaligned exception if the misaligned access should not be emulated, e.g., if accesses to the memory region have side effects. ^我对这句话的理解是,反证地,如果引发的是非对齐异常而不是访问错误异常的话,那么handler接到这个异常后,可能EEI还是会让这个(非对齐)访问操作发射出去,这可能导致某些不该发射的访问被handler发射出去了... 所以在不该被发射的情况下,还是应当引发一个访问错误异常,防止这个访问被发射出去。如果我的理解有问题,请立即在评论区指出! ^register indirect only ^New misaligned hardware addressing modes take considerable space in the instruction encoding or require very simplified addressing modes (e.g., register indirect only). ^The FENCE instruction is used to order device I/O and memory accesses as viewed by other RISCV harts and external devices or coprocessors. Any combination of device input (I), device output (O), memory reads (R), and memory writes (W) may be ordered with respect to any combination of the same. ^Informally, no other RISC-V hart or external device can observe any operation in the successor set following a FENCE before any operation in the predecessor set preceding the FENCE. ^The FENCE instruction also orders memory reads and writes made by the hart as observed by memory reads and writes made by an external device. ^AMO是原子指令的opcode,见原书Chapter 8 ^Base implementations shall treat all such reserved configurations as normal fences with fm=0000, and standard software shall use only non-reserved configurations. ^The SYSTEM instructions are defined to allow simpler implementations to always trap to a single software trap handler. ^EBREAK was primarily designed to be used by a debugger to cause execution to stop and fall back into the debugger. ^Another use of EBREAK is to support “semihosting”, where the execution environment includes a debugger that can provide services over an alternate system call interface built around the EBREAK instruction. ^Implementations are always allowed to ignore the encoded hints. 来自专栏 一些计算机科学书的目录与序言翻译

VN Vortex · 32 篇内容 【翻译】可综合SystemVerilog教程(1) / Synthesizing SystemVerilog 文章 · 8 赞同 · 1 评论 申请转载 评论

写下你的评论... icon

Reisen1969 commented 2 years ago

【翻译】 RISC-V 非特权规范文档 第3章:"Zifencei"取指屏障 ver2.0 Ratified // 版本:20191214-draft

VN Vortex

时间会冲刷一切。 【翻译】 RISC-V 非特权规范文档 第3章:"Zifencei"取指屏障 ver2.0 Ratified // 版本:20191214-draft 7 人赞同了该文章 原文:The RISC-V Instruction Set Manual Volume I: Unprivileged ISA Chapter 3 "Zifencei" Instruction-Fetch Fence, Version 2.0 (Document Version 20191214-draft) November 19, 2021

虽然是翻译但其实本质上还是个人笔记... 所以一切请以最新的官方标准文档为准。其实之前也有大佬翻译过,但是后来版本更新了,所以我想在加深理解RISC-V spec的同时顺便翻译翻译~ 如果能帮到在看的你就更好了!!

当然... 碍于个人水准,出现疏漏肯定在所难免。如果发现了还请务必在评论区指出,我会非常感谢>_<!!!!!!!

如果注释了大段原文,则说明这段原文被严重地重新表述了,建议在阅读本文的同时打开原版文档,并与原文相互对照。

请务必指出的包括:

翻译错误,和原文对不上,或者没有表达出原文要表达的东西/语气;或者术语/单词翻译错误。 错字错句(由于拼音输入法笨笨的,肯定有很多词打错了)。 读起来不顺畅。比如某个句子特别特别长,以至于看到了尾巴又忘了开头,这种肯定是没翻译好的,习惯上,我会通过用逗号/空格来拆分这种句子,或者彻底调整句子结构。 更好的翻译,如果读者发现某句话可以翻译地更加优雅,也请务必指出。 需要加注原文。首先术语肯定是要加注原词;其次就是某些句子,比较重要或者原文读起来更舒服,也需要加注原句。 我也不知道为什么反正就是觉得怪怪的:这种情况也要指出! 其它想指出的东西。 以下为正文:

本章定义“Zifencei”扩展,其包含FENCE.I指令,该指令用于在(同一hart上的)指令内存写入(writes to instruction memory)与取指(instruction fetches)间提供显式同步。目前该指令是唯一能确保St

Reisen1969 commented 2 years ago

【翻译】 RISC-V 非特权规范文档 第3章:"Zifencei"取指屏障 ver2.0 Ratified // 版本:20191214-draft

VN Vortex

时间会冲刷一切。 【翻译】 RISC-V 非特权规范文档 第3章:"Zifencei"取指屏障 ver2.0 Ratified // 版本:20191214-draft 7 人赞同了该文章 原文:The RISC-V Instruction Set Manual Volume I: Unprivileged ISA Chapter 3 "Zifencei" Instruction-Fetch Fence, Version 2.0 (Document Version 20191214-draft) November 19, 2021

虽然是翻译但其实本质上还是个人笔记... 所以一切请以最新的官方标准文档为准。其实之前也有大佬翻译过,但是后来版本更新了,所以我想在加深理解RISC-V spec的同时顺便翻译翻译~ 如果能帮到在看的你就更好了!!

当然... 碍于个人水准,出现疏漏肯定在所难免。如果发现了还请务必在评论区指出,我会非常感谢>_<!!!!!!!

如果注释了大段原文,则说明这段原文被严重地重新表述了,建议在阅读本文的同时打开原版文档,并与原文相互对照。

请务必指出的包括:

翻译错误,和原文对不上,或者没有表达出原文要表达的东西/语气;或者术语/单词翻译错误。 错字错句(由于拼音输入法笨笨的,肯定有很多词打错了)。 读起来不顺畅。比如某个句子特别特别长,以至于看到了尾巴又忘了开头,这种肯定是没翻译好的,习惯上,我会通过用逗号/空格来拆分这种句子,或者彻底调整句子结构。 更好的翻译,如果读者发现某句话可以翻译地更加优雅,也请务必指出。 需要加注原文。首先术语肯定是要加注原词;其次就是某些句子,比较重要或者原文读起来更舒服,也需要加注原句。 我也不知道为什么反正就是觉得怪怪的:这种情况也要指出! 其它想指出的东西。 以下为正文:

本章定义“Zifencei”扩展,其包含FENCE.I指令,该指令用于在(同一hart上的)指令内存写入(writes to instruction memory)与取指(instruction fetches)间提供显式同步。目前该指令是唯一能确保Store可见于hart的同时也可见于该hart的取指的标准机制。

我们考虑过“存指令字(store instruction word)”指令(如MAJC[21]),但并未将其纳入标准。JIT编译器可能会在单个FENCE.I指令前生成一个较大的指令踪迹(trace of instructions)[1],并将转换后的指令写至已知不在I-Cache中的内存区域上,以摊销指令缓存监听/无效化(instruction cache snooping/invalidation)的开销。 FENCE.I的设计能用于支持多种具体实现。简单实现可以在执行FENCE.I时冲刷(flush)本地指令缓存 和 指令流水线。 更复杂的实现可以在每次数据(指令)缓存缺失时监听指令(数据)缓存,或者采用一个包含性的统一私有L2缓存(inclusive unified private L2 cache),使得在它们被本地store指令写入时会让主缓存中的行无效化[2]。若指令与数据缓存以这种方式保持一致(coherent),又或者内存系统仅由未缓存的RAMs(uncached RAMs)组成的话,则FENCE.I只需冲刷fetch pipeline即可。 FENCE.I指令曾是base I指令集的一部分。但有两个主要原因促使其从base中分离了出来。尽管直至目前其仍是维持取指一致性(coherence)的唯一标准方法。 首先我们注意到,在某些系统上FENCE.I的实现成本会很高(目前内存模型任务小组(memory model task group)正在讨论替代机制)。特别是对那些指令和数据缓存没有一致性(incoherent I-Cache and incoherent D-Cache),又或者在重填指令缓存时不监听相关(coherent)数据缓存的设计。这种情况下,一旦遇到FENCE.I指令就必须彻底冲刷这两个缓存。并且在统一缓存(unified cache)或外部内存系统前面存在多级指令和数据缓存时,这个问题还会加剧[3]。 其次,这个指令还不足以在unix-like OS环境中的user-level上提供使用。FENCE.I只能同步本地hart,但OS可能在FENCE.I后将user hart重新调度到不同的physical hart上,从而导致OS在每次执行上下文切换时(context migration)需要执行额外的FENCE.I。出于该原因,Linux标准ABI已经在user-level中移除了FENCE.I,目前取指一致性(coherence)需要通过系统调用来维持,这样能让OS最大程度地减少当前系统所需执行FENCE.I的指令数,并对未来所改进的取指一致性机制(coherence)保持前向兼容。 目前关于未来取指一致性的在讨论方案包括提供受限版的FENCE.I,其仅以一个特定地址(由rs1提供)为目标,以及和/或允许软件使用有关machine-mode缓存维护操作的ABI[4]。

FENCE.I指令用于在指令和数据流之间进行同步[5]。RISC-V本身并不保证取指一定能看见对指令存储器的Store操作,直至hart执行了一个FENCE.I指令。FENCE.I指令保证RISC-V hart上随后的取指可看见任何在此之前的Store操作。 FENCE.I并不保证多处理器系统中其它RISC-V hart上的取指能看见本地hart的Store。要让所有RISC-V hart都能看见对指令内存的Store,除要求所有远程RISC-V harts(remote RISC-V harts)执行一个FENCE.I外,执行该写入的hart还必须在此之前执行一个数据FENCE[6]。 FENCE.I指令中的未使用字段imm[11:0], rs1, rd保留给后续扩展中的细粒度FENCE。为保持前向兼容,基础实现(base implementations)应当忽略这些字段,标准软件则应将这些字段归零。

FENCE.I仅会对hart本身的取指 排序Store,因此应用程序代码应该仅在应用程序线程不会被迁至不同hart时才依赖FENCE.I。EEI可提供有效的多处理器指令流同步机制。

参考 ^指令跟踪(instruction trace) ^A more complex implementation might snoop the instruction (data) cache on every data (instruction) cache miss, or use an inclusive unified private L2 cache to invalidate lines from the primary instruction cache when they are being written by a local store instruction. ^First, it has been recognized that on some systems, FENCE.I will be expensive to implement and alternate mechanisms are being discussed in the memory model task group. In particular, for designs that have an incoherent instruction cache and an incoherent data cache, or where the instruction cache refill does not snoop a coherent data cache, both caches must be completely flushed when a FENCE.I instruction is encountered. This problem is exacerbated when there are multiple levels of I and D cache in front of a unified cache or outer memory system. ^Future approaches to instruction-fetch coherence under discussion include providing more restricted versions of FENCE.I that only target a given address specified in rs1, and/or allowing software to use an ABI that relies on machine-mode cache-maintenance operations ^The FENCE.I instruction is used to synchronize the instruction and data streams ^the writing hart also has to execute a data FENCE before requesting that all remote RISC-V harts execute a FENCE.I. 来自专栏 一些计算机科学书的目录与序言翻译

VN Vortex · 32 篇内容 【翻译】可综合SystemVerilog(2) / Synthesizing SystemVerilog 文章 · 5 赞同 · 0 评论 申请转载 评论 1

写下你的评论... icon

伊卡洛斯

码一下先,每次看设计文档都能学到东西 2021-11-23 ​

查看全部评论​

Reisen1969 commented 2 years ago

【翻译】RISC-V 特权规范文档 第1章:引言 // 文档版本:20211105-signoff

VN Vortex

时间会冲刷一切。 【翻译】RISC-V 特权规范文档 第1章:引言 // 文档版本:20211105-signoff 8 人赞同了该文章 原文:The RISC-V Instruction Set Manual Volume II: Privileged Architecture Chapter 1: Introduction (Document Version 20211105-signoff) November 19, 2021

虽然是翻译但其实本质上还是个人笔记... 所以一切请以最新的官方标准文档为准。其实之前也有大佬翻译过,但是后来版本更新了,所以我想在加深理解RISC-V spec的同时顺便翻译翻译~ 如果能帮到在看的你就更好了!!

当然... 碍于个人水准,出现疏漏肯定在所难免。如果发现了还请务必在评论区指出,我会非常感谢>_<!!!!!!!

请务必指出的包括:

翻译错误,和原文对不上,或者没有表达出原文要表达的东西;或者术语/单词翻译错误。 错字错句(由于拼音输入法笨笨,肯定有很多词打错了)。 读起来不顺畅。比如某个句子特别特别长,以至于看到了尾巴又忘了开头,这种肯定是没翻译好的,需要用逗号/空格隔开,或者彻底调整句子结构。 更好的翻译,如果读者发现某句话可以翻译地更加优雅,也请务必指出。 需要加注原文。首先术语肯定是要加注原词;其次就是某些句子,比较重要或者原文读起来更舒服,也需要加注原句。 我也不知道为什么反正就是觉得怪怪的:这种情况也要指出! 其它想指出的东西。 第一章目录:

RISC-V 特权软件栈术语(RISC-V Privileged Software Stack Terminology) 权级(Privilege Levels) 调试模式(Debug Mode) 以下为正文:

这篇文档描述的是RISC-V特权体系结构(RISC-V privileged architecture),涵盖了RISC-V系统在非特权ISA(unprivileged ISA)之外的所有方面,包括特权指令,以及要运行操作系统和连接外部设备所需的附加功能。

我们关于设计决策的评注格式如本段所示,诺读者仅对specification(规范)本身感兴趣,则可跳过这些非规范的文本。

这里我们要指明,本文所描述的整个权级设计也可被取代为完全不同的权级设计,且无需改变非特权ISA,甚至可能ABI都不用改。特别地,这个特权规范是为运行现有流行操作系统而设计的,因此所展现的是基于等级的传统保护模型(level-based protection model)。替代的特权规范也可以展现出其它跟灵活的保护域模型(protection-domain models)。 为简化表述,文本会写成就好像这是唯一可行特权架构的样子。 1.1 RISC-V 特权软件栈术语 这节介绍我们用于描述 可能用于RISC-V特权软件栈(privileged software stacks)的各种广泛组件 的术语。

Figure 1.1: 支持各种特权执行形式的不同实现栈 Figure 1.1 展示了RISC-V支持的一些软件栈。左边是一个简单的系统,它仅支持在应用程序执行环境(AEE, Application Execution Environment)上运行单个应用程序。应用程序会按照特定的应用程序二进制接口(ABI)进行编写,ABI包括所支持的user-level ISA,以及一组用于与AEE互动的ABI调用(ABI calls)。

ABI对应用程序隐藏了AEE的细节,以便AEE能够更加灵活地实现。相同ABI也可能在多个不同主机操作系统上原生(natively)地实现,或者在支持不同原生ISA的机器上由其运行的user-level模拟环境(emulation environment)支持。

在图表惯例中,我们用带白色文本的黑框表示抽象接口,以将它们与用于实现接口的具体组件示例分开。 中间的构造则展示了一个传统的操作系统(OS),其支持多个应用的多程序执行(multiprogrammed execution)。其中每个应用都通过ABI与OS通信,AEE由OS提供。

RISC-V操作系统通过supervisor二进制接口(SBI)与supervisor执行环境(SEE)交互,就像应用程序通过ABI与AEE交互那样。SBI同时包括user-level ISA和supervisor-level ISA,以及一组SBI功能调用(SBI function calls)。在所有的SEE实现间使用单个SBI,可以允许单个OS二进制映像(binary image)运行在任何SEE上。SEE既可以是低端硬件平台上的简单boot loader和BIOS-style IO系统,也可以是高端服务器上由hypervisor提供的虚拟机,亦或者是架构模拟环境中主机操作系统上的轻型转换层(thin translation layer)。

许多supervisor-level的ISA都并没有将SBI与执行环境和/或硬件平台分开,使得虚拟化以及新硬件平台的提出变得复杂。 最右边的展示了一个虚拟机监视器(virtual machine monitor)的构造,其hypervisor上支持了多个多程序OS。其中每个OS都通过SBI与hypervisor通信,SEE由hypervisor提供。hypervisor通过hypervisor二进制接口(HBI)与hypervisor执行环境(HEE)通信,以对hypervisor隔离硬件平台的细节。

ABI,SBI,HBI仍然处于WIP(Work-In-Progress)状态下。但我们正在优先考虑对Type-2 hypervisors的支持,其中SBI由S-mode OS递归地提供。 RISC-V ISA的硬件实现通常还需要特权ISA之外的额外功能,以用于支持各种执行环境(AEE,SEE或HEE)。

1.2 权级 任何时候,RISC-V硬件线程(hart)都是在,由一或多个CSRs(control and status registers)所编码的权级模式下运行的。目前已经定义了三个RISC-V权级,如Table 1.1所示。

Table 1.1: RISC-V权级 在描述中,我们会尝试将 代码所面向的权级 与 代码实际运行的权级 分开,尽管两者往往是牢牢系在一起的[1][2]。例如,一个supervisor-level的操作系统既可以在带有三种特权模式的系统上以supervisor-mode运行,也可在具有二或多种特权模式的经典虚拟机监视器上以user-mode运行。这两种情况中所用的supervisor-level操作系统的二进制代码可以是相同的,按照supervisor-level SBI编写的,从而能按照预期使用supervisor-level的特权指令和CSRs。对于运行在user-mode的guest OS,所有的supervisor-level操作都会陷入(trapped)给权级更高的SEE,由其模拟(emulate)。 machine-level权级最高,也是RISC-V硬件平台唯一必须有的权级。在machine-mode(M-mode)下所运行的代码一般都是默认被信任的,因为它可对机器进行最低级的访问。M-mode可用于管理RISC-V上的安全执行环境(secure execution environment)。user-mode(U-mode)和supervisor-mode(S-mode)则分别用于传统应用程序以及操作系统。

每个权级都各有一组特权ISA的核心扩展,以及相关可选扩展和变体[3]。例如,由machine-mode支持的一个关于内存保护的可选标准扩展。同样,也可通过扩展supervisor-mode来支持type-2 hypervisor,见Chapter 8。

如Table 1.2所示,具体实现(implementation)可以选择提供1至3种特权模式,具体实现能为控制实现成本而(cost)在隔离级别上做出取舍[4]。

Table 1.2 支持的特权模式组合 所有硬件实现都必须提供M-mode,因为这是唯一能无限制访问整个机器的模式。最简单的RISC-V实现可以只提供M-mode,尽管这不能提供针对不正确或恶意应用代码的保护。

可选的PMP[5]设施中的锁定特性(lock feature)能提供一些有限的保护,即便是只实现了M-mode。 许多RISC-V还会再支持一个U-mode,以保护 系统中的某些部分 不受 其余部分中的应用程序代码 的影响[6]。可通过添加S-mode以在supervisor-level OS和SEE间提供隔离。

hart通常在U-mode下运行应用程序代码,直到遇到某些trap(如supervisor call或者timer interrupt)而迫使其切换至trap handler(trap handler通常在更高权级运行)。然后hart将运行trap handler,并最终在造成trap的原指令 上/后 以U-mode恢复执行(resume execution)。

会提高权级的trap被称之为vertical trap(垂直陷阱),而保持相同权级的trap则称之为horizontal trap(水平陷阱)。RISC-V提供了能灵活路由到各个不同权级层的trap。

也可以用vertical trap来实现horizontal trap,只要将控制返回给特权模式更低的horizontal trap handler。 1.3 调试模式 具体实现(implementation)中还可以包含一个debug mode,以支持片外调试和/或制造测试。debug mode(D-mode)可被看作为一个额外的特权模式,它的权限甚至比M-mode还多。分离出的debug specification中描述了debug mode下RISC-V hart的操作。debug mode保留了一些CSR地址,这些地址只能在D mode下访问,此外也可以在平台上保留一些物理地址空间。

参考 ^In the description, we try to separate the privilege level for which code is written, from the privilege mode in which it runs, although the two are often tied. ^这句话我的理解是,比如某些代码可能是是面向supervisor-mode编写的,就是说这个代码本身期望运行在supervisor-mode下,但实际上却给运行在了user-mode下。 ^Each privilege level has a core set of privileged ISA extensions with optional extensions and variants. ^Implementations might provide anywhere from 1 to 3 privilege modes trading off reduced isolation for lower implementation cost, as shown in Table 1.2. ^PMP指Physical Memory Protection,即物理内存保护机制,请见本文档中后面的有关章节。 ^Many RISC-V implementations will also support at least user mode (U-mode) to protect the rest of the system from application code. 来自专栏 一些计算机科学书的目录与序言翻译

VN Vortex · 32 篇内容 【翻译】可综合SystemVerilog(2) / Synthesizing SystemVerilog 文章 · 5 赞同 · 0 评论 申请转载 评论

写下你的评论... icon

Reisen1969 commented 2 years ago

【翻译】RISC-V 特权规范文档 第2章:控制与状态寄存器CSRs / ver.20211105-signoff

VN Vortex

时间会冲刷一切。 【翻译】RISC-V 特权规范文档 第2章:控制与状态寄存器CSRs / ver.20211105-signoff 4 人赞同了该文章 原文:The RISC-V Instruction Set Manual Volume II: Privileged Architecture Chapter 2: Control and Status Registers(CSRs) Document Version 20211105-signoff

只是个人笔记,有错误还请指出。

第二章目录:

CSR地址映射协定(CSR Address Mapping Conventions) CSR列表(CSR Listing) CSR字段规范(CSR Field Specifiations) CSR位宽调控(CSR Width Modulation) 以下为正文:

主操作码SYSTEM(SYSTEM major opcode)用于编码RISC-V ISA中的所有特权指令。可将这些指令分为两大类:第一类是那些对控制与状态寄存器(CSRs)进行原子性读-改-写(atomically read-modify-write)的指令,它们定义于Zicsr。以及所有的其它特权指令。

特权架构需要Zicsr扩展;需要哪些特权指令则取决于特权架构的功能集。

除了本手册卷I所述的用户级程序状态(user-level state)外,某些具体实现可能还会含有更多的CSR,并可在部分权级下通过用户级(user-level)手册中描述的CSR指令访问。

在本章中,我们将描绘CSR地址空间(CSR address space)。后续的章节则各自按照权级分别描述CSRs的功能,及通常与特定权级密切相关的其它特权指令。需注意的是,尽管CSRs和指令们通常关联在某个特定权级上,它们也能在任何更高权级上访问(accessible)[1]。

对标准CSRs的读取不会造成副作用(side effect),但写入则有可能。

2.1 CSR地址映射协定 标准RISC-V ISA为多达4096个的CSRs预留了一个12bit地编码空间(csr[11:0])。按照协定,CSR地址的高4bits(csr[11:0])用于编码CSRs在各个权级上的可读写性,见Table 2.1。最高2bit(csr[11:0])用于编码该寄存器是可读写(00,01或10)还是只读(11)。接下来的2bit(csr[9:8])编码了允许访问该CSR的最低权级。

CSR地址协定中用CSR地址的高位来编码默认访问权限,这简化了硬件中的错误检查,并提供了更大的CSR空间,但确实也限制了CSRs在地址空间中的映射。 具体实现中可能允许较高权级(more-privleged level)在某些CSR上捕获(trap)较低权级(less-privleged level)的访问,来拦截这些访问。这一变动应当对较权级的软件透明。 试图访问一个不存在的CSR将造成一个非法指令异常(illegal instruction exception)。试图在不当的权级下访问CSR,或者尝试写入一个只读寄存器也都将造成非法指令异常。可读写寄存器中也可能包含一些只读位,此时忽略对只读位的写入。

Table 2.1还指定了哪些CSR地址是标准用途,而哪些是自定义用途的。用于自定义的CSR地址不会在未来标准扩展中被重新定义。

machine-mode standard read-write CSRs 0x7A0-0x7BF保留给debug系统使用。machine-mode可访问其中0x7A0-0x7AF这些CSRs,0x7B0-0x7BF只对debug mode可见。实现中在machine-mode下访问后一组寄存器时应造成非法指令异常。

高效的虚拟化需要尽量地在虚拟环境中原生运行(run natively)指令,同时任何特权访问都将trap到虚拟机监视器[1]。对于较低权级CSR上只读的CSRs,它们会在一些可以读写它们的较高权级上被隐藏至(shadowed)独立的CSR地址。从而在能保证捕捉所有非法访问的前提下,避免对允许的低权级访问的误捕捉(trap)[2] 目前只有counters是被隐藏的CSRs。 2.2 CSR列表(CSR Listing) Table 2.2-2.6 列出了目前已分配有CSR地址的CSRs。timer、counter、float-point CSRs都是标准的非特权CSRs。正如后续章节所述,其它寄存器都用于特权代码。需注意的是,并非所有寄存器都需要被实现。

Table 2.1: RISC-V CSR地址范围分配

Table 2.2: 目前已分配的RISC-V非特权CSR地址

Table 2.3: 目前已分配的RISC-V主管级CSR地址。

目前已分配的RISC-V 监管与VS CSR地址

目前已分配的RISC-V机器级CSR地址

目前已分配的机器级CSR地址。 2.3 CSR字段规范(CSR Field Specifications) 以下定义和缩写用于指定CSR中各个字段的行为。

写时保护保留值,读时忽略值(WPRI, Reserved Writes Preserve Values, Reads Ignore Values)

某些可读写的完整字段是保留给将来使用的。为前向兼容,软件应忽略所读取的值,并在向同一寄存器其它字段写入值时保留这些字段中的值。寄存器描述中会将这些字段标记为WPRI。

为了简化软件模型,任何在未来后向兼容(backward-compatible)的对CSR保留字段的定义 ,都必须考虑并应对好 用非原子性读-修改-写序列更新CSR中其它字段 这种可能性[3]。或者,最开始的CSR定义就必须明确说明子字段只能被原子地更新,这可能会需要一个双指令序列来清除/设置bit,并在中间值不合法时可能造成问题。 只读写合法值(WLRL, Write/Read Only Legal Values)

某些可读写的CSR字段(read/write CSR fields)仅定义了其部分可行二进制编码的行为[4][5]。除合法值外,软件不应向此类字段写入任何其它内容,也不应假设读取能返回合法值,除非最后一次写入的是合法值,或在其它操作(如重置)将寄存器设为合法值后,在这次读取前并没有被写入。寄存器描述中会将这些字段标记为WLRL。

硬件具体实现中所提供的状态bit数只要能区分所支持的值就行了,但在读取时必须返回任何所支持值的完整bit编码[6]。 硬件在指令试图将不支持的值写入WLRL字段时,允许但并不要求引发非法指令异常。如果上次写入的是非法值,则具体实现在读取WLRL字段时可以返回任意的bit组合(bit pattern),但所返回的值应取决于 非法写入的值 和 字段在写入前的值。

写任意值,读合法值(WARL, Write Any Values, Reads Legal Values)

某些可读写CSR字段仅定义了其二进制编码的部分[7][8],但允许写入任何值,且同时保证读取时将返回合法值。假设在所写入的CSR在写入时没其它副作用,则可通过尝试写入预期的配置值然后读取并查看值是否被保留,来确定其所支持的值范围。寄存器描述中会将这些字段标记为WARL。

具体实现在向WARL字段写入不支持的值时不会引发异常。最后一次写入的是非法值时,实现可在读取WARL字段时返回任意合法值,但所返回的合法值应取决于非法写入值和hart的体系结构状态。

2.4 CSR位宽调控(CSR Width Modulation) 如果CSR的位宽被改变(例如,通过改变MXLEN或UXLEN),则除非另有规定,新位宽的CSR的可写字段和位的值应根据以下算法,由旧位宽的CSR来决定:

旧位宽CSR的值复制到相同位宽的临时寄存器中。 对于旧位宽CSR的只读位,临时寄存器中相同位置的位设为零。 临时寄存器的位宽变为新位宽。如果新位宽W小于旧位宽,则保留临时寄存器最低有效的W位,将更高的有效位丢弃。如果新位宽大于旧位宽,则临时寄存器通过零扩展(zero-extension)扩展新位宽。 新位宽CSR的每个可写字段取自临时寄存器中相同位置的位。 更改CSR的位宽这一操作并非对CSR的读取或写入,因此不触发任何副作用。

参考 ^In addition to the user-level state described in Volume I of this manual, an implementation may contain additional CSRs, accessible by some subset of the privilege levels using the CSR instructions described in the user-level manual. In this chapter, we map out the CSR address space. The following chapters describe the function of each of the CSRs according to privilege level, as well as the other privileged instructions which are generally closely associated with a particular privilege level. Note that although CSRs and instructions are associated with one privilege level, they are also accessible at all higher privilege levels. ^This avoids trapping permitted lower-privilege accesses while still causing traps on illegal accesses. ^To simplify the software model, any backward-compatible future definition of previously reserved fields within a CSR must cope with the possibility that a non-atomic read/modify/write sequence is used to update other fields in the CSR. ^例如假如这里有一个3bit的字段,仅指定000和001的行为,剩下的010, 011... 111都是未定义的,那么前面的000和001就是已指定行为了的部分可能编码。 ^Some read/write CSR fields specify behavior for only a subset of possible bit encodings ^还是例如,这里有一个3bits的WLRL字段,其合法值只有000和001,那么实现时只需要提供1bit的寄存器来存储最低bit就行了,但在读取时仍应读出完整的3bit数据,即高2bit硬接到0。 ^例如一个3bit的字段,其仅定义了000和001两个编码。 ^Some read/write CSR fields are only defined for a subset of bit encodings 来自专栏 一些计算机科学书的目录与序言翻译

VN Vortex · 32 篇内容 【翻译】可综合SystemVerilog(2) / Synthesizing SystemVerilog 文章 · 5 赞同 · 0 评论 申请转载 评论

写下你的评论... icon

Reisen1969 commented 2 years ago

【翻译】RISC-V 特权规范文档 第3章:机器级ISA ver 1.12 Frozen (3.1.1-3.1.5) / ver.20190608-Priv-MSU-Ratified

VN Vortex

时间会冲刷一切。 【翻译】RISC-V 特权规范文档 第3章:机器级ISA ver 1.12 Frozen (3.1.1-3.1.5) / ver.20190608-Priv-MSU-Ratified 8 人赞同了该文章 原文:The RISC-V Instruction Set Manual Volume II: Privileged Architecture Chapter 3: Machine-Level ISA, Version 1.12 Document Version 20190608-Priv-MSU-Ratified

只是个人笔记,有错误还请指出。

第三章目录(█本文包含3.1.1~3.1.5,即关于机器信息与hart ID的CSRs):

3.1 机器级CSRs(Machine-Level CSRs) █3.1.1 机器ISA寄存器 "misa"(Machine ISA Register misa) █3.1.2 机器厂商ID寄存器"mvendorid"(Machine Vendor ID Register mvendorid) █3.1.3 机器架构ID寄存器"marchid"(Machine Architecture ID Register marchid) █3.1.4 机器实现ID寄存器"mimpid"(Machine Implementation ID Register mimpid) █3.1.5 硬件线程ID寄存器"mhartid"(Hart ID Register mhartid) 3.1.6 机器状态寄存器"mstatus"和"mstatush"(Machine Status Registers (mstatus and mstatush)) 3.1.6.1 "mstatus"中的特权中断与全局中断使能(Privilege and Global Interrupt-Enable Stack in mstatus register) 3.1.6.2 "mstatus"中的base ISA控制(Base ISA Control in mstatus Register) 3.1.6.3 "mstatus"中的内存权限(Memory Privilege in mstatus Register) 3.1.6.4 "mstatus"和"mstatush"中的端序控制(Endianness Control in mstatus and mstatush Registers) 3.1.6.5 "mstatus"中的虚拟化支持(Virtualization Support in mstatus Register) 3.1.6.6 "mstatus"中的扩展上下文状态(Extension Context Status in mstatus Register) 3.1.7 机器陷阱向量基地址寄存器"mtvec"(Machine Trap-Vector Base-Address Register (mtvec)) 3.1.8 机器陷阱委托寄存器"medeleg"和"mideleg"(Machine Trap Delegation Registers (medeleg and mideleg)) 3.1.9 机器中断寄存器"mip"和"mie"(Machine Interrupt Registers (mip and mie)) 3.1.10 硬件性能监视器(Hardware Performance Monitor) 3.1.11 机器计数器使能寄存器"mcounteren"(Machine Counter-Enable Register (mcounteren)) 3.1.12 机器计数器抑制寄存器"mcountinhibit"(Machine Counter-Inhibit CSR (mcountinhibit)) 3.1.13 机器scratch寄存器"mscratch"(Machine Scratch Register (mscratch)) 3.1.14 机器异常程序计数器"mepc"(Machine Exception Program Counter (mepc)) 3.1.15 机器成因寄存器"mcause"(Machine Cause Register (mcause)) 3.1.16 机器陷阱值寄存器"mtval"(Machine Trap Value Register (mtval)) 3.1.17 机器配置指针计数器"mconfigptr"(Machine Configuration Pointer Register (mconfigptr)) 3.1.18 机器环境配置寄存器"menvcfg"和"menvcfgh"(Machine Environment Configuration Registers (menvcfg and menvcfgh)) 3.1.19 机器安全性配置寄存器"mseccfg"(Machine Security Configuration Register (mseccfg)) 3.2 机器级内存映射寄存器(Machine-Level Memory Mapped Registers) 3.3 机器级特权指令(Machine-Mode Privileged Instructions) 3.4 重置(Reset) 3.5 不可屏蔽中断(Non-Maskable Interrupts) 3.6 物理内存属性(Physical Memory Attributes) 3.6.1 主存、I/O或空闲区(Main Memory versus I/O versus Vacant Regions) 3.6.2 支持访问类型PMAs(Supported Access Type PMAs) 3.6.3 原子性PMAs(Atomicity PMAs) 3.6.3.1 AMO PMA(AMO PMA) 3.6.3.2 可保持性PMA(Reservability PMA) 3.6.3.3 对齐性(Alignment) 3.6.4 内存序PMAs(Memory-Ordering PMAs) 3.6.5 一致性与可缓存性PMAs(Coherence and Cacheability PMAs) 3.6.6 冥等性PMAs(Idempotency PMAs) 3.7 物理内存保护(Physical Memory Protection) 3.7.1 物理内存保护CSRs(Physical Memory Protection CSRs) 3.7.2 物理内存保护与分页(Physical Memory Protection and Paging) 以下为正文:

本章介绍机器模式(M模式)(machine-mode (M-mode))下可用的机器级操作(machine-level operations),该模式是RISC-V系统中的最高权级模式。M模式用于对硬件平台(hardware platform)进行低级访问(low-level access),是复位(reset)进入的第一个模式。M模式也可用于实现那些,在硬件中直接实现会过于困难或者昂贵的功能。RISC-V的机器级ISA包含有一个共同核心,然后其根据所支持的其它权级和硬件实现中的其它细节扩展[1]。

3.1 机器级CSRs(Machine-Level CSRs) 除本节所描述的机器级CSRsa外,M模式的代码也能访问所有处于更低权级的CSRs。

3.1.1 机器ISA寄存器 "misa"(Machine ISA Register misa) misa CSR(WARL, read-write)用于汇报hart所支持的ISA。该寄存器在任何实现中都必须可读,但可返回值0以表示没实现misa寄存器,此时需要通过其它非标准机制(non-standard)来确定CPU的功能。

Figure 3.1: 机器ISA寄存器(misa) MXL(Machine XLEN)字段编码本地基础整数ISA的宽度(native base integer ISA width),如Table 3.1所示。在支持有多个基础ISA的具体实现中MXL字段可以是可写的。M模式下的有效XLEN、MXLEN由MXL的设置给出,或者如果misa为零,则其为一个固定值。复位时总是将MXL字段设为各种所支持ISA变体中最宽的那个。

Table 3.1: misa中MXL字段的编码 misa CSR的位宽为MXLEN bits,如果从misa读出的值非零,则该值中的MXL字段总将表示当前的MXLEN。如果对misa的写入造成了MXLEN的改变,MXL的位置将移动到新位宽misa的最高有效2bits。

可以用读取misa所返回的值的符号进行诺干分支(第一次分支直接根据符号进行分支,以检查最高位;以及如果需要第二次先将misa左移1位,然后再根据符号进行分支,以检查次高位),来快速确定基础指令集(base)的宽度。可以在不知道机器寄存器位宽(XLEN)的情况下用汇编代码来编写这些检查。基础指令集宽度由 [公式] 给出。 对于misa为零的情况,可以通过将立即数 4 放入一个寄存器,然后将其左移32bits,以此来判断基础指令集:如果是一次移位后得到零,则机器是RV32的;如果是两次移位后得到零,则机器是RV64的;否则就是RV128。 Extensions字段编码了目前存有的标准扩展,其每个bit都对应了字母表中的一个字母(bit 0编码扩展“A”是否存在,bit 1编码扩展“B”是否存在... 直至bit 25编码“Z”)。如果基础ISA是RV32I、RV64I或RV128I,则置位“I”bit,否则如果基础ISA是RV32E,则置位“E”bit。

Extensions字段是一个能包含可写位的WARL字段(如果实现允许修改所支持的ISA)。

复位(reset)时,Extensions应包含所支持扩展的最大集[2],如果E和I都可用,则优先选择I[3]。

在通过清除misa中相应bit来禁止一个标准扩展时,由该扩展所定义或修改的指令和CSR将恢复为该扩展未实现时的定义,或者保留行为(revert to their defined or reserved behaviors as if the extension is not implemented)。

RV128 base ISA的设计尚未完工,尽管预计本specification中大部分的剩余部分都将适用于RV128,但本版本的文档仅关注RV32和RV64。

如果支持用户模式(user mode),则将“U”bit置位;如果支持主管模式(supervisor mode),则将“S ”bit置位。

如果存在任何非标准扩展(non-standard extensions),则将“X”bit置位。

misa CSR向机器模式的代码展示了CPU的基本功能目录。作为引导过程(boot process)的一部分,可通过探测其它机器寄存器,以及系统中的其它ROM存储器来获得更多信息。 我们要求较低的权级用环境调用来确定每个权级的可用功能,而不是直接读取CPU寄存器。这使得虚拟化层能够改变在任何级别上可观察到的ISA,并支持更加丰富的命令接口,且不会带来额外硬件设计负担。

Table 3.2: misa中Extensions字段的编码。所有保留给未来使用(reserved)的bit在读取时都要返回0 “E”bit是只读的。读取出的“E”bit始终都与“I”bit互补[4],除非misa为只读的全零。对于同时支持RV32E和RV32I的实现,可通过清除“I”bit来选择RV32E。

如果ISA特性x依赖于ISA特性y,则尝试在启用x的同时禁用y会导致两个功能都被禁用。例如,设置"F"=0和"D"=1会导致"F"和"D"都被清除(cleared)。

具体实现可能会在2或多个misa字段的集体设置上施加额外约束,此时将它们的集体看作是一个WARL字段[5]。试图向其中写入一个不支持的组合会导致这些bits被置为某个支持的组合。

写入misa可能会增加IALIGN,例子如禁用“C”扩展。如果某指令向misa写入,并且会导致增加IALIGN,且后续指令并非IALIGN-bit对齐,则该向misa的写入会被抑制,misa将保持不变。

在软件启用一个之前被禁用的扩展时,除该扩展另有规定(specified),否则所有单独与该扩展有关的状态都将是未指定的(unspecified)。

3.2.1 机器厂商ID寄存器"mvendorid"(Machine Vendor ID Register mvendorid) mvendorid CSR(32-bit, read-only)用于提供core供应商的JEDEC制造商ID。在任何实现中该寄存器都必须可读,但可返回值0以表示该字段未实现或者这是非商业实现。

Figure 3.2: 厂商ID寄存器(mvendorid) JEDEC制造商IDs通常编码为单字节代码0x7f的连续序列,并以一个不等于0x7f的单字节ID终止,其中每个字节中都有一个奇校验位在最高有效位上。mvendorid在Bank字段中编码单字节连续代码的数量,并在Offset字段中编码最后的字节,并丢弃奇偶校验位。例如JEDEC制造商ID 0x7f 0x7f 0x7f 0x7f 0x7f 0x7f 0x7f 0x7f 0x7f 0x7f 0x7f 0x7f 0x8a(12个连续代码和紧接的0x8a)就会在mvendorid CSR中编码为0x60a。

按照JEDEC的说法,bank编号要比连续编码的数目多一个;因此,mvendorid的Bank字段所编码的值少一个JEDEC bank编号[6]。

在以前,供应商ID由RISC-V International分配,但这重复了JEDEC维护制造商ID的工作。目前(撰写本文时)向JEDEC注册制造商ID的一次性费用为$500。 3.1.3 机器架构ID寄存器"marchid"(Machine Architecture ID Register marchid) marchid CSR(MXLEN-bit, read-only)用于编码hart的基础微架构(base microarchitecture)。该寄存器在任何实现中都必须可读,但可返回值0以表示该字段未实现。mvendorid和marchid应组合成所实现的hart微架构类型的唯一标识符。

Figure 3.3: 机器架构ID寄存器(marchid) 开源项目的架构ID由RISC-V International全局分配(allocated globally)为非零的架构IDs,其中最高有效位(MSB)为零。商业架构ID由每个商业供应商单独分配,但必须将MSB置位,且剩余的MXLEN-1 bit不能为零。

架构ID用于代表与开发repo相关的微架构,而非特定组织。开源设计的商业制造商应(并且可能被lisense要求)保留原始架构ID。有助于减少碎片化和工具支持成本,并提供归属。开源架构ID由RISC-V International管理,只分配给已发布且正在运行的开源项目。商业架构ID可由任何注册过的厂商独自管理,但诺厂商希望同时使用闭源和开源的微架构,则要拥有与开源架构不相干的ID(MSB set),以防冲突。 接下来的实现字段中所采用的协定可用于区分同一架构设计的分支,包括按组织结构划分。misa寄存器还有助于区分不同设计变体。 3.1.4 机器实现ID寄存器"mimpid"(Machine Implementation ID Register mimpid) mimpid CSR用于提供处理器实现版本的唯一编码。在任何实现中该寄存器都必须可读,但可返回值0以表示该字段未实现。实现值反映的是RISC-V处理器本身的设计,而非任何外周系统。

Figure 3.4: 机器实现ID寄存器(mimpid) 这个字段的格式留给架构源码提供者来决定,但通常会被标准工具打印为一个不带任何前导或尾随零的十六进制字符串,因此可以让实现值左对齐(即,从最高有效的4bit开始,逐渐向下填入),以及将子字段对齐到4bit边界上,以提高人类可读性。 3.1.5 硬件线程ID寄存器"mhartid"(Hart ID Register mhartid) mhartid CSR(MXLEN-bit, read-only)含有正在运行代码的硬件线程的整数ID。在任何实现中该寄存器都必须可读。在多处理器系统中,Hart ID不一定为连续编号,但其中至少有一个Hart的Hart ID为0。在执行环节中,每个Hart ID都必须是唯一的。

Figure 3.5: Hart ID寄存器(mhartid) 在某些情况下,我们必须确保只有一个hart在运行某些代码(例如在reset时),因此,要求至少有且已知一个hart的hart ID为零。 为提高效率,系统实现者应试图让系统中的最大hart ID尽可能小。

参考 ^The RISC-V machine-level ISA contains a common core that is extended depending on which other privilege levels are supported and other details of the hardware implementation. ^就是说所有支持的扩展的对应bit都设置为1 ^就是说,比如如果同时支持RV32I和RV32E,则复位时优先选择RV32I,即将I设为1(E为0,两者互斥)。 ^即两者相加始终为1。 ^they function collectively as a single WARL fiel ^In JEDEC’s parlance, the bank number is one greater than the number of continuation codes; hence, the mvendorid Bank field encodes a value that is one less than the JEDEC bank number. 来自专栏 一些计算机科学书的目录与序言翻译

VN Vortex · 33 篇内容 【目录序言翻译】算术电路综合:FPGA,ASIC与嵌入式系统 Synthesis of Arithmetic Circuits : FPGA, ASIC, and Embedded Systems 文章 · 18 赞同 · 3 评论 申请转载 评论

写下你的评论... icon

Reisen1969 commented 2 weeks ago

好的,这里是一个示例程序,展示了如何实现一个主线程监视数据并分类处理数据的流程。在这个程序中,每种类型的数据都有一个对应的工作线程,工作线程会记录处理的数据,并使用 logging 模块记录日志。

示例程序代码:

import threading
import time
import logging
from queue import Queue, Empty

# 设置日志记录配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(threadName)s - %(message)s')

class DataMonitor(threading.Thread):
    def __init__(self, data_queue):
        super().__init__(daemon=True)
        self.data_queue = data_queue
        self.data_types = ['type1', 'type2', 'type3']  # 假设有三种数据类型

    def run(self):
        counter = 0
        while True:
            # 模拟接收不同类型的数据
            data_type = self.data_types[counter % len(self.data_types)]
            data = f"data_{counter}"
            self.data_queue.put((data_type, data))
            logging.info(f"Received data: {data} of type: {data_type}")
            counter += 1
            time.sleep(1)  # 模拟数据接收间隔

class WorkerThread(threading.Thread):
    def __init__(self, data_type, data_queue):
        super().__init__(daemon=True)
        self.data_type = data_type
        self.data_queue = data_queue
        self.logger = logging.getLogger(f"Worker-{data_type}")
        self.logger.setLevel(logging.INFO)

    def run(self):
        while True:
            try:
                # 尝试从队列中获取数据,超时时间为1秒
                data = self.data_queue.get(timeout=1)
                self.process_data(data)
            except Empty:
                continue

    def process_data(self, data):
        self.logger.info(f"Processing {data}")
        # 模拟处理数据的时间
        time.sleep(2)
        self.data_queue.task_done()

class DataDispatcher:
    def __init__(self):
        self.data_queue = Queue()
        self.workers = {}

    def start_monitoring(self):
        monitor_thread = DataMonitor(self.data_queue)
        monitor_thread.start()

    def dispatch(self):
        while True:
            try:
                data_type, data = self.data_queue.get(timeout=1)
                if data_type not in self.workers:
                    self.create_worker(data_type)
                self.workers[data_type].data_queue.put(data)
            except Empty:
                continue

    def create_worker(self, data_type):
        worker_queue = Queue()
        worker_thread = WorkerThread(data_type, worker_queue)
        worker_thread.start()
        self.workers[data_type] = worker_thread
        logging.info(f"Created worker for data type: {data_type}")

if __name__ == '__main__':
    dispatcher = DataDispatcher()
    dispatcher.start_monitoring()

    # 启动调度器
    dispatcher_thread = threading.Thread(target=dispatcher.dispatch, daemon=True)
    dispatcher_thread.start()

    # 主线程保持运行
    while True:
        time.sleep(1)

代码说明:

  1. DataMonitor 类

    • 继承自 threading.Thread,模拟持续监视数据的主线程。
    • run 方法中定期生成不同类型的数据并将其放入 data_queue
  2. WorkerThread 类

    • 继承自 threading.Thread,用于处理特定类型数据的工作线程。
    • run 方法中从自己的队列中获取数据并处理。
    • process_data 方法模拟数据处理并记录日志。
  3. DataDispatcher 类

    • 管理监视线程和工作线程。
    • start_monitoring 方法启动数据监视线程。
    • dispatch 方法将数据分配给对应的工作线程,如果没有对应的工作线程则创建一个。
    • create_worker 方法创建新的工作线程。
  4. 主程序

    • 创建 DataDispatcher 实例并启动监视线程和调度器线程。
    • 主线程保持运行,以便持续处理数据。

这个程序展示了如何使用 threadingqueue 模块来实现多线程数据监视和处理,同时使用 logging 模块记录日志。您可以根据需要扩展此示例以处理更多类型的数据和更复杂的处理逻辑。