海南省网站建设_网站建设公司_网站制作_seo优化
2025/12/18 14:28:05 网站建设 项目流程

UVM TLM 非阻塞Put端口:"敲门询问"式通信

你好!今天我们要学习UVM中非阻塞TLM通信。这是一种"先敲门,再进入"的通信方式,发送方不会傻等,而是先询问接收方是否准备好,再决定是否发送数据。

🎯 一句话理解非阻塞Put

非阻塞Put就像拜访朋友的礼貌方式:

  • 阻塞Put:直接去朋友家,敲门后一直等到开门(可能等很久)
  • 非阻塞Put:先打电话问"在家吗?",得到肯定回答再过去

⚡ 为什么需要非阻塞通信?

场景对比:快递员送货

想象两种送货方式:

  • 阻塞方式(普通Put):快递员到你门口,一直敲门直到你开门取件
  • 非阻塞方式(非阻塞Put):快递员先打电话问"现在方便吗?",不方便就晚点再打

非阻塞通信的优势:

  1. 不浪费时间:发送方不会被无限期阻塞
  2. 灵活调度:发送方可以做其他事情
  3. 资源高效:避免无意义的等待

🔌 阻塞 vs 非阻塞对比图解

先通过一个流程图理解两种方式的根本区别:

📦 核心概念:三个关键方法

非阻塞Put提供了三种与接收方交互的方式:

方法类型作用类比
try_put()函数尝试发送,立即返回成功/失败敲门问"能进来吗?"
can_put()函数仅查询是否准备好,不发送打电话问"在家吗?"
put()任务阻塞发送,等待完成直接进门等主人

🔍 完整代码深度解析

第一步:定义数据包类

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

注意:非阻塞通信使用函数(function),所以数据包在发送后不能被修改,因为函数立即返回,可能数据包还在传输中。

第二步:发送方实现(componentA)

版本1:基础try_put(示例1)
class componentA extends uvm_component;`uvm_component_utils(componentA)// 1. 声明非阻塞put端口uvm_nonblocking_put_port #(Packet)m_put_port;intm_num_tx=2;// 发送次数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_put_port=new("m_put_port",this);endfunction virtual taskrun_phase(uvm_phase phase);phase.raise_objection(this);repeat(m_num_tx)begin bit success;Packet pkt=Packet::type_id::create("pkt");assert(pkt.randomize());`uvm_info("COMPA","尝试发送数据包",UVM_LOW)pkt.print();// 3. 关键:尝试发送(非阻塞)success=m_put_port.try_put(pkt);if(success)`uvm_info("COMPA","发送成功",UVM_MEDIUM)else`uvm_info("COMPA","发送失败",UVM_MEDIUM)end phase.drop_objection(this);endtask endclass

关键点

  • try_put()函数,不是任务
  • 立即返回1(成功)或0(失败)
  • 发送方不会被阻塞
版本2:循环try_put模拟阻塞(示例2)
virtual taskrun_phase(uvm_phase phase);phase.raise_objection(this);repeat(m_num_tx)begin bit success;Packet pkt=Packet::type_id::create("pkt");assert(pkt.randomize());`uvm_info("COMPA","尝试发送数据包",UVM_LOW)pkt.print();// 循环尝试,直到成功dobegin success=m_put_port.try_put(pkt);if(success)`uvm_info("COMPA","发送成功",UVM_MEDIUM)elsebegin `uvm_info("COMPA","发送失败,1ns后重试",UVM_MEDIUM)#1;// 等待1ns后重试end endwhile(!success);// 直到成功才退出循环end phase.drop_objection(this);endtask

这种模式实现了"非阻塞API的阻塞行为"

  • 发送方主动等待,但不是被接收方阻塞
  • 可以控制重试间隔(如#1)
  • 更加灵活,可以添加超时机制
版本3:使用can_put查询(示例3)
virtual taskrun_phase(uvm_phase phase);phase.raise_objection(this);repeat(m_num_tx)begin bit ready;Packet pkt=Packet::type_id::create("pkt");assert(pkt.randomize());`uvm_info("COMPA","准备发送数据包",UVM_LOW)pkt.print();// 先查询接收方是否就绪`uvm_info("COMPA","等待接收方就绪...",UVM_MEDIUM)dobegin ready=m_put_port.can_put();// 仅查询,不发送endwhile(!ready);// 等待直到就绪`uvm_info("COMPA","接收方已就绪,开始发送",UVM_MEDIUM)// 确认就绪后发送(这时应该100%成功)m_put_port.try_put(pkt);end phase.drop_objection(this);endtask

can_put的优势

  • 纯粹的查询,不改变状态
  • 可以在发送前做其他准备工作
  • 适用于复杂的发送逻辑

第三步:接收方实现(componentB)

接收方需要实现两个函数:try_put()can_put()

