免费跨平台绘图终极方案:draw.io桌面版完整使用指南
2026/6/10 16:02:50
你好!今天我们要学习UVM中非阻塞TLM通信。这是一种"先敲门,再进入"的通信方式,发送方不会傻等,而是先询问接收方是否准备好,再决定是否发送数据。
非阻塞Put就像拜访朋友的礼貌方式:
想象两种送货方式:
非阻塞通信的优势:
先通过一个流程图理解两种方式的根本区别:
非阻塞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),所以数据包在发送后不能被修改,因为函数立即返回,可能数据包还在传输中。
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()是函数,不是任务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的阻塞行为":
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);endtaskcan_put的优势:
接收方需要实现两个函数:try_put()和can_put()
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// 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 endfunctionvirtual 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@0: [COMPA] 尝试发送数据包 @0: [COMPB] 收到数据包 ← 立即接收 @0: [COMPA] 发送成功 ← 立即返回成功特点:发送立即完成,类似阻塞put但没有等待。
@0: [COMPA] 尝试发送数据包 @0: [COMPA] 发送失败,1ns后重试 ← 第一次失败 @1: [COMPB] 收到数据包 ← 1ns后成功 @1: [COMPA] 发送成功 ← 循环结束特点:模拟真实场景,接收方可能忙碌。
@0: [COMPA] 准备发送数据包 @0: [COMPA] 等待接收方就绪... ← 开始查询 @0: [COMPA] 接收方已就绪,开始发送 ← can_put返回1 @0: [COMPB] 收到数据包 ← 发送成功特点:确保发送时接收方100%就绪。
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 endclassclass 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 endclassclass 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// ❌ 错误:非阻塞接口实现任务 virtual task try_put(packet pkt); // 应该是function! // ✅ 正确:非阻塞接口实现函数 virtual function bit try_put(packet pkt);// 必须处理返回值bit success=port.try_put(pkt);if(!success)begin// 处理失败情况:重试、记录、丢弃等handle_failure(pkt);end// 潜在问题:查询后状态可能改变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);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| 特性 | 阻塞Put | 非阻塞Put |
|---|---|---|
| 接口类型 | uvm_blocking_put_port | uvm_nonblocking_put_port |
| 实现类型 | uvm_blocking_put_imp | uvm_nonblocking_put_imp |
| 方法类型 | 任务(task) | 函数(function) |
| 阻塞性 | 发送方被阻塞 | 发送方立即返回 |
| 主要方法 | put() | try_put(),can_put() |
| 返回值 | 无 | 1(成功)/0(失败) |
| 适用场景 | 简单同步 | 复杂异步、性能敏感 |
| 典型应用 | 顺序数据流 | 总线通信、多线程 |
// 测试代码:比较阻塞和非阻塞的性能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)endtaskclass 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 endclassclass 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 endclassclass 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是"礼貌、高效、灵活"的通信方式:
记住核心区别:
阻塞put用任务,发送方会等待;
非阻塞用函数,立即知成败;
try_put尝试发,can_put查状态;
灵活又高效,复杂场景爱。
掌握了非阻塞Put,你就能构建响应更快、资源利用率更高的验证平台!现在尝试在你的测试中用非阻塞通信替换一些阻塞调用,体验性能提升吧!