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

In this post I show how to use the streaming operators to unpack data into variables or data structures in SystemVerilog. This is the second part of a 3-post series on data packing/unpacking operations and the natural follow-up to the first part that focuses on packing data using streaming operators.

The unpacking operation is the reverse of packing: it distributes a single high granularity value to lower granularity values. In order to perform an unpack operation, the streaming operator must be used on the left hand side of an assignment. The article’s sections below detail typical unpacking operations:

1. Unpacking an int into byte variables

A 32-bit int variable can be unpacked into four byte (8-bit) variables in the following way:

module example_1;
  initial begin
    static int  value = 32'h8C00A4FF;
    static byte a;
    static byte b;
    static byte c;
    static byte d;
 
    {>>{a, b, c, d}} = value;
 
    $display("a = 0x%h", a);
    $display("b = 0x%h", b);
    $display("c = 0x%h", c);
    $display("d = 0x%h", d);
  end
endmodule

If only three byte variables are used (a, b, and c), the least significant byte of the int variable (0xFF) will be discarded. If five or more variables are used, the extra variables will be set to 0, regardless of their previous value.

2. Unpacking a packed array into an unpacked array

Instead of writing unpacked_array = ‘{ packed_array[2], packed_array[1], packed_array[0] }, a left-to-right ( {>>{}} ) streaming operator can be used to get the same result. The syntax is useful when the array size gets larger.

module example_2;
  initial begin
    static bit [2:0] packed_array = 3'b011;
    static bit       unpacked_array[3];
 
    {>>{unpacked_array}} = packed_array;
 
    foreach (unpacked_array[i])
      $display("unpacked_array[%0d] = %b", i, unpacked_array[i]);
  end
endmodule

3. Unpacking an array of bytes

3.1. Array of bytes into byte variables

The left-to-right streaming operator ( {>>{}} ) can be used to copy items from an array and place them into distinct variables of the same size. You can see it as a shorthand operator for copying array values.

module example_3_1;
  initial begin
    static byte array[4] = '{ 8'h8C, 8'h00, 8'hA4, 8'hFF };
    static byte a;
    static byte b;
    static byte c;
    static byte d;
 
    {>>{a, b, c, d}} = array;
 
    $display("a = 0x%h", a);
    $display("b = 0x%h", b);
    $display("c = 0x%h", c);
    $display("d = 0x%h", d);
  end
endmodule

3.2. Array of bytes into queue of bytes

As above, the left-to-right streaming operator can be used to copy items from an array and place them into a queue. You can see it as a shorthand operator for transforming arrays into queues.

module example_3_2;
  initial begin
    static byte array[4] = '{ 8'h8C, 8'h00, 8'hA4, 8'hFF };
    static byte queue[$];
 
    {>>{queue}} = array;
 
    foreach (queue[i])
      $display("queue[%0d] = 0x%h", i, queue[i]);
  end
endmodule

4. Reversing the elements of a byte array

When you want to change the order of the elements inside an array, right-to-left streaming operator ( {<<{}} ) can be of great help. Below is an example of reversing the order of the elements inside an array. The slice size of the right-to-left streaming operator should be equal to the size of the element’s type (byte in our case).

module example_4;
  initial begin
    static byte array_a[4] = '{ 8'h8C, 8'h00, 8'hA4, 8'hFF };
    static byte array_b[4];
 
    {<<byte{array_b}} = array_a;
 
    foreach (array_b[i])
      $display("array_b[%0d] = 0x%h", i, array_b[i]);
  end
endmodule

If you want to reverse the order of the elements inside an array using a granularity of 2 elements (one element = byte = 8 bits, two elements = shortint = 16 bits), you can use {<<shortint{array_b}} = array_a. This will make array_b equal to ‘{8’hA4, 8’hFF, 8’h8C, 8’h00} .

5. Unpack an array into the fields of a structure

Data from a parallel or serial bus might initially get collected inside an array. The meaning of data can then be revealed by unpacking it into a more abstract level, like a structure. Here is how you can unpack array values into the fields of a structure.

