第四章  连接设计和Testbench · GitBook

您所在的位置:网站首页 atm模型有几个层次结构 第四章  连接设计和Testbench · GitBook

第四章  连接设计和Testbench · GitBook

2024-07-10 04:52| 来源: 网络整理| 查看: 265

第四章 连接Testbench和设计4.1 分离Testbench和设计4.1.1 testbench与被测设备之间的通信4.1.2 与端口的通信4.2 接口结构4.2.1 使用接口简化连接4.2.2 连接接口和端口4.2.3 使用Modports对接口中的信号进行分组4.2.4 在总线设计中使用Modports4.2.5 新建接口监控器4.2.6 接口的权衡4.2.7 更多信息和例子4.2.8 接口中的logic与wire4.3 刺激的时机4.3.1 用一个时钟块控制同步信号的定时4.3.2 Verilog中的定时问题4.3.3 试验台-设计竞赛条件4.3.4 程序块和定时区域4.3.5 指定设计和testbench之间的延迟4.4 接口驱动和采样4.4.1 接口同步4.4.2 接口信号样本4.4.3 接口信号驱动4.4.4 驱动接口信号通过一个时钟块4.4.5 接口的双向信号4.4.6 指定时钟块中的延迟4.5 程序块的考虑4.5.1 仿真结束4.5.2 为什么在程序中总是不允许块?4.5.3 时钟发生器4.6 把它们连在一起4.6.1 端口列表中的接口必须已连接4.7 顶级的范围4.8 程序模块的交互4.9 SystemVerilog断言4.9.1 直接断言4.9.2 自定义断言操作4.9.3 并发断言4.9.4 探索断言4.10 四端口ATM路由器4.10.1 带端口的ATM路由器4.10.2 带端口的ATM顶层模块4.10.3 使用接口简化连接4.10.4 ATM接口4.10.5 使用接口的ATM路由器模型4.10.6 带接口的ATM顶级模块4.10.7 带接口的ATMtestbench4.11 Ref端口方向4.12 结论4.13 练习第四章 连接Testbench和设计

验证一个设计需要几个步骤:产生激励、捕获响应、确定正确性和衡量进度。然而,首先你需要合适的testbench,并连接到设计,如图4.1所示。

图4.1 Testbench-DUT环境

testbench包裹着设计,发送激励并捕获设计的响应。试验平台围绕设计形成“真实世界”,模拟设计的整个运行环境。例如,处理器模型需要连接到各种总线和设备,这些总线和设备在testbench中被建模,即总线功能模型。对于网络设备,需要连接到基于标准协议建模的多个输入和输出数据流。对于视频芯片,需要连接到总线上对其发送指令,然后形成图像并写入存储器模型。总之,关键的概念是,testbench模拟所有不包含在被测设计中的东西。

由于Verilog的端口描述有时候长达数页,连接的时候容易出错,testbench需要一种更高级别的方法来与设计通信。需要一种健壮的方式来描述时序,以便始终在正确的时间驱动和采样同步信号,避免Verilog中常见的竞争状态。

4.1 分离Testbench和设计

在理想情况下,所有项目都有两个独立的组:一个组创建设计,一个组验证设计。在现实世界中,有限的预算可能要求你同时承担这两种角色。每个团队都有自己的一套专业技能,比如创建可合成的RTL代码,或者找出新的方法来发现设计中的错误。这两组人都阅读了原始的设计说明书,并做出了各自的解释。设计人员必须创建符合规范的代码,而验证工程师的工作是创建设计与描述不符的场景。

同样,您的testbench代码与设计代码在一个单独的块中。在经典的Verilog中,两种代码都放在单独的模块中。然而,使用一个模块来保存testbench通常会导致围绕驱动和采样的时序问题,因此SystemVerilog引入了程序块(program block),从逻辑上和时序上来分离testbench。更多细节请参见4.3节。

随着设计的复杂性增加,模块之间的联系也在增加。两个RTL模块可以共享几十个信号,这些信号必须以正确的顺序列出,以便它们能够正常通信。一个不匹配或错位的连接,将会导致设计将无法工作。可以通过使用按名称连接的方法来减少错误,但这将使输入负担增加一倍以上。如果这是一个微妙的错误,比如交换了只是偶尔切换的pin,那么可能在一段时间内不会注意到这个问题。更糟糕的是,当在两个块之间添加一个新信号时。不仅需要编辑当前模块以添加新端口,还需要编辑与其连接的上层模块。再一次地,在任何层次上只要有一个错误的连接,设计就会停止工作。或者更糟,系统只是间歇性地失效!

解决方案是使用接口,接口是代表一束连线的SystemVerilog构造。此外,您还可以指定时间、信号方向,甚至添加功能代码。接口像模块一样被实例化,但像信号一样连接到端口。

4.1.1 testbench与被测设备之间的通信

接下来的几节将展示连接到仲裁器的testbench,使用单独的信号和接口。图4.2是一个顶层的示意图,包括一个testbench,仲裁器,时钟发生器,以及连接它们的信号。这个DUT(测试中的设计)是一个很小 的设计,因此您可以将注意力集中在SystemVerilog概念上,而不会陷入设计中。在本章的最后,展示了一个ATM路由器。

图4.2testbench-没有使用接口的仲裁器

4.1.2 与端口的通信

下面的代码显示了将RTL模块连接到testbench所需的步骤。第一个是仲裁模型的端口描述,如例4.1所示。这使用了Verilog-2001风格的端口声明,其中类型和方向在文件头部。为了清晰起见,省略了一些代码。

正如第2.1.1节所讨论的,SystemVerilog扩展了经典的reg类型,这样您就可以像使用wire一样使用它来连接不同模块。为了表示到它的新功能,reg类型有了新的名称logic。唯一不能使用logic变量的地方是含有多个驱动的连线,在那里你必须使用线类型,比如wire。

