设计一个RISC-VCPU,软件工程师如何学习硬件设计

2024-06-16 0

设计RISC-VCPU,软件工程师如何学习硬件设计

我没有数字逻辑设计经验。也就是说,直到最近我才决定尝试设计自己的CPU并在FPGA上运行它!如果您也是一名软件工程师并且对硬件设计感兴趣,我希望您会发现我学到的这一系列文章有用且有趣。在本系列文章的第一部分中,我们将回答以下问题:

什么是数字逻辑设计?

如何开始以及应该使用哪些工具?

我将在未来的系列文章中详细讨论我的CPU设计和RISC-V架构,并回答以下问题:

数字逻辑设计和软件设计的本质区别是什么

数字逻辑设计和软件设计有什么相似之处?

您可以在此处查看我编写的CPU代码,或在此处查看最新版本。

什么是数字逻辑设计?

数字逻辑设计是设计逻辑电路来对二进制值执行运算。基本元件是逻辑门:例如,与门有两个输入和一个输出。如果两个输入都为1,则其输出为1。

我们设计的同步电路通常使用触发器来存储状态,以便电路操作与公共时钟同步。触发器由逻辑门组成。

模拟电路设计包括构成逻辑门的电子元件,例如晶体管和二极管。这种抽象通常用于直接处理来自模拟传感器的信号的应用程序,例如无线电接收器。这种抽象级别在设计CPU时不起作用:现代CPU拥有数十亿个晶体管!

相反,我们使用将数字逻辑设计转换为不同可用格式的工具:FPGA的配置(见下文);

什么是FPGA?为什么要使用FPGA?

我们在上面指出,无论我们是创建定制ASIC芯片还是配置FPGA,都可以使用相同的数字逻辑设计工具。现场可编程门阵列(FPGA)是包含一系列可编程逻辑块的集成电路。您可以将其视为可以通过多种方式连接的大型逻辑门阵列。

定制一款芯片往往要花费数百万美元,当然,芯片一旦制造出来就无法更改。所以FPGA通常用于以下几种情况:

由于缺乏资金,无法承担定制ASIC的费用(例如,如果您只是像我一样的黑客,而不是ARM或Intel)。

无力制造定制ASIC,因为产量太低,无法证明一次性成本较高(例如,如果您使用定制数据采集硬件制造少量MRI机器)。

需要灵活性。

有什么缺点?FPGA每个芯片的成本要高得多,而且由于它能够以非常灵活的方式将逻辑块连接在一起,因此速度通常要慢得多。相比之下,定制设计可以在不考虑灵活性的情况下减少晶体管的数量。

我认为,将ASIC的定制设计流程与FPGA的设计流程进行比较很有用:

逻辑设计:与FPGA一样,ASIC的逻辑设计也是使用硬件描述语言完成的。

验证:FPGA设计可以验证,但ASIC除外!设计过程更加严谨。毕竟,设计一旦产生,就无法更改!验证通常涉及对部分设计的形式验证。

综合:这将创建一个网表、逻辑块及其连接的列表。连接称为网络,块称为单元。对于FPGA和ASIC,这些单元是特定于供应商的。

布局和布线(PR):对于FPGA,这涉及将网表中描述的逻辑块映射到FPGA中的实际块。生成的二进制文件通常称为比特流。对于ASIC,这涉及决定将单元放置在芯片上的位置以及如何连接它们。这两种应用程序通常都使用自动优化工具。

我需要什么工具?

硬件描述语言:我用的是nMigen

您可能听说过Verilog或VHDL:两种流行的硬件描述语言(HDL)。当我在这里说“流行”时,我的意思是广泛使用,而不是广泛流行。

我不会假装对这些工具了解很多。我所知道的是,那些比我聪明并且在逻辑设计方面有丰富经验的人讨厌这些工具。由于Verilog和其他类似工具存在问题,人们一直在努力开发更有用和用户友好的替代方案。nMigen是一个用Python创建领域特定语言的项目。用他自己的话说:

尽管使用Verilog和VHDL进行硬件设计比输入原理图更快,但由于某些原因硬件设计仍然繁琐且低效。对于目前在逻辑设计中发挥重要作用的同步电路来说,事件驱动模型引入了不必要的问题并引入了手工编码。违反直觉的算术规则会导致更陡峭的学习曲线,并为微小的设计缺陷提供滋生的土壤。最后,通过“生成”语句对逻辑过程生成(元编程)的支持非常有限,并且限制了代码的泛化、重用和组织方式。

