当前位置: 首页 > news >正文

UVM验证—UVM 简述

1 虚接口(回顾)

在 SystemVerilog 验证环境中,虚拟接口(virtual interface)是连接 “抽象验证组件(如 driver/monitor 等类)” 与 “物理硬件信号” 的核心桥梁。它解决了一个关键问题:SystemVerilog 的类(class)无法直接访问硬件信号(如 wire/reg 类型的端口),而虚拟接口通过 “句柄引用” 的方式,让类能够间接操作或读取硬件信号,实现验证环境与 DUT 的交互。

1.1 示例

假设我们有一个简单的 DUT(加法器),RTL 代码如下:

// DUT:一个简单的加法器
module adder(input        clk,      // 时钟信号(硬件信号)input        rstn,     // 复位信号(硬件信号)input        en,       // 使能信号(硬件信号)input [3:0]  a, b,     // 输入数据(硬件信号)output [4:0] sum       // 输出结果(硬件信号)
);
// 内部逻辑:当en=1时,sum = a + b
always @(posedge clk or negedge rstn) beginif(!rstn) sum <= 0;else if(en) sum <= a + b;
end
endmodule

步骤 1:定义物理接口(连接验证环境和 DUT)

首先需要一个物理接口(interface),把 DUT 的硬件信号 “打包”,作为验证环境和 DUT 的连接点:

// 物理接口:定义DUT的所有硬件信号
interface adder_if;logic        clk;      // 时钟信号(硬件信号)logic        rstn;     // 复位信号(硬件信号)logic        en;       // 使能信号(硬件信号)logic [3:0]  a, b;     // 输入数据(硬件信号)logic [4:0]  sum;      // 输出结果(硬件信号)
endinterface

步骤 2:在 Testbench 中绑定 DUT 和接口

在 Testbench 中,将 DUT 的硬件信号与接口信号 “绑定”,让接口直接对应 DUT 的物理信号:

module tb;// 1. 实例化物理接口(创建一个具体的“信号集合”)adder_if phy_if();  // 物理接口实例,包含实际的硬件信号// 2. 实例化DUT,将其端口与物理接口的信号绑定adder u_adder(.clk  (phy_if.clk),   // DUT的clk → 物理接口的clk.rstn (phy_if.rstn),  // DUT的rstn → 物理接口的rstn.en   (phy_if.en),    // DUT的en → 物理接口的en.a    (phy_if.a),     // DUT的a → 物理接口的a.b    (phy_if.b),     // DUT的b → 物理接口的b.sum  (phy_if.sum)    // DUT的sum → 物理接口的sum);
endmodule

步骤 3:用虚拟接口让 driver 访问硬件信号 — 作为物理接口的 “句柄”

  • 虚拟接口是物理接口的 “引用(句柄)”,本质是一个变量,用于指向物理接口实例。它的声明方式是在接口类型前加virtual关键字。
  • driver 是一个类(class),本身无法直接访问硬件信号(SystemVerilog 的类不能直接引用硬件信号),因此需要通过虚拟接口(virtual interface) 间接访问:
// Driver类:负责给DUT发送激励(需要访问硬件信号)
class driver;// 声明虚拟接口(指向物理接口的句柄)virtual adder_if vif;  // 关键:虚拟接口作为“桥梁”// 构造函数:通过参数接收物理接口的引用,赋值给虚拟接口function new(virtual adder_if vif);this.vif = vif;  // 虚拟接口指向物理接口实例(phy_if)endfunction
endclass

步骤 4:在 Testbench 中传递虚拟接口

在 Testbench 中,将物理接口的实例(phy_if)通过构造函数new(vif)传递给验证组件(如 driver),此时组件的虚拟接口就指向了实际的物理接口,从而能够访问硬件信号。

        

步骤 5:验证组件通过虚拟接口与 DUT 交互

验证组件(driver/monitor)通过虚拟接口,直接读写物理接口的信号,从而实现与 DUT 的交互:

  • driver 通过虚拟接口给 DUT 发送激励(如设置en=1a=3b=5);
  • monitor 通过虚拟接口采样 DUT 的输出(如读取sum=8)。