例4.1使用端口的仲裁模型

例4.2中的testbench被保存在一个模块中,以将其与设计分离。

通常,它通过端口连接到设计。

例4.2使用端口的Testbench模块

顶层模块连接testbench和DUT,包括一个简单的时钟生成器。

例4.3 带有端口的顶层模块

在例4.3中,模块很简单,但是真正的设计需要数百个引脚,需要数页的信号和端口声明。所有这些连接都很容易出错。当信号在几个层次结构中移动时,必须反复声明并连接它。最糟糕的是,如果您只想添加一个新信号,则必须在多个文件中声明并连接它。SystemVerilog接口在这些情况下都有帮助。

4.2 接口结构

设计已经变得如此复杂,甚至块之间的通信可能需要分离成单独的实体。为了对此进行建模,SystemVerilog使用了接口结构,可以将其视为一个智能的连接束。它包含连接性、同步,以及(可选)两个或多个块之间通信的功能,以及(可选)错误检查。它们将设计块和/或testbench连接起来。

Sutherland(2006)讨论了设计级接口。这本书专注于连接设计块和testbench的接口。

4.2.1 使用接口简化连接

对仲裁器示例的第一个改进是将连接捆绑到一个接口中。图4.3是testbench和仲裁器使用接口进行通信的框图。注意接口是如何扩展到两个模块的,这两个模块分别代表testbench和DUT的驱动程序和接收器。时钟可以是接口的一部分,也可以是一个单独的端口。

图4.3一个接口跨越两个模块

最简单的接口是一个双向信号包,如例4.4所示。

使用logic数据类型,这样您就可以从过程语句驱动信号。

例4.4用于仲裁器的简单接口

例4.5是被测试的设备(DUT),仲裁器,它使用接口而不是端口。

例4.5使用简单接口的仲裁器

例4.6展示了testbench。通过使用实例名arbif.request创建分层引用来引用接口中的信号。接口信号应该总是使用非阻塞赋值来驱动。第4.4.3和4.4.4节对此进行了更详细的解释。

例4.6 Testbench使用一个简单的仲裁器接口

所有这些块都在顶部模块中实例化并连接,如例4.7所示。

例4.7顶层模块,带有一个简单的仲裁接口

使用接口的好处已经立刻体现出来了,即使在这个小设备上:连接变得更清晰,更不容易出错。如果您想在接口中放入一个新信号,您只需要将它添加到接口定义和实际使用它的模块中。您不必更改任何模块,比如不需要更改只传递接口的top模块。这种语言特性大大减少了布线错误的可能性。

这本书只展示了与一个时钟连接在顶层的接口。如果你的接口需要多个时钟,请将它们视为接口内的其他信号,并将接口连接到时钟生成器。如果你在高层次上工作,并将接口视为一个基于循环的构造,你的工作效率会更高。下一个级别是基于事务的,这超出了典型的RTL代码。

确保在模块和程序块之外声明接口。如果忘记了,就会有各种各样的麻烦。有些编译器可能不支持在模块中定义接口。即便允许,接口将是模块的本地的局部变量,因此,对设计的其余部分是不可见的。例4.8展示了在其他include语句之后紧接着包含接口定义的常见错误。

例4.8 错误示例 模块包括接口

4.2.2 连接接口和端口

如果您有一个带有端口的Verilog-2001遗留设计,不能通过更改端口来使用接口,那么您可以只将接口的信号连接到各个端口。例4.9将例4.1中的原始仲裁器连接到例4.4中的接口。

例4.9连接接口到使用端口的模块

4.2.3 使用Modports对接口中的信号进行分组

例4.5使用点对点连接方案,接口中没有信号方向。使用端口的原始模块具有编译器用来检查连接错误的信息。modport可以构造在一个接口让你分组信号和指定方向。例4.10中的MONITOR modport允许您将监控模块连接到接口。

例4.10接口与modports

例4.11显示了仲裁器模块和testbench, modport在他们的端口连接列表中。注意,你在接口名称arb_if后面加上modport名称,DUT或TEST。除了modport的名字,这些都是和前面的例子相同的。

例4.11使用modports接口的仲裁器模块

例4.12带有modports接口的Testbench

尽管代码没有太大的变化(除了接口变大了),但这个接口更准确地表示了真实的设计,尤其是信号方向。

有两种方法可以在你的设计中使用这些modport名称。您可以在连接接口信号的模块中指定它们。在本例中,除了模块名之外,top模块与例4.7相比没有任何更改。这本书推荐这种风格,因为modport是一个实现细节,不应该使顶层模块变得混乱。

另一种方法是在实例化模块时指定modport,如例4.13所示。

例4.13顶层模块与modports

使用这种风格,你可以灵活地实例化一个模块不止一次,每个实例连接到不同的modport,也就是接口信号的不同子集。例如,一个字节范围的RAM模型可以连接到32位总线上的四个插槽之一。在这种情况下,你需要在实例化模块时指定modport,而不是在模块本身中。

注意,modports定义在接口中,并在模块端口列表中指定,但从不在信号名称中指定。arb_if.TEST.grant 是非法的!

4.2.4 在总线设计中使用Modports

并不是每个信号都需要在每个modport传输。考虑一个带有接口的CPU -内存总线。中央处理器是总线主设备,驱动信号的子集,如request、command和address。存储器是一个从设备,接收那些信号并准备好驱动ready信号。主从设备都会驱动data信号。总线仲裁器只查看request和grant信号,而忽略所有其他信号。因此,你的接口将有主、从和仲裁三个modport,外加一个可选的监视器modport。

4.2.5 新建接口监控器

您可以使用monitor modport创建总线监视器。例4.14展示了仲裁器的一个简单监视器。对于一个真实的总线,你可以解码命令并打印状态:完成,失败,等等。

