� Efficient Packet Handling with the Streaming Operator �
Imagine a design where you have a 146-bit data input along with a valid signal that indicates when a valid
packet arrives. Additionally, there's a last signal that goes high when the final beats of the packet are
received.
Now, let’s look at the packet structure that the design accepts. It includes fields such as:
✅ Source Address
✅ Destination Address
✅ Source Address Type
✅ Length or Number of Reloads
The Requirement
Pack :
function void create_packet();
pkt_in_q = {}; // Clear queue
// Manually extract and push bytes Part Selection (+:) to
for (int i = 3; i >= 0; i--) pkt_in_q.push_back(da[i*8 +: 8]); manually pack each field
for (int i = 3; i >= 0; i--) pkt_in_q.push_back(sa[i*8 +: 8]);
into the queue pkt_in_q.
for (int i = 1; i >= 0; i--) pkt_in_q.push_back(etype[i*8 +: 8]);
for (int i = 1; i >= 0; i--) pkt_in_q.push_back(len[i*8 +: 8]);
foreach (payload_q[i]) pkt_in_q.push_back(payload_q[i]);
`
// Display
foreach (pkt_in_q[i]) $display("pkt_in_q[%0d] = %h", i, pkt_in_q[i]);
endfunction
Why This is Difficult to Manage?
❌ Manual Extraction of Each Byte
For every field, we manually loop through each byte and push_back() it into pkt_in_q.
The order must be maintained correctly (from MSB to LSB).
More lines of code increase complexity and maintenance effort.
❌ Error-Prone for Large Packets
If the packet structure changes (e.g., da becomes 6 bytes instead of 4), we must update multiple
loops manually.
A single wrong index in Part selection (+:) can cause incorrect data alignment.
❌ Difficult to Modify and Scale
Suppose the order of fields changes (e.g., sa comes before da).
We must rewrite multiple loops manually instead of just changing one line.
If new fields are added, we must write new loops instead of appending them in one statement.
function void create_packet(); Streming operator (>>) create a
pkt_in_q = {>>{da,sa,etype,len,payload_q}};
packet by packing all fields into
foreach(pkt_in_q[i]) $display("pkt_in_q[%0d] = %h",i,pkt_in_q[i]);
a single array
endfunction
Why {>>} is Better?
✅ Eliminates all push_back() loops – Packs everything in a single statement.
✅ Automatically handles bit widths & order – No need to worry about MSB/LSB extraction.
✅ Easier to modify – If da moves after sa, just change the order inside {>>}.
✅ Highly scalable – Works for any number of fields without extra loops
Unpack:
// Manually extracting each field from pkt_in_q
item_collected.da = {item_collected.pkt_in_q[0], item_collected.pkt_in_q[1], item_collected.pkt_in_q[2],
Item_collected.pkt_in_q[3] };
item_collected.sa = { item_collected.pkt_in_q[4], item_collected.pkt_in_q[5], item_collected.pkt_in_q[6],
item_collected.pkt_in_q[7] };
item_collected.etype = { item_collected.pkt_in_q[8], item_collected.pkt_in_q[9] };
item_collected.len = { item_collected.pkt_in_q[10], item_collected.pkt_in_q[11] };
// Manually extract payload Manually extracting each field from
for (int i = 12; i < pkt_in_q.size(); i++) Pkt_in_q
item_collected.payload_q.push_back(pkt_in_q[i]);
Problems with Manual Unpacking
❌ Manually extracting every field → Requires keeping track of field positions in pkt_in_q
manually.
❌ Error-prone if field sizes or positions change → Need to modify multiple lines carefully.
❌ More lines of code → Extracting each field separately increases complexity.
❌ Difficult to maintain for large packets → Hard to extend if more fields are added.
// Unpacking all fields using streaming operator
{>>{item_collected.da, item_collected.sa, item_collected.etype, item_collected.len,
item_collected.payload_q}} = pkt_in_q;
// Resize payload array based on length
item_collected.payload_q = new[item_collected.len] (item_collected.payload_q);
the streaming operator {<<} automatically
unpacks the entire packet in one line!
Benefits of {<<} Streaming Operator
✅ One-line unpacking – No need to extract fields manually.
✅ Handles bit widths automatically – Avoids manual part selection errors.
✅ Easier to modify – Just reorder the fields inside {<<} instead of modifying multiple lines.
✅ More scalable – Works for any packet size without extra effort.
need to implement a driver to send this packet and a monitor to sample it from the interface.
You need to drive this all packet field which different in sizes on data input as per packet structure. And
sample this data from interface and segregate this in all packet fields in monitor.
Make a code without stream and show dificullties
We just took a very simple packet structure here but think if the packets is complex having multiple fields.
And think about a situation where there is a change come in packet structure either with respective to field
position or field size. are your code ready to accomadate these changes easily
without much rework and efforts ?
The Power of the Streaming Operator
How can we efficiently handle packet-based transactions? The streaming operator makes this process
seamless! Take a look at the code—see how cleanly and efficiently it is written? It helps in:
✅ Packing all fields into a single array for easy transfer
✅ Handling packet-based and frame-based protocols with minimal effort
✅ Reducing complexity in both driving and monitoring
Simplifying Code Modifications
What if your project specifications change?
� The source address width changes from 32 bits to 46 or 64 bits?
� The field order changes, requiring the source address to be sent before the destination address?
With traditional coding, these modifications could be time-consuming. But with the streaming operator, all
you need to do is update a single line in the driver. No need for lengthy rewrites—just tweak the
streaming operator position, and you're done!
Key Takeaways
� Write generic, reusable, and efficient code
� Reduce development time with minimal modifications
� Easily adapt to changing protocol specifications
This is the power of the streaming operator in handling packet-based transactions efficiently! �
What are your thoughts on this approach? Have you used the streaming operator in your projects? Let’s
discuss in the comments! ⬇
Without streming Operator
class transaction;
// Destination Address
rand bit [15:0] etype; // Type Field
rand bit [15:0] len; // Payload Length
rand bit [7:0] payload_q[]; // Payload Data
bit [7:0] pkt_in_q [$]; // Queue to store packet bytes
constraint data_size { payload_q.size() == len; solve len before payload_q.size; }
function void create_packet();
pkt_in_q = {}; // Clear queue before pushing data
// Push each field byte-by-byte using array slicing
for (int i = 3; i >= 0; i--) pkt_in_q.push_back(da[i*8 +: 8]); //TODO
for (int i = 3; i >= 0; i--) pkt_in_q.push_back(sa[i*8 +: 8]); //TODO
for (int i = 1; i >= 0; i--) pkt_in_q.push_back(etype[i*8 +: 8]); //TODO
for (int i = 1; i >= 0; i--) pkt_in_q.push_back(len[i*8 +: 8]); //TODO
// Push payload bytes into pkt_in_q
foreach (payload_q[i]) pkt_in_q.push_back(payload_q[i]);// TODO
// Displaying packet content
foreach (pkt_in_q[i]) $display("pkt_in_q[%0d] = %h", i, pkt_in_q[i]);
endfunction
function void post_randomize();
create_packet();
endfunction
endclass
module top();
transaction trans_h;
transaction item_collected;
bit clk;
bit [7:0] data_in;
bit valid;
bit last;
// Generate packet
task generate_();
trans_h = new();
trans_h.randomize() with { da == 32'hAABBCCDD; sa == 32'h11223344; etype == 16'h55AA; len == 3; };
endtask
// Drive packet to data input interface
task drive();
foreach(trans_h.pkt_in_q[i]) begin
@(negedge clk);
data_in = trans_h.pkt_in_q[i];
valid = 1'b1;
if (i == (trans_h.pkt_in_q.size() - 1)) last = 1'b1;
end
@(negedge clk);
valid = 1'b0;
last = 1'b0;
endtask
// Monitor and extract fields
task monitor();
bit [31:0] da, sa;
bit [15:0] etype, len;
bit [7:0] payload_q[$];
item_collected = new();
forever begin
@(posedge clk iff valid);
item_collected.pkt_in_q.push_back(data_in);
if (last) begin
$display("Received Packet:");
foreach (item_collected.pkt_in_q[i]) $display("Data[%0d] = %h", i, item_collected.pkt_in_q[i]); // TODO
// Manually extract fields
da = { item_collected.pkt_in_q[0], item_collected.pkt_in_q[1], item_collected.pkt_in_q[2],
item_collected.pkt_in_q[3] }; //TODO
sa = { item_collected.pkt_in_q[4], item_collected.pkt_in_q[5], item_collected.pkt_in_q[6],
item_collected.pkt_in_q[7] }; //TODO
etype = { item_collected.pkt_in_q[8], item_collected.pkt_in_q[9] };//TODO
len = { item_collected.pkt_in_q[10], item_collected.pkt_in_q[11] };//TODO
for (int i = 12; i < item_collected.pkt_in_q.size(); i++) begin
payload_q.push_back(item_collected.pkt_in_q[i]);//TODO
end
// Display Extracted Fields
$display("Decoded Fields:");
$display("Monitor: DA = %h", da);
$display("Monitor: SA = %h", sa);
$display("Monitor: Etype = %h", etype);
$display("Monitor: Len = %h", len);
foreach (payload_q[i]) $display("Monitor: Payload[%0d] = %h", i, payload_q[i]);
end
end
endtask
// Clock Generation
always #5 clk = ~clk;
initial begin
fork
generate_();
drive();
monitor();
join
end
initial begin
#1000 $finish;
end
endmodule
With streming operator
// Packet Structure: | da | sa | etype | len |
// |4 | 4 | 2 | 2 | :bytes
// Transaction class to represent the packet structure
class transaction;
// Declaring the packet fields as random variables
rand bit [31:0] sa; // Source Address
rand bit [31:0] da; // Destination Address
rand bit [15:0] etype; // Type field
rand bit [15:0] len; // Payload length
rand bit [7:0] payload_q[]; // Payload array
bit [143:0] pkt_in_q [$]; // Queue to hold the packed packet data
// Constraint to ensure that the payload size matches the length field
constraint data_size {payload_q.size() == len; solve len before payload_q.size;}
// Function to create a packet by packing all fields into a single array
function void create_packet();
pkt_in_q = {>>{da, sa, etype, len, payload_q}}; // Streaming operator to pack data
foreach(pkt_in_q[i]) pkt_in_q[i] = {<<8{pkt_in_q[i]}}; // Convert to big-endian format
foreach(pkt_in_q[i]) $display("pkt_in_q[%0d] = %h", i, pkt_in_q[i]); // Display the packed data
endfunction
// Function called after randomization to create the packet
function void post_randomize();
create_packet();
endfunction
endclass
// Top module to drive and monitor the transaction
module top();
transaction trans_h; // Handle for transaction object
transaction item_collected; // Handle for collected transaction data
bit clk; // Clock signal
bit [143:0] data_in; // Input data line for packet transmission
bit valid; // Valid signal to indicate a valid data transfer
bit last; // Last signal to indicate the last beat of a packet
// Task to generate a transaction with predefined values
task generate_();
trans_h = new();
trans_h.randomize() with {da == 32'h01020304; sa == 32'h05060708; etype == 16'h090A; len == 3;};
endtask
// Task to drive the packet data onto the interface
task drive();
foreach(trans_h.pkt_in_q[i]) begin
@(negedge clk); // Wait for negative edge of clock
data_in = trans_h.pkt_in_q[i]; // Assign data to input
valid = 1'b1; // Set valid signal high
if (i == (trans_h.pkt_in_q.size()-1)) last = 1'b1; // Set last signal for final data word
end
@(negedge clk);
valid = 1'b0; // Deassert valid signal
last = 1'b0; // Deassert last signal
endtask
// Task to monitor and reconstruct the packet
task monitor();
bit [127:0] payload_tmp_q [$]; // Temporary queue for payload
bit [((16-(4+4+2))*8)-1:0] payload_tmp; // Temporary variable for payload
bit [7:0] payload_q_tmp [$]; // Temporary payload queue
item_collected = new(); // Create new transaction object
forever begin
@(posedge clk iff valid); // Wait for positive clock edge when valid is high
item_collected.pkt_in_q.push_back(data_in); // Store received data
if (last) begin
// Display collected packet data
foreach(item_collected.pkt_in_q[i]) $display("monitor pkt_in_q[%0d] = %h", i, item_collected.pkt_in_q[i]);
// Unpack received data back into individual fields
foreach(item_collected.pkt_in_q[i])
if (i == 0)
{>>{item_collected.da, item_collected.sa, item_collected.etype, item_collected.len, payload_tmp}} =
{<<8{item_collected.pkt_in_q[i]}};
else
payload_tmp_q.push_back({<<8{item_collected.pkt_in_q[i]}});
// Merge collected payload data
{>>{item_collected.payload_q}} = {>>{payload_tmp, payload_tmp_q}};
item_collected.payload_q = new[item_collected.len] (item_collected.payload_q);
// Display extracted packet fields
$display("monitor : da = %h", item_collected.da);
$display("monitor : sa = %h", item_collected.sa);
$display("monitor : etype = %h", item_collected.etype);
$display("monitor : len = %h", item_collected.len);
foreach(item_collected.payload_q[i]) $display("monitor : item_collected.payload_q[%0d] = %h", i,
item_collected.payload_q[i]);
end
end
endtask
// Clock generation (period = 10 time units)
always #5 clk = ~clk;
// Initial block to start tasks in parallel
initial begin
fork
generate_(); // Generate a packet
drive(); // Drive the packet onto the interface
monitor(); // Monitor and reconstruct the packet
join
end
// End simulation after 1000 time units
initial begin
#1000 $finish;
end
endmodule
Here’s a structured description of your code with code snippets followed by explanations:
� Transaction Class: Defining the Packet Structure
class transaction;
rand bit [31:0] sa; // Source Address
rand bit [31:0] da; // Destination Address
rand bit [15:0] etype; // Type field
rand bit [15:0] len; // Payload length
rand bit [7:0] payload_q[]; // Payload array
bit [143:0] pkt_in_q [$]; // Queue to hold the packed packet data
constraint data_size {payload_q.size() == len; solve len before payload_q.size;}
function void create_packet();
pkt_in_q = {>>{da, sa, etype, len, payload_q}}; // Packing all fields into one
array
foreach(pkt_in_q[i]) pkt_in_q[i] = {<<8{pkt_in_q[i]}}; // Convert to big-endian
format
foreach(pkt_in_q[i]) $display("pkt_in_q[%0d] = %h", i, pkt_in_q[i]); // Display
the packed data
endfunction
function void post_randomize();
create_packet(); // Call create_packet after randomization
endfunction
endclass
� The transaction class defines the structure of a packet.
� Randomization constraints ensure payload length matches len.
� Streaming operator (>>{}) efficiently packs data into a single array (pkt_in_q).
� The post_randomize function automatically calls create_packet() after randomization.
� Top Module: Driving and Monitoring the Packet
module top();
transaction trans_h;
transaction item_collected;
bit clk;
bit [143:0] data_in;
bit valid;
bit last;
� Declares transaction objects for sending (trans_h) and receiving (item_collected).
� Defines data_in for packet transmission and valid/last signals.
� Task to Generate a Packet
task generate_();
trans_h = new();
trans_h.randomize() with {da == 32'h01020304; sa == 32'h05060708; etype ==
16'h090A; len == 3;};
endtask
� Randomizes packet fields with predefined values.
� Creates a new instance of transaction before randomization.
� Task to Drive the Packet
task drive();
foreach(trans_h.pkt_in_q[i]) begin
@(negedge clk);
data_in = trans_h.pkt_in_q[i];
valid = 1'b1;
if (i == (trans_h.pkt_in_q.size()-1)) last = 1'b1;
end
@(negedge clk);
valid = 1'b0;
last = 1'b0;
endtask
� Sends packet word by word on data_in.
� Valid signal indicates active transmission.
� Last signal asserts when the final part of the packet is sent.
� Task to Monitor and Extract the Packet
task monitor();
bit [127:0] payload_tmp_q [$];
bit [((16-(4+4+2))*8)-1:0] payload_tmp;
bit [7:0] payload_q_tmp [$];
item_collected = new();
forever begin
@(posedge clk iff valid);
item_collected.pkt_in_q.push_back(data_in);
if (last) begin
foreach(item_collected.pkt_in_q[i])
$display("monitor pkt_in_q[%0d] = %h", i,
item_collected.pkt_in_q[i]);
foreach(item_collected.pkt_in_q[i])
if (i == 0)
{>>{item_collected.da, item_collected.sa, item_collected.etype,
item_collected.len, payload_tmp}} = {<<8{item_collected.pkt_in_q[i]}};
else
payload_tmp_q.push_back({<<8{item_collected.pkt_in_q[i]}});
{>>{item_collected.payload_q}} = {>>{payload_tmp, payload_tmp_q}};
item_collected.payload_q = new[item_collected.len]
(item_collected.payload_q);
$display("monitor : da = %h", item_collected.da);
$display("monitor : sa = %h", item_collected.sa);
$display("monitor : etype = %h", item_collected.etype);
$display("monitor : len = %h", item_collected.len);
foreach(item_collected.payload_q[i]) $display("monitor :
item_collected.payload_q[%0d] = %h", i, item_collected.payload_q[i]);
end
end
endtask
� Monitors incoming data and stores it in pkt_in_q.
� Uses streaming operator (>>{}) to unpack received data.
� Displays decoded fields (da, sa, etype, len, payload_q).
� Clock Generation
always
#5 clk = ~clk;
� Generates a clock signal with a 10-time unit period.
� Running the Simulation
initial begin
fork
generate_(); // Generate packet
drive(); // Drive packet to interface
monitor(); // Monitor received packet
join
end
initial begin
#1000 $finish; // Stop simulation after 1000 time units
end
endmodule
� fork-join starts generate_(), drive(), and monitor() in parallel.
� Simulation ends after 1000 time units.
� Key Takeaways
✅ Streaming Operator (>>{}) simplifies packing/unpacking.
✅ Modular design makes the code efficient and reusable.
✅ Minimal changes needed when modifying field order or width.
✅ Monitored output matches transmitted data, ensuring verification correctness.
This structured format makes the explanation clear and easy to follow. Let me know if you need further
refinements! �