Chapter 15 Talking to Multiple Objects
既然我们使用OOP编写程序,那么就应该充分利用OOP编程的优势。我们希望在测试平台中,避免每个模块都读取BFM备份,然后根据clock信号一拍拍的采集数据,进行数据处理。我们希望可以让objects之间可以通信,后面我们将逐步介绍两种object communication的方式:
- Single-Thread Communication: 同线程内的对象调用本线程内的其他对象的方法;
- Dual-Thread Communication:两个不同线程内的对象之间的通信;
本章首先通过色子实验介绍single-thread,下一章介绍uvm_testbench中的单线程通信。
掷骰子实验
- 要求: 写一段程序实现以下功能,掷2个骰子:
- 记录两个骰子的和的平均值;
- 记录两个骰子之和的频数;
- 记录两个骰子之和在2-12之间的覆盖率;
15.1 不使用object间的通信
我们以求平均值的功能为例,首先声明名称为average的类,average扩展自uvm_component(average需要是验证环境中验证组件),其中:average中通过write函数计算所有掷骰子的结果的和,并统计掷骰子的次数,分别存入dice_total,count; 并在report_phase中打印输出的结果。average类的代码如下:
class average extends uvm_component;
`uvm_component_utils(average);
protected real dice_total; //protected variables, 不希望在类外直接访问的数据
protected real count;
function new(string name, uvm_component parent = null);
super.new(name,parent);
dice_total = 0.0; //构造函数中给变量赋初值
count = 0.0;
endfunction : new
function void write(int t);
dice_total = dice_total + t; //计算掷骰子结果之和
count++; //统计掷骰子的次数
endfunction : write
function void report_phase(uvm_phase phase);
$display ("DICE AVERAGE: %2.1f",dice_total/count); //打印掷骰子的平均值
endfunction : report_phase
endclass : average
对应的,我们定义histogram和coverage类,并在其中定义write函数进行数据处理,在report_phase函数中打印结果;
定义完测试平台中的structure(uvm_components),我们就可以定义dice_test类,因为本章主要关注线程内通信的特性,所有没有使用uvm_env将各组件打包。我们之间在dice_test中例化环境组件,代码如下:
class dice_test extends uvm_test;
`uvm_component_utils(dice_test);
dice_roller dice_roller_h; //stimulus,类似此前的add_tester,也是uvm_component
coverage coverage_h;
histogram histogram_h;
average average_h;
function void build_phase(uvm_phase phase); //在build_phase中例化环境中的组件
coverage_h = new("coverage_h", this); //没有使用factory机制例化
histogram_h = new("histogram_h",this);
average_h = new("average_h",this);
dice_roller_h = new("dice_roller_h",this);
endfunction : build_phase
task run_phase(uvm_phase phase); //在run_phase中调用其他组件的write方法
int the_roll;
phase.raise_objection(this);
repeat (20) begin //重复20次
the_roll = dice_roller_h.two_dice(); //the_roll返回int类型的,随机掷2个骰子之和
coverage_h.write(the_roll); //将掷骰子的结果写入coverage
histogram_h.write(the_roll); //将掷骰子的结果写入histogram
average_h.write(the_roll); //将指投资的结果写入average函数
end
phase.drop_objection(this);
endtask : run_phase
function new(string name, uvm_component parent);
super.new(name,parent);
endfunction : new
endclass : dice_test
以上就是我们按照现有的UVM知识实现的掷骰子功能,(除了未使用env将测试平台的structure和stimulus分开),但是我们没有用到connect的概念,我们仍然需要在dice_test的run_phase中手动将dice_roller的结果写入average,histogram中。这样的代码可以实现功能,但是代码不够简单,结构不够清晰,不能称之为优秀的代码。
为了优化以上代码,UVM提供类似Observer design pattern的模式,使用uvm_analysis_port.
15.2 使用uvm_analysis_port实现objects间的通信
-
Observer design pattern类似于朋友圈模式,在朋友圈中我们只需要编辑内容,点击发送就可以完成。我们不需要告诉我们的朋友我发了一条状态,他们就可以接受到我们分享的信息,此外,朋友圈中的每个朋友都有可以得到我们分享信息的一份备份,并对该信息进行评论回复。甚至,即使我们朋友圈中没有好友我们也可以分享状态。observer design patter的本质和朋友圈类似,在这种设计模式下,一个object(比如dice_roller)可以创建信息并向整个平台分享,无论他的平台中有多少个其他object observer(朋友),平台中其他object可以得到dice_roller发出的信息的备份,可以对此信息进行任何处理。
-
在掷骰子实验中,dice_roller就是分享信息的object,而其他的object observer都在关注dice_roller发出的信息。但是在UVM中不叫observer而是叫做subscriber;
-
UVM通过两个类实现observer design pattern设计模式:
- uvm_analysis_port: 为类似dice_roller的object提供向observer广播数据的通道;
- 在dice_roller中定义analysis变量,并指定需要传输的数据类型;
- 在build_phase例化analysis port变量;
- 通过write()函数向analysis port中写数据,write函数就像发状态时按下发送键;
- 以上三步就是实现analysis port的过程;
- 一旦通过write()函数向analysis port写数据,数据就会达到所有subscriber;
- analysis port也包含connect函数,我们可以在env或test的connect_phase中将port连接至所有subscriber;connect函数的的参数是analysis_export类型的变量;
- uvm_subscriber:uvm_component的子类,允许其子类订阅dice_roller,监控其发出的信息。
- uvm_subscriber为我们提供了一些的功能,但也提出了一些需求;
- uvm_subscriber为我们提供了analysis_export, 我们可以通过这个export接受analysis的数据;
- uvm_subscriber要求我们重载write()函数,在write函数中对接受的数据进行处理;
- uvm_analysis_port: 为类似dice_roller的object提供向observer广播数据的通道;
现在我们使用UVM提供的uvm_analysis_port和uvm_subscriber重新撰写掷骰子程序。
- 在dice_roller中使用uvm_analysis_port
class dice_roller extends uvm_component;
`uvm_component_utils(dice_roller);
uvm_analysis_port #(int) roll_ap; //1. 定义uvm_analysis_port变量
function void build_phase (uvm_phase phase);
roll_ap = new("roll_ap",this); //2. 在build_phase中例化analysis变量
endfunction : build_phase
rand byte die1;
rand byte die2;
constraint d6 { die1 >= 1; die1 <= 6;
die2 >= 1; die2 <= 6; }
task run_phase(uvm_phase phase);
int the_roll;
phase.raise_objection(this);
void'(randomize());
repeat (20) begin
void'(randomize());
the_roll = die1 + die2;
roll_ap.write(the_roll); //3. 在run_phase中调用write函数
end //不需要关心到底哪些组件在处理这些数据
phase.drop_objection(this);
endtask : run_phase
function new(string name, uvm_component parent);
super.new(name,parent);
endfunction : new
endclass : dice_roller
- 使用uvm_subscriber扩展average,histogram等组件(扩展时需要制定analysis_export传输的变量类型)
class average extends uvm_subscriber #(int); //参数化的类subscriber,扩展式需要指定类型
`uvm_component_utils(average);
real dice_total;
real count;
//1. 虽然没写出来,但average从uvm_subscriber中继承了analysis_export变量、
//此变量通常作为connect函数的参数
function new(string name, uvm_component parent = null);
super.new(name,parent);
dice_total = 0.0;
count = 0.0;
endfunction : new
function void write(int t); //2. 使用write函数处理analysis接受到的数据
dice_total = dice_total + t;
count++;
endfunction : write
function void report_phase(uvm_phase phase);
$display ("DICE AVERAGE: %2.1f",dice_total/count);
endfunction : report_phase
endclass : average
- 在uvm_test或uvm_env的connect_phase中将analysis_port和export连接起来
class dice_test extends uvm_test;
`uvm_component_utils(dice_test);
dice_roller dice_roller_h;
coverage coverage_h;
histogram histogram_h;
average average_h;
function void connect_phase(uvm_phase phase);
dice_roller_h.roll_ap.connect(coverage_h.analysis_export); //在connect_phase调用analysis_port的connect函数,并将各个observer中的analysis_export传入
dice_roller_h.roll_ap.connect(histogram_h.analysis_export); //conect_phase调用顺序 bottom-up
dice_roller_h.roll_ap.connect(average_h.analysis_export); //先调用coverage,在调用uvm_test
endfunction : connect_phase
function new(string name, uvm_component parent);
super.new(name,parent);
endfunction : new
function void build_phase(uvm_phase phase); //build_phase调用顺序 top-down
//先调用test的build_phase,在调用coverage的build_phase
dice_roller_h = new("dice_roller_h", this);
coverage_h = new("coverage_h", this);
histogram_h = new("histogram_h",this);
average_h = new("average_h",this);
endfunction : build_phase
endclass : dice_test
- 使用UVM的analysis_port和subscriber类之后,我们的测试平台环境如下图所示:
- 我们通过observer design pattern将测试平台中各个object连接起来,当我们需要新增class处理数据时,我们只需要新写一个subscriber扩展的class,然后将其连接到dice_roller的analysis port上,其他不用作任何改动。下一章介绍如何将UVM的analysis_port和subscriber类加入我们此前的测试平台。