制詳解:端口連接與踩坑排查指南)
UVM_TLM通信機(jī)制我?guī)缀趺總€UVM驗證環(huán)境里都要和它打交道。說白了TLM就是UVM里搬運事務(wù)的接口協(xié)議。driver到monitor的數(shù)據(jù)、monitor到reference model的采樣結(jié)果、reference model到scoreboard的期望值全靠這一層端口連起來。很多同學(xué)剛學(xué)UVM時最頭疼的就是那一堆端口put、get、analysis、port、export、imp名字看著都像連起來卻各種報錯要么編譯過不了要么仿真跑了大半天數(shù)據(jù)對不上。我寫這篇東西不是要把UVM手冊翻譯一遍而是按自己平時搭環(huán)境的思路把這套通信機(jī)制拆開講——它到底解決什么問題端口該怎么連代碼怎么寫以及那些手冊里提都不提的踩坑經(jīng)驗和排查方法。正在學(xué)UVM的、被端口連接折磨過的人應(yīng)該都能在這里找到自己要的東西。1. 為什么是事務(wù)級TLM在驗證環(huán)境中的角色1.1 從信號級到事務(wù)級的轉(zhuǎn)變邏輯早期做定向驗證組件之間互相對接的是一大把信號。驅(qū)動端寫地址總線、寫數(shù)據(jù)總線接收端去讀總線稍微復(fù)雜一點的接口幾十根線交叉連接某根線接錯仿真結(jié)果就全亂了。這種信號級連接有一個很致命的問題組件之間被物理連線綁死了想換個接口、想復(fù)用某個組件幾乎都要重寫連接邏輯。UVM把這一層抽象掉了組件之間不再直接傳遞一組wire而是傳遞一個封裝好的事務(wù)對象——transaction對象。這個對象內(nèi)部可以有地址、數(shù)據(jù)、狀態(tài)、時間戳、隨機(jī)約束等所有信息。這就引出了TLMTransaction Level Modeling事務(wù)級建模的真正價值它把組件之間的通信從“電平怎么變”提升到了“事務(wù)怎么傳”。舉個例子總線driver把一組數(shù)據(jù)寫到DUTmonitor從總線上采到同樣的數(shù)據(jù)兩邊不需要關(guān)心這組數(shù)據(jù)跨越了多少個時鐘周期、經(jīng)歷了多少種總線狀態(tài)它們只需要共同認(rèn)可一個事務(wù)類型把對象遞過去就行。UVM_TLM通信機(jī)制就是這層傳遞的標(biāo)準(zhǔn)化貨架put、get、transport、analysis加上port、export、imp組合起來形成了完備的通信接口。最早這套體系其實來自系統(tǒng)級建模社區(qū)UVM把它吸納進(jìn)來后驗證組件的復(fù)用性一下子提升了很多。信號級環(huán)境下?lián)Q個驗證平臺可能連驅(qū)動模塊都要改事務(wù)級環(huán)境下只要事務(wù)類型一致組件可以直接搬走。還有一個容易被忽略的點信號級通信里時序是寫在連接代碼里的你得自己保證數(shù)據(jù)在哪個沿采樣事務(wù)級通信把時序約定收斂到組件內(nèi)部連接代碼只管把對象交給誰具體什么時候調(diào)用、什么時候等待都是接口實現(xiàn)方自己的事。這么一拆環(huán)境搭建的工作量就大幅減少了。1.2 一次put握手背后發(fā)生了什么看到這里我建議先記住一句話TLM通信的實質(zhì)是方法調(diào)用。表面上看起來是發(fā)數(shù)據(jù)底層其實是發(fā)起方調(diào)用了接收方的一個方法把事務(wù)對象作為參數(shù)傳進(jìn)去。明白這一點后面所有端口問題的根因都能想通。以最簡單的blocking_put為例// 發(fā)起方 class my_source extends uvm_component; uvm_component_utils(my_source) uvm_blocking_put_port #(my_transaction) put_port; function new(string name, uvm_component parent); super.new(name, parent); put_port new(put_port, this); endfunction task run_phase(uvm_phase phase); my_transaction tr my_transaction::type_id::create(tr); tr.addr 32h1000; tr.data 32hDEAD_BEEF; put_port.put(tr); // 調(diào)用接收方的put endtask endclass接收方class my_sink extends uvm_component; uvm_component_utils(my_sink) uvm_blocking_put_imp #(my_transaction, my_sink) put_imp; function new(string name, uvm_component parent); super.new(name, parent); put_imp new(put_imp, this); endfunction task put(my_transaction tr); // 在這里處理事務(wù) $display(sink get: addr%0h data%0h, tr.addr, tr.data); endtask endclass我用的是uvm_blocking_put_port和uvm_blocking_put_imp前者的put方法只是一個空殼調(diào)用時會把控制權(quán)交給后者實現(xiàn)的put任務(wù)。阻塞的含義是source在put_port.put(tr)這一行會一直等著直到sink的put返回。也就是說sink的put任務(wù)處理完這次事務(wù)source的put調(diào)用才返回然后source才執(zhí)行下一行代碼。這種“調(diào)用方等處理完”的語義就是blocking阻塞模式。如果把blocking改成nonblocking接口里就沒有put方法而是try_put和can_put。try_put的意思是“嘗試發(fā)送”接收方有空位才收下沒有空位返回0can_put則是先問一句“你現(xiàn)在能收嗎”。這兩種模式在UVM_TLM里都有完整定義具體用哪種取決于通道另一端的處理節(jié)奏如果兩端生產(chǎn)消費嚴(yán)格同步用blocking最省心如果接收方處理速度跟不上又不想讓發(fā)送方干等那就要考慮后面會講的TLM FIFO。1.3 同步與異步拿打電話和發(fā)郵件來理解把TLM通信比作日常溝通會很好理解。blocking_put就像打電話你撥號對方接起你們把事情說清楚掛斷你才能去干下一件事。溝通期間誰也走不開。try_put像往對方工位丟紙條你試著丟了一下接住了說明對方有空沒接住你就自己處理。analysis端口更像群發(fā)郵件你把郵件發(fā)給一群人郵件發(fā)出后你立刻回去干活收件人什么時候看、怎么看你完全不關(guān)心。這個類比能幫你在設(shè)計階段做選型組件之間是強(qiáng)同步、必須一收一發(fā)還是松耦合、能緩沖就緩沖不同的答案決定了你會選blocking、nonblocking還是analysis加FIFO。選錯了后面調(diào)試成本成倍增加。我見過一個環(huán)境在一個明明是廣播場景的地方用了blocking_put結(jié)果每次發(fā)送都要等兩個接收方把數(shù)據(jù)處理完本來想并行做的事情全被拖成了串行性能問題倒還是小事更麻煩的是接收方一旦掛住發(fā)送方直接卡死。這類問題在后面的排查章節(jié)會再展開。2. 端口體系port、export、imp的職責(zé)分工2.1 一條連接鏈上的三個角色UVM_TLM通信機(jī)制里端口分為三大類port、export、imp。我平時給新人講的時候習(xí)慣用“請求發(fā)起方、請求轉(zhuǎn)發(fā)方、請求執(zhí)行方”來對應(yīng)。端口類型身份定位是否實現(xiàn)協(xié)議方法實際使用位置uvm_*_port發(fā)起調(diào)用請求源頭否source組件內(nèi)部成員uvm_*_export中間轉(zhuǎn)發(fā)連接樞紐否中間模塊或agent內(nèi)部uvm_*_imp最終接收實現(xiàn)協(xié)議方法是目標(biāo)組件內(nèi)部成員綁定到組件本身連接規(guī)則上port可以向下連export也可以直接連impexport只能繼續(xù)往imp方向連。port和port之間、export和export之間都不應(yīng)該直接相連雖然UVM在某些情況下允許但大多數(shù)連接錯誤都出在這種規(guī)則沒理順的地方。此外每個imp實例都對應(yīng)一個具體組件的具體方法實現(xiàn)同一個imp不能在一個連接鏈上被重復(fù)使用。一個基本經(jīng)驗是在搭建連接關(guān)系之前先在紙上畫出源組件、目標(biāo)組件、中間組件把每個端口從源頭到終點完整走一遍再去看代碼。這里的“方法調(diào)用方向”和“數(shù)據(jù)流動方向”需要區(qū)分。以put為例數(shù)據(jù)從source流向sink但調(diào)用方向是source調(diào)用sink的put方法。簡單說數(shù)據(jù)方向從發(fā)送方到接收方代碼控制權(quán)也順著這個方向走。如果你看到一條連接線是從monitor的analysis_port指向scoreboard的analysis_imp數(shù)據(jù)就是從monitor流向scoreboard控制權(quán)也在這條鏈路上順流而下。反過來如果用get從FIFO取數(shù)據(jù)數(shù)據(jù)從FIFO到getter控制權(quán)也從FIFO的get方法跳回getter這個方向感一定要建立起來。2.2 analysis系列廣播為什么省事在UVM里analysis端口是很特殊的一族也是我在日常環(huán)境中用得最多的一族。它和標(biāo)準(zhǔn)TLM端口最大的區(qū)別在于語義上的“無阻塞、一對多”。一個analysis_port可以同時連接多個analysis_imp數(shù)據(jù)發(fā)出時不等待任何接收方每個訂閱者都會收到這個對象。典型場景就是monitor采樣到一筆總線事務(wù)希望同時交給reference model和scoreboard兩邊各自處理互不影響。analysis_imp的實現(xiàn)方法是write它是一個function不是task。這一點務(wù)必記住class my_scoreboard extends uvm_component; uvm_component_utils(my_scoreboard) uvm_analysis_imp #(my_transaction, my_scoreboard) analysis_imp; function new(string name, uvm_component parent); super.new(name, parent); analysis_imp new(analysis_imp, this); endfunction function void write(my_transaction tr); // 不能在這里放耗時的阻塞邏輯 $display(scoreboard get %0d, tr.id); endfunction endclass因為write是function實現(xiàn)里不應(yīng)該出現(xiàn)wait語句或者循環(huán)延時。有些新人把郵箱、FIFO、阻塞同步邏輯寫進(jìn)write里跑起來要么編譯報錯要么出現(xiàn)莫名奇妙的時序偏差。真想異步處理應(yīng)該把write收到的對象推進(jìn)一個隊列或者交給uvm_analysis_fifo再由獨立的run_phase任務(wù)去消費。還有一個細(xì)節(jié)值得展開。如果一個組件需要接收多個analysis端口的廣播又要區(qū)分來源比如scoreboard要同時接收monitor來的實際數(shù)據(jù)、reference model來的期望數(shù)據(jù)你可以直接聲明兩個分析方法也可以用UVM提供的宏來擴(kuò)展imp類型避免兩個分析方法同名沖突。class my_scoreboard extends uvm_component; uvm_component_utils(my_scoreboard) uvm_analysis_imp_decl(_FROM_MON) uvm_analysis_imp_decl(_FROM_MODEL) uvm_analysis_imp_FROM_MON #(my_transaction, my_scoreboard) mon_imp; uvm_analysis_imp_FROM_MODEL #(my_transaction, my_scoreboard) model_imp; function new(string name, uvm_component parent); super.new(name, parent); mon_imp new(mon_imp, this); model_imp new(model_imp, this); endfunction function void write_FROM_MON(my_transaction tr); // 來自monitor的實際數(shù)據(jù) endfunction function void write_FROM_MODEL(my_transaction tr); // 來自reference model的期望數(shù)據(jù) endfunction endclass這是UVM_TLM通信機(jī)制里比較實用的技巧之一。沒有這個宏時同類型imp只能有一個兩個數(shù)據(jù)來源全部擠進(jìn)同一個write比對邏輯就得靠寫額外字段去區(qū)分來源有了它直接按來源寫兩個處理方法清晰很多。2.3 transport、mixed與TLM FIFO不止put/get標(biāo)準(zhǔn)TLM除了put、get還有transport、master、slave等更復(fù)雜的接口。我平時用得相對少但有一點值得聊transport的含義是“一次發(fā)起既有請求又有響應(yīng)”。典型的transport_port會同時提供put和get兩個方向的阻塞方法適合底層總線模型那種請求-應(yīng)答模式。對于大多數(shù)驗證組件尤其是由相對獨立任務(wù)驅(qū)動的那類我建議優(yōu)先用put/get或者analysis把方向分開處理而不是硬塞進(jìn)一個transport里。transport用起來簡單分解起來卻麻煩一個復(fù)雜環(huán)境里到處都是transport端口排查問題時會很痛苦。當(dāng)生產(chǎn)者和消費者的速率不匹配時最好引入TLM FIFO。UVM里內(nèi)置了uvm_tlm_fifo #(T)它內(nèi)部維護(hù)一個隊列實現(xiàn)了put、get兩邊接口class fifo_bridge extends uvm_component; uvm_blocking_put_port #(my_transaction) put_port; uvm_blocking_get_port #(my_transaction) get_port; uvm_tlm_fifo #(my_transaction) fifo; function new(string name, uvm_component parent); super.new(name, parent); fifo new(fifo, this); endfunction virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); put_port.connect(fifo.put_export); get_port.connect(fifo.get_export); endfunction endclassuvm_tlm_fifo在內(nèi)部實現(xiàn)了put_export、get_export等端口connect的時候直接把外部port連到這些export上即可。它天然支持blocking和nonblocking兩種調(diào)用內(nèi)置的隊列大小還可以通過參數(shù)配置。跟它長得很像的uvm_analysis_fifo #(T)則更常用在analysis場景它本身帶一個analysis端口寫入方直接調(diào)用write()讀走方調(diào)用get()中間自動緩沖。選擇其實很簡單一端是analysis端口就用uvm_analysis_fifo兩端都是blocking put或blocking get想解耦節(jié)奏就用uvm_tlm_fifo。3. 實操搭建一條monitor到scoreboard的標(biāo)準(zhǔn)通路3.1 先看整體拓?fù)鋽?shù)據(jù)往哪兒走這一節(jié)我以一個典型的驗證環(huán)境為例driver驅(qū)動DUTmonitor采樣總線采樣結(jié)果同時送到reference model和scoreboardreference model根據(jù)輸入生成期望輸出也送給scoreboardscoreboard最后把兩份數(shù)據(jù)做比對。這套結(jié)構(gòu)幾乎是所有UVM驗證環(huán)境的基礎(chǔ)。數(shù)據(jù)流向按下面的方式理解最方便driver --(put)-- fifo -- model | monitor --(analysis_port)-- scoreboard | -- model.analysis_imp這里面monitor與scoreboard之間用的是analysis端口monitor與reference model之間也用了analysis端口一個端口廣播到兩處。driver到model之間則通過FIFO解耦因為driver產(chǎn)生的激勵節(jié)奏和model的運算節(jié)奏不一定實時匹配。拓?fù)淅斫獾轿缓蟠a只是把藍(lán)圖落下來而已。3.2 第一步monitor發(fā)出去monitor里聲明analysis端口很簡單class my_monitor extends uvm_component; uvm_component_utils(my_monitor) uvm_analysis_port #(my_transaction) ap; virtual dut_if vif; function new(string name, uvm_component parent); super.new(name, parent); ap new(ap, this); endfunction task run_phase(uvm_phase phase); my_transaction tr; forever begin (posedge vif.clk); if (vif.valid) begin tr my_transaction::type_id::create(tr); tr.addr vif.addr; tr.data vif.data; ap.write(tr); end end endtask endclass注意ap的創(chuàng)建放在new里這是UVM組件的通用規(guī)則——端口屬于構(gòu)建期對象必須在所有連接動作發(fā)生前就存在。我看到過有的同學(xué)在run_phase里才去new端口對象連接自然失敗了因為connect_phase在run_phase之前已經(jīng)執(zhí)行完連接階段根本找不到這個端口對象。3.3 第二步scoreboard接進(jìn)來scoreboard這邊的接收端口是analysis_imp第二個模板參數(shù)是這個imp屬于哪個組件class my_scoreboard extends uvm_component; uvm_component_utils(my_scoreboard) uvm_analysis_imp #(my_transaction, my_scoreboard) scoreboard_imp; function new(string name, uvm_component parent); super.new(name, parent); scoreboard_imp new(scoreboard_imp, this); endfunction function void write(my_transaction tr); // 暫存到隊列等reference model的輸出一起比對 actual_queue.push_back(tr); endfunction endclass這時候需要把兩邊在父組件里連起來。UVM中連接關(guān)系一定是在公共父級建立。比如monitor和scoreboard都屬于env那么在env的connect_phase里寫monitor.ap.connect(scoreboard.scoreboard_imp)。第一次寫UVM環(huán)境的人可能不理解為什么要放在父級而不是子級里因為兩個端口對象都?xì)w父級管理父級才知道它們各自的完整身份才能安全地建立連接。子級去連的話它看不到另一個組件的端口對象你會被迫使用全局查找機(jī)制那反而是更脆弱的寫法。3.4 第三步reference model怎么并入reference model需要消費monitor廣播的數(shù)據(jù)同時它生成的期望數(shù)據(jù)又要送到scoreboard。最簡單的接法就是讓scoreboard開第二個imp用2.2節(jié)講的uvm_analysis_imp_decl宏區(qū)分兩個來源。monitor的ap同時connect給scoreboard的一個imp和model的一個analysis_imp即可class my_env extends uvm_component; my_monitor monitor; my_model model; my_scoreboard scoreboard; virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); monitor.ap.connect(scoreboard.mon_imp); monitor.ap.connect(model.model_imp); model.ap.connect(scoreboard.model_imp); endfunction endclass這段代碼里的關(guān)鍵點在于monitor.ap被connect了兩次。analysis_port天然支持一對多廣播所以這個寫法是正確的但你如果把這個思路照抄到blocking_put_port上編譯和運行就會出問題因為put只能一對一。connect_phase里寫完連接后建議在run_phase之前調(diào)用連接報告確認(rèn)每個端口到底連到了哪里。好多“數(shù)據(jù)沒到”的問題第一步就該檢查連接關(guān)系是否完整。3.5 用FIFO給driver和model之間加緩沖很多環(huán)境下driver與model之間也用TLM FIFO。driver把激勵事務(wù)直接put進(jìn)FIFOmodel端用一個get端口取出來做運算。這里給一個稍完整的連接寫法class my_model extends uvm_component; uvm_component_utils(my_model) uvm_blocking_get_port #(my_transaction) get_port; uvm_analysis_port #(my_transaction) ap; function new(string name, uvm_component parent); super.new(name, parent); get_port new(get_port, this); ap new(ap, this); endfunction task run_phase(uvm_phase phase); my_transaction tr; my_transaction exp; forever begin get_port.get(tr); // 從FIFO取激勵 exp compute(tr); // 做參考模型運算 ap.write(exp); // 期望結(jié)果廣播出去 end endtask endclass class my_env extends uvm_component; uvm_tlm_fifo #(my_transaction) input_fifo; virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); input_fifo new(input_fifo, this); driver.put_port.connect(input_fifo.put_export); model.get_port.connect(input_fifo.get_export); endfunction endclass注意我是在connect_phase里new的input_fifo因為FIFO屬于連接階段需要用的中間對象常見做法是直接在env的new里創(chuàng)建也行兩種都可以但要保證在connect前存在。使用FIFO之后driver不會再因為model沒準(zhǔn)備好而卡住model也很有彈性地按自己的節(jié)奏取數(shù)據(jù)生產(chǎn)消費之間的耦合一下就被解開了。3.6 連接完成后先用一個簡單用例冒煙這是我自己的一個習(xí)慣任何TLM連接改完不要直接跑完整回歸先在test里制造一兩個事務(wù)讓monitor、model、scoreboard各打一行打印確認(rèn)從driver到model、從monitor到scoreboard這兩條鏈路都各收到了一筆數(shù)據(jù)。這條冒煙流程能省掉后面絕大部分排查時間。說白了連接寫對了數(shù)據(jù)才談得上流向正確數(shù)據(jù)流向正確后面的比對邏輯才有意義。4. 進(jìn)階擴(kuò)展事務(wù)帶ID、多接口接入與復(fù)位管理4.1 給事務(wù)帶上身份信息在UVM_TLM通信機(jī)制里傳的transaction對象只是一個容器怎么用全看定義方。我通常會在事務(wù)類型里加幾個輔助字段序號id、采樣時間戳、來源標(biāo)簽。別小看這幾個字段比對時它們的作用非常大。比如scoreboard同時收到來自monitor的實際數(shù)據(jù)和來自model的期望數(shù)據(jù)兩邊的事務(wù)順序不一定完全一致但如果各自帶一個全局遞增idscoreboard就可以按照id去匹配而不是簡單依賴FIFO順序。時序敏感的驗證中我還會加一個timestamp字段記錄數(shù)據(jù)被采樣時的仿真時刻比對超過一定范圍的偏差時報錯。這些字段不會影響TLM本身的通信邏輯卻能大幅提高環(huán)境診斷能力。4.2 多個agent多個端口如何折疊連接關(guān)系驗證環(huán)境稍一復(fù)雜monitor可能不止一個。比如一個跨平臺系統(tǒng)里有總線agent和中斷agent各自都有monitor和analysis端口。scoreboard接收的源變得很多端口也變多。這時候我習(xí)慣把所有輸入在scoreboard內(nèi)部折疊成兩條隊列一條實際數(shù)據(jù)隊列一條期望數(shù)據(jù)隊列。每個analysis_imp的write方法只負(fù)責(zé)往對應(yīng)隊列push對象比對任務(wù)單獨跑。這樣多路輸入不會互相干擾也方便日后增加新數(shù)據(jù)源。多個端口同時到達(dá)時analysis_imp的write本身是function不存在阻塞等待因此不會因為write執(zhí)行順序不同而把隊列搞亂。你要擔(dān)心的只是隊列內(nèi)數(shù)據(jù)的有序性如果兩個數(shù)據(jù)源之間天然存在先后關(guān)系光靠analysis_imp自身是無法保證的需要在source端定義好發(fā)送順序或者在scoreboard側(cè)按id重排。我一般推薦前者因為更可控。4.3 復(fù)位場景FIFO殘留、事務(wù)作廢與通道清理驗證環(huán)境跑復(fù)位用例時TLM鏈路最容易出問題。復(fù)位期間DUT的輸出是隨機(jī)的monitor可能采樣到大量無效事務(wù)同時FIFO里可能屯著一堆復(fù)位前數(shù)據(jù)如果這些數(shù)據(jù)被scoreboard拿來比對必然大面積誤報。我的習(xí)慣是在事務(wù)里加一個reset標(biāo)記或者干脆用單獨的復(fù)位監(jiān)控信號復(fù)位開始時清掉中間FIFO里尚未消費的數(shù)據(jù)復(fù)位結(jié)束后重新建立比對基線。uvm_tlm_fifo本身提供了flush()方法可以在復(fù)位回調(diào)中調(diào)用把這之前緩存的事務(wù)全部丟棄。還有一種做法是給FIFO設(shè)置一個深度上限超過就說明消費端跟不上及時報告。這些內(nèi)容嚴(yán)格來說不算TLM協(xié)議本身但通信機(jī)制能不能在真實場景里用順往往就取決于這些邊界處理。5. 常見問題與排查實錄5.1 編譯報錯端口類型對不上UVM端口編譯錯誤最常見的一句話是“method not found”或者“cannot connect”根因基本都是端口類型不匹配。put端口連接了analysis_exportblocking端口連接了nonblocking端口或者imp里的方法簽名和接口定義不一致。這類錯誤在編譯階段就能發(fā)現(xiàn)解決辦法也很直接不要讓工具告訴你哪一步錯了而是自己先把鏈路走一遍。發(fā)起端口是什么協(xié)議方法中間轉(zhuǎn)發(fā)端口就必須保證兼容最終imp也必須實現(xiàn)對應(yīng)方法。我在搭建一個環(huán)境之前會先列一張表每個端口用什么族、什么模式、連到哪全部列清楚再寫代碼編譯錯誤大幅減少。5.2 仿真卡住阻塞調(diào)用沒人接仿真跑到一半掛住不動最常見的原因是blocking端口對上了但調(diào)用方向沒有人響應(yīng)。比如driver的put阻塞在put_port.put(tr)遲遲不返回是因為連接鏈路末端根本沒有實現(xiàn)對應(yīng)的put任務(wù)或者在put任務(wù)里等某個永遠(yuǎn)不會來的信號。遇到這種卡死第一步不是去翻run_phase而是打印當(dāng)前進(jìn)程的調(diào)用??纯ㄔ谀囊粋€端口方法上第二步回到連接關(guān)系確認(rèn)這個端口接的imp是否存在、是否真的在組件樹里被實例化。有一個小細(xì)節(jié)如果某個imp被聲明了但組件沒有被例化編譯不一定報錯connect_phase也不會報錯但通信就是完全斷的。這種情況最坑人排查時必須檢查組件樹的例化情況。5.3 數(shù)據(jù)只收到一部分廣播連接沒接齊analysis_port一對多廣播時如果只連了一個接收方另一個接收方自然什么都收不到。這聽起來很簡單但實際環(huán)境里數(shù)據(jù)源多、接收方也多常常在某次改動中漏掉一條connect。排查方法很好記打印整棵組件樹和連接拓?fù)湟粭l條核對端口連接。廣播場景下還得注意imp數(shù)量限制同一個analysis_imp被同一port重復(fù)connectUVM會提示重復(fù)連接錯誤而不同imp各自connect一次是正常的。如果看到連接已存在的提示多半是連接腳本里寫了重復(fù)邏輯。5.4 數(shù)據(jù)明明發(fā)了比對卻老是匹配不上這個問題經(jīng)常出在順序錯位和重復(fù)發(fā)送上。scoreboard先收到model的期望數(shù)據(jù)后收到monitor的實際數(shù)據(jù)兩份數(shù)據(jù)量一樣但到達(dá)順序不一致或者monitor在一次握手過程中發(fā)出了多筆事務(wù)scoreboard卻只記錄了一筆。排查這類問題我習(xí)慣在事務(wù)里打上連續(xù)遞增的序號再分別在write和比對開始的地方打印序號流水一次仿真跑下來順序?qū)Σ粚Α⒂袥]有漏發(fā)、有沒有重發(fā)一目了然。至于到底是發(fā)送端多發(fā)了還是接收端漏接了把序號流水和波形對齊看通常幾分鐘就能定位。5.5 我的排查順序和個人習(xí)慣最后分享一個我自己一直在用的排查順序也當(dāng)是給新手的一個檢查清單。第一先打印拓?fù)浯_認(rèn)每條連接都真實存在。第二確認(rèn)端口協(xié)議方向port、export、imp有沒有放反。第三確認(rèn)imp里面實現(xiàn)的方法簽名與接口定義完全一致尤其是方法是否需要帶ref參數(shù)、返回類型是什么。第四在關(guān)鍵write、put、get方法入口打印事務(wù)的基本字段看數(shù)據(jù)是否真的到達(dá)。第五對比發(fā)送順序與接收順序確認(rèn)沒有因為analysis廣播或者FIFO緩沖導(dǎo)致順序錯位。按這個順序走下來絕大多數(shù)TLM通信問題都能定位到具體某一層。我自己在搭建UVM環(huán)境時幾乎每一次都會重新走一遍這個流程事實證明它比想象中節(jié)省時間得多。TLM這套機(jī)制本身并不難難的是把端口方向和連接關(guān)系真正想清楚一旦想清楚了后面無論環(huán)境多復(fù)雜你都能一眼看出數(shù)據(jù)通路是否暢通。