原文地址:http://bbs.eetop.cn/viewthread.php?tid=383872&extra=&authorid=828160&page=1
我相信很多朋友都在壇子里下載過一份《UVM1.1應用指南和源代碼分析》的資料,我很佩服這位前輩,我也從中收益匪淺,但是可惜在講解phase的時候對一些初學者來說有些東西跳躍性有點大,更主要的是沒有用一個稍微全面而復雜的例子來進行進一步的總結,讓讀者知道在實際項目中如何構建一個user-defined的phase組織架構圖。我在這里也是志在狗尾續貂吧,廢話少說,開始吧。
一、什么是phase?
phase翻譯成中文就是相、階段的意思。
在UVM中,官方的說法是:phase是使tb中各種各樣的component按照各自的需求可以階段性執行的一種自動化的機制。
簡單的說就是使驗證組件能夠按需自動化執行的一種機制。
二、OVM有phase的概念嗎?
有。
那為什么OVM中沒有看到在各個function 或者 task的形參表里看到(ovm_phase phase)呢?
因為OVM已經為用戶提供了現成的、固定的幾個phase,它們是 new phase(對一個合格的tb來說,new phase是被 ovm root component在執行run_test()的時候通過factory機制調用的,也就是通過靜態函數create調用的)、build phase、connect phase、end_of_elaboration phase、 start_of_simulation phase、run phase、extract phase、check phase、report phase。
OVM 沒有提供API給用戶進行一些操作,比如增加一個phase、讓不同的component的同一個phase異步的運行等等.......
三、UVM為什么在OVM的基礎上擴展、增強這個功能?
因為不夠用所以才需要擴展,因為不完美用才需要增強。
不完美體現在哪里?我提一點,其余的大家可以挖掘。
一個正常的DUT,從上電到正常工作到關閉是一個相對比較通用的流程,所以把這些比較通用的流程放在一個run phase里面可以但不完美,那我把run phase按照這些通用流程拆分成幾個子的階段不是更好么?比如拆分成reset phase、configure phase、main phase、shutdown phase。
不夠用體現在哪里?我同樣提一點,其余的大家繼續挖掘、跟帖。
一個大的chip里面有很多功能性比較獨立的模塊,這些功能性獨立意味着一個模塊A在run的時刻另一個模塊B可以不run,也可以run,B運行不運行和A運行不運行關聯度不大甚至沒有關聯,比如A是只負責處理發通路的而B只負責收通路;但是另一方面,功能性獨立並不意味着什么都獨立,舉例來說,A模塊和B模塊功能性很獨立,比如A是一個clock generation模塊,B是一個processor模塊,那在A沒有正常work的前提下B是不能正常工作的。
由此我們至少可以提出兩點需求:
(1)能不能讓B的reset phase發生在A的reset phase之后,這樣等待clock都穩定了再對B做reset操作或者release reset操作?
(2)能不能讓A的main phase和B的main phase異步的運行?
上面兩點至少需要用到UVM中的的如下機制:新建一個domain、給不同的component設置不同的domain、不同的domain之間phase的同步和異步。
再如,功能更復雜的tb可能需要新建一個user-defined的phase,讓它在某一需要個時刻點運行,可以是function性質的或者task性質的。
四、UVM phase的組織架構
(1)在大學學習數據結構的時候,我們知道有幾種基本的數據結構,其它的結構都可以分解成這幾種的組合;它們分別是線性數據結構(比如鏈表)、樹形結構(比如二叉樹)、圖形結構(有向圖和無向圖);UVM的phase的組織結構也不例外,都可以分解成上述三種。總體上來說,是按照有向圖結構進行組織的。
(2)既然是有向圖形結構,那么這個數據結構中自然有若干節點和連接這些節點的有向邊。另外,一個圖也可以分解為若干個子圖。
在默認情況下,這些節點就像鐵路的一個個站點,每個節點都有自己的屬性;這里所說的屬性就是指的是UVM庫中定義的如下幾種:
UVM_PHASE_DOMAIN:它的含義是從我開始,到后面的某個節點為止,這期間經過的所有節點都是我的管轄范圍。
UVM_PHASE_SCHEDULE:它的含義和UVM_PHASE_DOMAIN相同,唯一的區別就是它不具有獨立行動的權利,它的外面至少需要套一UVM_PHASE_DOMAIN;形象點說,UVM_PHASE_DOMAIN可以代表整個圖形結構或者代表某個子圖結構,但是,UVM_PHASE_SCHEDULE則只能代表某個子圖結構,它必須屬於某個DOMAIN,也就是它必須被某個DOMAIN wrapper起來!
UVM_PHASE_NODE:它的含義是圖形結構中的某一個節點,它是一個句柄,它自己不干具體的活(比如main phase具體要干什么)。
UVM_PHASE_IMP:它的含義就是說它所代表的就是具體干什么活,上述的UVM_PHASE_NODE就是會指向某一個UVM_PHASE_IMP。
UVM_PHASE_TERMINAL:它是用來標定UVM_PHASE_DOMAIN和UVM_PHASE_SCHEDULE的勢力范圍的!就是說我后面的節點就不是你們的管轄范圍了,只有一個domain或者schedule才有這個東西。
UVM_PHASE_NODE和UVM_PHASE_IMP的關系如果不好理解的話,你可以對照TLM中的port、export、和imp來理解,這樣就容易些了;像port、export只負責傳話或者發號施令,而imp才比較苦逼,是真正干活的。
有向圖中的邊在UVM phase里則代表了運行的順序,就是說這個節點過了,下個節點是哪些,我想這個應該比較好理解。
(3)默認UVM phase的具體的圖形結構:
common (UVM_PHASE_DOMAIN) id=204
|
build (UVM_PHASE_NODE) id=222
|
connect (UVM_PHASE_NODE) id=234
|
end_of_elaboration (UVM_PHASE_NODE) id=246
|
start_of_simulation (UVM_PHASE_NODE) id=258
/ \
run (UVM_PHASE_NODE) id=270 uvm (UVM_PHASE_DOMAIN) id=319
| |
| uvm_sched (UVM_PHASE_SCHEDULE) id=331
| |
| pre_reset (UVM_PHASE_NODE) id=349
| |
| reset (UVM_PHASE_NODE) id=361
| |
| post_reset (UVM_PHASE_NODE) id=373
| |
| pre_configure (UVM_PHASE_NODE) id=385
| |
| configure (UVM_PHASE_NODE) id=397
| |
| post_configure (UVM _PHASE_NODE) id=409
| |
| pre_main (UVM_PHASE_NODE) id=421
| |
| main (UVM_PHASE_NODE) id=433
| |
| post_main (UVM_PHASE_NODE) id=445
| |
| pre_shutdown (UVM_PHASE_NODE) id=457
| |
| shutdown (UVM_PHASE_NODE) id=469
| |
| post_shutdown (UVM_PHASE_NODE) id=481
| |
| uvm_sched_end (UVM_PHASE_TERMINAL) id=337
| |
| uvm_end (UVM_PHASE_TERMINAL) id=325
\ /
extract (UVM_PHASE_NODE) id=277
|
check (UVM_PHASE_NODE) id=289
|
report (UVM_PHASE_NODE) id=301
|
final (UVM_PHASE_NODE) id=313
|
common_end (UVM_PHASE_TERMINAL) id=210
這個圖的每個節點是通過調用m_print_successors()函數得到的,當然中間的“|”這些符號是我自己編輯的,這個函數在UVM class reference的pdf資料里沒有提到,應該是UVM開發人員自己調試用的,但是如果你自己閱讀代碼就知道有這個函數,因為它不是local的,所以我們可以隨時調用它。
每個節點的名字,屬性和id號都一清二楚,細心的朋友可能已經注意到了這些id號不是隨機的,而是有順序的,是的,它是按照這些節點的建立的先后順序分配的,id越大表示越晚建立,實際上這個id號就是通過調用get_inst_id()得來的,這個函數是uvm_object的一個基本函數,我就不多講這個了。
從這個圖中,我們可以得到如下幾個重要的信息:
(1)這張大的圖形結構中有兩個domain,一個叫做common,一個叫做uvm,這個概念很重要,以后的分析中會反復用到;
(2)UVM中新加的12個phase(不包括final phase),從pre reset到post shutdown都被一個叫做uvm_sched的 SCHEDULE包起來了,作為一個子圖隸屬於uvm domain;
(3)OVM和UVM中的run phase屬於common domain;
(4)start_of_simulation phase節點有兩個后繼節點:run phase node和uvm domain;
五、UVM的phase是如何自動運行起來的
從build phase到最后的final phase,自動運行會經過以下幾個主要步驟:
1、在tb_top.sv中的某個地方調用 run_test(),這個函數在uvm_root.sv中,是整個tb 樹形組織結構的根節點,這個函數主要是干三件事情:
(1)通過factory模式create你自己希望運行的testcase instance,不管你的testcase叫神馬名字,最后uvm都會給它重命名為“uvm_test_top”。
(2)調用uvm phase的一個函數,叫做m_run_phases(),等下再講這個函數主要干的事情。
(3)等待(2)結束,kill所有和(2)有關的進程,如果允許則調用$finish結束仿真,退出仿真器。
2、m_run_phases()主要干這么兩件事情:
(1)創建UVM默認的所有phases,即我們熟知的build phase、connect phase、run phase、main phase等等,並將其組織成我上面所講的那個有向圖結構;實現這一步主要是靠調用uvm_domain class中的get_common_domain()。
(2)開始運行(1)創建組織好的phases,當然如果有用戶自定義的phase,也會在規定的點運行起來;實現這一步主要是靠調用對應的phase execute_phase()函數和一個無限循環的監控進程。
3、uvm_domain中的get_common_domain()主要是干這么三件事情:
(1)創建我上述phase有向圖結構的第一部分,即build->connect->end_of_elaboration->start_of_simulation->run->extract->check->report->final。很自然的實現這個功能需要用到兩個函數:創建它們用new()函數,組織它們讓它們有如此先后順序用add()函數。
(2)創建我上述phase有向圖結構的第二部分,即
pre_reset->reset->post_reset->pre_configure->configure->post_configure->pre_main->main->post_main->
pre_shutdown->shutdown->post_shutdown。很自然的實現這個功能需也要用到兩個函數:創建它們用new()函數,組織它們讓它們有如此先后順序用add()函數。
(3)正如phase有向圖結構所示,(2)搞出來的東西是作為一個子圖呈現在(1)所搞出來的結構中的;那么怎嘛讓(2)搞出來的東西就成了(1)所搞出來的東西的一部分了呢?而且還要和run phase並行的運行呢?還是要靠這個add()函數!只不過需要用到add()函數形參表中一個叫做with_phase的東西。
顯而易見,這個定義在uvm phase class中的add()函數真是無比的重要,正式它讓我們可以根據需要把一個個phase節點組織成一個任意的被UVM 規則所允許的圖形結構,所以既然它這么重要,我們下一貼就准備專門來談談它!
4、phase的execute_phase()函數主要干哪些事情呢?
簡單點說就是按照phase的有向圖結構一個一個節點的走下去,知道結束;當然這個走的過程是復雜的、曲折的,呵呵,此函數也是比較 復雜、曲折的;但是有一點,也是最主要最重要的一點,那就是它會調用每個節點所擁有的traverse()函數!正式通過這個函數,我們在各個phase中所寫的自己的代碼才得意被調用、執行!
既然這個函數也是如此的重要,那我將用另一個帖子來專門討論它一下。
5、traverse()函數主要干的事情就是根據每個節點的屬性來調用我們自己重載的各個phase。
主要是兩個函數一個是exec_func(uvm_component comp, uvm_phase phase),一個是exec_task(uvm_component comp, uvm_phase phase)。很自然的我們想到,執行build phase這種function性質的就會調用exec_func(),執行如main phase這種task性質的就會調用exec_task(),沒錯,的確如此!
注意到了這兩個函數的形參表了么?我想大家都猜出它們是干什么用的吧。比如component A的build phase在執行,那么形參comp就是A,形參phase就是build。
六、UVM phase class中的add()函數
1、函數原型
1 function void uvm_phase::add(uvm_phase phase, 2 uvm_phase with_phase=null, 3 uvm_phase after_phase=null, 4 uvm_phase before_phase=null);
比如調用是 A.add(B,C)就表示把B加入到A的大家庭中,在A這個大家庭中的位置如何取決於后面的三個參數,根據它們的名字我想不難知道它們的意思。
2、函數主體一:參數有效性判斷
(1)
1 if (phase == null) 2 `uvm_fatal("PH/NULL", "add: phase argument is null");
這個含義很明顯,如果你想加的這個phase還沒有創建,你做這個操作是毫無意義的,simulator會直接退出。
(2)
1 if (with_phase != null && with_phase.get_phase_type() == UVM_PHASE_IMP) 2 begin
3 string nm = with_phase.get_name(); 4 with_phase = find(with_phase); 5 if (with_phase == null) 6 `uvm_fatal("PH_BAD_ADD",{"cannot find with_phase '",nm,"' within node '",get_name(),"'"}); 7 end
這個檢查理解起來也不困難,以A.add(B,C)來說,意思就是說你想把B加入到A中來,並且想讓B和C可以並行的運行,首先要確保在A中確實有C這個人才行,不然這個操作肯定也會有問題,而且也是fatal。
(3)
1 if (before_phase != null && before_phase.get_phase_type() == UVM_PHASE_IMP) begin
2 string nm = before_phase.get_name(); 3 before_phase = find(before_phase); 4 if (before_phase == null) 5 `uvm_fatal("PH_BAD_ADD",{"cannot find before_phase '",nm,"' within node '",get_name(),"'"}); 6 end
(4)
1 if (after_phase != null && after_phase.get_phase_type() == UVM_PHASE_IMP) begin
2 string nm = after_phase.get_name(); 3 after_phase = find(after_phase); 4 if (after_phase == null) 5 `uvm_fatal("PH_BAD_ADD",{"cannot find after_phase '",nm,"' within node '",get_name(),"'"}); 6 end
有了(2)的解釋,(3)和(4)想必就不用再解釋了吧。
(5)
1 if (with_phase != null && (after_phase != null || before_phase != null)) 2 `uvm_fatal("PH_BAD_ADD", "cannot specify both 'with' and 'before/after' phase relationships");
這個含義就是說,你說明了用with_phase(或者說用after_phase/before_phase)這個參數已經可以確定你想要放置的位置了,不需要也不允許在用后面兩個參數進行進一步框定了,這個主要是為了防止一些低級失誤。這個就好比說,數值A的值等於圓周率,並且數值A介於3和4之間;前半句已經可以確定A是多少了,后面那句是廢話;萬一我后半句由於筆誤寫成了並且數值A介於4和5之間,那不是麻煩了。
(6)
1 if (before_phase == this || after_phase == m_end_node || with_phase == m_end_node) 2 `uvm_fatal("PH_BAD_ADD","cannot add before begin node, after end node, or with end nodes");
這個檢查的意思是說:before_phase == this,表示你想插入到我當前這個domain或者schedule的祖先節點的前面,那是不允許的,this表示了這個domain或者schedule的樹根。
after_phase == m_end_node,表示你想插入到我當前這個domain或者schedule的最后一個終結節點的后面,m_end_node表示了這個domain或者schedule的終結節點,注意這個終結節點的類型是UVM_PHASE_TERMINAL而不是UVM_PHASE_NDOE,with_phase == m_end_node,表示你想和我的終結節點並行運行也是不行的,為什么?因為UVM_PHASE_TERMINAL類型節點(和UVM_PHASE_DOMAIN類型節點以及UVM_PHASE_SCHEDULE類型節點)都是不運行的,也就是說它們三個的運行時間是0。
為什么是這樣,這個會在后面的execute_phase()函數中看到UVM是這么做的。
3、函數主體二:根據插入節點的類型來進行插入位置的框定
1 if (phase.get_phase_type() == UVM_PHASE_IMP) begin
2 new_node = new(phase.get_name(),UVM_PHASE_NODE,this); 3 new_node.m_imp = phase; 4 begin_node = new_node; 5 end_node = new_node; 6 end
7 else begin
8 begin_node = phase; 9 end_node = phase.m_end_node; 10 phase.m_parent = this; 11 end
這個if else看似不多,也不復雜,其實需要你對UVM phase的組織流程圖、UVM phase的各種類型的節點如NODE、DOMAIN、TERMINATER等真正含義的准確理解,不然的話你會覺得這個if else 的內容很奇怪!
其實我個人覺得這個 if else是這個函數的難點,其它的很容易懂!
以A.add(B,C)來解釋。
(1)首先解釋幾個變量的意思
begin_node,是這個函數的局部變量,它表示了B這個子結構中的開始節點是哪一個。
end_node,是這個函數的局部變量,它表示了B這個子結構中的結束節點是哪一個。
phase就是這里B。
m_parent是uvm phase class的一個變量,它表示了包含B的更大一層范疇的結構,注意不是父節點的意思;比如說在圖形結構中,子圖A屬於更大一點的子圖B,而B又屬於整個圖C的一部分,那么A的m_parent就是B,B的m_parent就是C,再次提請注意,是范疇的概念不是單純的父節點的概念。
m_imp也是uvm phase class的一個變量,我們知道在uvm節點圖形結構中的每個節點都有一個屬性,或者是UVM_PHASE_DOMAIN,或者是UVM_PHASE_NODE,或者是UVM_PHASE_TERMINAL等,但不會是UVM_PHASE_IMP類型;這其中的UVM_PHASE_NODE就是一個指針,它所指向的那個東西的類型如果是UVM_PHASE_IMP的話,那么這個指針的m_imp就是那個UVM_PHASE_IMP類型的節點;那么有人可能會問,我也問過的一個問題就是,為什么要這么做,我直接把UVM_PHASE_IMP類型的節點插進來不就得了,為什么要插入它的指針,然后需要的時候再用這個指針索引?豈不麻煩和多此一舉?我個人覺得可能這樣做以后的靈活性和可擴展性會更好,因為我們知道面向對象的最大的威力是組合而不是繼承,我們可以想想模式設計中的brige模式和strategy模式(當然還有很多),就是用這種類似的機制完成了一些繼承不能做到或者大大增強了以后的可擴展性和靈活性。
(2)if 分支
如果插入進入的子圖(注意我改用子圖而不是節點了)類型是UVM_PHASE_IMP,那么new一個指向它的句柄,句柄本身的類型設定成UVM_PHASE_NODE,然后有三句賦值語句,第一句的意思前面已經在解釋變量的時候解釋過了,那么第二句和第三局是神馬意思?其實意思很簡單,只要你清楚一件事實,那就是只有一個節點的東西也叫做子圖或圖,而不是說非要有很多節點,節點之間或串行或並行的那種復雜的結構才叫子圖或者圖;只有一個節點的結構是最簡單的圖形結構。在次情況下,它既是begin節點也是end節點,這樣那兩句賦值語句就了然了。
(3)else分支
有了對(2)的理解,這個分支就不難理解了,能進入else分支就表示要進入的這個東西或者說子圖肯定不是if分支中的簡單情況,至少有兩個節點或者以上。那好了,begin節點就是這個子圖的句柄,end節點就是這個子圖的end節點,你加入了我,那么我當然是你的上級,所以m_parent=this就是這個意思。
有人讀上面(3)的某些話可能覺得讀不懂,特別是“begin節點就是這個子圖的句柄”這句話,對這句話的理解涉及到對uvm phase圖形結構的理解,比如說有個圖形結構叫做A,它有兩個節點B和C,按道理來說這個圖形結構就是 A->B就完了,但是UVM不是這么做的,它為了處理的方便會在生成圖形結構的時候多搞兩個節點:一個開始節點,一個終結節點;也就是uvm會生成如下圖形結構:
A->B->C->終結節點。
如果覺得還不好理解的話,我再舉個特別形象例子,C語言中的字符串。
比如說一個“hello”的C字符串,
上面的A就相當於取“hello”的地址,即A=&“hello”,
上面的終結節點就相當於C的 '\0',
上面的B和C就相當於字符串的真正的內容hello。
4、函數主體三:默認情況
1 if (with_phase == null && after_phase == null && before_phase == null) begin
2 before_phase = m_end_node; 3 end
就是說,如果你就是用A.add(B)這種形式的話,那么B會加到A的最后面,就相當於C語言字符串中加入字符,如果A=“hello”,B=“uvm”,那么A.add(B)執行時,上面的before_phase = m_end_node;就相當於before_phase=‘\0’,執行完成之后變成了 "hellouvm"。
5、函數主體四:add操作的真正部分
這部分代碼我就不貼了,這個add函數剩余的部分都是的,反而我覺得這部分最沒什么好講的,難的反而是類型檢查啊、位置定位啊這些前戲。
這部分代碼無非就是一些后繼和前驅的更新而已,如果你了解數據結構中的鏈表的插入和刪除操作,那么這段代碼無非就是C換成了SV,沒其他別的東西。如有一個鏈表L,內容是A->B->C,如果想把D插入到B的前面A的后面,那么需要
step1:temp = A.后繼
step2:D.后繼 = temp
step3:D.前驅 = A
step4:A.后繼 = D
如此而已。
add()函數討論到這里我覺得對我們以后應用來說也差不多夠了,到此為了吧。
這一貼就討論到這里,很多細節我沒有講,我覺得抓住主線了,那么枝節的東西不難理解,而且UVM class的源代碼隨處可下,要想弄個清楚明白,讀源代碼是必不可少的。如果設計到這一貼中的某些細節需要了解,可以聯系我,我們私下討論。