SV学习笔记(七)

类型转换

写在前面

  • 类型转换可以分为 静态转换和动态转换 。
  • 静态转换即需要在转换的表达式前 加上单引号 即可,该方式并不会对转换值做检查。如果发生转换失败,我们也无从得知。
  • 动态转换即需要使用 系统函数$cast(tgt, src) 做转换。
  • 静态转换和动态转换均需要操作符号或者系统函数介入,统称为 显式转换 。
  • 不需要显式操作的转换,我们称之为 隐式转换 。例如赋值语句右侧是4位的矢量,而左侧是5位的矢量,隐式转换会先做位宽扩展(隐式),然后再做赋值。

动态转换

  • 当我们使用类的时候, 类句柄的向下转换 ,即从父类句柄转换为子类句柄时, 需要 使用$cast() 函数 进行转换, 否则会出现编译错误,这一步也是编译器的保护措施,防止用户出现错误的赋值。
  • 如果将子类句柄赋值给父类句柄时,编译器则认为赋值是合法的,但分别 利用子类句柄和父类句柄调用相同对象的成员时,将可能有不同的表现 。

子类句柄赋值于父类句柄

class Transaction;
    rand bit[31:0] src;
    function void display(input string prefix="");
        $display("%s Transaction: src=%0d", prefix, src);
    endfunction
endclass

class BadTr extends Transaction;
    bit bad_crc;
    function void display(input string prefix="");
        $display("%s BadTr: bad_crc=%b", prefix, bad_crc);
        super.display(prefix);
    endfunction
endclass

Transaction tr;
BadTr bad, bad2;

Transaction tr;
BadTr bad;
bad=new() ; //构建BadTr扩展对象
tr=bad; //基类句柄指向扩展对象
$display(tr.src) ; //显示基类对象的变量成员
tr.display() ; //??
  • tr.display() ; 调用的是父类的display。

父类句柄转换为子类句柄

tr=new() ; //创建一个父类对象
bad=tr; //ERROR:编译错误,即便使用cast进行转换,虽然解决编译错误,但是返回值为0,bad仍为空句柄。
$display(bad.bad_crc) ; //bad_crc成员不在父类对象中
  • 将一个父类句柄赋值给一个子类句柄并不总是非法的。
  • 但是SV编译器对这种直接赋值的做法是禁止的,也就是说无论父类句柄是否真正指向了一个子类对象,赋值给子类句柄时,编译(静态)都将出现错误。
  • 因此需要$cast(tgt, src) 来实现句柄类型的动态转换。
  • $cast(tgt, src) 会检查句柄所指向的对象类型, 而不仅仅检查句柄本身。
  • 一旦源对象跟目的句柄是同一类型,或者是目的句柄的扩展类,$cast() 函数执行即会成功, 返回1, 否贝返回0。

bad=new(); //创建Bad Tr子对象
tr=bad;   //父类句柄指向子类对象
//动态类型转换, 检查tr的源对象是否是bad 2类型或者其子类
//如果转换失败,将报告错误信息
if(!$cast(bad2, tr))
    $display("cannot assign tr to bad2");

$display(bad2.bad_crc); //bad 2指向的对象包含band_src成员
bad2.display() ; //??
  • bad2.display() ; 调用子类的方法。

虚方法

写在前面

class basic_test;
    int fin;
    int def = 100;
    function new() ;
        $display("basic_test::new") ;
    endfunction
    task test() ;
        $display("basic_test::test") ;
    endtask
endclass

class test_wr extends basic_test;
    int def = 200;
    function new() ;
        super.new() ;
        $display("test_wr::new") ;
    endfunction
    task test() ;
        super.test() ;
        $display("test_wr::test") ;
    endtask
