MOESI FSM的全路径测试用例

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)。

cpp 复制代码
`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_end
    
    function new(string name = "moesi_transaction");
        super.new(name);
    endfunction
    
    constraint 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);
    endfunction
    
    task 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;})
        end
    endtask
endclass

// Driver: 驱动DUT输入
class moesi_driver extends uvm_driver #(moesi_transaction);
    `uvm_component_utils(moesi_driver)
    virtual interface moesi_vif vif;  // 假设有vif连接DUT
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
    task run_phase(uvm_phase phase);
        forever begin
            seq_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();
        end
    endtask
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);
    endfunction
    
    task 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有state
            ap.write(txn);
        end
    endtask
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;  // 初始I
    
    function 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)
        endcase
        if (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);
    endfunction
    
    function 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);
    endfunction
    
    function 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);
    endfunction
    
    function 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);
    endfunction
    
    function 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);
    endfunction
    
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        env = moesi_env::type_id::create("env", this);
    endfunction
    
    task 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)
endinterface

module top;
    // 实例化DUT和vif
    moesi_fsm_extended dut (...);  // 连接信号
    moesi_vif vif();
    // ... (信号连接)
    
    initial begin
        uvm_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:

cpp 复制代码
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};
        }
    endgroup
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
        moesi_cg = new();  // 实例化
    endfunction
    
    task run_phase(uvm_phase phase);
        // ... (前述monitor代码)
        forever begin
            @(posedge vif.clk);
            // 计算next_state (从DUT或参考)
            next_state = vif.state;  // 简化; 实际从scoreboard获取
            moesi_cg.sample();  // 采样覆盖
            // ... (其余代码)
        end
    endtask
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的转换。这是一个测试技巧,用于隔离和验证特定路径。下面详细解释其过程、原因和实际含义。

步骤分解(代码回顾)

cpp 复制代码
// 阶段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: 手动设置到Mstate = 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代码中已实现(基本序列覆盖类似路径)。
相关推荐
程序员三藏8 小时前
Selenium+python自动化测试:解决无法启动IE浏览器及报错问题
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例
葵野寺7 天前
【个人项目】跑者天地—测试用例
测试用例
中草药z7 天前
【测试】Bug+设计测试用例
功能测试·测试工具·测试用例·bug·压力测试·测试
@Aurora.8 天前
【接口自动化测试】---YAML、JSON Schema
selenium·单元测试·测试用例·压力测试·postman·ab测试·测试覆盖率
测试199810 天前
Pytest中实现自动生成测试用例脚本代码
自动化测试·软件测试·python·测试工具·测试用例·pytest·接口测试
lovingsoft12 天前
测试场景和测试用例应该如何选择?
测试用例
天才测试猿13 天前
什么是单元测试?
自动化测试·软件测试·python·测试工具·职场和发展·单元测试·测试用例
程序员三藏14 天前
软件测试之单元测试
自动化测试·软件测试·python·测试工具·职场和发展·单元测试·测试用例