例4.14使用modports接口的仲裁监视器

4.2.6 接口的权衡

接口不能包含模块实例,只能包含其他接口的实例。与使用信号连接的传统端口相比,使用modports接口有一些权衡。

使用接口的优点如下。

接口是设计重用的理想选择。当两个块使用两个以上的信号与指定的协议通信时,考虑使用一个接口。如果一组信号反复重复,就像在网络交换机中一样,你应该额外使用虚拟接口,如第十章所述。

接口接受您在每个模块或程序中反复声明的混乱信号,并将其放在一个中心位置,减少了错误连接信号的可能性。

要添加一个新信号,您只需在接口中声明一次,而不是在上层模块中,这再次减少了错误。

Modports允许模块很容易地从一个接口获取信号子集。您可以指定信号方向进行额外的检查。

使用接口的缺点如下:

对于点对点连接,带有modports的接口几乎和使用带有信号列表的端口一样冗长。接口的优点是所有声明仍然在一个中心位置,减少了出错的机会。

除了信号名之外,还必须使用接口名,这可能会使模块更冗长,但对于调试来说也更容易理解。

如果你用一个不会被重用的唯一协议连接两个设计块,接口可能比仅仅将端口连接在一起要做更多的工作。

连接两个不同的接口很困难。一个新的接口(bus_if)可能包含现有接口(arb_if)的所有信号,加上新的信号(地址、数据等)。你可能需要拆分出独立的信号,并适当地驱动它们。

4.2.7 更多信息和例子

SystemVerilog LRM为您指定了许多其他使用接口的方法。Sutherland(2006)提供了更多使用接口进行设计的例子。

4.2.8 接口中的logic与wire

本书建议将接口中的信号声明为logic,而VMM有一个规则要求使用wire。区别在于易用性和可重用性。

如果您的testbench在带有过程分配的接口中驱动异步信号,那么信号必须是logic类型。wire只能用连续赋值语句驱动。对于一个时钟块中总是同步的信号,可以声明为logic或wire。例4.15显示了如何直接驱动logic信号,而wire需要额外的代码。

例4.15在接口中驱 动logic和wire

接口信号使用logic的另一个原因是,如果你无意中使用了多个结构驱动程序,编译器将给出一个错误。

VMM采用了一种更长期的方法。以您所创建的测试代码为例,这些测试代码在当前项目上运行良好,并在以后的新设计中使用。

如果你的接口与它所有的logic信号连接,现在一个信号有多个驱动会怎样?工程师们将不得不把这个logic换成wire,如果信号没有通过一个时钟块,就改变过程赋值语句。这样就有两个版本的接口,现有的测试必须修改后才能重用。 重写好的代码违背了 VMM 原则。

4.3 刺激的时机

testbench和设计之间的时间安排必须精心安排。在周期水平上,您需要在与时钟相关的适当时间驱动和接收同步信号。开车太晚或测试太早,你的testbench就会停止循环。即使在单个时隙内(例如,在100ns时间发生的所有事情),混合设计和testbench事件也可能导致竞争条件,例如当信号同时被读写时。你是读旧的值,还是刚写的值?在Verilog中,当测试模块驱动DUT时,非阻塞赋值会有帮助,但测试不能总是确保它采样了由设计驱动的最后一个值。SystemVerilog有几个结构可以帮助您控制通信的时间。

4.3.1 用一个时钟块控制同步信号的定时

一个接口应该包含一个时钟块来指定同步信号相对于时钟的时间。时钟块主要由testbench使用,但也允许您创建抽象的同步模型。时钟块中的信号是同步驱动或采样的,确保您的testbench在正确的时间与信号交互。合成工具不支持时钟块,所以您的RTL代码不能利用它们。计时块的主要好处是,您可以将所有详细的计时信息放在这里,而不会使您的testbench混乱。

一个接口可以包含多个时钟块,每个时钟域一个,因为每个时钟块中都有一个时钟表达式。典型的时钟表达式是@(posedge clk)为一个单边缘时钟和@(clk)为一个DDR(双数据速率)时钟。

您可以使用default语句在时钟块中指定一个时钟倾斜,但默认行为是在design exe- cutes之前对输入信号进行采样,并在当前时隙期间将输出驱动回设计中。下一节提供了关于设计和testbench之间的时间安排的更多细节。

一旦你定义了一个时钟块,你的testbench就可以用@arbif等待时钟表达式。而不是必须拼出准确的时钟和边缘。现在,如果您更改时钟块中的时钟或边缘,则不必更改您的testbench。

例4.16与例4.10相似,除了测试modport现在将请求和授予视为同步信号。时钟块cb声明信号在时钟的正边缘是活跃的。信号的方向是相对于他们使用的modport的。因此,在测试modport中,request是一个同步输出,grant是一个同步输入。在测试modport中,信号rst是异步的。

例4.16接口与一个时钟块

4.3.2 Verilog中的定时问题

您的testbench需要与设计分离,不仅是逻辑上,而且是暂时的。考虑一下硬件测试器如何与同步信号的芯片进行交互。在实际的硬件设计中,DUT的存储元素将来自测试器的输入锁存在主动时钟边缘。这些值通过存储元素的输出传播,然后逻辑云到下一个存储元素的输入。从第一个存储器的输入到下一个存储器的时间必须小于一个时钟周期。

因此,硬件测试人员需要在时钟边缘驱动芯片的输入,并在下一个边缘之前读取输出。

testbench必须模拟这个测试人员的行为。它应该在活动时钟边缘上或之后驱动,并且应该在协议计时规范允许的尽可能晚的时间采样,就在活动时钟边缘之前。

