创建接口(interface)。
目录
一、interface概述
在systemverilog中引入了程序块,将testbench从逻辑和时间上与DUT分开。随着设计复杂度的增加,模块之间的连接变得更加复杂。testbench需要一种更高层次的方法和设计建立通信,避免由于端口连接造成的错误。
接口interface包含了连接、同步两个或者更多块之间的通信功能,所以使用接口连接设计块和测试平台。可以形象理解为在testbench中的连接interface只是一捆线,到了DUT当中再把这一捆线拆开,一个一个连接到DUT的各个端口上。
interface中并不能例化模块,但是可以例化其他interface。模块module可以例化interface,在interface中可以包括端口、任务task、函数function、过程块、程序块等,也可实现参数化。
在接口中使用modport可以将信号分组并直指定方向。
使用接口的优势:
(1)接口将所有的声明集中在一个地方,减少了出错的几率。接口便于设计重用,便于DUT和TB解耦,不会互相影响
(2)接口可以用来替代原来需要在模块或者程序中反复声明并且位于代码内部的一系列信号,减少了连接错误的可能性。
(3)要增加一个新的信号时,只需要在接口中声明一次,不需要在更高层的模块层声明,这进一步减少了错误。
(4)将接口中的一系列信号捆绑到一起很方便,也可以为信号指定方向以方便工具自动检查。
使用接口的劣势:
(1)针对点对点的连接,使用modport会跟使用信号列表的端口一样冗余复杂。
(2)必须同时使用信号名和接口名,可能会使模块变得更加冗长。
(3)如果要连接的两个模块使用的是一个不会被重用的专用协议,使用接口需要做比端口连线更多的工作。(不重复使用的话性价比低)
(4)连接两个不同的接口很困难。一个新的接口可能包含了现有接口的所有信号并新增了信号(地址、数据等等)。需要拆分出独立的信号并正确地驱动它们。(有时候会很繁琐)
另外在接口中很重要的就是时序的问题。DUT中的存储单元在时钟的有效沿锁存输入信号,数值由存储单元输出,然后通过逻辑块到达下一个存储单元。这里要求从上一个存储单元的输入到下一个存储单元的输入之间的延时必须小于一个时钟周期,所以需要在时钟沿之后驱动DUT的输入,之后在下一个时钟沿到来之前读取输出。也就是说:
测试平台应该在有效时钟边沿或者边沿之后驱动DUT,之后在下一个有效时钟边沿到达之前,尽可能晚地进行采样,并且需要满足时序协议。
但是如果测试平台在时钟边沿驱动DUT,就会存在竞争状态。又或者时钟不稳定出现,出现抖动或者偏斜,那么采样就有可能乱七八糟。添加很小的延时并不能从根本上解决这个问题,所以在SV当中引入了program block和timing rigion以解决这种冲突问题。
将时间片进行划分,active区域执行Verilog代码,observerd区域检查断言,reactive区域执行testbench代码,postponed区域进行采样。时钟块则同步了DUT和testbench。默认时序是在#1step延时之后对输入信号进行采样,在#0之后对输出信号进行驱动。#1step的规定保证了信号能处于postponed区域,在设计出现新的变化之前采样,能够在时钟改变之前捕获输出值。对于驱动来说,输出信号则是同步的,驱动会直接完成,所以在reactive区域会再次触发active区域出现一次loop back。
驱动的符号使用 "<=" ,至于它是否和非阻塞赋值一个意思不作讨论。不过因为有时钟的存在所以驱动总会是同步的,也就是如果没有赶上时钟有效沿,则需要等待下一次有效沿到来。总结来说就是:
SV中引入了时钟块(clocking block),采样过程的预期是采样前面,驱动过程的预期是驱动后面,时钟块存在的意义是为了保证这种预期,保证testbench在正确的时间点与DUT进行交互。
二、interface创建
interface一般都需要输入时钟和复位信号,当然也可以进行参数化。
上代码:
interface wr_interface #(parameter DSIZE = 8)(input wclk,input wrst_n);
logic [DSIZE-1:0] wdata;
logic winc;
logic wfull;
clocking drv_cb @(posedge wclk);
output wdata;
output winc;
input wfull;
endclocking
clocking mon_cb @(posedge wclk);
input wdata;
input winc;
input wfull;
endclocking
endinterface
这里规定了数据位宽为8,对于此FIFO因为信号比较少所以简单,统一用logic定义数据,使能和写满信号。这里就分了两个clocking block,一个给driver用于驱动,一个给monitor用于采样。对于driver来说,需要输出所驱动的数据和使能,采样写满信号,对于monitor则全部是输入。所在的时钟域都为写时钟域,对于时钟域的定义则要在top中创建好时钟后分配。
读时钟域一样:
interface rd_interface #(parameter DSIZE = 8)(input rclk,rrst_n);
logic [DSIZE-1:0] rdata;
logic rinc;
logic rempty;
clocking drv_cb @(posedge rclk);
output rinc;
input rdata;
input rempty;
endclocking
clocking mon_cb @(posedge rclk);
input rinc;
input rdata;
input rempty;
endclocking
endinterface:rd_interface
这里需要驱动的只有使能信号,其他全部为采样。
三、interface例化
例化一般在顶层top中完成,同时需要对各个端口进行连接。
上代码:
module top #(parameter DSIZE = 8,parameter ASIZE = 4);
logic wclk;
logic wrst_n;
logic [DSIZE-1:0] wdata;
logic winc;
logic wfull;
logic rclk;
logic rrst_n;
logic [DSIZE-1:0] rdata;
logic rinc;
logic rempty;
int WCLK_PERIOD;
int RCLK_PERIOD;
int SAME_PERIOD;
fifo1 #(.DSIZE(DSIZE),
.ASIZE(ASIZE)
)
U_(
.wclk(wclk),
.wrst_n(wrst_n),
.wdata(wdata),
.winc(winc),
.wfull(wfull),
.rclk(rclk),
.rrst_n(rrst_n),
.rdata(rdata),
.rinc(rinc),
.rempty(rempty)
);
wr_interface#(.DSIZE(DSIZE)) wr_drv_if(wclk,wrst_n);
wr_interface#(.DSIZE(DSIZE)) wr_mon_if(wclk,wrst_n);
rd_interface#(.DSIZE(DSIZE)) rd_drv_if(rclk,rrst_n);
rd_interface#(.DSIZE(DSIZE)) rd_mon_if(rclk,rrst_n);
// WCLK driver interface
assign winc = wr_drv_if.winc;
assign wdata = wr_drv_if.wdata;
assign wr_drv_if.wfull = wfull;
// WCLK monitor interface
assign wr_mon_if.winc = winc;
assign wr_mon_if.wdata = wdata;
assign wr_mon_if.wfull = wfull;
// RCLK driver interface
assign rinc = rd_drv_if.rinc;
assign rd_drv_if.rdata = rdata;
assign rd_drv_if.rempty = rempty;
// RCLK monitor interface
assign rd_mon_if.rinc = rinc;
assign rd_mon_if.rdata = rdata;
assign rd_mon_if.rempty = rempty;
...
进行例化的时候对两个interface分配了不同的时钟和复位信号。对于端口的赋值也可以在对DUT信号例化的时候直接完成,不过这种方法局限性大,所以并不推荐。为了数量众多的端口信号以及传输方向的清晰明了,使用assign来赋值显然会更好。
arb_port u_arb_port(
.rst(arbif.rst),
.clk(arbif.clk),
.grant(arbif.grant),
.grant_valid(arbif.grant_valid),
.request(arbif.request)
);
总结
systemverilog中引入了程序块将testbench从逻辑和时间上与DUT分开,随着设计复杂度的增加,模块之间的连接变得更加复杂。testbench需要一种更高层次的方法和设计建立通信,避免由于端口连接造成的错误,这个时候就引入了接口。
在接口当中时钟块非常重要,可以总结为:
SV中引入了时钟块(clocking block),采样过程的预期是采样前面,驱动过程的预期是驱动后面,时钟块存在的意义是为了保证这种预期,保证testbench在正确的时间点与DUT进行交互。