针对这些问题,我们开发了nMigenFHDL,这是一个库,它取代了事件驱动范式,采用了组合语句和同步语句的概念,并采用算术规则使整数始终表现得像数学整数,最重要的是,Python允许到程序。构建设计的逻辑。它允许硬件设计人员利用Python语言的丰富性:面向对象编程、函数参数、生成器、运算符重载、库等来构建组织良好且可重用的优雅设计。

如果您像我一样从未使用过Verilog,那么它对您来说不仅仅是抽象。但这听起来很有希望,而且我可以证明,没有Verilog的障碍,开始逻辑设计非常简单。如果你对Python非常熟悉,我会推荐它!

我能想到的唯一缺点是nMigen仍在开发中,尤其是文档不完整。但您可以通过chat.freenode.net上的#nmigen频道找到有用的社区。

用于检查模拟的波形显示:我使用GTKWave

nMigen提供模拟工具。我用它来进行pytest编写的测试。为了帮助调试,我记录了这些测试的信号并在波形显示中观察它们。

FPGA开发板:我用的是myStormBlackIceII

您不需要使用FPGA开发板来创建自己的CPU。在模拟中你可以做任何事情。对我来说,乐趣在于在工作中使用一块板来闪烁LED并观看我的设计运行。

当然,如果您要创建比我最基本的CPU更有用的东西,您可能需要硬件来运行它,而这不是一个“可选”选项!

从nMigen开始

在nMigen系统中,我并没有立即尝试设计CPU,而是先做了一个算术逻辑单元(ALU)。在我见过的所有CPU设计中,ALU都是一个关键组件:它执行算术运算。

为什么从这里开始?我知道我的CPU需要ALU;我知道我可以制作一个简单的ALU;

我的设计看起来像这样:

"""算术逻辑单元"""importenum

进口线粒体

classALUOp(enum.IntEnum):

"""ALU的操作"""

添加0

子1

classALU(pm.可扩展):

”“”

算术逻辑单元

*on(in):teop代码

*a(in):第一个测试边

*b(in):第二个操作数

*o(out):输出

”“”

定义(自身,宽度):

”“”

初始化器

参数:

width(int):数据宽度

”“”

self.opnm.Signal()

self.anm.信号(宽度)

self.bnm.信号(宽度)

self.unm.信号(宽度)

溶解(自身,):

mnm.Module()

withm.Switch(self.op):

withm.Case(ALUOp.ADD):

m.d.combself.o.eq(self.aself.b)

withm.Case(ALUOp.SUB):

m.d.combself.o.eq(self.a-self.b)

返回

复制代码

正如你所看到的,我们创建了大量的nMigenSignal实例来很好地表示定义ALU接口的信号!但这个复杂的方法是什么呢?这个扩展方法是什么?我的理解是,“elaboration”是综合网表的第一步的名称(见上文)。在上面的nMigen代码中,想法是我们创建了某种可精细结构(通过继承nm.Elaborable),这通常是描述了你想要合成的数字逻辑。这个扩展方法必须返回一个nMigen模块。

我们来仔细看看扩展方法的内容。Switch将创建某种形式的综合设计决策逻辑。但m.d.comb是什么?nMigen引入了同步(m.d.sync)和组合(m.d.comb)控制域的概念。来自nMigen文档:

控制域是一组在相同条件下改变其值的命名信号。

所有设计都有一个预定义的组合域,其中包含当用于计算信号的任何值发生变化时发生变化的所有信号。名称“comb”是为组合字段保留的。

设计还可以具有任意数量的用户定义的同步域,也称为时钟域,其中包含当域的时钟信号上出现特定边沿时发生变化的信号,或者,对于具有异步复位功能的域,域的复位信号将发生变化。大多数模块仅使用一个同步域。

信号分配在组合域和同步域中表现不同。一般来说,同步域中的信号包含设计的状态,而组合域中的信号不能形成反馈环或维持状态。

下面以移位寄存器为例来说明所要设计的逻辑。假设移位寄存器有8位,每个时钟周期位值将移位一位(最左边的值来自输入信号)。它必然是同步的:此功能不能通过简单地将位连接在一起来创建,而在nMigen中,组合字段中的位分配将代表此功能。

