一、TLM是什么? TLM是Transaction Level Modeling(事务级建模)的缩写。所谓transaction level是相对DUT中各个模块之间信号线级别的通信来说的。 TLM通常有三种模式:
put操作,通信的发起者A把一个transaction发送给B。在这个过程中,A称为“发起者”,而B称为“目标”。A具有的端口(用方框表示)称为PORT,而B的端口(用圆圈表示)称为EXPORT。这个过程中,数据流是从A流向B的。
get操作。在这个过程中,A依然是“发起者”,B依然是“目标”,A上的端口依然是PORT,而B上的端口依然是EXPORT。这个过程中,数据流是从B流向A的。PORT和EXPORT体现的是控制流而不是数据流。
transport操作,transport操作相当于一次put操作加一次get操作,这两次操作的“发起者”都是A,目标都是B。在这个过程中,数据流先从A流向B,再从B流向A。在现实世界中, 相当于是A向B提交了一个请求(request),而B返回给A一个应答(response)。
二、put操作 2.1、建立PORT和EXPORT的连接 UVM中使用connect函数来建立连接关系。如A要和B通信(A是发起者),那么可以这么写:A.port.connect(B.export)。下面是A的代码部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class A extends uvm_component ; `uvm_component_utils(A) uvm_blocking_put_port#(my_transaction) A_port; function new (string name, uvm_component parent) ; super .new(name, parent); endfunction extern function void build_phase (uvm_phase phase) ; extern virtual task main_phase (uvm_phase phase) ; endclass function void A::build_phase(uvm_phase phase); super .build_phase(phase); A_port = new ("A_port" , this ); endfunction task A::main_phase(uvm_phase phase); endtask
然后得到B的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class B extends uvm_component ; `uvm_component_utils(B) uvm_blocking_put_export#(my_transaction) B_export; function new (string name, uvm_component parent) ; super .new(name, parent); endfunction extern function void build_phase (uvm_phase phase) ; extern virtual task main_phase (uvm_phase phase) ; endclass function void B::build_phase(uvm_phase phase); super .build_phase(phase); B_export = new ("B_export" , this ); endfunction task B::main_phase(uvm_phase phase); endtask
然后在env将两者进行链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class my_env extends uvm_env ; A A_inst; B B_inst; function new (string name = "my_env" , uvm_component parent) ; super .new(name, parent); endfunction virtual function void build_phase (uvm_phase phase) ; super .build_phase(phase); A_inst = A::type_id::create("A_inst" , this ); B_inst = B::type_id::create("B_inst" , this ); endfunction extern virtual function void connect_phase (uvm_phase phase) ; `uvm_component_utils(my_env) endclass function void my_env::connect_phase(uvm_phase phase); super .connect_phase(phase); A_inst.A_port.connect(B_inst.B_export); endfunction
2.2 IMP组件 除了TLM中定义的PORT与EXPORT外,UVM中加入了第三种端口:IMP,起作用相当于在EXPORT后进行接受操作。 添加IMP后,A的代码变为:
1 2 3 4 5 6 7 8 9 task A::main_phase(uvm_phase phase); my_transaction tr; repeat(10 ) begin #10 ; tr = new ("tr" ); assert (tr.randomize()); A_port.put(tr); end endtask
在B中需要改动的要多一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class B extends uvm_component ; `uvm_component_utils(B) uvm_blocking_put_export#(my_transaction) B_export; uvm_blocking_put_imp#(my_transaction, B) B_imp; function new (string name, uvm_component parent) ; super .new(name, parent); endfunction extern function void build_phase (uvm_phase phase) ; extern function void connect_phase (uvm_phase phase) ; extern function void put (my_transaction tr) ; extern virtual task main_phase (uvm_phase phase) ; endclass function void B::build_phase(uvm_phase phase); super .build_phase(phase); B_export = new ("B_export" , this ); B_imp = new ("B_imp" , this ); endfunction function void B::connect_phase(uvm_phase phase); super .connect_phase(phase); B_export.connect(B_imp); endfunction function void B::put(my_transaction tr); `uvm_info("B" , "receive a transaction" , UVM_LOW) tr.print(); endfunction
在上述连接关系中,IMP是作为连接的终点。在UVM中,只有IMP才能作为连接关系的终点。如果是PORT或者EXPORT作为终点,则会报错。
三、get操作 get系列端口与put系列端口在某些方面完全相反。在这种连接关系中,数据流依然是从A到B,但是A由动作发起者变成了动作接收者,而B由动作接收者变成了动作发起者。
B_port的类型为uvm_blocking_get_port,A_export的类型为uvm_blocking_get_export,A_imp的类型为uvm_blocking_get_imp。A的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class A extends uvm_component ; `uvm_component_utils(A) uvm_blocking_get_export#(my_transaction) A_export; uvm_blocking_get_imp#(my_transaction, A) A_imp; my_transaction tr_q[$]; function new (string name, uvm_component parent) ; super .new(name, parent); endfunction extern function void build_phase (uvm_phase phase) ; extern function void connect_phase (uvm_phase phase) ; extern virtual task get (output my_transaction tr) ; extern virtual task main_phase (uvm_phase phase) ; endclass function void A::build_phase(uvm_phase phase); super .build_phase(phase); A_export = new ("A_export" , this ); A_imp = new ("A_imp" , this ); endfunction function void A::connect_phase(uvm_phase phase); super .connect_phase(phase); A_export.connect(A_imp); endfunction task A::get(output my_transaction tr); while (tr_q.size() == 0 ) #2 ; tr = tr_q.pop_front(); endtask task A::main_phase(uvm_phase phase); my_transaction tr; repeat(10 ) begin #10 ; tr = new ("tr" ); tr_q.push_back(tr); end endtask
在A的get任务中,每隔2个时间单位检查tr_q中是否有数据,如果有则发送出去。当B在其main_phase调用get任务时,会最终执行A的get任务。在A的connect_phase,需要把A_export和A_imp连接起来。下面是B的部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class B extends uvm_component ; `uvm_component_utils(B) uvm_blocking_get_port#(my_transaction) B_port; function new (string name, uvm_component parent) ; super .new(name, parent); endfunction extern function void build_phase (uvm_phase phase) ; extern virtual task main_phase (uvm_phase phase) ; endclass function void B::build_phase(uvm_phase phase); super .build_phase(phase); B_port = new ("B_port" , this ); endfunction task B::main_phase(uvm_phase phase); my_transaction tr; while (1 ) begin B_port.get(tr); `uvm_info("B" , "get a transaction" , UVM_LOW) tr.print(); end endtask
在这些连接关系中,需要谨记的是连接的终点必须是一个IMP。
四、transport端口
A代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 task A::main_phase(uvm_phase phase); my_transaction tr; my_transaction rsp; repeat(10 ) begin #10 ; tr = new ("tr" ); assert (tr.randomize()); A_transport.transport(tr, rsp); `uvm_info("A" , "received rsp" , UVM_MEDIUM) rsp.print(); end endtask
B中需要定义一个类型为uvm_blocking_transport_imp的IMP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class B extends uvm_component ; `uvm_component_utils(B) uvm_blocking_transport_imp#(my_transaction, my_transaction, B) B_imp; function new (string name, uvm_component parent) ; super .new(name, parent); endfunction extern function void build_phase (uvm_phase phase) ; extern task transport (my_transaction req, output my_transaction rsp) ; endclass function void B::build_phase(uvm_phase phase); super .build_phase(phase); B_imp = new ("B_imp" , this ); endfunction task B::transport(my_transaction req, output my_transaction rsp); `uvm_info("B" , "receive a transaction" , UVM_LOW) req.print(); #5 ; rsp = new ("rsp" ); endtask
env中的代码是:
1 2 3 4 function void my_env::connect_phase(uvm_phase phase); super .connect_phase(phase); A_inst.A_transport.connect(B_inst.B_imp); endfunction
在A中调用transport任务,并把生成的transaction作为第一个参数。B中的transaport任务接收到这笔transaction,根据这笔transaction做某些操作,并把操作的结果作为transport的第二个参数发送出去。A根据接收到的rsp来决定后面的行为。
五、nonblocking端口 1 2 3 4 5 6 7 8 9 task A::main_phase(uvm_phase phase); my_transaction tr; repeat(10 ) begin tr = new ("tr" ); assert (tr.randomize()); while (!A_port.can_put()) #10 ; void '(A_port.try_put(tr)); end endtask
由于端口变为了非阻塞的,所以在送出transaction之前需要调用can_put函数来确认是否能够执行put操作。can_put最终会调用B中的can_put:
六、analysis端口 UVM中还有两种特殊的端口:analysis_port和analysis_export。该端口有两点需要注意的地方:
一个analysis_port(analysis_export)可以连接多个IMP,analysis_port(analysis_export)与IMP 之间的通信是一对多的通信。analysis_port(analysis_export)更像是一个广播。
put与get系列端口都有阻塞和非阻塞的区分。但是对于analysis_port和analysis_export来说,没有阻塞和非阻塞的概念。
一个analysis_port可以和多个IMP相连接进行通信,但是IMP的类型必须是uvm_analysis_imp,否则会报错。
下面是A的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class A extends uvm_component ; `uvm_component_utils(A) uvm_analysis_port#(my_transaction) A_ap; function new (string name, uvm_component parent) ; super .new(name, parent); endfunction extern function void build_phase (uvm_phase phase) ; extern virtual task main_phase (uvm_phase phase) ; endclass function void A::build_phase(uvm_phase phase); super .build_phase(phase); A_ap = new ("A_ap" , this ); endfunction task A::main_phase(uvm_phase phase); my_transaction tr; repeat(10 ) begin #10 ; tr = new ("tr" ); assert (tr.randomize()); A_ap.write(tr); end endtask
A的代码很简单,只是简单地定义一个analysis_port,并在main_phase中每隔10个时间单位写入一个transaction。 B的代码为:
1 2 3 4 function void B::write(my_transaction tr); `uvm_info("B" , "receive a transaction" , UVM_LOW) tr.print(); endfunction
在env中通过下面方式进行连接:
1 2 3 4 5 function void my_env::connect_phase(uvm_phase phase); super .connect_phase(phase); A_inst.A_ap.connect(B_inst.B_imp); A_inst.A_ap.connect(C_inst.C_imp); endfunction
上面只是一个analysis_port与IMP相连的例子。analysis_export和IMP也可以这样相连接,只需将上面例子中的uvm_analysis_port改为uvm_analysis_export就可以。
七、monitor与scoreboard之间的通信 和上一个一样,在两段分别进行定义,monitor的代码为:
1 2 3 4 5 6 7 8 task my_monitor::main_phase(uvm_phase phase); my_transaction tr; while (1 ) begin tr = new ("tr" ); collect_one_pkt(tr); ap.write(tr); end endtask
scoreboard的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function void my_scoreboard::write_monitor(my_transaction tr); my_transaction tmp_tran; bit result; if (expect_queue.size() > 0 ) begin tmp_tran = expect_queue.pop_front(); result = tr.compare(tmp_tran); if (result) begin `uvm_info("my_scoreboard" , "Compare SUCCESSFULLY" , UVM_LOW); end else begin `uvm_error("my_scoreboard" , "Compare FAILED" ); $display("the expect pkt is" ); tmp_tran.print(); $display("the actual pkt is" ); tr.print(); end end else begin `uvm_error("my_scoreboard" , "Received from DUT, while Expect Queue is empty" ); $display("the unexpected pkt is" ); tr.print(); end endfunction
之后在env中可以使用connect连接。 由于monitor与scoreboard在UVM树中并不是平等的兄妹关系,这里选择下面的连接方式: 在agent中声明一个ap,但是不实例化它,让其指向monitor中的ap。在env中可以直接连接agent的ap到scoreboard的imp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 agent: class my_agent extends uvm_agent ; uvm_analysis_port #(my_transaction) ap; ... function void my_agent::connect_phase(uvm_phase phase); ap = mon.ap; ... endfunction endclass env: function void my_env::connect_phase(uvm_phase phase); o_agt.ap.connect(scb.scb_imp); ... endfunction
在上面的例子中,scoreboard只接收一路数据。但在现实情况中,scoreboard除了接收monitor的数据之外,还要接收reference model的数据。相应的scoreboard就要再添加一个 uvm_analysis_imp的IMP。此时问题就出现了,由于接收到的两路数据应该做不同的处理,所以这个新的IMP也要有一个write任务与其对应。但是write只有一个,怎么办? 可以使用宏定义的方法:
1 2 3 4 5 6 7 8 9 10 11 12 `uvm_analysis_imp_decl(_monitor) `uvm_analysis_imp_decl(_model) class my_scoreboard extends uvm_scoreboard ; my_transaction expect_queue[$]; uvm_analysis_imp_monitor#(my_transaction, my_scoreboard) monitor_imp; uvm_analysis_imp_model#(my_transaction, my_scoreboard) model_imp; `uvm_component_utils(my_scoreboard) extern function new (string name, uvm_component parent = null ) ; extern virtual function void build_phase (uvm_phase phase) ; extern virtual task main_phase (uvm_phase phase) ; endclass
上述代码通过宏uvm_analysis_imp_decl声明了两个后缀_monitor和_model。 当与monitor_imp相连接的analysis_port执行write函数时,会自动调用write_monitor函数,而与model_imp相连接的analysis_port执行write 函数时,会自动调用write_model函数。
八、使用FIFO通信 使用fifo的方法能够让两个端口都能实现主动的接收,因此下面的例子便是利用FIFO来实现monitor和scoreboard的通信。 FIFO的本质是一块缓存加两个IMP。在monitor与FIFO的连接关系中,monitor中依然是analysis_port,FIFO中是uvm_analysis_imp,数据流和控制流的方向相同。在scoreboard与FIFO的连接关系中,scoreboard中使用blocking_get_port端口:
1 2 3 4 5 6 7 8 9 10 class my_scoreboard extends uvm_scoreboard; my_transaction expect_queue[$]; uvm_blocking_get_port #(my_transaction) exp_port[16]; uvm_blocking_get_port #(my_transaction) act_port; `uvm_component_utils(my_scoreboard) extern function new(string name, uvm_component parent = null); extern virtual function void build_phase(uvm_phase phase); extern virtual task main_phase(uvm_phase phase); endclass
而FIFO中使用的是一个get端口的IMP。在这种连接关系中,控制流是从scoreboard到FIFO,而数据流是从FIFO到scoreboard。
在env中连接方式如下:
1 2 3 4 5 6 7 8 9 10 11 function void my_env::connect_phase(uvm_phase phase); super.connect_phase(phase); i_agt.ap.connect(agt_mdl_fifo.analysis_export); mdl.port.connect(agt_mdl_fifo.blocking_get_export); for(int i = 0; i < 16; i++) begin mdl.ap[i].connect(mdl_scb_fifo[i].analysis_export); scb.exp_port[i].connect(mdl_scb_fifo[i].blocking_get_export); end o_agt.ap.connect(agt_scb_fifo.analysis_export); scb.act_port.connect(agt_scb_fifo.blocking_get_export); endfunction
FIFO中有两个IMP,但是在上面的连接关系中,FIFO中却是EXPORT,这是为什么呢?实际上,FIFO中的analysis_export和blocking_get_export虽然名字中有关键字export,但是其类型却是IMP。UVM为了掩饰IMP的存在,在它们的命名中加入了export关键字。 但事实上,FIFO上的端口并不局限于上述两个,一个FIFO中有众多的端口。端口列表如下:
总结 总结来说,这一章主要讲了数据在UVM中的传递方式,学习这一章可以更好的编写灵活性更高的UVM代码。