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代码中已实现(基本序列覆盖类似路径)。