2.1.2 -2【SV接口、采样和数据驱动、测试开始结束、调试方法】

接口

什么是接口 (int erface) ?

接口可以用作设计,也可以用作验证
在验证环境中,接口可以使得连接变得简洁不易出错 (mod port)
interface和module的使用性质很像。它可以定义端口,也可以定义双相信号;它可是使用initialalways,t也可以定义function和task.
interface可以在硬件环境和软件环境中传递,例如作为module的 口列表,也可以作为软件方法的形式参数(需要关键词 virtual)
初学者可以将interface看做一个"插排”,而DUT与TB之间的数据驱动关系都可以使用interface这个插排来完成。

如果要测试arbiter,现有的经典方法是什么?
在这里插入图片描述

如果使用了interface,那么测试的链接关系会变成什么?
tb是tb的部分,arbiter是单独的部分,tb通过interface把激励给arbiter,arbiter通过interface反馈信息给tb
在这里插入图片描述
设计部分
在这里插入图片描述
关键词 interface ,endinterface
名称:arb_if
信号被 tb 和 arbiter使用
interface 可以做arbiter设计的一部分。用sv做设计,可以把interface 作为一个特殊的端口 (arb_if ),在module的端口列表里面声明,不需要声明方向 。(arb_if arbif)(类型,参数)。 arbiter驱动grant信号,tb接收grant信号

测试部分
在这里插入图片描述
知道接口arbif,就知道了接口时钟 arbif.clk,然后利用时钟做驱动,利用时钟做了grant信号,等待一定的时间,等待grant,通过$finish结束仿真

顶层top:例化了arbiter , test, 接口arbif,时钟对于接口来讲是它的输入。接下来例化 tb 和 arb的时候 要把arbif interface的实例分别传递给 例化过程里面DUT的实例 和tb产生激励的实例
在这里插入图片描述

接口的优势

  • 有关信号封装在同一个接口中,对于设计和验证环境都便于维护和使用。如果你需要新添加信号,只需要在接口中定义这个信号,而在使用这个接口的模块或者验证环境中做出相应修改
  • 由于接口既可以在硬件世界 (module) 中使用,又可以在软件世界 (class) 中使用,(软件里面不能例化接口,但可以利用接口的指针找到接口的实例,进一步找到接口里面的信号),interface作为SV中唯一的硬件和软件环境的媒个交互,它的地位不可取代,所以verifier一定要精通接口的使用,对应的实验部分在后期会主要使用接口来做数据驱动
  • 接口由于可以例化的特性,使得对于多组相同的总线,在例化和使用时变得更加灵活,不仅使得代码变得简洁,也更易于验证环境的管理和维护
    在这里插入图片描述
  • 每个slave initiator的端门列表中只需要一个slave if接口即可,不再需要分散的端口
  • arbiter responder也只需要一个arb_if接口
  • 顶层在例化时,只需要罗列出接口的数量,不再需要分散的变量作为TB与DUT之间的连线
  • stimulator驱动数据只需要借助对应的interface即可,易于操作和管理

接口的定义和使用

  • interface定义与module类似,初学者可以参考
  • 在interface的端口列表中只需要定义时钟复位公共信号或者不定义任何端口信号,转而在变量列表中定义各个需要跟DUT和TB连接的logic变量。为了简单易用,我们推荐使用logic来定义变量。(接口里面为什么使用logic四值逻辑,而不使用bit 两值逻辑?)
  • interface也可以依靠参数化(参数可以控制接口信号里面的宽度、结构)方式提高复用性
  • interface在例化时,同module的例化方式一样
  • 对于有对应interface的DUT和TB组件 (例如stimulator),在其例化时,也只需要传递匹配的interface变量名即可完成interface的变量传递。
  • module和 interface的差别:模块里面可以例化模块,例化接口;接口里面可以例化接口(更高一级的接口可以嵌套子一级的接口,但是接口本身作为一个数据传递的媒介,没有理由例化模块),但是不能例化模块。

采样和数据驱动

学习接口做数据方面的驱动和采样,stimilator 和monitor都会用到接口,它们是主要利用接口的组件

