Casting

Jan. 7, 2024 || Item:1.9.Casting

1. Casting

A data type can be changed by using a cast ( ’ ) operation. The expression to be cast must be enclosed in parentheses or within concatenation or replication braces and is self-determined.

int’(2.0 * 3.0)

shortint’{8’hFA,8’hCE}

A positive decimal number as a data type means a number of bits to change the size.

17’(x - 2)

The signedness can also be changed.

signed’(x)

A user-defined type can be used.


mytype’(foo)

The expression inside the cast must be an integral value when changing the size or signing. When changing the size, the signing passes through unchanged. When changing the signing, the size passes through unchanged.

When casting to a predefined type, the prefix of the cast must be the predefined type keyword. When casting to a user-defined type, the prefix of the cast must be the user-defined type identifier.

When a shortreal is converted to an int or to 32 bits, its value is rounded, as in Verilog. Therefore, the conversion can lose information. To convert a shortreal to its underlying bit representation without a loss of information, use $shortrealtobits. To convert from the bit representation of a shortreal value into a shortreal, use $bitstoshortreal.

Structures can be converted to bits preserving the bit pattern, which means they can be converted back to the same value without any loss of information. When unpacked data is converted to the packed representation, the order of the data in the packed representation is such that the first field in the structure occupies the most significant bits. The effect is the same as a concatenation of the data items (struct fields or array elements) in order. The type of the elements in an unpacked structure or array must be valid for a packed representation in order to be cast to any other type, whether packed or unpacked.

An explicit cast between packed types is not required since they are treated as integral values, but a cast can be used by tools to perform stronger type checking.

The following example demonstrates how the $bits attribute is used to obtain the size of a structure in bits, which facilitates conversion of the structure into a packed array:

typedef struct {

bit isfloat;

union { int i; shortreal f;} n; // anonymous type

} tagged_st; // named structure

typedef bit [$bits(tagged_st) - 1 : 0] tagbits; // tagged_st defined above

tagged_st a [7:0];          // unpacked array of structures

tagbits t = tagbits’(a[3]); // convert structure to array of bits

a[4] = tagged_st’(t);       // convert array of bits back to structure

Note that the bit data type loses X values. If these are to be preserved, the logic type should be used instead. The size of a union in bits is the size of its largest member. The size of a logic in  bits is 1.

For compatibility, the Verilog functions $itor, $rtoi, $bitstoreal, $realtobits, $signed, $unsigned can also be used.

2. Dynamic casting $cast

SystemVerilog provides the $cast system task to assign values to variables that might not ordinarily be valid because of differing data type. $cast can be called as either a task or a function.

The syntax for $cast is:

function int $cast( singular dest_var, singular source_exp );

or

task $cast( singular dest_var, singular source_exp );

The dest_var is the variable to which the assignment is made.

The source_exp is the expression that is to be assigned to the destination variable.

Use of $cast as either a task or a function determines how invalid assignments are handled.

When called as a task, $cast attempts to assign the source expression to the destination variable. If the assignment is invalid, a runtime error occurs and the destination variable is left unchanged.

When called as a function, $cast attempts to assign the source expression to the destination variable, and returns 1 if the cast is legal. If the cast fails, the function does not make the assignment and returns 0. When called as a function, no runtime error occurs, and the destination variable is left unchanged.

It’s important to note that $cast performs a run-time check. No type checking is done by the compiler, except to check that the destination variable and source expression are singulars.

For example:

typedef enum { red, green, blue, yellow, white, black } Colors;

Colors col;

$cast( col, 2 + 3 );

This example assigns the expression (5 => black) to the enumerated type. Without $cast, or the static compile-time cast described below, this type of assignment is illegal.

The following example shows how to use the $cast to check if an assignment will succeed:

if ( ! $cast( col, 2 + 8 ) ) // 10: invalid cast

$display( "Error in cast" );

Alternatively, the preceding examples can be cast using a static SystemVerilog cast operation.

For example:

col = Colors’(2 + 3);

However, this is a compile-time cast, i.e, a coercion that always succeeds at run-time, and does not provide for error checking or warn if the expression lies outside the enumeration values.

Allowing both types of casts gives full control to the user. If users know that it is safe to assign certain expressions to an enumerated variable, the faster static compile-time cast can be used. If users need to check if the expression lies within the enumeration values, it is not necessary to write a lengthy switch statement manually, the compiler automatically provides that functionality via the $cast function. By allowing both types of casts, users can control the time/safety trade-offs.

