向UVM-TLM通信發(fā)起決戰(zhàn)
前言
開(kāi)頭想先說(shuō)點(diǎn)體會(huì),最早學(xué)習(xí)uvm時(shí),TLM通信這一章,自己最開(kāi)始并沒(méi)有很重視。到了親自搭驗(yàn)證環(huán)境時(shí)才發(fā)現(xiàn)TLM至關(guān)重要,沒(méi)有TLM,產(chǎn)生的事務(wù)無(wú)法在各個(gè)驗(yàn)證組件之間流通。這就好比人空有一副骨架,但沒(méi)有血液在流通。不掌握TLM機(jī)制,會(huì)導(dǎo)致很多代碼看不明白。
個(gè)人總結(jié)的TLM的難點(diǎn)如下:
- 端口類(lèi)非常的多,且大量用到參數(shù)化的類(lèi)這種寫(xiě)法
- 組件之間的連接該用那個(gè)端口類(lèi)比較模糊,沒(méi)有一個(gè)清晰的架構(gòu)圖。
基于上述自己的短板,所以有了這篇文章,希望能對(duì)這部分有個(gè)全面的梳理和了解。
梳理的結(jié)果有三部分:
- 理論概念部分:port,export,imp的關(guān)系
- 淺析源代碼部分:TLM端口類(lèi)的類(lèi)庫(kù)地圖,并根據(jù)源代碼淺析他們之間的繼承關(guān)系
- 實(shí)戰(zhàn)部分:uvm驗(yàn)證環(huán)境端口連接關(guān)系圖,這對(duì)于實(shí)際搭建uvm驗(yàn)證環(huán)境端口類(lèi)的選取非常有用
一、理論概念部分
1.1 uvm1.2中,TLM1.0和TLM2.0的關(guān)系?
去看uvm_pkg.sv中的內(nèi)容,會(huì)發(fā)現(xiàn)里面同時(shí)包括了tlm1和tlm2

去看tlm1和tlm2中的內(nèi)容,發(fā)現(xiàn)tlm2并不是對(duì)tlm1中的類(lèi)進(jìn)行了完善和優(yōu)化,tlm1中出現(xiàn)的類(lèi),在tlm2中沒(méi)有再出現(xiàn),tlm2中定義了tlm1中完全沒(méi)有的全新的類(lèi)。所以tlm2.0是完全的新的東西。
而對(duì)于uvm的初級(jí)使用,基本上只會(huì)涉及到tlm1.0的內(nèi)容。tlm2.0是更高階的玩法。因此本篇只解析TLM1.0,僅TLM1.0就夠我喝一壺的了。
接口與方法差異:
- TLM 1.0 定義了諸如
put()、get()、peek()等基礎(chǔ)的傳輸方法,組件之間的通信基于這些簡(jiǎn)單的方法實(shí)現(xiàn)。例如,一個(gè)發(fā)起方組件調(diào)用put()方法將事務(wù)發(fā)送給目標(biāo)方組件。- TLM 2.0 引入了新的接口和方法,其通信機(jī)制更為復(fù)雜和精確。它有
nb_transport_fw()、nb_transport_bw()等方法,用于支持雙向通信和精確的時(shí)序建模。這些新的接口和方法與 TLM 1.0 的接口在功能和使用方式上有很大不同,不能直接相互調(diào)用。
時(shí)序建模方式差異:
- TLM 1.0 對(duì)時(shí)序的支持非常有限,通常不考慮事務(wù)傳輸?shù)木_時(shí)間,主要關(guān)注功能驗(yàn)證。
- TLM 2.0 強(qiáng)調(diào)精確的時(shí)序建模,通過(guò)時(shí)間注解來(lái)描述事務(wù)的傳輸時(shí)間和延遲。這種差異使得 TLM 1.0 組件和 TLM 2.0 組件在處理時(shí)序時(shí)難以直接協(xié)同工作。
1.2 TLM1端口類(lèi)型及端口方法
首先,端口是不能單獨(dú)存在的,它必須是被例化在某個(gè)component中。端口就相當(dāng)于是component的門(mén),而門(mén)不能是孤立的單獨(dú)存在。 要想使用TLM機(jī)制,就必須要在對(duì)應(yīng)的組件中創(chuàng)建相應(yīng)的端口,TLM的功能是通過(guò)端口來(lái)實(shí)現(xiàn)的。
component就像房子,而端口就是房子的門(mén)和窗戶。如果沒(méi)有端口,component之間就是孤立的
在TLM機(jī)制中,擁有port類(lèi)型端口的組件是發(fā)起操作的主動(dòng)方(master),而擁有imp類(lèi)型端口的組件是被動(dòng)方(slave)
端口類(lèi)型分為
- port,export,imp
- analysis_port,analysis_export,analysis_imp
總體分為兩大類(lèi),帶analysis的和不帶analysis的。
兩類(lèi)端口之間不互通。不能混用,即非analysis端口之間可以互相連接,analysis端口之間可以互相連接。但是非analysis端口和analysis端口之間不能互相連接
不帶analysis的端口,只能單個(gè)點(diǎn)對(duì)點(diǎn)連接。優(yōu)先級(jí)port>export>imp。連接方式有:
port.connect(port)port.connect(export)port.connect(imp) export.connect(export)export.connect(imp)帶有analysis類(lèi)型的端口表示可以一對(duì)多連接。其他情況與非analysis端口保持一致


