GTLC全球技术领导力峰会·上海站,首批讲师正式上线! 了解详情
写点什么

设计一个 RISC-V CPU(一):软件工程师如何学习硬件设计

2021 年 4 月 06 日

设计一个 RISC-V CPU(一):软件工程师如何学习硬件设计

本文最初发表于作者个人网站,经原作者 Hannah McLaughlin 授权,InfoQ 中文站翻译并分享。


我在数字逻辑设计方面并没有经验。也就是说,直到最近我才决定尝试设计自己的 CPU,并在 FPGA 上运行!如果你也是一名软件工程师,并对硬件设计有兴趣,那么我希望这一系列关于我所学到的知识的文章能够对你有所帮助,并让你感到有趣。本系列文章的第一部分中,将回答以下问题:


  • 什么是数字逻辑设计?

  • 如何开始,我应该使用什么工具?


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


  • 数字逻辑设计与软件设计有什么本质区别?

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


你可以在这里看到我写这篇文章时的 CPU 的代码,或者在这里查看最新的版本。


什么是数字逻辑设计?


数字逻辑设计就是设计一个逻辑电路,对二进制数值进行运算。基本元件是逻辑门:例如,与门一样,有两个输入和一个输出。它的输出为 1 或 iff,两个输入均为 1。


我们所设计的同步电路,一般都是利用触发器来储存状态,使电路运行与共时钟同步。触发器由逻辑门组成。


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


相反,我们使用的工具可以将数字逻辑设计转化为不同的有用格式:FPGA 的配置(见下文);模拟;晶片布局。


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


上文中我们指出,不管我们是创建自定义 ASIC 芯片还是配置 FPGA,都可以使用相同的数字逻辑设计工具。现场可编程门阵列(Field-Programmable Gate Array,FPGA)是一种集成集成电路,其中包含了可编程逻辑块阵列。你可以把它想象成一个大型的逻辑门阵列,可以通过多种方式连接起来。


定制一款芯片动辄需要几百万美元,当然,一旦芯片被生产出来,就无法对它进行更改。所以 FPGA 通常用于下列情况:


  • 由于缺乏资金,无法负担制作定制 ASIC 的费用(例如,如果你只是像我这样的黑客,而不是 ARM 或英特尔)。

  • 无法负担制作定制 ASIC 的费用,因为产量太低,不值得一次性支付高昂的费用 (例如,如果你正在使用定制的数据采集硬件生产少量的 MRI 机器)。

  • 需要灵活性。


缺点是什么?那就是 FPGA 的单芯片成本要高得多,并且由于它能够以非常灵活的方式将逻辑块连接在一起,因此速度通常要慢得多。与此相反,定制的设计可以减少晶体管的数量,而无需考虑灵活性。


在我看来,比较 ASIC 的定制设计过程和 FPGA 的设计过程是很有帮助的:


  • 逻辑设计:就像做 FPGA 一样,ASIC 的逻辑设计也是用硬件描述语言来完成的。

  • 验证:FPGA 设计可能会被验证,但是可以期待 ASIC!设计的过程更严格了。毕竟,设计一旦制造出来就不能更改!验证通常包括设计部分的正式验证。

  • 合成:这将创建一个网表,一个逻辑块及其连接的列表。连接被称为网,而块被称为单元。对于 FPGA 和 ASIC 来说,单元是特定于厂商的。

  • 布局布线(Placement and routing,P&R):对于 FPGA 来说,它涉及到将网表中描述的逻辑块映射到 FPGA 中的实际块。由此产生的二进制通常称为比特流。对于 ASIC 来说,这涉及到决定在晶片上何处放置单元,以及如何将它们连接起来。这两种应用通常都要使用自动优化工具。


我需要什么工具?


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


你可能听说过 Verilog 或 VHDL:这两种流行的硬件描述语言(hardware description language,HDL)。这里我所说的“流行”,是指广泛使用,而非广受欢迎。


我不会假装对这些工具很了解。我只知道那些比我更聪明的人,有着丰富的逻辑设计经验,却对这些工具恨之入骨。由于 Verilog 和其他类似工具存在的问题,人们尝试着开发出更有用、更友好的替代方法。nMigen 就是在 Python 中创建一 门领域专用语言的项目。用它自己的话就是:


虽然用 Verilog 和 VHDL 进行硬件设计比输入原理图的速度要快,但是由于一些原因,硬件设计还是很枯燥,而且效率也不高。对目前逻辑设计中占有重要地位的同步电路而言,事件驱动模型引入了不必要的问题,并引入了人工编码。逆直觉的算术规则导致了更陡峭的学习曲线,并为设计上的微小缺陷提供了温床。最后,通过“generate”语句来支持逻辑过程生成(元编程)非常有限,并且限制了代码的通用、重用和组织方式。


针对这些问题,我们开发了 nMigen FHDL,该库取代了事件驱动范例,它采用了组合语句和同步语句的概念,并采用了算术规则,使整型始终像数学整型一样,最重要的是允许 Python 程序构建所设计的逻辑。这一点使硬件设计人员能够充分利用 Python 语言的丰富内容:面向对象编程、函数参数、生成器、操作符重载、库等,构建组织良好、可重用的优雅设计。


假如你和我一样,从未使用过 Verilog,那么这些对你来说不仅仅是抽象的含义。但是听起来确实很有前景,而且我可以证明,在没有 Verilog 障碍的情况下,从逻辑设计开始就非常简单。如果你对 Python 非常熟悉,我将推荐它!


