From scratch, build a simple UVM verification platform (1)

Foreword:

        This series will build a UVM verification platform from scratch to help some friends who have learned SV and UVM knowledge but have no concept of building a complete verification environment.

UVM pre-basis:

1. UVM basics - factory mechanism, phase mechanism

2. UVM basics - components (driver, monitor, agent...)

3.UVM foundation-TLM communication mechanism (1)

4.UVM foundation-TLM communication mechanism (2)

...still updating

Build a UVM verification platform from scratch:

From scratch, build a simple UVM verification platform (1)

From scratch, build a simple UVM verification platform (2)

Starting from scratch, build a simple UVM verification platform (3)

From scratch, build a simple UVM verification platform (4)

...still updating


Table of contents

Composition of verification platform

DUT writing (Design Under Test)

Driver build

factory mechanism

objection mechanism        


Composition of verification platform

        ① The verification platform must simulate various real usage conditions of the DUT, which means that various incentives must be applied to the DUT, and the incentive function is realized by the driver .

        ② The verification platform must be able to judge whether the behavior of the DUT is in line with expectations based on the output of the DUT. This function is accomplished by the scoreboard (called a checker in SV).

        ③ The verification platform needs to collect the output of the DUT and pass them to the scoreboard . It is the monitor that completes this function.

        ④ The verification platform must be able to give the expected results. Assuming that the DUT is an adder, when 1 is input in its addend and augend respectively, that is, 1+1 is input, the DUT is expected to output 2. When the DUT is calculating the result of 1+1, the verification platform must also complete the same process and calculate 1+1 once. In the verification platform, it is the reference model that completes this process.

        A simple verification platform block diagram is shown in the figure.

DUT writing (Design Under Test)

        The driver is the most basic component of the verification platform and the source of the data flow of the entire verification platform.

Suppose there is a DUT (design under test) described as follows:

module dut(
  input             clk           , 
  input             rstn          ,
  input      [7:0]  data_i        ,
  input             data_i_valid  ,
  output reg [7:0]  data_o        ,
  output reg        data_o_valid
);

always @(posedge clk)begin
  if(!rstn)begin
    data_o       <= 8'd0;
    data_o_valid <= 1'b0;
  end
  else begin
    data_o       <= data_i;
    data_o_valid <= data_i_valid;
  end
end

endmodule 

        As the previous DUT needs to be verified by us, the function of the above design is very simple, input data data_i and its data valid signal data_i_valid, and output it with a beat . data_o_valid is an output data valid signal.

Driver build

        UVM is a library in which almost everything is implemented using classes. Class is one of the greatest inventions in the System verilog object-oriented programming language, and it is the essence of object-oriented.

        The class has functions (function) and tasks (task). Through these functions and tasks, the output stimulation function of the driver, the monitoring function of the monitor, the calculation function of the reference model, and the comparison function of the scoreboard can be completed.

       The three elements of class encapsulation, inheritance and polymorphism . Inheritance is one of the most important characteristics of classes. When we build the verification environment, we must ensure that all components in the verification platform should inherit from the classes in UVM.

        The driver in the UVM verification platform should be derived from uvm_driver, a simple driver can be as follows.

`include "uvm_macros.svh"
import uvm_pkg::*;

class my_driver extends uvm_driver;
  function new(string name = "my_driver", uvm_component parent = null);
    super.new(name, parent);
  endfunction
  extern virtual task main_phase(uvm_phase phase);
endclass

task my_driver::main_phase(uvm_phase phase);
  top_tb.data_i       <= 8'd0;
  top_tb.data_i_valid <= 1'b0;
  while(!top_tb.rstn)
    @(posedge top_tb.clk);
  for(int i = 0; i < 256; i = i+1)begin
    @(posedge top_tb.clk)
    top_tb.data_i <= $urandom_range(0,255);
    top_tb.data_i_valid <= 1'b1;
    `uvm_info("my_driver", "data is drived", UVM_LOW) 
  end
  @(posedge top_tb.clk);
  top_tb.data_i_valid <= 1'b0;