endclass
  • 类的继承是从 继承成员变量和成员方法 两个方面。
  • 从例码中可以看到test_wr类和test_rd类分别继承了basic_test类的成员变量以及成员方法。
  • 除了介绍的类的封装和继承,关于类的 多态性(polymorphism) 也是必须关注的。
  • 正是由于类的多态性,使得用户在设计和实现类时, 不需要担心句柄指向的对象类型是父类还是子类,只要通过虚方法,就可以实现动态绑定(dynamic binding) , 或者在SV中称之为动态方法查找(dynamic method lookup) 。

非虚函数的调用

basic_test t;
test_wr wr;
initial begin
    wr=new() ;
    t=wr;
    $display("wr test starts") ;
    wr.test() ;
    $display("wr test ends") ;
    $display("t test starts") ;
    t.test() ;
    $display("t test ends") ;
end

//输出结果:
// wr test starts
// basic_test::test
// test_wr::test
// wr test ends
// t test starts
// basic_test::test
// t test ends
  • 首先, 在执行wr.test() 时, 由于wr类型为test_wr, 则索引到的test() 应该为test_wr类的方法test。
  • 同时, 由于在test_wr::test中显式调用了super.test() , 则会先执行basic_test::test, 然后再执行test_wr::test中其余的代码。
  • 这里值得注意的是, 默认情况下, 子类覆盖(override) 的方法并不会继承父类同名的方法, 而只有通过super.method()的方式显式执行,才会达到继承父类方法的效果,初学SV的用户容易在这里混淆方法覆盖和类继承的概念。
  • 当wr对象的句柄传递给t后, 由于t本身是basic_test类, 所以,在执行t.test时, t只会搜寻basic_test::test方法。
  • (多态的问题是什么?在句柄传递中,子类句柄赋值给父类句柄,调用同名函数时,子类句柄调用的是子类函数,父类句柄调用的是父类函数,即便把子类句柄赋值给了父类句柄,也无法通过父类句柄调用子类函数)

虚函数的调用

  • 我们将已经在编译阶段就可以确定下来调用方法所处作用域的方式称之为静态绑定(static binding) , 而与之相对的是动态绑定。
  • 动态绑定指的是,在调用方法时,会 在运行时来确定句柄指向对象的类型,再动态指向应该调用的方法
  • 为了实现动态绑定, 我们将basic_test::test定义为虚方法。
class basic_test;
    ...
    virtual task test(stm_ini ini) ;
        $display("basic_test::test") ;
    endtask
    ...
endclass

//wr test starts只做了这么一个改动以后,我们重复运行之前的测试代码,可以看到运行结果变为:
// wr test starts
// basic_test::test
// test_wr::test
// wr test ends
// t test start.s
// basic_test::test
// test_wr::test
// t test ends
  • 由于声明了basic_test::test为虚方法, 系统在执行t.test时,会检查t所指向对象的类型为test_wr类, 进而调用test_wr::test。
  • 于是, 输出结果与调用wr.test一致。
  • 我们就可以通过虚方法的使用来实现类成员方法调用时的动态查找,用户无需担心使用的是父类句柄还是子类句柄,因为最终都会实现动态方法查找,执行正确的方法。

虚方法的建议

  • 在为父类定义方法时,如果该方法 日后可能会被覆盖或者继承 ,那么应该声明为虚方法。
  • 虚方法如果要定义,应该 尽量定义在底层父类中 。这是因为如果virtual是声明在类继承关系的中间层类中, 那么只有从该中间类到其子类的调用链中会遵循动态查找,而最底层类到该中间类的方法调用仍然会遵循静态查找。
  • 虚方法 通过virtual声明, 只需要声明一次即可 。例如上面代码中,只需要将basic_test::test声明为virtual, 而 其子类则无需再次声明 ,当然再次声明来表明该方法的特性也是可以的。
  • 方法的继承也需要 遵循相同的参数和返回类型 ,否则,子类定义的方法须归为同名不同参的其它方法。

为什么使用虚方法

  • 首先:当 父类指针指向子类对象 ,如果不将test函数声明为虚函数,最终调用的是父类test函数;如果将test函数声明为虚函数,那么调用的是子类test函数。
  • 为什么要用父类指针指向子类对象呢?用子类指针指向子类对象不好吗?

