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.
There are two types of fields in any struct, physical fields and virtual fields. Physical fields have the following characteristics:
They are typically extracted from the design specification.
Their values need to be injected into the DUT.
Virtual fields have the following characteristics:
These fields have no direct meaning in the design specification.
These fields improve controllability and generation of the fields of the struct.
These fields define variations of data items (when subtyping).
These fields are required for checking, debugging etc.
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 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 }; }; '>
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.
scalars
strings
lists and list subtypes (same type but different width)
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.
option | For basic packing, this parameter is one of the following: |
| 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. |
| 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. |
| 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 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.
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.
option | For basic packing, this parameter is one of the following. |
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. | |
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. | |
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 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.