端口阻塞方法(阻塞當(dāng)前進(jìn)程):
| put(input T t) | 發(fā)起端把數(shù)據(jù)包發(fā)送給目標(biāo)端。數(shù)據(jù)流向?yàn)榘l(fā)起段到目標(biāo)端 task類(lèi)型,阻塞當(dāng)前進(jìn)程,直到trans發(fā)送成功 |
| get(output T t) | 發(fā)起段向目標(biāo)端索要數(shù)據(jù)包。數(shù)據(jù)流向?yàn)槟繕?biāo)端到發(fā)起端 task類(lèi)型,阻塞當(dāng)前進(jìn)程,直到trans獲取成功 |
| peek(output T t) | 和get方法一樣,區(qū)別是peek是獲取數(shù)據(jù)包的復(fù)制包 task類(lèi)型,阻塞當(dāng)前進(jìn)程,直到trans獲取成功 |
端口非阻塞方法(不阻塞當(dāng)前進(jìn)程):
| try_put(input T t) | 嘗試發(fā)送trans,執(zhí)行成功返回1,失敗返回0 function類(lèi)型,不會(huì)阻塞當(dāng)前進(jìn)程,傳輸可能成功也可能失敗 |
| can_put() | 檢查接收端是否準(zhǔn)備好了接收事務(wù),執(zhí)行成功返回1,失敗返回0 function類(lèi)型,不會(huì)阻塞當(dāng)前進(jìn)程 |
| try_get(output T t) | 嘗試獲取trans,function類(lèi)型,其他同上 |
| can_get() | 檢查對(duì)方是否能夠返回事務(wù),function類(lèi)型 |
| try_peek(output T t) | function類(lèi)型,其他同上 |
| can_peek() | function類(lèi)型,其他同上 |
| write() | 為analysis類(lèi)型端口獨(dú)有且僅有的方法,非阻塞 TODO |
幾點(diǎn)結(jié)論:
- port,export,imp體現(xiàn)的是控制流而非數(shù)據(jù)流。地位優(yōu)先級(jí)port>export>imp。put操作中,數(shù)據(jù)從port流向imp,get操作中,數(shù)據(jù)從imp流向port,這類(lèi)似于master發(fā)起的寫(xiě)和讀。操作都是由地位更高的master來(lái)發(fā)起的。analysis類(lèi)型的同理。
- 在兩個(gè)端口的連接過(guò)程中,只有高優(yōu)先級(jí)的才能調(diào)用connect函數(shù),而相對(duì)低優(yōu)先級(jí)的只能作為connect函數(shù)的參數(shù)。不能倒反天罡。
- connect函數(shù)調(diào)用要有始有終。開(kāi)始一定是port發(fā)起,結(jié)束一定是imp端口。如果port和imp直接連接,那么總共調(diào)用一次connect函數(shù)port.connect(imp)。如果中間有export。這條連接線的結(jié)尾一定不能是export,export還需要連到最終的imp上。port.connect(export),export.connect(imp)。
1.3 TLM原理

以put方法為例,TLM傳輸可以分為幾個(gè)步驟:
- 在connect phase中通過(guò)connect函數(shù)將端口連接
- 主控方的端口發(fā)起操作,調(diào)用put任務(wù)
- 在port端口的put任務(wù)中,調(diào)用了與其連接的export端口的put任務(wù)
- 在export端口的put任務(wù)中,調(diào)用了與其連接的imp端口的put任務(wù)
- 在imp端口的put任務(wù)中,調(diào)用了imp所在的component的put任務(wù)
上述的原理是基于概念圖的抽象解釋,于是引發(fā)了如下疑問(wèn):
- 這些方法在原型類(lèi)中都是空的,最終的實(shí)現(xiàn)都是在imp所在的component即slaveB中完成的。那么slaveB中的put()任務(wù)究竟該怎么寫(xiě)?
- 源頭的port端口調(diào)用put任務(wù)后,是如何引發(fā)后續(xù)端口的put任務(wù)的?
其實(shí)在port端口的put任務(wù)中,調(diào)用了與之相連的export的put任務(wù)。而在export的put任務(wù)中,又調(diào)用了與之相連的imp端口的put任務(wù)。imp的put任務(wù)中又調(diào)用了imp所在的component的put任務(wù)。
而port端口的put任務(wù)中,之所以能調(diào)用export的put任務(wù)。根本前提是通過(guò)connect函數(shù)將port和export連接成功之后,port端口類(lèi)中拿到了指向export端口的句柄。其他同理。這部分的具體實(shí)現(xiàn)將在下文中源代碼解析部分詳細(xì)闡述。
到此為止,通過(guò)一些抽象的圖初步理解了TLM通信的基本原理。但是如果真正去看一個(gè)uvm驗(yàn)證環(huán)境,會(huì)發(fā)現(xiàn)還是看不懂,會(huì)看到各種奇奇怪怪的類(lèi)出現(xiàn)。只根據(jù)幾個(gè)概念圖去了解原理,而不去看真正的代碼實(shí)現(xiàn),根本不能叫做理解TLM機(jī)制!!!
uvm中,基于上面提到的各種端口類(lèi)型都提供了一系列的類(lèi)。實(shí)際驗(yàn)證環(huán)境中,各種端口類(lèi)都是基于uvm提供的這些類(lèi)來(lái)例化的。
因此,必須從源頭出發(fā),全面透徹的梳理TLM1中的類(lèi),清楚有哪些類(lèi),這些類(lèi)可以分為幾部分?這些類(lèi)之間的繼承關(guān)系是什么?在實(shí)際搭建環(huán)境時(shí),該選取什么樣的類(lèi)?
二、走到代碼中去
2.1 TLM1中用到的class全覽
tlm1中用到的所有類(lèi)都集中在了這些文件中了,從上往下看可以分為四個(gè)部分
2.2 TLM1中的端口類(lèi)
uvm_tlm_if_base#(T1,T2) ★★★
這個(gè)類(lèi)是tlm1中所有端口類(lèi)的基類(lèi)。注意,該類(lèi)是最基礎(chǔ)的基類(lèi),它沒(méi)有從任何類(lèi)擴(kuò)展而來(lái),注意端口類(lèi)并不是從uvm_object或者uvm_component擴(kuò)展而來(lái)的

