引言
本文简单介绍 SystemVerilog 的接口。
前文链接:
我的 System Verilog 学习记录(1)
我的 System Verilog 学习记录(2)
我的 System Verilog 学习记录(3)
我的 System Verilog 学习记录(4)
我的 System Verilog 学习记录(5)
我的 System Verilog 学习记录(6)
我的 System Verilog 学习记录(7)
接口
接口是啥 ?
接口是一种将信号封装成一个块的方式,将所有相关的信号组合在一起形成一个接口块,以便同一接口可以被其他项目重用,也使得与DUT和其他验证组件的连接变得更容易。
示例
APB总线协议信号放在给定的接口中。请注意,信号在 interface 和 endinterface 中声明。
为啥信号声明为 logic ?
logic 是一种新的数据类型,它允许通过分配语句和过程块中驱动这种类型的信号。请记住,在verilog中,您只能在过程块中驱动reg,而只能在分配语句中驱动wire。但这只是一个原因。
连接到DUT的信号应该支持4个状态,以便可以捕获X/Z值。如果这些信号是位的,那么X/Z就会显示为0,你就会错过DUT的X/Z值。得不到正确的验证结果。
如何定义端口方向 ?
接口信号可以在各种验证组件以及DUT中使用,因此 modport 用于定义信号方向。不同的modport定义可以传递给不同的组件,从而允许我们为每个组件定义不同的输入输出方向。
DUT和接口如何连接 ?
接口对象应该在实例化 DUT 的顶级测试平台模块中创建,并传递给DUT。这对于确保将正确的modport 分配给DUT至关重要。
有啥优点 ?
接口可以包含任务、函数、参数、变量、功能覆盖率和断言。这使我们能够通过此块中的接口监视和记录事务。由于信息被封装在接口中,因此无论它有多少端口,连接到设计都变得更容易。
接口参数化怎么做 ?
和 module 定义的一样。
时钟块是啥 ?
在时钟块内指定的信号将相对于该时钟进行采样/驱动。一个接口中可以有多个时钟块。请注意,这是针对与测试平台相关的信号。您希望控制TB驱动和采样DUT信号的时间。解决了部分竞争条件,但不是全部。您还可以对时钟偏斜值进行参数化。
在上面的例子中,我们已经指定,默认情况下,输入应在 clk 的上升沿之前3ns采样,输出应在clk的上升沿之后驱动2ns。
时钟块如何使用 ?
如上,在将 1 分配给Enable值之前,不必等待时钟的上升边沿。这样,您就可以确保Enable将在下一个时钟上升沿之后驱动2 ns。
简介
SystemVerilog接口允许我们将许多信号组合在一起,并将它们表示为单个端口。所有这些信号都可以在一个地方声明和维护,并且很容易维护。接口中的信号由接口实例句柄访问。
语法
接口块是在 interface 和 endinterface 关键字中定义和描述的。它可以像模块一样实例化,可以带端口或不带端口。
接口还可以具有函数、任务、变量和参数,使其更像一个类模板。它还具有通过 modport 构造为不同模块端口定义方向信息的策略的能力,以及带有时钟块的测试台同步功能。它还可以具有断言、覆盖记录和其他协议检查元素。另外,它还可以包含 initial 和 always 以及连续的赋值语句(assign)。
接口内部不能例化模块,但是模块内部可以例化接口。
SystemVerilog现在作为一种HDL语言很流行,让我们看看在Verilog和SystemVerilog中使用相同设计的接口的两种情况。为了在这个介绍性示例中保持简单,我们只创建一个简单的接口。
Verilog 设计的接口
让我们看看如何在测试平台中使用接口并通过端口列表连接到标准的Verilog设计。下面显示的代码是在Verilog中设计的递增-递减计数器。该模块接受一个参数来决定计数器的宽度。它还接受只有当Load_en为1时才加载到计数器中的输入加载值LOAD。
当输入down为1时,计数器开始向下计数,否则向上计数。翻转输出指示计数器何时从最大值转换到0或从0转换到最大值。
下面使用一个可参数化值作为计数器信号的宽度声明了一个名为cntif的接口。此任务还有一个任务init() 来赋值 。
仿真结果:
SV设计的接口
现在让我们看看如何在测试平台中使用接口并将其连接到SystemVerilog设计模块。SystemVerilog允许模块接受接口作为端口列表,而不是单个信号。在下面显示的设计示例中,我们用用于定义设计功能的接口句柄替换了计数器 ud 的端口列表。
向设计实例传递一个名为 cnt_if 的接口句柄,该接口句柄用于驱动从测试台到设计的输入。如果需要,可以使用相同的接口句柄来监视设计的输出。
这和 Verilog 有啥区别 ?
Verilog通过其模块端口连接不同的模块。对于大型设计,这种连接方法可能会变得更加耗时和重复。其中一些端口可能包括与AXI/AHB等总线协议相关的信号、时钟和复位引脚、进出RAM/存储器和其他外围设备的信号。
使用 Verilog 端口
让我们考虑这样一个场景,在上面所示的设计中有12个从模块。如果在 d_slave 模块端口上进行了更改,那么该更改也必须反映在 d_top 的所有12个从实例连接中。
缺点
使用Verilog端口方法进行连接的一些缺点是:
- 繁杂的跟踪、调试和维护
- 太容易破坏设计功能
- 设计要求的变更可能需要修改多个模块
- 在多个模块、通信协议和其他地方需要的复制
使用SV的接口
请注意,模块d_top只是使用接口与从实例连接,而不是如前所示重复声明与从块的每个信号的连接。
现在,如果从接口中的一个信号发生变化,它将自动应用于所有实例。在SystemVerilog中,模块端口列表还可以具有接口类型的端口,而不是通常的 input、output 和 inout 。
接口数组
在下面的示例中,创建了一个名为mylninterface空端口列表的接口,并在顶级测试平台模块中实例化了该接口。省略空端口列表的圆括号也很好,而用分号截断语句。
可以实例化一个名为 if0 的接口,并且应该通过引用该句柄来访问该接口内的信号。然后,可以使用该句柄来驱动和采样进入DUT的信号。
我们还可以有一个接口数组,这里这个数组的名称是wb_if,它有4个接口实例。
当接口被引用为端口时,假设其中的变量和网络分别具有 ref 和 inout 访问权限。如果在设计中使用相同的标识符作为接口实例名称和端口名称,则也可以使用隐式端口连接。
接口捆绑(bundle)
上节涵盖了对接口的需要,如何实例化和连接接口与设计。
有两种方式可以书写设计:
- 通过使用一个现有的接口名称来专门地只使用该接口
- 通过使用任何接口都可以传递到的通用接口句柄
显然,当接口定义更新为具有不同名称的较新版本时,通用方法效果最好,并且需要支持使用它的较旧设计。
示例
在本例中,设计引用了访问其信号的实际接口名称。下面的示例显示了设计模块 myDesign 和您的设计都在端口列表中声明了一个名为 if0 的端口来访问信号。
使用通用捆绑的示例
在本例中,设计使用 interface 关键字作为实际接口类型的占位符。下面的示例显示设计模块myDesign和您的设计都使用占位符句柄来引用信号。然后在设计模块实例化期间传递实际接口。此通用接口引用只能使用端口声明语法的ANSI样式声明,否则是非法的。
Modport
带有方向的modport列表是在接口中定义的,用于对模块内的接口访问施加某些限制。关键字modport表示声明方向时就像在模块内一样。
语法
下面所示的是接口 myInterface 的定义,内部含有几个信号的定义以及2个 modport 声明。modport dut0主要表述了信号 ack 和 sel 为输入信号,gnt 和 irq0为输出信号,不论什么模块使用此特定modport。
同样地,另外一个 modport dut1 声明的方向刚好相反。
已命名的端口 bundle 示例
在这种风格中,设计将从其端口列表中提到的接口对象中获取所需的正确modport定义。测试台只需要向设计提供整个接口对象。
连接端口 bundle 示例
在这种风格中,设计简单地接受提供给它的任何方向信息。因此,测试平台负责为设计提供正确的modport值。
modport 的需求是什么 ?
默认情况下,在简单接口内声明的网络是 inout ,因此连接到同一网络的任何模块都可以驱动值或从中获取值。简而言之,值传播的方向没有限制。您可能会在网络上得到X,因为测试台和设计都将两个不同的值驱动到同一接口网络。测试台编写器应该特别小心,以确保不会发生这种情况。这可以通过使用modport从本质上避免。
连接通用接口的示例
模块还可以有一个通用接口作为端口列表。通用句柄可以接受从上面的层次结构传递给它的任何modport。
设计示例
让我们考虑两个通过非常简单的总线结构连接的模块:主模块和从模块。假设总线能够发送地址和数据,而从模块需要捕获和更新其内部寄存器中的信息。因此,主模块必须始终启动传输,并且从模块能够通过其 sready 信号向主模块指示它是否准备好接受数据。
接口
下面显示的是主模块和从模块之间共享的接口定义。
设计
假设主机简单地将地址从0迭代到3,并发送等于该地址乘以4的数据。主机应仅在从机准备接受且由 sready 信号指示时才发送数据。
假设从机接受每个Addr的数据并将其分配给内部寄存器。当地址从3回绕到0时,从机需要额外的1个时钟才能准备就绪。
这两个设计模块在顶层捆绑在一起。
testbench
测试台将把接口句柄传递给设计器,然后设计器将主和从调制解调器分配给子模块。
请记住,主机发起总线事务,从机捕获数据并将其存储在相应地址的内部寄存器reg_*中。
Questa Sim 仿真:
时钟块
默认情况下,模块端口和接口不指定任何定时要求或同步方案。在 clocking 和 endclocking 之间定义的时钟块。它是与特定时钟同步的信号集合,有助于指定时钟和信号之间的时序要求。
这将允许测试编写者更多地关注事务,而不是担心信号何时会与时钟进行交互。一个测试台可以有许多时钟块,但每个时钟只有一个块。
语法
延迟值表示信号要被采样或驱动的距离时钟事件多少时间单位的偏差。如果未指定默认偏差,则所有输入信号将被采样#1step,并且输出信号在指定事件之后被驱动。
注意以下几点:
- 创建一个名为ck1的时钟块,它将在clk的上升沿被激活
- 默认情况下,时钟块内的所有输入信号将在5ns之前被采样,时钟块内的所有输出信号将在时钟钟块的正边缘后被驱动2ns
- data,valid, ready信号被声明为块的输入,因此将在clk的定位前5ns采样
- grant 是对具有自身时序要求的块的输出信号。在这里,授予将在clk的负边缘驱动,而不是默认的状态。
接口内使用
简单地说,时钟块封装了一组共享公共时钟的信号。因此,在接口内声明时钟块可以帮助节省连接到测试台所需的代码量,并有助于节省开发期间的时间。
时钟块内的信号方向是相对于测试台而不是DUT的。
时钟块允许在指定的时钟事件下采样输入和驱动输出。如果时钟块提到输入偏差,则该时钟块内的所有输入信号将在时钟事件之前的偏差时间单位被采样。如果时钟块被提及输出偏差,则该块中的所有输出信号将在相应时钟事件之后的偏差时间单位被驱动。
输入/输出偏斜是啥 ?
偏斜被指定为常量表达式或参数。如果仅使用数字,则偏斜被解释为跟随给定范围内的活动时间刻度。
在上面给出的例子中,我们声明了一个名称cb的时钟块,以描述当属于这个块的信号时必须进行采样。信号req被指定为有1ps的歪斜,并将在时钟边缘clk之前采样1ps。输出信号gnt有2个时间单位的输出倾斜,因此将遵循当前范围内所遵循的时间尺度。如果我们有一个1ns/1ps的时间尺度,那么#2代表2 ns,因此将在时钟边缘后被驱动2 ns。最后一个信号sig是inout类型的,将在时钟边缘前1 ns采样,在时钟边缘后3 ns驱动。
1step 的输入倾斜表示信号应该在前一个时间步结束时采样,或者换句话说,在正时钟边缘之前立即采样。
具有显式 #0 偏斜的输入将与其相应的时钟事件同时采样,但在观察区域内,以避免竞争条件。同样,没有偏差或显式#0的输出将在 Re-NBA 区域中与计时事件同时被驱动。(No Blocking Assignment)
示例
考虑一个简单设计,具有输入 clk 和 req ,并驱动输出信号 gnt 以使事情简单,让我们只要接收到请求就提供许可。
为了处理设计端口信号,让我们创建一个名为if的简单接口。
下一步是驱动设计的输入,以便它返回许可信号。
仿真结果:
输出偏斜
为了清楚地了解输出偏差,让我们调整接口,使其具有三个不同的时钟块,每个时钟块具有不同的输出偏斜。然后,让我们使用每个时钟块来驱动 req ,以查看不同之处。
在我们的测试平台中,我们将使用for循环来迭代每个激励,并为每个迭代使用不同的时钟块。
仿真:
值得注意的是,通过cb_1时钟块采样的测试台代码设法获得值0x3,而cb_0获得0xd。请注意,对于其他仿真器,这些值可能不同,因为它们可以采用不同的随机化种子值。