// driver中驱动激励的任务(通过虚拟接口)
task driver::drive();forever begin@(posedge vif.clk);  // 同步到时钟上升沿(通过虚拟接口访问clk)vif.en = 1;          // 使能有效(操作硬件信号en)vif.a = 3;           // 输入数据a=3(操作硬件信号a)vif.b = 5;           // 输入数据b=5(操作硬件信号b)// DUT会自动计算sum = a + b = 8(硬件信号sum被更新)end
endtask// monitor中采样输出的任务(通过虚拟接口)
task monitor::sample();forever begin@(posedge vif.clk);  // 同步到时钟上升沿if(vif.en) begin     // 当使能有效时(读取硬件信号en)$display("DUT输出sum = %d", vif.sum);  // 读取硬件信号sumendend
endtask

1.2 虚拟接口的核心作用与优势

连接抽象与物理
验证组件(类)是抽象的软件逻辑,无法直接访问硬件信号;虚拟接口作为 “句柄”,让类能够间接操作物理信号,实现 “软件验证环境” 与 “硬件 DUT” 的通信。

提高可重用性
若更换 DUT,只需修改物理接口的绑定(如将adder_if换成multiplier_if),验证组件(driver/monitor)的代码无需修改(只需传递新的虚拟接口),大幅提升验证环境的复用性。

支持多实例验证
一个虚拟接口可以指向不同的物理接口实例,支持同时验证多个 DUT(如多个加法器),每个实例用独立的物理接口,虚拟接口动态指向对应实例即可。

1.3 一句话总结

虚拟接口就像验证组件(如 driver)的 “遥控器”:物理接口是 “插座”(连接 DUT 的硬件信号),虚拟接口是 “遥控器的无线连接”,让组件无需直接接触硬件,就能通过这个 “无线连接” 控制或读取 DUT 的信号,是 SystemVerilog 验证中 “软硬件交互” 的核心机制。

2 Hello Word(UVM)

2.1 代码

import uvm_pkg::*;
`include "uvm_macros.svh"// 定义环境类,继承自uvm_env
class hello_world_env extends uvm_env;// 构造函数function new(string name, uvm_component parent = null);super.new(name, parent);  // 调用父类构造函数endfunction : new
endclass : hello_world_env// 定义测试类,继承自uvm_test
class hello_world_test extends uvm_test;`uvm_component_utils(hello_world_test)  // 注册组件到UVM工厂hello_world_env env;  // 声明环境实例// 构造函数function new(string name, uvm_component parent = null);super.new(name, parent);env = new("env", this);  // 实例化环境,指定父组件为当前测试类endfunction : new// 主测试阶段任务task main_phase(uvm_phase phase);super.main_phase(phase);// 提起异议:告诉UVM不要提前结束仿真phase.raise_objection(this);#1000ns;  // 延迟1000ns// 打印信息:UVM的标准打印宏`uvm_info(this.get_name(), "***Hello World From UVM***", UVM_LOW)// 放下异议:允许UVM结束仿真phase.drop_objection(this);endtask : main_phase
endclass : hello_world_test// 测试平台顶层模块
module tb_hello_world();initial begin// 启动UVM测试,指定运行hello_world_testrun_test("hello_world_test");end
endmodule : tb_hello_world

2.2 代码结构与核心概念解析

  • UVM 库与宏引入
    • import uvm_pkg::*;:导入 UVM 库,包含所有 UVM 核心类和方法
    • include "uvm_macros.svh":引入 UVM 宏定义(如uvm_component_utilsuvm_info等)
  • 环境类(hello_world_env)

    • 继承自uvm_env(UVM 环境基类),用于组织验证组件
    • 目前是一个空环境,实际项目中会包含 driver、monitor、checker 等组件
  • 测试类(hello_world_test)
    • 继承自uvm_test(UVM 测试基类),是 UVM 验证的入口点
    • `uvm_component_utils(hello_world_test):将类注册到 UVM 工厂,使其可以通过字符串动态创建
    • 实例化了hello_world_env环境,形成 "测试 - 环境" 的层次结构
  • 主测试阶段(main_phase)
    • UVM 的标准 phase 机制,main_phase是主要的测试执行阶段
    • phase.raise_objection(this):阻止 UVM 仿真提前结束(UVM 默认在没有异议时会立即结束)
    • #1000ns:仿真延迟,模拟实际测试中的时间流逝
    • `uvm_info(...):UVM 标准打印宏,输出 "Hello World" 信息,包含三个参数:
      • 第一个参数:组件名称(通过this.get_name()获取)
      • 第二个参数:要打印的消息
      • 第三个参数:消息冗余度(UVM_LOW 表示基本信息)
    • phase.drop_objection(this):释放异议,允许 UVM 结束仿真
  • 顶层测试平台(tb_hello_world)
    • 简单的模块,通过run_test("hello_world_test")启动 UVM 测试
    • run_test函数会根据传入的字符串("hello_world_test")从 UVM 工厂中创建对应的测试实例并执行