如果DUT和testbench仅仅由Verilog模块组成,那么这个结果几乎不可能实现。如果testbench在时钟边缘驱动DUT,可能会出现竞争条件。如果时钟在testbench刺激之前传播到一些DUT输入,但稍后传播到其他输入,那会怎样?从外部看,时钟边缘都到达相同的仿真时间,但是在设计中,一些输入得到的值是上一个周期驱动的,而其他输入得到的值是当前周期的。

解决这个问题的一种方法是给系统添加小的延迟,比如#0。这将迫使Verilog代码的线程在所有其他代码之后停止并重新调度。然而,大型设计总是有几个部分都需要最后执行。谁的#0胜出?它可能在不同的运行中变化,并且在不同的模拟器中是不可预测的。使用#0延迟的多个线程会导致不确定性行为。避免使用#0,因为它会使你的代码不稳定和不可移植。

下一个解决方案是使用更大的延迟,#1。RTL代码没有计时,除了时钟边缘,所以在时钟之后的一个时间单位,逻辑已经确定。然而,如果一个模块使用1ns的时间精度,而另一个模块使用10ps的分辨率,那会怎样?1是表示1ns, 10ps,还是别的什么?您希望在具有活动时钟边缘的时钟周期之后尽快驱动,而不是在这段时间内,并且在其他任何事情发生之前。更糟糕的是,你的DUT可能包含没有延迟的RTL代码和有延迟的gate代码。正如您应该避免使用#0一样,要避免使用#1延迟来修复时间问题。参见Cummings(2000)和他的其他论文获得额外的指导方针。

4.3.3 试验台-设计竞赛条件

例4.17显示了testbench和设计之间潜在的竞争条件。竞争条件发生在测试驱动启动信号,然后驱动其他端口。内存正在等待启动信号,并可以立即唤醒,而写入信号仍然有它的旧值,而addr和data有新值。根据LRM,这种行为是完全合法的。按照Cummings(2000)的建议,您可以使用非阻塞分配来稍微延迟所有这些信号,但是请记住,testbench和design都在使用这些分配。仍然有可能在testbench和设计之间获得竞争条件。

对设计输出进行抽样也有类似的问题。您希望在活动时钟边缘之前的最后一刻获取这些值。也许你知道下一个时钟边缘是100ns。你不能在时钟边缘的100ns采样,因为一些设计值可能已经改变了。你应该在时钟边缘之前在Tsetup取样。

例4.17试验台与设计的竞争条件

4.3.4 程序块和定时区域

问题的根源是设计和testbench事件在同一时间段的混合,即使在纯RTL中也可能发生同样的问题。正确使用非阻塞赋值等良好的cod指导方针可以减少这些争用条件,但编码不当的赋值有偷偷摸摸进入的习惯。如果有一种方法可以暂时分离这些事件,就像分离代码一样,会怎么样呢?在100ns下,您的testbench可以在时钟有机会更改和任何设计活动发生之前对设计输出进行采样。根据定义,这些值将是前一个时间段中最后一个可能的值。然后,在所有设计事件完成后,您的testbench将开始。

SystemVerilog如何知道将testbench事件与设计事件分开调度?在SystemVerilog中,testbench代码位于程序块中,它类似于模块,因为它可以包含代码和变量,并可以在其他模块中实例化。但是,一个程序不能有任何层次结构,比如模块、接口或其他程序的实例。

SystemVerilog中引入了一个新的时隙区域,如图所示。

4.4。在Verilog中,大多数事件都在活动区域中执行。还有几十个其他区域用于非阻塞赋值、PLI执行等,但它们可以是 图4.4 SystemVerilog时间步长的主要区域

从之前的时间段开始

活动(设计)

观察(断言)

回送

如果更多的事件

反应(testbench)

推迟(样本)

到下一时段

就本书而言,我们忽略了这一点。有关SystemVerilog事件区域的详细信息,请参见表4.1、LRM和Cummings(2006)。

在时间段内首先执行的是活动区域,设计事件在该区域运行。这些代码包括传统的RTL和门代码,以及时钟生成器。第二个区域是观察区域,在此对SystemVerilog断言进行评估。接下来是程序中testbench代码执行的反应区域。注意,时间并不严格地向前流动——在当前周期中,观察到的和反应性区域中的事件可以触发活动区域中的进一步设计事件。最后是延迟区,在设计活动完成后的只读期,在时隙结束时采样信号。

表4.1主要SystemVerilog调度区域

的名字 活动

活跃的 模拟设计代码在模块观察 SystemVerilog断言的响应式评估 推迟了程序中testbench代码的执行 testbench输入的采样设计信号

例4.18显示了用于仲裁的部分testbench代码。注意状态@arbif。cb等待时钟块的活动边缘,@(posedge clk),如例4.16所示。这个例子表明您的testbench代码是

在稍高的抽象级别上编写,使用逐周期计时,而不是担心单个时钟边缘。

第4.4节解释了更多关于驱动和接口信号采样的内容。

例4.18 Testbench使用接口与时钟块

您的测试应该包含在一个单独的程序中。您应该使用OOP从对象而不是模块构建一个动态的、分层的testbench。如果您使用来自其他人的代码或组合多个测试,则模拟可能有多个程序块。

正如第3.6.1节所讨论的,你应该总是将你的程序块声明为自动的,这样它的行为就更像你可能使用过的基于堆栈的语言的例程,比如C语言。

注意,并不是所有的供应商都平等地对待程序块。参见Rich(2009)的另一种观点。

4.3.5 指定设计和testbench之间的延迟

时钟块的默认计时是对偏置为#1的输入进行采样,并驱动延迟为#0的输出。1步延迟指定信号在任何设计活动之前,在前一个时隙的延迟区域内采样。因此,您可以在时钟更改之前获得输出值。通过时钟块,testbench的输出是同步的,因此它们直接流向设计。在反应区运行的程序块产生刺激

