目录
0.前言
在上一篇文章中介绍了该router的端口说明和协议,从这篇文章中我们正式开始做lab1,该lab的目标有以下2点:
1.创建interface连接DUT和TB
2.根据协议产生复位激励,初始化DUT
该lab搭建起来的组件及连接关系如下草图:
1.Interface代码
1.1声明接口
使用SV中的logic类型声明端口,logic可代表reg/wire型,这样就不用管各个端口信号相对于TB是输入还是输出,即不用管是input还是output,直接logic类型声明简单粗暴。
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;//所有DUT接口声明
endinterface
1.2声明clocking block
clocking block的主要作用是同步驱动和采样信号,同时声明信号相对于TB的方向,并声明输入/输出偏差,需要注意的是,若不声明,这里默认为:
default input #1step output #0;
路桑在一篇文章中( IC验证培训——实战SV验证学习(lab1)_路科验证-CSDN博客_sv验证 )表明:input #1ns 指的是采样时间相对时钟上升沿提前1ns,但不在波形上显示;用来模拟真实电路中的建立时间。output #1ns指的是驱动时间相对时钟上升沿推后1ns,会在波形上显示出来;用来模拟真实电路中的传播延时。
关于clocking block的更详细的介绍我会另写一篇文章深入讲解。
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;
clocking cb @(posedge clock);//用来同步采样和驱动信号,相对于TB方向
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
endinterface
1.3声明modport
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;
clocking cb @(posedge clock);
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
modport TB(clocking cb,output reset_n) ;//声明clocing用来同步采样和驱动信号,另外又在modport中单独声明了reset_n,表明reset_n可同步(cb中)可异步(modport中)
endinterface
2.test代码
该test实现的功能十分简单,即打印Hello World和按照router复位协议初始化DUT。该复位协议时序图如下,有如下几点关键点:
1.复位时,reset_n为低电平,frame_n和valid_n为高电平
2.有效复位至少保持1个clk
3.复位后至少等待15个时钟周期后才可以发送数据
按照协议写test如下,具体讲解在代码后注释说明:
program automatic test (router__io.TB rtr__io);//使用接口连接
initial begin
$display("Hello World!"); //打印字符串
$vcdpluson;
reset ();//调用复位函数
end
task reset();
router.reset_n=1'b0;//注意这里调用的是modport中的reset,是异步复位,用阻塞赋值即可,因此不用等待时钟沿;这里用异步复位的原因是为了在0时刻直接将reset拉低
router.cb.frame_n.<='b1; //按照协议将frame和valid拉高
router.cb.valid_n<='b1;
##2 router.cb.reset_n<=1'b1;//根据协议复位至少保持一个clk,这里等待2个时钟周期后把reset拉高
repeat(15) @(router.cb); //按照协议,复位后至少等待15个时钟周期后复位结束
endtask
endprogram
3.Test Harness 文件
顶层控制文件是为了将TB和interface和DUT连接起来,并生成全局时钟,具体讲解在代码后注释说明:
`timescale 1ns/lOOps//声明时间单位和精度
module router_test__top;
parameter simulation_cycle = 100;//声明时钟周期
bit SystemClock; //声明全局时钟变量
router_io top__io (SystemClock) ;//例化接口
test t (top_io); //例化TB并连接到接口上
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)
);//例化DUT并连接到接口上,由于DUT是按照verilog风格写的,所以不能像test一样直接连接接口,而是要通过信号映射的方式连接
initial begin
$timeformat(-9, 1, "ns", 10) ; //设置时间格式
SystemClock = 0;//0时刻时钟变量为0
forever begin
#(simula.tion__cycle/2) SystemClock = -SystemClock;//生成周期为100ns的时钟
end
end
endmodule
4.波形文件
由于default input #1ns output #1ns的作用,根据前述路桑的讲解,结合如下波形文件可见,TB驱动信号valid_n和frameo_n的输出偏差可见,比时钟上升沿50ns晚1ns;而TB的采样信号在波形上在时钟上升沿50ns处变化,但实际上是提前1ns采样,但在波形上确实没有反映出来。
配合代码看波形,首先是clock,以100ns为一个周期没有问题;再看reset_n,使用的是异步阻塞赋值,因此在0ns是0,##2表示在2个时钟上升沿后,采样cb中的同步reset_n非阻塞赋值,因此在150ns时拉高为1,但输出偏差延迟了1ns,所以在151ns拉高为1;再看驱动信号frame_n和valid_n,其使用cb中的信号非阻塞赋值,因此在0ns为x值,等到第一个时钟上升沿时同时变为1。