虽然这样说,但是实际开发过程中不是这样的,当我们使用一些类库、框架的时候,这些类库、框架是事先就写好的。我们在使用的时候不能直接修改类库的源码,我们只能派生类库中的类来覆盖一些成员函数以实现我们的功能,但这些成员函数有的是由框架调用的。这种情况下,用虚函数是很好的办法。

NND,这不就是C++吗?

对象拷贝

写在前面

  • 对于拷贝(copy) , 对象的拷贝要比其它SV的变量类型都让人“当心”。
  • 因为就SV普通的变量拷贝而言,只需要通过赋值操作符“=”就足够了。
  • 而对象的拷贝则无法通过“=”来实现,因为这一操作是句柄的赋值,而不是对象的拷贝。
test_wr h;
initial begin
    wr = new();
    h = wr;
    $display("wr.def=%Od", wr.def);
    $display("h.def=%0d", h.def);
    h.def = 300;
    $display("wr.def=%Od", wr.def);
    $display("h.def=%0d", h.def);
end
//输出结果:
// wr.def = 200
// h.def = 200
// wr.def = 300
// h.def = 300

  • 在h=wr之后,由于是句柄的赋值,所以h.def=300的操作, 实际上是对这两个句柄指向的共同对象做的成员变量赋值。所以,从最终打印的结果可以看出,wr.def与h.def的值相同。
  • 那么如果要拷贝对象,指的是首先创建一个新的对象(开辟新的空间),再将目标对象的成员变量值拷贝给新对象的成员,这就使得新对象与目标对象的成员变量数值保持一致,即完成了对象的拷贝(成员变量的拷贝)。
  • 初学者需要区别句柄拷贝与对象拷贝的区别。

软件来说,我们可能称呼的是值拷贝与引用拷贝

class basic_test; //父类
    ...
    virtual function void copy_data(basic_test t) ;
        t.def=def;
        t.fin=fin;
    endfunction
    virtual function basic_test copy() ;
        basic_test t=new(0) ; //创建父类对象
        copy_data(t) ;
        return t;
    endfunction
endclass


class test_wr extends basic_test; //子类
    ...
    function void copy_data(basic_test t) ;
        test_wr h;
        super.copy_data(t) ;
        $cast(h, t); //关键代码,句柄转化
        h.def=def;
    endfunction
    function basic_test copy() ;
        test_wr t=new() ; //创建子类对象
        copy_data(t) ;
        return t;
    endfunction
endclass
module tb;
    ....
    test_wr wr; //子类
    test_wr h; //子类
    initial begin
        wr=new() ;
        $cast(h, wr.copy()); //copy会返回父类句柄,所以这里将父类句柄转化为子类句柄
        $display("wr.def=%0d", wr.def);
        $display("h.def=%0d", h.def);
        h.def=300;
        $display("wr.def=%0d", wr.def);
        $display("h.def=%0d", h.def);
    end
    ...
endmodule

//输出结果:
// wr.def=200
// h.def=200
// wr.def=200
// h.def=300

赋值和拷贝

声明变量和创建对象是两个过程,也可以一步完成。

Packet pl;
p1=new;

如果将p1赋值给另外一个变量p2,那么依然只有一个对象,只是指向这个对象的句柄有p1和p2.

以下这种方式表示p1和p2代表两个不同的对象。

在创建p2对象时, 将从p 1拷贝其成员变量例如integer、string和句柄等, 该种拷贝方式称为浅拷贝(shallowcopy) 。

Packet pl;
Packet p2;
p1=new;
p2=new p1;

总结

  • 将成员拷贝函数copy_data() 和新对象生成函数copy() 分为两个方法,这样使得子类继承和方法复用较为容易。
  • 为了保证父类和子类的成员均可以完成拷贝,将拷贝方法声明为虚方法,且遵循只拷贝该类的域成员的原则,父类的成员拷贝应由父类的拷贝方法完成。
  • 在实现copy_data() 过程中应该注意句柄的类型转换, 保证转换后的句柄可以访问类成员变量。