这将应用于DUT,然后在同一时间段的活动区域中对DUT进行计算。DUT评估它的逻辑并驱动它的输出,这些输出是通过时钟块进入testbench的输入。然后在延迟的区域采样,循环重复。如果你有设计背景,你可以通过想象时钟块在设计和测试之间插入一个同步器来记住这一点,如图4.5所示。通过正确使用程序和时钟块,testbench和DUT之间的竞争条件几乎可以消除。

图4.5时钟块同步DUT和试验台

4.4 接口驱动和采样

您的testbench需要驱动和采样来自设计的信号,主要通过与时钟块的接口。下一节将使用例4.16中的仲裁器接口和例4.9中的顶级模块。

异步信号如rst通过接口没有延迟。时钟块中的信号得到同步,如下节所示。

4.4.1 接口同步

您可以使用Verilog @和wait构造来同步testbench中的信号。例4.19展示了各种构造。

例4.19信号同步

4.4.2 接口信号样本

当你从一个时钟块读取一个信号时,你得到的值刚好在最后一个时钟边缘之前采样,即。来自推迟的地区。例4.20显示了一个从DUT读取同步授予信号的程序块。在100ns周期的中间,arb模块驱动授予1和2,然后在时钟边缘准确地授予3。这段代码仅用于说明,并不是真实的、可合成的RTL。

例4.20同步接口例和驱动模块

图4.6波形图表明,在程序中,arbif.cb。grant从时钟边缘之前获取值。当接口输入在时钟边缘发生变化(例如250ns)时,该值直到下一个周期(从350ns开始)才会传播到testbench。

clk

DUT arb。格兰特X

1 2 3.

测试arbif.cb.grant

X 1 2 3

50纳秒 150纳秒 250纳秒 350纳秒

图4.6同步接口采样

4.4.3 接口信号驱动

例4.21有一个简化版的仲裁器测试程序,它使用例4.16中定义的仲裁器接口。

例4.21 Testbench使用带有时钟块的接口

当使用带有时钟块的modports时,一个同步的接口信号,如request必须以接口名arbif和时钟块名cb作为前缀。在样本4。21中,arbif。cb。请求合法,但无效。

不是要求。这是接口和时钟块最常见的编码错误。

4.4.4 驱动接口信号通过一个时钟块

您应该始终使用非阻塞分配使用同步驱动器在一个时钟块中驱动接口信号。这是因为设计信号不会在你分配完任务后立即改变——记住你的testbench会执行

在无功区,而设计规范在有源区。如果您的testbench驱动的是arbif.cb。请求100ns,与arbif同时。cb(根据时钟块是@(posedge clk)),要求在100ns时改变设计。然而,如果您的测试bench试图驱动arbif.cb。请求时间101ns,在时钟边缘之间,更改直到下一个时钟边缘才传播。这样,您的驱动器总是同步的。在例4.20中,arbif。grant由模块驱动,可以使用阻塞赋值。

如果testbench在时钟的活动边缘驱动同步接口信号,如例4.22所示,该值立即传播到设计。这是因为一个时钟块的默认输出延迟是#0。如果testbench在活动边缘之后驱动输出,则该值直到时钟的下一个活动边缘才会在设计中看到。

例4.22接口信号驱动器

例4.23展示了在时钟周期的不同点上驱动同步接口信号会发生什么情况。它使用了例4.16中的接口以及例4.9中的顶层模块和时钟生成器。

例4.23驱动一个同步接口

注意,在图4.7中,第一个周期中间驱动的值3在第二个周期开始时被DUT看到。值2在第二个循环的中间被驱动。当testbench在第二个周期结束时驱动1时,被测试人员不会看到它。

clk 测试arb.cb。请求DUT arbif.request

图4.7驱动同步接口

异步驱动时钟块信号会导致丢失值。相反,通过在驱动器上使用周期延迟前缀在时钟边缘驱动,如例4.24所示。

例4.24接口信号驱动器

如果你想在驱动信号之前等待两个时钟周期,你可以使用" repeat (2) @arbif.cb; "或者使用周期延迟##2。后一个延迟只作为一个时钟块中的信号驱动器的前缀,因为它需要知道使用哪个时钟来进行延迟。

如果时钟在这个时隙中断言,根据时钟块,在一个分配中的##0周期延迟将立即驱动该值。如果时钟没有被断言,信号被驱动在时钟的下一个活跃边缘。##1的周期延迟总是等待时钟的下一个活动边缘,即使时钟在当前时隙中断言。

裸周期延迟声明##3;如果你的程序或模块有一个默认的时钟块,它就可以工作。这本书只建议把一个时钟块放在一个接口,而不是创建一个默认的时钟块。您应该始终明确所引用的时钟。

4.4.5 接口的双向信号

在Verilog-1995中,如果希望从过程代码驱动双向信号(如端口),则需要连续赋值来将reg连接到电线上。在SystemVerilog中,当为您添加连续赋值时,接口中的同步双向信号更容易使用,如例4.25所示。当您从一个程序写入网络时,SystemVerilog实际上写入一个驱动网络的临时变量。你的程序直接从线路上读取数据,看到从所有驱动程序解析出来的值。模块中的设计代码仍然使用经典的寄存器加连续赋值语句。

例4.25程序和接口中的双向信号

SystemVerilog LRM不清楚如何使用接口驱动异步双向信号。两种可能的解决方案是使用跨模块引用和连续赋值,或者使用第10章中所示的虚拟接口。

4.4.6 指定时钟块中的延迟

一个时钟块确保你的信号被驱动和采样在指定的时钟边缘。您可以使用默认语句或指定单个信号的延迟来调整这些时间。这在模拟具有真实延迟的网络列表时很有用。例4.26显示了一个带有default语句的时钟块,该语句对所有信号都具有倾斜度。在这个例子中,输入在时钟波塞dge之前采样15ns,输出在时钟波塞dge之后驱动10ns。

