Systemverilog知识点总结

数据类型

l 合并数组和非合并数组

1)合并数组:

存储方式是连续的,中间没有闲置空间。

例如,32bit的寄存器,可以看成是4个8bit的数据,或者也可以看成是1个32bit的数据。

表示方法:

数组大小和位,必须在变量名前指定,数组大小必须是【msb:lsb】

Bit[3:0] [7:0] bytes ;

2)二维数组和合并数组识别:

合并数组: bit [3:0] [7:0] arrys; 大小在变量名前面放得,且降序

二维数组: int arrays[0:7] [0:3] ; 大小在变量名后面放得,可降序可升序

位宽在变量名前面,用于识别合并和非合并数组,位宽在后面,用于识别数组中元素个数。

3)非合并数组

一般仿真器存放数组元素时使用32bit的字边界,byte、shortint、int都放在一个字中。

非合并数组:字的地位存放变量,高位不用。

表示方法:

Bit [7:0] bytes;

4)合并数组和非合并数组的选择

(1)当需要以字节或字为单位对存储单元操作。

(2)当需要等待数组中变化的,则必须使用合并数组。例如测试平台需要通过存储器数据的变化来唤醒,需要用到@,@只能用于标量或者合并数组。

Bit[3:0] [7:0] barray[3] ; 表示合并数组,合并数组中有3个元素,每个元素时8bit,4个元素可以组成合并数组

可以使用barry[0]作敏感信号。


l 动态数组

随机事物不确定大小。

使用方法:数组在开始是空的,同时使用new[]来分配空间,在new[n]指定元素的个数。

Int dyn[];

Dyn = new[5]; //分配5个元素空间

Dyn.delete() ;     //释放空间

l 队列
在队列中增加或删除元素比较方便。

l 关联数组

当你需要建立一个超大容量的数组。关联数组,存放稀疏矩阵中的值。

表示方法:

采用在方括号中放置数据类型的形式声明:

Bit[63:0] assoc[bit[63:0]];

l 常量:

1)Verilog 推荐使用文本宏。

好处:全局作用范围,且可以用于位段或类型定义

缺点:当需要局部常量时,可能引起冲突。

2)Parameter

作用范围仅限于单个module

3)Systemverilog:

参数可以在多个模块里共同使用,可以用typedef 代替单调乏味的宏。

过程语句

可以在for循环中定义变量,作用范围仅在循环内部

for(int i=0;i<10;i++)

array =i;

l 任务、函数及void函数

1) 区别:

Verilog中task 和function最重要的区别是:task可以消耗时间而函数不能。函数中不能使用#100的延时或@的阻塞语句,也不能调用任务;

Systemverilog中函数可以调用任务,但只能在fork joinnone生成的线程中。

2)使用:

如果有一个不消耗时间的systemverilog任务,应该把它定义成void函数;这样它可以被任何函数或任务调用。

从最大灵活性角度考虑,所有用于调用的子程序都应该被定义成函数而非任务,以便被任何其它任务或函数调用。(因为定义成任务,函数调用任务很有限制)

l 类静态变量

作用:

1)类的静态变量,可以被这个类的对象实例所共享。

当你想使用全局变量的时候,应该先想到创建一个类的静态变量

静态变量在声明的时候初始化。

2)

类的每一个实例都需要从同一个对象获取信息。

l 静态方法

作用:

当静态变量很多的时候,操作它们的代码是一个很大的程序,可以用在类中创建一个静态方法读写静态变量,但是静态方法不能读写非静态变量。

l ref高级的参数类型

Ref 参数传递为引用而不是复制。Ref比 input 、output、inout更好用。

Function void print_checksum(const ref bit [31:0] a[ ]);

也可以不用ref进行数组参数传递,这时数组会被复制到堆栈区,代价很高。

用带ref 进行数组参数传递,仅仅是引用,不需要复制;向子程序传递数组时,应尽量使用ref以获得最佳性能,如果不希望子程序改变数组的值,可以使用const ref。

Ref参数,用ref 传递变量;可以在任务里修改变量而且,修改结果对调用它的函数可见,相对于指针的功能。

l Return语句

增加了return语句。Task任务由于发现了错误而需要提前返回,如果不这样,那么任务中剩下的语句就必须被放到一个else条件语句中。体会下

Task load_array(int len. Ref int array[ ]);  If(len<0) begin

  $display(“Bad len”); Returun; 

//任务中其它代码

endtask

l 局部数据存储 automatic作用

