对mcdf这个验证项目,从顶层环境来看一共有:
第一个子模块是channel=node+fifo,按我自己的理解是接收apb总线从端写入过来的数据。
这里的问题是,mcdf的输入端数据的来源是?
要写chnl_pkg,整体的结构是怎么样的?
子模块的顶层环境是agent,agent包含了driver,sequencer,monitor,driver和seqr之间的通信通过两个组件自带的tlm端口实现。通过这个sequencer的item是chnl_trans,在sequence中产生。
其中,chnl_trans需要传递的包,包含的成员变量包括:数据本身,通道的id(有4个node),发包的间隔,数据之间的间隔,包与包之间的间隔,最后是发送完毕的单比特标志位rsp。
正文开始,按照自己的理解敲出来代码,然后再修正。
chnl_pkg
第一版本如下,一个class写完就订正吧。
//要写的是chnl_pkg,所以先写大标题
package chnl_pkg;
//需要用到uvm,两句话少不了
import uvm_pkg::*;
`includ "uvm_macro.svh"
//正片开始,从小写到大,从item开始写
//chnl_trans继承于uvm_sequence_item类
class chnl_trans extends uvm_sequence_item;
//声明随机变量,也就是包的成员变量,按照刚才说的
rand int [31:0] data[];//数据个数不确定,用动态数组,数据是32位的
rand int [ 1:0] chnl_id;//一共4个通道
rand int data_idle;//间隔的位数要用多少?怎么确定?
rand int pkg_idle;//同理
bit rsp;//标志位不用随机变量
//做约束,也就是初始化
constraint trans_cnst {
//data的随机化按chnl_id左移8位+id先
soft data[i]=chnl_id<<8 + chnl_id;
soft chnl_id=2'd0;
soft data_idle inside {0:4};
soft pkg_idle inside {0:10};
}
//然后做注册和域的自动化
`uvm_utils_begin(chnl_trans)
`uvm_field_array_int(data[i], UVM_ALL_ON)
`uvm_field_int(chnl_id, UVM_ALL_ON)
`uvm_field_int(data_idle, UVM_ALL_ON)
`uvm_field_int(pkg_idle, UVM_ALL_ON)
`uvm_utils_end(chnl_trans)
//然后是每个类都需要的new函数,item继承于trans事务类,又继承于object类
function void new (string "name");
super.new("chnl_trans");
endfunction
endclass: chnl_trans
uvm两句话,少了个s
import uvm_pkg::*;
`include "uvm_macros.svh"
声明随机变量时只有data有位宽,其他都不用
成员变量还有包的id
随机变量的约束,动态数组限制size在4-32,用等式==而不是赋值=;数据生成用foreach,规律是'hC0000000+数据左移24位+包id左移8位+i;约束的花括号最后要加分号;
域的自动化,动态数组只写data,rsp也需要写到,
new函数的写法,参数是string name="chnl_trans",super的参数写name即可
第二个版本
package chnl_pkg;
import uvm_pkg::*;
`includ "uvm_macros.svh"
class chnl_trans extends uvm_sequence_item;
rand int [31:0] data[];
rand int chnl_id;
rand int pkg_id;
rand int data_idle;
rand int pkg_idle;
bit rsp;
constraint trans_cnst {
soft data.size() inside {4:32};
foreach(data[i]) data[i]=='hC0000000+(chnl_id<<24)+(pkt_id<<8)+i;
soft chnl_id==0;
soft pkg_id==0;
soft data_idle inside {0:2};
soft pkg_idle inside {1:10};//发包时序,有印象是不能连续发包
};//注意分号
`uvm_object_utils_begin(chnl_trans)
`uvm_field_array_int(data, UVM_ALL_ON)
`uvm_field_int (chnl_id, UVM_ALL_ON)
`uvm_field_int ( pkg_id, UVM_ALL_ON)
`uvm_field_int (data_idle, UVM_ALL_ON)
`uvm_field_int (pkg_idle, UVM_ALL_ON)
`uvm_filed_int (rsp, UVM_ALL_ON)
`uvm_object_utils_end
function void new (string name="chnl_trans");
super.new(name);
endfunction
endclass: chnl_trans
错误如下:
pkt_id的类型是int而不是bit,手误了;
约束中data.size不用加括号;
约束中foreach后data要加soft;
data约束是两个id相加不是加自身data,并且两个id要加上this前缀表示在这个class中的;
约束中两个inside后面要加{},里面才是范围[1:10]
约束的花括号最后要加分号;
注册的宏是`uvm_object_utils_begin(chnl_trans)和`uvm_object_utils_end;
new函数没有void;
总结:这里data其实是payload。一个包可以是数量不同、间隔不同的数据组成的
chnl_driver
接下来是driver ,负责从sequencer中拿到刚才写的item,解析,设置好通道和包的id后返回给sequencer一个rsp并点亮rsp,通信手段是自带的seq_item_port和export端口。
第一个版本
class chnl_driver extends uvm_driver #(chnl_trans);
chnl_trans req,rsp;
`uvm_component_utils(chnl_driver)
function new (string name="chnl_driver", uvm_parent parent)
super.new(name, parent);
endfunction
task run();
fork
get_and_drive();
join_none
endfunction
function ? get_and_drive(req);
seq_item_port.get_next_item(req);
this.chnl_write(req);
function chnl_write(req)
endfunction
要用到接口,声明,加上local和virtual;作为函数set_interface的参数,不用配置语句;
检查接口句柄若为空则报错,否则将参数接口给到当前类的接口
接口的代码看看,数据,奇偶校验位,valid和wait信号,奇偶校验位错误信号,共5个
interface chnl_intf(input clk, input rstn);
logic [31:0] ch_data;
logic ch_data_p;
logic ch_valid;
logic ch_wait;
logic ch_parity_err;
clocking drv_ck @(posedge clk);
default input #1ps output #1ps;
output ch_data, ch_valid, ch_data_p;
input ch_wait, ch_parity_err;
endclocking
clocking mon_ck @(posedge clk);
default input #1ps output #1ps;
input ch_data, ch_valid, ch_data_p, ch_wait, ch_parity_err;
endclocking
endinterface
run_phase中两个线程,drive和reset;
run_phase的参数是uvm_phase phase;
第二个版本
class chnl_driver extends uvm_driver #(chnl_trans);
local virtual chnl_intf intf;
`uvm_component_utils(chnl_driver)
function new (string name="chnl_driver", uvm_component parent)
super.new(name, parent);
endfunction
function void set_interface(virtual chnl_intf intf);
if( intf==null) $error("interface handle is null!");
else this.intf=intf;
endfunction
task run_phase(uvm_phase phase);
fork
do_drive();
do_reset();
join
endtask
task do_drive();
chnl_trans req, rsp;
seq_item_port.get_next_item(req);
this.chnl_write(req);
$cast(rsp, req.clone());
rsp.rsp=1;
endtask
task chnl_write(input chnl_trans t);
endtask
task do_reset();
endtask
endclass: chnl_driver
do_drive要用rstn上升沿触发,且forever运行着
点亮rsp后要获取seq的id,最后要item_done送回rsp告知seq
复位任务复位的是什么成员变量/信号?(chnl_if的三个输出信号:valid、data、data_p在复位信号拉低后全部置0)
chnl_write需要做什么事情?(对每个data[i]在clk上升沿将三个信号赋值给时钟块的对应信号,在下降沿等待wait为0,完成传输,然后等待data的间隔,最后每个包发完等待每个pkg的间隔,等待的时候三个信号拉低)
获取奇偶校验位的函数是对32位数据进行缩位异或后return
句柄类型转换需要加'void?
【SV】$cast的用法_lbt_dvshare的博客-CSDN博客_sv中cast函数
第三个版本
class chnl_driver extends uvm_driver #(chnl_trans);
local virtual chnl_intf intf;
`uvm_component_utils(chnl_driver)
function new (string name="chnl_driver", uvm_component parent)
super.new(name, parent);
endfunction
function void set_interface(virtual chnl_intf intf);
if( intf==null) $error("interface handle is null!");
else this.intf=intf;
endfunction
task run_phase(uvm_phase phase);
fork
do_drive();
do_reset();
join
endtask
task do_drive();
chnl_trans req, rsp;
@(posedge intf.drv_ck.rstn)
forever begin
seq_item_port.get_next_item(req);
this.chnl_write(req);
'void($cast(rsp, req.clone));
rsp.rsp=1;
rsp.set_sequence_id(req.get_seq_id(req));
seq_item_port.item_done(rsp);
end
endtask
task chnl_write(input chnl_trans t);
forever begin
foreach(t.data[i])
@(posedge intf.clk);
drv_ck.ch_valid<=1;
drv_ck.ch_data<=t.data[i];
drv_ck.ch_data_p<=get_parity(t.data[i]);
@(negedge intf.clk);
wait(intf.drv_ck.ch_wait==0);
repeat(t.data_idle) do_idle();
repeat(t.pkg_idle) do_idle();
end
endtask
function get_parity(bit [31:0] data);
return ^data;
endfunction
task do_idle();
@(posedge intf.clk);
forever begin
drv_ck.ch_valid<=0;
drv_ck.ch_data<=0;
drv_ck.ch_data_p<=0;
end
endtask
//第二个线程
task do_reset();
@(negedge intf.rstn);
forever begin
drv_ck.ch_valid<=0;
drv_ck.ch_data<=0;
drv_ck.ch_data_p<=0;
end
endtask
endclass: chnl_driver
run_phase的两个线程要加this前缀;
复位任务的forever应该比触发条件早;
rstn不需要加时钟块;
克隆函数要加括号;
句柄转换void'
req.get_sequence_id不需要参数;
chnl_write不需要forever,foreach要加begin,ch_wait不需要时钟块且要===全等于0
第四个版本
class chnl_driver extends uvm_driver #(chnl_trans);
local virtual chnl_intf intf;
`uvm_component_utils(chnl_driver)
function new (string name="chnl_driver", uvm_component parent)
super.new(name, parent);
endfunction
function void set_interface(virtual chnl_intf intf);
if( intf==null) $error("interface handle is null!");
else this.intf=intf;
endfunction
task run_phase(uvm_phase phase);
fork
this.do_drive();
this.do_reset();
join
endtask
task do_drive();
chnl_trans req, rsp;
@(posedge intf.rstn)
forever begin
seq_item_port.get_next_item(req);
this.chnl_write(req);
void'($cast(rsp, req.clone()));
rsp.rsp=1;
rsp.set_sequence_id(req.get_seq_id());
seq_item_port.item_done(rsp);
end
endtask
task chnl_write(input chnl_trans t);
foreach(t.data[i]) begin
@(posedge intf.clk);
drv_ck.ch_valid<=1;
drv_ck.ch_data<=t.data[i];
drv_ck.ch_data_p<=get_parity(t.data[i]);
@(negedge intf.clk);
wait(intf.ch_wait===0);
repeat(t.data_idle) do_idle();
repeat(t.pkg_idle) do_idle();
end
endtask
function get_parity(bit [31:0] data);
return ^data;
endfunction
task do_idle();
@(posedge intf.clk);
forever begin
drv_ck.ch_valid<=0;
drv_ck.ch_data<=0;
drv_ck.ch_data_p<=0;
end
endtask
//第二个线程
task do_reset();
forever begin
@(negedge intf.rstn);
drv_ck.ch_valid<=0;
drv_ck.ch_data<=0;
drv_ck.ch_data_p<=0;
end
endtask
endclass: chnl_driver
chnl_sequencer
class chnl_sequencer extends uvm_sequencer #(chnl_trans);
`uvm_component_utils(chnl_sequencer)
function new(string name="chnl_sequencer", uvm_component parent);
super.new(name, parent);
endfunction
endclass: chnl_sequencer
sequencer只需要注册和new函数,也是个参数类,下一个。
chnl_sequence
sequence需要发送item,要声明sequencer,需要随机化到的成员变量也要重新声明(不论在不在item中)
第一个版本
class chnl_sequence extends uvm_sequence #(chnl_trans);
rand int data_idle;
rand int pkg_idle;
rand int chnl_id;
rand int pkg_id;
rand int ntrans;
rand int data[];
constraint cstr{
};
`uvm_declare(chnl_sequencer)
function new (string name="chnl_sequence");
super.new(name);
endfunction
task body();
repeat(ntrans) send_trans();
endtask
task send_trans();
chnl_trans req, rsp;
`uvm_do_with{req, }
endtask
endclass: chnl_sequence
约束怎么写?(记得soft,local的变量如果大于等于0就赋值给对应变量,用条件约束的形式)
uvm_do_with中的约束怎么写?
my_sequencer的宏怎么写?(uvm_declare_p_sequencer(sequencer))
成员变量有哪些?(channel和pkg的id以及间隔,payload的数量,声明的时候赋值-1,)
有变量就要有域的自动化;
sequence从driver获得rsp视为握手结束标志;
断言检查rsp中的单比特rsp位是否拉高,否就报错;
随机化之后打印信息的函数;
第二个版本
class chnl_sequence extends uvm_sequence #(chnl_trans);
rand int data_idle=-1;
rand int pkg_idle=-1;
rand int chnl_id=-1;
rand int pkg_id=0;
rand int ntrans=10;
rand int data[];
rand int data_size=-1;
constraint cstr{
soft data_idle==-1;
soft pkg_idle==-1;
soft chnl_id==-1;
soft pkg_id==0;//为什么这个就是0
soft n_trans==10;
soft data_size==data.size();
foreach(data[i]) soft data[i]==0;
};
`uvm_object_utils_begin(chnl_sequence)
`uvm_field_int(data_idle, UVM_ALL_ON)
`uvm_field_int( pkg_idle, UVM_ALL_ON)
`uvm_field_int(chnl_id, UVM_ALL_ON)
`uvm_field_int( pkg_id, UVM_ALL_ON)
`uvm_field_int(ntrans, UVM_ALL_ON)
`uvm_field_int(data_size, UVM_ALL_ON)
`uvm_field_array_int(data, UVM_ALL_ON)
`uvm_object_utils_end
`uvm_declare_p_sequencer(chnl_sequencer)
function new (string name="chnl_sequence");
super.new(name);
endfunction
task body();
repeat(ntrans) send_trans();
endtask
task send_trans();
chnl_trans req, rsp;
`uvm_do_with{req, {
local::data_idle>=0 -> data_idle==local::data_idle;
local:: pkg_idle>=0 -> pkg_idle==local:: pkg_idle;
local:: chnl_id >=0 -> chnl_id ==local:: chnl_id;
local:: pkg_id >=0 -> pkg_id ==local:: pkg_id;
local::data_size>=0 -> data_size==local::data_size;
local:: ntrans>=0 -> ntrans==local::ntrans;
foreach(data[i]) local::data[i]>=0 -> data[i]==local::data[i];}
}
get_response(rsp);
assert(rsp.rsp) $error("no response");
endtask
function post_randomize();
string s;
s={"------"};
return s;
endfunction
endclass: chnl_sequence
pkg_id初始化是0;
data_size随机化是赋值给data.size();对应到uvm_do_with的随机化;
data[i]的随机化也是-1不是0;且不用域的自动化;
ntrans不用随机化;
uvm_do_with后要做一次pkg_Id++表示发送完一个包了;是括号,随机化部分才是{}
报错中加上时间点%0t,$time;
随机化回调函数不用返回任何东西,最后用uvm_info打印出来;s的格式是{s, "??"},调用sprint()函数要加super;
class chnl_sequence extends uvm_sequence #(chnl_trans);
rand int data_idle=-1;
rand int pkg_idle=-1;
rand int chnl_id=-1;
rand int pkg_id=0;
rand int ntrans=10;
rand int data[];
rand int data_size=-1;
constraint cstr{
soft data_idle==-1;
soft pkg_idle==-1;
soft chnl_id==-1;
soft pkg_id==0;//为什么这个就是0
soft n_trans==10;
soft data.size()==data_size;
foreach(data[i]) soft data[i]==-1;
};
`uvm_object_utils_begin(chnl_sequence)
`uvm_field_int(data_idle, UVM_ALL_ON)
`uvm_field_int( pkg_idle, UVM_ALL_ON)
`uvm_field_int(chnl_id, UVM_ALL_ON)
`uvm_field_int( pkg_id, UVM_ALL_ON)
`uvm_field_int(ntrans, UVM_ALL_ON)
`uvm_field_int(data_size, UVM_ALL_ON)
`uvm_field_array_int(data, UVM_ALL_ON)
`uvm_object_utils_end
`uvm_declare_p_sequencer(chnl_sequencer)
function new (string name="chnl_sequence");
super.new(name);
endfunction
task body();
repeat(ntrans) send_trans();
endtask
task send_trans();
chnl_trans req, rsp;
`uvm_do_with(req, {
local::data_idle>=0 -> data_idle==local::data_idle;
local:: pkg_idle>=0 -> pkg_idle==local:: pkg_idle;
local:: chnl_id >=0 -> chnl_id ==local:: chnl_id;
local:: pkg_id >=0 -> pkg_id ==local:: pkg_id;
local::data_size> 0 -> data.size()==local::data_size;
//local:: ntrans>=0 -> ntrans==local::ntrans;
foreach(data[i]) local::data[i]>=0 -> data[i]==local::data[i];}
)
this.pkg_id++;
`uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
get_response(rsp);
`uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
assert(rsp.rsp) $error("no response");
endtask
function void post_randomize();
string s;
s={s, "---------after randomization \n"};
s={s, super.sprint()};
`uvm_info(get_type_name(), s, UVM_HIGH)
endfunction
endclass: chnl_sequence
chnl_monitor
monitor要收集的只有两个变量,data和id,先用typedef定义为合并变量mon_data_t;
需要接口;所以也有set_interface;
声明并例化ap;
由于是comp有run_phase,执行mon_trans,和接口进行赋值
第一个版本
typedef struct packed{
int [31:0] data;
int [ 1:0] id;} mon_data_t
class chnl_monitor extends uvm_monitor;
`uvm_component_utils(uvm_monitor)
local virtual chnl_intf intf;
uvm_analysis_port #(chnl_trans) mon_ana_port;
function void new(string name="chnl_monitor", uvm_parent parent)
super.new(name, parent);
mon_ana_port=new();
endfunction
function void set_interface(virtual chnl_intf intf);
if(intf==null) $error("intf handle is empty!");
else this.intf=intf;
endfunction
task run_phase();
this.mon_trans();//别的模块中有同名任务,加个this
endtask
task mon_trans();
mon_data_t m;
@(posedge intf.clk iff(intf.mon_ck.ch_valid==1 && intf.mon_ck.ch_wait==0));
//用到接口,回去声明和写函数
//在valid拉高wait拉低时才是有效传输的数据
m.data<=intf.mon_ck.ch_data;
m.id <=intf.mon_ck.;
endtask
endclass: chnl_monitor
typedef{}后是名称,要加分号;
monitor的port的参数是mon_data_t;
new函数不用void;例化port要加名字和this
run_phase有参数uvm_phase phase;
mon_trans要有forever,clk时钟信号用时钟块代替,接口的data赋值给mon_data_t的;
port要调用write函数进行广播,参数是mon_data_t句柄,然后将data打印出来'h%8x
第二个版本
typedef struct packed{
int [31:0] data;
int [ 1:0] id;} mon_data_t;
class chnl_monitor extends uvm_monitor;
`uvm_component_utils(uvm_monitor)
local virtual chnl_intf intf;
uvm_analysis_port #(mon_data_t) mon_ana_port;
function new(string name="chnl_monitor", uvm_component parent)
super.new(name, parent);
mon_ana_port=new("mon_data_t", this);
endfunction
function void set_interface(virtual chnl_intf intf);
if(intf==null) $error("intf handle is empty!");
else this.intf=intf;
endfunction
task run_phase(uvm_phase phase);
this.mon_trans();//别的模块中有同名任务,加个this
endtask
task mon_trans();
mon_data_t m;
forever begin
@(posedge intf.mon_ck iff(intf.mon_ck.ch_valid===1 && intf.mon_ck.ch_wait===0));
//用到接口,回去声明和写函数
//在valid拉高wait拉低时才是有效传输的数据
m.data<=intf.mon_ck.ch_data;
mon_ana_port.write(m);
//m.id <=intf.mon_ck.;
`uvm_info(get_type_name(), $sformatf("data is 'h%8x", m.data), UVM_HIGH)
end
endtask
endclass: chnl_monitor
clk用时钟块就不用POSedge了,赋值用=阻塞赋值,条件用===完全等于;
ap调用write,参数为mon_data_t;
chnl_agent
顶层环境,build_phase获取接口,例化3个comp,例化用 组件名=类名::type_id::create(“组件名”,this),connect_phase连接driver和sequencer,并把接口连上
class chnl_agent extends uvm_agent;
local virtual chnl_intf intf;
chnl_driver driver;
chnl_sequencer sqr;
chnl_monitor monitor;
`uvm_component_utils(chnl_agent)
function new(string name="chnl_agent", uvm_component parent);
super.new(name, parent);
endfunction
function build_phase(uvm_phase phase);
if(intf==null) $error("intf handle is empty");
else this.intf=intf;
driver=chnl_driver::type_id::create("driver");
sqr =chnl_sequencer::type_id::create("sqr");
monitor=chnl_monitor::type_id::create("monitor");
endfunction
function connect_phase(uvm_phase phase);
driver.seq_item_port.connect(sqr.seq_item_export);
endfunction
endclass: chnl_agent
build和connect对接口的操作有何不同?(build_phase为配置,用config机制去get;工厂例化的第二个参数是this;connect直接调用set_interface,参数为vif)
agent的set interface函数是调用driver和monitor的同名函数,要前缀和参数
build要调用super的build phase函数,参数为phase;
connect要调用super的connect_phase函数,参数为phase;
build和connect要加void;
第二个版本
class chnl_agent extends uvm_agent;
local virtual chnl_intf vif;
chnl_driver driver;
chnl_sequencer sqr;
chnl_monitor monitor;
`uvm_component_utils(chnl_agent)
function new(string name="chnl_agent", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if( !uvm_config_db#(virtual chnl_intf)::get(this,"","vif",vif)
begin `uvm_fatal("GETVIF", "cannot get intf ") end
driver=chnl_driver::type_id::create("driver", this);
sqr =chnl_sequencer::type_id::create("sqr", this);
monitor=chnl_monitor::type_id::create("monitor", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
driver.seq_item_port.connect(sqr.seq_item_export);
this.set_interface(vif);
endfunction
function void set_interface(virtual chnl_intf vif);
driver.set_interface(vif);
monitor.set_interface(vif);
endfunction
endclass: chnl_agent
uvm的include少了个e
动态数组的数据类型不能是int,要改为bit;struct也是同理
笔误pkg_id写成pkt_id;域的自动化field拼错;
函数第一行要加分号;配置接口少了括号;
时钟块记得加上接口;
monitor接口数据给到句柄m的data用阻塞赋值;
拿下。