例4.26带有default语句的时钟块

例4.27显示了等效的时钟块,但是在单个信号上指定了延迟。

单个信号延迟的样本4.27时钟块

4.5 程序块的考虑 4.5.1 仿真结束

在Verilog中,模拟在有预定事件时继续,或者直到执行$finish。SystemVerilog增加了一种结束模拟的额外方法。一个程序块被当作包含一个测试来处理。如果只有一个程序,当你完成程序中每个初始块的最后一条语句时,模拟就结束了,因为这被认为是测试的结束。即使程序或模块中仍有线程在运行,模拟也会结束。因此,当测试完成时,你不必关闭所有的监视器和驱动程序。

如果有几个程序块,模拟在最后一个程序完成时结束。这样,当最后一个测试完成时,模拟就结束了。您可以通过执行$exit提前终止任何程序块。当然,您仍然可以显式调用

$finish结束模拟,但如果有多个程序,这可能会导致问题。

然而,模拟还没有结束。模块或程序可以有一个final块,其中包含要在模拟器终止之前运行的代码,如例4.28所示。这是执行清理工作的好地方,比如关闭文件,打印错误和警告的数量报告。您不能安排任何事件,也不能在最后一个块中有任何可能导致时间流逝的延迟。您不必担心释放任何已分配的内存,因为这将自动完成。

样本4.28最后一个块

4.5.2 为什么在程序中总是不允许块?

在SystemVerilog中,您可以在程序中放置初始块,但不总是放置块。如果您习惯了Verilog模块,这看起来可能有点奇怪,但与Verilog的许多并发执行的小块硬件相比,SystemVerilog程序更接近C语言的程序,具有一个(或多个)入口点,这有几个原因。在设计中,从模拟开始,一个always块可能会触发时钟的每个正边缘。相比之下,testbench有初始化、刺激和响应设计,然后结束仿真的步骤。连续运行的always块将无法工作。

当程序中最后一个初始块完成时,模拟将隐式结束,就像执行了$finish一样。如果您有一个always块,它将永远运行,因此您必须显式地调用$exit来通知程序完成。但不要绝望。如果你真的需要一个always块,你可以使用initial forever来完成同样的事情。

4.5.3 时钟发生器

现在您已经看到了程序块,您可能想知道时钟生成器是否应该在一个模块中。与testbench相比,时钟与设计的联系更为紧密,因此时钟生成器应该留在一个模块中。生成器应该在与DUT相同的级别上实例化,这样它就可以同时驱动DUT和testbench,当您细化设计、创建时钟树时,当时钟进入系统并通过块传播时,您必须小心地控制偏差。testbench就没有那么挑剔了。它只是想让时钟边缘知道何时驱动和采样信号。功能验证关心的是在正确的周期提供正确的值,而不是分数纳秒延迟和相对

时钟倾斜。

程序块不是放置时钟生成器的地方。例4.29试图将生成器放入程序块中,但只会导致竞争条件。clk和数据信号都从反应区传播到活动区的设计,并可能导致竞争条件取决于哪个先到达。

程序块中的坏时钟生成器

通过始终将时钟生成器放在模块中来避免竞争条件。如果你想要随机化生成器的属性,创建一个带有随机变量的类,如第6章所示,包括倾斜、频率和其他特征。您可以在generator模块或testbench中使用该类。

例4.30展示了模块中一个好的时钟生成器。它故意避免时刻0的优势,以防止竞争条件。在活动区域内,所有的时钟边缘都以阻塞分配来触发事件。如果你必须在时间0产生一个时钟边缘,使用一个非阻塞分配来设置初始值,这样所有的时钟敏感逻辑,如always块将在时钟改变值之前开始。

样品4.30良好的时钟发生器模块

最后,不要试图用功能验证来验证低级时间。本书中描述的testbench检查DUT的行为,而不是时间,使用静态时间分析工具更好。您的testbench应该足够灵活,以兼容使用反向注释时间运行的门级模拟。

4.6 把它们连在一起

现在,您有了模块中描述的设计、程序块中的testbench以及将它们连接在一起的接口。例4.31有一个顶级模块,它实例化并连接所有的片段。

例4.31带有隐式端口连接的Top模块

这和样本4。7几乎一样。它使用了一种快捷符号。*(隐式端口连接),自动连接模块实例端口到当前级别的信号,如果它们具有相同的名称和数据类型。

4.6.1 端口列表中的接口必须已连接

SystemVerilog编译器不允许编译使用端口列表中的接口的单个模块或程序。为什么不呢?毕竟,带有由单个信号组成端口的模块或程序可以在不实例化的情况下编译,如例4.32所示。

例4.32模块只有端口连接

编译器创建连接并将其连接到悬空信号。但是,在端口列表中具有接口的模块或程序必须连接到该接口的实例。

带有接口的模块例4.33

对于例4.33,编译器甚至不能构建一个简单的接口。如果你有modports或程序块使用一个接口的时钟块,编译器有一个更困难的时间。即使您只是想消除语法错误,您也必须完成连接。这可以按照例4.34所示完成。

样品4.34顶部模块连接DUT和接口

4.7 顶级的范围

有时你需要在你的模拟中创建一些程序或模块之外的东西,以便所有的块都能看到它们。在Verilog中,只有宏可以跨模块边界扩展,因此可以用于创建全局常量。SystemVerilog引入了编译单元,它是一组被编译在一起的源文件。任何模块、宏模块、接口、程序、包或原语边界之外的作用域称为编译单元作用域,也称为$unit。在这个范围中定义的任何东西,比如参数,都类似于全局变量,因为它可以被所有低级块看到。然而,它并不是真正的全局参数,因为在编译其他文件时不能看到该参数。