2.3 运行流程

  • 仿真开始,执行tb_hello_world模块的initial
  • 调用run_test启动 UVM 框架,创建hello_world_test实例
  • hello_world_test的构造函数创建hello_world_env环境
  • UVM 自动执行各 phase,当进入main_phase时:
    • 提起异议,防止仿真结束
    • 延迟 1000ns
    • 打印 "Hello World From UVM"
    • 放下异议
  • 所有异议被放下后,UVM 结束仿真

3 UVM组件概述(树形结构)

3.1 类继承树的核心层级

从顶到底,基础类层层派生,构建 UVM 的 “基因库”:

  • uvm_void
    UVM 最顶层的空类,是所有类的 “祖先”(类似 Java 的 Object ),没有实际功能,仅作为继承起点。

  • uvm_object
    UVM 中所有 “可复用对象” 的基类,提供基本方法(如 copy/compare/print ),是 UVM 面向对象设计的基础。

3.2 两大分支(uvm_transaction vs uvm_component

uvm_object 派生为两大核心分支,对应 UVM 的两类关键对象:

(1)uvm_transaction 分支(“数据对象” 侧 )

  • 作用:处理 “事务级数据”(如测试用例的激励、响应 ),不参与组件层次管理,专注数据操作。
  • 派生关系
    uvm_transaction → uvm_sequence_item → uvm_sequence_base → uvm_sequence
  • 典型类
    • uvm_sequence_item:最小的事务单元(如包含 addr/data/en 的数据包 )。
    • uvm_sequence:事务序列,可把多个 sequence_item 按顺序发给 sequencer,构建复杂测试场景。

(2)uvm_component 分支(“组件层次” 侧 )

  • 作用:处理 “验证组件”(如 driver/monitor/env ),负责构建 UVM 的层次化结构(父组件→子组件 ),并通过 phase 机制控制执行顺序。
  • 派生关系
    uvm_object → uvm_report_object → uvm_component
  • 典型类(右侧分支 ):
    • uvm_driver:驱动 DUT 输入,把 sequence 发的 item 转化为硬件信号。
    • uvm_monitor:采样 DUT 输出,把硬件信号转化为 sequence_item
    • uvm_agent:封装 driver+monitor+sequencer,作为一个 “功能单元” 复用。
    • uvm_scoreboard:比对 monitor 采的实际数据和 driver 发的预期数据,判断 DUT 功能。
    • uvm_env:封装多个 agent/scoreboard,构建更上层的验证环境。
    • uvm_test:验证的顶层入口,实例化 uvm_env,配置并启动测试。
    • uvm_root:全局唯一的顶层组件(隐藏的 “根节点” ),所有 uvm_test 都是它的子组件。

(3)uvm_sequencer 相关(序列器 )

  • uvm_sequencer_base → uvm_sequencer_param → uvm_sequencer
  • 作用:作为 sequence 和 driver 的 “中间人”,管理 sequence 发的 item,按顺序转发给 driver,是 “序列驱动” 机制的核心。

3.3 继承关系的意义

  • 代码复用:基础类(如 uvm_object/uvm_component )实现通用功能(如 new 构造、phase 执行 ),派生类(如 uvm_driver )只需专注自己的逻辑(如驱动硬件信号 )。
  • 层次化验证uvm_component 分支的类通过继承,天然支持 父→子组件层次(如 uvm_env 包含 uvm_agent ),配合 phase 机制(如 build_phase/run_phase ),让验证平台的执行顺序可控(先建父组件,再建子组件;先跑 reset_phase,再跑 main_phase )。

3.4 和 phase 机制的关联

  • 图里提到 “内部 phase 机制可以使得平台的顺序执行”,因为 uvm_component 及其派生类 都继承了 phase 执行逻辑:
    • UVM 会按固定顺序(如 build_phaseconnect_phaserun_phase )遍历组件层次树,依次执行每个组件的 phase 方法,保证验证平台 “先构建、再连接、最后运行” 的有序流程。

这张图是 UVM 的 “家族基因树”:从 uvm_void 开始,派生 uvm_object,再分 “事务数据”(uvm_transaction 分支 )和 “组件层次”(uvm_component 分支 ),右侧 driver/monitor/env 等类都是 uvm_component 的 “子孙”,共同构建层次化验证平台,并用 phase 机制控制执行顺序,让验证流程有序运行 。

3.5 组件实例化与层次结构 

3.5.1 uvm_component 构造函数声明

function new (string name, uvm_component parent=null);
  • string name:当前组件的名称(如 "my_driver" ),用于标识层次结构(方便 uvm_info 打印、调试 )。
  • uvm_component parent:父组件的句柄,null 表示无父组件(顶层组件用 null ,子组件需指定父组件 )。

3.5.2 子组件实例化示例

class my_env extends uvm_env;my_driver my_driver;  // 声明子组件function new(string name, uvm_component parent=null);super.new(name, parent);  my_driver = new("my_driver", this);  // 实例化子组件,指定父组件为 `this`(当前 `my_env` )endfunction
endclass
  • my_driver = new("my_driver", this);
    • 第一个参数 "my_driver" 是子组件的名称;
    • 第二个参数 this 表示 当前类(my_env)作为父组件,让子组件归属到 my_env 的层次下。

3.5.3 this 关键字的含义

  • this 在类的方法(如 new 函数)中,代表 当前类的实例(当前 my_env 对象 )。
  • 在实例化子组件时,this 传给 parent 参数,作用是 “把当前类作为父组件”,让子组件(my_driver )成为当前类(my_env )的子节点,构建 UVM 的 层次化组件树(父→子关系 )。

3.5.4 UVM 层次化结构的意义

  • 层次化管理:通过 parent 串联组件(如 my_env → my_driver ),形成验证平台的树形结构(类似文件夹的层级 ),方便管理复杂验证环境。
  • 资源共享:父组件可以传递配置(uvm_config_db )、时钟 / 复位等资源给子组件。
  • 调试与日志:每个组件的 name + 层次路径(如 my_env.my_driver ),让 uvm_info 打印的信息能清晰定位来源,方便调试。

3.5.5 实际验证平台中的作用

  • 顶层组件(如 uvm_test ):实例化时 parent 传 null(无父组件 ),是层次树的根。
  • 中间组件(如 uvm_env ):实例化子组件(driver/monitor )时,用 this 作为父组件,把子组件挂在自己的层次下。
  • 底层组件(如 uvm_driver ):被上层组件实例化,通过 parent 融入整个层次树。

4 UVM 通信组件 TLM

这是 UVM 中 TLM(Transaction Level Modeling,事务级建模)端口的分类与连接规则 讲解,核心是教你区分 port/export/imp 三类 TLM 端口,以及它们如何协作实现组件间的事务通信,拆解如下:

4.1 三类 TLM 端口的角色与功能

(1)port / analysis_port(发起端接口 )

  • 角色:作为 “发起端”(如 uvm_sequence/uvm_driver )的出口,主动发起事务通信(如发送 sequence_item )。
  • 核心区别
    • port:一对一通信(一个 port 连一个 imp )。
    • analysis_port:一对多通信(一个 port 可以连多个 imp ,比如 monitor 同时把数据发给 scoreboard 和 coverage )。

(2)export / analysis_export(中间层接口 )

  • 角色:作为 “中间过渡层”(如 uvm_agent/uvm_env 内部 ),不发起通信,只 转发事务,让端口连接能跨越多层组件。
  • 核心区别
    • export:一对一转发(类似 port 的中间版 )。
    • analysis_export:一对多转发(类似 analysis_port 的中间版 )。

(3)imp(目标端末端 )

  • 角色:作为 “目标端”(如 uvm_scoreboard/uvm_sequencer )的入口,是事务通信的 终点,必须实现实际的事务处理逻辑(如 put/get 方法 )。

4.2 端口连接规则(优先级与层级)

  • 优先级port > export > impport 优先级最高,imp 最低 )。
  • 连接规则
    • 高优先级端口可以连接低优先级端口(port 能连 export 或 imp ;export 能连 export 或 imp )。
    • 低优先级端口不能连高优先级端口(export 不能连 port )。
    • 最终连接必须终止于 imp(事务的终点必须是 imp,否则通信无法完成 )。