endtask

 

        The function of this driver is to send 256 random data to data_i, and at the same time pull the input data valid signal data_i_valid high, and pull the data_i_valid signal low after the data is sent. There are two points to note in this driver:

        ① The new function of all classes derived from uvm_driver has two parameters , one is the name of string type , and the other is the parent of uvm_component type . These two parameters are essential and are required by uvm_component. Each class derived from uvm_component or its derivative needs to specify two parameters in its new function: name and parent, which is a major feature of the uvm_component class. uvm_driver is a class derived from uvm_component, so it will naturally have two parameters, name and parent.

        ② Almost everything the driver does is done in main_phase . UVM uses phase to manage the operation of the verification platform. These phases are named as xxxx_phase, and each has a parameter of type uvm_phase and name phase. main_phase is a pre-defined task in uvm_driver. So it can be considered almost simply that implementing a driver is equivalent to implementing its main_phase.

        The picture above shows a complete phase process. 

        The uvm_info macro also appeared in the driver (in addition to the uvm_error macro and the uvm_warning macro), which has three parameters . The first parameter is a string , which is used to classify the printed information; the second parameter is also a string . It is the specific information that needs to be printed; the third parameter is the redundancy level , indicating the urgency of this command. For the third point, in the verification platform, some information is very critical, such information can be set to UVM_LOW, and some information can be set to UVM_HIGH if it is optional, and UVM_MEDIUM is in between. By default, UVM only displays UVM_MEDIUM or UVM_LOW information.

        The uvm_info macro is very powerful. It includes the physical file source of the printed information, the logical node information (path index in the UVM tree species), the printing time, the classification organization of the information, and the printed information. Therefore, when building a verification platform, you should try to use the uvm_info macro instead of the display statement.

        After the my_driver class is defined, it needs to be instantiated . There is a difference between the definition of a class and the instantiation of a class. The definition of a class is a class-end block, which defines a class and tells the class what functions it has. The instantiation of the class is to use the new () function to create an instance.

class A;
...
endclass

A a_inst;
a_inst = new();

        After the emulator receives the instruction of new, it will divide a space in the memory. Before dividing, it will first check whether this class has been defined in advance. "Allocate space, and return the pointer of this space to a_inst, and then you can use a_inst to view each member variable in the class, call member functions/tasks, etc. For most classes, it is meaningless if they are only defined but not instantiated; and if they are instantiated directly without definition, the emulator will report an error.

        Instantiate my_driver and finally build the verification platform as follows:        

`timescale 1ns/1ps
`include "uvm_macros.svh" //这是UVM中的一个文件,包含了众多宏定义

import uvm_pkg::*;        //只有导入了这个库,编译器在编译my_driver.sv文件时才会认识其中继承的uvm_driver等类名

`include "my_driver.sv"

module top_tb;

reg clk,rstn;
reg  [7:0] data_i;
reg  data_i_valid;
wire [7:0] data_o;
wire data_o_valid;

dut my_dut(
  .clk            (clk)           ,
  .data_i         (data_i)        ,
  .data_o         (data_o)        ,
  .data_i_valid   (data_i_valid)  ,
  .data_o_valid   (data_o_valid)
);

initial begin
  my_driver drv; // instance
  drv = new("drv", null);
  drv.main_phase(null);
  $finish();
end

initial begin
  clk = 0;
  forever begin
    #100 clk = ~clk;
  end
end

initial begin
  rstn = 1'b0;
  #1000
  rstn = 1'b1;
end

endmodule

Simulation results: 

 It can be seen in the simulation window that data is driven is output 256 times.

factory mechanism

        So far we have implemented the verification operation of using the driver to send incentives to the DUT, but the practical and simple SV can also achieve such a function. Using the characteristics of UVM requires the introduction of the factory mechanism to realize: automatically create an instance of a class and call the function in it (function) and task (task).

Make some modifications on the driver.sv we originally wrote to generate incentives:

`include "uvm_macros.svh"
import uvm_pkg::*;

class my_driver extends uvm_driver;
  `uvm_component_utils(my_driver)  //注册
  function new(string name = "my_driver", uvm_component parent = null);
    super.new(name, parent);
    `uvm_info("my_driver", "new is called", UVM_LOW)
  endfunction
  extern virtual task main_phase(uvm_phase phase);
endclass

task my_driver::main_phase(uvm_phase phase);
  `uvm_info("my_driver", "main phase is called", UVM_LOW);
  top_tb.data_i       <= 8'd0;
  top_tb.data_i_valid <= 1'b0;
  while(!top_tb.rstn)
    @(posedge top_tb.clk);
  for(int i = 0; i < 256; i = i+1)begin
    @(posedge top_tb.clk)
    top_tb.data_i <= $urandom_range(0,255);
    top_tb.data_i_valid <= 1'b1;
    `uvm_info("my_driver", "data is drived", UVM_LOW) 
  end
  @(posedge top_tb.clk);
  top_tb.data_i_valid <= 1'b0;
