快速开放硬件开发 (ROHD) 框架
使用 ROHD 在 Dart 中描述硬件
ROHD(发音类似“road”)是一个用于在 Dart 编程语言中描述和验证硬件的框架。ROHD 允许您使用不受限制的软件构建和遍历模块对象之间的连接图。
ROHD 的特点包括:
- 利用现代 Dart 语言的全部能力进行硬件设计和验证
- 使 验证辅助工具 的开发和调试更简单。一个未来的软件包(如 ROHD 的 UVM)计划很快发布,以帮助构建测试平台。
- 在硬件设计中开发 抽象层,使其更灵活和强大
- 易于 IP 集成 和 接口;使用 IP 就像导入一样简单。减少集成过程中繁琐、冗余和易错的方面
- 简单快速的构建,无需复杂的构建系统和 EDA 供应商工具
- 可以使用优秀的 pub.dev 包管理器 及其提供的所有软件包
- 内置的基于事件的 快速模拟器,带 波形导出器 到 .vcd 文件格式
- 将模块转换为等效的、人类可读的、结构相似的 SystemVerilog,用于集成或下游工具消费
- 运行时动态 模块端口定义(数量、名称、宽度等)和内部模块逻辑,包括递归模块内容
- 简单、免费、开源的工具栈,没有库依赖、文件排序、精化/分析选项、+defines 等问题
- 优秀、简单、快速的 单元测试 框架
- 比替代方案 更简洁(更少的代码行)
- 实现 更高质量 的开发
- 用强大的 原生控制设计生成 取代了用于自动化的笨拙的 perl/python 脚本
- 更少的 bug 和代码行意味着 更短的开发周期
- 支持 与 verilog 模块进行协同仿真 和在生成的 SystemVerilog 代码中 实例化 verilog 模块
- 使用 现代 IDE,如 Visual Studio Code,具有出色的静态分析、快速自动完成、内置调试器、linting、git 集成、扩展等等
- 在同一种语言和环境中,使用 不同抽象级别的模型 进行仿真,从架构级到功能级,再到周期精确级,再到 RTL 级。
ROHD 不是 一种新语言,它 不是 一种硬件描述语言 (HDL),也 不是 高级综合 (HLS) 的一个版本。ROHD 可以归类为生成器框架。
您可以将此项目视为尝试 取代 SystemVerilog 和相关构建系统,成为行业首选的前端方法论。
ROHD 的目标之一是帮助围绕可重用硬件设计和验证组件建立一个开源社区。
为什么选择 Dart?
Dart 是 Google 开发的一种现代、相对较新的语言。它旨在用于客户端应用程序开发(例如应用程序和网站),但在一般任务中也具有出色的性能。它借鉴了 C++、Java、C#、JavaScript/TypeScript 和 Kotlin 等语言中最受喜爱的语法和功能。Dart 非常用户友好、使用有趣且 易于学习。配合现代 IDE 及其自动完成功能的出色、快速的静态分析,使您在工作中轻松学习。Dart 具有许多出色的现代语言特性,包括空安全。
由于它旨在处理异步请求(即向服务器发送请求而不冻结应用程序等待响应),Dart 内置了 async/await 和 Future,并使用 隔离区 进行并发编程。这些构造使代码能够并行执行而无需多线程。这些特性使得硬件建模变得非常容易。
Dart 可以编译为原生机器码,但也包含自己的高性能 VM 和 JIT 编译器。在开发过程中,您可以使用“热重载”功能在程序主动执行时更改代码。
Dart 有一个名为“pub”的优秀包管理器 (https://pub.dev)。可以托管私有 Dart Pub 服务器,用于不应广泛共享的包(例如绝密 IP)。
尝试新语言的合理性挑战
此 StackOverflow 回答 讨论了为什么值得尝试 Chisel(ROHD 的替代品),其中包含关于为何通常很难向从未用过新语言的人证明其合理性的宝贵见解
语言的 能力 众所周知难以客观评估。Paul Graham 在他的 “超越平均水平”一文 中将此描述为“Blub 悖论”。Graham 的论点是,精通功能较弱的语言的工程师无法评估功能更强大的语言的实用性。
如果您正在想“SystemVerilog 就很好,我不需要新东西”,那么值得阅读 StackOverflow 答案或 Paul Graham 的文章,或者两者都读。
更多关于 Dart 的信息
在这里从您的浏览器立即尝试 Dart:https://pad.dart.ac.cn/?null_safety=true
在这里查看一些 Dart 语言示例:https://dart.ac.cn/samples
有关 Dart 和教程的更多信息,请参阅 https://dart.ac.cn/ 和 https://dart.ac.cn/overview
开发建议
- 一个类似 UVM 的框架,用于为 ROHD 中建模的硬件构建测试平台,计划很快发布。
- Visual Studio Code (vscode) 是一个很棒的免费 IDE,对 Dart 有出色的支持。它在所有平台上都能很好地工作,包括原生 Windows 或适用于 Linux 的 Windows 子系统 (WSL),后者允许您在 Windows 中运行原生 Linux 内核(例如 Ubuntu)。您还可以使用 vscode 通过 Remote SSH 扩展在远程机器上进行开发。
入门
安装 Dart 后,如果您还没有项目,可以使用 dart create 创建一个:https://dart.ac.cn/tools/dart-tool
然后将 ROHD 添加为您的 pubspec.yaml 文件中的依赖项。在 ROHD 在包管理器上可用之前,您可以将此仓库克隆到本地磁盘并包含相对或绝对路径,如下所示:
dependencies:
rohd:
path: /path/to/rohd
或者 您可以直接依赖 github 仓库,如下所示:
dependencies:
rohd:
git: https://github.com/intel/rohd.git
现在您可以使用以下方式将其导入到您的项目中:
import 'package:rohd/rohd.dart';
完整的 API 文档可在 https://intel.github.io/rohd/rohd/rohd-library.html 获得。
如果您需要帮助,可以访问我们的 讨论 页面。这是一个友好的地方,您可以提出问题、分享想法或只是公开讨论!您也可以前往 StackOverflow.com(使用标签 rohd)提问或寻找答案。
请务必注意 pubspec.yaml 中指定的 ROHD 所需的最低 Dart 版本(至少 2.14.0)。如果您使用的是 Flutter 随附的 Dart 版本,它可能比这更旧。
硬件的包管理器
在 Dart 生态系统中,您可以使用包管理器来定义所有包依赖。包管理器允许您定义所有 直接 依赖项的版本受限子集,然后该工具将解决构建项目所需的所有(直接和间接)依赖项的连贯集合。无需手动找出工具版本、构建标志和选项、环境设置等,因为所有这些都保证能正常工作。其他包(无论是工具还是硬件 IP)的集成变得像 import 语句一样简单。与 SystemVerilog IP 集成相比!
在此处阅读有关包管理器的更多信息:https://en.wikipedia.org/wiki/Package_manager
在此处查看 Dart 的包管理器 pub.dev:https://pub.dev
ROHD 语法和示例
以下小节提供了一些 ROHD 中的实现和语法示例。
计数器模块的完整示例
为了快速了解 ROHD 的样子,下面是一个简单计数器模块在 ROHD 中的示例。
// Import the ROHD package
import 'package:rohd/rohd.dart';
// Define a class Counter that extends ROHD's abstract Module class
class Counter extends Module {
// For convenience, map interesting outputs to short variable names for consumers of this module
Logic get val => output('val');
// This counter supports any width, determined at run-time
final int width;
Counter(Logic en, Logic reset, Logic clk, {this.width=8, String name='counter'}) : super(name: name) {
// Register inputs and outputs of the module in the constructor.
// Module logic must consume registered inputs and output to registered outputs.
en = addInput('en', en);
reset = addInput('reset', reset);
clk = addInput('clk', clk);
var val = addOutput('val', width: width);
// A local signal named 'nextVal'
var nextVal = Logic(name: 'nextVal', width: width);
// Assignment statement of nextVal to be val+1 (<= is the assignment operator)
nextVal <= val + 1;
// `FF` is like SystemVerilog's always_ff, in this case trigger on the positive edge of clk
FF(clk, [
// `If` is a conditional if statement, like `if` in SystemVerilog always blocks
If(reset, then:[
// the '<' operator is a conditional assignment
val < 0
], orElse: [If(en, then: [
val < nextVal
])])
]);
}
}
一个更复杂的例子
有关任意功能的对数深度树的更高级示例,请参阅 doc/TreeExample.md。
逻辑信号
ROHD 中最基本的信号构建块称为 Logic。
// a one bit, unnamed signal
var x = Logic();
// an 8-bit bus named 'b'
var bus = Logic(name: 'b', width: 8)
信号值
您可以使用 value 访问信号的当前值。您无法将其作为可综合 ROHD 代码的一部分进行访问。ROHD 支持 X 和 Z 值以及传播。如果信号有效(不包含 X 或 Z),您也可以使用 valueInt 将其转换为整数(否则 ROHD 将抛出异常)。如果信号的位数超过 Dart int(通常为 64 位),您需要使用 valueBigInt 来获取 BigInt(否则 ROHD 将再次抛出异常)。
Logic 的位是 LogicValue 类型,具有预定义的常量枚举值 x、z、one 和 zero。Logic 的值由 LogicValues 表示,其行为类似于 LogicValue 的集合。LogicValue 和 LogicValues 都具有许多内置逻辑运算(例如 &、|、^、+、- 等)。
var x = Logic(width:2);
// a LogicValues
x.value
// an int
x.valueInt
// a BigInt
x.valueBigInt
您可以使用各种构造函数创建 LogicValues,包括 fromInt、fromBigInt、filled(类似于 SystemVerilog 中的 '0、'1、'x 等)和 from(接受任何 Iterable<LogicValue>)。
监听和等待更改
您可以通过一些内置事件触发 Logic 的更改。ROHD 使用 Dart 同步 streams 来处理事件。
ROHD Logic 中内置了三个测试台可用的流:changed、posedge 和 negedge。您可以使用 listen 在每次边缘转换时触发一些操作。请注意,这 不可 被 ROHD 综合,不应与可综合的 always(@) 类型的语句混淆。传递给监听器的事件参数类型为 LogicValueChanged,其中包含 previousValue 和 newValue 的信息。
Logic mySignal;
...
mySignal.posedge.listen((args) {
print("mySignal was ${args.previousValue} before, but there was a positive edge and the new value is ${args.newValue}");
});
您还可以使用辅助获取器 nextChanged、nextPosedge 和 nextNegedge,它们返回 Future<LogicValueChanged>。您可以将它们视为类似于 SystemVerilog 测试台代码中的 @(posedge mySignal);。同样,这些不应该包含在可综合的 ROHD 硬件中。
常量
常量通常可以由ROHD自动推断,但也可以使用Const(扩展自Logic)明确定义。
// a 16 bit constant with value 5
var x = Const(5, width:16);
有一个方便的函数用于将二进制转换为整数
// this is equvialent to and shorter than int.parse('010101', radix:2)
bin('010101')
赋值
要将一个信号的值赋给另一个信号,请使用 <= 运算符。这是一个硬件可综合的赋值,将两条线连接在一起。
var a = Logic(), b = Logic();
// assign a to always have the same value as b
a <= b;
简单的逻辑、数学和比较运算
信号上的逻辑操作与 SystemVerilog 中的非常相似。
a_bar <= ~a; // not
a_and_b <= a & b; // and
a_or_b <= a | b; // or
a_xor_b <= a ^ b; // xor
and_a <= a.and(); // unary and
or_a <= a.or(); // unary or
xor_a <= a.xor(); // unary xor
a_plus_b <= a + b; // addition
a_sub_b <= a - b; // subtraction
a_times_b <= a * b; // multiplication
a_div_b <= a / b; // division
a_eq_b <= a.eq(b) // equality NOTE: == is for Object equality of Logic's
a_lt_b <= a.lt(b) // less than NOTE: < is for conditional assignment
a_lte_b <= a.lte(b) // less than or equal NOTE: <= is for assignment
a_gt_b <= (a > b) // greater than NOTE: careful with order of operations, > needs parentheses in this case
a_gte_b <= (a >= b) // greater than or equal NOTE: careful with order of operations, >= needs parentheses in this case
移位操作
Dart 实现了三位移运算符 (>>>),其方式与 SystemVerilog 中实现的方式 相反。也就是说,在 Dart 中,>>> 表示 逻辑 右移(用 0 填充),而 >> 表示 算术 右移(保持符号)。ROHD 与 Dart 的实现保持一致,以避免在您编写的 Dart 代码(无论是 ROHD 还是普通 Dart)中引入混淆。
a << b // logical shift left
a >> b // arithmetic shift right
a >>> b // logical shift right
总线范围和混洗
多位总线可以通过单一位和范围访问,或者由多个其他信号组成。
var a = Logic(width:8),
b = Logic(width:3),
c = Const(7, width:5),
d = Logic(),
e = Logic(width: 9);
// assign b to the bottom 3 bits of a
b <= a.range(2,0);
// assign d to the top bit of a
d <= a[7];
// construct e by swizzling bits from b, c, and d
// here, the MSB is on the left, LSB is on the right
e <= swizzle([d, c, b]);
// alternatively, do a reverse swizzle (useful for lists where 0-index is actually the 0th element)
// here, the LSB is on the left, the MSB is on the right
e <= rswizzle([b, c, d]);
ROHD 目前不支持对总线子集进行赋值。也就是说,您 不能 执行类似 e[3] <= d 的操作。如果您需要从一组其他信号构建总线,请使用混洗。
模块
Modules 类似于 SystemVerilog 中的模块。它们具有输入、输出和连接它们的逻辑。在实现模块时,必须遵循一些规则。
Module中的所有逻辑都必须仅直接或间接消耗模块的输入(来自input或addInput方法)。Module外部的任何逻辑都必须仅通过模块的输出(来自output或addOutput方法)来消耗信号。- 如果
build()方法被覆盖,逻辑必须在调用super.build()之前定义,并且super.build()必须始终 在build()方法的末尾调用。
这些规则的原因与 ROHD 如何确定给定模块中存在哪些逻辑和 Module 以及 ROHD 如何构建连接有关。如果不遵循这些规则,生成的输出(包括波形和 SystemVerilog)可能会不可预测。
您应该努力在 Module 的构造函数中(直接或通过构造函数中的方法调用)构建逻辑。这样,任何代码都可以在创建 Module 后立即使用它。请注意 消耗模块的注册 input 并驱动注册的 output,而不是“原始”参数。
将逻辑放在 build 函数的覆盖中是合法的,但这会强制模块的用户在使用模块之前始终调用 build 才能进行简单仿真。如果将逻辑放在 build() 中,请确保将对 super.build() 的调用放在方法的 末尾。
请注意,build() 方法返回 Future<void>,而不仅仅是 void。这是因为在某些情况下,build() 方法允许消耗实际的挂钟时间,例如用于设置与其他模拟器的协同仿真。如果您期望您的构建消耗挂钟时间,请确保 Simulator 知道需要等待才能继续。
并非所有逻辑都必须直接放在扩展 Module 的类中。您可以将可综合逻辑放在其他函数和类中,只要逻辑最终连接到模块的输入或输出,如果您希望将其转换为 SystemVerilog。除非希望生成的波形和 SystemVerilog 具有模块层次结构,否则不必在模块中使用子模块,而不是普通的类或函数。
Module 基类有一个可选的字符串参数“name”,它是一个实例名。
Modules 具有以下基本结构
// class must extend Module to be a Module
class MyModule extends Module {
// constructor
MyModule(Logic in1, {String name='mymodule'}) : super(name: name) {
// add inputs in the constructor, passing in the Logic it is connected to
// it's a good idea to re-set the input parameters so you don't accidentally use the wrong one
in1 = addInput('in1', in1);
// add outputs in the constructor as well
// you can capture the output variable to a local variable for use
var out = addOutput('out');
// now you can define your logic
// this example is just a passthrough from 'in1' to 'out'
out <= in1;
}
}
ROHD 中除赋值语句外的所有门或功能都通过模块实现。
输入、输出、宽度和 getter
输入和输出的默认宽度为 1。您可以通过 addInput() 和 addOutput() 的 width 参数控制端口的宽度。您可以选择将其设置为静态数字,基于其他变量,甚至根据输入参数的宽度动态设置。这些函数还会返回输入/输出信号。
使用 Dart getter 来表示信号名称会很方便,这样访问模块的输入和输出就不需要每次都调用 input() 和 output()。这也可以让您的模块更容易被使用。
下面是一些模块中输入和输出的示例。
class MyModule extends Module {
MyModule(Logic a, Logic b, Logic c, {int xWidth=5}) {
// 'a' should always be width 4, throw an exception if its wrong
if(a.width != 4) throw Exception('Width of a must be 4!');
addInput('a', a, width: 4);
// allow 'b' to always be any width, based on what's passed in
addInput('b', b, width: b.width);
// default width is 1, so 'c' is 1 bit
// addInput returns the value of input('c'), if you want it
var c_input = addInput('c', c)
// set the width of 'x' based on the constructor argument
addOutput('x', width: xWidth);
// you can dynamically set the output width based on an input width, as well
// addOutput returns the value of output('y'), if you want it
var y_output = addOutput('y', width: b.width);
}
// A verbose getter of the value of input 'a'
Logic get a {
return input('a');
}
// Dart shorthand makes getters less verbose, but the functionality is the same as above
Logic get b => input('b');
Logic get x => output('x');
Logic get y => output('y');
// it is not necessary to have all signals accessible through getters, here we omit 'c'
}
时序逻辑
ROHD 有一个基本的 FlipFlop 模块,可以用作触发器。对于更复杂的时序逻辑,请使用“条件语句”部分中描述的 FF 块。
Dart 没有将某些信号视为“时钟”而非“非时钟”的概念。您可以将任何信号用作时序逻辑的时钟输入,并且可以拥有任意数量、任意频率的时钟。
条件语句
ROHD 支持各种 Conditional 类型语句,这些语句必须始终位于 _Always 块内,类似于 SystemVerilog。_Always 块有两种类型:FF 和 Combinational,它们分别映射到 SystemVerilog 的 always_ff 和 always_comb。Combinational 接受一个 Conditional 语句列表。不同类型的 Conditional 语句,如 If,可能由更多的 Conditional 语句组成。您可以创建任意深度的 Conditional 组合链。
条件语句像 SystemVerilog 中 always 块的内容一样,以命令式和按顺序执行。ROHD 中的 _Always 块在转换时与 SystemVerilog always 语句一对一映射。
If
下面是一个 ROHD 中 If 语句的示例
Combinational([
If(a, then: [
y < a,
z < b,
x < a & b,
q < d,
], orElse: [ If(b, then: [
y < b,
z < a,
q < 13,
], orElse: [
y < 0,
z < 1,
])])
]);
IfBlock
IfBlock 使 if / else if / else 链的语法更简洁。例如:
FF(clk, [
IfBlock([
// the first one must be Iff (yes, with 2 f's, to differentiate from If above)
Iff(a & ~b, [
c < 1,
d < 0
]),
ElseIf(b & ~a, [
c < 1,
d < 0
]),
// have as many ElseIf's here as you want
Else([
c < 0,
d < 1
])
])
]);
Case 和 CaseZ
ROHD 支持 Case 和 CaseZ 语句,包括优先级和唯一风格,其实现方式与 SystemVerilog 相同。例如:
Combinational([
Case(swizzle([b,a]), [
CaseItem(Const(LogicValues.fromString('01')), [
c < 1,
d < 0
]),
CaseItem(Const(LogicValues.fromString('10')), [
c < 1,
d < 0,
]),
], defaultItem: [
c < 0,
d < 1,
],
conditionalType: ConditionalType.Unique
),
CaseZ(swizzle([b,a]),[
CaseItem(Const(LogicValues.fromString('z1')), [
e < 1,
])
], defaultItem: [
e < 0,
],
conditionalType: ConditionalType.Priority
)
]);
请注意,ROHD 支持“z”语法,而不是“?”语法(这在 SystemVerilog 中是等效的)。
不支持 SystemVerilog 的 casex 等效项,因为它很容易生成不可综合的代码(参见:https://www.verilogpro.com/verilog-case-casez-casex/)。
接口
接口使定义模块的端口连接以可重用的方式变得更容易。下面显示了一个使用接口重新实现的计数器示例。
Interface 接受一个用于方向类型的泛型参数。这使您能够将信号分组,以便对于共享此接口的不同模块,添加它们作为输入/输出变得更容易。
Port 类扩展了 Logic,但其构造函数接受 width 作为位置参数,使接口端口定义更简洁。
当将 Interface 连接到 Module 时,您应该始终创建一个新的 Interface 实例,这样您就不会修改通过构造函数传入的实例。如果多个 Modules 正在使用相同的 Interface,修改相同的 Interface 将产生负面后果,并且还会违反 Module 输入和输出连接的规则。
connectIO 函数在底层直接调用 Module 上的 addInput 和 addOutput,并将这些 Module 端口连接到 Interfaces 上正确的端口。连接基于信号名称。您可以使用 connectIO 中的 uniquify 函数参数来唯一化输入和输出,以防您的模块连接了相同 Interface 的多个实例。您还可以使用 setPort 函数直接设置 Interface 上的单个端口,而不是通过标记端口集。
// Define a set of legal directions for this interface, and pass as parameter to Interface
enum CounterDirection {IN, OUT}
class CounterInterface extends Interface<CounterDirection> {
// include the getters in the interface so any user can access them
Logic get en => port('en');
Logic get reset => port('reset');
Logic get val => port('val');
final int width;
CounterInterface(this.width) {
// register ports to a specific direction
setPorts([
Port('en'), // Port extends Logic
Port('reset')
], [CounterDirection.IN]); // inputs to the counter
setPorts([
Port('val', width),
], [CounterDirection.OUT]); // outputs from the counter
}
}
class Counter extends Module {
late final CounterInterface intf;
Counter(CounterInterface intf) {
// define a new interface, and connect it to the interface passed in
this.intf = CounterInterface(intf.width)
..connectIO(this, intf,
// map inputs and outputs to appropriate directions
inputTags: {CounterDirection.IN},
outputTags: {CounterDirection.OUT}
);
_buildLogic();
}
void _buildLogic() {
var nextVal = Logic(name: 'nextVal', width: intf.width);
// access signals directly from the interface
nextVal <= intf.val + 1;
FF( SimpleClockGenerator(10).clk, [
If(intf.reset, then:[
intf.val < 0
], orElse: [If(intf.en, then: [
intf.val < nextVal
])])
]);
}
}
不可综合信号的存入
对于测试平台代码或其他不可综合的代码,您可以在任何 Logic 上使用 put 或 inject 来存入一个值。这两个函数具有相似的行为,但 inject 是在 Simulator.injectAction 内部调用 put 的简写,它允许存入的更改在同一个 Simulator 周期内传播。
var a = Logic(), b = Logic(width:4);
// you can put an int directly on a signal
a.put(0);
b.inject(0xf);
// you can also put a `LogicValue` onto a signal
a.put(LogicValue.x);
// you can also put a `LogicValues` onto a signal
b.put(LogicValues([
LogicValue.one,
LogicValue.zero,
LogicValue.x,
LogicValue.z,
].reversed // reverse since arrays start with 0
));
注意:直接使用 put() 更改值会传播该值,但不会触发触发器边缘检测或协同仿真交互。
具有自定义内联 SystemVerilog 表示的自定义模块行为
Dart 中许多基本的内置门都实现了自定义行为。下面以 NotGate 的实现为例。对于可以内联的函数和不能内联的函数(~ 可以内联)有不同的语法。在这种情况下,使用了 InlineSystemVerilog mixin,但如果它不能内联,则可以使用 CustomSystemVerilog。请注意,对于非时序模块,在首次创建模块时提供初始值计算是强制性的。
class NotGate extends Module with InlineSystemVerilog {
/// Name for a port of this module.
late final String _a, _out;
/// The input to this [NotGate].
Logic get a => input(_a);
/// The output of this [NotGate].
Logic get out => output(_out);
NotGate(Logic a, {String name = 'not'}) : super(name: name) {
_a = Module.unpreferredName(a.name);
_out = Module.unpreferredName('${a.name}_b');
addInput(_a, a, width: a.width);
addOutput(_out, width: a.width);
_setup();
}
/// Performs setup steps for custom functional behavior.
void _setup() {
_execute(); // for initial values
// Custom behavior should subscribe to 'glitch'
a.glitch.listen((args) {
_execute();
});
}
/// Executes the functional behavior of this gate.
void _execute() {
out.put(~a.value);
}
// specify how to convert this behavior to Verilog
@override
String inlineVerilog(Map<String,String> inputs) {
if(inputs.length != 1) throw Exception('Gate has exactly one input.');
var a = inputs[_a]!;
return '~$a';
}
}
ROHD 模拟器
ROHD 模拟器是一个静态类,可作为 Simulator 访问,它实现了一个简单的基于事件的模拟器。Dart 中的所有 Logic 都具有 glitch 事件,这些事件将值传播到下游连接的 Logic。通过这种方式,ROHD 将值传播到硬件的整个图表示(无需 Simulator 参与)。模拟器具有(无单位的)时间概念,并且任意 Dart 函数可以注册在模拟器中的任意时间发生。请求模拟器运行时,它会遍历所有注册的时间戳并按时间顺序执行函数。当这些函数将信号存入 Logic 时,它会将值传播到硬件。模拟器具有围绕时间戳滴答执行的许多事件,以便 FlipFlop 等可以知道时钟和信号何时无毛刺。
- 要在任意时间戳注册函数,请使用
Simulator.registerAction - 要设置最大仿真时间,请使用
Simulator.setMaxSimTime - 要立即在当前时间戳结束时终止仿真,请使用
Simulator.endSimulation - 要只运行下一个时间戳,请使用
Simulator.tick - 要运行模拟器直到完成,请使用
Simulator.run - 要重置模拟器,请使用
Simulator.reset- 请注意,这只重置了
Simulator,而没有重置任何Module或Logic值
- 请注意,这只重置了
- 要在 当前 时间步长中向模拟器添加操作,请使用
Simulator.injectAction。
外部模块的实例化
ROHD 可以实例化外部 SystemVerilog 模块。ExternalModule 构造函数需要顶层 SystemVerilog 模块名称。当 ROHD 为包含 ExternalModule 的模型生成 SystemVerilog 时,它将实例化指定的 topModuleName 实例。这对于集成相关活动非常有用。
一个用于 SystemVerilog 与 ROHD 协同仿真的即将发布的软件包,它将协同仿真功能添加到 ExternalModule 中,计划很快发布。
单元测试
Dart 在 pub.dev 上提供了一个很棒的单元测试包:https://pub.dev/packages/test
ROHD 包在 test/ 目录中提供了一组很好的示例,说明如何为 ROHD Module 编写单元测试。
请注意,在使用 ROHD 进行单元测试时,使用 Simulator.reset() 重置 Simulator 非常重要。
贡献
ROHD 正在积极开发中。如果您有兴趣贡献、有反馈或问题,或者发现了一个 bug,请参阅 CONTRIBUTING.md。
与替代方案的比较
有很多用于开发硬件的选项。本节简要讨论了 ROHD 的流行替代方案及其一些优点和缺点。
SystemVerilog
SystemVerilog 是最流行的 HDL(硬件描述语言)。它基于 Verilog,并在此基础上添加了额外的类软件结构。SystemVerilog 的一些主要缺点是:
- SystemVerilog 陈旧、冗长且受限,这使得代码更容易出错
- 在 SOC 层面集成 SystemVerilog IP 非常困难且耗时。
- 当使用 SystemVerilog 编写时,验证辅助工具难以开发、调试、共享和重用。
- 构建需要根据依赖关系正确 `include 包的顺序,.f 文件中编译器读取的文件的顺序,正确指定包和库依赖关系的顺序,以及正确的分析和精化选项。这是一个耗费许多工程师时间进行调试的领域。
- 构建和仿真依赖于昂贵的 EDA 供应商工具或不完整的开源替代方案。每个工具都有其自身的复杂性、依赖性、许可、开关等,并且不同的工具可能会以功能上不等效的方式综合或仿真相同的代码。
- 纯 SystemVerilog 中可配置和灵活模块的设计通常需要参数化、编译时定义和“generate”块,这些都可能具有挑战性、难以调试并限制方法。
- 人们经常依靠 perl 脚本来弥合差距,以迭代生成更复杂的硬件或将大量模块拼接在一起。
- 测试台归根结底是软件。SystemVerilog 可以说是一种糟糕的编程语言,因为它主要关注硬件描述,这使得开发测试台极具挑战性。SystemVerilog 中缺少基本的软件质量生活功能。
- 通过 DPI 调用(例如 C++ 或 SystemC)连接到其他语言来缓解问题,也有其自身的复杂性,包括额外的头文件、难以建模并行执行和边缘事件、传递回调等。
- UVM 用宏和样板代码来解决问题,但这并没有解决根本的限制。
ROHD 旨在实现 SystemVerilog 的所有最佳部分,同时完全消除上述每个问题。构建是自动化的,并且是 Dart 的一部分,包和文件可以按需导入,不需要任何供应商工具,可以使用所有可用的软件结构构建硬件,并且 Dart 是一种功能齐全的现代软件语言,具有现代特性。
您可以在此处阅读有关 SystemVerilog 的更多信息:https://en.wikipedia.org/wiki/SystemVerilog
Chisel
Chisel 是一种基于 Scala 构建的领域特定语言 (DSL),而 Scala 又基于 Java 虚拟机 (JVM) 构建。Chisel 的目标与 ROHD 的目标有些一致。Chisel 也可以转换为 SystemVerilog。
- Scala(以及 Chisel)的语法对大多数硬件工程师来说可能不那么熟悉,而且可以说比使用 Dart 的 ROHD 更冗长,主观上更丑陋。
- Scala 和 JVM 的调试可能不如 Dart 代码用户友好。
- Chisel 主要关注硬件 设计者 而不是 验证者。其语言的许多设计选择都围绕着使参数化和综合逻辑变得更容易。ROHD 在创建时考虑了验证者。
- Chisel 生成的逻辑更接近于网表,而不是 SystemVerilog 中类似实现的样子。这可能使调试或验证生成的代码变得困难。ROHD 生成结构相似的 SystemVerilog,看起来与您编写的方式接近。
在此处阅读有关 Chisel 的更多信息:https://www.chisel-lang.org/
MyHDL (Python)
人们曾多次尝试在 Python 之上创建 HDL,但 MyHDL 似乎是其中最成熟的选项之一。MyHDL 的许多目标与 ROHD 相似,但它选择在 Python 而不是 Dart 中开发。MyHDL 也可以转换为 SystemVerilog。
- MyHDL 使用“生成器”和装饰器来帮助建模硬件的并发行为,这可以说不如 ROHD 中基于 async/await 和事件的仿真用户友好和直观。
- 虽然 Python 是一种适用于正确用途的优秀编程语言,但 Dart 的某些语言特性使其更适合表示硬件。上面已经提到了 Dart 的隔离区和 async/await,这些在 Python 中不存在或以不同的方式存在。Dart 是带有空安全的静态类型语言,而 Python 是动态类型语言,这使得 Python 中的静态分析(包括智能感知、类型安全等)更具挑战性。Python 在没有精心设计的情况下,扩展到大型程序也可能具有挑战性。
- Python 本质上比 Dart 执行速度慢。
- MyHDL 通过 VPI 调用支持与 SystemVerilog 模拟器进行协同仿真。MyHDL C/VPI 实现在 ROHD 生态系统中的一个独立包中被 gratefully 重用于协同仿真。
在此处阅读有关 MyHDL 的更多信息:http://www.myhdl.org/
高级综合 (HLS)
高级综合(HLS)使用 C++ 和 SystemC 的子集来描述算法和功能,EDA 供应商工具可以将其编译成 SystemVerilog。HLS 的真正优势在于,它能够通过适当的分阶段和对目标工艺特性的了解,实现设计探索以优化更高层次的功能意图,从而优化面积、功耗和/或性能。
- HLS 是 RTL 级建模之上/之外的一步,这在某些情况下是一个优势,但在其他情况下可能不适合。
- HLS 使用 C++/SystemC,这可以说是一种不如 Dart“友好”的语言。
在此处阅读有关 HLS 工具(Cadence 的 Stratus 工具)的一个示例的更多信息:https://www.cadence.com/en_US/home/tools/digital-design-and-signoff/synthesis/stratus-high-level-synthesis.html
还有许多其他尝试使 HLS 变得更好,包括 XLS 和 Dhalia。关于如何合理地将 HLS 方法的一些优势融入 ROHD 的讨论正在进行中。
事务级 Verilog (TL-Verilog)
事务级 Verilog (TL-Verilog) 就像 SystemVerilog 之上的一个扩展,它使流水线设计更简单、更简洁。
- TL-Verilog 使 RTL 设计更容易,但在验证方面并没有增加太多
- 流水线抽象是 ROHD 可以实现的功能,但尚未在基本 ROHD 中实现。
在此处阅读有关 TL-Verilog 的更多信息:https://www.redwoodeda.com/tl-verilog
PyMTL
PyMTL 是在 Python 中创建 HDL 的另一次尝试。它由康奈尔大学开发,目前第三版 (PyMTL 3) 处于 Beta 阶段。PyMTL 旨在解决许多与 ROHD 相同的问题,但使用 Python。它支持转换为 SystemVerilog 和仿真。
- 关于 MyHDL 一节中描述的 Python 语言权衡也适用于 PyMTL。
在此处阅读有关 PyMTL 的更多信息:https://github.com/pymtl/pymtl3 或 https://pymtl3.readthedocs.io/en/latest/
cocotb
cocotb 是一个基于 Python 的测试台框架,用于测试 SystemVerilog 和 VHDL 设计。它不尝试表示硬件或创建模拟器,而是通过 VPI 调用等方式连接到其他硬件模拟器。
在此处阅读有关 cocotb 的更多信息:https://github.com/cocotb/cocotb 或 https://docs.cocotb.org/en/stable/
2021年8月6日
作者:Max Korbel <[email protected]>
版权所有 (C) 2021 Intel Corporation
SPDX-许可证标识符:BSD-3-Clause