【IC验证面试常问问题-2】

IC验证面试常问问题

  • 1 SV基础语法类
    • 1.1 Task和Function的区别
    • 1.2 OOP的特性
    • 1.3 在TB中使用interface和clocking blocking的好处
    • 1.4 SV中ref参数传递--引用
    • 1.5 SV类中的this是什么?super是什么?

【博客首发与微信公众号《漫谈芯片与编程》,欢迎专注一下】
本篇博客继续介绍IC验证常问问题;

1 SV基础语法类

1.1 Task和Function的区别

task和function都是用来封装代码块,来定义可重用的代码块;
task:用于定义一个可重用一系列操作的代码块,可能需要耗费时间;
1.可以包含延迟、事件触发和其他复杂的控制结构;
2.可以有输入参数和输出参数,也可以没有参数;
3.可以调用其他task或function.
function:用于定义一个可以执行一系列操作并返回一个值的代码块。不耗时间;
1.通常用于执行一些计算或逻辑操作,并返回结果;
2.可以有输入参数,但不能有输出参数;
3.可以调用其他function,但不能调用task;

// 定义一个 function,返回两个整数的和
function int add(int a, int b);
    return a + b;
endfunction

// 定义一个 task,打印两个整数的和,并等待一段时间
task print_sum_and_wait(int a, int b);
    #10;  // 等待 10 时间单位
    $display("The sum is: %0d", a + b);  // 打印结果
endtask

module tb;
    initial begin
        int result;
        result = add(5, ¾);  // 调用 function 并将结果赋值给 result
        $display("The sum is: %0d", result);  // 输出结果

        print_sum_and_wait(5, ¾);  // 调用 task
        $display("After the task");  // 这条语句会在 task 执行完毕后执行
    end
endmodule

1.2 OOP的特性

面向对象编程(Object-Oriented Programming, OOP)是一种编程范式,它通过“对象”来组织代码,这些对象是数据和对这些数据执行操作的封装。OOP 的核心思想是将现实世界中的实体抽象成程序中的对象,从而提高代码的复用性、模块化和可维护性。这些对象是类的实例,类定义了对象的属性(数据)和行为(方法)。
1.封装:将数据(属性)和操作这些数据的方法(行为)捆绑在一起,形成一个独立的实体(对象)。这样可以隐藏对象的内部实现细节,只通过对象提供的方法来访问和修改数据,提高了代码的安全性和可维护性。
实现机制支持:在SV中提供了class类的封装机制;类的属性和方法可以设置为 public、protected 或 local,从而控制其访问级别。

class Counter;
    local int count;  // 局部变量,外部不可访问

    function new(int init_value);
        count = init_value;
    endfunction

    function void increment();
        count++;
    endfunction

    function int get_count();
        return count;
    endfunction
endclass

2.继承:允许一个类(子类)继承另一个类(父类)的属性和方法。这样可以重用代码,减少重复,并且可以构建类的层次结构,使得代码更加清晰和易于理解。派生类可以添加新的属性和方法,或者重写基类的方法。
实现机制支持:使用 extends 关键字来实现继承;

class Animal;
    virtual function void make_sound();
        $display("Some generic sound");
    endfunction
endclass

class Dog extends Animal;
    function void make_sound();
        $display("Woof woof!");
    endfunction
endclass

module tb;
    initial begin
        Animal a = new();
        Dog d = new();
        
        a.make_sound();  // 输出 "Some generic sound"
        d.make_sound();  // 输出 "Woof woof!"
    end
endmodule

3.多态:同一个方法调用可以有不同的行为实现,允许不同类的对象通过相同的接口调用;这通常通过继承和接口实现来完成,具体是方法重载(overloading)和方法重写(overriding)来实现。多态使得代码更加灵活,可以根据对象的实际类型来执行不同的操作。
实现机制支持:在 SystemVerilog 中,多态通过虚方法(virtual method)和动态绑定(dynamic binding)来实现;

class Shape;
    virtual function real area();
        return 0.0;
    endfunction
endclass

class Circle extends Shape;
    real radius;

    function new(real r);
        radius = r;
    endfunction

    function real area();
        return 3.14 * radius * radius;
    endfunction
endclass

class Rectangle extends Shape;
    real width, height;

    function new(real w, real h);
        width = w;
        height = h;
    endfunction

    function real area();
        return width * height;
    endfunction
endclass

module tb;
    initial begin
        Shape s1 = new();
        Shape s2 = new Circle(5.0);
        Shape s3 = new Rectangle(4.0, 6.0);

        $display("Area of shape 1: %f", s1.area());  // 输出 0.0
        $display("Area of shape 2: %f", s2.area());  // 输出 78.500000
        $display("Area of shape 3: %f", s3.area());  // 输出 24.000000
    end
endmodule

4.抽象:忽略对象的复杂细节,只关注其基本特征和功能。抽象类和接口是实现抽象的两种方式,它们定义了一组方法的签名,但不提供具体实现,由子类或实现类来提供具体实现。
实现机制支持:在SV中,使用纯虚方法(pure virtual method)来实现抽象。

virtual class Shape;
    pure virtual function real area();
endclass

class Circle extends Shape;
    real radius;

    function new(real r);
        radius = r;
    endfunction

    function real area();
        return 3.14 * radius * radius;
    endfunction
endclass

