本文
芯片验证系列文章主要分为三部分:验证工程师与芯片验证、SV学习笔记、UVM学习笔记。此为 SV学习笔记 部分第八篇,主要介绍一些实验练习中的具体代码解析。
版本 | 说明 |
---|---|
0.1 | 初版发布 |
参考
名称 | 作者 | 来源 |
---|---|---|
《芯片验证漫游指南》 | 刘斌 | 书籍 |
《SystemVerilog验证》 | 克里斯.斯皮尔 | 书籍 |
专业术语与缩略语
缩写 | 全称 | 说明 |
---|---|---|
MCDF | multi-channel data formatter | 多通道数据整流器 |
SV入门练习
基本数据类型
- 有符号无符号、四状态双状态、枚举类型、结构体
|
|
字符串类型
- 构建字符串和字符串拼接 (一般常用$sformatf函数)
|
|
数组类型
- 合并非合并、赋值与循环、动态数组、队列、关联数组
|
|
接口的定义与例化
- 接口里也可以定义方法
|
|
类的封装
|
|
类的继承
|
|
package的使用
|
|
随机约束
|
|
线程的同步
|
|
线程的控制
|
|
虚方法
|
|
方法(任务与函数)
|
|
SV用于设计
|
|
SV进阶练习
设计描述
所有SV练习都是针对硬件设计MCDF进行的, 在这里介绍一下它的结构、功能、寄存器和时序。
该设计我们称之为多通道数据整形器(MCDF, multi-channel data formatter) , 它可以将上行(uplink) 多个通道数据经过内部的FIFO,最终以数据包(data packet) 的形式送出。由于上行数据和下行数据的接口协议不同,我们也将在后面的接口描述和时序部分进一步讲解。此外,多通道数据整形器也有寄存器的读写接口,可以支持更多的控制功能。
-
从上图的MCDF结构来看主要可以分为如下几个部分 :
- 上行数据的通道从端(Channel Slave) , 负责接收上行数据, 并且存储到其FIFO中。
- 仲裁器(Arbiter) 可以选择从不同的FIFO中读取数据, 进而将数据进一步传送至整形器(formatter) 。
- 整形器(Formatter) 将数据按照一定的接口时序送出至下行接收端。
- 控制寄存器(Control Registers) 有专用的寄存器读写接口, 负责接收命令并且对MCDF的功能做出修改。
-
接口描述 :
系统信号接口
信号名 | 位宽 | 方向 | 说明 |
---|---|---|---|
clk | [0] | I | 时钟信号 |
RSTN | [0] | I | 复位信号,低有效 |
通道从端接口 :
信号名 | 位宽 | 方向 | 说明 |
---|---|---|---|
CHx_DATA | [31:0] | I | 通道数据输入(x为0/1/2) |
CHx_VALID | [0] | I | 通道数据有效标志,高有效 |
CHx_READY | [0] | O | 通道数据接收信号,高位表示接收成功 |
整形器接口 :
信号名 | 位宽 | 方向 | 说明 |
---|---|---|---|
FMT_CHID | [1:0] | O | 整形数据包的通道ID号 |
FMT_LENGTH | [4:0] | O | 整形数据包的长度信号 |
FMT_REQ | [0] | O | 整形数据包发送请求 |
FMT_GRANT | [0] | I | 整形数据包被允许发送的标志 |
FMT_DATA | [31:0] | O | 数据输出端口 |
FMT_START | [0] | O | 数据包起始标志 |
FMT_END | [0] | O | 数据包结束标志 |
控制寄存器接口 :
信号名 | 位宽 | 方向 | 说明 |
---|---|---|---|
CMD | [1:0] | I | 寄存器读写命令 |
CMD_ADDR | [7:0] | I | 寄存器地址 |
CMD_DATA_IN | [31:0] | I | 寄存器输入端口 |
CMD_DATA_OUT | [31:0] | O | 寄存器输出端口 |
- 接口时序 :
通道从端接口时序
当valid为高时,表示要写入数据。如果该时钟周期ready为高,则表示已经将数据写入;如果该时钟周期ready为低,则需要等到ready为高的时钟周期才可以将数据写入。
整形器接口时序 :
整形器发送数据是按照数据包的形式发送的,可以选择数据包的长度有4、8、16和32。整形器必须完整发送某一个通道的数据包后, 才可以转而准备发送下一个数据包, 在发送数据包期间, fmt_chid和fmt_length应该保持不变, 直到数据包发送完毕。
在整形器准备发送数据包时, 首先应该将fmt_req置为高, 同时等待接收端的fmt_grant。当fmt_grant变为高时, 应该在下一个周期将fmt_req置为低。fmt_start也必须在接收到fmt_grant高有效的下一个时钟被置为高, 且需要维持一个时钟周期。在fmt_start被置为高有效的同一个周期,数据也开始传送,数据之间不允许有空闲周期,即应该连续发送数据,直到发送完最后一个数据时, fm_tend也应当被置为高并保持一个时钟周期。
相邻的数据包之间应该至少有一个时钟周期的空闲,即fmt_end从高位拉低后,至少需要经过一个时钟周期,fmt_req才可以被再次置为高。
控制寄存器接口时序 :
在控制寄存器接口上, 需要在每一个时钟解析cmd。当cmd为写指令时, 需要把数据cmd_data_in写入到cmd_addr对应的寄存器中; 当cmd为读指令时, 即需要从cmd_addr对应的寄存器中读取数据, 并在下一个周期, 将数据驱动至cmd_data_out接口。
- 寄存器描述 :
地址 0x00 通道0控制寄存器32bits读写寄存器
位 | 功能 |
---|---|
bit(0) | 通道使能信号。1为打开, 0位关闭。复位值为1。 |
bit(2:1) | 优先级。0为最高, 3为最低。复位值为3。 |
bit(5:3) | 数据包长度, 解码对应表为, 0对应长度4,1对应长度8,2对应长度16,3对应长度32,其它数值(4-7)均暂时对应长度32。复位值为0。 |
bit(31:6) | 保留位, 无法写入。复位值为0。 |
地址 0x04 通道1控制寄存器32bits读写寄存器
同通道0控制寄存器描述。
地址0x08通道2控制寄存器32bits读写寄存器
同通道0控制寄存器描述。
地址0x10通道0状态寄存器32bits只读寄存器
位 | 功能 |
---|---|
bit(7:0) | 上行数据从端FIFO的可写余量, 同FIFO的数据余量保持同步变化。复位值为FIFO的深度数 |
bit(31:8) | 保留位,复位值为0 |
地址0x14通道1状态寄存器32bits只读寄存器
同通道0状态寄存器描述。
地址0x18通道2状态寄存器32bits只读寄存器
同通道0状态寄存器描述。
- 简化版MCDF :
在了解了MCDF的设计之后,我们接下来的几次实验将首先围绕着MCDF中的子模块channel和arbiter的组合MCDT着手, 暂时不考虑formatter的数据打包功能和寄存器的配置作用。因此我们所选择的MCDF的“片段”MCDT即由三个slave channel和一个arbiter构成。从之前介绍的各个模块功能来看, 这个组合的作用类似于一个高级的“MUX”多路选择器, 只不过这个选择器的无需外部的选择配置, 而是会根据slave channel的数据发起请求来做选择授权, 继而将数据送出。
lab1
-
从verilog到SV的进场 :
- 将下面是tb1.v的代码,将文件名修改为tb1.sv后,编译仿真,查看仿真行为是否同tb1.v的仿真行为一致?这说明了什么呢?
- 将tb1.sv中的信号变量类型由reg或者wire修改为logic类型, 再编译仿真,查看行为是否同修改之前的一致呢?这是为什么?
3)在步骤2) 的基础上, 如果将rstn的类型由logic修改为bit类型,再编译仿真, 行为是否同步骤2)的一致呢?这是为什么?
|
|
- 方法task和函数function :
- 在tb2.sv文件中, 可以看到不同于tb1.sv文件的是,之前产生时钟和发起复位的两个inital过程块语句都被两个task(即clk_gen和rstn_gen)取代了。
- 在两个intal块中分别调用产生时钟和复位的task,再编译仿真查看时钟信号和复位信号,是否正常?
- 为什么要将两个task在两个initial块中调用?这是为什么呢?是否可以在一个initial块中调用呢?如果可以,调用它们的顺序是什么?
- 是否可以读出目前时钟的周期和频率呢?该如何测量呢?如果我们想改进clk_gen()方法, 使其变为可以设置时钟周期的任务clk_gen(int peroid), 那么该怎么修改目前的任务clk_gen()呢? 修改成功后,请在initial块中调用任务ck_gen(20),看看波形中的时钟周期是否变为20ns呢?
- 如果将`timescale 1ns/1ps修改为`timescale 1ps/1ps, 那么仿真中的时钟周期是否发生变化?这是为什么呢?
|
|
- 数组的使用 :
- 如果要求对每一个slave的数据发出100个数, 那么该怎么实现呢?
- 如果现在要先生成100个数,并对它们按照目前的数值规则进行赋值,那么请创建3个动态数组, 分别放置要发送给3个slave的数据。
- 接下来利用之前生成的数组数据, 将它们读取并发送给三个channel。
|
|
- 验证结构 :
为了实现清晰的验证结构,我们希望将DUT和激励发生器(stimulator)之间划分。因此,我们可以将激励方法chnl_write()封装在新的模块chnl_initiator中。
从tb4.sv中可以发现之前的inital语句块channel_write_task已经不见了,在其位置上的变为了三个例化的chnl_initiator实例chnl0_init、chnl1_init和chnl2_init。它们的作用扮演每个channel slave通道对应的stimulator,发送激励,因此我们在其模块chnl_initiator中定义了它的三个方法, 即set_name() 、chnl_write() 和chnl_idle() 。
- chnl_ixle() 要实现一个时钟周期的空闲,在该周期中,ch_valid应为低,ch_data应为0。
- chn_write() 要实现一次有效的写数据, 并随后调用chnl_idle(), 实现一个空闲周期,在实现有效写数据时,请考虑如何使用ch_ready信号,结合功能描述的channel slave接口时序来看, 只有当valid为高且ready为高时,数据写入才算成功,如果此时ready为低,那么则应该保持数据和valid信号,直到ready拉高时,数据写入才算成功。
- set_name()即设置实例的名称, 在inital过程块“data test”中, 在发送各个channel数据前, 请设置各个channel initiator的实例名称, 这样方便在仿真时各个实例的打印信息可以显示它们各自的名称、数据发送时间和数据内容,便于阅读和调试。
- 最后,进入了本次实验的最后一个步骤了,之所以提出发送更多的数据,并且发送更紧凑高速的数据, 是为了可以观察到,是否你的三个channel_slave各自的chX_ready信号可以拉低呢?如果拉低了,这代表着什么?那么请你试试看,考虑如何发送更多更快的数据,让M CDT的三个chX_ready信号可以拉低吧。
|
|
lab2
练习2部分将主要回顾之前接口、仿真结束、类和包的使用,在这一个练习中,将逐渐从使用硬件盒子(module)验证过渡到使用接口和软件盒子(class) 来验证设计。而这一个练习之所以重要也是因为它是硬件验证方式与软件验证方式之间的过渡,同时作为验证环境的启蒙。在本练习的最后一个小节中也能够初步体会到类的继承和层次包含关系,而这些都将作为日后学习高阶UVM知识的重要基础。
- 接口的使用 :
在lab2的tb1.sv的代码部分承接的是上一次实验的最后代码部分,不过最显著的差别在于,我们将验证组件和DUT之间通过接口来连接,所以验证组件chnl_initiator的端口变得非常*干净”, 即chnl_intf intf。在使用接口之前我们需要定义接口chnl_intf和内部的端口,同时我们也声明了一个时钟块(clocking block) , 它的功能是为了消除可能存在的竞争(race) 问题,确保时钟驱动数据之间有一定的延迟, 以便于DUT顺利采样。
因此更新后的代码, 可以发现例化的实例包括了只产生数据的channel generator, 只负责发送数据的channel_initiator以及作为验证组件和DUT之间的接口chnl intf。而例化之后,在inital块中需要通过调用组件的方法完成初始化、名字设置和空闲周期的设置。这里初始化是为了设置ID,名字设置是为了稍后打印时容易区分各个组件,而空闲周期的设置则关系到我们接下来的实验要求:
- 首先观察波形,可以发现channel_initiator发送的数据例如valid和data与时钟clk均在同一个变化沿,没有任何延迟。同我们课程中所讲到的,这种0延迟的数据发送不利于波形查看和阅读, 因此请在已有代码的基础上使用intf.ck的方式来做数据举动,并且再观察波形,查看驱动的数据与时钟上升沿的延迟是多少。
- 为了更好地控制相邻数据之间的空闲间隔,又引入了一个变量idle_cycles,它表示相邻有效数据之间的间隔。已有代码会使得有效数据之间保持固定的一个空闲周期,需要使用idle_cycles变量,来灵活控制有效数据之间的空闲周期。通过这个方法,在tb的initial块中 通过方法set_idle_cycles()使得三个channel_initiator的空闲周期变为0, 即可以实现有效数据的连续发送。
|
|
- 仿真的结束 :
tb2.sv中需要课外学习fork join的基本功能和使用方法,了解它的并行运行特性,以此此来区分不同的测试内容,这是由于每一个测试任务的测试目的和要求都不相同,具体要求可以来实现三个chnl_initiator同时发送数据的要求。同时我们又将不同的test也组装到task中,以在代码中查找tb2.sv需要首先移植tb1.sv的要求内容,接下来再完成新的实验要求。
- 可以参考task basic_test(), 来完成burst_test(),它的要求是使得每个chnl_initiator的idle_cycles设置为0,同时发送500个数据, 最后结束测试。
- 参考task basic_test()来完成task fifo_full_test() 。它的要求是无论采取什么数值的idle_cycles,也无论发送多少个数据,只要各个chnl_initiator的不停发送使得对应的channel缓存变为满标志(ready拉低),那么可以在三个channel都拉低过ready时(不必要同时拉低,先后拉低即可),便可以立即结束测试。
|
|
- 类的例化和类的成员 :
在这一部分, 将之前用来封装验证功能的硬件盒子(module) 中的数据和内容移植到软件盒子(class) 中来,以通过前后代码的相同点和不同点来比较使用类的时候,需要注意什么地方,同时也可以基本掌握类的例化,类的成员变量访问权限以及类的成员方法如何定义和使用。
- 在将module chnl_initiator和module chnl_generator分别改造为class chnl_initiator和class chnl_generator后,可以发现我们同时定义了一个用来封装发送数据的类chnl_trans。要求需要在inital块中分别例化3个已经声明过的chnl_initiator和3个chnl_generator。
- 由于每一个chnl_initiator都需要使用接口chnl_intf来发送数据,在发送数据之前我们需要确保chnl_initiator中的接口不是悬空的,即需要由外部被传递。所以接下来的实验要求需要通过调用chnl_initiator的方法来完成接口的传递。
- 接下来就可以调用已经定义过的三个test任务来展开测试了。
- 最后是关于类的例化问题,请欢察chnl_generator在例化chnl_trans t 时,有没有不恰当的地方,如果有请指出来现有的代码会造成什么样的潜在问题呢?
|
|
- 包的定义和类的继承 :
到了tb4.sv,又进一步引入了新的类chnl_agent、chnl_root_test、chnl_basic_test、chnl_burst_test和chnl_fifo_full_test,同时将所有的类(都是与chanel相关的验证组件类),封装到专门包裹软件类的容器package chnl_pkg中且完成编译。因此编译后的chnl_pkg会被默认编译到work库中,与其它的module是一同并列放置的。
关于chnl_agent,将它作为一个标准组件单元,它应该包括generator、driver(initiator)和monitor。在tb4.sv中,在tb4.sv中暂时只有chn_generater和chnl_initiator,因此将它们在agent中例化。同时,将之前用task来实现的测试任务也由类来实现。可以发现,父类是chnl_root_test,而我们已经先移植了chnl_basic_test,接下来需要实现另外两个类。
- 由于将各个类首先封装在了package chnl_pkg中,因此在module tb4中要声明类的句柄,首先应该从chnl_pkg中引入其中定义的类。
- 可以参考之前已经实现的burst_test()和fifo_full_test()任务,以及已经实现的类chnl_basic_test,按照同样的要求来实现两个新的类chn_burst_test和chnl_fifo_full_test。
- 例化已经声明过的三个test组件。
- 完成从test一层的接口传递任务,使得其内部各个组件都可以得到需要的接口。
- 调用各个test的方法,展开测试。
|
|
- 验证结构 :
请画出来你理解的tb4.sv的验证环境结构。环境结构中需要包含以下要素:
- DUT
- 接口的名称和数量
- 不同验证组件的名称、数量以及结构关系
这不单单是一个实验要求,这对于接下来后续练习中不断强调的验证环境构成、模块环境到系统环境的集成都是有很大帮助的,所以务必尝试画出你认为的tb4.sv的验证结构来。尽管目前每个人对环境的认识都有差别,但考虑到绘制验证环境将是日后从事验证工作的基本功,所以无论你的第一幅验证框图是什么样的,至少请你从这个实验开始迈出这一步。
lab3
练习3的部分将主要就随机约束和环境结构做实践。在这个练习中,将升级练习2部分中对generator和initiator之间的数据生成和数据传输的处理, 同时也将完善何时结束测试,将其主动权交于generator而不再是test组件。在组件结构实践部分中,将在原有的的initiator,generator、agent和test组件的基础上再认识monitor和checker,并且使其构成一个有机的整体,最终可以通过在线比较数据的方式完成对MCDT的测试。
- 随机约束 :
为了更早习惯各个验证文件独立放置的原则,已经先将chnl_pkg1.sv文件和tb1.sv文件独立开来,所以tb1需要编译两个文件即chnl_pkg1.sv和tb.sv。在这个练习中会进一步了解随机约束在类中定义方式、如何随机化对象、随机种子的使用方法、对象的产生等等。接下来,请按照要求开始练习吧。
- lab3继承了大部分lab2的代码,包括chnl_basic_test类,而对于这个类所需要生成的数据包提出了新的约束要求。需要注意的是,与lab2不同的是,这次数据类chnl_trans的定义发生了很大的变化,它不再只局限于包含一个数据,而是多个数据,同时跟数据之间时间间隔的控制变量也在该类中声明为随机成员变量, 那么请按照代码中具体的约束来定义chnl_basic_test类,注意该代码的修改需要在chnl_trans类中实现,因为目前的代码结构只有chnl_trans类的更新是较为合适的办法。
- 需要将原本在chnl_root_test类中用来结束仿真的$finish(变迁到generator中,那么请将它放置到合适的地方,然后由generator来结束仿真吧)。
- 请尝试着多次重新启动仿真,可以使用“restart”命令来重启,再对比连续两次生成的随机数据,看看它们之间是否相同呢?然后再在仿真器命令行处使用命令"vsim -novopt -solvefaildebug -sv_seed 0 work.tb1"来加载仿真。这里我们多传递了两个必须的仿真参数, -solvefaildebug是为了调试随机变量的,而-sv_seed NUM则是为了传递随机的种子。那么使用这个命令再看, 是否与之前没有使用-sv_seed 0的命令产生了相同的数据呢?最后,“vsim -novopt -solvefaildebug -sv_seed random work.tb1"命令再来加载仿真,比较前后两次的数据是否相同?那么,对-sv_seed random的仿真选项的认识是什么?
- 在仿真的最后,可以发现最后打印出来的chnl_obj对象的obj_id值是1200,那么这代表什么含义?为什么会有1200个chanl_obj对象产生呢?整个仿真过程中,在同一时刻,最多的时候一同有几个chnl_trans对象在仿真器内存中存在呢?这么做对内存的利用是否合理?是否还有更好的办法使得在同一时间chnl_trans对象的数量比代码中用到的更少呢?
|
|
|
|
- 更加灵活的测试控制 :
如果要实现不同的test类,例如chnl_basic_test、chnl burst_test和chnl_fifo_full_test,那么对于不同的test需要对chnl_generator的随机变量做出不同的控制,继而进一步控制其内部随机的chnl_tans对象。也就是说,随机化也是可以分层次的,例如在test层可以随机化generator层,而依靠generator被随机化的成员变量,再来利用它们进一步随机化generator中的chnl_tans对象,由此来达到顶层到底层的随机化灵活控制。那么从这个角度出发,就需要将generator从agent单元中搬迁出来,并且搁置在test层中来方便test层的随机控制,因此在chnl_pkg2.sv和tb2.sv中主要去认识如何更好的组织验证结构,从而实现更加方便的测试控制。
- 由于将generator搬迁到test层次中,所以需要将gen和agent中组件的mailbox连接起来,方便gen与agent中init的数据通信。
- 在领略了如何在test中的do_config对gen[0]进行随机化控制后,需要对gen[1]也按照代码中的具体要求进行随机控制。
- 请按照代码中的具体要求对gen[2]进行随机控制。
- 按照代码中的具体要求,在chnl_burst_test::do_config()任务中对三个generator进行随机控制。
- 按照代码中的具体要求,在chnl_fifo_full_test::do_config()任务中对三个generator进行随机控制。
- 在tb2.sv中,对于测试的选择将由仿真时的参数传递来完成。这意味着,以后的递归测试,即创建脚本命令,由仿真器读入,分别传递不同的随机种子和测试名称即可完成对应的随机测试,而这种方式即是回归测试的雏形。所以请按照之前的仿真命令,在命令窗口中添加额外的命令"+TESTNAME=testname”,这里的+TESTNAME=表示的仿真命令项,在由内部解析之后,testname会被捕捉并且识别,例如可以传递命令"+TESTNAME=chnl_burst_test"来在仿真时运行测试chnl_burst_test。请充分理解此要求,懂得如何捕捉命令,如何解析命令,最后如何选择正确的测试来运行。
|
|
|
|
- 测试平台的结构 :
最后一个实验部分即指导认识验证环境的其它组件,monitor和checker。并且通过合理的方式来构成最终用来测试MCDT的验证环境,在这个环境中需要再回顾generator、intitator、monitor和checker各自的作用。在顶层环境中,将checker置于test层中,而不是agent中,需要思考这么做的好处在什么地方。同时需要在认识generator和initiator有数据通信的同时,可以掌握monitor与checker之间的数据通信,还有checker如何针对MCDT利用内部的数据缓存进行数据比较。
- 在chnl_monitor类和mcdt_monitor类各自的mon_trans()方法中需要采集正确的数据,将它们写入mailbox缓存,同时将捕捉的数据也打印出来,便于我们的调试。
- 在chnl_agent中,参考如何例化的initiator对象,也对chnl_monitor对象开始例化、传递虚接口和使其运行。
- 在chnl_checker的任务do_compare()中,需要从checker自己的数据缓存mailbox中分别取得一个输出端的采集数据和一个输入端的采集数据,继而将它们的内容进行比较,需要注重的是,输出端的缓存只有一个,而输入端的缓存有三个,需要考虑好从哪个输入端获取数据与输出端缓存的数据进行比对。
- 在顶层环境chnl_root_test类中,需要对mcdt_monitor和chnl_checker进行例化、传递虚接口,并且将chnl_monitor、mcdt_monitor的邮箱句柄分别指向chnl_checker中的邮箱实例。
|
|
|
|
lab4
在接下来进入lab4之前,你头脑中需要再复习这些概念: 第一,验证环境按照隔离的观念,应该分为硬件DUT,软件验证环境,和处于信号媒介的接口interface;第二,对于软件验证环境,它需要经历立阶段(build)、连接阶段(connect)、产生激励阶段(generate)和发送激励阶段(transfer),只有当所有的激励发送完毕并且比较完全之后,才可以结束该测试 。
与lab3相比,lab4的环境是完善的,这其中的原因主要有两方面,一方面是因为从lab4开始在验证更大的子系统,即MCDF。与MCDT相比,MCDF主要添加了寄存器控制和状态显示功能,同时也添加了一个重要的数据整形功能,因此,你会发现lab4的验证文件多了。另外一方面,代码之所以增多是为了让整个验证环境的各个组件之间相互独立,功能清晰,可以发现不同的package之间的功能是独立的,同一个package中的各个验证组件的功能也是独立的。那么不同组件之间的同步和通信依靠什么呢?没错,那就是已经学习到的event和mailbox。
如何开始lab4呢?大胆地编译所有的文件,然后给出仿真命令就可以了:
vsim -novopt -classdebug -solvefaildebug -sv_seed 0 +TESTNAME=mcdf_data_consistence_basic_test -I mcdf_data_consistence_basic_test.log work.tb
来看看上面主要的仿真命令项:
- -classdebug,这是为了提供更多的SV类调试功能
- -solvefaildebug,这是为了在SV随机化失败之后有更多的信息提示出来,
- -sv_seed 0,暂时给固定的随机种子值0
- +TESTNAME=mcdf_data_consistence_basic_test,这是指定仿真选择的测试
- -I mcdf_data_consistence_basic_test.log,这是让仿真的记录保存在特定的测试文件名称中
那么在开始测试并且最终结束之后,在你的项目目录下会有两个文件产生:mcdf_data_consistence_basic_test_sim.log会保存所有的仿真信息,,而mcdf_data_consistence_basic_test_checker.log则只会保存做数据比较和最终比较报告信息。需要注意的是,在测试的过程中,如果测试发生错误会立即停止下来,可以根据当前时刻的错误报告定位出数据比较错误的时间点,再去核对波形,分析是硬件问题还是环境问题。
那么验证环境这么完善,是不是不需要做lab4了呢?NONONO…,lab4的重点是要在本次练习中领会,怎么样才是一个完整的验证计划和实施。什么是验证计划的核心要素点?那就是围绕着设计的功能点罗列出需要展开测试的功能点,以及如何展开测试,并且指明测试的验收标准是什么。我们lab4中主要以测试是否通过为原则,而在lab5中,将进一步掌握代码覆盖率和功能覆盖率的验收标准。
- 从验证框图可以更好地理解验证环境的组件和组件之间的通信连接情况。无论对于接下来着手构建环境,还是将它作为验证代码的一个形象说明,它都比代码更直接地形容整个验证环境。那么接下来,请画出验证结构吧。
- 接下来,会指定你需要在mcdf_pkg.sv中参考以后测试类mcdf_data_consistence_basic_test而去创建其它的测试类名,它们对应的测试功能点,以及测试标准是什么,在你按照要求,实现了所有的测试之后,记得将每个测试都至少跑一遍,如果你跑的过程中发现mcdf_checker报错,分析是不是设计问题,确定是设计bug可以提交bug,待设计修复后继续测试。理论上,如果对同一个测试,每次使用不同的随机种子即”-sv_seed RANDNUM”,那么你得次数越多,就越有机会发现新的bug,而至于需要跑多少遍,或者每个测试中需要发送多少次数据包(发送得越多越好,那么究竟要发送多少次才可以停下来呢?标准是什么?) 这个问题在lab5会解答。
测试功能点 | 测试内容 | 测试通过标准 | 测试类名 |
---|---|---|---|
寄存器读写测试 | 所有控制寄存器的读写测试 所有状态寄存器的读写测试 | 读写值是否正确 | mcdf_reg_write_read_test |
寄存器稳定性测试 | 非法地址读写,对控制寄存器的保留域进行读写,对状态寄存器进行写操作 | 通过写入和读出,确定寄存器的值是预期值,而不是紊乱值,同时非法寄存器操作也不能影响MCDF的整体功能 | mcdf_reg_illegal_access_test |
数据通道开关测试 | 对每一个数据通道对应的控制寄存器域en配置为0,在关闭状态下测试数据写入是否通过 | 在数据通道关闭情况下,数据无法写入,同时ready信号应该保持为低,表示不接收数据,但又能使得数据不被丢失,因此数据只会停留在数据通道端口 | mcdf_channel_disable_test |
优先级测试 | 将不同数据通道配置为相同或者不同的优先级,在数据通道使能的情况下进行测试 | 如果优先级相同,那么arbiter应该采取轮询机制从各个通道接收数据,如果优先级不同,那么arbiter应该先接收高优先级通道的数据,同时,最终所有的数据都应该从MCDF发送出来 | mcdf_arbiter_priority_test |
发包长度测试 | 将不同数据通道随机配置为各自的长度,在数据通道使能的情况下进行测试 | 从formatter发送出来的数据包长度应该同对应通道寄存器的配置值保持一—对应,同时数据也应该保持完整 | mcdf_formatter_length test |
下行从端低带宽测试 | 将MCDF下行数据接收端设置为小存储量,低带宽的类型,由此使得在由formatter发送出数据之后,下行从端有更多的机会延迟grant信号的置位,用来模拟真实场景 | 在req拉高之后,grant应该在至少两个时钟周期以后拉高,以此来模拟下行从端数据余量不足的情况。当这种激励时序发生10次之后,可以停止测试。 | mcdf formatter_grant_test |
测试代码根据后文链接,自行下载。
lab5
本次练习将认识如何定义覆盖率,如何从验证计划到测试用例的实现,最后再到覆盖率的量化。从本次练习,可以掌握验证量化的两种基本数据,即代码覆盖率和功能覆盖率。从这两种覆盖率,就可以掌握何时结束验证,确认验证的完备性。
lab5的代码是基于lab4的代码,主要修改的文件即mcdf_pkg.sv。在mcdf_pkg.sv中,已经为添加了一个关于MCDF的覆盖率模型mcdf_coverage,并且将它例化在顶层环境中。你可以阅读代码,了解mcdf_coverage的例化、虚接口的传递、覆盖率的定义和采样。同时,提供了一个新的test,即mcdf_full_random_test。这个test尽可能的将一些测试相关参数在仿真时进行了随机化。在接下来的仿真中,依然可以复用你之前lab4按照要求创建的几个test,将它们搬迁到lab5中。不过,lab5的要求是,需要最终达到“尽可能高的代码覆盖率和功能覆盖率”。所以结合本次练习最终的验收目标,可以复用你之前的测试用例。当然,在学习了覆盖率相关的课程之后,就懂得了,当最终覆盖率无法提升时,需要修改的约束,或者创建新的test。
- 通过等级:代码覆盖率大于90%,功能覆盖率大于90%。
- 优秀等级:代码覆盖率大于95%,功能覆盖率等于100%。
测试代码根据后文链接,自行下载。
文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。