该系列实验分为6大部分,每部分都会尽可能地还原我在每个实验中的思路与步骤,旨在记录我在学习SV验证道路上的点点滴滴,同时也希望能与众多志同道合的兄弟们多多交流在学习验证过程中遇到的问题,共同进步,大家加油。
在搭建"测试平台(Testbench)"之前,首先要先确定拿到手项目验证的透明度。
可以按照激励的生成方式和检查的功能点分布将验证划分为三种基本方式:
黑盒验证
如果验证人员对DUT(Device Under Tset—待验证设计)的内部逻辑缺乏了解,黑盒验证无疑是一种合适的方式,因为验证环境只需要将激励给入设计的外部输入接口,检查设计的输出足矣,其判断的标准为根据一个输入是否能够得到一个正确的输出即可,而测试平台本身不会去关注设计内部具体的逻辑细节。但由此引发而来的问题包括:1.若测试失败则无法直接定位问题来源 验证人员只能判断测试结果是否成功,无法进一步定位到缺陷所在的位置进而与设计师不能很好的协作 2.难以发现一些较深的缺陷 验证人员无法通过设计代码给出更苛刻的随机约束从而定向地生成一些激励,而且这对后面的功能覆盖率收集也造成了不小的麻烦。
白盒验证
验证人员同设计人员一样,对设计内部的逻辑、信号等十分了解,测试失败时能够逐级追踪信号从而准确定位问题,另一方面也能结合设计需求文档检查设计内部的完整性。但白盒验证往往容易1.忽略设计整体的功能测试若甲方提供的设计需求文档描述的有缺陷,我们较难发现设计本身违规的错误 2.设计发现版本更新时,测试平台的维护较麻烦 白盒验证的测试用例通常是从设计内部细节提取的,例如某寄存器在某些条件下处于某些数值之类。这对于后期接手的工程师来说无疑是巨大麻烦,因此白盒测试的低复用性的缺点就显现出来了。
灰盒验证
个人认为,灰盒验证是目前验证领域使用最多的一种验证方式(黑盒与白盒的结合),也是笔者在入行以来唯一正在使用的验证方式,此种验证方式不仅能够弥补白盒测试对于整体功能验证的较低的把握程度,而且也能够将设计里的重点内部逻辑进行着重测试,yyds!
就拿目前Synopsys公司的这个小实验来说,旨熟悉基于SV的整套验证流程,因此采用黑盒验证的方式
什么是测试平台?
1. 对DUT创建测试序列
2. 观察DUT的输入输出
3. 对DUT的输出数据与预期数据进行对比
4. 报告检查结果(对比相同PASS,否则FAIL)
Stimulator(激励发生器):它也被称为drive(驱动器),主要负责模拟与DUT的接口协议。其可分为两部分:1)initiator 主动发起接口数据传输;2)responder 对接口的数据发送请求做出响应,而它本身不会主动发送数据。
Monitor(监视器):观察DUT的边界或者内部信号,并且经过打包整理传送给其他验证平台的组件。
Checker(比较器):缓存各个monitor收集到的数据,通过数据比较的方法,检查实际收集到的DUT输出端接口数据是否同期望数据保持一致。
如下图所示,其整体验证结构同样也是基于通用验证结构搭建而成:
Stimulator(激励发生器) 对应Configure、Generator、Transactor、Driver四部分;
Monitor(监视器) 对应Monitor;
Checker(比较器) 对应Self Check。
搞清楚这些之后,接下来会对其中每个部件进行详细说明,首先我们先从interface、Test program、Top level harness file三大部分进行搭建。
该设计实现的具体功能为:某个具有16输入与16输出功能的路由器,其中任意输入可以对应任意输出。由于该实验我们采用的方法为黑盒测试,因此原设计的具体代码不做粘贴,只需知道输入输出端口即可。
创建一个名为router_io.sv的文件,以下为该文件中所有代码,这部分代码也可称为接口(interface)部分的代码:
`timescale 1ns/100ps
//---one--
interface router_io(input bit clock);
logic reset_n ;
logic [15:0] din ;
logic [15:0] frame_n ;
logic [15:0] valid_n ;
logic [15:0] dout ;
logic [15:0] valido_n;
logic [15:0] busy_n ;
logic [15:0] frameo_n
//---two---
clocking cb @(posedge clock);
//input #1ns:采样时间相对时钟上升沿提前1ns
//output #1ns:驱动时间相对时钟上升沿推后1ns
default input #1ns output #1ns;
output reset_n ;
output din ;
output frame_n ;
output valid_n ;
input dout ;
input valido_n;
input busy_n ;
input frameo_n;
endclocking: cb
//---three---
modport TB(clocking cb,output reset_n);
endinterface: router_io
代码分析: one: 该部分代码中描述了该设计与外部连接的所有接口,该部分接口都是异步且没有方向的,之所以使用“logic”(四值逻辑)而非“bit”(二值逻辑)来定义信号的数据类型,是因为在SV中规定跟硬件所有连接的信号都应该为四值逻辑,我们可以直接使用logic来达到通用的存储硬件数据的目的。two: 我们需声明一个由时钟上升沿所驱动的时钟模块,简称cb。目的是采样和驱动DUT的信号,其中接口的方向应在此处被说明,需注意此时信号的方向应与DUT设计中定义的信号方向相反(若din信号相对于DUT为输入,则相对于时钟模块应为输出)。default input #1ns output #1ns 此句根据具体情况增添或删除,代表含义已注释在代码块中。three: 需创建modport用来连接测试代码,在它的参数列表中,应该引用之前创建的时钟模块与所有可能会用到的异步信号。
创建一个名为test.sv的文件,在这个文件中,声明一个带有参数的测试程序块,它连接到接口中的modport TB:
`timescale 1ns/100ps
program automatic test(router_io.TB router_io);
endprogram: test
代码分析: program中所有模块若要做驱动,必须经过modport,并且定义一个插口router_io。
个人拙见:时钟信号与复位信号是采样/驱动DUT的前提。在该文件中,需先将DUT的输入信号在复位阶段的状态进行重置,因此输入信号相对于复位与时钟之间的时序关系显得尤为重要,提供的时序图如下所示:
从上图可以看出:复位信号reset_n与frame_n信号均是相对与时钟的上升沿进行电平跳转,因此必须连接至由时钟上升沿所驱动的时钟模块(cb)中;复位信号reset_n与frame_n在第二个时钟上升沿时,分别为0与1,经2ns后,复位信号reset_n由低电平“0”跳转到高电平“1”;并在复位信号reset_n之后的15个时钟周期frame_n信号由高电平“1”跳转到低电平“0”。由于valid_n未在上图绘制出,先暂且认为valid_n复位前后始终为高电平“1”。代码如下:
`timescale 1ns/100ps
program automatic test(router_io.TB router_io);
initial begin
//----创建一个vcd+dump文件来产生可见波形
// $vcdpluson;
reset();
end
task reset();
router_io.reset_n = 1'b0 ; //异步
router_io.cb.frame_n <= '1 ; //同步
router_io.cb.valid_n <= '1 ; //同步
#2 router_io.cb.reset_n <= 1'b1 ; //同步
repeat(15) @(router_io.cb);
router_io.cb.frame_n <= 1'b0 ;
endtask: reset
endprogram: test
创建一个名为router_test_top.sv的文件,其作用是将DUT接口实例化与验证环境相连接,同时生成DUT所需时钟,代码如下:
`timescale 1ns/100ps
//---one--
`include "router_io.sv"
module router_test_top;
//---two--
parameter simulation_cycle = 100;
bit SystemClock;
//---three---
test t(top_io);
//---four---
initial begin
$timeformat(-9, 1, "ns", 10)
SystemClock = 0;
forever begin
#(simulation_cycle/2)
SystemClock = ~SystemClock;
end
end
//---five---
router_io top_io(SystemClock);
//---six---
router dut(
.reset_n (top_io.reset_n),
.clock (top_io.clock),
.din (top_io.din),
.frame_n (top_io.frame_n),
.valid_n (top_io.valid_n),
.dout (top_io.dout),
.valido_n (top_io.valido_n),
.busy_n (top_io.busy_n),
.frameo_n (top_io.frameo_n)
);
endmodule
代码分析: one: router_io.sv中所有的内容放到该文件中 two: 需定义系统时钟周期与系统时钟信号 three: 为了使test.sv与 router_test_top.sv 连接起来,我们需将test.sv在router_test_top.sv 中的例化t与router_io.sv(接口)在router_test_top.sv 中的例化top_io相连接 four: 生成DUT所需时钟SystemClock
Tip:($timeformat( Units, Precision, Suffix, MinFieldWidth )
1.Unit 是 0 到-15 之间的整数值,表示打印的时间的单位:0 表示秒,-3 表示毫秒,-6 表示微秒。-9 表示毫微秒; -12 表示微微秒; -15 表示毫微微秒;中间值也可以使用:例如-10表示100ps单位。
2.Precision 是在小数点后面要打印的小数位数。
3.Suffix 是在时间值后面打印的一个字符串。
4.MinFieldWidth 是打印的最小数量字符,包括前面的空格。如果要求更多字符,那么打印的字符更多。
five: 实例化系统时钟 six: 将DUT与验证环境的顶层连接起来(例化)。
至此,整个测试平台的结构已经初步形成,通过接下来一步步的添砖加瓦与不断完善,最后,将会构建起一个基于SV的完整验证平台,大家继续努力,针对仿真(QuestaSim)部分,后续会继续更新,感谢大家支持。