chnl_pkg——MCDF验证环境(1)

对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用阻塞赋值;

拿下。 

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值