SystemVerilog笔记
时间:2022-09-24 14:00:00
SV笔记
数据类型
四值逻辑类型:integer、logic、reg、net-type(例如wire、tri)
二值逻辑类型:byte、short int、int、long int、bit
符号类型:byte、short int、int、long int、integer
无符号类型:bit、logic、reg、net-type(例如wire、tri)
定宽数组:int a[x]; a[] = '{ };
动态数组:int a[]; a = new[x]; a[x] = '{ };
队列: int a[$]; a = {};
logic:任何使用线网的地方都可以使用logic,但要求logic不能有多个结构驱动,比如对双向总线建模时,需要使用线网类型。
**定宽数组:**int a[5] = 5}
**动态数组:**int a[]; a = new[5]; a.delete(); 需要动态数组new创建,delete删除其他用途与定宽数组相同
**队列:**int a[$]; a.delete(); 队列需要delete不需要删除new创建
常用方法:a.insert(pos, data); a.pop_front(); a.pop_back(); a.push_front(data); a.push_back(data)
**关联数组:**bit[63:0] assoc[int], idx=1;
repeat(64) begin assoc[idx]=idx; idx = idx<<1; end
assoc.delete();
数组方法:
- 数组缩减法:sum求和、product积、and与、or或、xor异或
- 数组定位方法:
- a.min();
- a.max();
- a.unique();
- a.find with(item>3)
- a.find_first with(item>3)
- a.sum with(item>3)
- a.sum with((item>3)*item)
- 数组的排序
- a.reverse();
- a.sort();
- a.rsort();
- shuffle();
parameter OPSIZE=8; typedef reg[OPSIZE-1:0] opreg_t; opreg_t op_a,op_b;
使用struct创新类型:
typedef struct {bit[7:0] r,g,b;} pixel_s; pixel_s my_pixel;
枚举类型:
typedef enum {RED, BLUE, GREEN} color; color c1,c2;
类型转换:
- 静态转换:int’(10.0);
- 动态转换:$cast(a, b);
流操作符:“>>”把数据从左至右变成流,“<<将数据从右到左转流
**常量:**const
**字符串:**s = “IEEE”;
常用方法:
- s.getc(pos, s1);
- s.putc(pos, s1);
- s.toupper();
- s.tolower;
- s.substr(start, end);
- 格式化字符串
- $psprintf("%s ]", s,42);
- $sformatf("%s ]", s,42);
过程语句和子程序
-
任务、函数和void函数
忽略函数的返回值:void’(function())
-
ref:
function void print_checksum(const ref bit [31:0] a[]);
systemverilog允许不带ref当数组被复制到堆栈区会被复制到堆栈区。除非是特别小的数组,否则这种操作成本很高。
systemvrilog规定了ref在自动存储的子程序中使用参数智能。如果您指示程序或模块automatic属性,整个程序内部自动存储。
尽量使用向子程序传输数组ref以获得最佳性能。如果您不希望子程序改变数组值,可以使用它const ref修饰。
-
函数声明中必须声明变量类型与输入或输出属性,不定义类型的默认属性与以前相同input,优先级与前面的变量相同。
-
静态变量:static;动态变量:automatic
设计例化和连接
**接口(interface)可以例化:**变量、输入输出端口、函数和任务
**模块(module)与接口(interface)的区别:**模块可以例化模块和接口;接口只能例化接口,不能例化模块
使用界面的优缺点
- 优点:
- 接口便于设计重用,当两个模块之间有两个以上的信号连接,并使用特定的协议通信时,应考虑使用接口。如果信号组一次又一次地重复,则应使用虚拟接口;
- 接口可用替换一系列需要在模块或程序中反复声明并位于代码内部的信号,以减少连接错误的可能性;
- 添加新信号时,只需在界面中声明一次,不需要在更高层的模块层声明,这进一步减少了错误;
- modport允许模块轻松地将接口中的一系列信号绑定在一起,还可以指定信号方向,方便工具自动检查。
- 缺点:
- 使用点对点连接modport接口描述与使用信号列表的端口相同。界面的好处是所有声明都集中在一个地方,降低了出错概率;
- 信号名和接口名必须同时使用,这可能会使模块更加冗长;
- 如果要连接的两个模块使用一个不会被重用的专用协议,使用接口需要比端口连接更多的工作;
- 很难连接两个不同的接口。(bus_if)可能包含现有接口(arb_if)所有信号都添加了信号(地址、数据等)。您需要拆分独立信号并正确驱动它们。
clocking block
modport限制信号传输方向,避免端口连接错误。
clocking包含modport的功能,clocking块是基于时钟周期驱动或采样信号的testbench不再担心如何准确及时地驱动或采样信号,消除信号竞争的问题。
clocking bus@(posedge clk); default input #2ns output #1ns; endclocking
- 为避免可能的采样竞争问题,verifier应在验证环境的驱动环节中添加固定延迟,使时钟与被驱动信号之间的时序前后关系更容易反映在模拟波形中,使页面更容易DUT准确处理和TB采样准确。
- 如果TB在采样从DUT时钟和驱动信号之间存在发送的数据delta_cycle时钟采样沿较早间段去模拟建立时间要求采样,这种方法也可以避免与delta_cycle问题带来的采样竞争问题。
- 当我们把clocking运用到interface中,用来声明各个接口与时钟的采样和驱动关系后,可以大大提高数据驱动和采样的准确性,从根本上消除采样竞争的可能性。
面向对象(类)
面对对象编程的三要素:封装、继承、多态
-
封装:封装就将变量和函数抽象为类,把对外接口暴露,将实现和内部数据隐藏。
-
继承:使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
-
实现继承是指使用基类的属性和方法而无需额外编码的能力;
-
接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
-
可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。
-
-
多态: 多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。**允许将子类类型的指针赋值给父类类型的指针。**由对象类型决定所调用的方法,而非指针的类型,前提是方法用virtual修饰(虚方法)。
类可以在哪里定义:program、module、package
-
new()和new[]的区别
调用new()函数仅创建了一个对象,而new[]操作则建立一个含有多元素的数组。
-
对象的接触分配
sv中没有析构函数,当没有句柄引用某个对象,sv会自动释放该对象的空间。
t = null;
-
静态变量
定义:static int count = 0;
访问:transaction::count; 静态方法访问
-
在sv中,所有成员变量都是公有的,除非标记为local或者protected。你应当尽量使用默认值,以保证对DUT行为的最大程度的控制,这比软件的长期稳定性更加重要。
随机化
class Packet;
rand bit [31:0] src,dst,data[8];
randc bit[7:0] kind;
constraint c{src >10;
src <15; }
endclass
Packet p;
initial begin
p = new();
assert(p.randomize());
else $fatal(0, "Packet::randomize failed");
transmit(p);
end
-
简单表达式
<、<=、==、>=、>
-
权重分布
constraint c_dist{ src dist {0:=40, [1:3]:=60}; //0: 40/220; 1: 60/220; 2: 60/220; 3: 60/220; dst dist {0:/40, [1:3]:/60}; //0: 40/100; 1: 20/100; 2: 20/100; 3: 20/100; }
-
集合(set)成员和inside运算符
constraint x_range{ a inside {[3:5]}; b inside {[$:4],[20:$]}; !(c inside {[3:5]}); }
-
条件约束
constraint c_io{ (io_space_mode)->addr[31]==1'b1; } constraint c_len_rw{ if (op==READ) len inside {[BYTE:LWRD]}; else len=LWRD; }
-
双向约束
constraint c_bidir{ r < t; s == r; t < 30; s > 25; }
-
内建函数constraint_mode()
p.c_io.constraint_mode(0); p.c_len_rw.constraint_mode(1);
-
内嵌约束
constraint c1{ soft addr inside{[0:100], [1000:2000]}; } p.randomize with{addr>=50; addr<=1500; data<10;}
当没有soft修饰时,约束发生冲突则报错;当soft修饰时,则以外部约束的优先级高于内部。
-
回调函数pre_randomize()、post_randomize()
-
随机函数
- $random()——平均分布,返回32位有符号随机数;
- $urandom()——平均分布,返回32位无符号随机数;
- $urandom_range()——在指定范围内的平均分布;
- $dist_exponential()——指数衰落;
- $dist_normal()——正态分布
- $dist_poission()——泊松分布
- $dist_uniform()——平均分布
-
数组约束
class dyn_size; rand logic [31:0] d[]; constraint d_size { d.size() inside {[1:10]}; d.sum() < 1024; foreach (len[i]) len[i] inside {[1:255]}; } endclass
-
unique 数组
UniqueArray ua; assert(ua.randomize());
-
随机化句柄数组
随机化句柄数组的功能是在调用其所在类的随机函数时,随机函数会随机化数组中的每一个句柄所指向的对象。因此随机句柄数组的声明一定要添加rand来表示其随机化的属性,同时在调用随机函数前要保证句柄数组中的每一个句柄元素都是非悬空的,这需要在随机化之前为每一个元素句柄构建对象。
如果要产生多个随即对象,那么你可能需要建立随机句柄数组。和整数数组不同,你需要在**随机化前分所有的元素,因为随机求解器不会创建对象。**使用动态数组可以按照需要分配最大数量的元素,然后在使用约束减小数组的大小。在随机化时,动态句柄数组的大小可以保持不变或减小,但不能增加。
rand会随机化数组中的每个句柄,同时随机每个句柄中被rand修饰变量或句柄。
-
线程及线程间通信
-
线程的使用
硬件模型中由于都是always语句块,所以可以看成时多个独立运行的线程,而这些线程会一直占用仿真资源,因为它们并不会结束。
软件测试平台中的验证环境都需要由initial语句块去创建,而在仿真过程中,验证环境中对象可以创建和销毁,因此软件测试端的资源占用是动态的。
线程的执行轨迹是呈树状结构的,即任何的线程都应该有父线程。
父线程可以开辟若干个子线程,父线程可以暂停或者终止子线程。
当子线程终止时,父线程可以继续执行。
当父线程终止时,其所开辟的所有子线程都应当会终止。
-
fork-join: 同步执行内部的所有线程,内部所有线程都结束后,才继续执行下面的语句。
-
fork-join_any: 同步执行内部的所有线程,内部任何一个线程结束后,继续执行下面的语句。
-
fork-join_none: 优先执行下面的语句,然后同时执行内部的所有线程。
-
wait fork; 等待所有fork中的线程结束后再退出。
-
disable fork_tag; 结束某个fork线程。
如果你给某个任务货哦这线程指明标号,那么当这个线程被调用多次以后,如果通过disable去禁止这个线程标号,所有衍生的同名线程都将被禁止。
-
-
线程间通信
-
event事件
verilog中,一个线程总是要等待一个带@操作符的事件。这个操作符是边沿敏感的,所以他总是阻塞着、等待事件的变化。
其他线程可以通过->操作符来触发事件,结束对死一个线程的阻塞。
-> e1; @e1;
若e1已触发,@e1则等待不到
-> e1; wait(e1.triggered());
即使e1先触发,wait(e1.triggered())仍能等待到
-
semaphore旗语
实现对同一资源的访问控制。
semaphore sem; sem = new(1); sem.put(1); sem.get(1); sem.try_get(); //返回1则代表有足够多的钥匙,返回0则表示没有钥匙。
-
mailbox信箱
线程之间如果传递信息,可以使用mailbox
mailbox是一种对象,因此也需要使用new()来例化。例化时有一个可选的参数size来限定其存储的最大数量。如果size是0或者没有指定,则信箱是无限大的,可以容纳任意多的条目。
使用put()可以把数据放入mailbox,使用get()可以从信箱移除数据。
如果信箱为满,则put()会阻塞;如果信箱为空,则get()会阻塞。
peek()可以获取对信箱里数据的拷贝而不移除它。
线程之间的同步方法需要注意,哪些是阻塞方法,哪些是非阻塞方法,即哪些是立即返回的,而哪些可能需要等待时间的。
mailbox mbx; mbx = new(1); mbx.get(); mbx.put(); mbx.peek(); //复制mailbox的一个数,但不取出,数仍在mailbox中 mbx.try_get(); mbx.try_put();
mailbox与queue的区别
- mailbox必须通过**new()**例化,而队列只需要声明
- mailbox可以将不同的数据类型同时存储,不过这么做是不建议的;对于队列它内部存储的元素类型必须一致。
- mailbox的存取方法put()和get()是阻塞方法(blocking method),即使用他们时,方法不一定会立即返回,而队列所对应的存取方法,push_bak()和pop_front()方法是非阻塞的,会立即返回。因此在使用queue取数时,需要额外填写wait(queue.size>0)才可以在其后对非空的queue做取数的操作。此外也应该注意,如果要调用阻塞方法,那么只可以再task中调用,因为阻塞方法时耗时的;而调用非阻塞方法,例如queue的push_back()和pop_front(),既可以在task又可以在function中调用。
- mailbox只能够用作FIFO,而queue除了按照FIFO使用,还有其他应用的方式例如LIFO(Last In First Out)。
- 对于mailbox变量的操作,在传递形式参数时,实际传递的时mailbox的指针;queue的形式参数声明是ref方向,因为如果采用默认的input方向,那么传递过程中发生的是数组的拷贝,以致于方法内部对queue的操作不会影响外部的queue本身。因此在传递数组时,应该需要考虑到,对数组做的是引用还是拷贝,进而考虑端口的声明方向。
-
功能覆盖率
-
代码覆盖率
设计文件的代码覆盖率
- 行覆盖率(statement / expression)
- 路径(分支)覆盖率(Branch)
- 翻转覆盖率(Toggle)
- 状态机覆盖率(FSM)
- 断言覆盖率(assert)
-
覆盖组
-
covergroup可以直接使用sample()函数进行采样。
covergroup CovPort; coverpoint tr.port; endgroup CovPort = new(); //CovPort cg1 = new(); CovPort.sample(); //cg1.sample();
-
covergroup可以在采样阻塞表达式或者使用**wait或@**实现在信号或时间上的阻塞。
event trans_ready; covergroup CovPort @(trans_ready); coverpoint ifc.cb.port; endgroup
-
-
数据采样
-
仓bin
covergroup和coverpoint都有options来控制
options.auto_bin_max=8;
covergroup CovKind; coverpoint tr.kind{ bins zero = {0}; bins lo = {[1:3],5}; //1个仓代表1、2、3、5 bins hi[] = {[8:$]}; //8个仓代表8:15 bins misc = default; //1个仓代表剩余所有 } endgroup
-
条件覆盖率
coverpoint port iff(!bus_iff.reset);
-
覆盖组的开始于结束
CovKind cg = new(); cg.start(); cg.stop();
-
记录跳转情况
covergroup CoverPort; coverpoin port{ bins t1 = (0=>1),(0=>2),(0=>3),(0=>1=>2); } endgroup
-
wildcard覆盖率
使用关键词wildcard来创建多个状态或者翻转。
covergroup CoverPort; coverpoin port{ wildcard bins even = {3'b??0}; wildcard bins even = {3'b??1}; } endgroup
-
忽略的bin
covergroup CoverPort; coverpoin port{ ignore_bins hi = {[6,7]}; } endgroup
-
交叉覆盖率
class Transaction; rand bit a, b; endclass covergroup CrossBinNames; a: coverpoint tr.a{ bins a0 = {0}; bins a1 = {1}; option.weight = 0; } b: coverpoint tr.b{ bins b0 = {0}; bins b1 = {1}; option.weight = 0; } ab: cross a,b{ bins a0b0 = binsof(a.a0) && binsof(b.b0); bins a1b0 = binsof(a.a1) && binsof(b.b0); bins b1 = binsof(b.b1); } endgroup
-
-
covergroup方法
- sample(): 采样
- get_coverage() / get_inst_coverage(): 获取覆盖率,返回0-100的real数值。
- set_inst_name(string): 设置covergroup的名称。
- start() / stop(): 使能或者关闭覆盖率收集。