UVM practice volume I learning notes 15 - appendix a introduction to SystemVerilog

Posted by SamLiu on Fri, 14 Jan 2022 12:12:12 +0100

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