手把手教你联想RISC-V CPU
(原标题:手把手教你联想RISC-V CPU)
淌若您但愿不错通常碰面,迎接标星储藏哦~
最近些年。RISC-V引起了公共情愫。这款创新性的 ISA 凭借其持续的创新,以及无数的学习和用具资源以及来自工程界的孝顺,像潮流般席卷了阛阓。RISC-V 最大的魔力在于它是一款开源 ISA。
在本文中,我(指代本文作家Mitu Raj,下同)将先容何如从零脱手联想一款RISC-V CPU ,咱们将教训界说规格、联想和革新架构、识别和不停挑战、配置 RTL、完毕 CPU 以及在仿真/FPGA 板上测试 CPU 的经由。
以下为著作正文:
从定名脱手
为你的想法定名或打造品牌至关进犯,这么才能激勉你继续前进,直至达成宗旨!咱们运筹帷幄构建一个相称通俗的处理器,是以我想出了一个花哨的名字“ Pequeno ”,在西班牙语中是“细小”的意象;完满称号是:Pequeno RISC-V CPU,笔名PQR5。
RISC-V 的 ISA 架构有多种作风和膨胀。咱们先从最通俗的RV32I脱手,它又称为 32 位基本整数 ISA。该 ISA 适用于构建复古整数运算的 32 位 CPU。因此,Pequeno 的第一个规格如下:
Pequeno 是一款 32 位 RISC-V CPU,复古 RV32I ISA。
RV32I 有 37 条 32 位基本提醒,咱们规划在 Pequeno 中完毕。因此,咱们必须深远了解每条提醒。我费了一番功夫才绝对掌持了 ISA。在此过程中,我学习了完满的法式,并联想了我方的汇编挨次pqr5asm,并与一些流行的 RISC-V 汇编挨次进行了考据。
“RISBUJ”
上头六个字母的单词纪念了 RV32I 中的提醒类型。这 37 条提醒属于以下类别之一:
R型:通盘寄存器上的整数计较提醒。
I 型:通盘基于寄存器和立即数的整数计较提醒。还包括 JALR 和 Load 提醒。
S型:全部存储讲明。
B型:通盘分支提醒。
U型:LUI、AUIPC等特殊提醒。
J型:访佛JAL的跳转提醒。
RISC-V 架构中有 32 个通用寄存器,x0-x31. 通盘寄存器都是 32 位的。在这 32 个寄存器中,零又称为x0寄存器,是一个很有用的特殊寄存器,它被硬连线为零,无法写入,况且遥远读取为零。那么它有什么用呢?你不错使用x0行动虚构宗旨来转储您不想读取的效果,或用作操作数零,或生成 NOP 提醒来闲置 CPU。
整数计较提醒是针对寄存器和/或12位立即数扩充的ALU提醒。加载/存储提醒用于在寄存器和数据存储器之间存储/加载数据。跳转/分支提醒用于将挨次限度滚动到不同的位置。
每条提醒的详备信息不错在 RISC-V 法式中找到:RISC-V 用户级 ISA v2.2。
要学习 ISA,RISC-V 法式文档就迷漫了。不外,为了更明晰起见,您不错计齐整下 RTL 中不同开放中枢的完毕。
除了 37 条基本提醒外,我还为 pqr5asm 添加了 13 条伪/自界说提醒,并将 ISA 膨胀至 50 条提醒。这些提醒源自基本提醒,旨在简化汇编挨次员的责任……举例:
NOP提醒与ADDI x0, x0, 0这在CPU上天然什么也不作念!但它更通俗,更容易在代码中解说。
在脱手联想处理器架构之前,咱们的渴望是绝对了解每条提醒何如以 32 位二进制进行编码以及它的功能是什么。
我用 Python 配置的 RISC-V RV32I 汇编器 PQR5ASM 不错在我的 GitHub 上找到。您不错参考《汇编器提醒手册》编写示例汇编代码。编译它,并稽察它何如转机为 32 位二进制文献,以便在连接下一步之前稳重/考据您的贯穿。
规格和架构
在本章中,咱们界说了 Pequeno 的完满规格和架构。前次咱们仅仅通俗地将其界说为 32 位 CPU。接下来,咱们将对其进行更详备的先容,以大要了解行将联想的架构。
咱们将联想一个通俗的单核 CPU,它豪迈按照得到提醒的法规一次扩充一条提醒,但仍接收活水线边幅。咱们不复古 RISC-V 特权法式,因为咱们面前不运筹帷幄让咱们的中枢操作系统复古该法式,也不运筹帷幄让它复古中断。
该CPU规格如下:
32位CPU,单辐射,单核。
经典的五级 RISC 活水线。严格有序活水线。
得当RV32I 用户级 ISA v2.2。复古全部 37 条基本提醒。
用于提醒和数据存储器侦察的孤独总线接口。(为什么?以后再参谋……)
适用于裸机期骗挨次,不复古操作系统和中断。(更确切地说是闭幕!)
正如上文所述,咱们将复古 RV32I ISA。因此,CPU 仅复古整数运算。
CPU 中的通盘寄存器都是 32 位的。地址和数据总线亦然 32 位的。CPU 接收经典的小端字节寻址内存空间。每个地址对应于 CPU 地址空间中的一个字节。
0x00 - byte7:0, 0x01 - byte15:8 ...
32 位字不错通过 32 位对都的地址侦察,即 4 的倍数的地址:
0x00—— byte 0,0x04—— byte 1……
Pequeno 是一款单辐射 CPU,即每次只从内存中得到一条提醒,并发出提醒进行解码和扩充。接收单辐射的活水线处理器的最大IPC = 1(或最小/最好CPI = 1),即最终宗旨是以每时钟周期 1 条提醒的速率扩充。这在表面上是不错完毕的最高性能。
经典的五级 RISC 活水线是贯穿任何其他 RISC 架构的基础架构。这对于咱们的 CPU 来说是最联想且最通俗的聘任。Pequeno 的架构即是围绕这种五级活水线构建的。让咱们深远探讨一下其底层观念。
通俗起见,咱们将不复古 CPU 活水线中的计时器、中断和额外。因此,CSR 和特权级别也无需完毕。因此, RISC-V 特权 ISA不包含在 Pequeno 确现时完毕中。
联想 CPU 最通俗的方法诟谇活水线边幅。让咱们望望非活水线 RISC CPU 的几种联想方法,并了解其错误。
让咱们假定 CPU 扩充提醒所奉命的经典挨顺序列:得到、解码、扩充、内存侦察和写回。
第一种联想方法是:将 CPU 联想成一个具有四到五个气象的有限气象机 (FSM),并按法规扩充通盘操作。举例:
但这种架构会严重影响提醒扩充速率。因为扩充一条提醒需要多个时钟周期。比如,写入寄存器需要 3 个时钟周期。淌若是加载/存储提醒,内存蔓延也会随之增多。这是一种晦气且原始的 CPU 联想方法。咱们透澈消除它吧!
第二种方法是:提醒不错从提醒存储器中取出,解码,然后由绝对组合逻辑扩充。然后,ALU 的效果被写回到寄存器文献。直到写回的通盘这个词过程不错在一个时钟周期内完成。这么的 CPU 称为单周期 CPU。淌若提醒需要侦察数据存储器,则应试虑读/写蔓延。淌若读/写蔓延为一个时钟周期,则存储提醒仍可能像通盘其他提醒一样在一个时钟周期内完成扩充,但加载提醒可能迥殊需要一个时钟周期,因为必须将加载的数据写回到寄存器文献。PC 生成逻辑必须处理这种蔓延的影响。淌若数据存储器读取接口是组合的(异步读取),则 CPU 对于通盘提醒都将真的变为单周期。
该架构的主要错误较着是从取指到写入存储器/寄存器文献的组合逻辑关节旅途较长,这闭幕了时序性能。但是,这种联想方法通俗,适用于低端微限度器中那些需要低时钟速率、低功耗和低面积的CPU。
为了完毕更高的时钟速率和性能,咱们不错将 CPU 的提醒法规处理功能分离出来。每个子进度被分派给孤独的处理单位。这些处理单位按法规级联,形成活水线。通盘单位并行责任,并对提醒扩充的不同部分进行操作。通过这种边幅,不错并行处理多条提醒。这种完毕提醒级并行性的时期称为提醒活水线。该扩充活水线组成了活水线 CPU 的中枢。
经典的五级 RISC 活水线有五个处理单位,也称为活水线阶段。这些阶段分别是:取指(IF)、解码(ID)、扩充(EX)、内存侦察(MEM)、写回(WB)。活水线的责任旨趣不错直不雅地暗示为:
每个时钟周期,一条提醒的不同部分会被处理,况且每个阶段都会处理不同的提醒。淌若仔细不雅察,会发现只好第 5 个周期,提醒 1 才完成扩充。这段蔓延被称为活水线蔓延。Δ此蔓延与活水线级数交流。在此蔓延之后,第 6 个周期:提醒 2 扩充完毕,第 7 个周期:提醒 3 扩充完毕,以此类推……表面上,咱们不错计较抽象量(每周期提醒数,IPC),如下所示:
因此,活水线CPU保证每个时钟周期扩充一条提醒。这是单辐射处理器中可能的最大IPC。
通过永别多个活水线阶段的关节旅途,CPU 现在也不错以更高的时钟速率运行。从数学上讲,这使得活水线 CPU 的抽象量比同等的非活水线 CPU 提高了一个倍数。
这被称为活水线加快。通俗来说,一个具有s阶段活水线 CPU 的时钟速率诟谇活水线家具的S倍。
活水线平凡会增多面积/功耗,但性能晋升是值得的。
数学计较假定活水线永远不会停滞,也即是说,数据在每个时钟周期内都会从一个阶段持续传输到另一个阶段。但在实验的 CPU 中,活水线可能会由于多种原因而停滞,主要原因是结构/限度/数据依赖性。
举个例子:寄存器X不可被Nth提醒读到,因为X并不是由(N-1)th提醒修改了X读回,这是活水线中数据风险的一个例子。
Pequeno 的架构接收了经典的五级 RISC 活水线。咱们将完毕严格的法规活水线。在法规处理器中,提醒的得到、解码、扩充和完成/提交都按照编译器生成的法规进行。淌若一条提醒停滞,通盘这个词活水线都会停滞。
在乱序处理器中,提醒按照编译器生成的法规得到妥协码,但扩充不错按不同的法规进行。淌若一条提醒停顿,除非存在依赖关系,不然它不会停顿后续提醒。孤独的提醒不错上前传递。扩充仍然不错按法规完成/提交(这即是面前大多数CPU的近况)。这为完毕各种架构时期怒放了大门,通过减少停顿所耗费的时钟周期并最大限定地减少气泡的插入(什么是“气泡”?连接阅读……) ,权贵提高抽象量和性能。
乱序处理器由于提醒的动态退换而十分复杂,但现在已成为面前高性能 CPU 中事实上的活水线架构。
五个活水线阶段被联想为孤独单位:取指单位(FU)、译码单位(DU)、扩充单位(EXU)、内存侦察单位(MACCU)和写回单位(WBU)。
取指单位(FU):活水线的第一级,与提醒存储器接口。FU 从提醒存储器中取指并送至译码单位。FU 可能包含提醒缓冲区、运行分支逻辑等。
解码单位(DU):活水线的第二阶段,肃肃解码来自扩充单位 (FU) 的提醒。DU 还会启动对寄存器文献的读取侦察。来自 DU 和寄存器文献的数据包被再行定时同步,并沿途发送到扩充单位 (Execution Unit)。
扩充单位(EXU):活水线的第三阶段,用于考据并扩充来自 DU 的通盘解码提醒。无效/不复古的提醒不允许在活水线中连接扩充,它们会成为“气泡”。算术单位 (ALU)肃肃通盘整数算术和逻辑提醒。分支单位 (Branch Unit)肃肃处理跳转/分支提醒。加载/存储单位 (Load-Store Unit)肃肃处理需要侦察内存的加载/存储提醒。
内存侦察单位(MACCU):活水线的第四级,用于与数据存储器接口。MACCU 肃肃把柄 EXU 的提醒发起通盘内存侦察。数据存储器是寻址空间,可能由数据 RAM、内存映射的 I/O 外设、桥接器、互连等组成。
写回单位(WBU):活水线的第五级或终末一级。提醒在此完成扩充。WBU 肃肃将 EXU/MACCU 中的数据(加载数据)写回寄存器文献。
在活水线阶段之间,完毕了灵验-就绪持手。乍一看这并不那么光显。每个阶段都会注册一个数据包并将其发送到下一阶段。该数据包可能是下一阶段或后续阶段要使用的提醒/限度/数据信息。该数据包通过灵验信号进行考据。淌若数据包无效,则在活水线中称为气泡(Bubble)。气泡只不外是活水线中的“洞”(hole),它仅仅在活水线中上前挪动,实验上不扩充当何操作。这访佛于 NOP 提醒。但不要以为它们莫得用!在后续部分参谋活水线风险时,咱们将看到它们的一种用途。下表界说了 Pequeno 提醒活水线中的气泡。
每个阶段还不错通过发出停顿信号来停顿前一个阶段。一朝停顿,该阶段将保留其数据包,直到停顿气象隐藏。此信号与回转的就绪信号交流。在法规处理器中,任何阶段产生的停顿都访佛于全局停顿,因为它最终会停顿通盘这个词活水线。
flush信号用于刷新管谈。刷新操作将一次性使之前阶段注册的所罕有据包失效,因为它们被识别为不再有用。
举个例子,当活水线在扩充跳转/分支提醒后,从诞妄的分支得到并解码了提醒,而该提醒仅在扩充阶段被识别为诞妄时,活水线应该被刷新,并从正确的分支得到提醒!
固然活水线权贵晋升了性能,但也增多了 CPU 架构的复杂性。CPU 的活水线时期老是伴跟着它的孪生昆季——活水线风险!现在,咱们假定咱们对活水线风险一无所知。咱们在联想架构时并莫得斟酌风险。
处理活水线风险
在本章中,咱们将探讨活水线风险。咱们前次告捷联想了 CPU 的活水线架构,但却莫得斟酌到随同活水线而来的“浮躁双胞胎”。活水线风险对架构可能形成哪些影响?需要进行哪些架构修改来缓解这些风险?让咱们连接,揭开它们的玄机面纱!
CPU 提醒活水线中的危境是指一些依赖关系,这些依赖关系会侵略活水线的日常扩充。当危境发生时,提醒无法在指定的时钟周期内扩充,因为这可能导致诞妄的计较效果或限度流。因此,活水线可能会被动暂停,直到提醒豪迈告捷扩充。
在上头的例子中,CPU 按照编译器生成的法规按序扩充提醒。假定提醒 i2对i1有一定的依赖性,比如i2需要读取某个寄存器,但该寄存器也正在被前一条提醒i1修改。因此,i2必须比及i1将效果写回寄存器文献,不然旧数据将被解码并从寄存器文献读取,供扩充阶段使用。为了幸免这种数据不一致,i2被强制暂停三个时钟周期。活水线中插入的气泡暗示暂停或恭候气象。只好当i1完成时,i2才会被解码。最终,i2在第 10 个时钟周期而不是第 7 个时钟周期完成扩充。由于数据依赖性导致的暂停,引入了三个时钟周期的蔓延。这种蔓延何如影响 CPU 性能?
联想情况下,咱们渴望 CPU 以满抽象量运行,即 CPI = 1。但是,当活水线暂停时,由于 CPI 增多,CPU 的抽象量/性能会裁减。对于非联想 CPU:
管谈中发生危境的边幅多种各种。管谈危境可分为三类:
结构性危境
限度危害
数据危害
结构性风险是由于硬件资源碎裂而发生的。举例,当活水线的两个阶段想要侦察归并资源时。举例:两条提醒需要在归并时钟周期内侦察内存。
在上头的例子中,CPU 只好一个内存用于存储提醒和数据。取指阶段每个时钟周期都会侦察内存以得到下一条提醒。因此,淌若内存侦察阶段的上一条提醒也需要侦察内存,则取指阶段和内存侦察阶段的提醒可能会发生碎裂。这将迫使 CPU 增多停顿周期,取指阶段必须恭候,直到内存侦察阶段的提醒开释资源(内存)。
松开结构性危境的一些方法包括:
暂停管谈,直到资源可用。
复制资源,这么就不会发生任何碎裂。
活水线资源,使得两条提醒将处于活水线资源的不同阶段。
让咱们分析一下可能导致 Pequeno 管谈出现结构性危境的不哀怜况,以及何如不停。 咱们意外使用停责任为缓解结构性危境的选项!
在 Pequeno 的架构中,咱们实施了上述三种不停有斟酌来松开各种结构性危境。
限度风险是由跳转/分支提醒引起的。跳转/分支提醒是 CPU ISA 中的经由限度提醒。当限度权到达跳转/分支提醒时,CPU 必须决定是否扩充该分支提醒。此时,CPU 应该选用以下操作之一。
在 PC+4 处得到下一条提醒(不扩充分支)或得到分支宗旨地址处的提醒(分支已扩充)。
只好在扩充阶段计较分支提醒的效果时,才能判断决策的正确与否。把柄分支是否被扩充,细则分支地址(CPU 应该分支到的地址)。淌若之前作念出的决策是诞妄的,那么在该时钟周期之前在活水线中得到妥协码的通盘提醒都应该被丢弃。因为这些提醒压根不应该被扩充!这是通过刷新活水线并不才一个时钟周期得到分支地址的提醒来完毕的。刷新使提醒无效并将其转机为 NOP 或冒泡。这会蹧跶无边的时钟周期行动刑事遭殃。这被称为分支刑事遭殃。因此,限度冒险对 CPU 性能的影响最严重。
在上头的例子中,i10在第 10 个时钟周期完成了扩充,但它应该在第 7 个时钟周期完成扩充。由于扩充了诞妄的分支提醒 (i5),因此亏损了 3 个时钟周期。当扩充阶段在第 4 个时钟周期识别出诞妄分支提醒时,必须在活水线中进行刷新。这会何如影响 CPU 性能?
淌若在上述 CPU 上运行的挨次包含 30% 的分支提醒,则 CPI 将变为:
CPU 性能裁减50%!
为了松开限度风险,咱们不错在架构中接收一些战略……
淌若提醒被识别为分支提醒,则只需暂停活水线即可。该解码逻辑不错在索求阶段自己完毕。一朝扩充了分支提醒并领路了分支地址,就不错索求下一条提醒并规复活水线。
在 Fetch 阶段添加访佛分支斟酌的专用分支逻辑。
分支斟酌的实验是:咱们在取指阶段接收某种斟酌逻辑来测度分支是否应该被扩充。不才一个时钟周期,咱们得到测度的提醒。这条提醒要么从 PC+4 处得到(斟酌分支不被扩充),要么从分支宗旨地址处得到(斟酌分支被扩充)。现在有两种可能性:
淌若在扩充阶段发现斟酌正确,则不扩充当何操作,管谈不错连接处理。
淌若发现斟酌诞妄,亿配资则刷新活水线,从扩充阶段领路的分支地址中得到正确的提醒。这会产陌生支刑事遭殃。
如您所见,分支斟酌淌若斟酌诞妄,仍然会招致分支刑事遭殃。联想宗旨应该是裁减诞妄斟酌的概率。CPU 的性能很大程度上取决于斟酌较法的“锋利”。像动态分支斟酌这么的复杂时期会保存提醒历史记载,以便以 80% 到 90% 的概率进行正确斟酌。
为了松开 Pequeno 中的限度风险,咱们将完毕一个通俗的分支斟酌逻辑。更多细节将在咱们行将发布的对于索求单位联想的博客中揭晓。
当一条提醒的扩充对活水线中仍在处理的上一条提醒的效果存在数据依赖时,就会发生数据风险。让咱们通过示例来了解三种类型的数据风险,以便更好地贯穿这个观念。
假定一条提醒i1将效果写入寄存器 x。下一条提醒i2也将效果写入归并寄存器。挨次法规中的任何后续提醒都应读取 x 处i2的效果。不然,数据完满性将受损。这种数据依赖关系称为输出依赖关系,可能导致 WAW((Write-After-Write)) 数据风险。
假定一条提醒i1读取了寄存器 x。下一条提醒i2将效果写入归并寄存器。此时,i1应该读取 寄存器X的旧值,而不是i2的效果。淌若 i2在i1读取效果之前将效果写入 x,则会导致数据风险。这种数据依赖称为反依赖,可能导致 WAR ((Write-After-Read))数据风险。
假定一条提醒i1将效果写入寄存器 x。下一条提醒i2读取归并个寄存器。此时,i2应该读取 i1写入寄存器 x 的值,而不是之前的阿谁值。这种数据依赖关系被称为真依赖,可能导致 RAW (Read-After-Write)数据风险。
这是活水线 CPU 中最常见、最主要的数据危境类型。
为了松开有序 CPU 中的数据危境,咱们不错接收一些时期:
检测到数据依赖性时,暂停活水线(参见第一张图)。解码阶段不错比及上一条提醒扩充完成后再扩充。
编译再行退换:编译器通过退换代码到稍后扩充来再行安排代码,以幸免数据风险。这么作念的宗旨是幸免挨次停顿,同期又不影响挨次限度流的完满性,但这并非老是可行。编译器也不错在两个具罕有据依赖性的提醒之间插入 NOP 提醒。但这会导致停顿,从而影响性能。
数据/操作数转发:这是法规扩充 CPU 中缓解 RAW 数据风险的突出架构不停有斟酌。让咱们分析一下 CPU 活水线,以了解这项时期背后的旨趣。
假定两个相邻的提醒i1和i2,它们之间存在 RAW 数据依赖性,因为它们都在侦察寄存器X。CPU 应该暂停提醒i2,直到i1将效果写回寄存器x。淌若 CPU 莫得停顿机制,则i2会在第三个时钟周期的解码阶段从 x 读取较旧的值。在第四个时钟周期,i2提醒会扩充诞妄的 x 值。
淌若你仔细不雅察管谈,咱们在第三个时钟周期就也曾得到了i1的效果。天然,它不会被写回寄存器文献,但效果仍然不错在扩充阶段的输出端使用。因此,淌若咱们豪迈以某种边幅检测数据依赖性,然后将该数据“forward”到扩充阶段的输入,那么下一条提醒就不错使用转发的数据,而不是来自解码阶段的数据。这么一来,数据风险就得到了缓解!这个想法是这么的:
这称为数据/操作数转发或数据/操作数旁路。咱们将数据定时代上前转发,以便活水线中后续的依赖提醒不错侦察这些被旁路的数据,并在扩充阶段扩充。
这个想法不错膨胀到不同的阶段。在一个按 i1、i2、..in法规扩充提醒的 5 级活水线中,数据依赖关系可能存在于:
i1和i2- 需要在扩充阶段妥协码阶段的输出之间旁路。
i1和i3- 需要在内存侦察阶段妥协码阶段的输出之间旁路。
i1和i4- 需要在写回阶段妥协码阶段的输出之间旁路。
用于缓解源自活水线任何阶段的 RAW 数据风险的架构不停有斟酌如下所示:
请斟酌以下情形:
两条相邻提醒i1和i2之间存在数据依赖关系,其中第一条提醒是 Load。这是数据风险的一种特殊情况。这里,在数据加载到 x1 之前,咱们无法扩充i2。那么,问题在于咱们是否仍然不错通过数据转发来缓解这种数据风险?加载数据仅在 i1的内存侦察阶段可用,况且必须将其转发到i2的解码阶段才能腐臭这种风险。该要求如下所示:
假定加载数据在第 4 个周期的内存侦察阶段可用,您需要将此数据“转发”到第 3 个周期,发送到i2的解码阶段输出(为什么是第 3 个周期?因为在第 4 个周期,i 就也曾在扩充阶段完成了扩充!)。实验上,您是在尝试将现时数据转发到畴昔,除非您的 CPU 进行时辰旅行,不然这是不可能的!这不是数据转发,而是“数据回溯”。
数据转发只可沿时辰标的上前进行。
这种数据风险称为活水线互锁(Pipeline Interlock)。不停这个问题的唯独方法是,在检测到数据依赖性时插入一个气泡,使活水线暂停一个时钟周期。
在 i1和i2之间插入了 NOP 提醒(又称 Bubble)。这会将i2蔓延一个周期,因此数据转发现在不错将加载数据从内存侦察阶段转发到解码阶段的输出。
到面前为止,咱们只参谋了何如缓解 RAW 数据风险。那么,WAW 和 WAR 风险又何如呢?RISC-V 架构自己就具备相悖有序活水线完毕的 WAW 和 WAR 风险的才气!
通盘寄存器的写回都按照提醒发出的法规进行。写回的数据老是会被后续写入归并寄存器的提醒遮掩。因此,WAW 风险永远不会发生!
写回是活水线的终末一个阶段。当写回发生时,读取提醒也曾告捷完成了对较旧数据的扩充。因此,WAR 风险永远不会发生!
为了缓解 Pequeno 中的 RAW 数据风险,咱们将使用活水线互锁保护功能硬件完毕数据转发。更多细节将在后文揭晓,届时咱们将在其中联想数据转发逻辑。
咱们贯穿并分析了现存 CPU 架构中可能导致提醒扩充失败的各种潜在活水线风险。咱们还联想了不停有斟酌和机制来缓解这些风险。让咱们整合必要的微架构,并最终联想出 Pequeno RISC-V CPU 的架构,使其绝对阻绝通盘类型的活水线风险!
在接下来的著作中,咱们将深远探讨每个活水线阶段/功能单位的 RTL 联想。咱们将参谋联想阶段中不同的微架构决策和挑战。
得到单位
从这里脱手,咱们脱手深远探讨微架构和 RTL 联想了!在本章中,咱们将构建和联想Pequeno 的Fetch Unit (FU) 。
取指单位 (FU) 是 CPU 活水线的第一阶段,用于与提醒存储器交互。取指单位 (FU) 从提醒存储器中取指,并将取指的提醒发送到译码单位 (DU) 。正如前文中 Pequeno 的革新架构所参谋的那样,FU 包含分支斟酌逻辑和刷新复古。
1
接口
让咱们界说 Fetch Unit 的接口:
2
提醒侦察接口
CPU 中 FU 的中枢功能是提醒侦察。提醒侦察接口 (Instruction Access:I/F)即用于此宗旨。提醒在扩充时代存储在提醒存储器 (RAM) 中。当代 CPU 从高速缓存 (Cache) 中得到提醒,而不是告成从提醒存储器中得到。提醒缓存(在计较机架构术语中称为主缓存或L1 缓存)更集会 CPU,通过缓存/存储平凡侦察的提醒并在隔邻预取较大块的提醒,完毕更快的提醒侦察。因此,无需持续侦察速率较慢的主存储器 (RAM)。因此,大多数提醒都不错告成从缓存中快速侦察。
CPU 不会告成侦察带有提醒缓存/内存的接口。它们之间会有一个缓存/内存限度器来限度它们之间的内存侦察。
界说一个挨次接口是一个好主意,这么任何挨次提醒存储器/缓存 (IMEM) 都不错讲理地插入到咱们的 CPU 中,况且只需少量的胶合逻辑以至无需胶合逻辑。让咱们界说两个用于提醒侦察的接口。央求接口 (I/F )处理从提醒存储器 (FU) 到提醒存储器的央求。反馈接口 (I/F)处理从提醒存储器到提醒存储器 (FU) 的反馈。咱们将为提醒存储器 (FU) 界说一个通俗的基于灵验就绪的请乞降反馈接口 (I/F),因为淌若需要,这很容易转机为 APB、AXI 等总线契约。
提醒侦察需要知谈提醒在内存中的地址。通过央求接口 (Request I/F) 央求的地址实验上即是 FU 生成的 PC。在 FU 接口中,咱们将使用暂停信号 (stall signal) 来代替就绪信号,其行为与就绪信号各异。缓存限度器平凡有一个暂停信号来暂停来自处理器的央求。该信号由cpu_stall暗示。来自内存的反馈是通过反馈接口 (Response I/F) 吸收到的已取提醒。除了已取提醒之外,反馈还应包含相应的 PC。PC 用作 ID,用于识别已收到反馈的央求。换句话说,它劝诱已取提醒的地址。这是 CPU 活水线下一阶段所需的进犯信息(何如完毕?咱们很快就会看到! )。因此,已取提醒过甚 PC 组成了对 FU 的反馈数据包。当里面活水线暂停时,CPU 可能还需要暂停来自提醒内存的反馈。该信号由mem_stall暗示。
此时,让咱们界说CPU 管谈中的 instruction packet= {instruction, PC}。
3
PC 生成逻辑
FU 的中枢是限度央求接口 (I/F) 的 PC 生成逻辑。由于咱们联想的是 32 位 CPU,因此 PC 的生成应该以 4 为增量。该逻辑复位后,每个时钟周期都会生成 PC。PC 的复位值不错硬编码。这是 CPU 复位后从中得到并扩充提醒的地址,即内存中第一条提醒的地址。PC 生成是目田运行的逻辑,仅由 c pu_stall暂停。
目田运行的PC不错通过刷新I/F和里面分支斟酌逻辑来绕过。PC生成算法完毕如下:
4
提醒缓冲器
FU 里面有两个背靠背的提醒缓冲区。缓冲区 1缓冲从提醒存储器中得到的提醒。缓冲区 1 不错告成侦察反馈接口 (Response I/F)。缓冲区 2缓冲来自缓冲区 1 的提醒,然后通过 DU I/F 将其发送到 DU。这两个缓冲区组成了 FU 里面的提醒活水线。
5
分支斟酌逻辑
正如上文所参谋的,咱们必须在 FU 中添加分支斟酌逻辑来缓解限度风险。咱们将完毕一个通俗且静态的分支斟酌较法。该算法的主要内容如下:
老是会进行无条目跳转。
淌若分支提醒是向后跳转,则扩充分支。因为可能性如下:
1、这条提醒可能是某些do-while 轮回的轮回退出检查的一部分。在这种情况下,淌若咱们扩充分支提醒,则正确的概率更高。
淌若分支提醒是上前跳转,则不要扩充它。因为可能性如下:
2、这条提醒可能是某些for 轮回或while 轮回的轮回进口检查的一部分。淌若咱们不扩充分支并连接扩充下一条提醒,则正确的概率更高。
3、这条提醒可能是某个if-else语句的一部分。在这种情况下,咱们老是假定if条目为真,并连接扩充下一条提醒。表面上,这笔来去(bargain)有50%是正确的。
缓冲区 1 的提醒包由分支斟酌逻辑监控和分析,并生因素支斟酌信号:branch_taken。该分支斟酌信号随后被注册,并与发送给 DU 的提醒包同步传输。分支斟酌信号通过 DU 接口发送给 DU。
6
DU
这是得到单位妥协码单位之间用于发送灵验载荷的主要接口。灵验载荷包含得到的提醒和分支斟酌信息。
由于这是CPU两个活水线阶段之间的接口,因此完毕了灵验就绪I/F。以下信号组成了DU I/F:
在之前的博文中,咱们参谋了 CPU 活水线中停顿和刷新的观念过甚进犯性。咱们还参谋了 Pequeno 架构中需要停顿或刷新的各种场景。因此,必须在 CPU 的每个活水线阶段中集成得当的停顿和刷新逻辑。细则在哪个阶段需要停顿或刷新至关进犯,以及该阶段中哪些逻辑部分需要停顿和刷新。
在实施停顿和刷新逻辑之前的一些初步想法:
活水线阶段可能会因外部或里面产生的条目而住手。
管谈阶段不错通过外部或里面生成的条目进行刷新。
Pequeno 中莫得结合式的停顿或刷壮盛成逻辑。每个阶段可能都有我方的停顿和刷壮盛成逻辑。
活水线中一个阶段只可被下一个阶段所险阻。任何阶段的险阻最终都会影响活水线的上游,并导致通盘这个词活水线险阻。
下流活水线中的任何一个阶段都不错刷新某个阶段。这被称为活水线刷新,因为上游的通盘这个词活水线都需要同期刷新。在 Pequeno 中,只好扩充单位 (EXU)中的分支未射中才需要进行活水线刷新。
停顿逻辑包含产生土产货和外部停顿的逻辑。刷新逻辑包含产生土产货和活水线刷新的逻辑。
土产货停顿在里面产生,并在土产货用于住手现时阶段的运行。外部停顿在里面产生,并通过外部发送到上游活水线的下一级。土产货和外部停顿均基于里面条目以及下流活水线下一级的外部停顿而产生。
土产货刷新 (Local flush)是指在里面生成并用于土产货刷新阶段的刷新。外部刷新或管谈刷新 (Pipeline flush)是指在里面生成并发送到外部上游管谈的刷新。这会同期刷新上游的通盘阶段。土产货刷新和外部刷新均基于里面条目生成。
只好 DU 不错从外部住手 FU 的运行。当 DU 置位停顿时,FU 的里面提醒活水线(缓冲区 1 –> 缓冲区 2)应立即住手,况且由于 FU 无法再吸收来自 IMEM 的数据包,它还应向 IMEM 置位mem_stall 。把柄 IMEM 中的活水线/缓冲深度,PC 生成逻辑最终也可能被来自 IMEM 的cpu_stall住手,因为 IMEM 无法再吸收任何央求。FU 中不存在导致土产货停顿的里面条目。
只好 EXU 可之外部刷新 FU。EXU 会在 CPU 提醒活水线中启动branch_flush 函数,并传入刷新活水线后要得到的下一条提醒的地址 ( branch_pc )。FU 提供了刷新接口 (Flush I/F),以便经受外部刷新。
FU 中的缓冲区 1、缓冲区 2 和 PC 生成逻辑通过branch_flush刷新。来自分支斟酌逻辑的信号branch_taken也充当了对缓冲区 1 和 PC 生成逻辑的土产货刷新。淌若分支被接收:
下一条提醒应从分支斟酌的 PC 中得到。因此,PC 生成逻辑应被刷新,况且下一条 PC 应 = branch_pc。
缓冲区 1 中的下一条提醒应被刷新并使其无效,即插入 NOP/bubble。
奇怪为什么 Buffer-2 莫得被branch_taken刷新?因为来自 Buffer-1 的分支提醒(肃肃刷壮盛成)应该不才一个时钟周期缓冲到 Buffer-2,并允许其在活水线中连接扩充。这条提醒不应该被刷新!
提醒内存活水线也应该进行得当的刷新。IMEM 刷新mem_flush由branch_flush和branch_taken生成。
让咱们整合面前为止联想的通盘微架构,以完成 Fetch Unit 的架构。
好了,诸位!咱们也曾告捷联想出Pequeno的Fetch Unit了。在接下来的部分中,咱们将联想Pequeno 的解码单位(DU:Decode Unit)。
解码单位
解码单位(DU)是 CPU 活水线的第二阶段,肃肃异日自取指单位(FU)的提醒译码,并送至扩充单位(EXU)。此外,它还肃肃将寄存器地址译码,并送至寄存器文献进行寄存器读操作。
让咱们界说解码单位的接口。
其中,FU接口是得到单位妥协码单位之转折收灵验载荷的主要接口。灵验载荷包含得到的提醒和分支斟酌信息。此接口已在上一部分参谋过。
EXU接口是解码单位和扩充单位之间发送灵验载荷的主要接口。灵验载荷包括解码后的提醒、分支斟酌信息妥协码数据。
以下是组成 EXU I/F 的提醒和分支斟酌信号:
解码数据是 DU 从得到的提醒中解码并发送到 EXU 的进犯信息。让咱们来了解一下 EXU 扩充一条提醒需要哪些信息。
Opcode、funct3、funct7:记号 EXU 对操作数要扩充的操作。
操作数:把柄操作码,操作数不错是寄存器数据(rs0,rs1),用于写回的寄存器地址(rdt),或 12 位/20 位立即数。
提醒类型:记号必须处理哪些操作数/立即值。
解码过程可能相比毒手。淌若您正确贯穿了 ISA 和提醒结构,就不错识别出不同类型的提醒景观。识别景观有助于联想 DU 中的解码逻辑。
以下信息被解码并通过 EXU I/F 发送到 EXU。
EXU 将使用此信息将数据解复用到得当的扩充子单位并扩充提醒。
对于 R 型提醒,必须解码并读取源寄存器rs1和rs2 。从寄存器读取的数据即为操作数。通盘通用用户寄存器都位于 DU 外部的寄存器堆中。DU 使用寄存器堆接口将rs0和rs1 的地址发送到寄存器堆进行寄存器侦察。从寄存器堆读取的数据也应与灵验载荷沿途在归并时钟周期内发送到 EXU。
寄存器文献读取寄存器需要一个周期。DU 也需要一个周期来寄存要发送到 EXU 的灵验载荷。因此,源寄存器地址由组合逻辑告成从 FU 提醒包解码。这确保了 1) 从 DU 到 EXU 的灵验载荷和 2) 从寄存器文献到 EXU 的数据的时序同步。
只好 EXU 不错从外部住手 DU 的运行。当 EXU 置位住手时,DU 的里面提醒活水线应立即住手,况且由于无法再吸收来自 FU 的数据包,它还应向 FU 置位住手。为了完毕同步操作,寄存器文献应与 DU 沿途住手,因为它们都位于 CPU 五级活水线的归并级。因此,DU 将外部住手从 EXU 反馈到寄存器文献。DU 里面不存在导致土产货住手的情况。
只好 EXU 可之外部刷新 FU。EXU 会在 CPU 提醒活水线中启动branch_flush 函数,并传入刷新活水线后要得到的下一条提醒的地址 ( branch_pc )。DU 提供了刷新接口 (Flush I/F),以便经受外部刷新。
里面活水线由branch_flush刷新。来自 EXU 的branch_flush应该立即使指向 EXU 的 DU 提醒无效,且蔓延时辰为 0 个时钟周期。这是为了幸免不才一个时钟周期 EXU 中出现潜在的限度风险。
在取指单位 (Fetch Unit) 的联想中,咱们莫得在收到branch_flush 提醒后,以 0 周期蔓延使 FU 提醒失效。这是因为 DU 不才一个时钟周期也会被刷新,因此 DU 中不会发生限度冒险 (control hazard)。是以,莫得必要使 FU 提醒失效。相同的想路也适用于从 IMEM 到 FU 的提醒。
上述经由图展示了来自 FU 的提醒包和分支斟酌数据如安在提醒活水线的 DU 中进行缓冲。DU 中仅使用单级缓冲。
让咱们整合迄今为止联想的通盘微架构,以完成解码单位的架构。
面前咱们也曾完成了:取指单位(FU)、译码单位(DU)。在接下来的部分中,咱们将联想Pequeno的寄存器文献。
寄存器文献
在 RISC-V CPU 中,寄存器文献是一个关节组件,它由一组通用寄存器组成,用于在扩充时代存储数据。Pequeno CPU 有 32 个 32 位通用寄存器 ( x0 – x31 )。
寄存器x0称为零寄存器 (zero register)。它被硬联接到一个常量值 0,提供一个有用的默许值,可与其他提醒沿途使用。假定您想将另一个寄存器运行化为 0,只需扩充mv x1, x0即可。
x1-x31是通用寄存器,用于保存中间数据、地址和算术或逻辑运算的效果。
在前文联想的 CPU 架构中,寄存器文献需要两个侦察接口。
当中,读侦察接口用于读取 DU 发送地址处的寄存器。某些提醒(举例ADD)需要两个源寄存器操作数rs1和rs2。因此,读取侦察接口 (I/F) 需要两个读取端口,以便同期读取两个寄存器。读取侦察应为单周期侦察,以便读取数据与 DU 的灵验载荷在归并时钟周期内发送到 EXU。这么,读取数据和 DU 的灵验载荷在活水线中保持同步。
写侦察接口用于将扩充效果写回到 WBU 发送地址处的寄存器。扩充收尾时仅写入一个宗旨寄存器rdt 。因此,一个写入端口就迷漫了。写入侦察应为单周期侦察。
由于 DU 和寄存器文献需要在活水线的归并阶段保持同步,因此它们应该遥远沿途住手(为什么?请稽察上一部分的框图!)。举例,淌若 DU 住手,寄存器文献不应将读取数据输出到 EXU,因为这会损坏活水线。在这种情况下,寄存器文献也应该住手。这不错通过将 DU 的住手信号回转生成寄存器文献的read_enable来确保。当住手灵验时,read_enable被驱动为低电平,先前的数据将保留在读取数据输出端,从而灵验地住手寄存器文献操作。
由于寄存器文献不向EXU发送任何提醒包,因此它不需要任何刷新逻辑。刷新逻辑只需在DU里面处理。
一言以蔽之,寄存器文献联想有两个孤独的读取端口和一个写入端口。读写侦察均为单周期。读取的数据会被寄存。最终架构如下:
面前咱们也曾完成了:取指单位(FU)、译码单位(DU)、寄存器文献。
后续部分,敬请期待。
https://chipmunklogic.com/digital-logic-design/designing-pequeno-risc-v-cpu-from-scratch-part-1-getting-hold-of-the-isa/
半导体杰作公众号推选
专注半导体限制更多原创内容
情愫公共半导体产业动向与趋势
*免责声明:本文由作家原创。著作内容系作家个东谈主不雅点,半导体行业不雅察转载仅为了传达一种不同的不雅点,不代表半导体行业不雅察对该不雅点赞同或复古,淌若有任何异议,迎接联系半导体行业不雅察。
今天是《半导体行业不雅察》为您共享的第4030期内容,迎接情愫。
『半导体第一垂直媒体』
及时 专科 原创 深度
公众号ID:icbank
可爱咱们的内容就点“在看”共享给小伙伴哦