4. 单向通信:put端口与get端口的实现、阻塞与非阻塞操作、put/get的时序图
在原型板部署场景中,验证组件(Driver、Monitor、Scoreboard)之间最基础的交互模式是单向数据传递。SystemVerilog 通过 put 与 get 端口提供了标准化的单向通信原语,支持阻塞与非阻塞两种操作语义。本章深入分析这两种端口的实现机制、操作差异以及时序行为。
4.1 put端口与get端口的基本概念
put 端口用于生产者向消费者发送数据;get 端口用于消费者从生产者接收数据。两者共同构成一个单向数据通道,数据流方向固定,无反向握手信号。
| 端口类型 | 方向 | 典型使用者 | 数据流 |
|---|---|---|---|
put |
输出 | Driver、Monitor | 生产者 → 通道 |
get |
输入 | Scoreboard、Collector | 通道 → 消费者 |
关键原则:put/get 端口不关心数据如何被消费,只保证数据从一端传递到另一端。这种解耦使得验证组件可以独立开发与复用。
4.2 阻塞操作与非阻塞操作
SystemVerilog 的端口操作分为阻塞(blocking)与非阻塞(non-blocking)两种模式,直接影响仿真调度与组件同步行为。
4.2.1 阻塞操作
- put:调用
put()任务时,若通道已满或消费者未就绪,调用者挂起直到操作完成。 - get:调用
get()任务时,若通道为空,调用者挂起直到数据到达。 - 特点:保证数据传递的原子性,适合需要严格同步的场景。
4.2.2 非阻塞操作
- try_put:尝试放入数据,若通道满则立即返回失败标志,不挂起。
- try_get:尝试取出数据,若通道空则立即返回失败标志,不挂起。
- 特点:避免仿真时间停滞,适合轮询或超时控制场景。
| 操作 | 方法签名 | 阻塞行为 | 返回值 |
|---|---|---|---|
| 阻塞 put | task put(input T t); |
挂起直到可写入 | 无 |
| 非阻塞 try_put | function bit try_put(input T t); |
立即返回 | 成功返回1,失败返回0 |
| 阻塞 get | task get(output T t); |
挂起直到可读取 | 无 |
| 非阻塞 try_get | function bit try_get(output T t); |
立即返回 | 成功返回1,失败返回0 |
4.3 put/get 端口的实现示例
以下代码展示一个基于 mailbox 的 put/get 端口实现,支持阻塞与非阻塞操作。
// 单向 put/get 端口实现
class put_get_port #(type T = int);
protected mailbox #(T) mbox;
function new();
mbox = new();
endfunction
// 阻塞 put
task put(T item);
mbox.put(item);
endtask
// 非阻塞 try_put
function bit try_put(T item);
return mbox.try_put(item);
endfunction
// 阻塞 get
task get(output T item);
mbox.get(item);
endtask
// 非阻塞 try_get
function bit try_get(output T item);
return mbox.try_get(item);
endfunction
endclass
// 使用示例:Driver 生产数据,Scoreboard 消费数据
class driver;
put_get_port #(my_transaction) put_port;
task run();
my_transaction tr;
repeat(10) begin
tr = new();
// 阻塞 put,等待 Scoreboard 就绪
put_port.put(tr);
#10;
end
endtask
endclass
class scoreboard;
put_get_port #(my_transaction) get_port;
task run();
my_transaction tr;
forever begin
// 非阻塞 try_get,轮询检查
if (get_port.try_get(tr))
$display("Received transaction at %0t", $time);
else
#5; // 等待后重试
end
endtask
endclass
实现要点:
- 使用
mailbox作为底层存储,天然支持阻塞/非阻塞语义。 - 阻塞操作适合数据流稳定的场景,非阻塞操作适合需要超时或并行处理的多任务环境。
- 端口类型参数化(
#(type T = int))提高复用性。
4.4 put/get 时序图
下图展示阻塞 put 与阻塞 get 在仿真时间轴上的交互时序。生产者与消费者通过共享通道同步,数据传递发生在双方都就绪的时刻。
4.5 非阻塞操作时序图
非阻塞操作不产生仿真时间挂起,调用者立即获得结果。下图展示 try_put 与 try_get 在通道满/空时的行为差异。
4.6 阻塞 vs 非阻塞:选择指南
| 场景 | 推荐操作 | 理由 |
|---|---|---|
| 数据流稳定,生产者快于消费者 | 阻塞 put / 阻塞 get | 自然背压,避免数据溢出 |
| 消费者需要轮询多个数据源 | 非阻塞 try_get | 避免被单一通道阻塞 |
| 超时控制(如等待 100ns 无数据则报错) | 非阻塞 try_get + 定时器 | 可精确控制等待时长 |
| 高吞吐量流水线 | 阻塞 put + 非阻塞 try_get 混合 | 生产者同步,消费者灵活 |
| 初始化阶段数据预填充 | 非阻塞 try_put | 避免初始化顺序依赖 |
常见陷阱:
- 阻塞操作在
initial块中可能导致仿真死锁,务必确保消费者与生产者配对。 - 非阻塞操作返回值必须检查,忽略返回值可能导致数据丢失或重复处理。
- 混合使用阻塞与非阻塞时,注意 mailbox 的容量设置,避免非阻塞 put 频繁失败。
4.7 本章小结
- put/get 端口是 UVM 验证组件间单向数据传递的基础,支持阻塞与非阻塞两种语义。
- 阻塞操作保证数据原子性,适合同步场景;非阻塞操作提供灵活性,适合轮询与超时控制。
- 时序图清晰展示了阻塞操作中的挂起区间与非阻塞操作的立即返回特性。
- 实际部署中应根据数据流特征、组件耦合度与仿真性能要求选择合适的操作模式。