Verilog中由于任务中局部变量会使静态存储区,当在多个地方调用同一个任务时,不同线程之间会窜用这些局部变量。

Systemverilog中,module和program块中,缺省使用静态存储;如果想使用自动存储,需加入automatic关键词。


测试平台

l Interface

背景 :

一个信号可能连接几个设计层次,如果增加一个信号,必须在多个文件中定义和连接。接口可以解决这些问题。

好处:

如果希望在接口中增加一个信号,不需要改变其他模块,如TOP模块。

使用方法:

(1)接口中去掉信号的方向类型;

(2)DUT 和测试平台中,信号列表中采用接口名,例化一个名字

注意:

因为去掉了方向类型,接口中不需要考虑方向信号,简单的接口,可以看做

是一组双向信号的集合。这些信号使用logic[url=]类型[/url][d1] 。

双向信号为何可以使用logic呢?

这里的双向,只是概念上的双向,不想verilog中databus多驱动的双向。

双向信号如何做接口?

(1)仲裁器的简单接口

Interface arb_if( input bit clk);

Logic [1:0] grant,request;

 Logic rst;
Endinterface
DUT 使用接口:
Module arb(arb_if arbif); …

Always @(posedge arbif.clk or negedge arbif.rst) …

endmodule

(2)DUT 不采用接口,测试平台中使用接口(推荐)
DUT 中源代码不需要修改,只需要再top中,将接口连接到端口上。

Module top;

Bit clk;

   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);
Endmodule

l Modport

背景:

端口的连接方式包含了方向信息,编译器依次来检查连续错误;接口使用无信号的连接方式。Modport将接口中信号分组并指定方向。

例子:

l 在总线设计中使用modport

并非接口中每个信号都必须连接。Data总线接口中就解决不了,个人觉得?

因为data是一个双驱动

l 时钟块

作用:

一旦定义了时钟块,测试平台就可以采用@arbif.cb等待时钟,而不需要描述确切的时钟信号和边沿,即使改变了时钟块中的时钟或边沿,也不需要修改测试代码

应用:

将测试平台中的信号,都放在clocking 中,并指定方向(以测试平台为参考的方向)。并且在modprot test(clocking cb,

最完整的接口:

Interface arb_if(input bit clk); Logic[1:0] grant,request; Logic rst; Clocking cb @(posedge clk); Output request; Input grant; Endclocking

Modport test (clocking cb, Output rst); Modport dut (input clk, request,rst, Output grant); endinterface
变化:将request 和grant移动到时钟块中去了,test中没有使用了。

l 接口中的双向信号

Interface master_if(input bit clk); //在类中为了,不使用有符号数,常用bit[]定义变量 Wire [7:0] data; Clocking cb@(posedge clk); Inout data; Endclocking

Modport TEST(clocking cb);

endinterface

program test(master_if mif); initial begin

   mif.cb.data <= ‘z; @mif.cb; $display(mif.cb.data); //总线中读数据 @mif.cb; Mif.cb.data <= 8’h5a; //驱动总线 @mif.cb; 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的时钟如何统一?

l 激励时序

DUT和测试平台之间时序必须密切配合。

l 测试平台和设计间的竞争状态

好的风格:

使用非阻塞赋值可以减少竞争。

systemverilog验证中initial 中都采用<= 赋值,而等待延迟采用@arbif.cb等待一个周期来实现。

而verilog中采用的风格时,initial 中采用 =阻塞赋值,沿时可以采用#2,等实现。

因此时钟发生器,只能放在module 中,而不能放在program中

l Program中不能使用always块

测试平台可以使用initial 但不能使用always,使用always 模块不能正常工作。

原因:测试平台的执行过程是进过初始化、驱动和响应等步骤后结束仿真。

如果确实需要一个always块,可以使用initial forever 来完成。比如:在产生时钟时。

l 类中static变量

背景:

如果一个变量需要被其他对象所共享,如果没有OPP,就需要创建全局变量,这样会污染全局名字空间,导致你想定义局部变量,但变量对每个人都是可见的。

1)作用:

类中static变量,将被这个类的所有实例(对象)所共享,使用范围仅限于这个类。

例:class transaction;

Static int count=0;

      Int id;

Endclass

Trasaction tr1,tr2;

Id不是静态变量,所以每个trasaction对象都有自己的id;count 是静态变量,所有对象只有一个count变量。

如何用?

当你打算创建一个全局变量的时候,首先考虑创建一个类的静态变量。

