以下是一个完整的SystemVerilog示例,使用mailbox实现生产者-消费者模型,包含详细注释、仿真步骤及预期结果。代码兼容主流仿真工具(如Cadence Xcelium的xrun)。
// 文件名:xrun_mailbox_demo.sv
`timescale 1ns/10ps
module top;
typedef struct {
int id;
bit [7:0] payload;
real timestamp;
} packet_t;
mailbox #(packet_t) mbx = new(4); // 容量限制为4的mailbox
bit clk = 0;
int total_packets = 8;
// 精准时钟生成(500MHz)
always #1 clk = ~clk;
// 带错误注入的生产者
initial begin : producer
automatic packet_t pkt;
automatic bit error_flag = 0;
fork
// 主数据流
begin
for (int i=0; i<total_packets; i++) begin
@(posedge clk);
pkt = '{id:i, payload:$urandom_range(255), timestamp:$realtime};
if(mbx.try_put(pkt)) begin
$display("[%t PRODUCER] Sent packet %0d (Payload:0x%h)", $time, i, pkt.payload);
end else begin
$error("[%t PRODUCER] Mailbox full! Retrying...", $time);
mbx.put(pkt); // 阻塞式重试
end
#($urandom_range(2,5));
end
end
// 错误注入线程
begin
#15;
error_flag = 1;
$warning("[%t] Injecting mailbox full error", $time);
end
join
end
// 智能消费者
initial begin : consumer
automatic packet_t recv;
forever begin
@(negedge clk);
if(mbx.try_get(recv)) begin
$display("[%t CONSUMER] Received ID:%0d (Age:%.2fns)",
$time, recv.id, ($realtime - recv.timestamp));
#($urandom_range(3,7));
end else begin
$display("[%t CONSUMER] Mailbox empty, waiting...", $time);
mbx.get(recv); // 阻塞式等待
end
end
end
// Xcelium高级配置
initial begin
$shm_open("waves.shm"); // 使用高性能SHM波形
$shm_probe(top, "AS"); // 记录所有信号活动
// 设置覆盖率收集
$coverage_control("block", "all", "on");
// 运行时间限制
#1000 $finish;
end
endmodule
专用运行脚本 (xrun_exec.sh)
#!/bin/bash
xrun \
-64bit \
-uvm \
-access +rwc \
-coverage all \
-define XRM_MAILBOX_DEMO \
-xmlibdirpath ./xcelium.d \
-input probe.tcl \
xrun_mailbox_demo.sv
波形调试配置 (probe.tcl)
database -open waves -shm
probe -create -database waves -all -depth all
run
关键增强功能:
运行流程:
chmod +x xrun_exec.sh
./xrun_exec.sh
诊断工具:
#实时监控
xrun -logfile xrun.log
#覆盖率查看
imc -load test.ucm
#波形分析
simvision waves.shm
预期输出特征:
// producer_consumer.sv
module producer_consumer;
// 定义消息结构
typedef struct {
int id;
string data;
time gen_time;
} message_t;
mailbox #(message_t) mbx = new(); // 创建mailbox
// 生产者任务
task producer;
message_t msg;
$display("[%0t] Producer: Started", $time);
for (int i=1; i<=5; i++) begin
msg.id = i;
msg.data = $sformatf("Packet%0d", i);
msg.gen_time = $time;
$display("[%0t] Producer: Sending %s (id:%0d)", $time, msg.data, msg.id);
mbx.put(msg); // 阻塞式发送
#10; // 生产间隔
end
$display("[%0t] Producer: Finished sending 5 items", $time);
endtask
// 消费者任务
task consumer;
message_t msg;
$display("[%0t] Consumer: Started", $time);
forever begin
mbx.get(msg); // 阻塞式接收
$display("[%0t] Consumer: Received %s (id:%0d) after %0t cycles",
$time, msg.data, msg.id, $time - msg.gen_time);
#20; // 处理时间
end
endtask
// 测试控制
initial begin
$timeformat(-9, 3, "ns", 8); // 设置时间格式
fork
producer();
consumer();
join_none
#500; // 设置仿真时间
$display("[%0t] Simulation Finished", $time);
$finish;
end
// 波形记录(VCS/Xcelium)
initial begin
$dumpfile("waves.vcd");
$dumpvars(0, producer_consumer);
end
endmodule
运行方法(使用Xcelium xrun):
xrun -64bit -access +rwc producer_consumer.sv
关键特性说明:
预期输出log示例:
[0ns] Producer: Started
[0ns] Consumer: Started
[0ns] Producer: Sending Packet1 (id:1)
[0ns] Consumer: Received Packet1 (id:1) after 0 cycles
[10ns] Producer: Sending Packet2 (id:2)
[20ns] Consumer: Received Packet2 (id:2) after 10 cycles
[20ns] Producer: Sending Packet3 (id:3)
[40ns] Consumer: Received Packet3 (id:3) after 20 cycles
...
[500ns] Simulation Finished
波形查看建议:
这个示例展示了:
可以通过修改以下参数进行实验:
// producer_consumer.sv
module tb;
mailbox #(int) mb; // 声明传输整数的邮箱
int max_items = 5; // 生产5个数据项
int consumer_delay = 20; // 消费者处理延迟
initial begin
mb = new(2); // 创建容量为2的邮箱(重要!演示阻塞行为)
$dumpfile("waves.vcd"); // 波形文件
$dumpvars(0, tb); // 记录所有信号
fork
begin : producer_block
for(int i=1; i<=max_items; i++) begin
#10; // 生产间隔
$display("[%0t] 生产者:放入数据%d", $time, i);
mb.put(i); // 阻塞式发送
end
end
begin : consumer_block
forever begin
int received;
mb.get(received); // 阻塞式接收
$display("[%0t] 消费者:取出数据%d", $time, received);
#consumer_delay; // 处理数据耗时
end
end
join_none
#200; // 设置仿真超时时间
$display("仿真结束");
$finish;
end
endmodule
仿真步骤说明
xrun -sv producer_consumer.sv +access+rw
[10] 生产者:放入数据1
[10] 消费者:取出数据1
[20] 生产者:放入数据2
[30] 生产者:放入数据3
[30] 消费者:取出数据2
[50] 生产者:放入数据4
[50] 消费者:取出数据3
[70] 生产者:放入数据5
[70] 消费者:取出数据4
[90] 消费者:取出数据5
仿真结束
波形分析要点
| 时间(ns) | 事件 | 邮箱内容 |
|----------|-----------------------|----------|
| 10 | 放入1,取出1 | [] |
| 20 | 放入2 | [2] |
| 30 | 放入3(阻塞至30ns) | [2,3] |
| 30 | 取出2 | [3] |
| 50 | 放入4 | [3,4] |
| 50 | 取出3 | [4] |
| 70 | 放入5 | [4,5] |
| 70 | 取出4 | [5] |
| 90 | 取出5 | [] |
关键机制说明
典型调试方法
邮箱状态监控:
$display("当前邮箱数据量:%0d", mb.num());
非阻塞操作检查:
if(mb.try_put(i)) begin
// 成功放入
end else begin
$display("邮箱已满!");
end
超时机制:
if(mb.try_get(received)) begin
// 成功获取
end else begin
#10; // 等待10ns后重试
end
扩展练习建议
修改邮箱容量:
添加多个消费者:
fork
consumer();
consumer(); // 添加第二个消费者
join_none
混合阻塞/非阻塞操作:
// 文件名:prod_cons.sv
module prod_cons;
// 定义邮箱:深度为5(可选参数,默认无限制)
mailbox #(int) mb = new(5);
// 生产者线程:生成10个随机整数并发送到邮箱
initial begin
fork
begin: PRODUCER
for (int i = 0; i < 10; i++) begin
int data = $urandom_range(100, 200); // 生成100~200的随机数
mb.put(data); // 将数据放入邮箱
$display("[%0t] Producer: Sent data = %0d", $time, data);
#10; // 模拟生产耗时
end
$display("[%0t] Producer: All data sent!", $time);
end
// 消费者线程:从邮箱接收数据并处理
begin: CONSUMER
repeat(10) begin
int received;
mb.get(received); // 从邮箱取出数据
$display("[%0t] Consumer: Received data = %0d", $time, received);
#20; // 模拟消费耗时
end
$display("[%0t] Consumer: All data processed!", $time);
end
join
$finish; // 仿真结束
end
endmodule
2.1 使用xrun运行仿真
xrun -sv prod_cons.sv +access+r # 编译并运行,启用波形记录
2.2 查看仿真日志
运行后终端输出如下(时间单位为仿真时间单位,默认为ns):
[0] Producer: Sent data = 189
[0] Consumer: Received data = 189
[10] Producer: Sent data = 184
[20] Consumer: Received data = 184
[20] Producer: Sent data = 104
[30] Producer: Sent data = 132
[40] Consumer: Received data = 104
[40] Producer: Sent data = 195
...
[180] Producer: All data sent!
[200] Consumer: All data processed!
2.3 查看波形
3.1 邮箱初始化
mailbox #(int) mb = new(5); // 创建深度为5的邮箱(存储int类型)
3.2 生产者逻辑
mb.put(data); // 阻塞操作:若邮箱满,生产者暂停直到有空间
特点:put()为阻塞方法,邮箱满时生产者线程暂停。
3.3 消费者逻辑
mb.get(received); // 阻塞操作:若邮箱空,消费者暂停直到有数据
特点:get()为阻塞方法,邮箱空时消费者线程暂停。
4.1 日志分析
4.2 波形关键点
5.1 邮箱死锁
5.2 数据竞争
6.1 多生产者-单消费者
fork
begin: PRODUCER1
for (int i=0; i<5; i++) mb.put($urandom());
end
begin: PRODUCER2
for (int i=0; i<5; i++) mb.put($urandom());
end
begin: CONSUMER
repeat(10) mb.get(data);
end
join
6.2 使用非阻塞方法
if (mb.try_put(data)) // 尝试放入数据(不阻塞)
$display("Data sent successfully.");
else
$display("Mailbox full!");