習(xí):從核心概念到搭建最小驗(yàn)證環(huán)境)
在驗(yàn)證領(lǐng)域從 SystemVerilog 逐步過渡到 UVM 的過程中很多人都會被組件劃分、phase 機(jī)制、config_db 傳參這些抽象概念繞暈。網(wǎng)上資料不少但要么是官方手冊的翻譯腔要么是一堆代碼片段缺少上下文。最近我在用 Codex 輔助學(xué)習(xí)和搭建 UVM 驗(yàn)證環(huán)境發(fā)現(xiàn)只要提問方式得當(dāng)它能把概念講解、代碼生成、報(bào)錯分析、面試刷題全部串起來非常適合驗(yàn)證方向的新人也適合想快速搭建驗(yàn)證環(huán)境的在職工程師。本文會從 Codex 安裝開始介紹如何通過提示詞學(xué)習(xí) UVM 核心概念再帶你使用 Codex 一步步搭建一個最小可用的 UVM 加法器驗(yàn)證環(huán)境最后補(bǔ)充寄存器模型學(xué)習(xí)、常見報(bào)錯排查和工程落地建議。閱讀本文不需要你已經(jīng)掌握 UVM 全貌只需要有最基本的 SystemVerilog 語法基礎(chǔ)以及一點(diǎn)命令行操作經(jīng)驗(yàn)。1. 為什么要用 Codex 學(xué) UVM驗(yàn)證學(xué)習(xí)的新思路1.1 UVM 學(xué)習(xí)的典型痛點(diǎn)UVM 的全稱是 Universal Verification Methodology即通用驗(yàn)證方法學(xué)。它并不是一種新的編程語言而是建立在 SystemVerilog 之上的一套類庫和編程規(guī)范用于搭建可重用、可擴(kuò)展的驗(yàn)證環(huán)境。它把驗(yàn)證環(huán)境抽象成一棵組件樹并通過 factory 機(jī)制、phase 機(jī)制、config_db 機(jī)制、sequence 機(jī)制等解決驗(yàn)證環(huán)境中的對象創(chuàng)建、執(zhí)行順序、配置傳遞和激勵生成問題。新手學(xué)習(xí) UVM 時(shí)通常會遇到幾個明顯的坎。第一組件數(shù)量多driver、monitor、sequencer、agent、scoreboard、env、test 之間關(guān)系復(fù)雜直接看完整代碼很容易迷失方向。第二UVM 中有大量宏和回調(diào)方法比如uvm_component_utils、uvm_object_utils、build_phase、connect_phase、run_phase如果不理解底層邏輯代碼只能靠記憶去抄。第三驗(yàn)證環(huán)境的運(yùn)行依賴 EDA 仿真器比如 ModelSim、QuestaSim、VCS很多入門者沒有現(xiàn)成的 license 或環(huán)境導(dǎo)致學(xué)習(xí)成本變高。這些痛點(diǎn)的本質(zhì)是UVM 不只是一個代碼庫更是一套“驗(yàn)證思維”。閱讀文檔只能知道組件有哪些但很難理解組件之間如何協(xié)作。如果有一個工具能隨時(shí)解釋概念、生成示例代碼、幫忙排查報(bào)錯學(xué)習(xí)效率會提升很多。Codex 恰好可以承擔(dān)這個角色。1.2 Codex 在驗(yàn)證學(xué)習(xí)中的價(jià)值Codex 是 OpenAI 推出的 AI 編程助手可以在終端中通過自然語言與其交互。它不只是簡單的代碼補(bǔ)全工具還可以理解項(xiàng)目上下文完成從代碼生成、代碼解釋、報(bào)錯分析到重構(gòu)優(yōu)化的完整任務(wù)。對于學(xué)習(xí) UVM 來說Codex 可以在以下幾個方面提供幫助概念講解用通俗語言解釋 UVM 的 phase 機(jī)制、factory 機(jī)制、config_db 機(jī)制等。代碼生成根據(jù)你給出的驗(yàn)證需求生成 transaction、sequence、driver、scoreboard 等組件代碼。代碼解釋把一段網(wǎng)上下載的 UVM 代碼逐行注釋幫助理解組件協(xié)作方式。報(bào)錯排查把仿真器報(bào)錯貼給 Codex讓它分析原因并給出修復(fù)方案。面試練習(xí)讓 Codex 扮演面試官針對 UVM 方法學(xué)提問幫你查漏補(bǔ)缺。學(xué)習(xí)規(guī)劃讓它制定一套從 SystemVerilog 到 UVM 再到驗(yàn)證項(xiàng)目的學(xué)習(xí)路線。不過需要明確一點(diǎn)Codex 可以幫你生成代碼但不能替代你思考。UVM 的工程落地最終還是要靠驗(yàn)證工程師自己理解場景、設(shè)計(jì)驗(yàn)證方案、檢查覆蓋率。把 Codex 當(dāng)作一個隨叫隨到的“結(jié)對工程師”而不是“答案生成器”是使用它的正確姿勢。1.3 本文適用讀者與學(xué)習(xí)目標(biāo)本文面向以下讀者正在學(xué)習(xí)芯片驗(yàn)證、FPGA 驗(yàn)證希望入門 UVM 的學(xué)生已經(jīng)有 SystemVerilog 基礎(chǔ)但還沒有完整搭建過 UVM 環(huán)境的工程師以及在實(shí)際項(xiàng)目中使用 UVM但想借助 AI 工具提高編碼和排錯效率的驗(yàn)證工程師。讀完本文后你應(yīng)該能夠完成這些事在自己的電腦上安裝并配置 Codex用自然語言向 Codex 提問學(xué)習(xí) UVM 核心概念讓 Codex 分步生成一個最小 UVM 驗(yàn)證環(huán)境理解 UVM 各組件的職責(zé)和連接方式用 Codex 輔助學(xué)習(xí)寄存器模型 RAL并應(yīng)付常見 UVM 面試問題。2. 環(huán)境準(zhǔn)備安裝 Codex 命令行工具2.1 安裝前置條件Codex CLI 是一個命令行工具主要面向開發(fā)者。安裝前需要確認(rèn)你的電腦具備以下條件操作系統(tǒng)Windows、macOS 或 Linux 均可不同系統(tǒng)下的安裝步驟稍有差異。Node.js 環(huán)境Codex CLI 通過 npm 分發(fā)需要安裝 Node.js 和 npm。建議使用較新的 LTS 版本版本太老可能導(dǎo)致安裝失敗或運(yùn)行異常。網(wǎng)絡(luò)環(huán)境Codex 需要聯(lián)網(wǎng)訪問模型服務(wù)請確保你的網(wǎng)絡(luò)環(huán)境能夠正常訪問 Codex 使用的 API。如果使用的是公司網(wǎng)絡(luò)還需要確認(rèn)網(wǎng)絡(luò)策略是否允許訪問相關(guān)域名。具體版本沒有統(tǒng)一標(biāo)準(zhǔn)因?yàn)?Codex 更新很快本文以常見環(huán)境為例重點(diǎn)演示配置思路。你在操作時(shí)可以根據(jù)本機(jī)環(huán)境靈活調(diào)整。2.2 安裝 Codex CLI最直接的安裝方式是通過 npm 全局安裝。打開終端執(zhí)行下面的命令npm install -g openai/codex安裝完成后驗(yàn)證是否安裝成功codex --version如果終端能輸出版本號說明 Codex CLI 已經(jīng)安裝成功。如果提示command not found可能是 npm 的全局 bin 目錄沒有加入系統(tǒng) PATH。你可以通過下面的命令查看 npm 全局安裝路徑npm prefix -g然后把輸出目錄中的 bin 子目錄加入系統(tǒng) PATH再重新打開終端測試。除了 npm 方式Codex 官方也可能會提供其他安裝方式比如通過 Homebrew 等包管理器安裝。具體以官方 Readme 或項(xiàng)目主頁的安裝說明為準(zhǔn)。安裝過程如果遇到權(quán)限問題可以檢查 npm 全局目錄是否有寫權(quán)限必要時(shí)使用sudo或給當(dāng)前用戶授權(quán)。2.3 登錄與模型配置安裝完成之后第一次使用需要登錄。在終端執(zhí)行codex login命令會引導(dǎo)你完成賬號授權(quán)登錄。登錄成功后Codex 就可以調(diào)用模型服務(wù)來處理你的提問和代碼任務(wù)。如果你希望使用 DeepSeek 等兼容 OpenAI 接口的模型服務(wù)Codex 也支持通過配置文件自定義模型供應(yīng)商。不同版本的配置字段略有差異建議先查看官方倉庫的 config 示例。整體思路是在配置中聲明模型供應(yīng)商的 base_url、API Key 環(huán)境變量、模型名稱并在全局 model 中指定。下面是一個結(jié)構(gòu)示意不是某個版本的完整配置使用時(shí)請以官方文檔為準(zhǔn)# ~/.codex/config.toml 結(jié)構(gòu)示意 model deepseek/deepseek-chat [model_providers.deepseek] name DeepSeek base_url https://api.deepseek.com/v1 env_key DEEPSEEK_API_KEY wire_api chat配置完成后需要在環(huán)境變量中設(shè)置對應(yīng)的 API Key例如export DEEPSEEK_API_KEY你的密鑰然后重新啟動 Codex就能使用你指定的模型服務(wù)了。這里需要特別提醒不同模型的能力差異很大UVM 相關(guān)代碼生成任務(wù)建議選擇推理能力較強(qiáng)的模型否則生成的代碼可能在 API 細(xì)節(jié)上出錯。2.4 第一次運(yùn)行 Codex安裝和登錄完成后可以通過交互模式啟動 Codexcodex進(jìn)入交互模式后你可以直接輸入自然語言問題。例如用通俗的語言解釋 UVM 中的 phase 機(jī)制Codex 會返回一段解釋。如果你只需要一次性輸出結(jié)果也可以直接作為命令行參數(shù)傳入codex 什么是 UVM這種方式適合快速測試交互模式則更適合深入學(xué)習(xí)、連續(xù)追問和多文件代碼生成。退出方式一般是在交互界面輸入 exit 或使用快捷鍵具體以你當(dāng)前版本的提示為準(zhǔn)。3. 先學(xué)會提問如何用 Codex 學(xué)習(xí) UVM 核心概念3.1 讓 Codex 用通俗語言解釋 UVM 架構(gòu)很多教程一上來就給出 UVM 類圖新手容易產(chǎn)生“每個字都認(rèn)識但連起來看不懂”的感覺。這時(shí)可以讓 Codex 換一種表達(dá)方式用比喻來解釋??梢赃@樣提問請用大白話解釋 UVM 的驗(yàn)證環(huán)境結(jié)構(gòu)。把 driver、monitor、scoreboard、agent、env、test 之間的關(guān)系講清楚可以用現(xiàn)實(shí)中的比喻不要直接貼代碼。Codex 通常會給出類似這樣的解釋思路test 是整個比賽的策劃env 是比賽場地agent 是場地中的一個工作小組driver 負(fù)責(zé)把指令發(fā)給選手monitor 負(fù)責(zé)記錄成績scoreboard 負(fù)責(zé)檢查成績是否合格。這種類比雖然不完全嚴(yán)謹(jǐn)?shù)珜θ腴T理解很有幫助。在此基礎(chǔ)上你可以繼續(xù)追問更深入的問題比如“UVM 為什么使用樹形結(jié)構(gòu)”“factory 機(jī)制解決什么問題”“config_db 是如何從上往下傳遞配置的”。通過連續(xù)提問把抽象概念拆成一個個具體問題比一次性閱讀大段文檔要輕松很多。3.2 讓 Codex 對比 UVM 與普通 SystemVerilog 驗(yàn)證很多從 SystemVerilog 驗(yàn)證轉(zhuǎn)向 UVM 的人會疑惑為什么不用簡單的initial塊和 task 完成激勵為什么要引入這么多類你可以讓 Codex 做一個對比總結(jié)。比如這樣提問傳統(tǒng) SystemVerilog 驗(yàn)證方式和 UVM 驗(yàn)證方式有什么區(qū)別請從可重用性、隨機(jī)激勵、覆蓋率收集、組件劃分、擴(kuò)展性幾個角度對比。通過學(xué)習(xí)對比輸出你會發(fā)現(xiàn) UVM 的核心價(jià)值在于重用和標(biāo)準(zhǔn)化。傳統(tǒng)方式的測試代碼往往和具體 DUT 強(qiáng)耦合換一個項(xiàng)目就要重新寫而 UVM 將驗(yàn)證環(huán)境拆分為多個標(biāo)準(zhǔn)化組件組件之間通過 TLM 端口和 sequence 機(jī)制交互換項(xiàng)目時(shí)只需要重新配置參數(shù)和封裝特定的 BFM很多組件可以直接復(fù)用。3.3 讓 Codex 拆解一個 UVM 類閱讀開源 UVM 代碼時(shí)最常見的困惑是不知道一個類從繼承到注冊再到 phase 回調(diào)的完整流程。遇到這種情況可以把代碼粘給 Codex讓它逐行注釋。例如你可以粘貼一段uvm_driver的代碼然后提問請逐行解釋這段 UVM driver 代碼。重點(diǎn)說明類繼承關(guān)系、uvm_component_utils 的作用、build_phase 中 uvm_config_db 的用法、run_phase 中封裝的握手邏輯。這種方式非常有效。Codex 會把代碼中隱含的 UVM 規(guī)范逐條拆出來比如“通過uvm_config_db獲取 virtual interface”“通過seq_item_port.get_next_item向 sequencer 請求事務(wù)”“通過item_done通知 sequencer 事務(wù)處理完畢”。這些內(nèi)容如果只看代碼確實(shí)不容易總結(jié)清楚。3.4 讓 Codex 出題自測并整理高頻面試題學(xué)習(xí) UVM 不只是為了寫代碼很多人在求職或轉(zhuǎn)崗時(shí)還需要通過面試。你可以讓 Codex 扮演面試官考察自己的理解深度。例如你現(xiàn)在是一名芯片驗(yàn)證面試官請圍繞 UVM 方法學(xué)向我提問考察范圍包括 phase 機(jī)制、factory 機(jī)制、sequence 機(jī)制、config_db 機(jī)制、寄存器模型。每次問一個問題等我回答后再給出評價(jià)和標(biāo)準(zhǔn)答案。這種模式非常適合自測。常見的 UVM 面試問題包括UVM 的 phase 階段分哪幾類run_phase 和 reset_phase 的區(qū)別是什么factory 的 override 怎么實(shí)現(xiàn)uvm_config_db的 get 和 set 參數(shù)分別代表什么sequence 和 sequencer 之間如何通信寄存器模型中的期望值、鏡像值和硬件實(shí)際值有什么區(qū)別你完全可以借助 Codex 把這些題目逐個過一遍。4. 實(shí)戰(zhàn)讓 Codex 幫你搭建一個最小 UVM 驗(yàn)證環(huán)境4.1 明確需求描述驗(yàn)證目標(biāo)學(xué)概念只是第一步真正理解 UVM 還需要動手搭建一個最小驗(yàn)證環(huán)境。這里我選擇最簡單的加法器作為 DUT目標(biāo)是驗(yàn)證一個 8 位加法器的功能是否正確。先給出待測設(shè)計(jì)。文件路徑rtl/adder.svmodule adder #(parameter WIDTH 8) ( input logic [WIDTH-1:0] a, input logic [WIDTH-1:0] b, output logic [WIDTH-1:0] sum ); assign sum a b; endmodule這個模塊很簡單輸入a和b輸出sum。驗(yàn)證環(huán)境要做的事情是隨機(jī)產(chǎn)生 8 位輸入驅(qū)動到 DUT同時(shí)讓參考模型計(jì)算預(yù)期結(jié)果然后比較 DUT 輸出和預(yù)期結(jié)果。向 Codex 描述需求時(shí)可以這樣寫請幫我搭建一個最小 UVM 驗(yàn)證環(huán)境DUT 是一個 8 位加法器輸入 a、b輸出 sum。 要求包含以下組件 1. transaction 類包含隨機(jī)輸入 a、b 和輸出 sum 2. sequence 類產(chǎn)生 10 個隨機(jī)激勵 3. driver從 sequencer 接收事務(wù)并驅(qū)動到接口 4. monitor采樣接口信號并發(fā)送給 scoreboard 5. scoreboard比較 DUT 輸出與預(yù)期結(jié)果 6. agent、env、test 和頂層 tb。 請分文件生成代碼每個文件標(biāo)注路徑。接下來幾節(jié)給出的代碼是讓 Codex 生成后按教學(xué)需要整理的結(jié)果。你可以把這段描述作為自己的提問模板然后結(jié)合 Codex 的實(shí)際輸出調(diào)整。4.2 生成事務(wù)類與序列先創(chuàng)建接口文件。文件路徑tb/adder_if.svinterface adder_if(input logic clk); logic [7:0] a; logic [7:0] b; logic [7:0] sum; endinterface接口在驗(yàn)證環(huán)境中用于連接 DUT 和驗(yàn)證組件。driver會驅(qū)動a和bmonitor會采樣a、b和sum。接下來是事務(wù)類。文件路徑tb/adder_transaction.svclass adder_transaction extends uvm_sequence_item; rand bit [7:0] a; rand bit [7:0] b; bit [7:0] sum; uvm_object_utils_begin(adder_transaction) uvm_field_int(a, UVM_ALL_ON) uvm_field_int(b, UVM_ALL_ON) uvm_field_int(sum, UVM_ALL_ON) uvm_object_utils_end function new(string name adder_transaction); super.new(name); endfunction endclass事務(wù)類繼承自uvm_sequence_item它描述的是驗(yàn)證環(huán)境中流動的一筆“數(shù)據(jù)”。這里把輸入a、b設(shè)置為rand這樣 sequence 可以隨機(jī)化它們。uvm_field_int宏會把字段自動注冊到 factory 的復(fù)制、比較、打印等方法中方便后續(xù)使用。然后是序列。文件路徑tb/adder_sequence.svclass adder_sequence extends uvm_sequence #(adder_transaction); uvm_object_utils(adder_sequence) function new(string name adder_sequence); super.new(name); endfunction task body(); repeat (10) begin req adder_transaction::type_id::create(req); start_item(req); assert(req.randomize()); finish_item(req); end endtask endclassadder_sequence繼承自uvm_sequence #(adder_transaction)它負(fù)責(zé)產(chǎn)生事務(wù)。body任務(wù)里循環(huán) 10 次每次創(chuàng)建事務(wù)、隨機(jī)化后通過start_item和finish_item發(fā)送給 sequencer。start_item會等待 sequencer 仲裁finish_item表示事務(wù)已經(jīng)交給 driver。4.3 生成驅(qū)動器與監(jiān)視器驅(qū)動器負(fù)責(zé)把事務(wù)數(shù)據(jù)驅(qū)動到 DUT 接口。文件路徑tb/adder_driver.svclass adder_driver extends uvm_driver #(adder_transaction); uvm_component_utils(adder_driver) virtual adder_if vif; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); if (!uvm_config_db#(virtual adder_if)::get(this, , vif, vif)) uvm_fatal(NOVIF, virtual interface not found) endfunction task run_phase(uvm_phase phase); forever begin seq_item_port.get_next_item(req); vif.a req.a; vif.b req.b; (posedge vif.clk); seq_item_port.item_done(); end endtask endclassdriver 是驗(yàn)證環(huán)境中的“執(zhí)行者”。它通過uvm_config_db獲取 virtual interface然后在run_phase中不斷從 sequencer 取事務(wù)把數(shù)據(jù)放到接口上。這里的item_done是通知 sequencer 當(dāng)前事務(wù)已經(jīng)處理完成可以發(fā)送下一筆。監(jiān)視器負(fù)責(zé)采樣接口數(shù)據(jù)。文件路徑tb/adder_monitor.svclass adder_monitor extends uvm_monitor #(adder_transaction); uvm_component_utils(adder_monitor) virtual adder_if vif; uvm_analysis_port #(adder_transaction) mon_ap; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); if (!uvm_config_db#(virtual adder_if)::get(this, , vif, vif)) uvm_fatal(NOVIF, virtual interface not found) mon_ap new(mon_ap, this); endfunction task run_phase(uvm_phase phase); adder_transaction tr; forever begin (posedge vif.clk); tr adder_transaction::type_id::create(tr); tr.a vif.a; tr.b vif.b; tr.sum vif.sum; mon_ap.write(tr); end endtask endclassmonitor 是驗(yàn)證環(huán)境中的“觀察者”。它在每個時(shí)鐘上升沿采樣接口信號把采樣結(jié)果封裝成事務(wù)對象并通過uvm_analysis_port廣播出去。scoreboard 可以訂閱這個端口從而拿到真實(shí)輸出進(jìn)行比對。4.4 生成代理、驗(yàn)證環(huán)境、測試與頂層agent 把 driver、monitor 和 sequencer 封裝在一起。文件路徑tb/adder_agent.svclass adder_agent extends uvm_agent; uvm_component_utils(adder_agent) adder_driver drv; adder_monitor mon; uvm_sequencer #(adder_transaction) sqr; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); drv adder_driver::type_id::create(drv, this); mon adder_monitor::type_id::create(mon, this); sqr uvm_sequencer#(adder_transaction)::type_id::create(sqr, this); endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); drv.seq_item_port.connect(sqr.seq_item_export); endfunction endclassagent 是驗(yàn)證環(huán)境中的可重用單元它把同一組 driver、monitor、sequencer 打包方便上層 env 直接使用。connect_phase 里把 driver 的seq_item_port和 sequencer 的seq_item_export連接起來構(gòu)成 sequence 到 driver 的數(shù)據(jù)通路。scoreboard 用于結(jié)果比對。文件路徑tb/adder_scoreboard.svclass adder_scoreboard extends uvm_scoreboard; uvm_component_utils(adder_scoreboard) uvm_analysis_imp #(adder_transaction, adder_scoreboard) mon_imp; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); mon_imp new(mon_imp, this); endfunction function void write(adder_transaction tr); bit [7:0] expect_sum; expect_sum tr.a tr.b; if (tr.sum ! expect_sum) begin uvm_error(SCOREBOARD, $sformatf(Mismatch: a%0d b%0d expect%0d actual%0d, tr.a, tr.b, expect_sum, tr.sum)) end else begin uvm_info(SCOREBOARD, $sformatf(Pass: a%0d b%0d sum%0d, tr.a, tr.b, tr.sum), UVM_MEDIUM) end endfunction endclassscoreboard 通過實(shí)現(xiàn)write方法接收 monitor 發(fā)來的事務(wù)對象。這里使用uvm_analysis_imp建立了一個 analysis 端口實(shí)際理解時(shí)可以把它看作一個可以接收廣播數(shù)據(jù)的“收件箱”。write方法里計(jì)算預(yù)期值并與 DUT 輸出比較。env 用于裝配 agent 和 scoreboard。文件路徑tb/adder_env.svclass adder_env extends uvm_env; uvm_component_utils(adder_env) adder_agent agent; adder_scoreboard scb; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); agent adder_agent::type_id::create(agent, this); scb adder_scoreboard::type_id::create(scb, this); endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); agent.mon.mon_ap.connect(scb.mon_imp); endfunction endclassenv 是驗(yàn)證環(huán)境的容器負(fù)責(zé)創(chuàng)建 agent 和 scoreboard并連接它們的端口。這一步體現(xiàn)了 UVM 樹形結(jié)構(gòu)的優(yōu)勢上層組件只需要關(guān)注組件的實(shí)例化和連接不需要關(guān)心內(nèi)部實(shí)現(xiàn)。test 是驗(yàn)證用例的入口。文件路徑tb/adder_test.svclass adder_test extends uvm_test; uvm_component_utils(adder_test) adder_env env; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); env adder_env::type_id::create(env, this); endfunction task run_phase(uvm_phase phase); adder_sequence seq; phase.raise_objection(this); seq adder_sequence::type_id::create(seq); assert(seq.randomize()); seq.start(env.agent.sqr); phase.drop_objection(this