竞争问题

  • 为了避免在RTL仿真行为中发生的信号竞争问题,我们建议通过非阻塞赋值或者特定的信号延迟解决同步的问题
    竞争、冒险与采样有关的事情,采样(利用时钟的上升沿采)时
    在这里插入图片描述
  • 同样地在仿真行为中,为了尽量避免时序电路中时钟和驱动信号的时序竞争问题,我们需要给出尽量明确的驱动时序和采样时序
  • 默认情况下,时钟对于组合电路的驱动会添加一个无限最小时间(delta-cycle)的延迟,而该延迟无法用绝对时间单位衡量它要比最小时间单位精度还小。(RTL仿真没有办法标注说具体不同的电路在真实的环境里面,这个寄存器和下个寄存器它们的延时是多少,这些延时在门极仿真的时候表现出来。需要把RTL级别对 应到网表。任何信号只要被时钟驱动\其它信号驱动,那么被驱动的信号和驱动的信号之间至少要有一个delta-cycle的延迟)
  • 在一个时间片 (time-slot) 中可以发生很多事情,例如在仿真器中敲入命令“run 0”,即是让仿真器运行一个delta-cycle的时间(一个时间片里面包含无穷多个 delta-cycle )
    那么,了解delta-cycle,对了解数据驱动和采样有什么帮助呢?
    我们来看下一道习题。

在这里插入图片描述
在这里插入图片描述
时钟1、2在上升沿采样,要建立保持时间,要在时钟的上升沿变化之前,d1变得稳定,如果采的话是采前面稳定的那一刻

  • 查看delta-cycle,得到更准确的时序关系
  • 将光标选中波形上的clk1,再将时间轴选中到clk1在45ns处的上升变化沿。选中工具栏处的"expanded time deltas mode再选中"expanded time at active cursor"
    在这里插入图片描述
  • 上述操作即是用来观察在特定时间点,时序逻辑或者组合逻辑中参与硬件模拟仿真的硬件变量之间的准确时序前后关系
    在"45ns +0 (delta-cycle)"处,clk1=1,clk2=0,d1=1
    在这里插入图片描述
    在"45ns +1 (delta-cycle)"处,clk1=1,clk2=1,d1=2
    在这里插入图片描述
  • 所以由于各种可能性,clk与被采样数据之间如果只存在若干个(0…N) delta-cycle的延迟,那么采样可能会存在问题,例如上面例子中clk1与clk2对d1采样,在同样的时刻中得到的是不同采样结果。(在真实的设计里面,时钟1 和 时钟2 ,为什么有子时钟呢? 在设计里面可能对它做一个分频,加了组合逻辑或者时序逻辑,对仿真器来讲就是加了delta-cycle,产生了新的时钟)
  • 因此采样数据中竞争问题会成为潜在困扰仿真采样准确性的问题
  • 如何避免采样的竞争问题呢?
    • 在驱动时,添加相应的人为延迟,模拟真实的延迟行为,同时加大clk与变量之间的的延迟,以此提高DUT使用信号时的准确度TB采样信号时的可靠性 (比如DUT在输出的时候已经明确把 d1 相比较时钟完了1ps/1ns 这样在仿真里面就没有delta-cycle了)
    • 对于一些采样时依然存在delta-cycle延迟的信号(设计没有做延迟处理),我们还可以依靠在采样事件前的某段时刻中进行采样,来模拟建立时间的采样要求,确保采样的可靠性。

接口中的clocking (时钟块)

  • 硬件世界和软件世界的连接可以通过灵活的interface来实现,也可以通过modport(V1课程里有讲,clocking包含了modport的限定方向)来进一步限定信号传输的方向,避免端口
    连接的错误
  • 我们也可以在接口中声明clocking (时序块,更多在interface里面用)和采样的时钟信号用来做信号的同步和采样
  • clocking块基于时钟周期对信号进行驱动或者采样的方式,使得testbench不再苦恼于如何准确及时地对信号驱动或者采样,消除了信号竞争的问题

