一、实验要求
1,定义slave与MCDF之间的接口为chnl_intf,并在接口中定义时钟块消除可能存在的竞争问题
2,用类实现
激励产生器:chnl_generator
激励发送器:chnl_initiator;调用 chnl_iniaitor中的方法来完成接口的传递
发送数据的类:chnl_trans
3,用类实现测试 chnl_basic_test 实现三个 chnl_initiator 同时发送数据的要求,能结束测试;
chnl_burst_test 实现每个 chnl_initiator 的idle_cycles设置为0,同时发送 500 个数据,最后结束测试;
chnl_basic_test 实现在三个 channel 都拉低过 ready 时(不必要同时拉低, 先后拉低即可),可以立即结束测试。
4, 将各个类封装到package chnl_pkg 中
package chnl_pkg;
endpackge: chnl_pkg
二、代码分析
时间单位
`timescale 1ns/1ps// 单位/精度
1
信号
每个通道的数据通过实现的接口输入到dut中
logic clk;
logic rstn;
logic [31:0] mcdt_data;//DUT输出当前输出的数据
logic mcdt_val; //DUT输出当前输出的有效信号
logic [ 1:0] mcdt_id; //DUT输出当前输出的通道号
例化
将tb的信号与dut的端口相连
mcdt dut(
.clk_i (clk )
,.rstn_i (rstn )
,.ch0_data_i (chnl0_if.ch_data )
,.ch0_valid_i (chnl0_if.ch_valid )
,.ch0_ready_o (chnl0_if.ch_ready )
,.ch0_margin_o(chnl0_if.ch_margin )
,.ch1_data_i (chnl1_if.ch_data )
,.ch1_valid_i (chnl1_if.ch_valid )
,.ch1_ready_o (chnl1_if.ch_ready )
,.ch1_margin_o(chnl1_if.ch_margin )
,.ch2_data_i (chnl2_if.ch_data )
,.ch2_valid_i (chnl2_if.ch_valid )
,.ch2_ready_o (chnl2_if.ch_ready )
,.ch2_margin_o(chnl2_if.ch_margin )
,.mcdt_data_o (mcdt_data )
,.mcdt_val_o (mcdt_val )
,.mcdt_id_o (mcdt_id )
);
产生时钟
initial begin
clk <= 0;
forever begin
#5 clk <= !clk;
end
end
产生复位
initial begin
#10 rstn <= 0;
repeat(10) @(posedge clk);
rstn <= 1;
end
接口chnl_intf
slave与dut之间的接口,四个传输的数据;
定义clocking,所有和clocking有关的信号都与时序逻辑有关;在时钟上升沿前一秒采样输入信号,在时钟上升沿后一秒采样输出信号
interface chnl_intf(input clk, input rstn);
logic [31:0] ch_data;
logic ch_valid;
logic ch_ready;
logic [ 5:0] ch_margin;
clocking drv_ck @(posedge clk);
default input #1ns output #1ns;
output ch_data, ch_valid;
input ch_ready, ch_margin;
endclocking
endinterface
发生数据的类chnl_trans
数据;通道号;第几个数据
class chnl_trans;
int data;
int id;
int num;
endclass: chnl_trans
激励发送器chnl_initiator
调用 chnl_iniaitor 中的方法来完成接口的传递
输入:name;cycle;接口
通过接口号可以判断发送的通道号
接口一定要用virtual修饰
class chnl_iniaitor;
local string name;//激励发生器的名字
local int idle_cycles;//间隔几个cycle发送激励
local virtual chnl_intf intf;//接口
function new(string name = "chnl_initiator");//构造函数,初始化name和cycle
this.name = name;
this.idle_cycles = 1;
endfunction
function void set_name(string s);//外部可以修改name
this.name = s;
endfunction
function void set_idle_cycles(int n);//外部可以修改cycle
this.idle_cycles = n;
endfunction
function void set_interface(virtual chnl_intf intf)//外部将接口传入
if(intf == null)//传入为空指针,则报错,提醒需要实例化接口
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task chnl_write(input chnl_trans t);//写数据task;将数据写入接口
@(posedge intf.clk);//模拟时序逻辑
intf.drv_ck.ch_valid <= 1;
intf.drv_ck.ch_data <= t.data;
@(negedge intf.clk);//不然可能采到发送data之前的ready信号
wait(intf.ch_ready === 'b1);//模拟组合逻辑
$display("%t channel initiator [%s] sent data %x", $time, name, t.data);
repeat(this.idle_cycles) chnl_idle();//插入无效序列
endtask
task chnl_idle(input chnl_trans t)
@(posedge intf.clk);//模拟时序逻辑
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
endtask
endclass: chnl_iniaitor
激励产生器:chnl_generator
队列存储数据
获取该队列中数据的函数
输入:id用于得到不同通道的不同数据
class chnl_generator;
chnl_trans trans[$];//该实验没用该队列,应该把每次的激励序列存储起来
int num;
int id;
function new(int chnl);
this.id = chnl;
this.num = 0;
endfunction
function chnl_trans get_trans();
chnl_trans t = new();//创建对象
t.data = 'h00C0_0000 + (this.id << 16) + this.num;
t.id = this.id;
t.num = this.num;
this.num++;
this.trans.push_back(t);
return t;
endfunction
endclass: chnl_generator
数据发送chnl_agent:将chnl_generator与chnl_initiator封装在一起
利用chnl_initiator与chnl_generator发送多个数据
将接口传入chnl_initiator
run为运行函数,需要实例化后逐层调用
输入通道名称,通道id
class chnl_agent;
chnl_generator gen;
chnl_initiator init;
local int ntrans;//设置数据个数
virtual chnl_intf vif;
function new(string name = "chnl_agent", int id = 0, int ntrans = 1)
this.gen = new(id);
this.init = new(name);
this.ntrans = ntrans;
endfuntion
function void set_ntrans(int n);
this.ntrans = n;
endfunction
function void set_interface(virtual chnl_intf vif);
this.vif = vif;
init.set_interface(vif);
endfunction
task run();
repeat(this.ntrans) this.init.chnl_write(this.gen.get_trans());
this.gen.chnl_idle();// set idle after all data sent out
endtask
endclass: chnl_agent
测试基函数chnl_root_test
调用agent的run函数开始测试用例
将接口传入agent中
class chnl_root_test;
protected string name;
//声明与实例化三个agent
chnl_agent agent[3];
function new(int ntrans = 100, string name = "chnl_root_test");
foreach(agent[i]) begin
this.agent[i] = new($psprintf("chnl_agent%0d",i), i, ntrans);
end
this.name = name;
$display("%s instantiate objects", this.name);
endfunction
task run();
$display("%s started testing DUT", this.name);
fork//并行运行,发送数据
agent[0].run();
agent[1].run();
agent[2].run();
join
$display("%s waiting DUT transfering all of data", this.name);
fork//直到数据全部发送完成,fifo全为满
wait(agent[0].vif.ch_margin == 'h20);
wait(agent[1].vif.ch_margin == 'h20);
wait(agent[2].vif.ch_margin == 'h20);
join
$display("%s: 3 channel fifos have transferred all data", this.name);
$display("%s finished testing DUT", this.name);
endtask
function void set_interface(virtual chnl_intf ch0_vif, virtual chnl_intf ch1_vif, virtual chnl_intf ch2_vif);//将接口传入agent中
agent[0].set_interface(ch0_vif);
agent[1].set_interface(ch1_vif);
agent[2].set_interface(ch2_vif);
endfunction
endclass: chnl_root_test
chnl_basic_test
发送200数据,之间有随机个空闲状态,能结束测试
class chnl_basic_test extends chnl_root_test;
function new(int ntrans = 200, string name = "chnl_basic_test");
super.new(ntrans, name);
foreach(agent[i]) begin
this.agent[i].init.set_idle_cycles($urandom(1,3));
end
$display("%s configured objects", this.name);
endfunction
endclass: chnl_basic_test
chnl_burst_test
每个 chnl_initiator 的idle_cycles设置为0,同时发送 500 个数据, 最后结束测试;
class chnl_burst_test extends chnl_root_test;
function new(int ntrans = 500, string name = "chnl_burst_test");
super.new();
foreach(agent[i]) begin
this.agent[i].init.set_idle_cycles(0);
end
$display("%s configured objects", this.name);
endfunction
endclass: chnl_burst_test
chnl_fifo_full_test
三个 channel 都拉低过 ready (fifo为空) 时(不必要同时拉低,先后拉低即可), 可以立即结束测试 fork
join_none多线程
开辟子线程端口发送数据;主线程检测接口的fifo是否为空,三个都为空时关闭子线程,子线程不再发送数据;主线程端口发送无效数据,确保所有激励发送器处于idle状态;等待fifo为满结束测试
class chnl_fifo_full_test extends chnl_root_test;
function new(int ntrans = 1_000_000, string name = "chnl_fifo_full_test");
super.new(ntrans, name);
foreach(agent[i]) begin
this.agent[i].init.set_idle_cycles(0);
end
$display("%s configured objects", this.name);
endfunction
task run();
$display("%s started testing DUT", this.name);
fork: fork_all_run
agent[0].run();
agent[1].run();
agent[2].run();
join_none
$display("%s: 3 agents running now", this.name);
$display("%s: waiting 3 channel fifos to be full", this.name);
fork
wait(agent[0].vif.ch_margin == 0);
wait(agent[1].vif.ch_margin == 0);
wait(agent[2].vif.ch_margin == 0);
join
$display("%s: 3 channel fifos have reached full", this.name);
$display("%s: stop 3 agents running", this.name);
disable fork_all_run;//关闭发送数据的线程
$display("%s: set and ensure all agents' initiator are idle state", this.name);
fork
agent[0].init.chnl_idle();
agent[1].init.chnl_idle();
agent[2].init.chnl_idle();
join
$display("%s waiting DUT transfering all of data", this.name);
fork
wait(agent[0].vif.ch_margin == 'h20);
wait(agent[1].vif.ch_margin == 'h20);
wait(agent[2].vif.ch_margin == 'h20);
join
$display("%s: 3 channel fifos have transferred all data", this.name);
$display("%s finished testing DUT", this.name);
endtask
endclass: chnl_fifo_full_test
导包并实例化接口与测试类
接口中的.*是什么意思呢?
import chnl_pkg::*;
//实例化三个接口
chnl_intf chnl0_if(.*);
chnl_intf chnl1_if(.*);
chnl_intf chnl2_if(.*);
//实例化三个要运行的测试类
chnl_basic_test basic_test;
chnl_burst_test burst_test;
chnl_fifo_full_test fifo_full_test;
为激励发生器的对象指定接口句柄,开始测试
initial begin
basic_test = new();//创建测试对象
burst_test = new();
fifo_full_test = new();
basic_test.set_interface(chnl0_if, chnl1_if, chnl2_if);//将实例化的接口通过测试类逐级传入
burst_test.set_interface(chnl0_if, chnl1_if, chnl2_if);
fifo_full_test.set_interface(chnl0_if, chnl1_if, chnl2_if);
basic_test.run(); //逐级调用run()运行测试用例
burst_test.run();
fifo_full_test.run();
$display("*****************all of tests have been finished********************");
$finish();
end