软件工程的历史是不断发展抽象机制以应对日益增长的复杂性的历史。然而,硬件设计并没有那么与时俱进。例如,两种最常用的硬件描述语言 (HDL)——Verilog 和 VHDL12,9——可以追溯到 20 世纪 80 年代。在类型、封装和参数化等结构抽象方面,它们标准的更新落后于现代编程语言。它们的行为语义甚至更加落后。它们是根据在单处理器冯·诺依曼机器上运行的事件驱动模拟器来指定的(即使对于它们最近的后代 SystemVerilog 和 SystemC 10,11 也是如此)。
这些 HDL 都具有“可综合子集”,这些子集约束它们以努力缩小这种行为差距,但这种不匹配永远无法完全消除。随着硬件芯片容量根据摩尔定律呈指数增长,并且我们被要求设计具有惊人多样性和复杂性的整个 SoC(片上系统),这种压力开始显现。
另一个重要的问题是,到目前为止,验证(测试)一直使用仿真完成,但这越来越不切实际。3 在现代 SoC 中,硬件规模庞大且复杂,并且运行着繁重的软件负载,例如功能齐全的操作系统和应用程序。验证涉及将所有这些一起模拟,软件模拟运行数天或数周的情况并不少见。仿真速度通常被引用为 10 到 100 kHz,而需要的速度是 MHz 级别。
解决此问题的唯一可行方案是硬件人员所说的仿真,即在 FPGA(现场可编程门阵列)上仿真硬件设计。然而,基于 FPGA 的仿真不能留到设计的最终草案;它必须从设计过程的最开始,从早期模型开始就进行。这样做对 HDL 的行为语义提出了进一步的要求。
然而,简单的仿真是不够的。以前,对于仅在软件仿真中运行的模型和测试平台,例如 SystemC TLM(事务级建模)19 和 SystemVerilog VMM(验证方法手册)2,使用单独的高级语言可能是可以接受的。然而,今天,需要的是一种 HDL,它可以用于所有这些目的——从早期模型和测试平台到详细实现——这些不同的组件可以自由混合,并且所有这些组件都可以综合以进行 FPGA 仿真。因此,HDL 需要是通用的,既要适用于各种数字设计(例如,不仅仅是信号处理),又要完全可综合,无论是用于模型、测试平台还是实现。
这些要求意味着对 HDL 进行彻底的重新思考。本文介绍了 BSV (Bluespec SystemVerilog)18,其设计动机正是这种重新思考,同时尽可能地重用 SystemVerilog 的功能。BSV 的结构扩展(涉及更具表现力的类型、重载、封装和参数化)受到函数式编程语言 Haskell20 的启发。其行为语义受到术语重写系统13(或受保护原子动作)的启发,后者最适合描述复杂的并发硬件。这不仅仅是一个理论练习——BSV 及其工具已经在工业界使用了五年多(您今天的智能手机或平板电脑很可能包含用 BSV 设计的组件)。
在研究 BSV 之前,有必要考虑是否完全有必要使用一种新语言。尽管从功能角度来看,“一切都只是计算”,但硬件系统设计的特点是与软件设计截然不同的特性。
硬件系统通常具有大规模、细粒度、异构和反应式的并行性。(与一些作者不同,我没有区分并发和并行这两个术语)。这种并行性提高了速度,这通常是在硬件中实现某些东西的主要原因。在这种上下文中,大规模意味着并行活动的数量可能达到数千甚至数百万。细粒度意味着并行活动频繁交互(以时钟周期的时间尺度衡量),可能在非常小的共享资源(例如几个位的单个寄存器)上进行。异构意味着并行活动涉及不同的任务——例如,与 SIMD(单指令多数据)或 SPMD(单程序多数据)计算形成对比。最后,反应式意味着并行活动通常由异步事件触发,例如不可预测的数据到达、中断、仲裁解决和 I/O。
不幸的是,大多数软件语言根本不是并行的,而是完全顺序的。一些用于大规模并行性的扩展仅处理 SIMD 并行性。线程扩展可以处理异构性,但它们对于大规模、细粒度或反应式并行性来说非常困难。1,22,15
在硬件系统设计中,良好的架构(及其相关的成本模型)是中心设计目标和设计活动的成果,而软件主要为固定的、给定的输入架构(通常是冯·诺依曼架构,可能带有 SIMD 等扩展)而设计,如图 1 所示。因此,在硬件设计中,算法和架构是不可分割的,谈论“纯算法设计”的抽象,而没有一些赋予其具体成本指标的架构模型,是没有意义的。
由于它们都是图灵完备的,因此可以使用现有的编程语言对任何架构进行建模或仿真,但这存在两个根本问题。首先,准确地建模复杂的架构需要非凡的纪律(因此需要大量的时间和精力)。其次,建模架构通常意味着执行速度会降低几个数量级,这既是因为额外的解释层,也因为被建模架构的自然并行性基本上被完全丢弃了。3 DSL(领域特定语言)可以解决第一个问题,但不能解决第二个问题。对于大多数软件编程语言来说,“原生”架构模型是冯·诺依曼模型(一个顺序进程,可以恒定时间访问大型扁平内存),并且只有原生冯·诺依曼算法才能快速执行。
术语架构透明性表达了源程序直接反映所需架构的思想。抽象机制可以隐藏细节,但不应扭曲最终架构。对于程序员/设计师来说,此属性至关重要,因为抽象是好的,前提是它不损害可预测性和控制。对于编译器(综合)生成高效实现,此属性也至关重要。
如前所述,今天的 SoC 需要基于 FPGA 的仿真才能获得可接受的仿真速度,这需要 HDL 的通用适用性(模型、测试平台和全套 SoC 组件的实现)和通用可综合性。不幸的是,没有软件语言适合这样做。2
行业中的许多人提倡使用 C++ 和 SystemC 的组合——前者描述单个 IP(知识产权)模块的算法内容,后者描述系统级通信、层次结构以及将这些 IP 模块集成到 SoC 中。IP 模块内部的 C++ 然后可以进行所谓的 HLS(高级综合5),使用工具根据诸如延迟、吞吐量、面积、功耗和目标硅技术等声明性目标,从顺序 C++ 自动生成并行硬件。详细的评论需要另一篇文章,但本节和 Stephen A. Edward 关于该主题的文章7 提供了一些背景信息。
让我们首先关注 BSV 的计算模型(即其行为语义),因为这是现有软件语言用于硬件设计的最大局限性。然后我们展示一个示例来演示现代结构抽象机制的使用。
Verilog 和 VHDL 起源于仿真语言,它们建立在单处理器冯·诺依曼模型之上:顺序进程、基于堆栈的过程调用、传统的基于堆栈的可更新变量和协作式多任务处理。另一方面,BSV 源于直接硬件描述——状态和并行状态转换。计算机科学文献中有一个非常适合此目的的计算模型,它被称为术语重写系统13、受保护原子动作或重写规则。它用于许多复杂并发系统的形式规范语言中,包括 Dijkstra 的受保护命令6、Chandy 和 Misra 的 UNITY4、Lamport 的 TLA+14、Abrial 的 Event-B16 等等。以下 BSV 摘录说明了对四个整数进行升序排序的计算。
Reg #(int) x1 <- mkRegU;
Reg #(int) x2 <- mkRegU;
Reg #(int) x3 <- mkRegU;
Reg #(int) x4 <- mkRegU;
rule swap12 (x1 > x2);
x1 <= x2;
x2 <= x1;
endrule
rule swap23 (x2 > x3);
x2 <= x3;
x3 <= x2;
endrule
rule swap34 (x3 > x4);
x3 <= x4;
x4 <= x3;
endrule
前几行实例化了四个名为x1 ... x4的寄存器(存储元件),包含类型为int的值。mkRegU右侧是构造函数,其细节在这里并不重要。
规则(或受保护原子动作)swap12就像一个反应式进程(即,当其布尔条件x1>x2为真时,它可以“触发”)。规则的主体是一个动作,在语义上是一个瞬时事件。在这里,它由两个较小的动作组成:一个将x1中的值分配给x2,反之亦然。最终效果是交换两个寄存器中的值。规则swap23和swap34是类似的。
规则之间没有固有的顺序——规则可以在其条件为真时随时触发。因此,在示例中,规则swap12和swap34swap12swap12和swap23和x2swap23
可能会并行执行交换。但是,对于作用于共享状态的规则,例如
Vector #(4, Reg #(int)) <- replicateM (mkRegU);
for (int j = 1; j < 4; j = j + 1)
rule swap_neighbors (x[j-1] > x[j]);
x[j-1] <= x[j];
x[j] <= x[j-1];
endrule
swap124和
swap23
x2?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。:
always @ (posedge CLK)
if (...cond1...)
x <= ... value1 ...
else if (...cond2...)
x <= ... value2 ...
else if ...
...
else
x <= ... valueN ...
前面的片段看起来很重复——四个相似的寄存器和三个相似的规则——但我们首先以这种方式编写它是为了解释语义。更可能的写法是这样这会扩展(在硬件语言术语中称为“静态精化”)为与之前基本相同的片段。当然,这里的4可以抽象为一个参数。该片段代表了入门编程教材中的经典冒泡排序算法——具有相同的 O(N2) 最坏情况复杂度——除了这里的“冒泡”可以并行发生,模原子性(即,任何一对已启用的、在共享寄存器上不冲突的规则都可以并行触发)。当原子性要求对两个已启用的冲突规则进行排序时,选择的顺序可以保持未指定状态。这最初让硬件设计师感到震惊,在他们看来,不确定性等同于不可预测的(即,糟糕的)结果。然而,对于形式规范来说,这通常是受欢迎的,因为过早地坚持时间表被认为是不必要的过度规范。在 BSV 中,可以使用各种技术在必要时确定特定的调度选择。
规则对硬件的意义,以及它们为什么重要可以抽象为一个参数。该片段代表了入门编程教材中的经典冒泡排序算法——具有相同的 O(N2) 最坏情况复杂度——除了这里的“冒泡”可以并行发生,模原子性(即,任何一对已启用的、在共享寄存器上不冲突的规则都可以并行触发)。所有硬件设计都涉及指定更新状态的并行活动。Verilog 中的经典风格(所有这些评论也适用于 VHDL)是以状态为中心的。对于每个状态元素?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。x?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。,Verilog 中的典型编码风格是
更一般地,可以在每个条件分支中更新多个状态元素,条件可以写成?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。和case语句等。然而,在可综合代码中,每个状态元素只能在一个?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。alwayscase块中更新,并且在每个条件分支中最多更新一次。
always @ (posedge CLK) begin
if (cond2)
y <= y - 1;
else if (cond1) begin
y <= y + 1;
x <= x - 1;
end
if (cond0 && !cond1)
x <= x + 1;
end
这就是在(可综合)Verilog 中解决共享资源的并行更新问题的方式:它仅在一个“进程”(always块)中更新,并且对于每个时钟,恰好指定一个x的可能新值。它几乎是对生成的硬件的直接、显式规范:条件表示register输入端的多路复用器;条件指定如何选择多路复用器输入之一以时钟输入到寄存器中;并且条件还可以指定寄存器是否根本不更新。所有这些逻辑统称为控制逻辑。?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。不幸的是,这种以状态为中心的行为组织方式与行为的典型概念化方式——作为步骤或事务的集合——是正交的。行为的每个步骤可能涉及(可能是有条件地)更新多个状态元素。从以事务为中心的维度到以状态为中心的维度的这种“转置”是 Verilog 问题的核心。随着并行活动数量的增加以及活动竞争共享状态的复杂性增加,以状态为中心的视图无法很好地扩展。考虑图 2 中的问题,该问题说明了两个寄存器
if (cond0 && (!cond1 || cond2))
x <= x + 1;
x?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。和
y
在某些条件下由三个并行进程更新。假设进程 0 对于更新
x
rule proc0 (cond0);
x <= x + 1;
endrule
rule proc1 (cond1);
y <= y + 1;
x <= x - 1;
endrule
rule proc2 (cond2);
y <= y - 1;
endrule
(* descending_urgency = "proc2, proc1, proc0" *)
的优先级低于进程 1,进程 1 对于更新
y
这段 Verilog 代码可以用多种风格编写,但它们都是以状态为中心的。从问题陈述到以状态为中心的代码的“转置”交织并模糊了原始事务。此外,优先级被硬编码到代码结构中——进程 2 相对于进程 1 的优先级在
if ... else的顺序性中表达。进程 1 相对于进程 0 的优先级在!cond1的顺序性中表达。进程 1 相对于进程 0 的优先级在项中表达。实际上,一个聪明的设计师可能会认识到,当
cond2
为真时,进程 1 无法更新 | ||
x | ,从而为进程 0 这样做打开了机会。因此,设计师可能会将最后两行“改进”为 | 注意控制的传递性:进程 0 对 |
y | 的更新受到非相邻进程 2 的条件的影响。第三,一些进程条件在多个子句中重复,存在不正确重复的常见风险。 | (); |
x | 想象一下对规范的更改(这在现实世界中太频繁地发生了),要求在三个进程之间使用不同的优先级。或者想象一下添加另一个进程,或者可能另一个共享状态变量,或者两者都有,以及新的控制条件。整个代码都需要修改;很难进行局部的增量更改。 | (); |
即使这样一个小的例子也带有如此微妙的控制逻辑,这一事实应该使读者意识到,在每个时钟的基础上以状态为中心的方式推理并行更新可能会变得非常棘手,同时扩展到更多的并行活动和共享状态元素,并扩展更新条件的复杂性。 |
此示例的 BSV 代码如下所示,从而为进程 0 这样做打开了机会。因此,设计师可能会将最后两行“改进”为这些规则是进程描述的直接表达(即,它们是以事务为中心的,而不是以状态为中心的),最后一行表达了调度优先级。从此代码综合产生的 Verilog 代码与之前显示的 Verilog 代码基本相同——即,管理共享状态并行更新所需的复杂控制逻辑。添加更多进程、更多共享状态或更复杂的更新条件很容易。?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。当扩展到组织成模块的系统时,规则变得更加重要;在描述模块化机制后,我们将返回到此讨论。将规则组织成模块在面向对象编程中,复杂的系统被组织成对象。关键行为构造是进程或线程(在顺序语言中只有一个)。进程植根于某个模块(可能是“main”),但它可以通过方法调用跨越对象边界。由于方法本身可以调用方法,因此进程可以访问无限数量的对象。每个方法在语义上都是进程的一个片段。的更新受到非相邻进程 2 的条件的影响。第三,一些进程条件在多个子句中重复,存在不正确重复的常见风险。类似地,在 BSV 中,一个模块中的语法想象一下对规范的更改(这在现实世界中太频繁地发生了),要求在三个进程之间使用不同的优先级。或者想象一下添加另一个进程,或者可能另一个共享状态变量,或者两者都有,以及新的控制条件。整个代码都需要修改;很难进行局部的增量更改。rule将规则组织成模块可以调用另一个模块中的方法。方法又可以调用其他方法。每个方法在语义上都是规则的一个片段。因此,方法不仅仅是一个过程——它是一个受保护的表达式。语义规则由语法组件组成:一个
rule
module mkFIFOint (FIFOint);
Reg #(int) data <- mkRegU;
Reg #(Bool) empty <- mkReg (True);
method Action enq (int x) if (empty);
data <= x; empty <= False;
endmethod
method int first () if (! empty);
return data;
endmethod
method Action deq () if (! empty);
empty <= True;
endmethod
endmodule
构造和(传递地)它调用的所有方法;此语义规则是并行性和原子性的单位。一个实现包含整数的 FIFO 的小模块说明了这一点。首先是其接口声明interface FIFOint; method Actionenq(int x); method intfirstdeqendinterface像 C++ 虚类一样,它仅描述模块的外部可见方法。method enqueues an item的值。,从而为进程 0 这样做打开了机会。因此,设计师可能会将最后两行“改进”为方法将项目入队。Action类型表明它不返回值,只有效果。?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。methodendinterface方法first的值。的更新受到非相邻进程 2 的条件的影响。第三,一些进程条件在多个子句中重复,存在不正确重复的常见风险。和想象一下对规范的更改(这在现实世界中太频繁地发生了),要求在三个进程之间使用不同的优先级。或者想象一下添加另一个进程,或者可能另一个共享状态变量,或者两者都有,以及新的控制条件。整个代码都需要修改;很难进行局部的增量更改。不带参数,并返回队列头部的元素值。method方法的更新受到非相邻进程 2 的条件的影响。第三,一些进程条件在多个子句中重复,存在不正确重复的常见风险。deqfirst是一个纯想象一下对规范的更改(这在现实世界中太频繁地发生了),要求在三个进程之间使用不同的优先级。或者想象一下添加另一个进程,或者可能另一个共享状态变量,或者两者都有,以及新的控制条件。整个代码都需要修改;很难进行局部的增量更改。Actionendinterface方法method enqueues an item.
——它没有参数或结果;它只是具有丢弃队列中第一个元素的效果。first这是实现此接口的一种可能模块的代码int这是一个模块构造函数,可以实例化多次以创建实际模块(因此在模块名称
mk
interface FIFOin #(type t);
method Action enq (t x);
endinterface
interface FIFOout #(type t);
method t first ();
method Action deq ();
endinterface
mkFIFOint
mk
interface XBar #(type packet_t);
interface List #(FIFOin #(packet_t)) input_ports;
interface List #(FIFOout #(packet_t)) output_ports;
endinterface
,暗示单词 make)。FIFOint在标头中指定接口。接下来的两行实例化了两个寄存器data和,包含一个整数,最初未指定;和empty,包含一个布尔值,最初为True方法和enq受条件if (empty)保护——只有当保护条件为真时,从中调用的任何规则才会被启用。当调用时,它将其参数
module mkXBar #(Integer n, // (P1)
Function UInt #(32) destinationOf (t x), // (P2)
Module #(Merge2x1 #(t)) mkMerge2x1) // (P3)
(XBar #(t) );
List#(Put#(t)) iports; List#(Get#(t)) oports;
if (n == 1) begin
FIFO#(t) f <- mkFIFO;
iports = cons(toFIFOin(f),Nil);
oports = cons(toFIFOout(f),Nil);
end
else begin
XBar#(t) upper <- mkXBar(n/2, destinationOf, mkMerge2x1);
XBar#(t) lower <- mkXBar(n/2, destinationOf, mkMerge2x1);
iports = append (upper.input_ports, lower.input_ports);
let midports = append (upper.output_ports, lower.output_ports);
List#(Merge2x1) merges <- replicateM (n, mkMerge2x1);
oports = map (oport_of, merges);
for (Integer j = 0; j < n; j = j + 1)
rule route;
let x = midports [j].first();
let jDest = computeRoute (destinationOf (x), j, n);
if (jDest == j) merges [j] .iport0.enq (x);
else merges [jDest].iport1.enq (x);
midports [j].deq();
endrule
end
interface input_ports = iports;
interface output_ports = oports;
endmodule: mkXBar
x存储在数据寄存器中,并将empty设置为False方法first和deq受条件if (! empty)
保护——只有当此保护条件为真时,从中调用的任何规则才会被启用。方法first返回data的值,并且不更改 FIFO。方法deq和具有设置empty为True方法和enq的效果。这个例子很简单,但可以通过将和data.
扩展到寄存器向量并维护通常的头部和尾部指针来推广到 N 元素 FIFO。FIFO 也可以是通用的(多态的),在其内容类型中,而不是仅仅包含int值。许多模块具有类似 FIFO 的接口——即,接受或产生值的流控制端口。我们可以为此定义多态接口除了语法细节外,这些功能与在面向对象语言中看到的功能类似。关键的区别在于,虽然 OO 方法是顺序进程的片段,但 BSV 方法是规则的片段,并且具有保护条件。if (empty)更大的例子data和,包含一个整数,最初未指定;和图 3 说明了一个 Butterfly 交叉开关,它是一个具有 N 个输入端口和 N 个输出端口(此处,N=4)的硬件设备。数据包从输入端口进入,每个数据包都被路由到输出端口。请注意,交换机具有递归结构:所示的 4x4 交换机包含两个 2x2 交换机作为组件,而每个 2x2 交换机又包含两个 1x1 交换机(缓冲区)。反过来,可以使用两个 4x4 交换机来制造一个 8x8 交换机,依此类推。另请注意,有三个基本组件:FIFO(1 入 1 出)、合并框(2 入 1 出)和路由逻辑(1 入 2 出)。这是接口类型声明这个例子很简单,但可以通过将和它是通用的或多态的,在类型packet_t的数据包中,这些数据包流过交换机。它是嵌套的或分层的,因为它是在两个子接口input_ports受条件和output_ports方面定义的。这些接口反过来使用标准List数据结构来引用受条件FIFOindata和
扩展到寄存器向量并维护通常的头部和尾部指针来推广到 N 元素 FIFO。FIFO 也可以是通用的(多态的),在其内容类型中,而不是仅仅包含FIFOout接口的集合。这是一个实现此接口的模块?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。mkXBar它是通用的或多态的,在类型P1-P3 是参数n请求一个 n x n 交换机;?规则具有原子事务的语义,因此,在这种情况下,这两个规则只能按某种顺序(即,顺序地)触发。对于此示例,选择两种可能的排序中的哪一种并不重要;无论如何,寄存器最终都会被排序。destinationOf
是一个从数据包(类型为
t的更新受到非相邻进程 2 的条件的影响。第三,一些进程条件在多个子句中重复,存在不正确重复的常见风险。和想象一下对规范的更改(这在现实世界中太频繁地发生了),要求在三个进程之间使用不同的优先级。或者想象一下添加另一个进程,或者可能另一个共享状态变量,或者两者都有,以及新的控制条件。整个代码都需要修改;很难进行局部的增量更改。)到目的地(类型为
UInt #(32),从而为进程 0 这样做打开了机会。因此,设计师可能会将最后两行“改进”为,无符号 32 位整数)的函数,可用于进行路由决策;
mkMerge2x1,从而为进程 0 这样做打开了机会。因此,设计师可能会将最后两行“改进”为是 2 输入、1 输出合并模块的构造函数。在语义上,模块构造函数只是从参数到模块的函数。此外,它是一个高阶函数——也就是说,它的参数本身可以是函数和模块构造函数。这些功能在 Haskell20 和 SML (Standard ML)17 等高级编程语言中很常见,但在 HDL 中,尤其是在可综合 HDL 中,非常不寻常。
模块的大部分是模块的递归定义。FIFOoutthen
(n==1)
。使用受条件mkFIFO,从而为进程 0 这样做打开了机会。因此,设计师可能会将最后两行“改进”为实现 1x1 交换机。下一行使用函数if (empty)toFIFOinif (empty)和
toFIFOout
(简单;未显示)将
FIFOin
和
今天另一个非常重要的考虑因素是功耗。 在细粒度级别上,BSV 时钟可以被门控,门控条件有机地集成到规则条件中。 在 IP 模块的粗粒度级别上,BSV 具有电源管理功能,可以以规范的方式关闭或缩放整个模块或子系统的电源和时钟。
一个有趣的想法是,BSV 也可以被编译成异步逻辑(这在电路时序和功耗方面具有许多潜在优势),但是如何做到这一点,以及如何在这个机制中推断系统性能,仍然是开放的研究问题。
将规则编译成高效硬件的技术在十多年前首次开发。8 从那时起,它不断得到改进,并已应用于数百个设计。 在某些设计中,存在用于同类比较的现有 RTL 设计,总的来说,质量是可比的(以硅面积和性能衡量),通常在 5% 以内。
在少数情况下,BSV 生成的 RTL 比手工编写的 RTL 具有明显更好的质量(15-25%)。 这起初令人惊讶,因为在软件中,您通常会为更高的抽象付出性能代价。 然而,如图 1 所示,硬件设计中更高的抽象可以产生显著的算法优势(即,更高效的架构)。
BSV 的通用适用性和可综合性为许多人打开了大门,让他们可以将 FPGA 用作其标准仿真引擎,从设计的最早阶段开始,并采用“设计细化”方法。
Bluespec 公司通过“吃自己的狗粮”,使用 BSV 为 FPGA 板创建了高度参数化的通信、调试和 IP 库。 这些库构成了一种用于 FPGA 的操作系统,使用户可以使用高级工具与主机通信并调试模型或设计,而无需像往常一样痛苦地与原始 FPGA 板硬件作斗争。
以前用户可能用 C++ 编写测试生成器、分析器或模型,但现在他们可以用 BSV 编写它们,而不会损失抽象性,并且现在还可以综合它们并将它们在其设计旁边的 FPGA 上运行。
因此,考虑到现在可以以低于 2,000 美元的价格购买具有显着 FPGA 容量的板卡(例如 Xilinx ML605),并且 BSV 中具有舒适的高级通信、调试和库抽象,设计人员现在可以将 FPGA 视为他们武器库中的另一种计算武器,就像 GPGPU(图形处理单元上的通用计算)一样。21
从历史上看,软件和硬件设计人员之间几乎完全分离,这在很大程度上归因于他们用于设计各自系统的语言之间存在的巨大文化(语义)差距。 现代系统要求减少或消除这种专业化。 今天所有有趣的硬件系统都必须运行复杂的软件,并且许多复杂的软件计算必须转移到硬件以满足性能和功耗目标。21
BSV 旨在解决这个问题。 BSV 没有试图将冯·诺依曼机器(例如 C++ 和线程)的解决方案强行塞入硬件描述中,而是从软件中汲取了非常适合硬件描述的优秀思想。 它使用规则(来自项重写系统)描述行为,这些规则非常适合大规模、细粒度、异构、反应式并发(硬件!)。 它使用类型、重载、高阶函数、参数化,甚至来自 Haskell 的 monad 来描述结构和结构抽象。 这些想法已经在现场测试了五年多,并已用于成品中,其中一种可能就在您的口袋里。
1. Adve, S. 2010. 数据竞争是邪恶的,没有任何例外。 Communications of the 53(11): 84.
2. Bergeron, J., Cerny, E., Hunter, A., Nightingale, A. 2006. SystemVerilog 验证方法学手册。 Springer。
3. Burger, D., Emer, J., Hoe, J. C., Chiou, D., Sendag, R., Yi, J. J. 2010. 架构仿真的未来。 IEEE Micro (五月/六月): 8-18.
4. Chandy, K., Misra, J. 1988. 并行程序设计:基础。 Addison Wesley。
5. Coussy, P., 和 A. Morawiec, eds. 2008. 高级综合:从算法到数字电路。 Springer。
6. Dijkstra, E. W. 1975. 受保护的命令、不确定性和程序的正式推导。 Communications of the 18(8): 453-457.
7. Edwards, S. A. 2006. 从类 C 语言合成硬件的挑战。 IEEE Design and Test of Computers 23(5).
8. Hoe, J. C. 2000. 以操作为中心的硬件描述和综合。 博士论文,麻省理工学院。
9. IEEE. 2002. IEEE 标准 VHDL 语言参考手册。 IEEE Std 1076-1993。
10. IEEE. 2005. IEEE System Verilog 标准——统一硬件设计、规范和验证语言。 IEEE Std 1800-2005。
11. IEEE. 2005. IEEE 标准 SystemC 语言参考手册。 IEEE Std 1666-2005。
12. IEEE. 2005. IEEE 标准 Verilog 硬件描述语言。 IEEE Std 1364-2005。
13. Klop, J. 1992. 项重写系统,第 2 卷。牛津大学出版社,1-116。
14. Lamport, L. 2002. 系统规范:TLA+ 语言以及硬件和软件工程师的工具。 Addison-Wesley Professional (Pearson Education)。
15. Lee, E. A. 2006. 线程的问题。 IEEE Computer 39(5): 33-42.
16. Metayer, C., Abrial, J.-R., Voisin, L. 2005. Event-B 语言 http://rodin.cs.ncl.ac.uk/deliverables.htm。
17. Milner, R., Tofte, M., Harper, R., MacQueen, D. 1997. 标准 ML 的定义(修订版)。 马萨诸塞州剑桥市:麻省理工学院出版社。
18. Nikhil, R. S., Czeck, K. R. 2010. BSV 示例。 CreateSpace。
19. OSCI. 2009. OSCI TLM-2.0 语言参考手册; www.systemc.org。
20. Peyton Jones, S., ed. 2003. Haskell 98 语言和库:修订报告。 剑桥:剑桥大学出版社; www.haskell.org。
21. Singh. S. 2011. 无处理器计算。 Communications of the 54(8): 46-54.
22. Williams, R. 2010. Photoshop 可扩展性:保持简单。 Communications of the 53(10): 36.
喜欢它,讨厌它? 请告诉我们
Rishiyur Nikhil ([email protected]) 从事 Haskell 和函数式编程语言以及并行性编译器、数据流和多线程架构研究约 20 年。 自 2000 年以来,他一直将这些想法应用于硬件设计。
© 2011 1542-7730/11/0800 $10.00
最初发表于 Queue vol. 9, no. 8—
在 数字图书馆 中评论本文
Brendan Burns, Brian Grant, David Oppenheimer, Eric Brewer, John Wilkes - Borg、Omega 和 Kubernetes
尽管软件容器的广泛兴趣是相对较新的现象,但在 Google,我们管理大规模 Linux 容器已超过十年,并且在此期间构建了三个不同的容器管理系统。 每个系统都深受其前辈的影响,即使它们是出于不同的原因而开发的。 本文介绍了我们从开发和运营它们中吸取的教训。
John R. Mashey - 通往 64 位的漫长道路
莎士比亚的文字常常涵盖超出他最疯狂梦想的情况。 即使人们提前计划,辛勤工作和麻烦也伴随着重大的计算转型。 为了校准“今天的明日遗产”,我们应该研究“昨天的明日遗产”。 明天的大部分软件仍将受到数十年前的决策驱动。 过去的决策会产生意想不到的副作用,这些副作用会持续数十年,并且难以消除。