module example_5;
  typedef struct {
    bit [3:0] address;
    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;
 
    {>>{packet.address, packet.data}} = array;
 
    $display("packet address = %b", packet.address);
    $display("packet data    = %b", packet.data);
  end
endmodule

Instead of manually specifying all the fields, the following syntax can be used: {>>{packet}} = array;

6. Unpacking a structure into another structure or a class

Streaming operators can be used to transform a structure of a specific type into a structure of another type. In a similar way as mentioned in the first example, if the structure sizes are different, either trimming or zero-padding will be performed when the assignment is done.

module example_6_1;
  typedef struct {
    bit [3:0] high_nibble;
    bit [3:0] low_nibble;
    bit [4:0] id;
  } layer1_t;
 
  typedef struct {
    bit [7:0] address;
    bit [3:0] data;
    bit       crc;
  } frame_t;
 
  initial begin
    static layer1_t layer1 = '{ 4'b1000, 4'b1100, 5'b11101 };
    static frame_t  frame;
 
    {>>{frame}} = layer1;
 
    $display("frame address = 0x%h", frame.address);
    $display("frame data    = 0x%h", frame.data);
    $display("frame crc     = %b"  , frame.crc );
  end
endmodule

When a class is involved in the unpack operation, the same transform operation using the left-to-right streaming operator ( {>>{}} ) can be applied, but this time the class fields must be explicitly specified.

module example_6_2;
  typedef struct {
    bit [3:0] high_nibble;
    bit [3:0] low_nibble;
    bit [4:0] id;
  } layer1_t;
 
  class frame_t;
    bit [7:0] address;
    bit [3:0] data;
    bit       crc;
  endclass
 
  initial begin
    static layer1_t layer1 = '{ 4'b1000, 4'b1100, 5'b11101 };
    static frame_t frame = new;
 
    {>>{frame.address, frame.data, frame.crc}} = layer1;
 
    $display("frame address = %b", frame.address);
    $display("frame data    = %b", frame.data);
    $display("frame crc     = %b", frame.crc );
  end
endmodule

The picture below illustrates the unpack operation for both code examples.

References

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

That’s all for now! Stay tuned for another tutorial about packing and unpacking using the predefined UVM methods.

Comments

13 Responses

    1. Hi Winston,

      Not really. The left-to-right streaming operator ( {>>{}} ) will insert the leftmost bit of packed_array (bit 2) in the leftmost element of unpacked_array (element 0).

      If you take a close look at the assignment:
      unpacked_array = ‘{ packed_array[2], packed_array[1], packed_array[0] }
      you will notice that this is the case: packed_array[2] is assigned to index 0 of unpacked_array, packed_array[1] is assigned to index 1 of unpacked_array and packed_array[0] is assigned to index 2 of unpacked_array.

      Also check the picture in the second example, as it’s easier to see where each of the bits ends up in the unpacked array.

      Regards,
      Horia

    1. Hello, John.

      We have not analyzed these streaming operators from the synthesis point of view, but only from the verification perspective. Still, your question is of interest and according to this paper, they are synthesizable:
      https://sutherland-hdl.com/papers/2013-SNUG-SV_Synthesizable-SystemVerilog_presentation.pdf
      https://sutherland-hdl.com/papers/2013-SNUG-SV_Synthesizable-SystemVerilog_paper.pdf

      If you experience a different behavior, probably the best thing to do is to contact your synthesis tool vendor.

  1. Hi thanks for this amzing article.
    i have one question about the example 3.2. Array of bytes into queue of shortints.
    i tried on vcs, and found that the results queue has only 2 elements, queue[0]=16’h8c00, queue[1]=16’ha4ff. is there something i misunderstand?

    1. Hi, kr.

      No, you are not misunderstanding. You’ve just found a bug in the code.
      The queue definition is wrong. Instead of:

      static shortint queue[$];

      we should be using

      static byte queue[$];

      I’ve fixed it in the article as well.

      Thank you for pointing this out.
      I’m glad you appreciate this article.

    2. The original code (https://web.archive.org/web/20210506010632/https://www.consulting.amiq.com/2017/06/23/how-to-unpack-data-using-the-systemverilog-streaming-operators/#array_of_bytes_into_queue_of_shortints) was correct and the image was the one that contained a bug: the resulting queue should have been indeed depicted with only two 16-bit elements, as kr noticed, to show that an array of bytes can be unpacked into a queue of a different type (shortint).

  2. I ususlly put the streaming operaton on the right hand side of an assignment, and never seen an issue.
    After reading this I tried in Questa simulation variations of example_1:
    From
    {>>{a, b, c, d}} = value;

    to
    {a, b, c, d} = {>>{value}};
    and
    {a, b, c, d} = {>>8{value}};

    And in both cases I got the same results

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Subscribe to our newsletter

Do you want to be up to date with our latest articles?