uvm 形式验证_4.小白学uvm验证 - UVM通信

一个基本的 uvm 验证环境结构如下图所示,包含两个 agent,其中 in_agent 用于驱动 DUT ,同时将驱动数据同时传递给 reference model, out_agent 用于按照协议采集 DUT 的输出数据,并将数据传递给 scoreboard,在 scoreboard 收集到 reference model 的运算结果后,再进行比对验证。

要想实现 monitor 和 reference model 进行通信,大致可以有以下几种方式,第一种,通过全局变量;第二种,通过 定义一个参数类来进行信息传递;第三种:通过前面刚谈到的 uvm_config 来进行传递信息。似乎这些东西都可以实现我们定义的任务,但是有个很重要的点还没有提到,如果我们需要一种 阻塞的场景,即有数据时就取数据,没有数据时就等待。这在验证中是一种比较常见的场景。为了解决这个问题 uvm 提炼出了一种具有阻塞和非阻塞功能的类型,取名为TML。

通俗意义上讲,TLM就是一些带阻塞和非阻塞 port 端口, 为了满足缓存需要,其中也有一些能缓存能力的 port。TLM 端口的使用有点像我们看电视时用插头插插座,插看好后电网的电量就源源不断的通过接口输入进电视机,结合我们的验证环境来说 driver 和 reference model 中包含一对匹配的结构,一个用于发送数据,一个用于接收数据。

1.端口连接

在 UVM 中 port 可以分为三种:

a.port : 优先级最高,可以作为数据链路的端口的起点或者端口中间节点 ;

b.export :优先级最中,可以作为数据链路的或者起点端口中间节点 ;

c.import :优先级最低,只能作为数据链路的重点 。

连接形式可以为

a.port -> import; port -> export -> import; port -> port -> import; port -> export -> export -> import ;

b.export -> import; export -> export -> import;

注意 -> 表示连接关系,A port -> B import,表示为 A.connect(B) ,其中 A 的优先级需要比 B 的优先级高。

2.端口操作

如下图所示,在 UVM 中 port 常见的两种操作就是:

a. put 操作 :通信的发起者 A 把一个 transaction 发送给接受者 B ;

b. get 操作 :通信的发起者 A 向 接受者 B 请求一个 transaction ;

c. transport操作,transport操作相当于一次 put 操作加一次 get 操作 。

uvm 包含了很多类型的 port/export/import,如 uvm_blocking_put_port/export/import、uvm_blocking_peek_port/export/import 和 uvm_blocking_transport_port/export/import 等,但除了这些,uvm 还包含了两种特殊的端口 analysis_port 和 analysis_export, 这两种端口和 put/peek/get 等系列端口类似,但是包含一些重要区别:

默认情况下,一个 analysis_port(analysis_export)可以连接多个 analysis_import,即 analysis_port(analysis_export)与 analysis_import 之间是一对多通信;而 put/peek/get 等系列端口只能进行一对一通信。analysis_port(analysis_export)的通信方式像一个广播。

put/peek/get 等系列端口都具有阻塞和非阻塞之分,但对于 analysis_port(analysis_export)来说,没有阻塞和非阻塞的概念。

不同于put/peek/get 等系列端口,对于analysis_port(analysis_export)两种端口仅包含 write 操作,与之相连的 analysis_imp 所在的 component 必须定义一个名为 write 的函数。

3.端口实例1 - uvm_analysis_port

在 uvm 环境中,常用 TML 通信的组件有 monitor、scoreboard和reference model 三者之间的通信,下面以 monitor 和 scoreboard 通信为例,monitor端 uvm_analysis_port的代码为:

my_monitor

class my_monitor extends uvm_monitor;

`uvm_component_utils(my_monitor)

uvm_analysis_port(my_transaction) A_port;

...

endclass

...

virtual function void my_monitor::build_phase(uvm_phase phase);

super.build_phase(phase);

A_port=new("A_port",this);

...

endfuction

virtual function void my_monitor::main_phase(uvm_phase phase);

my_transaction tr;

forever begin

assert(tr.randomize());

A_port.write(tr);

end

endfuction

scoreboard 端 uvm_analysis_port 的代码为:

scoreboard

class my_scoreboard extends uvm_scoreboard;

`uvm_component_utils(my_scoreboard)

uvm_analysis_import(my_transaction,my_scoreboard) A_import;

...

endclass

...

virtual function void my_scoreboard::build_phase(uvm_phase phase);

