https://m.toutiao.com/is/ejKKhEt/ 我在数字逻辑设计方面并没有经验。也就是说,直到最近我才决定尝试设计自己的 CPU,并在 FPGA 上运行!如果你也是一名软件工程师,并对硬件设计有兴趣,那么我希望这一系列关于我所学到的知识的文章能够对你有所帮助,并让你感到有趣。本系列文章的第一部分中,将回答以下问题:
我将在以后的系列文章中详细讨论我的 CPU 设计和 RISC-V 架构,并将回答以下问题:
你可以在这里看到我写这篇文章时的 CPU 的代码,或者在这里查看最新的版本。 什么是数字逻辑设计?数字逻辑设计就是设计一个逻辑电路,对二进制数值进行运算。基本元件是逻辑门:例如,与门一样,有两个输入和一个输出。它的输出为 1 或 iff,两个输入均为 1。 我们所设计的同步电路,一般都是利用触发器来储存状态,使电路运行与共时钟同步。触发器由逻辑门组成。 模拟电路设计包括构成逻辑门的电子元件,例如晶体管和二极管。这种抽象通常是用于直接处理来自模拟传感器的信号的应用,例如无线电接收器。在设计 CPU 时,这种抽象水平是行不通的:现代的 CPU 有几十亿个晶体管! 相反,我们使用的工具可以将数字逻辑设计转化为不同的有用格式:FPGA 的配置(见下文);模拟;晶片布局。 FPGA 是什么,为什么要用 FPGA?上文中我们指出,不管我们是创建自定义 ASIC 芯片还是配置 FPGA,都可以使用相同的数字逻辑设计工具。现场可编程门阵列(Field-Programmable Gate Array,FPGA)是一种集成集成电路,其中包含了可编程逻辑块阵列。你可以把它想象成一个大型的逻辑门阵列,可以通过多种方式连接起来。 定制一款芯片动辄需要几百万美元,当然,一旦芯片被生产出来,就无法对它进行更改。所以 FPGA 通常用于下列情况:
缺点是什么?那就是 FPGA 的单芯片成本要高得多,并且由于它能够以非常灵活的方式将逻辑块连接在一起,因此速度通常要慢得多。与此相反,定制的设计可以减少晶体管的数量,而无需考虑灵活性。 在我看来,比较 ASIC 的定制设计过程和 FPGA 的设计过程是很有帮助的:
我需要什么工具?硬件描述语言:我使用的是 nMigen你可能听说过 Verilog 或 VHDL:这两种流行的硬件描述语言(hardware description language,HDL)。这里我所说的“流行”,是指广泛使用,而非广受欢迎。 我不会假装对这些工具很了解。我只知道那些比我更聪明的人,有着丰富的逻辑设计经验,却对这些工具恨之入骨。由于 Verilog 和其他类似工具存在的问题,人们尝试着开发出更有用、更友好的替代方法。nMigen 就是在 Python 中创建一 门领域专用语言的项目。用它自己的话就是:
假如你和我一样,从未使用过 Verilog,那么这些对你来说不仅仅是抽象的含义。但是听起来确实很有前景,而且我可以证明,在没有 Verilog 障碍的情况下,从逻辑设计开始就非常简单。如果你对 Python 非常熟悉,我将推荐它! 我能想到的唯一缺点是,nMigen 仍然处于开发阶段,特别是文档还不完整。但你可以通过 chat.freenode.net 的 #nmigen 频道找到有用的社区。 用于检查模拟的波形显示器:我使用的是 GTKWavenMigen 提供了模拟工具。我将它用于用 pytest 编写的测试。为了帮助调试,我记录了这些测试中的信号,并在波形显示器中观察它们。 FPGA 开发板:我使用的是 myStorm BlackIce II你不必使用 FPGA 开发板来创建自己的 CPU。在模拟中,你可以做任何事情。对于我来说,工作中使用板子的乐趣就是能闪烁 LED,看着自己的设计运行。 当然,如果你要创建的东西比我的最基本的 CPU 更有用,那么你可能需要一些硬件来运行它,而这并非“可选”选项! 开始使用 nMigen在 nMigen 系统中,我并没有立刻尝试设计一个 CPU,而是首先制作一个算术逻辑单元(Arithmetic Logic Unit ,ALU)。 在我见过的所有 CPU 设计中, ALU 是一个关键部件:它执行算术运算。 为什么要从这里开始呢?我知道我的 CPU 需要一个 ALU;我知道我能做一个简单的 ALU;我知道当开始一个新的项目时,做事情的感觉是一种重要的动力! 我的设计看起来像这样: '''Arithmetic Logic Unit'''import enumimport nmigen as nmclass ALUOp(enum.IntEnum):'''Operations for the ALU''' ADD = 0 SUB = 1 class ALU(nm.Elaboratable):''' Arithmetic Logic Unit * op (in): the opcode * a (in): the first operand * b (in): the second operand * o (out): the output '''def __init__(self, width):''' Initialiser Args: width (int): data width ''' self.op = nm.Signal() self.a = nm.Signal(width) self.b = nm.Signal(width) self.o = nm.Signal(width)def elaborate(self, _): m = nm.Module()with m.Switch(self.op):with m.Case(ALUOp.ADD): m.d.comb += self.o.eq(self.a + self.b)with m.Case(ALUOp.SUB): m.d.comb += self.o.eq(self.a - self.b)return m 复制代码 正如你所看到的,我们已经创建了大量的 nMigen Signal 实例,以很好地表示定义 ALU 接口的信号!但这个复杂的方法是什么呢?这个 elaborate 方法又是什么呢?我的理解是,“elaboration”是合成网表的第一步的名称(见上文)。在上面的 nMigen 代码中,我们的想法是,已经创建了一些可阐述的结构(通过继承 nm.Elaboratable),也就是用来描述想要合成的数字逻辑的东西。这个 elaborate 方法描述了数字逻辑。它必须返回一个 nMigen 模块。 下面让我们进一步了解一下 elaborate 的方法的内容。Switch 将创造某种形式的合成设计决策逻辑。但什么是 m.d.comb 呢? nMigen 提出了同步(m.d.sync)和组合(m.d.comb)控制域的概念。来自 nMigen 文档:
下面以移位寄存器为例,说明要设计的逻辑。假定移位寄存器有 8 位,每个时钟周期,该位值都会有一个移位(最左边的值来自输入信号)。这必然是同步的:不能通过简单地将位连接在一起来创建这个功能,而在 nMigen 中,将位分配到组合域中将代表此功能。 我将在这个系列博客的下一部分详细讨论我的 CPU 设计。现在的情况是,我试图在每个周期中只停用一个指令,而不使用流水线——这很不寻常,但是我希望这样做可以简化 CPU 的各个方面。其结果是,大多数逻辑是组合的,而非同步的,因为我几乎没有在时钟周期之间维持这种状态。现在,我的寄存器文件设计有问题,为了解决这个问题,我可能需要重新考虑我的“无流水线”想法。 编写测试对于 Python 测试,我喜欢使用 pytest,当然你也可以使用任何能吸引你的框架。以下是我在上面测试的 ALU 代码:
复制代码 以及我的 conftest.py: '''Test configuration'''import osimport shutilimport nmigen.simimport pytestVCD_TOP_DIR = os.path.join( os.path.dirname(os.path.realpath(__file__)),'tests','vcd')def vcd_path(node): directory = os.path.join(VCD_TOP_DIR, node.fspath.basename.split('.')[0]) os.makedirs(directory, exist_ok=True)return os.path.join(directory, node.name + '.vcd')@pytest.fixture(scope='session', autouse=True)def clear_vcd_directory(): shutil.rmtree(VCD_TOP_DIR, ignore_errors=True)@pytest.fixturedef comb_sim(request):def run(fragment, process): sim = nmigen.sim.Simulator(fragment) sim.add_process(process)with sim.write_vcd(vcd_path(request.node)): sim.run_until(100e-6)return run@pytest.fixturedef sync_sim(request):def run(fragment, process): sim = nmigen.sim.Simulator(fragment) sim.add_sync_process(process) sim.add_clock(1 / 10e6)with sim.write_vcd(vcd_path(request.node)): sim.run()return run 复制代码 每次测试都会生成一个 vcd 文件,我可以通过 GTKWave 等波形显示器来查看,以便调试。你会注意到,组合模拟固定运行的时间段是任意小的,而同步模拟功能运行的时间段是确定的时钟周期数。 一个信号产生于一个测试函数,它将从模拟器请求它的当前值。对于组合逻辑,我们生成 nnmigen.sim.Settle() ,要求完成模拟。 对于同步逻辑,还可以开始新的时钟周期,而不需要参数。 设计一个 CPU在熟悉了 nMigen 之后,我开始尝试绘制一个框图来显示我的 CPU。在本系列博客的下一部分中,我将对这个问题进行更详细的讨论,但我将简单地说,我先绘制出一个指令所需要的逻辑,然后绘制出另一个指令的逻辑,然后找到如何将它们结合起来的方法。这里有第一个混乱的草图: 在弄清楚不同元件的接口要求是什么时,这个框图步骤非常有价值,但是在开始使用 nMigen 和在这个过程中学习数字逻辑设计之前,我不想这么做。修改后的框图如下所示: |
|
来自: 山峰云绕 > 《硬件描述语言工业软件》