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

26 Responses

  1. 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

  2. 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?

    1. 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
      
  3. 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

  4. 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?

    1. 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

    2. 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

    3. 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

    1. 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.

  5. Greetings. Thanks for this great page, i keep coming back to it. Any thoughts on how streams can work with 2D dynamic arrays?

    bit a [][];
    int width;
    int length;
    int filler = 'h1234abcd;
    
    a = new[length]
    foreach(a[i])
      a[i] = new[width]
    

    now i want to fill each element of the array with filler[width-1:0]. So for example if width is 16 and length is 5, the resulting table looks like
    abcd
    abcd
    abcd
    abcd
    abcd

    the ugly way:

    foreach(a[i])
      foreach(a[i][j])
        a[i][j] = filler[j];
    

    this works too

    foreach(a[i])
       a[i] = {<<{filler}};
    

    is there anyway to assign a in a one-liner? I think the idea would be

    a = {<<width{filler}};
    

    but that does not compile because width is a variable.

  6. Greetings. This is a great article.

    But I have a doubt in the last section: 6.) Advanced packing.
    The last sentence says: “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.”

    So, should I just use:

    static packet_t packetBigEndian = {>>{array}};

    for big-endian ordering scheme? How is it big-endian? Can someone please explain?
    Thanks in advance…

    1. Hi Sai,
      The order is big-endian because it’s the way we chose to interpret the data and it refers to the data in the array, not the resulting packet. If the data is adjusted, using that right to left streaming operator, it should return the same result as the first example. In both cases, the packet’s arrays are little-endian.

      static bit [1:0] array_little_endian[] = '{ 2'b10, 2'b01, 2'b11, 2'b00 };
      static bit [0:1] array_big_endian[] = { 2'b01, 2'b10, 2'b00, 2'b11};
      static packet_t  packet;
      packet = {<<4{ {<<2{array_little_endian}} }};
      $display("packet addr = %b", packet.addr);
      $display("packet data = %b", packet.data);
      packet = {>>{array_big_endian}};
      $display("packet addr = %b", packet.addr);
      $display("packet data = %b", packet.data);
      

      If you run the code, you will see that both will return the same result. The first array contains the initial example, and the second contains the data changed to match the new big-endian order.

  7. Thank you for your post!

    I’m using stream operator to inverse bits of a signal. The result is correct but there are warnings when I do compile.
    Do you know about the cause of the below warnings? I also tried adding “1” right before the operator but it has the same message.

    assign ACT_DR_R[9:0] = {<<{FMT_LANE_E[6:1]}}, {<<{FMT_LANE_F[3:0]}} 
    
    Warning-[TRFSO] Typecast required for streaming operator
    "{ << 1{FMT_LANE_E[6:1]}}"
      Stream operands require explicit typecast to be used with other operators
    
    Warning-[TRFSO] Typecast required for streaming operator
    "{ << {FMT_LANE_F[3:0]}}"
      Stream operands require explicit typecast to be used with other operators
    
    1. Hello, LinhNV.

      First of all you did not mention the simulator you are using.
      I also suspect that the assign code is incomplete. You should also have a concatenation operator {,} after the equal sign of the assign.

      I think the error code refers to the two RHS streaming operators.
      They can’t be implicitly concatenated. You need to explicitly cast their result. Either use a cast operator, either use 2 other variables

      Option1:

      module top;
        typedef logic[5:0] my_6_b_t;
        typedef logic[3:0] my_4_b_t;
        
        logic[9:0] c,a,b;
      
        assign c[9:0] = {my_6_b_t'({<<{a[6:1]}}), my_4_b_t'({<<{b[3:0]}})};
      endmodule
      

      Option2

      module top;
        typedef logic[5:0] my_6_b_t;
        typedef logic[3:0] my_4_b_t;
        
        logic[9:0] c,a,b;
        my_6_b_t a_6 = {<<{a[6:1]}};
        my_4_b_t b_4 = {<<{b[3:0]}};
      
        assign c[9:0] = {a_6, b_4};
      endmodule
      
  8. I generally use the bit slicing syntax to create a bus from an array of bytes from my C-DPI return value

    bit[127:0] rx_det_out;
    byte det_out[64];

    for(int i=0; i<64; i++)
    rx_det_out[((i*2)+1)-:2] =det_out[i];

    Is it possible to use a streaming operator to do something equivalent? When I try I get LHS != RHS errors

    rx_det_out = {<<2{{det_out}}};

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?