The when struct member creates a conditional subtype of the current struct type, if a particular field of the struct has a given value. This is called when inheritance, and is one of two techniques e provides for implementing inheritance. The other method of creating subtypes is called like inheritance.
When inheritance is the recommended technique for modeling in e. Like inheritance is more appropriate for procedural testbench programming. Like inheritance is not covered in this book.
The purpose of a when keyword is to create a struct subtype. You can use the when construct to create families of objects, in which multiple subtypes are derived from a common base struct type.
A subtype is a struct type in which specific fields of the base struct have particular values, for example:
If a struct type named "packet" has a field named "kind" that can have a value of "eth" or "atm", then two subtypes of "packet" are "eth packet" and "atm packet".
If the "packet" struct has a boolean field named "good", two subtypes are "FALSE'good packet" and "TRUE'good packet".
Subtypes can also be combinations of fields, such as "eth TRUE'good packet" and "eth FALSE'good packet".
Struct members defined in a when construct can be accessed only in the subtype, not in the base struct. This provides a way to define a subtype that has some struct members in common with the base type and all of its other subtypes, but has other struct members that belong only to the current subtype.
The general syntax for a when construct is as follows:
when struct-subtype base-struct-type {struct-member; ...};
The applications of the when keyword are described in Table 4-8 below.
base-struct-type | The struct type of the current struct (in which the subtype is being created). |
struct-member | Definition of a struct member for the struct subtype. One or more new struct members can be defined for the subtype. |
struct-subtype | A subtype declaration in the form type-qualifier'field-name. The type qualifier is one of the legal values for the field named by field name. If the field name is a boolean field, and its value is TRUE for the subtype, you can omit type qualifier. That is, if "big" is a boolean field, "big" is the same as "TRUE'big". The field name is the name of a field in the base struct type. Only boolean or enumerated fields can be used. If the field type is boolean, the type qualifier must be TRUE or FALSE. If the field type is enumerated, the qualifier must be a value of the enumerated type. If the type qualifier can apply to only one field in the struct, you can omit 'field-name. More than one type-qualifier'field-name combination can be stated, to create a subtype based on more than one field of the base struct type. |
Example 4-7 shows the definition of a struct instance where the "op1" field in the struct definition below can have one of the enumerated "reg_n" type values (REG0, REG1, REG2, or REG3). The "kind" field can have a value of "imm" or "reg", and the "dest" field can have a value of "mm_1" or "reg".
The "REG0'op1" subtype specification in the first when construct creates a subtype of instances in which the "op1" value is "REG0". This subtype has all the "instr" struct fields plus a "print_op1()" method.
The "reg'kind" subtype specification in the second when construct creates a subtype of instances in which the "kind" value is "reg". This subtype also has all the "instr" struct fields plus a "print_kind()" method.
It is necessary to add the "'kind" expression in the second when construct because the "dest" field can also have a value of reg, which means that "reg" is ambiguous without the further specification of the field name. This is the explicit definition of a when construct.
// Example of explicit when construct <' type reg_n : [REG0, REG1, REG2, REG3]; //Define an enumerated type type instr_type: [imm, reg]; //Define an enumerated type type dest_type: [mm_1, reg]; //Define an enumerated type struct instr { // Define a struct %op1: reg_n; //This field is always visible kind: instr_type; //This field is always visible dest: dest_type; //This field is always visible when REG0'op1 instr { // The print_op1() method is visible only // when op1 field value is generated to be // REG0. This is an explicit when construct. print_op1() is { out("instr op1 is REG0"); }; }; when reg'kind instr { // The print_kind() method is visible only // when kind field value is generated to be // reg. This is an explicit when construct. print_kind() is { out("instr kind is reg"); }; }; }; '>
Example 4-8 shows an instance of the "packet" struct that has a field "kind" of either "transmit" or "receive". The when construct creates a "transmit packet" subtype. The "length" field and the print() method apply only to packet instances that have "kind" values of "transmit".
Example of a when construct <' type packet_kind: [transmit, receive]; //Define an enumerated type struct packet { kind: packet_kind; //Define a field of the enumerated type when transmit packet { // When the kind field is "transmit" // only then are the additional length // and the print method visible. // Notice that since there is only one // field kind that can have the value // transmit, there is no need to specify // the field kind explicitly // i.e. when transmit'kind packet{. length: int; //Field only visible when kind == transmit print() is { //Method only visible when kind == transmit out("packet length is: ", length); }; }; }; '>
There are two general rules governing the extensions of when subtypes:
If a struct member is declared in the base struct, it cannot be redeclared in any when subtype, but it can be extended.
With the exception of coverage groups and the events associated with them, any struct member defined in a when subtype does not apply or is unknown in other subtypes, including fields, constraints, events, methods, on, expect, and assume constructs.
A method defined or extended within a when construct is executed in the context of the subtype and can freely access the unique struct members of the subtype with no need for any casting.
When a method is declared in a base type, each extension of the method in a subtype must have the same parameters and return type as the original declaration. In Example 4-9, because do_op() is defined with two parameters in the base type, extending do_op() in the ADD subtype should also have two parameters only. If the number of parameters is different, a load time error will result.
Example that shows how methods can be extended in subtypes Such extensions change the behavior of the methods based on the values of the fields generated. If the method is already defined in the base type, it should have the same number of arguments in the when extension. <' struct operation { opcode: [ADD, ADD3]; op1: uint; op2: uint; do_op(op1: uint, op2: uint): uint is { // Method defined in base // struct result = op1 + op2; }; }; extend operation { when ADD3'opcode operation { //When opcode == ADD3 additional //fields and extensions are added. op3: uint; //New field in extension do_op(op1:uint,op2:uint): uint is also { result = result + 2; }; }; }; '>
However, if a method is not declared in the base type, each definition of the method in a subtype can have different parameters and return type. The variation shown in Example 4-10 below loads without error.
Example when method that is not defined in the base type but is defined in the when subtype <' struct operation { //Base struct, no method definition opcode: [ADD, ADD3]; op1: uint; op2: uint; }; extend operation { when ADD operation { //Define method with 2 arguments do_op(op1: uint, op2: uint): uint is { return op1 + op2; }; }; when ADD3 operation { //Define method with 3 arguments op3: uint; do_op(op1:uint,op2:uint,op3:uint): uint is { return op1 + op2 +op3; }; }; }; '>