在这里插入图片描述

  • 第一行定义了一个clocking块bus,由clock1的上升沿来驱动和采样
  • 第二行指出了在clocking块中所有的信号,默认情况下会在clocking事件(clock1上升沿)的前10ns来对其进行输入采样,在事件的后2ns对其进行输出驱动
  • 下一行是声明了要对其采样的三个输入信号,data,ready 和 enable 信号,这三个信号作为输入,它们的采样事件即采用了默认输入事件(clock1上升沿前的10ns)
  • 第四行声明了要驱动的ack信号,而驱动该信号的事件是时钟clock1的下降沿,即覆盖了原有的默认输出事件(clock1上升沿后的2ns)
  • 接下来的addr,也采用了自身定义的采样事件即clock1上升沿前的1step。这里的1step会使得采样发生在clock1上升沿的上一个时间片采样区域,即可以保证采样到的数据是上一个时钟周期的数据。
  • clocking块不但可以定义在interface中,也可以定义在moduleprogram
  • clocking中列举的信号不是自己定义的,而是应该由interface或者其它声明clocking的模块定义的。
  • clocking在声明完名字之后,应该伴随着定义默认的采样事件,即"default input/output event”。如果没有定义,则会默认地在clocking采样事件前的1step对输入进行采样,在采样事件后的#0(很多个delta-cycle)对输出进行驱动
  • 除了定义默认的采样和驱动事件,也可以在定义信号方向时,用新的采样事件对默认事件做覆盖
    在这里插入图片描述

结论

  • 为了避免可能的采样竞争问题,verifier应该在 验证环境的驱动环节 就添加固定延迟,使得在仿真波形中更容易体现出时钟与被驱动信号之谊的时序前后关系,同时这样也便于DUT的准确处理和TB的准确采样
  • 如果TB在采样从DUT送出的数据,在时钟与被驱动信号之间存在delta-cycle时,应该考虑在时钟采样沿的更早时间段模拟建立时间要求采样,这种方法也可以避免由于delta-cycle问题带来的采样竞争问题。
  • 当我们把clocking运用到interface中,用来声明各个接口与时钟的采样和驱动关系后,可以大大提高数据驱动和采样的准确性,从根本上消除采样竞争的可能性

测试的开始和结束

  • 各个设计自身可以作为一个大的线程,内部又包含多个并行的线程,而模块之间即线程的通信,主要依靠信号的变化
  • 可以想象,对于一个设计,如果在仿真开始没有任何的激励,譬如时钟和复位信号,那么仿真不具备执行的条件,也可以认为已经结束。因为对于设计内部并没有产生任何新的事件,也不会由这些事件进一步触发组合逻辑和时序逻辑
  • 如果我们在仿真开始后提供时钟和复位信号,这对于验证而言是必要的步骤,但是它本身不会对设计的功能产生实质的功能影响。从设计的角度来看,复位信号只是为了让设计进入确定的状态,而时钟信号如同血管的供血功能来保证设计可以正常地“跳动”
  • 在Verilog的测试方式中,即便我们只给设计提供复位和时钟信号,整个仿真也会一直持续下去,并不会主动结束。即使DUT的输入激励已经执行完毕,仿真也会一直进行下去,这时就需要通过Verilog系统函数主动结束仿真

系统函数调用方式结束

  • 在Verilog测试中,需要通过Verilog提供的系统函数来结束仿真。下面的例子即在仿真500ns时通过系统函数 $finish() 结束了仿真;而用户也可以考虑使用 $stop() 来暂停仿真。这两者的区别在于 $finish() 会使得仿真退出,将控制权交回给操作系统,仿真无法再次继续; $stop() 会使得仿真暂停,用户还有机会让仿真继续运行。
    加粗样式

仿真开始

在这里插入图片描述
run 0

  • 仿真加载后各个信号的数值
    在这里插入图片描述
  • 仿真执行了“ run 0 ”后各个信号的数值
    加粗样式
  • 从此例同学们需要理解assign(连线)和always在连线逻辑的不同,即assign在0时刻会执行一次,而always必须等到信号变化才执行.

program隐式结束

  • 在SV推出program将验证部分与设计部分进行有效隔离以后,SV也将每一个program作为一个独立的测试。如果testbench中只有一个program,则会在执行完该program中最后一个initial过程块后自动结束仿真。如果testbench中有多个program,那么需要等待所有program中最后一个initial过程块完成后,才能结束仿真.

program显示结束

  • 从上面program隐式结束方式来看,要求仿真自动结束的前提是所有program的initial块都应该在一定时间内完成,而实际上有的program内的initial语句块会一直运行下去,这就使得仿真无法等到所有的program都执行完毕,也就无法自动结束,这时候,我们可以在目标program内置入系统函数 $exit() 来要求该 program 强行结束,待该 program 结束之后,仿真器仍然会等待其它program执行完毕后再结束仿真
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