2)static变量的引用

句柄或类名加::

  1. static 变量的初始化
static变量通常在声明时初始化。不能在构造函数中初始化,因为每一个新的对象都会调用构造函数。

l 静态句柄:

背景:当类的每一个对象,都需要从同一个对象(另一个类)中获取信息的时候。如果定义成非静态句柄,则每个对象都会有一份copy,造成内存浪费。

l 静态方法

背景:

当使用更多静态变量的时候,操作他们的代码会很长。

作用:

可以在类中创建一个静态方法用于读写静态变量。

注:systemverilog不允许,静态方法读写非静态变量。

l 类之外的方法

背景:解决类太长的问题。类最好控制在一页内,如果方法很都很长。

l This

背景:如果在类很深的底层作用域,却想引用类一级的对象。在构造函数中最常见。

作用:this指向类一级变量

l 如何做类,类做多大?

上限:类不能太大

当类中存在多处相同的代码,你需要将这段代码做成当前类的一个成员函数或父类的成员函数。

下限:类不能太小

类太小,增加了层次。

方法:如果一个小类只被例化了一次,可以将它合并到父类中去。

l 动态对象

概念区分:方法中修改对象 和修改句柄

修改对象——将对象的变量重新赋值。

修改句柄——在任务中new()对象。

1) 当你将对象传递给方法

背景:句柄,new()后变成对象,在将其作为参数传递给方法。

实质和作用:

传递的是句柄。这个方法可以读取对象中的值;也以改变对象中的值

2) 修改标量变量的值

背景:在方法的参数中,前面加ref;(用ref传递,ref传递的是变量的地址)。

作用:

方法可以修改变量的值,并将修改的值,传递给主程序。

引申:

方法可以改变对象,即使没有使用ref 修饰句柄。

因为传递的是句柄,句柄是地址。不要将句柄和对象混为一谈,如果传递的是对象,对象是单向的,那方法以外也不能传递回来。可以这样理解吧。

读写对象中的值:

例:

Task  transmit(Transcation t); Cbbus.rx_data <= t.data; t.stats.startT = $time; //在任务中,改变了对象 endtask

trancation t; initilal beign

t = new(); t.addr = 42; transmit(t); end

既然传递的是句柄,那数据就没传过去,如何读取值?

答:主程序中new()创建了一个对象,而句柄是指向对象的指针,传递的是句柄,transmit中也指向了对象,所以transmit中可以读写对象。

3) 在任务中修改句柄

背景:

在方法中,参数为句柄,前面加ref。

作用:

可以在方法中new()对象,并将初始化放在方法中;在主程序中仅仅调用。

注意:正确的事物发生器,参数是带ref的句柄

Function void create(ref transaction tr)

Endfunction

方法的参数是句柄,句柄前有ref 和没ref的差别:

没ref,在方法中不能new()该句柄的对象,因为没ref,句柄是不能传递到主程序的; 有ref,可以在方法中new()该句柄的对象。

原因:没ref传递的是句柄,不能修改句柄,有ref,传递的是句柄的地址,可以修改句柄。

例子:

Function void create( Transcation tr) tr = new(); 不正确

   tr.addr = 42; Endfunction

Transcation t; Initial begin Create(t); $diasplay (t.addr); End

l 程序中修改对象

背景:

应该在循环中,new()多个对象,而不是先new()对象再循环发送事物。

作用:

创建多个对象

正确产生器,创建多个对象:

Task generator (int n); Transaction t; Repeat(n) begin

   t=new(); t.addr =$random(); transmit(t); endtask
将new()放在循环内,这样创建了许多对象。

l 对象的复制

目的:防止对象的方法修改原始对象的值。或在一个发生器中保留约束。

分两种情况,类中不包含其他类的句柄和包含

方法:

1) 使用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();

#笔记##读书笔记#
全部评论
这个语言好学不
点赞 回复 分享
发布于 2022-05-28 21:26

相关推荐

用户64975461947315:这不很正常吗,2个月开实习证明,这个薪资也还算合理,深圳Java好多150不包吃不包住呢,而且也提前和你说了没有转正机会,现在贼多牛马公司骗你说毕业转正,你辛辛苦苦干了半年拿到毕业证,后面和你说没hc了😂
点赞 评论 收藏
分享
ResourceUtilization:四六级不愧是大学最有用的证之一
点赞 评论 收藏
分享
评论
2
8
分享

创作者周榜

更多
牛客网
牛客企业服务