endtask

 

The difference from the previous driver is that a registration macro is added in the middle of the class definition, uvm_component_utils (my_driver)

        After the factory mechanism is introduced, the functions and tasks in the class can be run automatically, which is very convenient. In the top_tb.sv file, replace the instantiation of my_driver and the call of main_phase with run_test("my_driver")

initial begin
    my_driver drv;
    drv = new("drv", null);
    drv.main_phase(null);
    $finish();
end

//将上述initial替换为下面的initial语句块

initial begin
  run_test("my_driver");
end

        The reason why it runs automatically is that after registering the factory, the run_test statement will automatically create an instance of my_driver and automatically call the main_phase of my_driver . What is passed to run_test is a string , and UVM will create an instance it represents based on this string.

        Note: All classes derived from uvm_component and its derived classes should be registered using the uvm_componet_utils() macro.

        But from the simulation results, we will find that main phase is called is output but data is driven is not output 256 times, and main_phase is a complete task, there is no reason to only execute the first sentence, and the following codes are not executed. This involves the objection mechanism of UVM. 

objection mechanism        

        In the above code, we did not use the finish function to terminate the verification platform, but the verification platform is indeed closed. In UVM, the objection mechanism is used to control the closing of the verification platform.

        In each phase, UVM will check whether an objection has been raised (raise_objection), if so, then wait for the objection to be revoked (drop_objection) and then stop the simulation; if not, end the current phase immediately .

        We did not mention objection in the instance my_driver that automatically created and executed the main_phase using the factory mechanism, but still executed the first statement of my_driver

`uvm_info("my_driver", "main_phase is called", null);

        Does it contradict what we said here, "If the objection is not mentioned, the current phase will end immediately"? In fact, it is not. This involves the concept of a simulation time slice. The emulator must first enter my_driver to detect whether the objection is raised in this instance. At the 0 moment of entering my_driver, the macro definition uvm_info has been executed. At the same time, The emulator does not detect that the objection is raised, and then exits, which does not exclude the execution of uvm_info.

        Add the objection mechanism at the beginning and end of the main_phase of my_driver, raise_objection at the head, and drop_objection at the end of the task.

Modify the code of my_driver.sv to:

`include "uvm_macros.svh"
import uvm_pkg::*;

class my_driver extends uvm_driver;
  `uvm_component_utils(my_driver)  //注册
  function new(string name = "my_driver", uvm_component parent = null);
    super.new(name, parent);
    `uvm_info("my_driver", "new is called", UVM_LOW)
  endfunction
  extern virtual task main_phase(uvm_phase phase);
endclass

task my_driver::main_phase(uvm_phase phase);
  phase.raise_objection(this);
  `uvm_info("my_driver", "main phase is called", UVM_LOW);
  top_tb.data_i       <= 8'd0;
  top_tb.data_i_valid <= 1'b0;
  while(!top_tb.rstn)
    @(posedge top_tb.clk);
  for(int i = 0; i < 256; i = i+1)begin
    @(posedge top_tb.clk)
    top_tb.data_i <= $urandom_range(0,255);
    top_tb.data_i_valid <= 1'b1;
    `uvm_info("my_driver", "data is drived", UVM_LOW) 
  end
  @(posedge top_tb.clk);
  top_tb.data_i_valid <= 1'b0;
  phase.drop_objection(this);
endtask

Before executing the drop_objection statement, the raise_objection statement must be called first, and the two always appear in pairs .

         After adding raise_objection and drop_objection, we will find that "data is driven" is output 256 times as expected.

Another point to mention is that UVM checks whether to mention objection at the moment of entering main_phase. Therefore, objection must be raised immediately after entering main_phase, instead of passing through any statement with delay such as @(posedge clk) or #1, As long as there is a delay statement before raise_objection, the objection will not be detected, which will cause the main_phase to exit immediately.

        So far, our verification platform with only driver has been built. So far we have covered several knowledge points: class inheritance and derivation, factory factory mechanism, phase mechanism, and objection mechanism. Later we will add virtual interface, add transaction, monitor component, agent component, sequence component, etc. on this basis.

Guess you like

Origin blog.csdn.net/qq_57502075/article/details/127218280