回调函数

写在前面

  • 理想的验证环境是在被移植做水平复用或者垂直复用时,应当尽可能少地修改模块验证环境本身,只在外部做少量的配置,或者定制化修改就可以嵌入到新的环境中。
  • 要做到这一点,一方面我们可以通过顶层环境的配置对象自顶向下进行配置参数传递,另外一方面我们可以在测试程序不修改原始类的情况下注入新的代码。
  • 例如, 当我们需要修改stimulator的行为时, 有两种选择, 一个是修改父类,但针对父类的会传播到其它子类;另外一个选择是,在父类定义方法时,预留回调函数入口,使得在继承的子类中填充回调函数,就可以完成对父类方法的修改。

实例

  • 示意:
virtual class Driver_cbs; //Driver回调虚类
    virtual task pre_tx(ref Transaction tr, ref bit drop) ;
        //默认不做操作
    endtask
    virtual task post_tx(ref Transaction tr) ;
        //默认不做操作
    endtask
endclass
  • 实例:
class Driver;
    Driver_ebs cbs[$];
    task run() ;
        bit drop;
        Transaction tr;
        forever begin
            drop=0;
            agt2drv.get(tr) ;
            foreach(ebs[il) cbs[il.pre_tx(tr, drop) ;
            if(drop) continue;
            transmit(tr) ;
            foreach(cbs[il) cbs[i] .post_tx(tr) ;
        end
    endtask
endclass

class Driver_cbs_drop extends Driver_abs;
    virtual task pre_tx(ref Transaction tr, ref bit drop) ;
        //1/100的传输事务丢弃概率
        drop=($urandom range(0, 99) ==0) ;
    endtask
endclass

program automatic test;
    Environment env;
    initial begin
        env=new() ;
        env.gen_cfg() ;
        env.build() ;
        begin//创建回调对象井且植入driver
            Driver_abs_drop dcd=new() ;
            env.drv.cbs.push_back(dcd) ;
        end
        env.run() ;
        env.wrap_up();
    end
endprogram

完成回调函数功能需要三步:

  • 预留回调函数入口
  • 定义回调的类和回调函数
  • 例化和添加回调的类

参数化类

写在前面

  • 参数化的使用是为了提高代码的复用率。
  • 无论是设计还是验证,如果代码会被更多的人使用或者被更多的项目所采用,那么就需要考虑使用参数来提高复用率。
  • 参数的使用越合理,后期维护的成本就会相应降低。
  • 在硬件设计中,参数往往是整型,例如端口数目或者位宽。在验证环境中,参数的使用更加灵活,可以使用各种类型来做类定义时的参数。
  • 在SV中,可以为类增加若干个数据类型参数,并在声明类句柄的时候指定类型。
  • SV的类参数化近似于C++中的模板。

实现一个简化的mailbox

class mailbox;
    local int queue[$] ;
    task put(input int i) ;
        queue.push_back(i) ;
    endtask

    task get(ref int o) ;
        wait(queue.size() >0) ;
        o=queue·pop_front() ;
    endtask

    task peek(ref int o) ;
        wait(queue.size() >0) ;
        o=queue[0] ;
    endtask
endclass
  • 这个简化的mailbox的问题在于它只能用于操作整数类型。
  • 如果要存储real类型, 或者某一个类的句柄,那么就得复制该类,然后将数据类型由int类型转换为real类型或者某一个类的类型。
  • 这样将导致类的快速增长,而且是重复代码的大规模增长,代码维护将变得冗长乏味而且还容易出错。
class mailbox #(type T=int) ;
    local T queue[$] ;

    task put(input T i) ;
        queue.push_back(i) ;
    endtask

    task get(ref T o) ;
        wait(queue.size() >0) ;
        o=queue.pop_front() ;
    endtask

    task peek(ref T o) ;
        wait(queue.size() >0) ;
        o=queue[Ol;
    endtask
endclass

initial begin
    real o;
    mailbox #(real) mb; //创建一个存储real类型的mailbox
    mb=new() ;
    for(inti=0; i<5; i++)
        mb.put(i*2.0) ; //将real值存储入mb
    for(inti=0; i<5; i++)
        mb.get(o) ; //从mb取出real值
end
  • 在类定义时添加参数#(typeT=int) , 这表示后期类在声明变量时如果不指定参数类型, 则默认采用int类型。
  • 将原代码int用参数T来代替。
  • 参数化的类将可以在后期例化时使用不同的参数,以此来存储不同的数据类型。

参考资料

  • Wenhui’s Rotten Pen
  • SystemVerilog
  • chipverify

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/518189.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

光猫桥接模式详细步骤

目录 一、前言 路由模式 &#xff08;宽带默认&#xff09; 桥接模式 二、桥接模式步骤 &#xff08;一&#xff09;图片记录备份 设备信息图 网络侧信息 远程管理密码 宽带上网设置 &#xff08;二&#xff09;桥接模式开始 光猫设置 路由器设置 一、前言 重点&a…

【学习笔记】java项目—苍穹外卖day10

文章目录 苍穹外卖-day10课程内容1. Spring Task1.1 介绍1.2 cron表达式1.3 入门案例1.3.1 Spring Task使用步骤1.3.2 代码开发1.3.3 功能测试 2.订单状态定时处理2.1 需求分析2.2 代码开发2.3 功能测试 3. WebSocket3.1 介绍3.2 入门案例3.2.1 案例分析3.2.2 代码开发3.2.3 功…

【python从入门到精通】-- 第四战:语句汇总

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

Python人工智能应用----文本情感分析

1.问题引入 接着前两节课的内容&#xff0c;今天我们要构建一个人工智能系统。 它的目的是像人类一样&#xff0c;区分评价的情感是正面还是负面的。 接下来&#xff0c;我们要对提取的文本进行感情色彩的分析&#xff0c;这个就是文本情感分析&#xff0c;我们要使用机器学习…

RecyclerView 与 ListView(一):使用

RecyclerView 与 ListView 功能对比 对比项AbsListViewRecyclerView定向刷新不支持支持局部刷新不支持支持刷新动画不支持支持Item点击支持不支持分隔线样式单一自定义样式布局方式列表/网格自定义样式头尾添加支持不支持 Adapter Adapter&#xff1a;1.创建View 2.绑定数据…

理解Three.js的相机

大家都知道我们生活中的相机&#xff0c;可以留下美好瞬间。那Three.js的相机是什么呢&#xff1f;Three.js创建的场景是三维的&#xff0c;而我们使用的显示器显然是二维的&#xff0c;相机就是抽象的定义了三维空间到二维显示器的投影方式。Three.js常见的相机有两类&#xf…

(React组件基础)前端八股文修炼Day6

一 类组件与函数组件有什么异同 在React中&#xff0c;类组件和函数组件是创建组件的两种主要方式。随着React的发展&#xff0c;尤其是自Hooks在React 16.8中引入以来&#xff0c;函数组件的功能变得更加强大&#xff0c;使得它们能够更加方便地与类组件相竞争。下面是类组件…

Apache Paimon实时数据糊介绍

Apache Paimon 是一种湖格式,可以使用 Flink 和 Spark 构建实时 数据糊 架构,用于流式和批处理操作。Paimon 创新地将湖格式和 LSM(日志结构合并树)结构相结合,将实时流式更新引入湖架构中。 Paimon 提供以下核心功能: 实时更新: 主键表支持大规模更新的写入,具有非常…

helm安装chart报unclosed action

我在尝试使用helm安装mysql主从集群&#xff0c;使用bitnami/mysql的官方chart&#xff0c;但是执行helm install mysql-cluster mysql -n mysql-cluster报如下错误&#xff1a; Error: parse error at (mysql/charts/common/templates/_labels.tpl:14): unclosed action 百度…

C++ 类(初篇)

类的引入 C语言中&#xff0c;结构体中只能定义变量&#xff0c;在C中&#xff0c;结构体内不仅可以定义变量&#xff0c;也可以定义函数。 而为了区分C和C我们将结构体重新命名成class去定义 类的定义 标准格式&#xff1a; class className {// 类体&#xff1a;由成员函…

css酷炫边框

边框一 .leftClass {background: #000;/* -webkit-animation: twinkling 1s infinite ease-in-out; 1秒钟的开始结束都慢的无限次动画 */ } .leftClass::before {content: "";width: 104%;height: 102%;border-radius: 8px;background-image: linear-gradient(var(…

“双碳”目标下资源环境中的可计算一般均衡(CGE)模型应用

我国政府承诺在2030年实现“碳达峰”&#xff0c;2060年实现“碳中和”&#xff0c;这就是“双碳”目标。为了实现这一目标就必须应用各种二氧化碳排放量很高技术的替代技术&#xff0c;不仅需要考虑技术上的可靠性&#xff0c;也需要考虑经济上的可行性。可计算一般均衡模型&a…

【C++初阶】第九站:vector的介绍及使用

前言&#xff1a; &#x1f3af;个人博客&#xff1a;Dream_Chaser &#x1f388;博客专栏&#xff1a;C &#x1f4da;本篇内容&#xff1a;vector的介绍及使用 ​ 目录 一、vector的介绍 二、vector的使用 1.vector的定义 2.vector iterator(迭代器)的使用 begin和end(…

第十届蓝桥杯大赛个人赛省赛(软件类) CC++ 研究生组2.0

A立方和 #include<iostream> #include<cmath> using namespace std; int main(){int n, t, flag, x;long long ans 0;for(int i 1; i < 2019; i){t i;flag 0;while(t && !flag){x t % 10;if(x 2 || x 0 || x 1 || x 9) flag 1;t / 10;}if(fl…

基于圆柱体镜子和光线跟踪实现镜反射观测全景观图的matlab模拟仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 光线与圆柱镜面的交点计算&#xff1a; 反射光线计算&#xff1a; 全景图坐标转换&#xff1a; 5.完整程序 1.程序功能描述 基于圆柱体镜子和光线跟踪实现镜反射观测全景观图.模拟的场景…

ISTQB选择国内版,还是国际版呢

1, ISTQB简介 ISTQB&#xff08;International Software Testing Qualifications Board&#xff09;是一个国际软件测试资格认证机构&#xff0c;旨在提供一个统一的软件测试认证标准。ISTQB成立于2002年&#xff0c;是非盈利性的组织&#xff0c;由世界各地的国家或地区软件测…

case语句

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 CASE 语句的执行方式与 IF...THEN...ELSIF 语句的执行方式类似&#xff0c;但是它是通过一个表达式的值来决定执行哪个分支 CASE 选择器表达式 WHEN 条件 1 THEN 语句序列 …

从多模态生物图数据中学习Gene的编码-MuSeGNN

由于数据的异质性&#xff0c;在不同的生物医学背景下发现具有相似功能的基因对基因表示学习提出了重大挑战。在本研究中&#xff0c;作者通过引入一种称为多模态相似性学习图神经网络的新模型来解决这个问题&#xff0c;该模型结合了多模态机器学习和深度图神经网络&#xff0…

Linux gcc day3

find命令&#xff08;importance&#xff09;&#xff1a; 语法&#xff1a;find pathname -options find /root -name test.c which命令&#xff1a; which [指令] 只搜索指令&#xff0c;在什么位置下 为什么文件夹带有颜色呢&#xff1f; 科普补充alias命令&#xff1a; ali…

redis的简单操作

redis中string的操作 安装 下载可视化软件&#xff1a;https://gitee.com/qishibo/AnotherRedisDesktopManager/releases。 Mac安装redis&#xff1a; brew install redisWindows安装redis: 安装包下载地址&#xff1a;https://github.com/tporadowski/redis/releases 1.…