版本1:总是就绪(示例1)
class componentB extends uvm_component;`uvm_component_utils(componentB)// 声明非阻塞put实现端口uvm_nonblocking_put_imp #(Packet,componentB)m_put_imp;functionnew(string name="componentB",uvm_component parent=null);super.new(name,parent);endfunction virtual functionvoidbuild_phase(uvm_phase phase);super.build_phase(phase);m_put_imp=new("m_put_imp",this);endfunction// 实现try_put:接收数据virtual function bittry_put(Packet pkt);`uvm_info("COMPB","收到数据包",UVM_LOW)pkt.print();return1;// 总是成功endfunction// 实现can_put:查询是否就绪virtual function bitcan_put();// 总是就绪return1;endfunction endclass
版本2:模拟随机就绪(示例2)
// try_put实现:随机决定是否接收virtual function bittry_put(Packet pkt);bit ready;std::randomize(ready);// 随机生成0或1if(ready)begin `uvm_info("COMPB","接收数据包",UVM_LOW)pkt.print();return1;// 成功endelsebegin `uvm_info("COMPB","忙碌中,无法接收",UVM_LOW)return0;// 失败end endfunction
版本3:独立的can_put逻辑(示例3)
virtual function bittry_put(Packet pkt);// 收到数据包`uvm_info("COMPB","接收数据包",UVM_LOW)pkt.print();return1;endfunction virtual function bitcan_put();// 随机返回是否就绪(与实际try_put解耦)return$urandom_range(0,1);endfunction

重要区别

  • can_put():只查询状态,不改变状态
  • try_put():尝试改变状态(接收数据)

第四步:环境连接

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);compA.m_num_tx=2;// 配置发送次数endfunction virtual functionvoidconnect_phase(uvm_phase phase);// 连接非阻塞端口compA.m_put_port.connect(compB.m_put_imp);endfunction endclass

📊 三种模式输出对比分析

模式1:基础try_put(总是成功)

@0: [COMPA] 尝试发送数据包 @0: [COMPB] 收到数据包 ← 立即接收 @0: [COMPA] 发送成功 ← 立即返回成功

特点:发送立即完成,类似阻塞put但没有等待。

模式2:循环try_put(随机成功)

@0: [COMPA] 尝试发送数据包 @0: [COMPA] 发送失败,1ns后重试 ← 第一次失败 @1: [COMPB] 收到数据包 ← 1ns后成功 @1: [COMPA] 发送成功 ← 循环结束

特点:模拟真实场景,接收方可能忙碌。

模式3:can_put查询

@0: [COMPA] 准备发送数据包 @0: [COMPA] 等待接收方就绪... ← 开始查询 @0: [COMPA] 接收方已就绪,开始发送 ← can_put返回1 @0: [COMPB] 收到数据包 ← 发送成功

特点:确保发送时接收方100%就绪。

🎯 实际应用场景

场景1:总线仲裁器

class bus_arbiter extends uvm_component;uvm_nonblocking_put_imp #(bus_transaction,bus_arbiter)put_imp;bit busy=0;// 当前是否忙碌virtual function bitcan_put();return!busy;// 不忙碌时返回1endfunction virtual function bittry_put(bus_transaction tr);if(busy)return0;// 忙碌则拒绝busy=1;// 标记为忙碌fork beginprocess_transaction(tr);// 处理事务busy=0;// 处理完成,标记为空闲end join_nonereturn1;// 接收成功endfunction endclass

场景2:带缓冲的接收器

class buffered_receiver extends uvm_component;uvm_nonblocking_put_imp #(packet,buffered_receiver)put_imp;packet buffer[$];intmax_buffer_size=10;virtual function bitcan_put();// 缓冲区未满时才能接收return(buffer.size()<max_buffer_size);endfunction virtual function bittry_put(packet pkt);if(buffer.size()>=max_buffer_size)return0;// 缓冲区满,拒绝buffer.push_back(pkt);// 存入缓冲区return1;endfunction// 后台处理线程virtual taskrun_phase(uvm_phase phase);forever beginwait(buffer.size()>0);process_packet(buffer.pop_front());#10;// 模拟处理时间end endtask endclass

场景3:多优先级发送

class priority_sender extends uvm_component;uvm_nonblocking_put_port #(packet)high_pri_port;uvm_nonblocking_put_port #(packet)low_pri_port;virtual taskrun_phase(uvm_phase phase);forever begin packet pkt=get_next_packet();if(pkt.priority==HIGH)begin// 高优先级:尝试发送,失败则等待while(!high_pri_port.try_put(pkt))#1;endelsebegin// 低优先级:尝试发送,失败则丢弃if(!low_pri_port.try_put(pkt))`uvm_warning("LOW_PRI","低优先级包被丢弃")end end endtask endclass

⚠️ 注意事项和最佳实践

1. 函数 vs 任务