program和module

  • 我们建议将设计部分放置在module中,而将测试采样部分放置在program中。下面是更多关于program实现的要求和建议:
    • 读者可以将program看做是软件的"领地”,所以program中不可以出现和硬件行为相关的过程语句和实例,例如always(纯硬件)、 module(纯硬件)、interface(硬件和软件的媒介),也不应该出现其它program例化语句。
    • 为了使得program进行类软件方式的顺序执行方式,可以在program内部定义变量,以及发起多个initial块
    • program内部定义的变量赋值的方式应该采用阻塞赋值 (软件方式)
    • program内部在驱动外部的硬件信号时应该使用非阻塞赋值 (硬件方式)
  • 关于program对于数据采样时也可以消除delta-cycle竞争问题的描述,请同学们在课后阅读“SV环境构建篇 之 程序和模块 (红宝书里的章节)”
  • 至此同学们需要了解module (硬件盒子)、program (软件盒子)和interface (硬件软件的媒介) 的作用和定义它们的初衷在于SV需要更清晰的界限来划分硬件域 (module) 、软件域( program 和 class ) 以及中间域 (interface)
  • 今后如果要为验证环境建立独立的测试盒子,可以考虑采用 program 来帮助消除采样竞争问题以及自动结束测试用例;也可以采用module硬盒子的方式,使用interface clocking来消除采样竞争问题,使用 s t o p ( ) 、 stop() 、 stop()finish() 系统方法来显式结束测试用例。
  • module和interface是我们学习SV的重点,关于program我们需要熟悉它的特性和定义该软件容器的出发点。

调试方法

使用库、仿真和过程窗口

  • 首先需要明确无论哪一种RTL仿真器,它们之前的特性都是互相借鉴的,也就是说,对于初学者你需要掌握的是一个工具它自身特性背后的逻辑: 它为什么需要这一种特性? 这种特性为了什么服务?如果没有这种特性,你的仿真调试会不会面临困难呢? 有没有可以取代的、比目前这种特性更好更便捷的方式呢?
  • 在我们课程中使用的仿真器,同学们需要首先理解库窗口仿真窗口过程窗口的含义,将它们与硬件模型在线运行的进程一一对应
  • 库 (library) 是编译的产物,在没有介绍软件之前,硬件(module、interface、 program) 都会编译到库中,如果不指定编译库的话,会被编译进入默认的库中。从容纳的类型来看库既可以容纳硬件类型,也可以容纳软件类型,例如类和方法也包括包 (package)
  • 对于编译的module、interface 和 package 这些硬件和软件会进入哪一个library呢? 如果没有额外的指定,它们都会被编译到默认library (work) 中。在默认库中各个module是互相识别的,当然module也识别同一个library中的package(好多软件的东西包在里面,先把类编译到package里面,package进一步编译到库里面)。如果要使用其他library中的module或者package,那么一个config文件是一项好的选择。

库窗口

在这里插入图片描述

  • 所以编译成功的硬件(module、interface、 program) 和软件 (class、package) 都可以被放入库中,如果不指定放入的位置它们会被放入默认的work库中。 可以创建自己的库
  • 放入库中是仿真的必要步骤,但不代表后续的链接 (elaboration) 就不会出错。编译 (compilation) 会检查语法,而链接会进一步检查硬件和软件之间的兼容性、内存安全、结构可靠性等问题.

仿真窗口

  • 仿真窗口 (sim window) 代表目前正在进行的仿真结构。一般在验证环境中,应该包含硬件测试的结构组件待测设计(DUT)
  • 在整个TB结构中,我们可以观察到TB的展开结构和DUT的结构,这种层次化的结构演示是所有仿真器的共性。而通过这种将硬件层次可视化展示的方式,可以方便verifier查找任何一层的信号
  • 需要注意的是,仿真窗口只能提供静态的层次结构,即由module、program、interface和package的内容,但是对于class的例化实例这种在验证环境中十分重要的软件动态内容,则需要在类窗 (class window) 和对象窗口 (obiect window)中查看,关于这一点我们也会在后续课程内容中为同学们介绍
  • 仿真窗口中展示的是TB和DUT的结构层次
  • 同时,被引入 (import) 的package和其组件(utilities) 、方法 (methods) 也会被显式出来
  • 下图中被默认引入的标准库 (std library) 的组件semaphore、mailbox和process我们将在后续课程中逐一介绍

