Chpater 21 UVM Transaction
21.1 Storing Data in Objects
从学习本书开始,我们将测试平台拆分形成一个小模块,每个模块,每个class只专注于一件事情。尽力避免一个class需要详细了解另一个class的工作内容的场景,两个不同class之间可以通过port的方式传递数据。以上工作都使得我们的测试平台变得十分高效,但是我们还留下了一些遗憾:没有对传输的数据进行处理!
TinyALU测试平台中的数据结构比较简单,command_s中包含三个变量:A,B,op。即使我们的TinyALU测试平台很小,我们也在许多地方反复调用这些值。driver,coverage,scoreboard,因此如果我们可以对数据进行处理优化,就可以进一步提升测试平台的工作效率。将数据存储在class中,可以使用函数进行数据处理,这是class优于struct的地方。
- 在UVM中这种数据class称作transaction,transaction封装了数据和数据的所有操作类型,这些操作如下:
- convert2string(): 将数据以字符串的形式打印;
- do_copy(): 将另外一个transaction的数据拷贝至当前transaction;
- do_compare(): 比较两个transaction的数据是否一致;
- randomize(): 利用SystemVerilog的random机制;
- 定义Transaction:
- 我们扩展uvm_transaction以生成需要的command_transaction,我们需要重新改写以下三个函数:
- do_copy()
- do_compare()
- convert2string()
21.1.1 定义数据的随机范围
command_transaction中需要定义随机变量,采用SV的rand定义,并用constraint约束变量的随机范围。这样我们就不再需要get_op()函数和get_data()函数,SV可以接手所有的随机工作,我们只需要在定变量的时候在前面增加rand关键字即可:
class command_transaction extends uvm_transaction;
`uvm_object_utils(command_transaction)
rand byte unsigned A; //定义操作数A,只需要在定义之前加rand关键字
rand byte unsigned B;
rand operation_t op;
constraint data { A dist {8'h00:=1, [8'h01 : 8'hFE]:=1, 8'hFF:=1};
B dist {8'h00:=1, [8'h01 : 8'hFE]:=1, 8'hFF:=1};}
//指定A B的范围可以指定,甚至可以用dist决定随机值的出现概率
21.1.2 uvm_transaction的new函数
uvm_transaction扩展自uvm_object,不是component类型,在构造时不需要parent handle,只需要name即可。(component类型的class在构建的时候需要知道在环境中的具体节点位置,所以需要parent)
function new (string name = "");
super.new(name);
endfunction : new
21.1.3 do_copy函数
uvm_object类和其子类都包含copy()函数和clone()函数,其中copy函数拷贝一个对象的数据至另一个同类型对象,clone函数返回数据完全一致的同类型的对象;copy是将数据拷贝一份,clone是返回一个新的对象。
copy函数和clone函数能够工作的前提是:我们必须重构do_copy函数,do_copy函数的工作机制和我们上一章节定义lion的convert2string的机制一致,需要调用父类的同名函数。
function void do_copy(uvm_object rhs);
command_transaction copied_transaction_h;
if(rhs == null)
`uvm_fatal("COMMAND TRANSACTION", "Tried to copy from a null pointer")
//检查传输的rhs不是command_transaction类型,拷贝失败
if(!$cast(copied_transaction_h,rhs))
`uvm_fatal("COMMAND TRANSACTION", "Tried to copy wrong type.")
super.do_copy(rhs); // copy all parent class data
A = copied_transaction_h.A;
B = copied_transaction_h.B;
op = copied_transaction_h.op;
endfunction : do_copy
21.1.4 clone_me函数
- 使用uvm_transaction的好处之一:在多个模块多个对象中都可以看到相同的数据,在模块较小的TinyALU中可能考不到优势,如果我们的设计包含250个port,原本需要250份备份数据的空间,现在只需要一个即可。
- 双刃剑:上述机制也存在一定的短板:无论任何一个对象对数据进行了修改,整个测试环境看到的数据都会变化。因此引入了MOOCOW(Manual Obligatory Object Copy On Write)机制,如果我们希望对transaction中的数据进行操作就需要强制拷贝一个备份,并在自己的世界里进行修改。
- 当然,有些设计中希望在某个模块(如uvm_callbacks)检测transaction数据,发现异常的时候就进行修改,这样也是充分利用了uvm_transaction的优势。
function command_transaction clone_me();
command_transaction clone;
uvm_object tmp;
tmp = this.clone(); //通过clone函数,我们得到的是一个新的对象,具有独立的存储空间
$cast(clone, tmp);
return clone; //函数的返回值是新的对象,和this具有不同的存储空间,但是包含完全一致的数据
endfunction : clone_me
21.1.5 do_compare函数
- 在scoreboard中我们需要对数据尽心对比,我们希望uvm_transaction自己就可以完成数据比对。所有数据都在transaction内部,使用也更加顺手,也可以将功能封装起来。
- do_compare需要传入uvm_object类型的同类型transaction,也需要传入一个uvm_comparer类型的变量comparer(此处不再介绍)
- do_compare函数属于deep operation,所以我们需要将transation传给父类,让父类函数比较父类成员是否一致。
function bit do_compare(uvm_object rhs, uvm_comparer comparer);
command_transaction compared_transaction_h;
bit same;
if (rhs==null) `uvm_fatal("RANDOM TRANSACTION",
"Tried to do comparison to a null pointer");
if (!$cast(compared_transaction_h,rhs))
same = 0;
else
same = super.do_compare(rhs, comparer) && //父类成员变量一致
(compared_transaction_h.A == A) && //this的成员变量一致才返回same=1
(compared_transaction_h.B == B) &&
(compared_transaction_h.op == op);
return same;
endfunction : do_compare
21.1.6 convert2string函数
和上一章lion中的机制一致,我们使用格式化打印显示成员变量。
function string convert2string();
string s;
//使用SV的格式化打印机制显示成员变量
s = $sformatf("A: %2h B: %2h op: %s",A, B, op.name());
return s;
endfunction : convert2string
21.2 使用UVM Transaction
我们把数据data从信号级传输封装成struct,然后又升级为uvm transaction,所以我们需要修改测试平台中的几乎每一个模块。不过这样有利于代码重用,有利于简化代码结构,一劳永逸。
21.2.1 创建rusult_transaction用于存储计算结果
class result_transaction extends uvm_transaction;
shortint result;
function new(string name = "");
super.new(name);
endfunction : new
......
function bit do_compare(uvm_object rhs, uvm_comparer comparer); //scoreboard可以直接嗲用这个函数进行DUT计算结果和预测结果比对
result_transaction RHS;
bit same;
assert(rhs != null) else
$fatal(1,"Tried to copare null transaction");
same = super.do_compare(rhs, comparer);
$cast(RHS, rhs);
same = (result == RHS.result) && same;
return same;
endfunction : do_compare
endclass : result_transaction
21.2.2 创建add_transaction仅产生add操作
利用继承,复用command_transaction定义的功能,但是将opcode限制为add。
class add_transaction extends command_transaction; //有没有感受到继承的魅力?
`uvm_object_utils(add_transaction)
constraint add_only {op == add_op;}
function new(string name="");super.new(name);endfunction
endclass : add_transaction
21.2.3 将base_tester改名为tester
此前我们定义了random_tester和add_tester产生随机操作和add操作,现在有了
class tester extends uvm_component;
`uvm_component_utils (tester)
uvm_put_port #(command_transaction) command_port;
task run_phase(uvm_phase phase);
command_transaction command; //使用定义的transaction
phase.raise_objection(this);
command = new("command");
command.op = rst_op;
command_port.put(command);
repeat (10) begin
command = command_transaction::type_id::create("command");
assert(command.randomize()); //使用randomize函数实现数据随机
command_port.put(command); //使用put函数将数据通过put_port送至测试平台
end
....
21.2.4 升级common_monitor
command_monitor传递的类型改变了,write函数需要更新。
class command_monitor extends uvm_component;
`uvm_component_utils(command_monitor);
virtual tinyalu_bfm bfm;
uvm_analysis_port #(command_transaction) ap; //analysis端口传输的数据类型改变
function new (string name, uvm_component parent);
super.new(name,parent);
endfunction
function void build_phase(uvm_phase phase);
if(!uvm_config_db #(virtual tinyalu_bfm)::get(null, "*","bfm", bfm))
`uvm_fatal("COMMAND MONITOR", "Failed to get BFM")
bfm.command_monitor_h = this;
ap = new("ap",this);
endfunction : build_phase
function void write_to_monitor(byte A, byte B, operation_t op);
command_transaction cmd; //噢,数据类型变成了command_transaction
`uvm_info("COMMAND MONITOR",$sformatf("MONITOR: A: %2h B: %2h op: %s",
A, B, op.name()), UVM_HIGH);
cmd = new("cmd"); //transaction使用需要先分配内存空间哟
cmd.A = A;
cmd.B = B;
cmd.op = op;
ap.write(cmd); //送出的数据类型也变成了command_transaction类型
endfunction : write_to_monitor
endclass : command_monitor
21.2.5 升级result_monitor
class result_monitor extends uvm_component;
`uvm_component_utils(result_monitor);
virtual tinyalu_bfm bfm;
uvm_analysis_port #(result_transaction) ap; //monitor的analysis端口的数据类型更新了哟
......
function void build_phase(uvm_phase phase);
if(!uvm_config_db #(virtual tinyalu_bfm)::get(null, "*","bfm", bfm))
`uvm_fatal("RESULT MONITOR", "Failed to get BFM")
bfm.result_monitor_h = this;
ap = new("ap",this);
endfunction : build_phase
function void write_to_monitor(shortint r);
result_transaction result_t; //噢,这里需要声明result_transaction类型对象
result_t = new("result_t"); //别忘了分配内存空间
result_t.result = r;
ap.write(result_t); //端口送出的数据类型也变了哦
endfunction : write_to_monitor
endclass : result_monitor
21.2.6 在scoreboard中使用transaction的do_compare函数
class scoreboard extends uvm_subscriber #(result_transaction);
`uvm_component_utils(scoreboard);
uvm_tlm_analysis_fifo #(command_transaction) cmd_f;
function void build_phase(uvm_phase phase);
cmd_f = new ("cmd_f", this);
endfunction : build_phase
......
function void write(result_transaction t);
string data_str;
command_transaction cmd; //get端口的数据类型也变了哟
result_transaction predicted; //预测结果的数据类型也变了哦
do
if (!cmd_f.try_get(cmd)) //还记的try_get是啥类型么?
$fatal(1, "Missing command in self checker");
while ((cmd.op == no_op) || (cmd.op == rst_op));
predicted = predict_result(cmd); //计算预测结果,(函数定义这里没给出)
data_str = { cmd.convert2string(),
" ==> Actual " , t.convert2string(),
"/Predicted ",predicted.convert2string()};
if (!predicted.compare(t)) //调用result_transaction的compare函数
`uvm_error("SELF CHECKER", {"FAIL: ",data_str})
else
`uvm_info ("SELF CHECKER", {"PASS: ", data_str}, UVM_HIGH)
endfunction : write
endclass : scoreboard
21.2.7 升级add_test
使用set_type_override告诉UVM,所有遇到commad_transction的地方都使用add_transaction代替,不用对random_test做任何修改。
class add_test extends random_test;
`uvm_component_utils(add_test);
function void build_phase(uvm_phase phase);
command_transaction::type_id::set_type_override(add_transaction::get_type()); //还记得override的作用么?
super.build_phase(phase);
endfunction : build_phase
function new (string name, uvm_component parent);
super.new(name,parent);
endfunction : new
endclass
21.3 Transaction Summary
- 本章中我们使用UVM Transaction,将数据打包。许多数据的处理都在UVM Transaction内部完成,测试平台的组件只需要直接调用transaction内部的函数即可。
- 下一章,我们重新回到UVM的hierarchy。我们将认识到,与给定块或接口关联的所有类可以组合在一起,并在使用该接口的其他测试平台中重用。
- 在env下再增加一级封装,env结构变化时这些给定块可以重用。