我将在本博客系列的下一部分中详细讨论我的CPU设计。发生的情况是,我尝试在每个周期仅禁用一条指令而不使用流水线——这很不寻常,但我希望它能简化CPU的各个方面。结果是大部分逻辑是组合的而不是同步的,因为我几乎在时钟周期之间维持这种状态。现在我的注册表文件设计有问题,为了解决它,我可能不得不重新考虑我的“无管道”想法。

编写测试

对于Python测试,我喜欢使用pytest,但您可以使用任何您感兴趣的框架。这是我上面测试的ALU代码:

"""ALU测试"""

导入nmigen.sim

导入测试

来自riscyboiimportalu

@pytest.mark.parameterize(

“向上,a,b,o”,[

(alu.ALUOp.ADD,1,1,2),

(alu.ALUOp.ADD,1,2,3),

(alu.ALUOp.ADD,2,1,3),

(铝.ALUOp.ADD,258,203,461),

(alu.ALUOp.ADD,5,0,5),

(alu.ALUOp.ADD,0,5,5),

(alu.ALUOp.ADD,2**32-1,1,0),

(alu.ALUOp.SUB,1,1,0),

(alu.ALUOp.SUB,4942,0,4942),

(alu.ALUOp.SUB,1,2,2**32-1)])

deftestalu(combsim,op,a,b,o):

aluinstalu.ALU(32)

deftestbench():

Yieldaluinst.op.eq(op)

yieldaluinst.a.eq(a)

yieldaluinst.b.eq(b)

yieldnmigen.sim.Settle()

断言(yieldaluinst.o)o

Combsim(alumst、测试平台)

复制代码

和我的confest.py:

"""测试配置"""

进口

导入实用程序

导入nmigen.sim

导入测试

VCDTOPDIRos.path.join(

os.path.dirname(os.path.realpath(文件)),

“测试”,

“VCD”)

defvcd路径(节点):

指南.path.join(VCDTOPDIR,node.fspath.basename.split("").[0])

os.makedirs(目录,existokTrue)

returnos.path.join(目录,node.name".vcd")

@pytest.fixture(范围“会话”,autouseTrue)

defclearvcd目录():

Shutil.rmtree(VCDTOPDIR,ignoreerrorsTrue)

@pytest.fixture

defcombsim(请求):

defrun(片段,进程):

simnmigen.sim.模拟器(片段)

sim.addprocess(进程)

withsim.writevcd(vcdpath(request.node)):

sim.rununtil(100e-6)

往回走

@pytest.fixture

defsyncsim(请求):

defrun(片段,进程):

simnmigen.sim.模拟器(片段)

sim.addsyncprocess(进程)

sim.addclock(1/10e6)

withsim.writevcd(vcdpath(request.node)):

sim.run()

往回走

复制代码

每次测试都会生成一个vcd文件,我可以通过GTKWave等波形监视器查看该文件以进行调试。您将看到组合仿真运行任意小的设定周期,而同步仿真功能运行固定数量的时钟周期。

测试函数生成信号,该函数将从模拟器请求其当前值。对于组合逻辑,我们生成nnmigen.sim.Settle(),这需要模拟才能完成。

对于同步逻辑,也可以在没有参数的情况下启动新的时钟周期。

设计一个CPU

熟悉了nMigen后,我开始画一个框图来展示我的CPU。我将在本博客系列的下一部分中更详细地讨论这一点,但我会简单地说,我先绘制出一个指令所需的逻辑,然后绘制出另一个指令的逻辑,然后找到围绕它的方法结合。这是混乱的第一个草图:

这个框图步骤对于弄清楚不同组件的接口要求非常有价值,但直到我开始使用nMigen并在此过程中学习数字逻辑设计后,我才想这样做。修改后的框图如下所示:

请继续关注本博客系列的下一部分,我将深入研究RISC-V和CPU设计。我想使用第三部分重新设计我的设计,以处理我想要实现的完整指令集(RV32I)

关于作者:

lochsh是一位居住在英国牛津的软件工程师,在PerspectumDiagnostics工作,为医学成像诊断工具编写C语言。曾在CMRSurgical工作,在那里他为下一代手术机器人编写了裸机嵌入式C语言。我对Rust很感兴趣,写过很多Python代码,并且愿意尝试更多的函数式编程。

原文链接:

https://mcla.ug/blog/risc-v-cpu-part-1.html

本站文章均由用户上传或转载而来,该文章内容本站无法检测是否存在侵权,如果本文存在侵权,请联系邮箱:2287318951@qq.com告知,本站在7天内对其进行处理。

相关推荐

发布评论