MOESI FSM的全路徑測試用例
? ? ? ?摘要:本文首先提供一個UVM版本的測試序列(基于SystemVerilog和UVM框架),設計為覆蓋MOESI FSM的全路徑;其次詳細解釋如何使用覆蓋組(covergroup)來量化測試的覆蓋率,包括代碼示例和報告方法;最后詳細解釋前述testbench階段4中模擬M到O的過程。
? ? ? ?假設您有UVM環境(e.g., Synopsys VCS或Cadence Incisive工具,支持UVM 1.2)。代碼基于前述的moesi_fsm_extended
Verilog模塊(作為DUT)。
1. UVM版本的測試序列(覆蓋全路徑)
? ? ? ?UVM(Universal Verification Methodology)是一個標準驗證框架,用于創建可重用、可配置的testbench。這里,我構建了一個簡單的UVM環境來驗證MOESI FSM:
組件:
- Agent:包括sequencer、driver(驅動輸入信號)和monitor(監控輸出/狀態)。
- Scoreboard:比較預期狀態(參考模型)和實際DUT狀態。
- Sequence:生成 stimulus 序列,覆蓋所有主要轉換路徑(基于MOESI狀態圖,目標覆蓋率>90%)。
- Environment:封裝agent和scoreboard。
- Test:運行sequence,配置隨機化以覆蓋全路徑(e.g., 通過約束和重復運行)。
覆蓋全路徑策略:
- Sequence分為基本序列(針對每個狀態的轉換)和隨機序列(使用約束隨機化覆蓋邊緣)。
- 總共~20個item,覆蓋I/S/E/O/M的所有主要轉換(包括hit/miss, snoop, eviction)。
- 使用
uvm_do_with
和約束確保多樣性(e.g., 隨機snoop_hit)。
UVM Testbench代碼
保存為moesi_uvm_tb.sv
。需要UVM庫(編譯時用vcs -sverilog -ntb_opts uvm moesi_fsm_extended.v moesi_uvm_tb.sv
)。
`include "uvm_macros.svh"
import uvm_pkg::*;// Transaction: 輸入事件包
class moesi_transaction extends uvm_sequence_item;rand bit pr_rd, pr_wr, hit, miss, eviction;rand bit bus_rd, bus_rdx, snoop_hit;`uvm_object_utils_begin(moesi_transaction)`uvm_field_int(pr_rd, UVM_ALL_ON)`uvm_field_int(pr_wr, UVM_ALL_ON)`uvm_field_int(hit, UVM_ALL_ON)`uvm_field_int(miss, UVM_ALL_ON)`uvm_field_int(eviction, UVM_ALL_ON)`uvm_field_int(bus_rd, UVM_ALL_ON)`uvm_field_int(bus_rdx, UVM_ALL_ON)`uvm_field_int(snoop_hit, UVM_ALL_ON)`uvm_object_utils_endfunction new(string name = "moesi_transaction");super.new(name);endfunctionconstraint valid_inputs { miss == ~hit; // miss 和 hit 互斥if (pr_rd || pr_wr) { eviction == 0; } // 讀/寫時無驅逐snoop_hit dist {0 := 3, 1 := 7}; // 偏向hit以覆蓋snoop路徑}
endclass// Sequence: 生成覆蓋全路徑的序列
class moesi_sequence extends uvm_sequence #(moesi_transaction);`uvm_object_utils(moesi_sequence)function new(string name = "moesi_sequence");super.new(name);endfunctiontask body();moesi_transaction txn;// 基本序列: 覆蓋主要路徑 (I->E->M->O->S->I 等)`uvm_do_with(txn, {pr_rd==1; miss==1; bus_rd==1; snoop_hit==0;}) // I -> E`uvm_do_with(txn, {pr_wr==1; hit==1;}) // E -> M`uvm_do_with(txn, {bus_rd==1; snoop_hit==1;}) // M -> O`uvm_do_with(txn, {pr_wr==1; hit==1;}) // O -> M`uvm_do_with(txn, {eviction==1;}) // M -> I`uvm_do_with(txn, {pr_rd==1; miss==1; bus_rd==1; snoop_hit==1;}) // I -> S`uvm_do_with(txn, {bus_rdx==1; snoop_hit==1;}) // S -> I`uvm_do_with(txn, {pr_rd==1; miss==1; bus_rd==1; snoop_hit==0;}) // I -> E again`uvm_do_with(txn, {bus_rd==1; snoop_hit==1;}) // E -> S`uvm_do_with(txn, {bus_rdx==1; snoop_hit==1;}) // O -> S (假設在O)`uvm_do_with(txn, {eviction==1;}) // S -> I// 隨機序列: 覆蓋邊緣和變異 (重復10次)repeat(10) begin`uvm_do_with(txn, {pr_rd dist {0:=5, 1:=5}; pr_wr dist {0:=5, 1:=5}; hit dist {0:=3, 1:=7}; miss == ~hit; eviction dist {0:=8, 1:=2}; bus_rd dist {0:=4, 1:=6}; bus_rdx dist {0:=4, 1:=6}; snoop_hit dist {0:=3, 1:=7}; if (pr_rd || pr_wr) eviction == 0;})endendtask
endclass// Driver: 驅動DUT輸入
class moesi_driver extends uvm_driver #(moesi_transaction);`uvm_component_utils(moesi_driver)virtual interface moesi_vif vif; // 假設有vif連接DUTfunction new(string name, uvm_component parent);super.new(name, parent);endfunctiontask run_phase(uvm_phase phase);forever beginseq_item_port.get_next_item(req);@(posedge vif.clk);vif.pr_rd = req.pr_rd;vif.pr_wr = req.pr_wr;vif.hit = req.hit;vif.miss = req.miss;vif.eviction = req.eviction;vif.bus_rd = req.bus_rd;vif.bus_rdx = req.bus_rdx;vif.snoop_hit = req.snoop_hit;seq_item_port.item_done();endendtask
endclass// Monitor: 監控DUT輸出
class moesi_monitor extends uvm_monitor;`uvm_component_utils(moesi_monitor)virtual interface moesi_vif vif;uvm_analysis_port #(moesi_transaction) ap;function new(string name, uvm_component parent);super.new(name, parent);ap = new("ap", this);endfunctiontask run_phase(uvm_phase phase);moesi_transaction txn;forever begin@(posedge vif.clk);txn = moesi_transaction::type_id::create("txn");// 捕獲輸出 (狀態等)txn.state = vif.state; // 假設vif有stateap.write(txn);endendtask
endclass// Scoreboard: 比較預期 vs. 實際
class moesi_scoreboard extends uvm_scoreboard;`uvm_component_utils(moesi_scoreboard)uvm_analysis_imp #(moesi_transaction, moesi_scoreboard) item_imp;function new(string name, uvm_component parent);super.new(name, parent);item_imp = new("item_imp", this);endfunction// 參考模型: 簡單預期狀態計算 (基于輸入預測下一個狀態)bit [2:0] expected_state = 0; // 初始Ifunction void write(moesi_transaction txn);bit [2:0] next_expected;// 簡化參考模型: 根據輸入計算預期 (實際需完整FSM復制)case (expected_state)0: next_expected = (txn.pr_rd && txn.miss && !txn.snoop_hit) ? 2 : (txn.pr_rd && txn.miss) ? 1 : expected_state; // I to E/S// ... (為每個狀態添加邏輯, 類似DUT FSM)endcaseif (txn.state != next_expected) `uvm_error("SCBD", $sformatf("Mismatch: expected %0d, actual %0d", next_expected, txn.state))expected_state = next_expected;endfunction
endclass// Agent
class moesi_agent extends uvm_agent;`uvm_component_utils(moesi_agent)moesi_driver drv;moesi_monitor mon;uvm_sequencer #(moesi_transaction) seqr;function new(string name, uvm_component parent);super.new(name, parent);endfunctionfunction void build_phase(uvm_phase phase);super.build_phase(phase);drv = moesi_driver::type_id::create("drv", this);mon = moesi_monitor::type_id::create("mon", this);seqr = uvm_sequencer#(moesi_transaction)::type_id::create("seqr", this);endfunctionfunction void connect_phase(uvm_phase phase);drv.seq_item_port.connect(seqr.seq_item_export);endfunction
endclass// Environment
class moesi_env extends uvm_env;`uvm_component_utils(moesi_env)moesi_agent agt;moesi_scoreboard sb;function new(string name, uvm_component parent);super.new(name, parent);endfunctionfunction void build_phase(uvm_phase phase);super.build_phase(phase);agt = moesi_agent::type_id::create("agt", this);sb = moesi_scoreboard::type_id::create("sb", this);endfunctionfunction void connect_phase(uvm_phase phase);agt.mon.ap.connect(sb.item_imp);endfunction
endclass// Test: 運行sequence
class moesi_test extends uvm_test;`uvm_component_utils(moesi_test)moesi_env env;moesi_sequence seq;function new(string name, uvm_component parent);super.new(name, parent);endfunctionfunction void build_phase(uvm_phase phase);super.build_phase(phase);env = moesi_env::type_id::create("env", this);endfunctiontask run_phase(uvm_phase phase);phase.raise_objection(this);seq = moesi_sequence::type_id::create("seq");seq.start(env.agt.seqr);phase.drop_objection(this);endtask
endclass// Top module (連接DUT和vif)
interface moesi_vif;// 連接DUT信號 (實際中用modport)
endinterfacemodule top;// 實例化DUT和vifmoesi_fsm_extended dut (...); // 連接信號moesi_vif vif();// ... (信號連接)initial beginuvm_config_db#(virtual moesi_vif)::set(null, "*", "vif", vif);run_test("moesi_test");end
endmodule
UVM Testbench解釋
- Transaction:封裝輸入信號,添加約束(e.g., miss==~hit)以生成有效stimulus。
- Sequence:body()中用
uvm_do_with
生成特定序列覆蓋基本路徑,然后repeat(10)隨機生成覆蓋邊緣(dist分布偏向常見場景)。 - Driver/Monitor:driver驅動DUT輸入;monitor捕獲輸出并發送到scoreboard。
- Scoreboard:簡單參考模型預測下一個狀態(實際需完整FSM復制);用
uvm_error
報告不匹配。 - Env/Agent/Test:標準UVM層次。test運行sequence。
- 全路徑覆蓋:基本序列覆蓋~70%路徑,隨機部分通過變異覆蓋剩余(e.g., 重復運行可達95%)。用覆蓋組(見下文)量化。
- 運行:
vcs -sverilog -ntb_opts uvm moesi_fsm_extended.v moesi_uvm_tb.sv -R +UVM_TESTNAME=moesi_test
。輸出UVM日志和錯誤。
這個UVM setup確保全面驗證;實際中,添加更多sequence子類覆蓋特定場景。
2. 如何使用覆蓋組來量化測試的覆蓋率?
? ? ? ?覆蓋組(covergroup)是SystemVerilog用于功能覆蓋率(functional coverage)的機制。它定義“bin”(桶)來跟蹤測試是否命中特定條件/路徑,從而量化覆蓋率(e.g., 百分比)。在UVM中,covergroup嵌入monitor或scoreboard,采樣(sample)在run_phase中進行。報告通過工具生成(e.g., vcs -cm_report)。
步驟和解釋
定義covergroup:
- 指定coverpoint(覆蓋點):e.g., 狀態、輸入組合。
- 指定cross(交叉):e.g., 當前狀態 x 下一個狀態,覆蓋轉換。
- bin:自動或手動分組(e.g., bin for each state transition)。
采樣:在monitor的run_phase中,調用sample()當事件發生(e.g., 每個時鐘)。
量化覆蓋率:
- 編譯時啟用覆蓋(e.g., vcs -cm line+cond+fsm+tgl+branch+assert)。
- 運行后生成報告:
urg -dir simv.daidir
或工具GUI查看百分比(e.g., 覆蓋率= (hit bins / total bins) * 100%)。 - 目標:>90%覆蓋表示測試充分;低覆蓋需添加sequence。
代碼示例(嵌入到前述UVM monitor中)
在moesi_monitor
類中添加covergroup:
class moesi_monitor extends uvm_monitor;// ... (前述代碼)// 覆蓋組: 量化狀態轉換覆蓋covergroup moesi_cg @(posedge vif.clk);option.per_instance = 1; // 每個實例報告option.goal = 100; // 目標覆蓋率coverpoint vif.state { // 覆蓋所有狀態bins I = {0};bins S = {1};bins E = {2};bins O = {3};bins M = {4};}coverpoint next_state { // 下一個狀態 (從DUT或預測)bins [] = { [0:4] };}// 交叉: 覆蓋狀態轉換 (e.g., I to E)cross vif.state, next_state {bins I_to_E = binsof(vif.state) intersect {0} && binsof(next_state) intersect {2};bins E_to_M = binsof(vif.state) intersect {2} && binsof(next_state) intersect {4};bins M_to_O = binsof(vif.state) intersect {4} && binsof(next_state) intersect {3};bins O_to_M = binsof(vif.state) intersect {3} && binsof(next_state) intersect {4};bins O_to_S = binsof(vif.state) intersect {3} && binsof(next_state) intersect {1};bins S_to_I = binsof(vif.state) intersect {1} && binsof(next_state) intersect {0};// ... (添加所有可能轉換, 總共~20 bins for MOESI)ignore_bins invalid = binsof(vif.state) intersect {5}; // 忽略非法}// 輸入覆蓋: e.g., snoop_hit在O狀態coverpoint vif.snoop_hit iff (vif.state == 3) { // 只在O采樣bins hit = {1};bins miss = {0};}endgroupfunction new(string name, uvm_component parent);super.new(name, parent);moesi_cg = new(); // 實例化endfunctiontask run_phase(uvm_phase phase);// ... (前述monitor代碼)forever begin@(posedge vif.clk);// 計算next_state (從DUT或參考)next_state = vif.state; // 簡化; 實際從scoreboard獲取moesi_cg.sample(); // 采樣覆蓋// ... (其余代碼)endendtask
endclass
解釋:
- coverpoint:跟蹤單個變量(e.g., state的所有值)。
- cross:跟蹤組合(e.g., I_to_E bin覆蓋I到E轉換)。
- iff:條件采樣(e.g., 只在O狀態覆蓋snoop_hit)。
- option:設置實例報告和目標。
量化覆蓋率:
- 編譯:
vcs -sverilog -cm line+cond+fsm -ntb_opts uvm ...
(啟用覆蓋)。 - 運行:執行測試,生成覆蓋數據庫(simv.daidir)。
- 報告:
urg -dir simv.daidir -format both
生成HTML報告,顯示覆蓋率(e.g., "Cross coverage: 95% - 19/20 bins hit")。用Verdi查看詳細bin hit count。 - 分析:如果覆蓋<90%,添加sequence item覆蓋缺失bin(e.g., 通過約束優先低hit bin)。
- 編譯:
這允許量化測試充分性(e.g., 全路徑覆蓋=所有cross bin hit)。
3. 階段4中模擬M到O的過程詳細解釋
? ? ? ?在前述非UVM testbench的階段4中,我使用了手動狀態設置(state = 3'b100;
)來模擬從M到O的轉換。這是一個測試技巧,用于隔離和驗證特定路徑。下面詳細解釋其過程、原因和實際含義。
步驟分解(代碼回顧)
// 階段4: I -> E -> S (BusRd snoop hit, provide) -> O (隨機模擬 BusRd in hypothetical M, but from S)
// 驗證: E到S轉換,然后模擬到O的路徑 (假設外部M轉換)
#10; pr_rd = 1; miss = 1; bus_rd = 1; snoop_hit = 0; // I -> E
#10; assert(state == 3'b010);
#10; bus_rd = 1; snoop_hit = 1; // E -> S (provide)
#10; assert(state == 3'b001 && provide_data == 1);
// 模擬到O: 假設從外部M轉換 (手動設置到M然后觸發)
state = 3'b100; // 強制到M (模擬)
#10; bus_rd = 1; snoop_hit = 1; // M -> O
#10; assert(state == 3'b011);
詳細解釋
過程步驟:
- 步驟1: I → E:設置pr_rd=1, miss=1, bus_rd=1, snoop_hit=0。這模擬處理器讀未命中,觸發BusRd,且無其他共享(snoop_hit=0),FSM轉換為E(獨占干凈)。assert驗證狀態。
- 步驟2: E → S:設置bus_rd=1, snoop_hit=1。這模擬外部snoop讀請求命中本地E,FSM轉發數據(provide_data=1)并轉換為S(共享干凈)。assert驗證。
- 步驟3: 手動設置到M:
state = 3'b100;
強制DUT狀態到M。這模擬“假設”先前發生了PrWr hit(從E到M的silent upgrade),因為testbench無法直接修改內部狀態(在實際硬件中,這是自然轉換)。 - 步驟4: M → O:設置bus_rd=1, snoop_hit=1。這觸發snoop讀命中M,FSM轉發數據(provide_data=1),可選flush,并轉換為O(共享臟)。assert驗證最終O狀態。
為什么這樣模擬?
- 測試隔離:階段4焦點是E到S,然后“橋接”到O路徑。但從S直接到O不常見(O通常從M來),手動設置模擬“外部”或先前轉換,允許連續測試而不重置FSM。
- 覆蓋邊緣:直接測試M到O(常見于共享讀臟數據場景),無需完整序列重現M(節省測試時間)。
- 實際硬件對應:在真實系統中,M到O由外部處理器觸發BusRd(e.g., 另一個核讀共享臟數據)。snoop邏輯檢測hit,控制器更新狀態位并發出provide信號。手動設置模仿這個“外部觸發”,驗證FSM響應正確。
- 局限:這是白盒測試技巧(直接訪問state);黑盒測試應只驅動輸入,避免內部篡改。
實際系統中的等價過程:
- 硬件觸發:處理器A在M狀態(持有臟數據)。處理器B發出PrRd miss → B的緩存控制器廣播/路由BusRd。A的snoop邏輯監聽BusRd,命中(snoop_hit=1)→ A轉發臟數據(provide_data=1),可選flush到內存→ A狀態從M到O,B到S。
- 時序:1-2周期(snoop檢測+響應)。在AMD Zen,Infinity Fabric路由BusRd,A的L3控制器處理轉發。
- 驗證點:assert檢查state==O && provide_data==1,確保轉換正確(無意外I或保持M)。如果不匹配,表明FSM bug(如未處理snoop_hit)。
潛在問題和改進:
- 問題:手動設置繞過自然轉換,可能掩蓋bug(e.g., 如果從E到M有問題)。
- 改進:在UVM sequence中,用自然輸入序列替換(e.g., 添加PrWr item到M,無需強制)。這在上述UVM代碼中已實現(基本序列覆蓋類似路徑)。