UVM TLM 非阻塞Get端口:"主动询问取货"式通信
你好!今天我们要学习UVM中非阻塞Get通信。这是一种"主动上门取货"的通信方式,接收方主动去要数据,而不是被动等待数据送上门。
🎯 一句话理解非阻塞Get
非阻塞Get就像去商店买东西:
- 阻塞Get:排队等店员服务,一直等到轮到你(可能等很久)
- 非阻塞Get:进店先问"有货吗?",有就买,没有就离开或等会儿再来
⚡ 为什么需要非阻塞Get通信?
场景对比:客户取货
想象两种取货方式:
- 阻塞方式(普通Get):客户到仓库等,直到货物准备好
- 非阻塞方式(非阻塞Get):客户打电话问"货好了吗?",没好就挂断,等会儿再打
非阻塞Get的优势:
- 主动控制:接收方决定何时取数据
- 避免死等:不会无限期阻塞等待
- 灵活调度:可以在多个数据源间轮询
🔌 Get vs Put 本质区别图解
先通过一个流程图理解Get和Put的主动被动关系:
📦 核心概念:三个关键方法
非阻塞Get提供了三种与发送方交互的方式:
| 方法 | 类型 | 作用 | 类比 |
|---|---|---|---|
| try_get() | 函数 | 尝试获取数据,立即返回成功/失败 | 问店员"能给我货吗?" |
| can_get() | 函数 | 仅查询是否有数据,不获取 | 问店员"有货吗?" |
| get() | 任务 | 阻塞获取,等待数据准备好 | 排队等到有货 |
🔍 完整代码深度解析
第一步:定义数据包类
class Packet extends uvm_object;rand bit[7:0]addr;// 地址字段rand bit[7:0]data;// 数据字段`uvm_object_utils_begin(Packet)`uvm_field_int(addr,UVM_ALL_ON)`uvm_field_int(data,UVM_ALL_ON)`uvm_object_utils_end functionnew(string name="Packet");super.new(name);endfunction endclass注意:Get通信是接收方主动,所以发送方需要准备好数据等待被取走。
第二步:发送方实现(componentA - 数据提供者)
角色反转:发送方变成"被请求方"
在Get模式中,componentA不再是主动发送者,而是数据提供者,等待被请求。
class componentA extends uvm_component;`uvm_component_utils(componentA)// 1. 声明非阻塞Get实现端口(不是Port!)uvm_nonblocking_get_imp #(Packet,componentA)m_get_imp;functionnew(string name="componentA",uvm_component parent=null);super.new(name,parent);endfunction virtual functionvoidbuild_phase(uvm_phase phase);super.build_phase(phase);// 2. 创建实现端口(等待被连接)m_get_imp=new("m_get_imp",this);endfunction// 3. 实现try_get:当接收方请求数据时调用virtual function bittry_get(output Packet pkt);// 创建新数据包pkt=new();assert(pkt.randomize());`uvm_info("COMPA","componentB请求数据包",UVM_LOW)pkt.print();return1;// 总是成功提供数据endfunction// 4. 实现can_get:查询是否可提供数据virtual function bitcan_get();// 总是可以提供服务return1;endfunction endclass关键点:
- 发送方实现
try_get()函数(不是任务!) - 参数
output Packet pkt是输出参数,填充数据后返回 - 返回1表示成功提供数据,0表示暂时无法提供
第三步:接收方实现(componentB - 数据请求者)
接收方主动请求数据,这是Get模式的核心。
版本1:基础try_get(示例1)
class componentB extends uvm_component;`uvm_component_utils(componentB)// 1. 声明非阻塞Get端口(主动请求方)uvm_nonblocking_get_port #(Packet)m_get_port;intm_num_tx=2;// 请求次数functionnew(string name="componentB",uvm_component parent=null);super.new(name,parent);endfunction virtual functionvoidbuild_phase(uvm_phase phase);super.build_phase(phase);// 2. 创建Get端口m_get_port=new("m_get_port",this);endfunction virtual taskrun_phase(uvm_phase phase);phase.raise_objection(this);repeat(m_num_tx)begin Packet pkt;bit success;// 3. 关键:尝试获取数据(非阻塞)success=m_get_port.try_get(pkt);if(success)begin `uvm_info("COMPB","成功获取数据包",UVM_LOW)pkt.print();endelsebegin `uvm_info("COMPB","获取数据失败",UVM_LOW)end end phase.drop_objection(this);endtask endclass关键点:
try_get()立即返回,不阻塞- 输出参数
pkt在成功时被填充 - 接收方控制请求时机
版本2:循环try_get模拟阻塞(可选实现)
virtual taskrun_phase(uvm_phase phase);phase.raise_objection(this);repeat(m_num_tx)begin Packet pkt;bit success;// 循环尝试,直到成功获取dobegin success=m_get_port.try_get(pkt);if(!success)begin `uvm_info("COMPB","componentA暂无数据,10ns后重试",UVM_LOW)#10;// 等待后重试end endwhile(!success);`uvm_info("COMPB","成功获取数据包",UVM_LOW)pkt.print();end phase.drop_objection(this);endtask版本3:使用can_get查询(示例2)
virtual taskrun_phase(uvm_phase phase);phase.raise_objection(this);repeat(m_num_tx)begin Packet pkt;// 先查询发送方是否有数据`uvm_info("COMPB","查询componentA是否有数据...",UVM_LOW)while(!m_get_port.can_get())begin #10;// 等待10ns后再次查询`uvm_info("COMPB","再次查询...",UVM_LOW)end `uvm_info("COMPB","componentA已就绪,开始获取数据",UVM_LOW)// 确认有数据后获取(这时应该100%成功)m_get_port.try_get(pkt);pkt.print();end phase.drop_objection(this);endtaskcan_get的优势:
- 纯粹的查询,不改变状态
- 可以优化重试策略
- 避免频繁调用try_get的开销
第四步:发送方的高级实现(模拟真实场景)
版本2:随机就绪的发送方
class componentA extends uvm_component;// ... 其他代码不变// try_get实现:随机决定是否提供数据virtual function bittry_get(output Packet pkt);bit ready;std::randomize(ready);// 随机生成0或1if(ready)begin pkt=new();assert(pkt.randomize());`uvm_info("COMPA","提供数据包",UVM_LOW)pkt.print();return1;endelsebegin `uvm_info("COMPA","暂时无法提供数据",UVM_LOW)return0;end endfunction// can_get实现:随机返回就绪状态virtual function bitcan_get();bit ready;std::randomize(ready)with{ready dist{0:/70,1:/30};};// 70%概率返回0(未就绪),30%概率返回1(就绪)returnready;endfunction endclass第五步:环境连接
class my_test extends uvm_test;`uvm_component_utils(my_test)componentA compA;// 数据提供者componentB compB;// 数据请求者functionnew(string name="my_test",uvm_component parent=null);super.new(name,parent);endfunction virtual functionvoidbuild_phase(uvm_phase phase);super.build_phase(phase);compA=componentA::type_id::create("compA",this);compB=componentB::type_id::create("compB",this);endfunction virtual functionvoidconnect_phase(uvm_phase phase);// 注意连接方向:Get端口连接到Get实现compB.m_get_port.connect(compA.m_get_imp);// 意思是:compB的请求端口连接到compA的实现端口endfunction endclass重要!Get模式连接方向:
- 接收方(请求者)的Port→ 发送方(提供者)的Imp
- 与Put模式相反!
📊 两种模式输出对比分析
模式1:基础try_get(总是成功)
@0: [COMPA] componentB请求数据包 ← 被调用 @0: [COMPB] 成功获取数据包 ← 立即返回成功特点:请求立即得到满足,类似阻塞get但没有等待。
模式2:can_get查询(随机就绪)
@0: [COMPB] 查询componentA是否有数据... @10: [COMPB] 再次查询... ← 等待10ns @20: [COMPB] 再次查询... ← 等待10ns @30: [COMPB] componentA已就绪,开始获取数据 @30: [COMPA] 提供数据包 ← 最终成功特点:模拟真实场景,发送方可能暂时无法提供数据。
🎯 Get vs Put 对比总结
| 特性 | Get模式 | Put模式 |
|---|---|---|
| 主动方 | 接收方(消费者) | 发送方(生产者) |
| 控制权 | 接收方决定何时取数据 | 发送方决定何时发数据 |
| 接口 | get(),try_get(),can_get() | put(),try_put(),can_put() |
| 数据流向 | 接收方 → 发送方(请求数据) | 发送方 → 接收方(发送数据) |
| 典型应用 | 处理器读取存储器 | 驱动器发送事务 |
| 实现方法 | 发送方实现try_get() | 接收方实现try_put() |
🔧 实际应用场景
场景1:处理器读取指令缓存
class instruction_cache extends uvm_component;uvm_nonblocking_get_imp #(instruction,instruction_cache)get_imp;instruction cache[1024];virtual function bittry_get(output instruction instr);if(pc_valid&&cache_hit)begin instr=cache[pc];return1;endelsebeginreturn0;// 缓存未命中end endfunction endclass class cpu_fetch_unit extends uvm_component;uvm_nonblocking_get_port #(instruction)get_port;virtual taskrun_phase(uvm_phase phase);forever begin instruction instr;// 尝试从缓存获取指令if(get_port.try_get(instr))beginprocess_instruction(instr);pc=pc+4;endelsebegin// 缓存未命中,处理异常或等待handle_cache_miss();#10;// 等待缓存填充end end endtask endclass场景2:DMA控制器读取数据
class dma_controller extends uvm_component;uvm_nonblocking_get_port #(data_chunk)get_port;virtual tasktransfer_data(intstart_addr,intsize);for(inti=0;i<size;i+=4)begin data_chunk chunk;// 尝试获取数据块while(!get_port.try_get(chunk))begin// 内存忙碌,等待并重试#5;end// 处理获取的数据process_data_chunk(chunk);end endtask endclass场景3:仲裁器从多个源获取数据
class round_robin_arbiter extends uvm_component;uvm_nonblocking_get_port #(packet)port_a,port_b,port_c;virtual taskrun_phase(uvm_phase phase);forever begin packet pkt;// 轮询各个端口if(port_a.try_get(pkt))beginprocess_from_a(pkt);endelseif(port_b.try_get(pkt))beginprocess_from_b(pkt);endelseif(port_c.try_get(pkt))beginprocess_from_c(pkt);endelsebegin// 所有源都无数据,等待#10;end end endtask endclass⚠️ 注意事项和最佳实践
1. 函数 vs 任务
// ❌ 错误:非阻塞接口实现任务virtual tasktry_get(output Packet pkt);// 应该是function!// ✅ 正确:非阻塞接口实现函数virtual function bittry_get(output Packet pkt);2. 输出参数处理
// 必须分配新的对象virtual function bittry_get(output Packet pkt);// ❌ 错误:不分配对象// pkt.addr = 8'hFF; // 空指针错误!// ✅ 正确:创建新对象pkt=new();pkt.randomize();return1;endfunction3. 连接方向
// ❌ 错误:Get模式连接反了compA.m_get_port.connect(compB.m_get_imp);// Put模式才这样// ✅ 正确:Get模式连接compB.m_get_port.connect(compA.m_get_imp);// 请求者→提供者4. 竞态条件处理
// 多个接收方竞争同一发送方时class shared_data_source extends uvm_component;uvm_nonblocking_get_imp #(Packet,shared_data_source)get_imp;semaphore lock=new(1);// 互斥锁virtual function bittry_get(output Packet pkt);if(lock.try_get(1))begin// 获取锁成功pkt=create_packet();lock.put(1);return1;endelsebegin// 获取锁失败(其他线程正在使用)return0;end endfunction endclass🔄 阻塞 vs 非阻塞Get对比
| 特性 | 阻塞Get | 非阻塞Get |
|---|---|---|
| 接口类型 | uvm_blocking_get_port | uvm_nonblocking_get_port |
| 实现类型 | uvm_blocking_get_imp | uvm_nonblocking_get_imp |
| 方法类型 | 任务(task) | 函数(function) |
| 阻塞性 | 接收方被阻塞 | 接收方立即返回 |
| 主要方法 | get() | try_get(),can_get() |
| 返回值 | 直接返回数据 | 1(成功)/0(失败) |
| 适用场景 | 简单同步 | 复杂异步、性能敏感 |
| 典型应用 | 顺序数据读取 | 总线访问、多线程读取 |
🚀 实战练习建议
练习1:基础非阻塞Get
- 实现基础非阻塞Get(示例1)
- 观察立即返回的特性
- 对比阻塞Get的时间消耗
练习2:模拟真实场景
- 让发送方随机忙碌(示例2)
- 实现接收方的轮询策略
- 添加超时机制
练习3:多源获取
- 实现一个接收方从两个发送方获取数据
- 使用轮询或优先级策略
- 处理数据源切换
练习4:性能优化
// 比较不同策略的性能virtual taskperformance_test();realtime start_time;intiterations=1000;// 策略1:频繁try_getstart_time=$realtime;for(inti=0;i<iterations;i++)begin Packet pkt;while(!get_port.try_get(pkt))#1;// 忙等待end realtime strategy1_time=$realtime-start_time;// 策略2:使用can_get减少尝试start_time=$realtime;for(inti=0;i<iterations;i++)begin Packet pkt;while(!get_port.can_get())#10;// 等待更长时间get_port.try_get(pkt);end realtime strategy2_time=$realtime-start_time;`uvm_info("PERF",$sformatf("策略1: %0t ns, 策略2: %0t ns",strategy1_time,strategy2_time),UVM_LOW)endtask💡 设计模式推荐
模式1:带超时的获取
class timeout_getter extends uvm_component;uvm_nonblocking_get_port #(Packet)get_port;virtual function bitget_with_timeout(output Packet pkt,inttimeout_ns);realtime start_time=$realtime;while($realtime-start_time<timeout_ns)beginif(get_port.try_get(pkt))return1;// 成功#5;// 等待5ns后重试end `uvm_warning("TIMEOUT","获取数据超时")return0;// 超时失败endfunction endclass模式2:批量获取优化
class batch_getter extends uvm_component;uvm_nonblocking_get_port #(data_item)get_port;virtual taskget_batch(data_item batch[$],intbatch_size);for(inti=0;i<batch_size;i++)begin data_item item;// 尝试获取,失败则等待while(!get_port.try_get(item))begin// 可以在这里做其他工作#10;end batch.push_back(item);end endtask endclass模式3:优先级多源获取
class priority_getter extends uvm_component;uvm_nonblocking_get_port #(packet)high_pri_port,low_pri_port;virtual taskrun_phase(uvm_phase phase);forever begin packet pkt;// 优先尝试高优先级源if(high_pri_port.try_get(pkt))beginprocess_high_priority(pkt);endelseif(low_pri_port.try_get(pkt))beginprocess_low_priority(pkt);endelsebegin// 都无数据,等待#20;end end endtask endclass🎓 总结
非阻塞TLM Get是"主动、灵活、高效"的拉取数据方式:
- 接收方主动:控制何时获取数据
- 立即返回:不会被无限期阻塞
- 状态感知:通过返回值知道获取结果
- 灵活控制:可以重试、等待、放弃或切换数据源
记住核心特点:
Get模式接收方主动,请求数据不阻塞;
try_get函数立即返,成功失败即刻知;
can_get查询状态用,优化性能减开销;
连接方向要注意,请求端口连实现。
掌握了非阻塞Get,你就能构建更加主动、高效的验证平台!现在尝试在你的测试中用Get模式替换一些被动等待的场景,让组件更加智能吧!