Note: $cast is similar to the dynamic_cast function available in C++, but, $cast allows users to check if the operation will succeed, whereas dynamic_cast always raises a C++ exception.

3. Bit-stream casting

Type casting can also be applied to unpacked arrays and structs. It is thus possible to convert freely between bit-stream types using explicit casts. Types that can be packed into a stream of bits are called bit-stream types. A bit-stream type is a type consisting of the following:

- Any integral, packed, or string type

- Unpacked arrays, structures, or classes of the above types

- Dynamically-sized arrays (dynamic, associative, or queues) of any of the above types

This definition is recursive, so that, for example, a structure containing a queue of int is a bit-stream type.

Assuming A is of bit-stream type source_t and B is of bit-stream type dest_t, it is legal to convert A into B by an explicit cast:

B = dest_t’(A);

The conversion from A of type source_t to B of type dest_t proceeds in two steps:

1) Conversion from source_t to a generic packed value containing the same number of bits as source_t. If source_t contains any 4-state data, the entire packed value is 4-state; otherwise, it is 2-state.

2) Conversion from the generic packed value to dest_t. If the generic packed value is a 4-state type and parts of dest_t designate 2-state types then those parts in dest_t are assigned as if cast to a 2-state.

When a dynamic array, queue, or string is converted to the packed representation, the item at index 0 occupies the most significant bits. When an associative array is converted to the packed representation, items are packed in index-sorted order with the first indexed element occupying the most significant bits.

Both source_t and dest_t can include one or more dynamically sized data in any position (for example, a structure containing a dynamic array followed by a queue of bytes). If the source type, source_t, includes dynamically-sized variables, they are all included in the bit-stream. If the destination type, dest_t, includes unbounded dynamically-sized types, the conversion process is greedy: compute the size of the source_t, subtract the size of the fixed-size data items in the destination, and then adjust the size of the first dynamically sized item in the destination to the remaining size; any remaining dynamically-sized items are left empty.

For the purposes of a bit-stream cast, a string is considered a dynamic array of bytes.

Regardless of whether the destination type contains only fixed-size items or dynamically-sized items, data is extracted into the destination in left-to-right order. It is thus legal to fill a dynamically-sized item with data extracted from the middle of the packed representation.

If both source_t and dest_t are fixed sized unpacked types of different sizes then a cast generates a compile-time error. If source_t or dest_t contain dynamically-sized types then a difference in their sizes will generate an error either at compile time or run time, as soon as it is possible to determine the size mismatch.

For example:

// Illegal conversion from 24-bit struct to int (32 bits) - compile time error struct {bit[7:0] a; shortint b;} a;

int b = int’(a);

// Illegal conversion from 20-bit struct to int (32 bits) - run time error

struct {bit a[$]; shortint b;} a = {{1,2,3,4}, 67};

int b = int’(a);

// Illegal conversion from int (32 bits) to struct dest_t (25 or 33 bits),

// compile time error

typedef struct {byte a[$];

bit b;

} dest_t;

int a;


dest_t b = dest_t’(a);

Bit-stream casting can be used to convert between different aggregate types, such as two structure types, or a structure and an array or queue type. This conversion can be useful to model packet data transmission over serial communication streams. For example, the code below uses bit-stream casting to model a control packet transfer over a data stream:

typedef struct {

shortint address;

reg [3:0] code;

byte command [2];

} Control;

typedef bit Bits [36:1];

Control p;

Bits stream[$];

p = ...                      // initialize control packet

stream = {stream, Bits’(p)}  // append packet to unpacked queue of bits

 

Control q;

q = Control’(stream[0]);              // convert stream back to a Control packet

stream = stream[1:$];        // remove packet from stream

The following example uses bit-stream casting to model a data packet transfer over a byte stream:

typedef struct {

byte length;

shortint address;

byte payload[];

byte chksum;

} Packet;

The above type defines a generic data packet in which the size of the payload field is stored in the length field. Below is a function that randomly initializes the packet and computes the checksum.

function Packet genPkt();

Packet p;

void’( randomize( p.address, p.length, p.payload )

with { p.length > 1 && p.payload.size == p.length } );

p.chksum = p.payload.xor();

return p;

endfunction

The byte stream is modeled using a queue, and a bit-stream cast is used to send the packet over the stream.

typedef byte channel_type[$];

channel_type channel;

channel = {channel, channel_type’(genPkt())};

And the code to receive the packet:

Packet p; int size;

size = channel[0] + 4;

p = Packet’( channel[0 : size - 1] );   // convert stream to Packet

channel = channel[ size, $ ];           // remove packet data from stream





Comments:

Leave a comment: