SystemVerilog验证教程(二)--SystemVerilog Interface 和 Timing Region

转载请标明出处:
原文发布于:浅尝辄止,未尝不可的博客
https://blog.csdn.net/qq_31019565

SystemVerilog验证教程(二)–SystemVerilog Interface 和 Timing Region

一、Interface

我们在做验证平台的时候呢,需要将我们的验证平台和DUT进行一个连接。在Verilog环境下,会有两个module进行连接。但是Verilog有很大的冗余和局限性,因此就产生了SV interface。首先,我们可以看一下,下面的这个环境。我们在搭建环境的时候,第一步就应该考虑,要验证的DUT和TestBench如何进行连接。SystemVerilog验证教程(二)--SystemVerilog Interface 和 Timing Region_第1张图片

Verilog形式的两个module的连接

SystemVerilog验证教程(二)--SystemVerilog Interface 和 Timing Region_第2张图片
通过一个简易的Verilog连接图可以看到,在传统的module连接情况下,需要定义两个module port。然后再通过一个顶层的文件,将连个module进行例化,然后进行连接。

module driver(
		output driver_out1,
		output driver_out2
);
...
endmodule

module slave(
		input slave_in1,
		input slave_in2
);
...
endmodule

module top(
		wire driver_to_slave1,
		wire driver_to_slave2
);
driver driver_instance(
		.driver_out1(driver_to_slave1),
		.driver_out2(driver_to_slave2)
);
driver slave_instance(
		.slave_in1(driver_to_slave1),
		.slave_in2(driver_to_slave2)
);
endmodule

SystemVerilog的连接

.*:当所有接口的名字和对应的连线的名字相同的时候,可以使用此符号代替繁杂的接口信号连接描述。
.name:当某个接口和某个连线的名字一样的时候可以使用此表示方法。

举例:

driver_instance(.req(req),.ack(ack));
#上面的可以简略表示为
driver_instance(.*);
#如果使用.name 可以表示为
driver_instance(.req,.ack);

从上面的Verilog的例子里面,我们的直观感受是,代码太过于冗余笨拙,每次都是要定义两个port module。然后还需要定义一个顶层的。并且需要重复写信号名。但是现在我需要将一个信号的名字进行改名,这时候劣势就出来了,需要对所有涉及到这个信号的module进行名字更正。这样就显得非常的笨拙。第二点,对于一些大型的设计。会有非常多的信号,然后呢,我这时候需要对一组信号进行修正操作,这时候问题就来了,冗杂的工作量,带来徒增的时间浪费。

在这种情境下,产生了SV的interface出现了:
我们先看一个简图,这个图概述了TestBench和DUT之间的关系,这相当于把SV信号打包看做一个整体,一个完整的Interface接口,在DUT和TestBench之间。并且Interface有自己的组成结构,不像Verilog那样只是一根一根的连线。
在这里插入图片描述
下面来看一下简单的实例:

# 首先按照interface的语法定义一个接口
interface  driver_slave_if(input bit clk);
	logic  [2:0] apple, pear;
	logic  banana;
endinterface 

# 模块Top层
module top;
	bit clk;
	always  #10 clk = ~clk;
	driver_slave_if  if_instance(clk);
	driver(if_instance);
	slave(if_instance);
endmodule:top

从这个例子里面可以看出,Interface可以作为一个整体打包传递,与Verilog相比带来了很大的便利性。当然,Verilog和SV的方式可以互相结合使用。

下面再讲几个知识点:

第一个是modport,它是module port的缩写,在一个接口里面可以包含很多的modport。是用来声明信号方向的,还是直接上例子。

#在interface 中定义modport,可以定义无数个。
interface driver_slave_if(input bit clk);
	logic req1,req2;
	logic [2:0] ack;
	modport driver(output req1,output req2,input ack,clk);
	modport slave(input req1,input req2,input clk, output ack);
endinterface
#定义结束modport之后,可以在对应的module里面使用
module  driver( driver_slave_if.driver driver_if);
#还可以在TB中使用
module _tb(driver_slave_if.slave slave_if);

小结:

  1. interface 是SV中独有的东西,可以解决在原来Verilog接口中存在的定义繁杂笨拙的缺点。
  2. interface 可以定义很多modport,可以作为一个modport接口,modport增加了不同的输入输出或者双向端口的属性。

二、Timing Region

在TestBench驱动激励到DUT的时候,会考虑在什么时间点儿进行激励驱动才会有效,并且尽可能的避免竞争现象。如果驱动DUT太晚的话,那么DUT可能在这个cycle就拿不到这个值,如果sample太早了,那么也可能拿到的这个值不是太合理的值。

来举个例子,画一个时序图,先看第一个图,当时钟上升沿到来的时候,write 信号值处于一个不确定的状态,这个时候,没有办法确定是否进行写信号。同时data和address的值也是不确定的状态。在实际的工作中,我们要避免这种状态。SystemVerilog验证教程(二)--SystemVerilog Interface 和 Timing Region_第3张图片
而我们比较理想的状态则是下图所示的一种状况 ,当在我上升沿来临的时候,write信号已经起来了,并且,相对应的地址信号和地址数据已经是一个确切的值。同时避免了真正的竞争冒险现象。

SystemVerilog验证教程(二)--SystemVerilog Interface 和 Timing Region_第4张图片

这里做一个简单的小结,对于Timing的这个问题呢,是指的是测试平台和DUT之间时序的一个相互关系。在驱动和采样输出结果的时候,我们需要明确一个相对固定的关系,从而才能很好的完成对DUT的验证工作。从上面的两张图片的对比可以看出,我们在进行DUT信号驱动或者是采样的时候,时钟上升沿和驱动信号是有一个明确的关系的。我们需要在时钟上升沿到来之前就要对DUT进行信号的驱动。而对于信号的采样,则是需要在时钟边沿之后进行。

为了更方便的控制时序关系,我们需要在interface中引入一个新的概念,那就是clocking blocks。我们一个接口里面可以包含多个cloking block。它里面的信号要么是去驱动DUT的,要么是对DUT的输出信号进行采样的。不管是输入和输出的,都是根据时钟进行同步。最终的目的也是实现正确的时钟逻辑。clocking block是只能用在TestBench里面的。

default input #1step output #0;
我们着重看一下这句话,这是用在TestBench里面的,input #1 是延后了一个时钟单位输入。意思就是当前TestBench上升沿采集的信号是上升沿#1时钟单位之前的信号值。而output #0 则是信号在上升沿不做任何延迟直接驱动出去。

#在这里要具体举个例子,这里的时序是可以自己根据需求进行改变的。
clocking driver_cb  @(posedge clk);
#在这里要特别注意的是default的情况
#  一个新的符号是 ##3表示等3个clocking block的时钟有效边沿。
    default input #1step output #0;// default case
	input #3 banana;
	output #4 apple;
endblocking
# 这里再介绍一个之前没有说过的用法,modport包含clocking block
modport driver (clockblock driver_cb);
#这里是可以这样用的,也是以后的主要用法。

clocking block对Timing具有重要的作用,它可以将信号同步到有效的边沿上,同时也可以在任意沿对信号进行同步。对于clocking block里面的数值需要用<=进行赋值。

Timing Region的部分呢,我在第一篇博客有详细写过自己得理解。可以参考下面的链接。
Verilog的时序问题和SystemVerilog TestBench激励时序(https://blog.csdn.net/qq_31019565/article/details/86241312)

接下来介绍一个用在SystemVerilog中的program block的概念。只要是指的是TestBench中的代码,一个program本身是和module是类似,可以例化在module里面的。但是program本身不可以例化module。下面写一个具体的示例来展示一下:

#我们构建bench就是写program代码。
program  tb_test( driver_slave_if vif);
	initial begin
		@vif.driver_cb;
		repeat (5)  vif.driver_cb;
	end
endprogram

program的好处是可以把 TestBench和DUT的代码分开,也包括时序上面,两遍之间可以分得比较清楚,减少时序的竞争冒险。算是bench开始执行的入口。可以在program封装一些运行所需要的数据。

最后看一个整体的TestBench的顶层

module top;
	bit clk;                 # 注意clk 一定需要在顶层产生。不能放在program中产生。
	driver_slave_if if(.*);  # 例化interface
	tb_test test(.*);        # 例化bench
	dut_d  dut(.*);          # 例化DUT
    always #5 clk = ~clk ;
endmodule

你可能感兴趣的:(学习笔记)