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!


Anargha November 30th, 2018 02:05:32

Hi,

In this line
static int value = {<<8{array}};

while reversing elements, instead of 8, can I use a variable? For ex:

int a = 4
static int value = {<<a{array}};

Would this be acceptable?


    Cristian Bob December 6th, 2018 17:19:04

    Hi, Anargha.

    Unfortunately it is not possible to use the streaming operators with a variable or a const variable. You can use a parameter instead.

    A parameterized version of example_2:

    
    module example_2;
      
      parameter a=8;
    
      initial begin
        static bit [7:0] array[4] = '{ 8'h8C, 8'h00, 8'hA4, 8'hFF };
        static int       value    = {<<a{array}};
    
        $display("value = 0x%h", value);
      end
    endmodule 
    

    Another workaround to this problem would be utilizing MACROs.

    
    `define A 8
    
    module example_2;
      initial begin
        static bit [7:0] array[4] = '{ 8'h8C, 8'h00, 8'hA4, 8'hFF };
        static int       value    = {<<`A{array}};
    
        $display("value = 0x%h", value);
      end
    endmodule
    

Sharan December 8th, 2018 17:34:25

Please note that example 2 shows different value than what is described.
I tried this as I got a doubt looking at the example.

This is the output: value = 0xffa4008c


    Mihai December 11th, 2018 14:05:42

    What do you mean ? Example 2 shows the exact same output value: 0xffa4008c.


David Larson January 11th, 2019 23:09:52

So let’s say that you are packing into a variable that is wider than the source data.

byte src_data[$] = {‘hAA, ‘hBB, ‘hCC, ‘hDD};
uvm_reg_data_t reg_data = {>>byte{pkt.data}};

This will pack the info from src_data into reg_data starting at the MSB, so it is left shifted:

reg_data = ‘hAABBCCDD00000000

That is not what I want. I want it right shifted:

reg_data = ‘hAABBCCDD

How to get this right?


    Dragos Dospinescu January 15th, 2019 12:39:53

    Hi David,

    There is no direct way that I know of to achieve what you need only using packing/unpacking operations.
    I did however explore a couple of possibilities, which I will show below.

    One option would be to shift the result by the number of bits needed to get rid of the padding.

    byte src_data[$] = {'hAA, 'hBB, 'hCC, 'hDD};
    uvm_reg_data_t reg_data = {>>byte{src_data}};
      
    // $bits(reg_data) = size (in bits) of the destination variable.
    // $bits(src_data) = size (in bits) of the source data.
    // The difference in bits represent exactly the number of '0's padded to the right.
    // Shift right the register to get rid of right padded 0s.
    reg_data >>= ($bits(reg_data) - $bits(src_data));
    

    The second option is to reverse the packet’s data bytes before packing it into the register, and then reversing the bytes in the register.
    Example:

    byte src_data[$] = {'hAA, 'hBB, 'hCC, 'hDD};
    uvm_reg_data_t reg_data;
    
    // reverse the bytes in the data packet
    src_data.reverse();
    // pack the reversed data into the register
    reg_data = {>>byte{src_data}}; // register now contains 'hDDCCBBAA_00000000
    // reverse the bytes in the register
    reg_data = {<<byte{reg_data}}; // register now contains 'h00000000_AABBCCDD
    

    Option 2 is far less efficient because it involves more operations, but it doesn’t rely on the $bits system function, which does not work with dynamic arrays for all simulators.
    So, in the end you will have to experiment to see what suits your needs.
    If you find a better alternative, please share your solution.

    Regards,
    Dragos


    David Larson January 15th, 2019 19:18:35

    Thank you Dragos. I didn’t think of the second solution, I like it. I suspect that we can skip src_data.reverse() by packing the reg_data using the right to left streaming operator and let it reverse the bytes for us (unconfirmed):

    reg_data = {<<byte{src_data}}; // register now contains 'hDDCCBBAA_00000000
    reg_data = {<<byte{reg_data}}; // register now contains 'h00000000_AABBCCDD

    I'll experiment with it.

    Thanks,
    David


    Dragos Dospinescu January 16th, 2019 11:02:43

    Hi David,

    You are correct indeed. I just checked your version of the solution and it seems to work just fine.

    byte src_data[$] = {'hAA, 'hBB, 'hCC, 'hDD};
    uvm_reg_data_t reg_data;
    
    // reverse the bytes in the data packet
    reg_data = {<<byte{src_data}}; // register now contains 'hDDCCBBAA_00000000
    // reverse the bytes in the register
    reg_data = {<<byte{reg_data}}; // register now contains 'h00000000_AABBCCDD
    

    Thanks for your input.

    Best Regards,
    Dragos


ElPrg February 14th, 2019 22:20:02

Thank you for post. I had some question : what program you used for pictures ?


    Aurelian Ionel Munteanu February 15th, 2019 09:55:39

    I’m glad you like it. It is written in the last paragraph:

    The above diagrams have been created using an open source design tool called Inkscape.


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.