How to Pack Data Using the SystemVerilog Streaming Operators (>>, <<)

The verification of digital circuits requires dealing with bits and bytes. It is not a trivial thing to pack or unpack bits, bytes, half words, words or user defined data structures.

This post is the first in a series of tutorials about packing and unpacking in SystemVerilog.

The article’s sections are:

Introduction

When doing packing/unpacking operations in SystemVerilog, a simple bit-stream cast is often enough:

typedef struct {
  bit [7:0] address;
  bit [7:0] payload[2];
} packet;

typedef bit [7:0] data_stream[$];
// ...
packet      pkt;
data_stream stream;
// ...
stream = { stream, data_stream'(pkt) };

For extra flexibility, streaming operators can be used in the cases where the bit ordering is important or a simple bit-stream cast is not sufficient..

There are two streaming operators, {>>{}} and {<<{}}, which operate on data blocks (or slices). By default, slices have the size 1, but the slice size can be changed according to the needs. Using {>>{}} will cause the data blocks to be streamed from left to right, while {<<{}} will stream the data blocks from right to left.

Because an image is worth a thousand words, I’ll use graphics to show how to use the streaming operators and how the individual bits are affected by the stream operators.

1. Pack bytes into an int

1.1 Byte variables to an int

When we need to pack several variables into a single variable, we can use the left-to-right streaming operator ( {>>{}} ).

module example_1_1;
  initial begin
    static byte a     = 8'h8C;
    static byte b     = 8'h00;
    static byte c     = 8'hA4;
    static byte d     = 8'hFF;
    static int  value = {>>{a, b, c, d}};

    $display("value = 0x%h", value);
  end
endmodule

Specifying a slice size for left-to-right streaming operator ( {>>{}} ) has the same effect as using the default slice size of 1. This means that {>>{}} is the same as {>>4{}}, {>>8{}}, or any other value.

1.2 Array of bytes to an int

Packing an array of bytes into a single variable is just as easy:

module example_1_2;
  initial begin
    static bit [7:0] array[4] = '{ 8'h8C, 8'h00, 8'hA4, 8'hFF };
    static int       value    = {>>{array}};

    $display("value = 0x%h", value);
  end
endmodule

2. Reverse the elements of a byte array and pack them into an int

We can reverse the order of an array’s elements and then pack them into a single value in the following way:

module example_2;
  initial begin
    static bit [7:0] array[4] = '{ 8'h8C, 8'h00, 8'hA4, 8'hFF };
    static int       value    = {<<8{array}};

    $display("value = 0x%h", value);
  end
endmodule

If the slice size would be 16 instead of 8, the output value would be 0xA4FF_8C00.

3. Reverse the bits in a byte

Below is an easy way to reverse the order of the bits within a byte. The slice size defaults to 1 if it is not specified. The slice size is a resolution/granularity like concept.

module example_3;
  initial begin
    static bit [7:0] value_a = 8'h8C;
    static bit [7:0] value_b = {<<{value_a}};

    $display("value_b = 0x%h", value_b);
  end
endmodule

If the slice size would be 2 instead of 1, the bits would be reversed in groups of 2, leading to an output value of 8’b00_11_00_10.

If the slice size would be 3, groups of 3 bits would be created starting from the least significant 3 bits. Since we have an 8-bit input value, the last (leftmost) group will contain the remaining 2 bits. No padding or truncation is performed. The bit group reversing would then be performed, leading to the output value 8’b100_001_10.

4. Reverse the nibbles in a byte

A byte contains a low nibble and a high nibble. The order of the nibbles inside the byte can be reversed using the right-to-left streaming operator with a slice size of 4:

module example_4;
  initial begin
    static bit [7:0] value_a = 8'h8C;
    static bit [7:0] value_b = {<<4{value_a}};

    $display("value_b = 0x%h", value_b);
  end
endmodule

5. Reverse the bits of an array and pack them into a shortint

Reversing the elements of an array and, at the same time, the bits of each element of the array is easily achievable using the right-to-left streaming operator:

module example_5;
  initial begin
    static bit [7:0] array[2] = '{ 8'h8C, 8'hA4 };
    static shortint  value    = {<<{array}};

    $display("value = 0x%h", value);
  end
endmodule

6. Advanced packing

In this final example, we’ll take an array of 2-bit values and pack it into a structure. The ordering scheme used for the values in the array is little endian. We can achieve this by taking the result of a right-to-left streaming operator with a slice size of 2, and feeding it as input to another right-to-left streaming operator with a slice size of 4:

module example_6;
  typedef struct {
    bit [3:0] addr;
    bit [3:0] data;
  } packet_t;

  initial begin
    static bit [1:0] array[] = '{ 2'b10, 2'b01, 2'b11, 2'b00 };
    static packet_t  packet  = {<<4{ {<<2{array}} }};

    $display("packet addr = %b", packet.addr);
    $display("packet data = %b", packet.data);
  end
endmodule

If the ordering scheme is big endian, the packing can be performed with a single right-to-left streaming operator ( {>>{}} ), using the default slice size.

References

The above diagrams have been created using an open source design tool called Inkscape. Stay tuned for more packing/unpacking tutorials (Unpacking using streaming operators and UVM pack/unpack).


Comments

Gaurav Gupta June 28th, 2017 09:50:45

Very helpful post to get the basics cleared.


    Horia-Răzvan Enescu September 5th, 2017 12:27:38

    Thank you Gaurav, I’m glad that it was useful for you!


mukund sojitra September 1st, 2017 19:42:14

very well explained. Thank you so much
please keep adding new example


    Horia-Răzvan Enescu September 5th, 2017 12:38:01

    Thank you Mukund! Do you have any particular example in mind that you would like to view?


Varun September 17th, 2017 21:57:03

function void fill_pkt_data();
   int pkt_data_size;
   pkt_data_size = $urandom_range(8,24);
   //make it double word aligned (multiple of 4)
   pkt_data_size = (pkt_data_size >> 2) <<2;
   // build the list of data
   for(int i=0; i < pkt_data_size; i++) begin
      pkt_data.push_back($urandom());
   end
 endfunction

Would you guys be able to tell me what this piece of code is doing


    Stefan Birman September 18th, 2017 11:15:06

    Hi Varun!
    It is not much to say: the code is self-explanatory and the are also some comments.


    Horia-Răzvan Enescu September 18th, 2017 12:00:20

    Also, note that the >> and << operators in your code are not streaming operators, but shift operators.


huseng September 18th, 2018 16:21:56

Really nice explanation .
Thanks and Best regards!


Leave a Comment:

Your comment will be visible after approval.

(will not be published)

This site uses Akismet to reduce spam. Learn how your comment data is processed.