A mailbox is a FIFO for data between processes. put blocks when a bounded box is full, get blocks when it is empty, and peek copies the head entry without removing it.
The nonblocking try_put, try_get and try_peek return status instead of blocking. num reports a snapshot count that can go stale before it is acted on.
Call | When full or empty | Use |
|---|---|---|
| Block | Default producer-consumer flow |
| Blocks when empty | Inspect without consuming |
| Return 0 | Polling without stealing |
| Snapshot only | Debug, never flow control |
new(0) builds an unbounded box where put never blocks. new(N) caps the depth, so a fast producer stalls until the consumer frees a slot.
That stall is backpressure, and it throttles the producer to the consumer rate for free. An unbounded box with a slow consumer instead grows memory all soak run.
A depth-4 mailbox between a fast generator and a driver that takes one item every 20 ns fills after four puts. The fifth put blocks until the driver frees a slot, and from then on the generator runs at the driver's rate, not its own.
A mailbox of class objects moves handles, not copies. The producer must construct a fresh object per put, or every queued entry aliases one mutating object.
Always write mailbox #(T) with a type parameter. The bare form defers type checks to run time, while the parameterized form fails at compile time.
Near the end. After four puts the box is full, so every later put waits for a get; the generator ends only a few items ahead of the consumer, which is what the check measures.
A slow consumer drains the box, and the check proves the generator was throttled. randomize() fills each item's rand fields with random values; the Randomization part teaches it.
class bus_txn_c;
rand bit [31:0] addr;
rand bit [31:0] data;
endclass
class generator_c;
mailbox #(bus_txn_c) mb;
int unsigned count;
function new(mailbox #(bus_txn_c) m, int unsigned c);
mb = m;
count = c;
endfunction
task run();
repeat (count) begin
bus_txn_c t = new();
void'(t.randomize());
mb.put(t);
end
endtask
endclass
module top_mbx;
timeunit 1ns;
timeprecision 1ps;
mailbox #(bus_txn_c) mb;
generator_c gen;
time gen_done_at;
int got = 0;
initial begin
mb = new(4); // declare above, construct here in order
gen = new(mb, 20);
fork
begin
gen.run();
gen_done_at = $time;
end
repeat (20) begin // slow consumer: one item per 20 ns
bus_txn_c t;
#20;
mb.get(t);
got++;
end
join
if (got != 20) $fatal(1, "FAIL: consumer got %0d of 20", got);
if (gen_done_at < 300) $fatal(1, "FAIL: generator finished at %0d ns, never throttled", gen_done_at);
$display("PASS: 20 random items through a depth-4 mailbox, generator throttled until %0d ns", gen_done_at);
$finish;
end
endmodulePASS: 20 random items through a depth-4 mailbox, generator throttled until 320 nsEvery queued entry is the same object, so all of them show whatever the generator wrote last. Build a fresh object for each put.
peek, since it copies without removing. get would steal the item and starve the consumer.
Six ordered items through a depth-2 store with an instant producer and a 20ns consumer. The stall flag proves backpressure engaged.
module tb_pipe_backpressure;
timeunit 1ns;
timeprecision 1ps;
int mem[2];
int head = 0;
int tail = 0;
int count = 0;
int errors = 0;
int stalled = 0;
int consumed = 0;
initial begin : producer
for (int i = 0; i < 6; i++) begin
if (count == 2) stalled = 1;
wait (count < 2);
mem[tail] = i;
tail = (tail + 1) % 2;
count++;
end
end
initial begin : consumer
int want = 0;
int got = 0;
repeat (6) begin
wait (count > 0);
got = mem[head];
head = (head + 1) % 2;
count--;
if (got !== want) errors++;
want++;
consumed++;
#20;
end
end
initial begin
wait (consumed == 6);
if (stalled !== 1) errors++;
if (count !== 0) errors++;
if (errors == 0) $display("PASS: 6 items in order through a depth-2 pipe with stalls");
else $fatal(1, "FAIL: consumed=%0d stalled=%0d", consumed, stalled);
$finish;
end
endmodulePASS: 6 items in order through a depth-2 pipe with stallsThe lab at the end of this topic builds PacketFIFO's stimulus path on this pattern: generator, mailbox, driver.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. These class benches were run on Verilator only.