我能想到的唯一缺点是,nMigen 仍然处于开发阶段,特别是文档还不完整。但你可以通过 chat.freenode.net 的 #nmigen 频道找到有用的社区。


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


nMigen 提供了模拟工具。我将它用于用 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 enum
import 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 文档


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


所有的设计都有一个预定义的组合域,其中包含所有的信号,当用来计算这些信号的任何值发生变化时,这些信号也随之发生变化。名称 comb 是为组合域保留的。


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


在组合域和同步域中,信号的赋值的行为各不相同。总的来说,同步域中的信号包含了设计的状态,而组合域中的信号并不能形成反馈回路或维持状态。


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


我将在这个系列博客的下一部分详细讨论我的 CPU 设计。现在的情况是,我试图在每个周期中只停用一个指令,而不使用流水线——这很不寻常,但是我希望这样做可以简化 CPU 的各个方面。其结果是,大多数逻辑是组合的,而非同步的,因为我几乎没有在时钟周期之间维持这种状态。现在,我的寄存器文件设计有问题,为了解决这个问题,我可能需要重新考虑我的“无流水线”想法。


编写测试


对于 Python 测试,我喜欢使用 pytest,当然你也可以使用任何能吸引你的框架。以下是我在上面测试的 ALU 代码:


"""ALU tests"""import nmigen.simimport pytest
from riscy_boi import alu
@pytest.mark.parametrize( "op, a, b, o", [ (alu.ALUOp.ADD, 1, 1, 2), (alu.ALUOp.ADD, 1, 2, 3), (alu.ALUOp.ADD, 2, 1, 3),(alu.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)])def test_alu(comb_sim, op, a, b, o): alu_inst = alu.ALU(32)
def testbench():yield alu_inst.op.eq(op)yield alu_inst.a.eq(a)yield alu_inst.b.eq(b)yield nmigen.sim.Settle()assert (yield alu_inst.o) == o
comb_sim(alu_inst, testbench)
复制代码


以及我的 conftest.py


"""Test configuration"""import osimport shutil
import nmigen.simimport pytest
VCD_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 和在这个过程中学习数字逻辑设计之前,我不想这么做。修改后的框图如下所示:



请关注本系列博客的下一部分,我将深入研究 RISC-V 和 CPU 设计。我想用第三部分来重新设计我的设计,使其适用于我要实现的全部指令集(RV32I)上工作😃


作者介绍:


Hannah McLaughlin,住在英国牛津的软件工程师,供职于 Perspectum Diagnostics,为医学图像诊断工具编写 C++。曾在 CMR Surgical 供职,在那里为下一代手术机器人编写裸机嵌入式 C。对 Rust 很感兴趣,已经写过很多 Python 代码,愿意尝试更多的函数式编程。


原文链接:


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

2021 年 4 月 06 日 18:291327

评论

发布
暂无评论
发现更多内容

信创舆情一线--5省发布区块链发展计划

统小信uos

区块链 舆情

数据分析师成长体系漫谈-数仓模型设计

analysis-lion

学习 数据仓库 数据分析 随笔杂谈

CORS 和 CSRF 修炼宝典

pingan8787

前端 Web CORS CSRF

人生就是体会矛盾的过程

封不羁

成长 感悟

鲲鹏说:高考之路你们走,高考阅卷我来守

脑极体

ArrayList源码阅读

慌张而黑糖

ArrayList 源码阅读

一致性hash

石刻掌纹

如何通过DDD构建一辆汽车

Winfield

领域驱动设计 DDD

rc-form源码解读

Lee Chen

前端进阶训练营

如何优雅地运用位运算实现产品需求?

梁桂钊

Java 架构

vagrant

飞翔

极客大学架构师训练营

华为云FusionInsight MRS通过信通院大数据能力评估 单集群突破2万+规模

FI洞见

大数据 FusionInsight MRS 华为云

这样的二维码,你见过吗?

诸葛小猿

Java Python 后端开发 二维码 myqr

Hadoop大数据存算分离下,如何解决新旧存储共存?

XSKY融合存储

毕业三年了,我开始明白为什么说三年是一个坎

鄙人薛某

程序员 程序人生 程序员成长 职场回顾

啃碎并发(七):深入分析Synchronized原理

猿灯塔

Java

架构师训练营-作业5

紫极

架构师师傅在训练营第5周感想

tuuezzy

架构师

技术选型课程小结

行下一首歌

极客大学架构师训练营

Java 后端博客系统文章系统——No1

猿灯塔

开发者必备——API设计问题

Noneplus

分布式缓存与消息队列

紫极

统一物品编码破解追溯“断链”困局

CECBC区块链专委会

redis系列之——分布式锁

诸葛小猿

Java redis 分布式 分布式锁

官宣 | 千呼万唤,Apache Flink 1.11.0 正式发布啦!

Apache Flink

flink

为了把握新基建风口,科技公司都在紧密筹备这件事...

极客时间企业版

Struct embedding in Go

Interstate5

golang time.Time dynamodb apigateway

如何学习Visual Studio Code

博文视点Broadview

学习 读书笔记 vscode 能力提升 编辑器

golang内存对齐

PONPON

go golang go内存对齐

依旧乐观的李彦宏,十年寻光的百度AI

脑极体

typora设置图片自动上传,实现快速发文章

诸葛小猿

Typora PicGo gitee 上传图片

DNSPod与开源应用专场

DNSPod与开源应用专场

设计一个 RISC-V CPU(一):软件工程师如何学习硬件设计-InfoQ