這個(gè)類(lèi)的主要作用是將端口的最基礎(chǔ)的方法做了封裝。該類(lèi)中包含的方法有:

put(),get(),peek(),try_put(),can_put(),try_get(),can_get(),try_peek(),can_peek(), transport(),nb_transport(),write()
uvm_tlm_if_base是一個(gè)virtual class。不能被實(shí)例化,只是做了一些定義,其內(nèi)部的虛方法也只是聲明了名字,方法內(nèi)部只有一句uvm_report_error。
這意味著從uvm_tlm_if_base擴(kuò)展而來(lái)的子類(lèi),如果要調(diào)用這些方法,必須對(duì)這些虛方法進(jìn)行重寫(xiě)。否則將會(huì)報(bào)錯(cuò)
uvm_sqr_if_base#(T1,T2) ★★★

uvm_sqr_if_base也是一個(gè)基類(lèi),它不從任何類(lèi)擴(kuò)展而來(lái),和uvm_tlm_if_base的地位是一樣的。而且這個(gè)類(lèi)是專門(mén)用于sequence,sequencer,driver之間的通信的。 該類(lèi)的作用也是封裝了一些方法:

get_next_item(),try_next_item(),item_done(),wait_for_sequences(), has_do_available(), get(),peek(),put(),put_response(),disable_auto_item_recording(),is_auto_item_recording_enabled()
同樣,這些方法必須在子類(lèi)中被重寫(xiě),否則將會(huì)引發(fā)uvm_report_error
uvm_port_component_base

這個(gè)類(lèi)繼承自u(píng)vm_component,是個(gè)抽象類(lèi),內(nèi)部定義了一些純虛方法:
get_connected_to(),get_provided_to(),is_port(),is_export(),is_imp
這個(gè)類(lèi)是干什么用的?
uvm_port_component#(PORT)
繼承自u(píng)vm_port_component_base,所以也是一個(gè)uvm_component類(lèi)。會(huì)傳入一個(gè)PORT參數(shù),在類(lèi)內(nèi)部,PORT類(lèi)會(huì)聲明一個(gè)句柄m_port,uvm_port_component類(lèi)在實(shí)例化時(shí)會(huì)傳入一個(gè)PORT類(lèi)的句柄port。 最終m_port指向的是傳入的port。
這個(gè)類(lèi)中所有的function和task都是圍繞m_port展開(kāi)的。


這里的函數(shù)調(diào)用很有意思,這里定義的函數(shù)是is_port,是對(duì)父類(lèi)uvm_port_component_base類(lèi)中純虛方法is_port的重寫(xiě)。函數(shù)內(nèi)部調(diào)用的是m_port.is_port()。這里比較奇怪,如果PORT默認(rèn)是uvm_object類(lèi)型的話,是沒(méi)有is_port()這個(gè)函數(shù)的。所以參數(shù)化的類(lèi),傳入的參數(shù),是根據(jù)實(shí)際傳入的類(lèi)型來(lái)看的
父類(lèi)中如果有純虛方法(pure virtual function),子類(lèi)在繼承時(shí),必須對(duì)純虛方法進(jìn)行實(shí)現(xiàn)
uvm_port_base#(IF) ★★★★★
uvm_port_base是整個(gè)TLM1中所有類(lèi)的核心。這個(gè)類(lèi)極其極其重要,后面會(huì)發(fā)現(xiàn)所有的port,export,imp的端口類(lèi)都是從此類(lèi)擴(kuò)展而來(lái)。

uvm_port_base#(IF)類(lèi)的聲明很有意思,給該類(lèi)傳入一個(gè)參數(shù)IF,該參數(shù)作為uvm_port_base的父類(lèi)。及uvm_port_base具有了IF類(lèi)的全部特性。
uvm_port_base在實(shí)際使用時(shí),傳入的IF參數(shù)要么是uvm_tlm_if_base#(T,T),要么是uvm_seqr_if_base(T,T)。T為該端口實(shí)際要處理的transaction事務(wù)類(lèi)
在該類(lèi)中,還有一個(gè)寫(xiě)的巧妙的地方。在uvm_port_base類(lèi)內(nèi)部,typedef這個(gè)類(lèi)為this_type,并將this_type作為uvm_port_component的PORT參數(shù)傳入。 聲明了一個(gè)uvm_port_component的句柄,m_comp
在該類(lèi)中,定義了非常多的方法。列舉一部分:
is_port(),is_export(),is_imp(),connect()
connect函數(shù)是整個(gè)的核心。port和export,port和imp,export和export,export和imp之間的連接都是通過(guò)connect函數(shù)實(shí)現(xiàn)的。整個(gè)的實(shí)現(xiàn)方法是非常復(fù)雜的,這里不深入研究。
uvm_*_port#(T)
在實(shí)際使用時(shí),傳入的參數(shù)類(lèi)T為該端口要處理的transaction事務(wù)類(lèi)

