systemverilog 为什么要用$casthive 类型转换 cast函数

C++中4种类型转换方式 cast操作详解_C 语言
作者:用户
本文讲的是C++中4种类型转换方式 cast操作详解_C 语言,
Q:什么是C风格转换?什么是static_cast,dynamic_cast以及reinterpret_cast?区别是什么?为什么要注意?
A:转换的含义是通过改变一个变量的类型为别的类型从而改变该变量的表示方式。为了类型转换一个简单对
Q:什么是C风格转换?什么是static_cast,dynamic_cast以及reinterpret_cast?区别是什么?为什么要注意?
A:转换的含义是通过改变一个变量的类型为别的类型从而改变该变量的表示方式。为了类型转换一个简单对象为另一个对象你会使用传统的类型转换操作符。比如,为了转换一个类型为doubole的浮点数的指针到整型:代码:
i=(int)d;或者:
对于具有标准定义转换的简单类型而言工作的很好。然而,这样的转换符也能不分皂白的应用于类(class)和类的指针。ANSI-C++标准定义了四个新的转换符:'reinterpret_cast','static_cast','dynamic_cast'和'const_cast',目的在于控制类(class)之间的类型转换。代码:reinterpret_cast&new_type&(expression)dynamic_cast&new_type&(expression)static_cast&new_type&(expression)const_cast&new_type&(expression)
1reinterpret_cast
'reinterpret_cast'转换一个指针为其它类型的指针。它也允许从一个指针转换为整数类型。反之亦然。(译注:是指针具体的地址值作为整数值?)这个操作符能够在非相关的类型之间转换。操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。
如果情况是从一个指针到整型的拷贝,内容的解释是系统相关的,所以任何的实现都不是方便的。一个转换到足够大的整型能够包含它的指针是能够转换回有效的指针的。
代码:classA{};classB{};
A*a=newA;B*b=reinterpret_cast&B*&(a);'reinterpret_cast'就像传统的类型转换一样对待所有指针的类型转换。
2static_cast
'static_cast'允许执行任意的隐式转换和相反转换动作。(即使它是不允许隐式的)
应用到类的指针上,意思是说它允许子类类型的指针转换为父类类型的指针(这是一个有效的隐式转换),同时,也能够执行相反动作:转换父类为它的子类。
在这最后例子里,被转换的父类没有被检查是否与目的类型相一致。代码:classBase{};classDerived:publicBase{};
Base*a=newBDerived*b=static_cast&Derived*&(a);'static_cast'除了操作类型指针,也能用于执行类型定义的显式的转换,以及基础类型之间的标准转换:
代码:doubled=3.;inti=static_cast&int&(d);
3dynamic_cast
'dynamic_cast'只用于对象的指针和引用。当用于多态类型时,它允许任意的隐式类型转换以及相反过程。不过,与static_cast不同,在后一种情况里(注:即隐式转换的相反过程),dynamic_cast会检查操作是否有效。也就是说,它会检查转换是否会返回一个被请求的有效的完整对象。检测在运行时进行。如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL.代码:classBase{virtualdummy(){}};classDerived:publicBase{};
Base*b1=newDBase*b2=newB
Derived*d1=dynamic_cast&Derived*&(b1);//succeedsDerived*d2=dynamic_cast&Derived*&(b2);//fails:returns'NULL'
如果一个引用类型执行了类型转换并且这个转换是不可能的,一个bad_cast的异常类型被抛出:代码:classBase{virtualdummy(){}};classDerived:publicBase{};
Base*b1=newDBase*b2=newB
Derivedd1=dynamic_cast&Derived&*&(b1);//succeedsDerivedd2=dynamic_cast&Derived&*&(b2);//fails:exceptionthrown
4const_cast
这个转换类型操纵传递对象的const属性,或者是设置或者是移除:代码:classC{};
constC*a=newC;
C*b=const_cast&C*&(a);其它三种操作符是不能修改一个对象的常量性的。注意:'const_cast'也能改变一个类型的volatilequalifier。
--------------------------------------------------------------------
C++的4种类型转换
一、C风格(C-style)强制转型如下:
(T)expression//castexpressiontobeoftypeT函数风格(Function-style)强制转型使用这样的语法:T(expression)//castexpressiontobeoftypeT这两种形式之间没有本质上的不同,它纯粹就是一个把括号放在哪的问题。我把这两种形式称为旧风格(old-style)的强制转型。
二、C++的四种强制转型形式:
C++同时提供了四种新的强制转型形式(通常称为新风格的或C++风格的强制转型):const_cast(expression)dynamic_cast(expression)reinterpret_cast(expression)static_cast(expression)
每一种适用于特定的目的:
·dynamic_cast主要用于执行“安全的向下转型(safedowncasting)”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型,也是唯一可能有重大运行时代价的强制转型。
·static_cast可以被用于强制隐型转换(例如,non-const对象转型为const对象,int转型为double,等等),它还可以用于很多这样的转换的反向转换(例如,void*指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个const对象转型为non-const对象(只有const_cast能做到),它最接近于C-style的转换。
·const_cast一般用于强制消除对象的常量性。它是唯一能做到这一点的C++风格的强制转型。
·reinterpret_cast是特意用于底层的强制转型,导致实现依赖(implementation-dependent)(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见。
旧风格的强制转型依然合法,但是新的形式更可取。首先,在代码中它们更容易识别(无论是人还是像grep这样的工具都是如此),这样就简化了在代码中寻找类型系统被破坏的地方的过程。第二,更精确地指定每一个强制转型的目的,使得编译器诊断使用错误成为可能。例如,如果你试图使用一个const_cast以外的新风格强制转型来消除常量性,你的代码将无法编译。
====dynamic_cast.vs.static_cast==
classB{...};classD:publicB{...};
voidf(B*pb){D*pd1=dynamic_cast&D*&(pb);D*pd2=static_cast&D*&(pb);}
IfpbreallypointstoanobjectoftypeD,thenpd1andpd2willgetthesamevalue.Theywillalsogetthesamevalueifpb==0.
IfpbpointstoanobjectoftypeBandnottothecompleteDclass,thendynamic_castwillknowenoughtoreturnzero.However,static_castreliesontheprogrammer'sassertionthatpbpointstoanobjectoftypeDandsimplyreturnsapointertothatsupposedDobject.
即dynamic_cast可用于继承体系中的向下转型,即将基类指针转换为派生类指针,比static_cast更严格更安全。dynamic_cast在执行效率上比static_cast要差一些,但static_cast在更宽上范围内可以完成映射,这种不加限制的映射伴随着不安全性.static_cast覆盖的变换类型除类层次的静态导航以外,还包括无映射变换,窄化变换(这种变换会导致对象切片,丢失信息),用VOID*的强制变换,隐式类型变换等...
====static_cast.vs.reinterpret_cast==
reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它.我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的.(这句话是C++编程思想中的原话)
static_cast和reinterpret_cast操作符修改了操作数类型.它们不是互逆的;static_cast在编译时使用类型信息执行转换,在转换执行必要的检测(诸如指针越界,类型检查).其操作数相对是安全的.另一方面,reinterpret_cast仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换,例子如下:
intn=9;doubled=static_cast&double&(n);
上面的例子中,我们将一个变量从int转换到double.这些类型的二进制表达式是不同的.要将整数9转换到双精度整数9,static_cast需要正确地为双精度整数d补足比特位.其结果为9.0.而reinterpret_cast的行为却不同:
intn=9;doubled=reinterpret_cast&double&&(n);
这次,结果有所不同.在进行计算以后,d包含无用值.这是因为reinterpret_cast仅仅是复制n的比特位到d,没有进行必要的分析.
一共四种cast。1、static_cast,支持子类指针到父类指针的转换,并根据实际情况调整指针的值,反过来也支持,但会给出编译警告,它作用最类似C风格的“强制转换”,一般来说可认为它是安全的;2、dynamic_cast,支持父类指针到子类指针的转换,并根据实际情况调整指针的值,和static_cast不同,反过来它就不支持了,会导致编译错误,这种转换是最安全的转换;3、reinterpret_cast,支持任何转换,但仅仅是如它的名字所描述的那样“重解释”而已,不会对指针的值进行任何调整,用它完全可以做到“指鹿为马”,但很明显,它是最不安全的转换,使用它的时候,你得头脑清醒,知道自己在干什么;4、const_cast,这个转换能剥离一个对象的const属性,也就是说允许你对常量进行修改。
以上是云栖社区小编为您精心准备的的内容,在云栖社区的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索类型转换
hive 类型转换 cast、sqlite 类型转换 cast、cast类型转换、java cast类型转换、oracle 类型转换 cast,以便于您获取更多的相关知识。
稳定可靠、可弹性伸缩的在线数据库服务,全球最受欢迎的开源数据库之一
6款热门基础云产品6个月免费体验;2款产品1年体验;1款产品2年体验
弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率
开发者常用软件,超百款实用软件一站式提供
云栖社区()为您免费提供相关信息,包括
,所有相关内容均不代表云栖社区的意见!转一篇Systemverilog的一个牛人总结
Systemverilog
合并数组和非合并数组
1)合并数组:
存储方式是连续的,中间没有闲置空间。
例如,32bit的寄存器,可以看成是4个8bit的数据,或者也可以看成是1个32bit的数据。
表示方法:
数组大小和位,必须在变量名前指定,数组大小必须是【msb:lsb】
Bit[3:0] [7:0]
bytes&& ;
2)二维数组和合并数组识别:
合并数组:& bit [3:0] [7:0]
&& 大小在变量名前面放得,且降序
二维数组:& int arrays[0:7]
[0:3] ;& 大小在变量名后面放得,可降序可升序
位宽在变量名前面,用于识别合并和非合并数组,位宽在后面,用于识别数组中元素个数。
3)非合并数组
一般仿真器存放数组元素时使用32bit的字边界,byte、shortint、int都放在一个字中。
非合并数组:字的地位存放变量,高位不用。
表示方法:
&Bit&& [7:0]
&4)合并数组和非合并数组的选择
(1)当需要以字节或字为单位对存储单元操作。
(2)当需要等待数组中变化的,则必须使用合并数组。例如测试平台需要通过存储器数据的变化来唤醒,需要用到@,@只能用于标量或者合并数组。
Bit[3:0] [7:0] barray[3]& ;
表示合并数组,合并数组中有3个元素,每个元素时8bit,4个元素可以组成合并数组
可以使用barry[0]作敏感信号。
随机事物不确定大小。
使用方法:数组在开始是空的,同时使用new[]来分配空间,在new[n]指定元素的个数。
Int dyn[];
new[5];&&&&
//分配5个元素空间
Dyn.delete()
//释放空间
在队列中增加或删除元素比较方便。
当你需要建立一个超大容量的数组。关联数组,存放稀疏矩阵中的值。
表示方法:
采用在方括号中放置数据类型的形式声明:
Bit[63:0] assoc[bit[63:0]];
1)Verilog 推荐使用文本宏。
好处:全局作用范围,且可以用于位段或类型定义
缺点:当需要局部常量时,可能引起冲突。
2)Parameter
作用范围仅限于单个module
3)Systemverilog:
参数可以在多个模块里共同使用,可以用typedef 代替单调乏味的宏。
可以在for循环中定义变量,作用范围仅在循环内部
for(int i=0;i&10;i++)
array[i] =i;
任务、函数及void函数
1) 区别:
Verilog中task
和function最重要的区别是:task可以消耗时间而函数不能。函数中不能使用#100的延时或@的阻塞语句,也不能调用任务;
Systemverilog中函数可以调用任务,但只能在fork&
joinnone生成的线程中。
如果有一个不消耗时间的systemverilog任务,应该把它定义成void函数;这样它可以被任何函数或任务调用。
从最大灵活性角度考虑,所有用于调用的子程序都应该被定义成函数而非任务,以便被任何其它任务或函数调用。(因为定义成任务,函数调用任务很有限制)
类静态变量
1)类的静态变量,可以被这个类的对象实例所共享。
当你想使用全局变量的时候,应该先想到创建一个类的静态变量
静态变量在声明的时候初始化。
类的每一个实例都需要从同一个对象获取信息。
当静态变量很多的时候,操作它们的代码是一个很大的程序,可以用在类中创建一个静态方法读写静态变量,但是静态方法不能读写非静态变量。
ref高级的参数类型
Ref 参数传递为引用而不是复制。Ref比 input
、output、inout更好用。
Function void print_checksum(const ref
bit [31:0] a[ ]);
也可以不用ref进行数组参数传递,这时数组会被复制到堆栈区,代价很高。
进行数组参数传递,仅仅是引用,不需要复制;向子程序传递数组时,应尽量使用ref以获得最佳性能,如果不希望子程序改变数组的值,可以使用const
Ref参数,用ref 传递变量;可以在任务里修改变量而且,修改结果对调用它的函数可见,相对于指针的功能。
Return语句
增加了return语句。Task任务由于发现了错误而需要提前返回,如果不这样,那么任务中剩下的语句就必须被放到一个else条件语句中。体会下
Task load_array(int len. Ref int array[
If(len&0)&
& $display(“Bad
//任务中其它代码
&&& endtask
局部数据存储 automatic作用
Verilog中由于任务中局部变量会使静态存储区,当在多个地方调用同一个任务时,不同线程之间会窜用这些局部变量。
Systemverilog中,module和program块中,缺省使用静态存储;如果想使用自动存储,需加入automatic关键词。
一个信号可能连接几个设计层次,如果增加一个信号,必须在多个文件中定义和连接。接口可以解决这些问题。
如果希望在接口中增加一个信号,不需要改变其他模块,如TOP模块。
使用方法:
(1)接口中去掉信号的方向类型;
(2)DUT 和测试平台中,信号列表中采用接口名,例化一个名字
因为去掉了方向类型,接口中不需要考虑方向信号,简单的接口,可以看做
是一组双向信号的集合。这些信号使用logic类型&。
双向信号为何可以使用logic呢?
这里的双向,只是概念上的双向,不想verilog中databus多驱动的双向。
双向信号如何做接口?
(1)仲裁器的简单接口
Interface arb_if( input bit clk);
Logic [1:0] grant,
Endinterface
DUT 使用接口:
Module arb(arb_if arbif);
Always @(posedge arbif.clk or negedge
arbif.rst)
(2)DUT 不采用接口,测试平台中使用接口(推荐)
中源代码不需要修改,只需要再top中,将接口连接到端口上。
Module top;
Always #2 clk =~clk;
Arb_if arbif(clk);
Arb_port al(.grant(arbif.grant),
&&&&&&&&&&&.request(arbif.grant),
&&&&&&&&&&&&&&&
.rst(arbif.rst),
&&&&&&&&&&&&&&&
.clk(arbif.clk)
&&&&&&&&&&&&&&&
Test t1(arbif);
端口的连接方式包含了方向信息,编译器依次来检查连续错误;接口使用无信号的连接方式。Modport将接口中信号分组并指定方向。
在总线设计中使用modport
并非接口中每个信号都必须连接。Data总线接口中就解决不了,个人觉得?
因为data是一个双驱动
一旦定义了时钟块,测试平台就可以采用@arbif.cb等待时钟,而不需要描述确切的时钟信号和边沿,即使改变了时钟块中的时钟或边沿,也不需要修改测试代码
将测试平台中的信号,都放在clocking
中,并指定方向(以测试平台为参考的方向)。并且在modprot test(clocking cb,
最完整的接口:
Interface arb_if(input bit clk);
Logic[1:0] grant,
Clocking cb @(posedge clk);
&&& Output
Endclocking
Modport test (clocking cb,
&&&&&&&&&&
Output rst);
Modport dut (input clk,
request,rst,
&&&&&&&&&&
Output grant);
endinterface
变化:将request
和grant移动到时钟块中去了,test中没有使用了。
接口中的双向信号
Interface master_if(input bit
clk);& //在类中为了,不使用有符号数,常用bit[]定义变量
Wire [7:0]
& Clocking cb@(posedge
& Endclocking
&Modport TEST(clocking
endinterface
program test(master_if mif);
initial begin
mif.cb.data &= ‘z;
$display(mif.cb.data);&&&&
//总线中读数据
Mif.cb.data &=
8’h5a;&&&&
//驱动总线
Mif.cb.data &=
‘z;&&&&&&&
//释放总线
(1)interface 列表中clk 采用的是input bit
clk;为什么要用bit?
(2)时钟块 clocking cb
中,一般将testbench中需要的信号,方向指定在这里;
&而在modprot
指定test信号方向的时候,采用clocking cb。
(3)interface中信号,不一定都用logic,也可采用wire(双驱动);systemverilog
中如果采用C代码的风格(参数列表中方向和类型写一起),必须采用logic类型
(4)现在的风格,DUT 没才用clocking cb
,测试平台和DUT的时钟如何统一?
DUT和测试平台之间时序必须密切配合。
测试平台和设计间的竞争状态
好的风格:
使用非阻塞赋值可以减少竞争。
systemverilog验证中initial 中都采用&=
赋值,而等待延迟采用@arbif.cb等待一个周期来实现。
而verilog中采用的风格时,initial 中采用
=阻塞赋值,沿时可以采用#2,等实现。
因此时钟发生器,只能放在module 中,而不能放在program中
Program中不能使用always块
测试平台可以使用initial 但不能使用always,使用always
模块不能正常工作。
原因:测试平台的执行过程是进过初始化、驱动和响应等步骤后结束仿真。
如果确实需要一个always块,可以使用initial forever
来完成。比如:在产生时钟时。
类中static变量
如果一个变量需要被其他对象所共享,如果没有OPP,就需要创建全局变量,这样会污染全局名字空间,导致你想定义局部变量,但变量对每个人都是可见的。
类中static变量,将被这个类的所有实例(对象)所共享,使用范围仅限于这个类。
例:class transaction;
Static int count=0;
Trasaction tr1,tr2;
Id不是静态变量,所以每个trasaction对象都有自己的id;count
是静态变量,所有对象只有一个count变量。
当你打算创建一个全局变量的时候,首先考虑创建一个类的静态变量。
2)static变量的引用
句柄或类名加::
static 变量的初始化
static变量通常在声明时初始化。不能在构造函数中初始化,因为每一个新的对象都会调用构造函数。
静态句柄:
背景:当类的每一个对象,都需要从同一个对象(另一个类)中获取信息的时候。如果定义成非静态句柄,则每个对象都会有一份copy,造成内存浪费。
当使用更多静态变量的时候,操作他们的代码会很长。
可以在类中创建一个静态方法用于读写静态变量。
注:systemverilog不允许,静态方法读写非静态变量。
类之外的方法
背景:解决类太长的问题。类最好控制在一页内,如果方法很都很长。
背景:如果在类很深的底层作用域,却想引用类一级的对象。在构造函数中最常见。
作用:this指向类一级变量
如何做类,类做多大?
上限:类不能太大
当类中存在多处相同的代码,你需要将这段代码做成当前类的一个成员函数或父类的成员函数。
下限:类不能太小
类太小,增加了层次。
方法:如果一个小类只被例化了一次,可以将它合并到父类中去。
概念区分:方法中修改对象 和修改句柄
修改对象——将对象的变量重新赋值。
修改句柄——在任务中new()对象。
1) 当你将对象传递给方法
背景:句柄,new()后变成对象,在将其作为参数传递给方法。
实质和作用:
传递的是句柄。这个方法可以读取对象中的值;也以改变对象中的值
2) 修改标量变量的值
背景:在方法的参数中,前面加ref;(用ref传递,ref传递的是变量的地址)。
&方法可以修改变量的值,并将修改的值,传递给主程序。
方法可以改变对象,即使没有使用ref 修饰句柄。
因为传递的是句柄,句柄是地址。不要将句柄和对象混为一谈,如果传递的是对象,对象是单向的,那方法以外也不能传递回来。可以这样理解吧。
读写对象中的值:
transmit(Transcation t);
Cbbus.rx_data &= t.
t.stats.startT& =&
//在任务中,改变了对象
initilal beign
t = new();
t.addr = 42;
transmit(t);
既然传递的是句柄,那数据就没传过去,如何读取值?
答:主程序中new()创建了一个对象,而句柄是指向对象的指针,传递的是句柄,transmit中也指向了对象,所以transmit中可以读写对象。
3) 在任务中修改句柄
在方法中,参数为句柄,前面加ref。
可以在方法中new()对象,并将初始化放在方法中;在主程序中仅仅调用。
注意:正确的事物发生器,参数是带ref的句柄
Function void create(ref transaction
Endfunction
方法的参数是句柄,句柄前有ref 和没ref的差别:
没ref,在方法中不能new()该句柄的对象,因为没ref,句柄是不能传递到主程序的;
有ref,可以在方法中new()该句柄的对象。
原因:没ref传递的是句柄,不能修改句柄,有ref,传递的是句柄的地址,可以修改句柄。
Function void create( Transcation
= new();&&&
tr.addr = 42;
Endfunction
Initial begin
& Create(t);
$diasplay (t.addr);
程序中修改对象
应该在循环中,new()多个对象,而不是先new()对象再循环发送事物。
创建多个对象
正确产生器,创建多个对象:
Task generator (int n);
Repeat(n) begin
t.addr =$random();
transmit(t);
&&& endtask
将new()放在循环内,这样创建了许多对象。
对象的复制
目的:防止对象的方法修改原始对象的值。或在一个发生器中保留约束。
分两种情况,类中不包含其他类的句柄和包含
使用new复制一个对象——简易复制(shallow copy)
Transaction src,dst;
Src = new()& //
dst = new src&
如果类中包含一个指向另外一个类的句柄,那么只有最高一级的对象被new复制,下层的对象都不会被复制。
会出现意想不到的错误。
当前类中变量和句柄被复制,这样两个对象,都有指向另外一个类的对象statistic(会带来意想不到的错误),但是statistic没有被复制。如果其中一个transaction对象,修改了statistic对象值,会影响到另一个transaction看到static的值。
2) 简单的复制函数
如何实现:
Copy函数一般放在类内部,函数名为该类的一个句柄,copy函数中new()对象。
类中不包含其他类。
3) 深层的复制函数 ——深层copy
目的:解决类中包含另外一个类,copy带来的问题。
在copy函数中,将调用另一个类的copy函数,赋值给该句柄;同时需要为statistic类和层次结构中每一个类增加一个copy()方法;copy函数的ID域也要保持一致,copy函数,copy本类,所以ID也要++.
&Copy.stats =
stats.copy();
Id =count++;
约束块中,只能包含表达式,不能赋值。
1)dist权重分布
dist带有一个值的列表及相应的权重,中间用:= 或
:/分开。值或权重可以是常量或变量。权重的和不必是100.
表示范围内,每一个值的权重是相同的;
表示范围内,权重要均匀分布
产生一个值的集合,在值的集合中取随机值时,机会相等。
3)在集合中使用数组
Systemverilog支持两种关系操作 &&和if—else
—&可产生和case效果类似的语句块,可以用于枚举类型的表达式。
控制多个约束块
作用:可以打开或关闭某个约束
可以使用内建的Handle.constraint.constraint_mode()打开或关闭。
背景:很多测试只会在代码的一个地方随机化对象,但是约束越来越复杂时,
Systemverilog可以使用randomized with
来增加额外的约束,这和在类里增加的约束是等效的。
Pre_randomize 和post_randomize函数
有时候需要再调用randomize()之前或之后立即执行一些操作。
随机化前:设置类里的一些非随机变量(如上下限、权重),
随机化后:计算数据的误差矫正值。
约束的技巧
1)& 约束中使用变量
2)& 使用非随机值
如果一套约束在已产生了几乎所有想要的激励向量,但还缺少几种。
可以使用rand_mode把这些变量设置为非随机变量。
Systemverilog可以用foreach对数组中的每一个元素进行约束。
线程及线程间的通信
测试平台使用许多并发执行的线程。测试平台隶属于程序块。
Systemverilog引入两种新的创建线程的方法—fork…join_none和fork…join_any
1) 使用fork…join_none来产生线程
在调度其内部语句时,父线程继续执行。
2) 使用fork…join_any实现线程同步
在调度块内语句,当第一个语句执行完,父线程才继续执行。
Systemverilog中可以动态创建线程。
fork…join_none放在了任务中,而不是包含两个线程。
主程序中有连个线程:发送和检测线程。但是不能同时启动,发送事物后,才能检测,否则还未产生数据,就开始检测;但是检测又不能阻塞下一次发送事物的线程。所以fork…join_none
放在了检测task 任务(后作用的线程中)中,
例:测试平台产生随机事物并发送到DUT中,DUT把事物返回到测试平台。测试平台必须等到事物完成,但同时不希望停止随机事物的发送。
Program automatic
test(bus_ifc.Tbbus);
check_trans(Transaction tr);
Wait(bus.cb.addr == tr.addr);
Initial begin
Repreat(10)& begin
Tr= new();
Assert.(tr.randomize());
//把事物发送到DUT中
Transmit(tr);
//等待DUT的回复
Check_trans(tr);
endprogram
并发线程中务必使用自动变量来保持数值。
#0 延迟,使得当前线程必须等到fork…join_none语句中产生的线程执行完后,才得以运行。
停止单个线程
使用fork ..join_any 后加disable。
3) 停止多个线程
Disable fork&
能停止从当前线程中衍生出来得所有子线程。
应该使用fork ..join 把目标代码包含起来,以限制Disable
fork的作用范围。
Verilog中当一个线程在一个事件上发生阻塞的同时,正好另一个线程触发了这个事件,则竞争就出现了。如果触发线程先于阻塞线程,则触发无效(触发是一个零宽度的脉冲)。
解决方法:
Systemverilog
引入了triggered()函数,用于检测某个事件是否已被触发过,包括正在触发。线程可以等待这个结果,而不用在@操作符上阻塞。
Event e1,e2;
Initial begin
Initial begin
上面的代码,假设先执行第一个块,再执行第二个块。第一个块会阻塞在@e2(阻塞先执行),直到e2触发,再运行(触发后执行);在执行第二个块时,会阻塞在@e1,但是e1已经触发(触发先执行,阻塞后执行,触发是个零宽度的脉冲,会错过第一个事件而锁住)
解决方法:用wait(e1.triggered())来代替阻塞@el,如果先触发,也可以执行。
等待多个事件
最好的办法是:采用线程计数器来等待多个线程。
Get()可以获取一个或多个钥匙,put()可以返回一个或多个钥匙。Try_get()获取一个旗语而不被阻塞。
背景:如何在两个线程中传递信息?考虑发生器需要创建很多事物并传递给驱动器的情况。
问题:如果使用发生器的线程去调用驱动器的任务。这样,发生器需要知道驱动器的层次化路径(类的层次化),降低了代码的可重用性;还迫使发生器和驱动器同一速率运行,当一个发生器需控制多个驱动器时会发生同步问题。
解决办法:把驱动器和发生器当成各个处理事物的对象,之间通过信道交换数据。信道允许驱动器和发生器异步操作;引入问题:你可能倾向于仅仅使用一个共享的数据或队列,但这样,编写实现线程间的读写和阻塞代码会很困难。解决办法:可以使用systemverilog中的信箱。把信箱看出一个具有源端和收端的FIFO.
1)信箱的容量可以指定,new(size),size限制信箱中的条目,size为0,或没指定,则信箱是无限大。
2)Put()放数据,get()可以移出数据。Peek()可以获取信箱中数据的copy而不移出。
3)信箱中可以放句柄,而不是对象。
漏洞:在循环外只创建一个对象,然后使用循环对对象随机化,信箱中是句柄,最终得到的是一个含有多个句柄的信箱,多个句柄都指向同一个对象。
解决办法:在循环中,创建多个对象。
异步线程间使用信箱
很多情况下,由信箱连接的两个线程应该步调一致,这样生产方
才不至于跑到消费方前。
好处:最好层的generator需要等待低层的数据发完后才能结束。测试平台能精确知道所有激励发出去的时间。
两个线程同步,需要额外的握手信号。否则,出现生产方运行到结束,消费方还启动。
信箱容量为1,两个线程同步
因阻塞,连个线程不需要握手信箱
3)& 容量不为1,线程间同步
需要使用握手信号,以使producer不超前于consumer;如果consumer超前于prodecer会阻塞。
使用定容信箱和peek实现线程同步:(比较好)
消费方:consumer
使用信箱方法peek()获取信箱里的数据的copy而不将其移出,当consumer处理完数据后,便使用get()移出数据。
特点:信箱容量定义为1,不需要握手信号。
Calss& consumer
Repeat(n)begin
Mbx.peek(i);
$display(“consumer:after get( )”,i);
Mbx.get(i);
如果直接使用get()替代peek(),那么事务会被立刻移出,这样可能会在consumer完成事务前,producer生成新的数据。
2)使用信箱和事件实现线程同步
使用边沿敏感的阻塞语句@handshake 代替电平触发wait(handshake.triggered())。
因为:线程中任务run()使用循环,事件阻塞只能使用@handshake。
局限:如果遇到producer线程的阻塞和consumer线程的触发同时发生,则可能出现次序上的问题。
3)使用两个信箱实现线程同步
使用另一个信箱把consumer的完成信息发回给producer。
目的:在producer线程中,处理完事物后,用一个get()来阻塞。
特点:信箱容量大于1.
Maibox& mbx,rtn;
Class prodecer
For(int i=0; i&4;i++) begin
Mbx.put(i);
Rtn.get(i);
Class consumer
Repeat(3) begin
Mbx.get(i);
Rtn.put(-i);
说明:信箱的构造函数中Mbx =new();Rtn
=new(),信箱容量为无穷大。如何实现同步?
虽然信箱容量为无穷大,producer线程发完一个数据后遇到get()会阻塞,不能放入第二个数据;等到consumer得到第一个数据并且处理完后,通过另一个信箱返回一个数据,producer才继续放第二个数据。
因为get()得到数据后,将信箱中数据取出。表象:信箱容量定义为无穷大,但是实际上也是producer放一个数据,consumer取一个数据;然后producer再放第二个数据,依次类推。
这样确保producer不会超前于consumer线程,而将数据都写入信箱。
4) 其他的同步技术
通过变量或旗语阻塞也可以实现握手。事件是最简单的结构,其次是通过变量阻塞。旗语相当于第2个信箱,但是没有交换信息。Systemverilog中的信箱比其他技术要差,原因是无法在producer放入第一个事务时,让它阻塞。Producer一直比consumer提前一个事务的时间。
Wait(handshake.triggered())和@handshake 使用范围
Wait(handshake.triggered()),用于等待一个事件;
循环中等待事件,只能用@handshake
两个线程的同步,一般任务run()使用循环,所以只能使用@handshake。
注意事项:
在循环中,等待事件不能用Wait(handshake.triggered()),因为如果事件触发一次,wait()语句一直为真,进入不断的循环。下一次循环中,不会阻塞。
2)& @handshake
如果触发事件,先于等待事件。会等不到事件,因为(事件触发,是一个零宽度的脉冲)
OPP的高级编程技巧
为总线事务增加一个错误功能并带可变延时的复杂类。方法如下:
使用合成,即在类中例化另一个类型的类。有时候很难将功能分成独立的部分。如果使用合成,则需要为正确和错误事务分别创建不同的类,正确类的测试平台需要重写以处理错误类的对象。
2)使用扩展类
当需要增加事务,而对现有的测试代码修改越少越好,。例如增加错误注入功能。
扩展类和类合成区别:
扩展类解决,增加新事务,使用类合成中,大量修改代码的麻烦。
如何使用:
扩展类共享基类的变量和子程序。
1)基本类中的方法,需标记为virtual,这样扩展类中才可以重新定义。扩展类中函数,和基类中函数名一样时,通过supper.函数名,调用基类中函数。Systemverilog中不允许supper.supper.new方式经行多层调用。
2)如果基类构造函数new()有参数,那么扩展类,必须有一个构造函数,并在构造函数的第一行调用基类的构造函数。
Class basel
Function new(input& int var);
&&&this.var =
endfunction
extended&& extends basel
&&& function
new(input int var);
super.new(var);
endfunction
3)OPP规则指出:基类的句柄,也可以指向扩展类的对象。(好好体会)
1)背景:一个简单的发生器,通过信箱将数据传递给驱动器。
class generator
mailbox&& gen2
function new(input mailbox gen2drv)
&&& this.gen2drv
endfunction
forever begin
tr = new();
assert(tr.randmize);
gen2drv.put(tr);&&
//mail.put(x)
endclass&&&&&&&&
存在问题:这个例子在循环内部创建事务对象,而不是在循环外部,避免了测试平台常见的错误。New()放在循环外部,错误原因是,mailbox中放入的是句柄,而不能是对象,所有的句柄都指向同一个对象。(1)任务Run创建了一个事物并立即随机化,意味着事务使用了默认的所有约束。要修改,必须要修改transaction类。(2)无法使用扩展
解决办法:将tr的创建和初始化分开,使用蓝图模式。
另一个问题:如果简单的把创建和初始化分开,而放在循环外部,而避免测试平台错误(P200),如何解决?蓝图模式如何解决
2)蓝图模式概念:
首先构建一个对象蓝图(金属模),然后修改它的约束,甚至可以用扩展对象替换它,随机化这个蓝图时,就得到想赋予的随机值;然后复制这个对象,将copy发给下游。
蓝图:是一个钩子,允许你改变发生器类的行为而无需修改其类代码。蓝图对象在一个地方构建(new()),在另一个地方(任务run)使用
3)P200与P221相对比分析:重要
蓝图模式,也就比new()在循环外地generator多了一个copy函数。问题(1)蓝图模式,new()在循环外,也只有一个对象,而mailbox中放入的只能是句柄,如何解决常见的平台错误?
因为copy,是对象的复制,而不是句柄的复制。这样蓝图模式只有一个句柄,但是随机化后,copy,相当于再循环中创建了许多对象。而测试平台常见错误的本质是,只创建了一个对象。这样就避免了问题。
(2)蓝图模式下,因为只有一个ID号,那么任务run循环中,下发了许多数据,这些只有一个ID号了?
因为copy是对象的复制,所以在copy中ID号也会增加。下发的每个数据,都有各自的ID号。
使用扩展的transaction
为了注入错误,需要将蓝图对象transaction变成Badtransaction(改变蓝图)。必须在环境的创建和运行阶段之间完成这个操作。注意:所有的badTr引用都在这一个文件中,这样就不需要改变environment类或generator类。
Env.build();
Badtr bad = new();
Env.gen.blueprint =
目的是:将一个对象取代另一个对象。New()后都是对象了,将对象赋值给对象,这是什么写法?不是复制呀?复制本质是将一个句柄指向一个对象。
解释:上述是句柄的复制,将扩展类句柄bad赋值给基类句柄blueprint,这样基类句柄指向扩展类对象,后面的代码调用的时候,就直接指向扩展类bad了,改变了蓝图。
Env.new()和nev.build()区别
Env.new()仅仅new()函数
nev.build()是将各个模块new(),并传达一些参数,通过这些参数将环境的各个模块,连接起来。P213
$cast 作类型向下转换
背景:基类句柄可以指向扩展类对象,不需要额外的代码;
扩展类句柄指向基类对象,一般情况下会出错,但有时候是可以的,前提是基类句柄指向了它的一个扩展类对象。
作用:扩展类句柄指向基类对象时,使用$cast()函数。在非法的情况下,不会编译报错,会返回了一个0.
$cast做任务使用时,systemverilog会在运行时,检查源对象类型和目的对象类型不匹配,会报错;
做函数使用时,运行时,仍做类型检查,在不匹配时,不会报错,$函数返回0.
前面所述:基类句柄可以指向任何它的扩展类的对象、
基类句柄指向扩展类对象——出现情况:修改蓝图,不改过多代码,增加功能
Transaction tr; //基类句柄
bad;& //扩展类句柄
Bad = new();
bad;& // 基类句柄指向扩展类对象
//掉用的是扩展类的方法
2) 扩展类句柄指向基类对象——出现情况:基类virtual
方法copy函数,它的继承类中copy函数
将基类句柄赋值给扩展类句柄,使扩展类句柄指向基类对象,一般编译器会出错,不能运行,所以非常小心;只有基类句柄指向扩展类对象时,再将扩展类句柄指向基类对象时,不出错。为了检测基类句柄是否指向了扩展对象,并且不让编译器报错,可以使用$cast()函数检测。
当把扩展类句柄指向基类对象时,发生什么?
Tr= new();
tr;&& //扩展类句柄指向基类句柄
上述会发生错误,编译不会被通过。因为有些属性在基类中不存在;但是扩展类句柄指向基类句柄不总是非法的(见下面代码,是可以的),当基类句柄指向一个扩展类对象时是允许的。
bad,bad2;&&&&&&&&&
Bad= new();
//基类句柄指向扩展类对象
$cast(bad2,tr);&&&&&
//扩展类句柄指向基类对象
if(!$cast(bad2,tr);
$display(“cannot assign tr to bad2”);
$display(bad2.bad_crc);
句柄类型和对象类型差异(书中翻译的不准,type of handdle 和 object)
个人理解:
Transaction tr;&
句柄tr类型是transaction
句柄类型:关键字
对象类型:类中成员的类型差异
虚方法和多态
多态:多个程序使用一个共同的名字的现象。
多态解决问题:计算机建构面临的一个问题。让物理内存很小的情况下,让处理器能够对很大的地址空间寻址。针对这个问题引入了虚拟内存。
虚拟方法继承劣势:
基类使用了虚拟方法,扩展类也必须使用相同的“签名”,扩展类中虚拟子程序不能增加或删除参数,这意味着必须提前做好规划。
1)& 因为是virtual
函数,扩展类中copy方法也必须是transaction型的,
但是要copy的是badtr类型的,所以要new一个bad
带有copy 的事物基类。
Rand bit[31:0] src,dst,data[8];
Virtual function transaction copy
Copy&& = new();
&&& Copy.src = s
&&& Copy.dst =
&&& Copy.data =
Copy.crc& =
Endfunction
带有copy的扩展类
Calss badtr extends transaction
&&& Rand bit
Virtual function badtr
copy();& //错误
Virtual function transaction
Bad = new();
bad.data =
bad.crc& =
Bad.bad_crc = bad_
Rendfunction
&&& endclass
2)优化途径一,创建一个独立的函数copy_data,这样每个类只负责copy其局部变量,即扩展类中的copy函数用super.copy_data(tr),代替了基类中变量的复制。代码的重用性提高。P8.22
$cast(bad,tr);&&&&&&
//扩展类句柄指向基类句柄
使用的情况: 因为virtual
函数,在继承中,虚拟函数必须和基类中名称和参数也一致。这样扩展类中copy_data函数参数仍然是transaction类型的tr,这样出现了参数是基类句柄,但是copy_data函数内要作的确实扩展类的成员,就要将基类句柄参数赋值给扩展类句柄,
要将扩展类badtr类型的数据返回,所以必须用$cast(bad,tr)。
优化途径二,最好的。前面的copy子程序都会创建一个新对象,改进的一种方法就是指定复制对象的存放地址。
Virtual function& transaction copy(transaction to
if(to == null)
&&&&&&&&&&&
copy = new();
&&&&&&&&&&&
copy_data(copy);
endfunction
抽象类和纯虚方法
背景:验证的目标之一是创建多个项目共享的代码。
目的:systemverilog
有两种方法创建共享的基类:抽象类和纯虚方法
Virtual class (抽象类):可以被扩展但是不能被直接例化。
Pure& virtual
function(纯虚方法):没有实体的方法原型,相当于一个声明。
由抽象类扩展而来的类,只有在所以的虚拟方法都有实体的时候才能被例化,
纯虚方法只能在抽象类中定义。
3)抽象类中,纯虚方法是没实体的,非纯虚方法最好也不写实体。
背景:测试平台目的:创建一个不做任何修改就能在所有测试中使用的验证环境。要做到这点的关键是测试平台使用钩子,(什么是钩子?)钩子作用,在不修改原始类的情况下注入新的代码。采用virtual
方法,也可以在扩展类中覆盖基类方法,但是需要重复原方法的所有代码,并且它的修改将传播到它的所有扩展类中。
作用:回调就是一个钩子,在不修改原始类的情况下注入新的代码。
实现:回调任务在顶层中创建,在最低级即驱动器中调用。这样驱动器不需要知道测试的任何信息,它只需要使用一个可以在测试中扩展的通用类。
1)& 使用回调注入干扰
回调的一个常见用法就是注入干扰,例如引入一个错误或者延迟。下面测试平台使用回调对象,随机地丢弃数据包。
扩展类是如何作用的?在扩展的回调类中注入错误,如何在驱动器中作用的?
关键是数据队列的作用,驱动器中使用了,回调基类的数据队列
回调基类是抽象类,在扩展的回调类中加入错误注入,而drive驱动类中,是回调基类的数据队列,在环境中将扩展类句柄让入驱动器类,回调基类的数据队列中。
begin // Create error injection callback
Driver_cbs_drop dcd = new();
env.drv.cbs.push_back(dcd); // Put into
与前面扩展类作用的差异?
前面代码,要使扩展类中增加代码,需要使基类句柄指向扩展类句柄。&
驱动器类:
下面的代码如何解释
2)回调也可以想scoreboard 发送数据或收集功能覆盖率。
优点:你可能想过将scoreboard和功能覆盖数据组置于一个事物处理器中,通过邮箱连接到测试平台中,这是一种笨拙的方法,原因如下:测试平台组件几乎都是被动和异步的,组件只有在测试平台给他数据的时候才被唤醒,而且不会主动地向下游事物处理器传递信息。麻烦:1)这样一个需要同时监视多个邮箱的事物处理器复杂了;2)你可能在多个地方采集数据,但是事物处理器设计用来处理单个数据源回调
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 hive 类型转换 cast 的文章

 

随机推荐