SystemVerilog is an object-oriented programming language. Compared with non object-oriented programming language, the most important feature of object-oriented language is that all functions should be implemented in class.
Use of structure
The most commonly used non object oriented programming is function. To implement a function, you must implement the corresponding function. When the function to be realized is simple, the function can easily achieve the goal. For example, to calculate the CRC check value of a string of data streams, although the CRC algorithm is complex, it can be implemented with a function. However, when the functions to be implemented are complex, it will be clumsy to use only functions.
Suppose a zoo wants to implement a simple animal management system in the park, which should have the following functions:
- Count the information of all animals in the park, such as name, date of birth, category (whether they can fly), daily food intake, health, etc.
- Print the information of all animals in the zoo.
To realize these functions, it is not enough to only consider how to write functions, but also how to store these information, that is, consider the data structure. Programming = algorithm + data structure. Therefore, setting a good data structure at the beginning of programming is half the success. In programming languages, structures are generally supported. Taking C language as an example, you can use struct to declare a structure:
struct animal { char name[20]; int birthday;/*example: 20030910*/ char category[20];/*example: bird, non_bird*/ int food_weight; int is_healthy; };
After declaring the structure, you can define the structure variable and take the structure variable as the parameter of the function to realize the above functions:
void print_animal(struct animal * zoo_member){ printf("My name is %s\n", zoo_member->name); printf("My birthday is %d\n", zoo_member->birthday); printf("I am a %s\n", zoo_member->category); printf("I could eat %d gram food one day\n", zoo_member->food_weight); printf("My healthy status is %d\n", zoo_member->is_healthy); } void main() { struct animal members[20]; strcpy(members[0].name, "parrot"); members[0].birthday = 20091021; strcpy(members[0].category, "bird"); members[0].food_weight = 20; members[0].is_healthy = 1; print_animal(&members[0]); }
From struct to class
Structures simply put different types of data together to make their aggregation have a specific meaning. Corresponding to this structure are some function operations. For these functions, they cannot be used without structural variables; For structure variables, without these functions, the structure has no meaning.
For such a close relationship between the two, the creator of object-oriented pioneered the concept of class. Class gathers the structure and its corresponding functions together to become a new form of data organization. There are two components in this new data organization form. One is the data variable from the structure, which is called member variable in the class; The other is from the function corresponding to the structure, which is called the interface of a class:
class animal; string name; int birthday;/*example: 20030910*/ string category;/*example: bird, non_bird*/ int food_weight; int is_healthy; function void print(); $display("My name is %s", name); $display("My birthday is %d", birthday); $display("I am a %s", category); $display("I could eat %d gram food one day", food_weight); $display("My healthy status is %d", is_healthy); endfunction endclass
After a class is defined, it can only be used after instantiation. After instantiation, you can call its functions:
initial begin animal members[20]; members[0] = new(); members[0].name = "parrot"; members[0].birthday = 20091021; members[0].category = "bird"; members[0].food_weight = 20; members[0].is_healthy = 1; members[0].print(); end
The new function is used here. It is a special function. The definition of new does not appear in the class definition, but it can be used directly. In object-oriented programming terms, new is called a constructor. The programming language provides a constructor by default, so you can use it directly without defining it.
Class encapsulation
If you only put structures and functions together, the advantages of that kind are not obvious, and object-oriented programming will not be so popular. The reason for the object-oriented programming process is that classes also have some characteristics. These features are the essence of object-oriented. Generally speaking, classes have three characteristics: encapsulation, inheritance and polymorphism.
In the example in the previous section, all member variables of animal are visible to the outside, so they can be directly assigned by direct reference in the initial statement. This direct reference is dangerous in some cases. When they are accidentally changed, it may cause fatal problems, which is a bit similar to global variables. Because it is globally visible, the value of global variables may be changed by any part of the program, resulting in a series of problems.
To avoid this situation, object-oriented developers have designed the type of private variable (local in SV, which is different from other programming languages). When a variable is set to local type, it will have two characteristics:
- This variable can only be accessed inside the class by the function / task of the class.
- Using direct reference outside the class will prompt an error.
class animal; string name; local int birthday;/*example: 20030910*/ local string category;/*example: bird, non_bird*/ local int food_weight; local int is_healthy; endclass
Since direct reference assignment cannot be performed, an initialization function needs to be defined inside the class to initialize the class:
function void init(string iname, int ibirthday, string icategory, int ifood _weight, int iis_healthy); name = iname; birthday = ibirthday; category = icategory; food_weight = ifood_weight; is_healthy = iis_healthy; endfunction
In addition to member variables being defined as local type, functions / tasks can also be defined as local type. This situation is usually used for some underlying functions. For example, animal has function A, which will call function B. B function will not and should not be called externally. In this case, it can be declared as local type:
local function void B(); ... endfunction
Class inheritance
The second feature of object-oriented programming is inheritance. There are two kinds of animals in the zoo, birds that can fly and reptiles that can't fly. Suppose the zoo has 100 birds and 200 reptiles. When establishing the zoo management system, you need to instantiate 100 animal variables, and the category of these variables is set to bird; At the same time, 200 animal variables are instantiated, and the category of these variables is set to non_bird. Doing the same thing 100 or 200 times is easier to make mistakes.
Considering this situation, the founders of object-oriented programming put forward the concept of inheritance. Analyze the problems to be solved, find out the commonalities, and use these commonalities to build a base class (parent class); On this basis, the problems are classified. Different classifications have their own commonalities, and the commonalities of these classifications are used to construct derived classes (subclasses).
All animals in a zoo can be drawn into the animal class shown in the previous section, and the bird class and non class are derived (inherited) from the animal class_ Bird class:
class bird extends animal; function new(); super.new(); category = "bird"; endfunction endclass class non_bird extends animal; function new(); super.new(); category = "non_bird"; endfunction endclass
When a subclass derives from the parent class, the subclass naturally has all the characteristics of the parent class, and the member variable / member function of the parent class is also the member variable / member function of the subclass. In addition to all the characteristics of the parent class, the child class can also have its own additional member variables and member functions. For example, for bird class, the fly function can be customized:
class bird extends animal; function void fly(); ... endfunction endclass
When talking about encapsulation, we mentioned the local type member variable. If a variable is of type local, it cannot be directly accessed externally. If a member variable in the parent class is of local type, can the child class use these variables? The answer is No. For the parent class, the child class is an "outsider", which is just a special "outsider". If you want to access member variables in the parent class and do not want these member variables to be accessed externally, you can declare these variables as protected types:
class animal; string name; protected int birthday;/*example: 20030910*/ protected string category;/*example: bird, non_bird*/ protected int food_weight; protected int is_healthy; endclass
Similar to local, protected can also be applied to functions / tasks.
Class polymorphism
Polymorphism is one of the most magical features of object-oriented programming, but it is also the most difficult to understand.
Suppose there is a function print in animal_ homehown:
class animal; function void print_hometown(); $display("my hometown is on the earth!"); endfunction endclass
At the same time, in bird and non_ The bird class also has its own print_hometown function:
class bird extends animal; function void print_hometown(); $display("my hometown is in sky!"); endfunction endclass class non_bird extends animal; function void print_hometown(); $display("my hometown is on the land!"); endfunction endclass
Now, there is a name called print_ Function of animal:
function automatic void print_animal(animal p_animal); p_animal.print(); p_animal.print_hometown(); endfunction
print_ The parameter of animal is a pointer of animal type. If a bird is instantiated and passed to print_animal function. This is completely allowed. Because bird is derived from animal, bird is essentially an animal:
initial begin bird members[20]; members[0] = new(); members[0].init("parrot", 20091021, "bird", 20, 1); print_animal(members[0]); end
The result is "my home is on the earth!", The expected result is "my home is in sky!". If you want to get the correct result, print_ Calling print_ in animal function Type conversion before Hometown:
function automatic void print_animal2(animal p_animal); bird p_bird; non_bird p_nbird; p_animal.print(); if($cast(p_bird, p_animal)) p_bird.print_hometown(); else if($cast(p_nbird, p_animal)) p_nbird.print_hometown(); endfunction
If you pass members[0] as a parameter to this function, you can get the expected result. Cast is a type conversion function. From animal to bird or non_bird type conversion is the type conversion from parent class to child class. This type conversion must be completed through cast. The type conversion from subclass to parent class can be completed automatically by the system, such as calling print_ If members[0] is bird, the system will automatically convert it to animal.
But print_animal2's approach is very complex, and the reusability of the code is not high. Now there are only bird and non_bird type. If you add another type, you need to modify this function again. When calling print_animal and print_ When animal2, the members[0] passed to them are of bird type. Is there a method that can automatically call bird's print_ What about the hometown function? The answer to this question is the virtual function.
In animal, bird and non_ Print is defined separately in bird_ Hometown2 function is defined by adding the virtual keyword in front of it:
class animal; virtual function void print_hometown2(); $display("my hometown is on the earth!"); endfunction endclass class bird extends animal; virtual function void print_hometown2(); $display("my hometown is in sky!"); endfunction endclass class non_bird extends animal; virtual function void print_hometown2(); $display("my hometown is on the land!"); endfunction endclass
In print_ This function is called in animal3:
function automatic void print_animal3(animal p_animal); p_animal.print(); p_animal.print_hometown2(); endfunction
After passing members[0] to this function in the initial statement, the printed result is "my home is in sky!", This is the desired result. If a non is instantiated in initial_ Bird and pass it to print_animal3:
initial begin non_bird members[20]; members[0] = new(); members[0].init("tiger", 20091101, "non_bird", 2000, 1); print_animal(members[0]); end
The printed result is "my home is on the land!". In print_animal3 also calls print_hometown2 function, but the output results are different, showing different forms, which is polymorphism. The realization of polymorphism depends on virtual functions; Ordinary functions, such as print_hometown cannot be polymorphic.
randomize and constraint
SV is a language for verification. It is important to verify that some random excitation can be generated. To this end, SV defines the randomize method for all classes:
class animal; bit [10:0] kind; rand bit[5:0] data; rand int addr; endclass initial begin animal aml; aml = new(); assert(aml.randomize()); end
In a class, only fields defined as type rand are randomized when the randomize method is called. data and addr in the definition will be randomized to a random value, and kind will still be the default value of 0 after randomize is called.
Constraint corresponds to randomize, which is a very distinctive and useful function in SV. Without any constraint, after randomization, the data in the above animal is any value from 0 to 'h3F. You can define a constraint to constrain its value:
class animal; rand bit[5:0] data; constraint data_cons{ data > 10; data < 30; } endclass
After the above constraints, when data is random, its value will be between 10 and 30.
In addition to constraining the data when defining a class, you can also constrain the data when calling randomize:
initial begin animal aml; aml = new(); assert(aml.randomize() with {data > 10; data < 30;}); end