port類(lèi)型的端口類(lèi)非常多,統(tǒng)計(jì)共有23個(gè),看著眼花繚亂,其實(shí)有規(guī)律可循,可以分成如下幾個(gè)部分。

圖片來(lái)自于博客:UVM Tutorial for Candy Lovers – 20. TLM 1 – ClueLogic
什么情況下該用什么樣的port類(lèi)?
上面的圖把繼承關(guān)系描述的非常清楚,最右側(cè)是擴(kuò)展而來(lái)的23個(gè)子類(lèi),這些子類(lèi)之間的本質(zhì)區(qū)別在于內(nèi)部封裝的方法不同。
這里以put_port為例,看上面的類(lèi)圖,從名字上會(huì)發(fā)現(xiàn)有如下規(guī)律:
uvm_blocking_put_port#(T),uvm_nonblocking_put_port#(T),uvm_put_port#(T),這三個(gè)都含有put,從源代碼可以看出,它們是封裝了*put方法相關(guān)的類(lèi)



這三個(gè)類(lèi)之間的區(qū)別在于,封裝的方法類(lèi)型不一樣。上面講到了put()任務(wù)為阻塞類(lèi)型,而try_put()方法和can_put()方法為非阻塞類(lèi)型。
- 從名字可以看出,uvm_blocking_put_port#(T)為阻塞類(lèi),所以其只能包含阻塞方法put()。
- uvm_nonblocking_put_port#(T)為非阻塞類(lèi),所以其只能包含非阻塞方法,try_put(),can_put()
- uvm_put_port#(T)既可以阻塞,也可以非阻塞,所以三種都包括
因此,一個(gè)component使用什么樣的端口,取決于主控方想要發(fā)起的操作是什么。如果masterA的端口想要執(zhí)行put()任務(wù),那么只能使用uvm_blocking_put_port#(T)和uvm_put_port#(T)這兩類(lèi)端口。如果masterA的端口想要執(zhí)行try_put()或者can_put(),那么只能使用uvm_nonblocking_put_port#(T)的端口類(lèi)。
在文件中看其他的類(lèi),和上面所說(shuō)的邏輯是完全一樣的,這里不再贅述,通過(guò)一張圖來(lái)進(jìn)行整理

uvm_*_export(T)
在實(shí)際使用時(shí),傳入的參數(shù)T為該端口要處理的transaction事務(wù)類(lèi)

export類(lèi)型的端口類(lèi)從uvm_port_base繼承而來(lái),也有23個(gè)子類(lèi),可以發(fā)現(xiàn)它們和port類(lèi)型的端口一一對(duì)應(yīng)。
數(shù)量一一對(duì)應(yīng)是必須的,必須保證使用的端口類(lèi)的前綴完全一致。因?yàn)樵谑褂胏onnect函數(shù)連接時(shí),會(huì)先檢查連接的端口類(lèi)型的前綴是否一致。否則會(huì)報(bào)錯(cuò)
如uvm_nonblocking_get_port#(T)的前綴是uvm_nonblocking_get_。它只能和uvm_nonblocking_get_export#(T)或者uvm_nonblocking_get_imp#(T)相連

uvm_*_imp(T,IMP)
注意,imp類(lèi)型的端口類(lèi)傳入的參數(shù)有兩個(gè),T是該端口要處理的transaction事務(wù)類(lèi),IMP是該imp端口所在的component類(lèi)

imp類(lèi)型的端口類(lèi)從uvm_port_base繼承而來(lái),也有23個(gè)子類(lèi),和上述兩類(lèi)端口一一對(duì)應(yīng)。

不同端口類(lèi)型的連接規(guī)則
port可以向port連接,可以向export發(fā)起連接,可以向imp發(fā)起連接
export可以向export發(fā)起連接,可以向imp發(fā)起連接
imp不能向任何端口發(fā)起連接
層級(jí)的要求是個(gè)問(wèn)題,需要探討
2.3 TLM1中的FIFO類(lèi)
注意FIFO類(lèi)和端口類(lèi)是完全不同的,F(xiàn)IFO類(lèi)屬于組件,是component。而端口類(lèi)必須是在component類(lèi)中例化的。
重視FIFO類(lèi)在使用中的重要性
在實(shí)際驗(yàn)證環(huán)境中會(huì)遇到需要將進(jìn)程1中的componentA連接到進(jìn)程2中的componentB的情況,因?yàn)門(mén)LM組件需要在自己的進(jìn)程中工作(解耦合)。

