Previous Section Next Section

9.1 Packing and Unpacking

A DUT requires data on its input HDL signals. You can drive scalar fields of structs directly on to HDL signals. However, a lot of the verification engineer's time is often spent in tracking the bit fields for a specific protocol.

An ideal scenario is one in which the verification engineer works with abstract structs at the input and the output and there is an automatic way to convert these abstract struct instances to a list of bits and bytes. These converted bits or bytes are then applied to the HDL signals of the DUT input.

The e language provides the pack() and unpack() mechanism to do this conversion. However, in order to understand these conversions, it is necessary to introduce the concept of physical fields.

9.1.1 Physical Fields

There are two types of fields in any struct, physical fields and virtual fields. Physical fields have the following characteristics:

Virtual fields have the following characteristics:

The e language syntax provides a way of tagging physical fields by preceding these fields with a % symbol. Physical fields are significant only for the pack() and the unpack() method. Otherwise, their behavior is identical to virtual fields. In other words, the difference between physical and virtual fields is limited to their behavior with the pack() and the unpack() method. Example 9-1 shows the definition of physical fields.

Example 9-1 Physical Fields
Example that shows the difference between virtual fields and
physical fields. Anything that is not tagged with a % symbol is
a virtual field.
<'
struct my_packet{
    atm: bool; //Virtual field
    type: [short, medium, long]; //Virtual field
    %addr:  int(bits:4); //Physical field
    %len: int(bits:10); //Physical field
    init() is also {
        addr = 3;
        len = 15;
    };
    keep type == short => len in [5..10]; //Virtual fields are used
                                          //to control generation
    when TRUE'atm my_packet { //Virtual fields are used for subtypes
       %trailer: int(bits:8); //Add an extra physical field
    };
};
'>

9.1.2 Packing

Packing[1] is commonly used to prepare high-level e data into a form that can be applied to a DUT. Packing performs concatenation of items, including items in a list or fields in a struct, in the order specified by the pack options parameter and returns a list of bits, bytes, or any uint/int size. The return value is automatically cast to its assigned item. This method also performs type conversion between any of the following:

[1] The pack() method is used more frequently in driving stimulus than in checking. However, this is the most relevant place to introduce this concept.

The syntax for the pack() method is as follows:

pack(option:pack option, item: exp, ...): list of bit;

Table 9-1 shows the components of a pack() method call.

Table 9-1. Components of pack() Method

option

For basic packing, this parameter is one of the following:

packing.high

Places the least significant bit of the last physical field declared or the highest list item at index [0] in the resulting list of bit. The most significant bit of the first physical field or lowest list item is placed at the highest index in the resulting list of bit.

packing.low

Places the least significant bit of the first physical field declared or lowest list item at index [0] in the resulting list of bit. The most significant bit of the last physical field or highest list item is placed at the highest index in the resulting list of bit.

NULL

If NULL is specified, the global default is used. This global default is set initially to packing.low.

item

A legal e expression that is a path to a scalar or a compound data item, such as a struct, field, list, or variable.

Example 9-2 shows the usage of the pack() method.

Example 9-2 Usage of pack() Method
Example shows the use of the pack() method
to pack an entire struct or a list of fields.
When packing an entire struct, the physical
fields are picked up in the order in which they
are defined. Packing a list of fields performs
a concatenation of those fields.

There are two options most commonly used with
the pack() method:
- packing.high: Starts filling the fields into higher
order positions.
- packing.low: Starts filling the fields into lower
order positions.
<'
struct packet {
    %dest      : int (bits : 8); //Physical Field
         keep dest == 0x55; //Constraint
    %version   : int (bits : 2); //Physical Field
         keep version == 0x0; //Constraint
    %type_pkt      : uint (bits : 6); //Physical Field
         keep type_pkt == 0x3f; //Constraint
    %payload : list of int(bits:4); //Physical Field
         keep payload.size() == 2; //Constraint
         keep for each in payload { //Constraint
              it == index;
          };

    //Fields to hold the packed data
    //We could also declare these as list of byte
    !data_packed_low : list of bit;
    !data_packed_high : list of bit;
    !data_packed_fields : list of bit;

    post_generate() is also { //Call the pack() method here
       //Entire struct packet is packed with packing.low
        data_packed_low = pack(packing.low, me);//Using packing.low
        //Entire struct packet is packed with packing.high
        data_packed_high = pack(packing.high, me);//Using packing.high
        //Individual fields of the packet are packed with packing.low
        data_packed_order = pack(packing.low, dest, version, type,
                                 payload);//Using packing.low
    };
};
'>

Figure 9-1 shows the order in which the fields are packed for the three possible scenarios listed above. The resulting list of bits is 24 bits wide.

Figure 9-1. Order of Packing

graphics/09fig01.gif

9.1.3 Unpacking

The unpack() method does exactly the opposite of packing. Unpacking is commonly used to convert a raw bit or byte stream into high level data by storing the bits of the value expression into the target expressions. Unpacking operates on scalar or compound (struct, list) data items. This is useful for data checking. The syntax for the unpack() method is as follows:

unpack(option: pack option, value: exp, target1: exp [, target2: exp, ...];

Table 9-2 shows the components of an unpack() method call.

Table 9-2. Components of unpack() Method

option

For basic packing, this parameter is one of the following.

packing.high

Places the most significant bit of the list of bit at the most significant bit of the first field or lowest list item. The least significant bit of the list of bit is placed into the least significant bit of the last field or highest list item.

packing.low

Places the least significant bit of the list of bit into the least significant bit of the first field or lowest list item. The most significant bit of the list of bit is placed at the most significant bit of the last field or highest list item.

NULL

If NULL is specified, the global default is used. This global default is set initially to packing.low.

value

A scalar expression or list of scalars that provides a value that is to be unpacked.

target1, target2

One or more expressions separated by commas. Each expression is a path to a scalar or a compound data item, such as a struct, field, list, or variable.

Example 9-3 shows the usage of the unpack() method.

Example 9-3 Usage of unpack() Method
Example shows the usage of the unpack() method
to unpack a list of bits or bytes into a destination format.
There are two options most commonly used with
the unpack() method:
- packing.high: Starts filling the fields into higher
order positions.
- packing.low: Starts filling the fields into lower
order positions.

<'
struct instruction { //Define target struct
    %opcode    : uint (bits : 3); //Target struct field
    %operand   : uint (bits : 5); //Target struct field
    %address   : uint (bits : 8); //Target struct field
};

extend sys {
    post_generate() is also {
        var inst : instruction; //Variable of struct instruction
        var packed_data: list of bit; //Source list of bit
        packed_data = {1;1;1;1;0;0;0;0;1;0;0;1;1;0;0;1};
        //This is equivalent to 1001_1001_0000_1111 with
        //1111 being the least significant nibble

        unpack(packing.high, packed_data, inst);
        //This function unpacks as follows
        //opcode == 100
        //operand == 1_1001
        //opcode == 0000_1111
        //packed_data is the source list of bytes
        //inst is the target struct instance
    };
};
'>

Figure 9-2 shows the order in which the source list packed_data is unpacked onto the fields of the variable inst.

Figure 9-2. Order of Unpacking

graphics/09fig02.gif

Previous Section Next Section