设计RISC-V CPU,第2部分:成功执行(某些)说明

2021-03-20 08:56:29

此前的这一份额是"基本上解释了FPGA和A' Hello World' nmigen例子。" 1在这篇文章中,我将详细说明我的CPU目前的设计,并追溯到我沿途所做的各种错误。与上一篇文章一起,我主要针对软件工程师的旨在新的硬件设计 - 但我希望它对尼格珍,RISC-V或CPU设计中的任何人感兴趣。

我希望我沿途的错误将是考虑这些问题的有用框架:

您可以在此处在此处撰写最新版本时查看CPU的代码。

RISC-V("风险五")是一个开放标准指令集架构(ISA)。" RISC"手段"减少指令集计算机"大概意味着Theisa优先考虑简单的指示。相比之下,CISC(复杂的指令套件)ISAS被优化,以便在只有您的目录中执行动作,因此它们的指令通常更复杂。 ARM Architecturesare RISC; X86相关的架构是CISC。

通常在工业中,ISAS获得专利,因此为了实现供应商的ISA(昂贵的)许可。通常,商业ISAS差不多,即使在此类许可协议之后,设计细节背后的动机并不总是不行性。

避免&#34的ISA;过度架构和#34;对于特定的微型校准urestyle,但这允许有效地实现其中任何一个。

此外,很多商业架构都很复杂,背负着兼容性约束。 RISC-V提供新的开始!

ISA不解释如何设计CPU的详细信息 - 它刚刚定义CPU的抽象模型,主要是通过定义TheCPU必须支持的指示,包括:

指令的编码(即如何构建CPU将运行的机器代码)

如何实际创建一个实现这些要求的CPU取决于我们\ o /

我试图设计一个单级CPU;也就是说,我试图设计一个Cuthat,每个时钟周期都没有管道的指令。通常是CPOSHAVE PIPELINING,以最大限度地提高效率。我希望能够避免这种速度更加适合我的学习。也许它会使设计的其他方面(如我的程序计数器有3个输出),但是当我只需要思考这个时钟周期和下一个时钟周期时,我肯定会发现它更容易在Myhead中保持设计。我将在我实施的下一个博客文章中更详细地讨论这一点。我有一个计划如何在一个周期中制作这些工作,但他们确实构成了一个特殊的挑战,我可能会改变我的设计作为听力。

RISC-V定义各种ISA模块;我将实现RV32i,Base32位整数指令集。

要设计我的CPU,我首先查看了jal(跳跃和链接)和addi(addimmediate)的指令,并画出了解码这些指令所需的硬件的框图。如果你&#39,我认为addi指令是掌握的,不习惯考虑机器代码如何衡量,所以我们' ll从那开始。如果这一切都是完全陌生的,你可以享受我对ARM组装的介绍。

直接类似于源代码中的文字价值;值本身在指令中编码,而不是例如例如。从寄存器中检索。

一旦我们读取了操作码,我们就知道位12-14包含了Funct3字段(3位3位),它们编码这是ADDI指令(或SLTI,ANDI& c。)

检索存储在寄存器RS1中的值,因此,我们的寄存器文件需要输入用于选择该寄存器的输入,以及从该寄存器读取的数据的输出。

签名将是必要的 - 这只是一个窄于(在我们的案例)32位的数字的过程,并填充了两种' s补充算术将被执行。我在下面的图中赢得了这个'

在所有这一切中都是隐式的,我们' ll需要某种方式来检索指令等,并将其传递给指令解码器。程序计数器是告诉指令存储器的地址以检索指令。

跳转和链接(jal)指令立即对符号偏移量乘以2个字节的倍数。偏移量是签名的,并添加到PC Toform跳转目标地址。 [...] jal将跳转(PC + 4)的指令的地址存储到寄存器RD中。

在这里有很多进展,特别是如果你arean' t熟悉使用冲突代码。我们上面注意到,程序计数器设置下次执行哪些指令。通常,程序计数器只需递增到每个周期的那个地址到目前述 - 那个'我们如何继续执行我们的程序!但是,我们的程序调用存储在内存中其他地方的子程序;我们' dneed是一种跳到子程序地址的方法。或者说,我们想要一个英特诺伊州(嵌入式软件中的无处不在!);我们需要一种方法可以在循环开始时跳跃4返回地址。 jal允许这些情况。

"链接" jal的一部分是下一个指令存储在theStinal寄存器中的部分。这是方便的跳转用于执行ASUBRoutine:一旦子程序完成,我们就可以跳回该存储intricle地址。

立即的LSB 5未被编码,因为它必须是0:支持仅支持2个字节的倍数。

无声地引起了我在我的代码中有些痛苦,所以它有趣地对我来说,Itwon' t是下面的框图的一部分。但它' stinstruction解码器内部的部分,而不是它的界面,这是我们在用这些图中确定的。随着RISC-V中的其他指导格式,默认的比特似乎可能似乎是荒谬的,但它们&#39

下一个挑战是绘制一个实现ADDI和JAL的框图。第一个明显的问题:alu的输入在两个杂志中都是不同的,也是输出的接线。我们需要某种逻辑块,根据某些控制信号在它们之间挑选它们:多路复用器(MUX)。

我们还需要一种方法来告诉程序计数器,无论下一个名字身份是否应来自SET地址或递增CurrendAddress。

在这里'我的设计目前看起来像什么(不包括一些我已经知道的东西,因为我知道我稍后需要它们,就像上述文件上的两个读取选择/数据信号):

如我之前的博客文章所涵盖,我使用nmigen实现我的设计。作为i' Mcurricly设计单级CPU,更多的设计是Combinatorial,而不是同步的,因为我不需要加工所需的额外的状态。这最有可能意味着我的设计不适当地运行,但它不是我的关注点。

我不认为它'很有助于发布我的实现Inthis博客的所有源代码,但我将在此处包含一些代码来说明我疯狂地从中吸取的错误。

在同步更新生效时,我最初在安全性地确认后,我最初在安全性地执行了我的程序计数器。我最初只有PC和PC_INC产出,因为我没有真正了解PC和PC_Next。它' s采取了一些习惯于考虑Whole逻辑电路"发生在一次" 7,而是在写软件时依次思考依次思考。这是导致我的混乱。以这种方式正确扫描您的电路是关键,如果您曾经写过软件,则挑战。

1"""程序柜台""" 2导入nmigen为nm 3 4 instr_bytes = 4 5 6 7类编程器(nm .elaboraitable):8"""" 9程序计数器10 11 *负载(IN):低至增量,高加载地址12 * INPUT_ADDRESS(IN):在加载地址13 14 * PC(OUT)时使用的输入:执行该指令的地址时钟周期15 * pc_next(out):下次执行的指令的地址16循环17 * pc_inc(out):在执行的指令之后的地址在执行18个时钟周期19""""& #34; 20 21 def __init__(self,width = 32):22 self .load = nm .signal()23 self .input_address = nm .signal(宽)24 self .pc = nm .signal(宽)25 self .pc_next = nm .signal(宽度)26自我.pc_inc = nm。股票(宽度)27 28 def精细精致(self,_):29 m = nm .module()30 31 m .d .comb + = self .pc_inc .eq(self .pc + instr_bytes)32 m .d .sync + = self .pc .eq(self .pc_next)33 34,m .if(self .load):35 m .d .comb + = self .pc_next .eq(self .input_address)36使用m .else():37 m .d .comb + = self .pc_next .eq(self .pc_inc)38 39返回m