这导致了一些困惑。一些模拟器将所有SystemVerilog代码编译在一起,因此$unit是全局的。其他模拟器和合成工具一次编译单个模块或一组模块,因此$unit可能只是一个或几个文件的内容。因此,$unit是不可移植的。包允许您在程序或模块之外编写代码,同时消除了同时编译所有模块的需求。

本书将block之外的作用域称为“顶级作用域”。“你可以在这个空间里定义变量、参数、数据类型甚至例程。例4.35声明了一个顶级参数TIMEOUT,它可以在层次结构中的任何地方使用。这个例子还有一个保存错误消息的const字符串。两种方式都可以声明顶级常量。

例4.35仲裁器设计的顶级范围

实例名$root允许您明确地引用系统中的名称,从顶级范围开始。在这方面,$root类似于Unix文件系统中的“/”。对于像VCS这样一次性编译所有文件的工具,$root和$unit是等价的。名称$root还解决了一个旧的Verilog问题。

当您的代码引用另一个模块中的名称时,例如i1。var,编译器首先查找局部作用域,然后查找下一个更高的作用域,以此类推,直到它到达顶部。你可能想用i1。var,但是中间作用域中名为i1的实例可能会使搜索分心,给您提供了错误的变量。通过指定绝对路径,您可以使用$root来确定明确的跨模块引用。

例4.36显示了一个在模块top中实例化的程序,该模块在顶级范围中隐式实例化。该程序可以使用模块中clk信号的相对或绝对引用。您可以使用宏来保存分层路径,这样当路径发生更改时,您只需更改一段代码。LRM不允许在顶级范围中显式地实例化模块。

例4.36使用$root跨模块引用

4.8 程序模块的交互

程序块可以读取和写入模块中的所有信号,也可以调用模块中的例程,但是模块对程序没有可见性。这是因为您的testbench需要查看和控制设计,但是设计不应该依赖于testbench中的任何东西。

程序可以调用模块中的例程来执行各种操作。例程可以在内部信号上设置值,也称为“后门负载”。接下来,因为当前的SystemVerilog stan- dard没有定义如何从程序块强制发送信号,您需要在设计中编写一个任务来执行强制,然后从程序调用它。

最后,对于您的testbench来说,使用函数从DUT获取信息是一个很好的实践。读取信号值在大多数情况下都可以工作,但是如果设计代码发生变化,您的testbench可能会错误地解释这些值。模块中的函数可以封装两者之间的通信,使您的testbench更容易与设计保持同步。第10章展示了如何在接口中嵌入函数和SystemVerilog断言。

4.9 SystemVerilog断言

您可以在设计中创建有关信号的时态断言,以检查它们的行为和与SystemVerilog断言(SVA)的时态关系。模拟程序跟踪已触发的断言,因此您可以收集有关它们的功能覆盖数据。

4.9.1 直接断言

当语句被exe切割时,立即断言检查表达式是否为真。您的testbench过程代码可以检查设计信号和testbench变量的值,并在出现问题时采取行动。例如,如果您已经断言总线请求,那么您希望在两个周期后断言grant。您可以使用例4.37中所示的if语句。

例4.37用if语句检查信号

断言比if语句更简洁。但是,请注意,与上面的if语句相比,逻辑是相反的。你想要括号内的表达式为真;否则,打印一个错误,如例4.38所示。

例4.38简单的即时断言

如果正确地断言了授予信号,则继续测试。如果信号没有期望的值,模拟器将生成一个类似于例4.39的消息。

例4.39来自失败的立即断言的错误

这是在文件test的第7行。sv,断言top.t1。a1在55ns开始检查信号arbif.cb。格兰特,但立即失败了。标签a1应该是唯一的,以便您可以快速定位失败的断言。

您可能想使用完整的SystemVerilog断言语法来检查一段时间内的复杂序列,但是要小心,因为它们很难调试。断言是声明性代码,而exe-与周围的过程性代码非常不同。只需几行断言,就可以验证时间关系;枚,

alent过程代码将更加复杂和冗长,但当下一个人必须阅读您的代码时,他们更容易理解。

如果您是一名VHDL程序员,此时您可能会忍不住开始在代码中直接使用断言。抵制诱惑!您的代码将正确工作数周或数月,直到有人决定改进模拟性能

曼斯通过禁用断言。模拟器将不再执行断言中的表达式。如果表达式有副作用,如递增值或调用函数,则该表达式将不再发生。

4.9.2 自定义断言操作

immediate断言有可选的then-和else-子句。如果您想增强默认消息,您可以添加自己的消息,如例4.40所示。

例4.40在即时断言中创建自定义错误消息

如果grant没有预期的值,您将看到类似于例4.41的错误消息。

例4.41来自失败的立即断言的错误

SystemVerilog有四个函数可以打印消息:$info、$warning、$error和$fatal。这些操作只允许在断言中进行,而不允许在过程代码中进行,不过SystemVerilog的未来版本可能允许这样做。

可以使用then子句记录断言成功完成时的情况,如例4.42所示。

例4.42创建自定义错误消息

4.9.3 并发断言

另一种断言类型是并发断言,您可以将其视为一个持续运行的小模型,为整个模拟检查信号值。它们的实例化与其他设计块相似,在整个模拟过程中都是活跃的。您需要在断言中指定采样时钟。例4.43有一个小的断言来检查仲裁请求信号除了复位期间没有X或Z值。该代码被放置在过程块(如initial和always)之外。样本4.43仅供说明。查看下面列出的其中一本书以获取更多信息。

例4.43检查X/Z的并发断言

4.9.4 探索断言

断言还有许多其他用途。例如,您可以将断言放在接口中。现在,您的接口不仅传输信号值,而且检查协议。

本节对SystemVerilog断言进行了简要介绍。更多信息,请参见Vijayaraghhavan和Ramanathan(2005)以及Haque等人(2007)。