super.build_phase(phase);

A_import=new("A_import",this);

...

endfuction

virtual function void my_scoreboard::write(my_transaction tr);

deal_with(tr);

endfuction

顶层 env 连接代码:

my_env

class my_env extends uvm_env;

...

endclass

...

virtual function void my_scoreboard::connect_phase(uvm_phase phase);

super.connect_phase(phase);

agt.monitor.A_port.connect(scb.A_import)

...

endfuction

实例中,在 monitor 中定义了 analysis_port;socreboard 中定义了 analysis_import;在 env 的 connect_phase 中对 analysis_port 和 analysis_import 进行连接。数据在 monitor 产生后,数据的流通过程如下图所示, 数据首先通过 analysis_port 的系统函数 write 调用 analysis_import 的系统函数 write,再由 analysis_import 的系统函数 write 触发 my_scoreboard 中用户自定义的 write 函数,这也为什么在定义 scoreboard 的 impport时,需要通过 uvm_analysis_port(my_transaction,my_scoreboard) A_import; 将 my_scoreboard 和 A_import 绑定的原因,就是为了 能顺利触发 自定义的 write 函数。而且这种触发是没有延迟的触发机制,可以通过这种 write 方式来满足一些特定时序要求。

在了解了上述内容后,在来看看 uvm_analysis_port 的最重要的特性,一对多通信是怎么实现的,现在考虑一种场景,scoreboard 需要收集 monitor 和 reference model 两个组件传输过来的数据,但是我们定义write 函数的时候只有一个名字,那么 monitor 和 reference model 中的 analysis_import 的系统函数 write 怎么触发 my_scoreboard 中用户自定义的 write 函数呢?

为了解决上述问题 uvm 定了一宏 uvm_analysis_imp_decl 来解决上述问题,现在我们来看看这个宏怎么用,更新后的 my_scoreboard 代码如下:

scoreboard

`uvm_analysis_imp_decl(_monitor)

`uvm_analysis_imp_decl(_model)

class my_scoreboard extends uvm_scoreboard;

my_transaction inpect_queue[$]

my_transaction expect_queue[$]

uvm_analysis_imp_monitor(my_transaction,my_scoreboard) mon_import;

uvm_analysis_imp_model(my_transaction,my_scoreboard) model_import;

...

extern function void write_monitor(my_transaction tr);

extern function void write_model(my_transaction tr);

...

endclass

...

virtual function void my_scoreboard::write_monitor(my_transaction tr);

inpect_queue.push_back(tr);

...

endfuction

virtual function void my_scoreboard::write_model(my_transaction tr);

expect_queue.push_back(tr);

...

endfuction

上述代码通过宏 uvm_analysis_imp_decl 声明了两个后缀 _monitor 和 _model 。UVM 会根据这两个后缀定义两个新的 import 类:uvm_analysis_imp_monitor 和 uvm_analysis_imp_model ,并在 my_scoreboard 中分别实例化这两个类:monitor_imp 和 model_imp。当与 monitor_imp 相连接的 analysis_port 执行 write 函数时,会自动调用 write_monitor 函数,而与 model_imp 相连接的 analysis_port 执行 write 函数时,会自动调用 write_model 函数。所以,只要完成后缀的声明,并在write后面添加上相应的后缀就可以正常工作了。

3.端口实例2 - uvm_tlm_analysis_fifo

另外一种带缓存能力的 analysis port ,叫做 uvm_tlm_analysis_fifo,其本质为 一块 FIFO 加一组端口,如下图所示,共有 3 类,12 import 为一类,包含 一族 *_put_export 、一族 *_get_export 和一族 *_peek_export ,似乎上述都是 export,其实这只是取名为 export 而已,它们本质上还是 import 。

*_put_export 族:调用时,导致 uvm_tlm_analysis_fifo 中的缓存单元(**本质为 mailbox ),元素个数加 1;

*_get_export 族:调用时,导致 uvm_tlm_analysis_fifo 中的缓存单元(本质为 mailbox ),元素个数减 1;

*_peek_export 族:调用时,导致 uvm_tlm_analysis_fifo 中的缓存单元(本质为 mailbox **),元素个数不变,同时把 transaction 复制一份发送出去。

现在来说说图中的另外两个端口 - put_ap 和 get_ap, 这两组的 端口的源码如下

scoreboard

virtual task void put(int T t);

m.put(t);

put_ap.write(t);