// ❌ 错误:非阻塞接口实现任务 virtual task try_put(packet pkt); // 应该是function! // ✅ 正确:非阻塞接口实现函数 virtual function bit try_put(packet pkt);

2. 返回值处理

// 必须处理返回值bit success=port.try_put(pkt);if(!success)begin// 处理失败情况:重试、记录、丢弃等handle_failure(pkt);end

3. can_put与try_put的竞态条件

// 潜在问题:查询后状态可能改变bit ready=port.can_put();// 返回1(就绪)// 在这期间,其他线程可能占用接收方bit success=port.try_put(pkt);// 可能失败!// 解决方案:循环尝试dobeginif(port.can_put())begin success=port.try_put(pkt);endif(!success)#1;// 等待后重试endwhile(!success);

4. 超时机制

virtual function bittry_put_with_timeout(packet pkt,inttimeout_ns);realtime start_time=$realtime;while($realtime-start_time<timeout_ns)beginif(m_put_port.try_put(pkt))return1;// 成功#1;// 等待1ns后重试end `uvm_warning("TIMEOUT","发送超时")return0;// 超时失败endfunction

🔄 阻塞 vs 非阻塞完整对比

特性阻塞Put非阻塞Put
接口类型uvm_blocking_put_portuvm_nonblocking_put_port
实现类型uvm_blocking_put_impuvm_nonblocking_put_imp
方法类型任务(task)函数(function)
阻塞性发送方被阻塞发送方立即返回
主要方法put()try_put(),can_put()
返回值1(成功)/0(失败)
适用场景简单同步复杂异步、性能敏感
典型应用顺序数据流总线通信、多线程

🚀 实战练习建议

练习1:基础非阻塞通信

  1. 实现基础非阻塞put(示例1)
  2. 观察立即返回的特性
  3. 对比阻塞put的时间消耗

练习2:模拟真实场景

  1. 让接收方随机忙碌(示例2)
  2. 实现发送方的重试机制
  3. 添加重试次数限制

练习3:高级应用

  1. 实现带缓冲的接收器
  2. 添加优先级机制
  3. 实现超时和错误处理

练习4:性能对比

// 测试代码:比较阻塞和非阻塞的性能virtual taskperformance_test();realtime start_time;intiterations=1000;// 测试阻塞putstart_time=$realtime;for(inti=0;i<iterations;i++)blocking_port.put(pkt);// 可能被阻塞realtime blocking_time=$realtime-start_time;// 测试非阻塞put(循环尝试)start_time=$realtime;for(inti=0;i<iterations;i++)beginwhile(!nonblocking_port.try_put(pkt))#1;// 忙等待end realtime nonblocking_time=$realtime-start_time;`uvm_info("PERF",$sformatf("阻塞: %0t ns, 非阻塞: %0t ns",blocking_time,nonblocking_time),UVM_LOW)endtask

💡 设计模式推荐

模式1:生产者-消费者带流控

class producer extends uvm_component;uvm_nonblocking_put_port #(data)put_port;virtual taskrun_phase(uvm_phase phase);forever begin data item=generate_data();// 使用can_put避免忙等待if(put_port.can_put())begin put_port.try_put(item);endelsebegin// 接收方忙碌,做其他工作do_something_else();#10;// 等待一段时间end end endtask endclass

模式2:带超时的发送

class timeout_sender extends uvm_component;uvm_nonblocking_put_port #(packet)put_port;virtual function bitsend_with_timeout(packet pkt,intmax_retries);for(inti=0;i<max_retries;i++)beginif(put_port.try_put(pkt))return1;// 成功#10;// 等待后重试end// 重试次数用尽`uvm_error("SEND_FAIL","发送失败")return0;endfunction endclass

模式3:批量发送优化

class batch_sender extends uvm_component;uvm_nonblocking_put_port #(packet)put_port;virtual tasksend_batch(packet batch[$]);foreach(batch[i])begin// 尝试发送,失败则等待并重试while(!put_port.try_put(batch[i]))begin// 接收方忙碌,可以:// 1. 发送其他数据// 2. 等待固定时间// 3. 调整发送策略if(i<batch.size()-1)begin// 尝试发送下一个包i++;endelsebegin #10;// 等待后重试当前包end end end endtask endclass

🎓 总结

非阻塞TLM Put是"礼貌、高效、灵活"的通信方式:

  1. 立即返回:发送方不会被无限期阻塞
  2. 状态感知:通过返回值知道发送结果
  3. 灵活控制:可以重试、等待、放弃或做其他事情
  4. 性能友好:避免无意义的等待时间

记住核心区别:

阻塞put用任务,发送方会等待;
非阻塞用函数,立即知成败;
try_put尝试发,can_put查状态;
灵活又高效,复杂场景爱。

掌握了非阻塞Put,你就能构建响应更快、资源利用率更高的验证平台!现在尝试在你的测试中用非阻塞通信替换一些阻塞调用,体验性能提升吧!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询