4.3 典型连接示例(结合上图)

  • 发起端(Initiator):用 port 发起事务 →
  • 中间层(中间的 export:用 export 转发事务 →
  • 目标端(Target):用 imp 接收并处理事务。
  • 作用:当发起端和目标端之间有多层组件(如 driver 在 agent 里,agent 在 env 里 ),export 可以让事务 “穿透” 中间层,最终到达 imp 处理,保证层次化组件间的通信。

4.4 imp 的实现细节

  • 声明方式

    systemverilog

    `uvm_blocking_put_imp#(T, IMP)
    
     
    • T:要传输的事务类型(如 uvm_sequence_item )。
    • IMP:实现该接口的组件(如 scoreboard 类名 )。
  • 功能imp 所在的组件必须实现 put/get 等方法(如 put 函数实际处理事务数据 ),TLM 只是通道,真正的逻辑由 imp 所在组件实现

4.5 实际应用价值

  • 层次化通信:让复杂验证环境(多层组件嵌套)中的事务能顺利传递(如 sequence 在 env 外层,通过 port+export 穿透到内层 driver 的 imp )。
  • 解耦与复用port/export/imp 解耦了发起端和目标端的实现,中间层组件无需关心事务细节,只需转发,提升验证平台的可复用性。

4.7 端口的方法

4.7.1 方法

(1)put 方法

  • 功能:发起端(如 driver )主动发送数据 给目标端(如 sequencer/scoreboard )。
  • 数据流向:发起端 → 目标端(单向,只发不收 )。
  • 典型场景driver 把 sequence_item 发给 sequencer,或 monitor 把采集的 item 发给 scoreboard
  • 声明示例

    systemverilog

    task put(T t);  // T 是事务类型(如 uvm_sequence_item)// 实现数据发送逻辑(目标端的 imp 要实现这个方法)
    endtask

(2)get/peek 方法

  • 功能:发起端(如 sequence )主动从目标端索取数据
  • 数据流向:目标端 → 发起端(单向,只收不发 )。
  • 区别
    • get:目标端把数据 移除 并返回给发起端(目标端数据会被 “取走” )。
    • peek:目标端把数据 复制 并返回给发起端(目标端数据保留,相当于 “偷看” )。
  • 典型场景sequence 从 sequencer 索取 sequence_item,或 scoreboard 从 monitor 复制数据做比对。

  声明示例

  systemverilog

task get(output T t);  // T 是事务类型// 实现数据索取逻辑(目标端的 imp 要实现)
endtasktask peek(output T t);// 类似 get,但数据不移除,只复制
endtask

(3)transport 方法

  • 功能:发起端 先发送数据,再等待响应(相当于 put + get 的组合 )。
  • 数据流向:发起端 → 目标端(发数据),然后 目标端 → 发起端(回响应),是 双向通信
  • 典型场景:需要 “请求 - 响应” 交互的场景(如 sequence 发请求给 driverdriver 回响应给 sequence )。
  • 声明示例

    systemverilog

    task transport(REQ request, output RSP response);  // REQ:请求类型,RSP:响应类型// 先发送 request,再接收 response
    endtask
    

(4)write 方法

  • 功能:针对 analysis_port(一对多广播 ),发起端 发送数据给多个目标端(如 monitor 同时发给 scoreboard 和 coverage )。
  • 数据流向:发起端 → 多个目标端(一对多,单向广播 )。
  •  特殊要求
    • 目标端的 imp 必须实现 write 函数,当 analysis_port 广播时,会 自动调用 所有连接的 imp 的 write 方法。
  • 典型场景monitor 用 analysis_port 把 item 同时发给 scoreboard(做比对 )和 coverage(做覆盖 )。
  • 声明示例

    systemverilog

    // 发起端用 analysis_port 广播
    uvm_analysis_port #(T) ap;  // 目标端的 imp 必须实现 write 函数
    function void write(T t);  // 处理广播过来的数据
    endfunction
    

4.7.2 方法的典型应用场景

  • put:简单的 “发数据” 场景(如 driver 发 item 给 sequencer )。
  • get/peek:“索取数据” 场景(如 sequence 从 sequencer 拿 item )。
  • transport:“请求 - 响应” 场景(如协议层的握手、命令 - 回包 )。
  • write:“一对多广播” 场景(如 monitor 同时通知 scoreboard 和 coverage )。

4.7.3 核心区别总结

方法数据流向典型场景特殊要求
put发起端 → 目标端单向发送(如 driver 发数)目标端 imp 实现 put 方法
get目标端 → 发起端单向索取(如 sequence 取数)目标端 imp 实现 get 方法
peek目标端 → 发起端复制索取(偷看数据)目标端 imp 实现 peek 方法
transport发起→目标→发起请求 - 响应(如命令 - 回包)需同时处理请求和响应
write发起端 → 多目标一对多广播(如 monitor 广播)目标端 imp 实现 write 方法
  • put 是 “发数据”,get/peek 是 “拿数据”,transport 是 “发 + 拿(请求 - 响应)”,write 是 “一对多广播”。
  • 不同方法对应不同的数据流向和场景,目标端的 imp 必须实现对应方法的逻辑,才能完成事务通信,是 UVM 组件间协作的核心手段 。

4.8 常用通信方式

analysis 端口和 TLM FIFO

import uvm_pkg::*;  // 导入UVM库,包含所有UVM核心类和方法
`include "uvm_macros.svh"  // 导入UVM宏定义(如`uvm_component_utils、`uvm_info等)// 1. 事务类:定义TLM通信中传输的数据结构
//    作用:封装需要传递的信息(地址、数据等),是TLM通信的基本单元
class tlm_item extends uvm_sequence_item;rand bit [31:0] addr;  // 32位地址字段(rand表示可随机化)rand bit [31:0] data;  // 32位数据字段// 注册到UVM工厂:使该类可通过工厂模式动态创建实例`uvm_object_utils(tlm_item)// 构造函数:初始化事务对象function new(string name = "tlm_item");super.new(name);  // 调用父类构造函数endfunction// 打印事务内容:UVM标准打印方法,用于调试时输出事务信息function void do_print(uvm_printer printer);super.do_print(printer);// 以十六进制格式打印addr和data字段printer.print_field("addr", addr, 32, UVM_HEX);printer.print_field("data", data, 32, UVM_HEX);endfunction
endclass// 2. 声明分析端口实现的后缀:
//    用于生成特定名称的分析端口实现类(uvm_analysis_imp_consumer)
//    _consumer为自定义后缀,需与后续实现方法名(write_consumer)匹配
`uvm_analysis_imp_decl(_consumer)// 3. 生产者类:负责生成事务并通过TLM端口发送
class tlm_producer extends uvm_component;// 声明两个分析端口(uvm_analysis_port):// 分析端口支持一对多通信,可连接多个接收端uvm_analysis_port#(tlm_item) ap_a;  // 端口a:用于直接发送事务uvm_analysis_port#(tlm_item) ap_b;  // 端口b:用于通过FIFO发送事务// 事务对象:存储要发送的数据tlm_item item_a;tlm_item item_b;// 注册到UVM工厂:使组件可被UVM层次结构管理`uvm_component_utils(tlm_producer)// 构造函数:初始化组件和端口function new(string name = "tlm_producer", uvm_component parent = null);super.new(name, parent);  // 调用父类构造函数,指定组件名称和父组件ap_a = new("ap_a", this);  // 初始化端口ap_a,父组件为当前producerap_b = new("ap_b", this);  // 初始化端口ap_b,父组件为当前produceritem_a = new();  // 创建事务对象item_aitem_b = new();  // 创建事务对象item_bendfunction : new// 主阶段任务:UVM标准phase,验证的主要执行阶段task main_phase(uvm_phase phase);super.main_phase(phase);  // 调用父类main_phase// 提起异议:告诉UVM仿真器不要提前结束(默认无异议时会立即退出)phase.raise_objection(this);// 给事务对象赋值(实际验证中通常使用randomize()随机生成)item_a.addr = 32'h1234;  // 地址赋值为0x1234item_a.data = 32'h4321;  // 数据赋值为0x4321item_b.addr = 32'h5678;  // 地址赋值为0x5678item_b.data = 32'h8765;  // 数据赋值为0x8765// 通过分析端口发送事务:调用write()方法`uvm_info(get_name(), "Sending item_a via ap_a", UVM_LOW)ap_a.write(item_a);  // 通过ap_a发送item_a`uvm_info(get_name(), "Sending item_b via ap_b", UVM_LOW)ap_b.write(item_b);  // 通过ap_b发送item_b#100;  // 延迟100ns:等待消费者处理事务// 放下异议:允许UVM仿真器结束仿真phase.drop_objection(this);endtask : main_phase
endclass : tlm_producer// 4. 消费者类:负责接收并处理生产者发送的事务
class tlm_consumer extends uvm_component;tlm_item item;  // 用于存储接收的事务数据// 声明端口:// 1. 分析端口实现(uvm_analysis_imp_consumer):接收ap_a发送的事务//    模板参数:<事务类型,实现类>uvm_analysis_imp_consumer#(tlm_item, tlm_consumer) port_a;// 2. 阻塞型get端口(uvm_blocking_get_port):从FIFO获取事务//    调用get()方法时会阻塞,直到有数据可用uvm_blocking_get_port#(tlm_item) port_b;// 注册到UVM工厂`uvm_component_utils(tlm_consumer)// 构造函数:初始化组件和端口function new(string name = "tlm_consumer", uvm_component parent = null);super.new(name, parent);  // 调用父类构造函数port_a = new("port_a", this);  // 初始化port_a,父组件为当前consumerport_b = new("port_b", this);  // 初始化port_b,父组件为当前consumerendfunction : new// 主阶段任务:通过port_b获取并处理事务task main_phase(uvm_phase phase);super.main_phase(phase);  // 调用父类main_phase// 从port_b获取事务:阻塞等待,直到FIFO中有数据port_b.get(item);// 打印接收的事务信息:使用UVM_INFO宏,包含组件名和数据`uvm_info(get_name(), $sformatf("Received from port_b: addr=0x%0h, data=0x%0h",item.addr, item.data), UVM_LOW);endtask : main_phase// 分析端口回调函数:当ap_a发送数据时,UVM会自动调用此方法// 方法名必须为"write_<后缀>",与`uvm_analysis_imp_decl(_consumer)的后缀匹配function void write_consumer(tlm_item item);// 打印接收的事务信息`uvm_info(get_name(), $sformatf("Received from port_a: addr=0x%0h, data=0x%0h",item.addr, item.data), UVM_LOW);endfunction : write_consumer
endclass : tlm_consumer// 5. 环境类:UVM验证环境的核心容器,负责实例化组件并连接端口
class tlm_env extends uvm_env;// 声明组件:生产者、消费者和FIFOtlm_producer tlm_pdr;  // 生产者实例tlm_consumer tlm_csr;  // 消费者实例// TLM分析FIFO:用于缓冲事务,解决生产者和消费者速度不匹配问题// 模板参数:<事务类型>uvm_tlm_analysis_fifo #(tlm_item) producer_to_consumer_fifo;// 注册到UVM工厂`uvm_component_utils(tlm_env)// 构造函数:初始化环境function new(string name, uvm_component parent = null);super.new(name, parent);  // 调用父类构造函数endfunction : new// 构建阶段(build_phase):UVM标准phase,用于实例化组件// 所有组件应在build_phase中创建,而非构造函数function void build_phase(uvm_phase phase);super.build_phase(phase);  // 调用父类build_phase// 通过UVM工厂创建组件实例:type_id::create(实例名, 父组件)tlm_pdr = tlm_producer::type_id::create("tlm_pdr", this);tlm_csr = tlm_consumer::type_id::create("tlm_csr", this);// 创建FIFO实例,父组件为当前envproducer_to_consumer_fifo = new("producer_to_consumer_fifo", this);endfunction : build_phase// 连接阶段(connect_phase):UVM标准phase,用于连接组件间的端口// 端口连接应在connect_phase中完成,确保所有组件已实例化function void connect_phase(uvm_phase phase);super.connect_phase(phase);  // 调用父类connect_phase// 连接1:生产者ap_a → 消费者port_a(直接通信,无缓冲)tlm_pdr.ap_a.connect(tlm_csr.port_a);// 连接2:生产者ap_b → FIFO → 消费者port_b(带缓冲的通信)// 步骤1:ap_b连接到FIFO的analysis_export(FIFO接收端)tlm_pdr.ap_b.connect(producer_to_consumer_fifo.analysis_export);// 步骤2:消费者port_b连接到FIFO的blocking_get_export(FIFO发送端)tlm_csr.port_b.connect(producer_to_consumer_fifo.blocking_get_export);endfunction : connect_phase// 主阶段任务:环境级的控制逻辑(当前为空,可扩展)task main_phase(uvm_phase phase);super.main_phase(phase);  // 调用父类main_phaseendtask : main_phase
endclass : tlm_env// 6. 测试类:UVM验证的入口点,用于启动验证环境
class tlm_test extends uvm_test;tlm_env env;  // 环境实例// 注册到UVM工厂`uvm_component_utils(tlm_test)// 构造函数function new(string name = "tlm_test", uvm_component parent = null);super.new(name, parent);  // 调用父类构造函数endfunction : new// 构建阶段:创建环境实例function void build_phase(uvm_phase phase);super.build_phase(phase);  // 调用父类build_phaseenv = tlm_env::type_id::create("env", this);  // 创建环境实例endfunction : build_phase
endclass : tlm_test// 7. 顶层测试平台模块:仿真的起点
module tb;initial begin// 启动UVM测试:通过字符串指定要运行的测试类(tlm_test)// UVM会自动创建测试实例并按phase顺序执行run_test("tlm_test");end
endmodule : tb

(1)完整的层次结构

  • 顶层模块 tb → 测试类 tlm_test → 环境类 tlm_env → 组件(tlm_producer 和 tlm_consumer
  • 遵循 UVM 标准的层次化结构,便于管理和扩展

(2)两条独立的通信链路

  • 链路 1(无缓冲直接通信)
    tlm_producer.ap_a → tlm_consumer.port_a
    生产者通过分析端口直接发送数据到消费者,消费者通过 write_consumer 方法接收

  • 链路 2(FIFO 缓冲通信)
    tlm_producer.ap_b → producer_to_consumer_fifo → tlm_consumer.port_b
    数据先存入 FIFO,消费者通过阻塞型get方法从 FIFO 中取数据,解决速度匹配问题

(3)UVM 标准实践

  • 所有组件都添加了uvm_component_utils宏注册
  • 组件实例化放在build_phase,端口连接放在connect_phase,符合 UVM 相位机制
  • 使用raise_objectiondrop_objection控制仿真周期

(4)事务流程

  • 生产者在main_phase中生成两个事务(item_aitem_b
  • 分别通过两条链路发送
  • 消费者分别通过port_aport_b接收并打印数据

(5)预期输出

运行仿真时,会产生类似以下的输出,展示两条通信链路的数据传输:

UVM_INFO tlm_test.sv(56) @ 0: uvm_test_top.env.tlm_pdr [tlm_pdr] Sending item_a via ap_a
UVM_INFO tlm_test.sv(98) @ 0: uvm_test_top.env.tlm_csr [tlm_csr] Received from port_a: addr=0x1234, data=0x4321
UVM_INFO tlm_test.sv(58) @ 0: uvm_test_top.env.tlm_pdr [tlm_pdr] Sending item_b via ap_b
UVM_INFO tlm_test.sv(91) @ 0: uvm_test_top.env.tlm_csr [tlm_csr] Received from port_b: addr=0x5678, data=0x8765

详细流程(点击)

http://www.dtcms.com/a/326674.html

相关文章:

  • 从0-1搭建webpack的前端工程化项目
  • MySQL杂项
  • OpenBMC中phosphor-dbus-interfaces深度解析:架构、原理与应用实践
  • 安装AI高性能推理框架llama.cpp
  • Untiy_SpriteShape
  • VSCode编辑器常用24款基础插件
  • QT QVersionNumber 比较版本号大小
  • 自主泊车算法
  • OFD一键转PDF格式,支持批量转换!
  • 客户端连接redis,redis如何配置
  • 钓鱼鱼饵制作的方式(红队)
  • 定义短的魔术数字时小心负数的整型提升
  • AIStarter修复macOS 15兼容问题:跨平台AI项目管理新体验
  • 【51单片机数码管循环显示3位数字】2022-10-26
  • Spring Boot文件上传功能实现详解
  • day25-IO
  • gateway进行接口日志打印
  • 刘强东的AI棋局
  • 高并发内存池 内存释放回收(6)
  • 如何生成测试报告
  • 加载模型使用torch_dtype参数来显式指定数据类型
  • 美股期权历史波动率数据研究分析教程
  • 基于STM32单片机超声波测速测距防撞报警设计
  • c# 线程的基础教学(winform 电梯模拟)
  • C9800 ISSU升级
  • 【Java工具】Java-sftp线程池上传
  • ADK[5]调用外部工具流程
  • (附源码)基于Spring Boot的4S店信息管理系统 的设计与实现
  • 每日算法刷题Day61:8.11:leetcode 堆11道题,用时2h30min
  • 【功能测试】软件集成测试思路策略与经验总结