4.10 四端口ATM路由器

仲裁器的例子是对接口的一个很好的介绍,但是真正的设计有不止一个输入和输出。本节讨论如图4.8所示的四端口ATM(异步传输模式)路由器。

图4.8没有接口的Testbench - ATM路由器图

4.10.1 带端口的ATM路由器

下面的代码片段展示了在将RTL块连接到testbench上时,您必须忍受的复杂的连接。第一个是ATM路由器模型的头。这使用了Verilog-1995风格的端口声明,其中类型和方向与头文件分开。

例4.44中路由器的实际代码被近一页的端口声明所挤占。

带端口的ATM路由器模型报头

那么,在例4.44末尾的“…”中包含了什么样的可合成代码呢?有关在模块和其他SystemVerilog设计构造中使用接口的更多信息和例,请参见Sutherland(2006)。

4.10.2 带端口的ATM顶层模块

例4.45包含顶级模块。

例4.45没有接口的顶级模块

例4.46显示了testbench模块的顶部。再次注意,端口和线占据了模块的大部分。

例4.46 Verilog-1995 testbench使用端口

你只看到了三页的代码,所有的都是连接——没有testbench,没有设计!接口提供了一种更好的方式来组织所有这些信息,并消除容易出错的重复部分。

4.10.3 使用接口简化连接

图4.9显示了连接到testbench的ATM路由器,将信号分组到接口中。

Testbench

Rx Tx

4 x4 ATM

路由器

图4.9带有接口的testbench路由器图

4.10.4 ATM接口

例4.47和4.48显示了与modports和时钟块的Rx和Tx接口。

例4.47 Rx接口与modports和时钟块

例4.48 Tx接口与modports和时钟块

4.10.5 使用接口的ATM路由器模型

例4.49包含ATM路由器模型和testbench,它们需要在端口连接列表中指定modport。注意,你把modport名称放在接口名称Rx_if之后。

带modports接口的ATM路由器模型例4.49

4.10.6 带接口的ATM顶级模块

例4.50中显示的顶层模块已经大幅收缩,出错的机会也减少了。

例4.50带有接口的顶级模块

4.10.7 带接口的ATMtestbench

例4.51展示了testbench捕获从路由器的TX端口传入的单元的部分。注意,接口名称是硬编码的,因此您必须为44 ATM路由器复制相同的代码四次。例如,只显示任务receive_cell0,最终代码还将包含receive_ cell1、receive_cell2和receive_cell30。第10章展示了如何使用虚拟接口来简化代码。

例4.51 Testbench使用一个带有时钟块的接口

4.11 Ref端口方向

SystemVerilog引入了一个用于连接模块的新端口方向:ref。您应该熟悉输入、输出和inout方向。最后一个用于双向连接建模。如果驱动信号有多个inout端口,SystemVerilog将通过组合所有驱动的值来计算信号的值,考虑到驱动的强度和Z值。

裁判是另一种野兽。它本质上是一种使两个名称都引用同一个变量的方法。只有一个存储位置,但有多个别名。Ref端口只能连接变量,不能连接信号。关于例程参数的ref方向的信息,请参阅3.4.3节。

在例4.52中,incr模块有两个ref端口,c和d。这两个变量与顶部模块中的c和d变量共享存储。当top改变c的值时,incr会立即看到它。然后incr增加c,结果返回到顶层模块中。如果端口c被声明为inout,您将不得不构建三态驱动程序,比如连续赋值语句,并确保您正确地驱动了enable信号和Z值。不要认为ref端口是inout端口的方便替代,因为合成只支持inout端口。

例4.52 Ref端口

4.12 结论

在本章中,您已经学习了如何使用SystemVerilog的接口来组织设计块和testbench之间的通信。有了这种设计结构,您可以用一个接口替换数十个信号连接,使您的代码更容易维护和改进,并减少布线错误的数量。

SystemVerilog还引入了程序块来容纳您的testbench,并减少被测试设备和testbench之间的竞争条件。有了接口中的一个时钟块,您的testbench将驱动和样本设计信号正确地相对于时钟。

4.13 练习 设计了ARM高级高性能总线(AHB)的接口和testbench。提供给您一个总线主机作为可以启动AHB事务的验证IP。您正在测试一个从属设计。testbench实例化接口、从服务器和主服务器。如果事务类型在HCLK的负边缘不是IDLE或NONSEQ,你的接口将显示一个错误。AHB信号如表4.2所示。

表4.2 AHB信号描述

信号

宽度

方向

描述

HCLK1

输出

时钟

HADDR

21

输出

地址

HWRITE1

输出

写标志:1=写,0=读

HTRANS2

输出

交易类型:

2b00 =闲置,2b10 = NONSEQ

HWDATA8

输出

写入数据

HRDATA8

输入

读取数据

对于下面的接口,添加以下代码。

对时钟负边缘敏感的一个时钟块,所有的I/O都与时钟同步。

一个用于testbench的modport称为master,一个用于DUT的modport称为slave

使用主modport的I/O列表中的时钟块。

对于练习2中的时钟块,请填写下面计时图中的data_in和data_out信号。

测试/ reg_bus / clk

测试/ reg_bus / data_in

测试/ reg_bus / data_out

16 h0000

16 h0001

reg_bus / cb / data_in reg_bus / cb / data_out

16 h0000

16 h0001

16 h0002

16 h0003

修改练习2中的时钟块为:

输出写和地址的输出偏移25ns

输入偏差15ns

限制data_in仅在时钟的正边缘更改

对于练习4中的时钟块,填写下面的计时图,假设时钟周期为100ns。

reg_bus / clk reg_bus /写

reg_bus/data_in reg_bus/data_out reg_bus/cb/data_in reg_bus/cb/data_out reg_bus/cb/write . txt



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3