class Rectangle extends Shape;
    real width, height;

    function new(real w, real h);
        width = w;
        height = h;
    endfunction

    function real area();
        return width * height;
    endfunction
endclass

module tb;
    initial begin
        Shape s1 = new Circle(5.0);
        Shape s2 = new Rectangle(4.0, 6.0);

        $display("Area of shape ¼: %f", s1.area());  // 输出 78.500000
        $display("Area of shape 2: %f", s2.area());  // 输出 24.000000
    end
endmodule

总结:
封装:将数据和操作数据的方法绑定在一起,隐藏内部实现细节。
继承:创建新类时继承现有类的属性和方法,提高代码复用性。
多态:不同类的对象通过相同的接口调用,表现出不同的行为,提高代码灵活性。
抽象:从具体实例中提取共同特征和行为,形成抽象概念,提高代码的可读性和可维护性。

1.3 在TB中使用interface和clocking blocking的好处

  • Interface提供了一种模块化的方式来定义信号和它们的行为。这使得TB的设计更加模块化,易于维护和重用;
  • Clocking blocking是interface的一个特性,它允许在时钟边沿同步地驱动和采样信号,这有助于确保信号的时序正确性。避免TB与 DUT的接口竞争,减少我们由于信号竞争导致的错误。采样提前,驱动落后,保证信号不会出现竞争。
    接口既可以在硬件世界(module)中使用,又可以在软件世界(class)中使用,interface作为SV中唯—的硬件和软件环境的媒介交互,解耦了硬件与软件;接口由于可以例化的特性, 使得对于多组相同的总线, 在例化和使用时变得更加灵活,不仅使得代码变得简洁, 也更易于验证环境的管理和维护。
interface my_interface(input logic clk, input logic rst_n);
    logic [7:0] data;
    logic valid;

    // 时钟块
    clocking cb @(posedge clk);
        output data, valid;
    endclocking

    // 时序断言
    property data_valid;
        @(posedge clk) disable iff (!rst_n) valid |-> data inside {8'h00, 8'hFF};
    endproperty
    assert property (data_valid) else $error("Data valid assertion failed");

endinterface

module tb;
    logic clk, rst_n;
    my_interface intf(clk, rst_n);

    // 时钟生成
    always #5 clk = ~clk;

    initial begin
        clk = 0;
        rst_n = 0;
        #10 rst_n = 1;

        // 使用时钟块驱动信号
        intf.cb.data <= ˜h55;
        intf.cb.valid <= 1;
        @(posedge clk);

        intf.cb.data <= ˜hAA;
        intf.cb.valid <= 0;
        @(posedge clk);

        $finish;
    end
endmodule

1.4 SV中ref参数传递–引用

ref参数传递:是一种将参数作为引用传递给函数或任务的方式。这意味着函数或任务内部对参数的修改会反映到函数或任务外部的原始变量上–操作同一块内存;
主要好处有:
1.修改原始数据:使用ref参数传递,函数或任务可以修改传入的参数值,如果不希望子程序改变数组的值,可以使用const ref类型。
2.减少内存使用:ref参数传递避免了复制大型数据结构的需要,因为参数是通过引用传递的,而不是通过值传递的。这可以减少内存使用和提高性能。
3.简化代码:避免返回值处理和额外的变量声明;

typedef struct {
    int id;
    string name;
} person;

module tb;
    initial begin
        person p;
        p.id = 1;
        p.name = "Alice";

        $display("Before: ID = %0d, Name = %s", p.id, p.name);
        modify_person(p);
        $display("After: ID = %0d, Name = %s", p.id, p.name);
    end

    // 使用 ref 参数修改结构体成员
    function void modify_person(ref person p);
        p.id = 2;
        p.name = "Bob";
    endfunction
endmodule

1.5 SV类中的this是什么?super是什么?

this:这个指针指向当前类的实例。它允许你在类的方法内部引用当前实例的成员,包括变量和方法。使用this可以避免在类的内部方法中对成员的引用产生歧义,特别是当方法的参数或局部变量与类的成员具有相同的名称时。

super:这个指针指向当前类的父类实例。它允许你在子类中调用父类的方法或访问父类的成员。使用super可以确保在子类中调用的是父类的方法,而不是子类中同名的方法。这在实现继承和方法重写时非常有用。

class Animal;
    string type;

    // 构造函数
    function new(string t);
        type = t;
    endfunction
   
    // 方法
    function void display();
        $display("Name: %s, Age: %0d", this.name, this.age);
    endfunction

    // 方法
    virtual function void make_sound();
        $display("Some generic sound");
    endfunction
endclass

class Dog extends Animal;
    // 构造函数
    function new(string t);
        super.new(t);  // 调用父类的构造函数
    endfunction

    // 重写父类的方法
    function void make_sound();
        super.make_sound();  // 调用父类的方法
        $display("Woof woof!");
    endfunction
endclass

module tb;
    initial begin
        Dog d = new("Canine");
        d.make_sound();  // 输出: Some generic sound
                         //       Woof woof!
    end
endmodule

语法类除了文字介绍外,看示例代码帮助理解;
【REF】
1.https://blog.****.net/graymount/article/details/121391722
2.https://www.nowcoder.com/discuss/445245619951734784

上一篇:[C++ 核心编程]笔记 4.4.1 全局函数做友元


下一篇:亚马逊运营如何利用Pinterest推广?