之前的端口直連的過(guò)程是,只存在一個(gè)進(jìn)程,port端口調(diào)用方法,會(huì)引發(fā)一系列的方法調(diào)用。
有些情況下,兩個(gè)平級(jí)的組件的run_phase是并行執(zhí)行的。它們?cè)诟髯缘木€程中調(diào)用自己的方法。這時(shí)就需要有一個(gè)緩沖區(qū)。即componentA發(fā)出的事務(wù)先放在一個(gè)FIFO里存起來(lái),componentB在執(zhí)行到自己的線程時(shí),再?gòu)腇IFO里去取。
uvm_tlm_fifo_base#(T)


uvm_tlm_fifo_base#(T)繼承于uvm_component,是一個(gè)virtual class,只能被繼承,不能被實(shí)例化。fifo類(lèi)可以看做是驗(yàn)證環(huán)境中的一個(gè)組件。在fifo類(lèi)中定義了非常多的端口和方法。
包含的端口

在uvm_tlm_fifo_base#(T)中,只定義了三種類(lèi)型的端口
- uvm_put_imp#(T,IMP)
- uvm_get_peek_imp#(T,IMP)
- uvm_analysis_port#(T)

注意,在uvm_tlm_fifo_base#(T)中沒(méi)有export類(lèi)型端口。
雖然圖上標(biāo)注的都是export端口名,但是從源代碼看出,這些都是imp類(lèi)的句柄名。雖然起名叫export,但實(shí)際是imp類(lèi)型。為什么要這樣有意而為呢?
答案:

雖然有這么多的句柄,但是在new函數(shù)中,只實(shí)例化了一個(gè)put_export,一個(gè)get_peek_export,一個(gè)put_ap,一個(gè)get_ap。 其余的句柄并沒(méi)有單獨(dú)實(shí)例化,而是指向了這四個(gè)實(shí)例。所以在圖上,沒(méi)有被實(shí)例化的句柄,用淺顏色標(biāo)注。
聲明的方法
build_phase(),flush(),size(),put(),get(),peek(),try_put(),try_get(),try_peek(),can_put(),can_get(),can_peek(),ok_to_put(),ok_to_get(),ok_to_peek(),is_empty(),is_full(),is_used()
這些方法都是空的,需要在子類(lèi)中被重寫(xiě),否則會(huì)報(bào)錯(cuò)。
uvm_tlm_fifo#(T)和uvm_tlm_analysis_fifo#(T)

由于uvm_tlm_fifo_base#(T)是fifo類(lèi)的基類(lèi),并且是virtual class。所以只定義了一些基礎(chǔ)的公共變量和方法。不能直接拿來(lái)使用。
我們?cè)趯?shí)際搭建驗(yàn)證環(huán)境時(shí),用到的fifo類(lèi)都從該基類(lèi)擴(kuò)展而來(lái)。有兩種,分別是uvm_tlm_fifo#(T)以及uvm_tlm_analysis_fifo#(T)。

圖片來(lái)自于博客:UVM Tutorial for Candy Lovers – 20. TLM 1 – ClueLogic
- uvm_tlm_fifo#(T)繼承了uvm_tlm_fifo_base#(T),并沒(méi)有再額外聲明端口,只是重寫(xiě)了uvm_tlm_fifo_base中的方法
- uvm_tlm_analysis_fifo#(T)繼承自u(píng)vm_tlm_fifo,多了一個(gè)uvm_analysis_imp類(lèi)型的端口,analysis_export

一個(gè)關(guān)鍵問(wèn)題:uvm_tlm_fifo是如何實(shí)現(xiàn)fifo的特性的
uvm_tlm_fifo是一個(gè)類(lèi),其具有fifo先入先出特性的核心是內(nèi)部例化了一個(gè)mailbox類(lèi)。mailbox本身就具有fifo的性質(zhì)。


mailbox機(jī)制可以參考:
使用uvm_tlm_fifo的工作原理

- connect:componentA.port.connect(uvm_tlm_fifo.put_export),componentB.connect(uvm_tlm_fifo.get_export)
- 在componentA的run_phase中,調(diào)用componentA.put(),從而調(diào)用uvm_tlm_fifo.put_export.put(),進(jìn)而調(diào)用uvm_tlm_fifo.put()。此時(shí)會(huì)把trans放入uvm_tlm_fifo的mailbox中
- 在componentB的run_phase中,調(diào)用componentB.get(),從而調(diào)用uvm_tlm_fifo.get_export.get(),進(jìn)而調(diào)用uvm_tlm_fifo.get()。此時(shí)trans會(huì)從mailbox中出來(lái)。
所以最終put()和get()方法的實(shí)現(xiàn)都是在imp所在的component類(lèi)中實(shí)現(xiàn)的。uvm_tlm_fifo#(T)中的這些方法繼承于uvm_tlm_fifo_base,并對(duì)這些方法進(jìn)行了重寫(xiě)。

這些方法都是對(duì)mailbox的操作,以及調(diào)用put_ap和get_ap的write()函數(shù)
這里put_ap和get_ap也沒(méi)有和別的口連接,調(diào)用它們的write有什么用? 調(diào)用write函數(shù),則put_ap和get_ap必須有所連接,否則應(yīng)該會(huì)報(bào)錯(cuò)吧。但是什么時(shí)候會(huì)把put_ap和get_ap連接呢?和誰(shuí)連接呢?必須要進(jìn)行連接嗎?
analysis_port和analysis_fifo的區(qū)別和聯(lián)系
剛開(kāi)始寫(xiě)代碼時(shí),會(huì)經(jīng)常混淆analysis_port和analysis_fifo。如果把前面的梳理清楚,這里我們自然就清楚兩者有著本質(zhì)的區(qū)別。
注意analysis port和analysis fifo有著本質(zhì)的不同,前者是端口類(lèi),而后者是FIFO類(lèi),F(xiàn)IFO可以認(rèn)為是一個(gè)組件component,在FIFO中,例化了非常多的imp端口以及若干的analysis port端口
2.4 TLM1中的Channel類(lèi)


暫未使用過(guò),之后遇到再補(bǔ)充。
2.5 sequence機(jī)制中用到的端口類(lèi)
sequence機(jī)制是事務(wù)產(chǎn)生和發(fā)送的核心機(jī)制。涉及到sequencer和driver之間的事務(wù)傳輸。用到的端口類(lèi)也比較特殊。只有一種前綴對(duì)應(yīng)的port,export,imp端口。這里把上述類(lèi)庫(kù)地圖的最后一部分單獨(dú)截圖出來(lái)。



這三種seq_port類(lèi)是
- uvm_seq_item_pull_port#(REQ,RSP)
- uvm_seq_item_pull_export#(REQ,RSP)
- uvm_seq_item_pull_imp#(REQ,RSP,IMP)
它們也是繼承自u(píng)vm_port_base#(IF),但是和其他端口類(lèi)的區(qū)別是,繼承的uvm_port_base#(IF)的IF參數(shù)是uvm_sqr_if_base#(REQ,RSP)。而不是uvm_tlm_if_base#(T,T)。兩者的區(qū)別在2.2節(jié)中已經(jīng)描述。
這三類(lèi)端口的典型使用場(chǎng)景就是在sequence機(jī)制中。在uvm_driver中例化了uvm_seq_item_pull_port#(REQ,RSP),在uvm_sequencer中例化了uvm_seq_item_pull_imp#(REQ,RSP,IMP)。



sequence機(jī)制中的TLM傳輸
- connect:在agent中,連接driver和sequencer。my_drv.seq_item_port.connect(my_seqr.seq_item_export)
- 在my_drv的run_phase中,調(diào)用seq_item_port.get_next_item(),進(jìn)而調(diào)用了my_seqr.seq_item_export.get_next_item(),進(jìn)而調(diào)用了my_seqr.get_next_item(),此時(shí)my_seqr收到my_drv的數(shù)據(jù)請(qǐng)求。會(huì)檢查是否有sequence發(fā)送到my_seqr,如果沒(méi)有則處于等待狀態(tài)。
- seq調(diào)用seq.start(my_seqr),將產(chǎn)生的事務(wù)發(fā)送給my_seqr。此時(shí)事務(wù)通過(guò)my_seqr傳遞給my_drv。
- my_drv處理完事務(wù)之后,調(diào)用seq_item_port.item_done(),表示結(jié)束,最終調(diào)用的是my_seqr.item_done()
2.6 TLM機(jī)制的源代碼解析:
在上面的傳輸過(guò)程中,多次描述到這樣一個(gè)過(guò)程:
當(dāng)調(diào)用port.put()方法時(shí),會(huì)引發(fā)調(diào)用與之相連的imp.put()方法,進(jìn)而會(huì)調(diào)用imp所在的component類(lèi)的put()方法。
那么由此引發(fā)兩個(gè)疑問(wèn):
- port.put()是如何引發(fā)后續(xù)方法的調(diào)用的?
- 最終的put()方法是在imp所在的component類(lèi)中實(shí)現(xiàn)的,具體要寫(xiě)成什么,才算實(shí)現(xiàn)了put()?
在"tlm1/uvm_ports.svh"文件中定義了各種各樣的port類(lèi),定義寫(xiě)法都是一樣的,這里找一個(gè)拆開(kāi)解析

首先,uvm_blocking_put_port#(T)繼承自u(píng)vm_port_base#(IF),這里傳入的IF參數(shù)是uvm_tlm_if_base#(T,T)類(lèi)。所以u(píng)vm_blocking_put_port繼承自u(píng)vm_port_base進(jìn)而繼承自u(píng)vm_tlm_if_base。 uvm_blocking_put_port傳入的參數(shù)T,最終會(huì)傳入到uvm_tlm_if_base中的參數(shù)(T,T)。 這里的參數(shù)T一般來(lái)說(shuō)是流經(jīng)端口的事務(wù)類(lèi)。是uvm_sequence_item的子類(lèi)。
在uvm_blocking_put_port中重寫(xiě)了uvm_tlm_if_base中的put任務(wù),在put任務(wù)中調(diào)用了this.m_if.put()。this.m_if是從uvm_port_base中繼承的,m_if是uvm_port_base類(lèi)的一個(gè)句柄。

這里的this.m_if指向的是與uvm_blocking_put_port相連的uvm_blocking_put_imp的實(shí)例。
這里有了一個(gè)關(guān)鍵性疑問(wèn),在put_port中的m_if,是在什么時(shí)候指向的put_imp實(shí)例?
很自然的會(huì)想到connect函數(shù),因?yàn)橹挥姓{(diào)用port.connect(imp)才建立兩個(gè)端口之間的聯(lián)系。
走進(jìn)connec()函數(shù)

代碼太長(zhǎng),這里就不貼了,總結(jié)connect函數(shù)主要干的事情。
connect()函數(shù)傳入的參數(shù)為一個(gè)uvm_port_base類(lèi)型的句柄,provider
- 檢查provider是否為空,是否是自己本身,檢查provider的m_if_mask和自己的m_if_mask關(guān)系
- 檢查端口連接關(guān)系,不能是export連port,自己本身不能是imp等等
- 檢查relationship,更復(fù)雜的層級(jí)關(guān)系要滿足
- 以上都滿足之后,說(shuō)明端口連接是合法的,就把provider放入當(dāng)前port端口類(lèi)中的關(guān)聯(lián)數(shù)組m_provided_by中。同時(shí)provider也把當(dāng)前port類(lèi)放入自己的關(guān)聯(lián)數(shù)組m_provided_to中
會(huì)發(fā)現(xiàn),connect()函數(shù)并沒(méi)有對(duì)m_if的任何操作。
我想說(shuō)的是接下來(lái)這部分,將是我看uvm以來(lái)最沖擊我的一段代碼。這段比較細(xì)節(jié),即使不清楚也無(wú)所謂。
uvm_port_base中,唯一出現(xiàn)m_if賦值的地方是:

尋找set_if(()被何時(shí)被調(diào)用

在uvm_port_phase中的resolve_bindings()函數(shù)中的最后一行,調(diào)用了set_if()函數(shù)。
resolve_bindings()函數(shù)做的事情是:
如果當(dāng)前端口不是imp端口,那么會(huì)遍歷m_provided_by這個(gè)關(guān)聯(lián)數(shù)組,這個(gè)關(guān)聯(lián)數(shù)組中是上述connect函數(shù)執(zhí)行之后,存放的與當(dāng)前port相連的端口。先調(diào)用了連接端口的resolve_bindings。 然后調(diào)用了m_add_list。 將m_provided_by中的值放到m_imp_list關(guān)聯(lián)數(shù)組中。
最終調(diào)用了set_if()。在set_if中調(diào)用了get_if。最終m_if指向的是m_imp_list關(guān)聯(lián)數(shù)組中的第一個(gè)imp實(shí)例。
那么resolve_bindings()是何時(shí)被調(diào)用的呢?

不起眼的m_comp是關(guān)鍵。在uvm_port_base中,例化了一個(gè)uvm_component類(lèi)型的類(lèi)。
m_comp的作用一個(gè)是繼承了來(lái)自u(píng)vm_component的一些report方法,便于打印一些信息。另外是m_comp在實(shí)例化之后,會(huì)根據(jù)phase機(jī)制,自動(dòng)執(zhí)行一些函數(shù)。

在m_comp中,有一個(gè)resolve_bindings函數(shù),會(huì)調(diào)用m_port.resolve_bindings()函數(shù)。
而m_comp中的resolve_bindings函數(shù)是重寫(xiě)的uvm_component的resolve_bindings()函數(shù)。

而uvm_component的resolve_bindings()函數(shù)是在end_of_elaboration phase開(kāi)始之前自動(dòng)執(zhí)行的。
至此,整個(gè)的順序就已經(jīng)清晰了。

回到最開(kāi)始uvm_blocking_put_port#(T)中put()任務(wù)的描述:

在put(T t)中,調(diào)用了this.m_if.put(t)。注意此時(shí)port端口處理的事務(wù)t,通過(guò)task的參數(shù)傳遞到了this.m_if.put(t)中。
再進(jìn)到uvm_blocking_put_imp#(T)中,看其內(nèi)部定義的put任務(wù)

可以注意到imp類(lèi)型的類(lèi),其參數(shù)多了一個(gè)IMP,這個(gè)IMP是端口所屬的component類(lèi)本身。一般在component中例化該imp端口時(shí),會(huì)傳入this。
在imp類(lèi)中,聲明IMP的一個(gè)句柄m_imp,imp類(lèi)的put(T t)任務(wù)中,調(diào)用m_imp的put(t)任務(wù)。最終masterA中的trans,被slaveB拿到。
put()任務(wù)的參數(shù)t,在調(diào)用過(guò)程中傳遞,是整個(gè)TLM通信中事務(wù)傳輸?shù)淖罱K實(shí)現(xiàn)。對(duì)于get()任務(wù),參數(shù)的傳遞方向改為output,與put()方向相反。

2.7 使用宏 `uvm_*_imp_decl
注意,該宏只針對(duì)imp類(lèi)的端口
該宏出現(xiàn)解決的問(wèn)題基于如下場(chǎng)景

不同于之前的一對(duì)一連接,現(xiàn)在有componentA和componentC都要和componentB連接。將會(huì)面例如下問(wèn)題:
當(dāng)按照常規(guī)方法,做如下連接時(shí),compA.portA.put(t)最終會(huì)調(diào)用compB.put(t),而compC.portC.put(t)最終也會(huì)調(diào)用compB.put(t)。如果A和C同時(shí)向B發(fā)起傳輸,將會(huì)同時(shí)調(diào)用put,并且無(wú)法解決各自的需求。這顯然是不合理的。
class componentA extends from uvm_component; uvm_put_port#(A_trans) portA; // …… task run_phase(uvm_phase phase); portA.put(A_trans); endtask endclass class componentC extends from uvm_component; uvm_put_port#(C_trans) portC; // …… task run_phase(uvm_phase phase); portC.put(C_trans); endtask endclass class componentB extends from uvm_component; uvm_put_imp#(A_trans,this) impAB; uvm_put_imp#(C_trans,this) impCB; task put(); // …… //這里將會(huì)引發(fā)矛盾和沖突 endtask endclass class test_env extends from uvm_env; componentA compA; componentB compB; componentC compC; function void connect_phase(uvm_phase phase); compA.portA.connect(compB.impAB); compC.portC.connect(compB.impCB); endfunction endclass
自然而然的會(huì)想到,在componentB中定義兩個(gè)put方法,put_A()和put_C()方法。
- 當(dāng)發(fā)起compA.portA.put()時(shí),最終調(diào)用compB.put_A()方法
- 當(dāng)發(fā)起compC.portC.put()時(shí),最終調(diào)用comB.put_C()方法
以此來(lái)實(shí)現(xiàn)兩路傳輸?shù)莫?dú)立性。但是依靠原有的框架,使用原有的類(lèi),是無(wú)法實(shí)現(xiàn)的。因?yàn)樵械膇mp類(lèi)中,方法是固定寫(xiě)死的,不會(huì)有我們?nèi)藶槎x添加的put_A()和put_C()。
我們可以做的是自定義新的impAB類(lèi)和impCB類(lèi),重新定義imp中的put()方法
- 在impAB類(lèi)中調(diào)用put()方法時(shí),調(diào)用的是compB.put_A()方法
- 在impCB類(lèi)中調(diào)用put()方法時(shí),調(diào)用的是compB.put_B()方法
而這所有的區(qū)別,僅在于后綴的不同。uvm中提供了宏幫我們來(lái)完成這個(gè)過(guò)程。
`uvm_*_imp_decl宏共有23種,這個(gè)宏展開(kāi)后其實(shí)就是對(duì)比常規(guī)的23種imp類(lèi)的重新定義。
uvm_blocking_put_imp_decl(SFX)
uvm_nonblocking_put_imp_decl(SFX)
uvm_put_imp_decl(SFX)
uvm_blocking_get_imp_decl
uvm_nonblocking_get_imp_decl(SFX)
uvm_get_imp_decl(SFX)
……
以u(píng)vm_blocking_put_imp_decl(SFX)為例,展開(kāi)解析:

這個(gè)宏是帶參數(shù)的宏,這個(gè)參數(shù)就是人為定義的后綴。使用該宏時(shí),就會(huì)產(chǎn)生一個(gè)新class的聲明。如`uvm_blocking_put_imp(_A)等價(jià)于聲明了一個(gè)class,類(lèi)名為 uvm_blocking_put_imp_A。
在類(lèi)內(nèi)部,又調(diào)用了兩個(gè)宏,展開(kāi)來(lái)看:
第一個(gè)宏就是在常規(guī)的imp類(lèi)中用到的宏,聲明了new函數(shù),以及指定了type_name。沒(méi)有什么特別之處。

第二個(gè)宏,和常規(guī)imp類(lèi)中的宏不同,這里重新定義了put()任務(wù)中的內(nèi)容:


至此,使用這個(gè)宏之后的代碼如下:
`uvm_put_imp_decl(_A) `uvm_put_imp_decl(_C)// 兩個(gè)宏的使用一定要在class之外,因?yàn)楹瓯旧泶淼木褪莄lass的聲明。 class componentA extends from uvm_component; uvm_put_port#(A_trans) portA; // …… task run_phase(uvm_phase phase); portA.put(A_trans); endtask endclass class componentC extends from uvm_component; uvm_put_port#(C_trans) portC; // …… task run_phase(uvm_phase phase); portC.put(C_trans); endtask endclass class componentB extends from uvm_component; uvm_put_imp_A#(A_trans,this) impAB; //使用新定義的imp類(lèi) uvm_put_imp_C#(C_trans,this) impCB; //使用新定義的imp類(lèi) task put_A(); // task名字必須和后綴保持一致 // …… endtask task put_B(); // task名字必須和后綴保持一致 // …… endtask endclass class test_env extends from uvm_env; componentA compA; componentB compB; componentC compC; function void connect_phase(uvm_phase phase); compA.portA.connect(compB.impAB); compC.portC.connect(compB.impCB); endfunction endclass
至此,上述情景的傳輸過(guò)程如下:

注意:在新定義的imp類(lèi)中,put任務(wù)是不帶后綴的,依舊是叫put()。這里的名字必須和port中的保持一致。區(qū)別是調(diào)用的不再是componentB中的put(),而是componentB中的put_*()
三、實(shí)戰(zhàn)部分
在這個(gè)網(wǎng)站中,提供了一些連接場(chǎng)景下的代碼模板
3.1 各component的連接結(jié)構(gòu)圖
需要畫(huà)一張圖,畫(huà)一個(gè)大而全的圖
3.2 代碼部分
本文轉(zhuǎn)自 https://blog.csdn.net/weixin_48157494/article/details/151726600,如有侵權(quán),請(qǐng)聯(lián)系刪除。


浙公網(wǎng)安備 33010602011771號(hào)