在这里插入图片描述

过程窗口

  • 过程窗口 (process window) 代表着整个仿真在某一个时间点上所有过程语句块 (initial、always、assign) 的状态
  • 如果状态为active,代表该进程块正在执行,如果是ready状态代表该进程块是非活跃状态 (但不一定代表它已经执行完毕或者不会再执行)
  • 过程窗口对于调试一些仿真挂起 (hang-on) ,时间无法继续前进的仿真难题特别有效
    在这里插入图片描述

查看信号和波形

  • 查看信号和波形应该是跟仿真器初学者联系最多的调试方法了在这里提醒大家掌握一些基本的技能。

    • 知道在仿真窗口中点击某一个层次,继而在信号窗口 (objects window)中选中需要的信号添加到波形窗口处。
    • 懂得信号的输入、输出和内部信号的属性,以及它们当前数值显示的默认进制 (十六进制)、也懂得如何在波形串口修改信号数值的进制显示格式,懂得如何强制修改 (force) 信号的数值。
    • 对于时钟信号,需要掌握测量时钟周期和频率的技巧
    • 在查看信号时,如果要找到下一个变化事件时间点,懂得使用工具栏的cursor部分去快速搜索信号的变化沿。
      在这里插入图片描述
    • 熟练应用如何开始仿真、重启仿真、结束仿真等操作。
    • 在命令窗口 (transcript window) 使用命令 " log -r /* ” 在开始仿真前调用,确保TB层次下所有信号都可以保存到数据库以供查看任何信号的波形。

打印消息

  • 打印消息是调试循环语句、顺序执行语句等查看执行路径和当前变量值的简便方式
  • 初学者需要掌握打印消息的基本要点:
    • 在 $ display() 系统方法中通过传递 $ time 来将显示消息发生的时间点。
    • 在 $ display() 中通过不同的数据格式来显式想要的数据格式,例如%x (十六进制)、%d (十进制) 、%b (二进制) 、%s (字符串) 、%t (时间)等。
    • 熟练掌握 $ display() (消息级别)、$ warning() (警告级别)、$ error()(错误级别) 、$ fatal()(严重级别) 的使用场景,例如数据比对错误时,应该使用 $ error() 方法而不是 $ display。
    • 在打印数据消息时,尽量采取格式美观、可读性高、便于维护的字符串显示格式。
    • 如果要实现对字符串string赋值,那么可以采用 $ sformatf() 的方式来对字符串变量格式化,例如string s = $sformatf("Hello, %s!” name_s)这里name_s是名字的字符串变量。

设置断点和查看变量

  • 设置断点是软件的方法,那么在硬件仿真中为什么需要设置断点呢? 首先我们需要理解什么是断点、执行顺序和断点的作用。
  • 在软件执行中,通过设置断点 (breakpoint) 可以查看在程序在执行到断点处(程序暂时停止) 时的变量数值,而设置断点就要求verifier对程序的执行顺序足够了解。
  • 设置断点可以便于查看软件程序 (function、task、object)中局部变量的数值。要注意的是,软件部分的变量(即动态变量dynamic variable,与硬件变量即静态变量相对) 是无法添加到波形窗口的,原因之一例如软件变量会在0时刻中完成多次运算,而波形窗口无法反映出在0时刻中的若干变化,而且这么做对于波形存储的压力也很大。因此软件部分的变量只可以通过设置断点来查看当前程序中的变量数值
  • 设置断点的另外一个作用在于可以用来调试程序执行的顺序,例如在顺序执行语句执行的多个位置设置断点,通过让仿真执行,查看是否程序在断点处停下,如果在某个断点没有停留,那么程序的挂起 (hang-on) 原因就应该在上一个停留的断点和下一个未停留的断点之间。通过这种方式可以缩小调试可疑程序的范围。
  • 如果要查看程序执行处的局部变量,需要使用的是局部变量窗口(locals window) ,可以从菜单栏View -> Locals选中,继而通过断点查看变量,不应该使用信号窗 (obiects window)查看变量,因为信号窗口是针对静态变量,而局部变量窗口是针对动态变量的。
    在这里插入图片描述
    在这里插入图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值