endtask

virtual task void get(int T t);

m_pending_blocked_gets++;

m.get( t );

m_pending_blocked_gets--;

get_ap.write( t );

endtask

当 uvm_tlm_analysis_fifo 上 的 _put_export 族端口被连接到 一个 put 端口时,且调用 put 函数时,put 函数会把传递过来的 transactoin 放在 uvm_tlm_analysis_fifo 的缓存单元 m (mailbox)中,同时把这个 transaction 通过 put_ap 端口的 write 函数发送给与之相连的端口;当 uvm_tlm_analysis_fifo 上 的_get_export 族 被连接到一个 get 端口,且调用 get 函数时,过程类似。我们常用的是 analysis_export 端口。带 FIFO 缓存的端口类型除了 uvm_tlm_analysis_fifo,还有一种是 uvm_tlm_fifo, 区别在于前者有一个 analysis_export 端口和一个 write 函数。

现在以 monitor 与 reference model 的通信为例,其 monitor 端代码如下

monitor

class my_monitor extends uvm_monitor;

`uvm_component_utils(my_monitor)

uvm_analysis_port(my_transaction) A_port;

...

endclass

...

virtual function void my_monitor::build_phase(uvm_phase phase);

super.build_phase(phase);

A_port=new("A_port",this);

...

endfuction

virtual function void my_monitor::main_phase(uvm_phase phase);

my_transaction tr;

forever begin

assert(tr.randomize());

A_port.write(tr);

end

endfuction

其 reference model 端代码如下

reference model

class my_reference extends uvm_component;

`uvm_component_utils(my_reference )

uvm_tlm_analysis_fifo(my_transaction) A_fifo_port;

...

endclass

...

virtual function void my_reference ::build_phase(uvm_phase phase);

super.build_phase(phase);

A_fifo_port=new("A_fifo_port",this);

...

endfuction

virtual function void my_reference ::main_phase(uvm_phase phase);

my_transaction tr = null;

forever begin

A_fifo_port.get(tr);

...

end

endfuction

顶层 env 连接代码:

my_env

class my_env extends uvm_env;

...

endclass

...

virtual function void my_scoreboard::connect_phase(uvm_phase phase);

super.connect_phase(phase);

agt.monitor.A_port.connect(ref.A_fifo_port.analysis_export)

...

endfuction

其中 monitor 通过 uvm_analysis_port A_port 调用 write 函数将 my_transaction 存入 uvm_tlm_analysis_fifo 的缓存单元中,在 reference model 中通过 get 函数将 tranansction 取出进行后续处理。

3.端口特例

这组特例 port 就是 uvm_seq_item_pull_port 和 uvm_seq_item_pull_imp ,通过名字可以知道,这组端口通常是在 sequencer 和 driver 中使用的,用于它们之间的数据传递。uvm library 中 原型代码如下所示:

uvm_seq_item_pull_port

class uvm_driver #(type REQ=uvm_sequence_item,type RSP=REQ) extends uvm_component;

uvm_seq_item_pull_port #(REQ,RSP) seq_item_port;

...

endclass

class uvm_sequencer #(type REQ=uvm_sequence_item,type RSP=REQ) extends uvm_sequencer_para_base #(REQ,RSP);

uvm_seq_item_pull_imp #(REQ,RSP) seq_item_export;

...

endclass

在使用时,因为 sequencer 中的操作被隐藏,所以使用时很简单,实例如所示:

uvm_seq_item_pull_port 实例

class my_sequence extends uvm_sequence #(my_transaction);

`uvm_object_utils(my_sequence);

virtual task body(uvm_phase phase);

`uvm_do(req);

get_response(rsp);

endtask

endclass

class my_sequencer extends uvm_sequencer #(my_transaction);

`uvm_component_utils(my_sequencer);

function new(string name, uvm_component phase);

super.new(name,parent);

endfunction

function void build_phase(uvm_phase phase);

super.build_phase(phase);

endfunction

endclass

class my_driver extends uvm_driver #(my_transaction);

`uvm_component_utils(my_driver );

...

virtual task run_phase(uvm_phase phase);

my_transaction tr;

while(1)begin

seq_item_port.get_next_item(req);

send(tr);

se_item_port.item_done();

seq_item_port.put_response(rsp);

end

endtask

endclass

sequence 发送数据,driver 接受数据,具体细节将在第 5 节中讲述。


版权声明:本文为weixin_39800957原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。