系列文章目錄
【UVM學習筆記】UVM基礎—一文告訴你UVM的組成部分
【UVM學習筆記】UVM中的“類”
文章目錄
- 系列文章目錄
- 前言
- 一、TLM是什么?
- 二、put操作
- 2.1、建立PORT和EXPORT的連接
- 2.2 IMP組件
- 三、get操作
- 四、transport端口
- 五、nonblocking端口
- 六、analysis端口
- 七、monitor與scoreboard之間的通信
- 八、使用FIFO通信
- 總結
前言
該專題用于記錄學習UVM芯片驗證的過程,主要學習書籍為經典的《UVM實戰》,同時也會去進行一些UVM的項目聯系。
一、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的代碼部分:
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);endfunctionextern function void build_phase(uvm_phase phase);extern virtual task main_phase(uvm_phase phase);
endclassfunction void A::build_phase(uvm_phase phase);super.build_phase(phase);A_port = new("A_port", this);
endfunctiontask A::main_phase(uvm_phase phase);
endtask
然后得到B的代碼:
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);endfunctionextern function void build_phase(uvm_phase phase);extern virtual task main_phase(uvm_phase phase);
endclassfunction void B::build_phase(uvm_phase phase);super.build_phase(phase);B_export = new("B_export", this);
endfunctiontask B::main_phase(uvm_phase phase);
endtask
然后在env將兩者進行鏈接
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);endfunctionvirtual 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);endfunctionextern virtual function void connect_phase(uvm_phase phase);`uvm_component_utils(my_env)
endclassfunction 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的代碼變為:
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中需要改動的要多一點:
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);endfunctionextern 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);
endclassfunction void B::build_phase(uvm_phase phase);super.build_phase(phase);B_export = new("B_export", this);B_imp = new("B_imp", this);
endfunctionfunction void B::connect_phase(uvm_phase phase);super.connect_phase(phase);B_export.connect(B_imp);
endfunctionfunction 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的代碼為:
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);endfunctionextern 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);
endclassfunction void A::build_phase(uvm_phase phase);super.build_phase(phase);A_export = new("A_export", this);A_imp = new("A_imp", this);
endfunctionfunction void A::connect_phase(uvm_phase phase);super.connect_phase(phase);A_export.connect(A_imp);
endfunctiontask A::get(output my_transaction tr);while(tr_q.size() == 0) #2;tr = tr_q.pop_front();
endtasktask 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的部分:
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);endfunctionextern function void build_phase(uvm_phase phase);extern virtual task main_phase(uvm_phase phase);
endclassfunction void B::build_phase(uvm_phase phase);super.build_phase(phase);B_port = new("B_port", this);
endfunctiontask B::main_phase(uvm_phase phase);my_transaction tr;while(1) beginB_port.get(tr);`uvm_info("B", "get a transaction", UVM_LOW) tr.print();end
endtask
在這些連接關系中,需要謹記的是連接的終點必須是一個IMP。
四、transport端口
A代碼如下所示:
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:
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);endfunctionextern function void build_phase(uvm_phase phase);extern task transport(my_transaction req, output my_transaction rsp);
endclassfunction void B::build_phase(uvm_phase phase);super.build_phase(phase);B_imp = new("B_imp", this);
endfunctiontask B::transport(my_transaction req, output my_transaction rsp);`uvm_info("B", "receive a transaction", UVM_LOW) req.print();//do something according to req#5;rsp = new("rsp");
endtask
env中的代碼是:
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端口
task A::main_phase(uvm_phase phase);my_transaction tr;repeat(10) begintr = 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的代碼:
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);endfunctionextern function void build_phase(uvm_phase phase);extern virtual task main_phase(uvm_phase phase);
endclassfunction void A::build_phase(uvm_phase phase);super.build_phase(phase);A_ap = new("A_ap", this);
endfunctiontask 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的代碼為:
function void B::write(my_transaction tr);`uvm_info("B", "receive a transaction", UVM_LOW) tr.print();
endfunction
在env中通過下面方式進行連接:
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的代碼為:
task my_monitor::main_phase(uvm_phase phase);my_transaction tr;while(1) begintr = new("tr");collect_one_pkt(tr);ap.write(tr);end
endtask
scoreboard的代碼為:
function void my_scoreboard::write_monitor(my_transaction tr);my_transaction tmp_tran;bit result;if(expect_queue.size() > 0) begintmp_tran = expect_queue.pop_front();result = tr.compare(tmp_tran);if(result) begin `uvm_info("my_scoreboard", "Compare SUCCESSFULLY", UVM_LOW);endelse begin`uvm_error("my_scoreboard", "Compare FAILED");$display("the expect pkt is");tmp_tran.print();$display("the actual pkt is");tr.print();endendelse 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:
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只有一個,怎么辦?
可以使用宏定義的方法:
`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端口:
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中連接方式如下:
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++) beginmdl.ap[i].connect(mdl_scb_fifo[i].analysis_export);scb.exp_port[i].connect(mdl_scb_fifo[i].blocking_get_export);endo_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代碼。