赞
踩
测试平台:对DUT创建测试序列、观察DUT的输入输出、对DUT的输出数据与预期数据进行对比、报告检查结果。
芯片开发流程:用户需求->设计结构和产品描述->系统设计->模块功能详述->硬件设计->硬件描述语言文件->功能验证->验证环境文件->后端综合->芯片产品。
只有经过充分量化验证才能有足够的信心去流片。
SystemVerilog是Verilog标准的扩展,旨在通过一种统一的语言来帮助工程师对大型复杂硬件系统进行建模,并且对其功能进行验证。
SV将硬件信号分为“类型”和“数据类型”。
类型:变量(variables、可以使用连续赋值或者过程赋值),线网类型(wire、只能使用连续赋值语句assign)
数据类型:四值逻辑(logic)、二值逻辑(bit)。
四值逻辑类型:logic、integer、reg、wire
二值逻辑类型:bit、byte、shortint、int、longint
无符号类型:logic、bit构成的向量(vector)、reg、wire
有符号类型(加上unsigned变为无符号类型):integer、byte、shortint、int、longint
通过typedef创建用户自定义类型
通过enum创建枚举类型
通过struct创建结构体类型
SV引入string类型用来容纳可变长度的字符串,存储单元为byte,长度为N时,索引值从0到N-1,结尾没有null字符。
str.len():返回字符串的长度
str.putc(i,c):将第i个字符替换成字符c,等同于str[i]=c
str.getc(i):返回第i个字符
str.substr(i,j):将从第i个字符到第j个字符的字符串返回
str.{atoi(),atohex(),atooct,atobin}:将字符串转变为十进制、十六进制、八进制、二进制数据
添加接口(interface)从而将通信和协议检查进一步封装
添加always_comb(组合逻辑)、always_latch(锁存逻辑)、always_ff(时序逻辑)等过程语句块
添加priority(可重复、按优先级选择)和unique(不可重复) case语句
SV在Verilog语言基础上扩展了接口(interface),提供一种新型的对抽象级建模的方式,可以简化建模和验证大型复杂设计
Interface允许多个信号被整合到一起来表示一个单一的抽象端口,多个模块可以使用同一个interface避免多个分散的端口信号连接
接口可以包含变量或者线网,封装模块之间的通信协议,还可以嵌入与协议有关的断言检查、功能覆盖率收集等
接口不同于模块的地方在于接口不允许包含设计层次,即接口不能例化module,但是接口可以例化接口
接口可以进一步声明modport来约束不同模块连接时信号方向
测试平台是整个验证系统的总称,包括各个组件、组件之间的连接关系、测试平台的配置和控制,还包括编译仿真的流程、结果分析报告和覆盖率检查等。
各个组件之间相互独立、验证组件与设计之间需要连接、验证组件之间也需要通信、验证环境也需要时钟和复位信号的驱动。
Stimulator的主要职责是模拟与DUT相邻设计的接口协议,只需要关注于如何模拟接口信号,使其能够以真实的接口协议来发送激励给DUT,不应该违反协议,但不拘束于真实的硬件行为,比真实硬件行为更丰富的激励,接口主要同DUT之间连接,可以有其它配置接口,也可以有存储接口数据生成历史的功能。
Monitor的主要功能用来观察DUT的边界信号或者内部信号(尽量少),并且结果打包整理传送给其他验证平台的组件(Checker比较器)。
Checker负责模拟设计行为和功能检查的任务,将DUT输入接口数据汇集给内置的reference model(参考模型),通过数据比较检查DUT功能。
函数function:
不会消耗仿真时间,无法调用task,可以调用function,可以返回一个单一数据或者不返回,可以作为一个表达式中的操作数
任务task:
会消耗仿真时间,可以调用function和task,不会返回数值
参数传递:input、output、inout属于值传递,只发生在方法的调用和返回时;ref属于指针传递;添加const修饰符变为只读变量。可以有默认参数。
非组合型数组:int a[7:0][1023:0]; 8\*1024
组合型数组:int [3:0][7:0] a; 4\*8
混合型数组,先看右边,再看左边。如:int[1:0][2:0] arr[3:0][4:0];
的维度是4*5*2*3。
初始化:
logic [3:0][7:0] a = 32’h0;
同向量初始化一致;int d[0:1][0:3] = ‘{‘{7,3,0,5},’{2,0,1,6}};
需要通过’{}
来对数组的每一个维度进行赋值。赋值:
byte a[0:3][0:3]; a[3] = ‘{‘hF, ‘hA ,’hC, ‘hE};a[1][0] = 8’h5;
logic[1:0][1:0][7:0] a;a=32'hF1A3C5E7;a[1][1][0]=1'b0;a[1][0][3:0]=4'hF;
拷贝:
动态数组:一开始为空,使用new[]来为其分配空间,有delete、size等内置函数
int d[];
d=new[5];
d=new[20](d);\\分配20个空间并且前五个空间进行拷贝
d=new[100];\\分配100个空间,之前20个空间丢失、
d.delete();
队列:使用[$]关键字,int q[$] = {3,4};
,有insert、push_back、push_front、pop_buck、pop_front等内置函数
关联数组:索引值可以为任意类型,不连续的值等
定位方法:返回值为队列,有min、max、unique等,也可以用foreach
类(class):包含成员变量和成员方法。
对象(object):类在例化后的实例。
句柄(handle):指向对象的指针。
原型(prototype):程序的声明部分,包含程序名、返回类型和参数列表
成员默认都是public,有私有的local(只有类里面才能访问),受保护的protected(只有类以及子类里面才能访问)
使用关键字 extends 继承父类的所有成员(变量和方法)。
class packet; integer i = 1; function new(int val); i = val + 1; endfunction function shift(); i << 1; endfunction endclass class linkedpacked extends packet; // integer i = 3; function new(int val); super.new(val);//子类必须要继承父类的new函数 if(val >= 2) i = val; endfunction function shift(); // super.shift(); i << 2; endfunction endclass module tb; initial begin packet p = new(3); linkedpacked lp = new(1); //packet tmp; //tem = p; $display("p.i = %0d", p.i);//4 $display("lp.i = %0d", lp.i);//2,会先进入父类的new在调用自己的new p.shift(); $display("after shift, p.i = %0d", p.i);//8 lp.shift(); $display("after shift, lp.i = %0d", lp.i);//8,不会进入父类的shift //$display("tem.i = %0d", tem.i); end endmodule
Package里面只能有软件(类里面的一些类、变量、方法、结构体、枚举)的部分,module属于硬件的部分。Interface也不能在package中,interface介于软件和硬件之间。
通过域的索引号::直接引用,通过通配符*将包中的所有类导入到指定容器中。
package pkg_a; class packet_a; int pkg_a_a; endclass typedef struct{ int data; int command; } struct_a; int va = 1; endpackage package pkg_b; class packet_b; int pkg_b_b; endclass typedef struct{ int data; int command; } struct_b; int vb = 2; endpackage module mod_a; endmodule module mod_b; endmodule module tb; class packet_tb; endclass typedef struct{ int data; int command; } struct_tb; class packet_a; int tb_a; endclass class packet_b; int tb_b; endclass mod_a ma();//可以识别 mod_b mb(); //添加import可以省去作用域的麻烦 //import pkg_a::packet_a; //import pkg_a::va; import pkg_a::*; import pkg_b::*; initial begin //packet_a pta = new();//module里面没有packet_a时,没有import出错,无法识别packet_a和packet_b //packet_b ptb = new(); //pkg_a::packet_a pta = new();//module里面没有packet_a时,没有import不会出错 //pkg_b::packet_b ptb = new(); //packet_a pta = new();//module里面没有packet_a时,有import不会出错 //packet_b ptb = new(); packet_a pta = new();//module里面有packet_a时先找到自己的,没有的时候再去找packet的 packet_b ptb = new(); packet_tb mptb = new(); //$display("pkg_a::va = %0d , pkg_b::vb = %0d", pkg_a::va, pkg_b::vb); $display("pkg_a::va = %0d , pkg_b::vb = %0d", va, vb); end endmodule
用系统函数std::randomize()
可以产生随机数。
$urandom()
,可以产生一个32位的无符号随机数。
$urandom_range(maxval, minval=0)
,可以产生maxval和minval之间的随机数。
**在面向DUT的随机激励发生过程中,为了符合协议、满足测试需求,我们需要添加一些“约束”。**使变量随着希望的变化方向去随机。用类来作为“载体”来容纳这些变量以及它们之间的约束,可将类的成员变量声明为“随机”属性,用rand
和randc
来表示。
任何类中的整型变量都可以声明为随机的,定长数组、动态数组、关联数组和队列都可以声明为rand/randc,可以对动态数组和队列的长度加以约束。指向对象的句柄也可以声明为rand(不可以为randc),随机时该句柄指向对象中的随机变量也会一并被随机。
rand bit [7:0] len;
rand integer data[1];
constraint db { data.size == len; }
class packet; rand bit [31:0] src, dst, data[4]; rand bit [7:0] kind; constraint cstr { src > 10; src < 15; } function print(); $display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", src, dst, data, kind); endfunction endclass module tb; packet p; initial begin p = new(); $display("before randomize"); p.print(); p.randomize(); $display("after randomize"); p.print(); end endmodule typedef struct{ rand bit [31:0] src; rand bit [31:0] dst; rand bit [31:0] data[4]; rand bit [7:0] kind; } packet_t; module tb2; packet_t pkt; initial begin $display("before randomize"); $display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", pkt.src, pkt.dst, pkt.data, pkt.kind); //添加临时约束 in-line/dynamic/temporary constraint std::randomize(pkt) with { pkt.src > 10; pkt.src < 15; }; $display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", pkt.src, pkt.dst, pkt.data, pkt.kind); end endmodule module tb3; bit [31:0] src; bit [31:0] dst; bit [31:0] data[4]; bit [7:0] kind; initial begin $display("before randomize"); $display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", src, dst, data, kind); std::randomize(src, dst, data, kind); $display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", src, dst, data, kind); end endmodule class packet2; rand packet_t pkt;//结构体也可以随机 function print(); $display(" src is %0d\n dst is %0d\n data is %p\n kind is %0d", pkt.src, pkt.dst, pkt.data, pkt.kind); endfunction endclass module tb4; packet2 p; initial begin p = new(); $display("before randomize"); p.print(); p.randomize(); $display("after randomize"); p.print(); end endmodule
约束块一般用类来实现,可以是一些随机的取值也可以为变量之间的关系。
rand integer x,y,z;
constraint c1 {x inside {3, 5, [9:15], [24:32], [y:2*y], z};}
rand integer a,b,c;
constraint c2 {a inside {b, c};}
integer fives[4] = '{5, 10, 15, 20 };
rand integer v;
constraint c3 {v inside {fives};}
也可以加上权重发布:①:=
操作符,表示每一个值的权重是相同的;②\=
操作符,表示权重会平均分配到每个值。
x dist {[100:102] := 1, 200 := 2, 300 := 5}//100,101,102,200,500分别为1-1-1-2-5
x dist {[100:102] :/ 1, 200 := 2, 300 := 5}//100,101,102,200,500分别为1/3-1/3-1/3-2-5
unique可以用来约束一组变量,在随机化之后变量之间的取值不会一样。
//b,a[2],a[3],excluded在随机化后将包含不同的取值
constraint u {unique {b, a[2:3], excluded};}
条件约束,可以使用if-else或者->操作符来表示条件约束
mode == little -> len < 10;
mode == big -> len > 100;
bit [3:0] a,b;
constraint c {(a == 0) -> (b == 1);}
if(mode == little)
len < 10;
else if(mode == big)
len > 100;
迭代约束,foreach可以用来迭代约束数组(定长、动态、关联数组,队列)中的元素
class c;
rand byte A[];
constraint c1 {foreach (A[i]) A[i] inside {2,4,8,16}; }
constraint c2 {foreach (A[j]) A[j] > 2 * j; }
end class
module tb5; class packet; rand byte arr[]; constraint cstr { foreach(arr[i]) arr[i] inside {2, 4, 8, 16}; foreach(arr[i]) arr[i] > 2 * i; arr.size() < 8; } endclass initial begin packet p = new(); repeat(10) begin if(p.randomize()) $display("arr size is %d ,data is %p", p.arr.size(), p.arr); else $error("randomize failure!"); end end endmodule
约束里面也可以使用函数
软约束(soft),可以用来指定变量的默认值和权重,若在外部做二次约束时,硬约束会覆盖掉软约束,并且不会导致随机数产生失败。
module tb6; class packet_a; rand int length; constraint cstr {soft length inside {[5:15]};} endclass class packet_b extends packet_a; //constraint cstr{ length inside {[10:20]};}//与父类同名会覆盖掉packet_a的约束 //constraint cstr1 { length inside {[10:20]};}//不同名同时满足packet_a的约束 //constraint cstr2 { length inside {[16:20]};}//满足硬约束 constraint cstr2 {soft length inside {[16:25]};}//满足就近约束 endclass initial begin packet_b pkt = new(); repeat(10) begin if(pkt.randomize() with {soft length inside {[26:30]};}) $display("pkt.length is %d", pkt.length); else $error("randomize failure!"); end end endmodule
模糊指向,使用local::
和this
来指定约束的指向。
module tb7; class packet1; rand bit[7:0] x; constraint cstr {soft x == 5;} endclass class packet2; bit[7:0] x = 10; function int get_rand_x(input bit[7:0] x = 20); packet1 pkt = new(); //pkt.randomize with {x == x;};//默认为5 //pkt.randomize with {x == local::x;};//30,为传进来的值 pkt.randomize() with {x == local::this.x;};//10,当前类中的值 return pkt.x; endfunction endclass initial begin packet2 pkt = new(); $display("pkt.x = %0d", pkt.get_rand_x(30)); end endmodule
随机控制,使用rand_mode()
来禁止随机化,使随机变量不参与到随机化中。p.rand_mode(0)
禁止类中所有的随机变量参与到随机化中,p.value.rand_mode(1)
将类中的指定变量解除禁止参与到随机化中。
约束控制,使用constraint()
来禁止约束使用,用法同随机控制。
fork join相当于begin end,此外还有fork join_any和fork join_none。
`timescale 1ns/1ps module tb; task automatic exec(int id, int t); $display("@%t exec[%0d] entered", $time, id); #(t*1ns); $display("@%t exec[%0d] exited", $time, id); endtask //initial 之间并行 initial begin exec(1, 10); end initial begin exec(2, 20); end initial begin exec(3, 30); end initial begin exec(4, 40); end endmodule module tb1; task automatic exec(int id, int t); $display("@%t exec[%0d] entered", $time, id); #(t*1ns); $display("@%t exec[%0d] exited", $time, id); endtask //initial 内部串行 initial begin exec(1, 10); exec(2, 20); exec(3, 30); exec(4, 40); end endmodule module tb2; task automatic exec(int id, int t); $display("@%t exec[%0d] entered", $time, id); #(t*1ns); $display("@%t exec[%0d] exited", $time, id); endtask initial begin //使用 fork join 并行 fork exec(1, 10); exec(2, 20); exec(3, 30); exec(4, 40); join end endmodule module tb3; task automatic exec(int id, int t); $display("@%t exec[%0d] entered", $time, id); #(t*1ns); $display("@%t exec[%0d] exited", $time, id); endtask initial begin //使用 fork join_any/join_none 并行 $display("@%t fork join_any entered", $time); fork exec(1, 10); exec(2, 20); join_any $display("@%t fork join_any exited", $time); $display("@%t fork join_none entered", $time); fork exec(3, 30); exec(4, 40); join_none $display("@%t fork join_none exited", $time); wait fork;//等待fork结束再继续执行 disable fork;//直接结束fork继续执行 $display("@%t ini_proc1 exited", $time);//无wait 10000,有wait 50000,有disable 10000 end endmodule
时序控制使用延迟控制或者事件等待来完成时序控制。
#
使用延迟控制:#10 x = y ;
@
来完成:@r x = y; @(posedge clock) x = y;
wait
语句可以与事件或者表达式结合:触发事件用->。
`timescale 1ns/1ps module tb; event e1, e2, e3; task automatic wait_event(event e, string name); $display("@%t start waiting event %s", $time, name); @e;//等待事件@ $display("@%t finist waiting event %s", $time, name); endtask initial begin fork wait_event(e1, "e1"); wait_event(e2, "e2"); wait_event(e3, "e3"); join end initial begin fork begin #10ns -> e1; end//触发事件 -> begin #20ns -> e2; end begin #30ns -> e3; end join end endmodule
等待信号的时候要用ref
module tb2; bit e1, e2, e3; task automatic wait_event(ref bit e, input string name); $display("@%t start waiting event %s", $time, name); @e; $display("@%t finist waiting event %s", $time, name); endtask initial begin fork wait_event(e1, "e1"); wait_event(e2, "e2"); wait_event(e3, "e3"); join end initial begin $display("before fork, e1 = %0d", e1); fork begin #10ns e1 = !e1; end begin #20ns e2 = !e2; end begin #30ns e3 = !e3; end join $display("after fork, e1 = %0d", e1); end endmodule
等待事件
@
:边沿触发wait()
:电平触发wait_order(a,b,c)
:按顺序触发,事件从左到右依次完成创建旗语:semaphore sm; sm = new(N=0)
,new括号里面的数字就是旗语钥匙的数量。
从旗语获取一个或多个钥匙(阻塞型):get(N=1)
,将钥匙归还:put(N=1)
尝试获取一个或多个钥匙而不会被阻塞(非阻塞型):try_get(N=1)
,可以拿到正常执行,没有拿到返回0。
旗语的等待遵循先进先出(FIFO)。
`timescale 1ns/1ns module tb; semaphore mem_acc_key; int unsigned mem[int unsigned]; task automatic write(int unsigned addr, int unsigned data); mem_acc_key.get(); #1ns mem[addr] = data; mem_acc_key.put(); endtask task automatic read(int unsigned addr, output int unsigned data); mem_acc_key.get(); #1ns if(mem.exists(addr)) data = mem[addr]; else data = 'x; mem_acc_key.put(); endtask initial begin int unsigned data = 100; mem_acc_key = new(1); forever begin fork begin #10ns; write('h10, data+100); $display("@%t write with data %d",$time,data); end begin #10ns; read('h10, data); $display("@%t read with data %d",$time,data); end join end end endmodule
信箱相当于FIFO来使用。当信箱写满时,后续写入的动作被挂起,直到信箱有空间才可以继续写入。
new(int count = 0)
,可以限定大小,如果不传入参数,默认为不限定大小put()
try_put()
get()
同时会取出数据,peek()
不会取出数据try_get()\try_peek()
num()
module tb; mailbox #(int) mb;//可以指定信箱中的数据类型是int initial begin int data; mb = new(8); forever begin case ($urandom() % 2) 0 : begin if(mb.num() < 8) begin data = $urandom_range(0, 10); mb.put(data); $display("mb put data %0d", data); end end 1 : begin if(mb.num() > 0) begin mb.get(data); $display("mb get data %0d", data); end end endcase end end endmodule
在父类和子类中声明的虚方法,其方法名、参数名、参数方向等都应该保持一致。
在调用虚方法时,它将调用句柄指向对象的方法,而不受句柄类型的影响。
在父类方法前添加关键字virtual,子类可以不声明virtual。
module tb; class BasePacket; int A = 1; int B =2 ; function void printA; $display ( "BasePacket: :A is %d",A);endfunction : printA virtual function void printB; $display ( "BasePacket: :B is %d",B);endfunction : printB endclass : BasePacket class My_Packet extends BasePacket; int A = 3; int B = 4 ; function void printA; $display ( "My_Packet: :A is %d", A); endfunction : printA virtual function void printB; $display("My_Packet: :B is %d",B); endfunction : printB endclass : My_Packet BasePacket P1 = new; My_Packet P2 = new; initial begin P1.printA; // displays 'BasePacket: :A is 1' P1.printB; // displays 'BasePacket: :B is 2' P1 = P2; // P1 has a handle to a My_packet object P1.printA; // displays 'BasePacket: :A is 1' P1.printB;// displays 'My _Packet::B is 4 ' latest derived method P2.printA; // displays 'My_Packet: :A is 3' P2.printB; // displays 'My_Packet: :B is 4' end endmodule
静态转换:Verilog对整数和实数类型,或者不同位宽的向量之间进行隐式转换。转换时指定目标类型,并在要转换的表达式前加上单引号即可。
int i;
real r;
i = int '(10.0 - 0.1);
r = real '(42);
动态转换:将子类的句柄赋值给父类的句柄。但是将父类的句柄赋值给子类句柄的时候会报错,可以使用$cast(sub_handle, base_handle)
将父类句柄转换为子类句柄,只要该父类句柄指向的是一个子类的对象。
function int $cast( singular dest _var, singular source_exp ) ;
//或者
task $cast( singular dest_var, singular source_exp );
module tb; class BasePacket; endclass : BasePacket class My_Packet extends BasePacket; endclass : My_Packet BasePacket P1 = new; My_Packet P2 = new; My_Packet P3 = new; initial begin P1 = P2; // 子类赋值给父类,父类P1指向子类对象P2 $cast(P3, P1); // 父类赋值给子类,子类P3指向父类对象P1,前提父类P1指向相同的子类类型My_Packet end endmodule
伴随着复杂SoC系统的验证难度系数成倍增加,无论是定向测试还是随机测试,我们在验证的过程中终究需要回答两个问题:
是否所有设计的功能在验证计划中都已经验证?
代码中的某些部分是否从未执行过?
覆盖率就是用来帮助我们在仿真中回答以上问题的指标。
一旦通过覆盖率来量化验证,我们可以在更复杂的情况下捕捉一些功能特性是否被覆盖:
当我们在测试X特性的时候,Y特性是否也在同一时刻被使能和测试?
是否可以精简我们已有的测试来加速仿真,并且取得同样的覆盖率?
覆盖率在达到一定的数值的时候,是否停滞,不再继续上升?
简单而言,覆盖率就是用来衡量验证精度和完备性的数据指标。覆盖率可以告诉我们在仿真时设计的哪些结构被触发,当然,它也可以告诉我们设计在仿真时的哪些结构从未被触发过
只有满足以下三个条件,才可以在仿真中实现高质量的验证:
没有任何一种单一的覆盖率可以完备地去衡量验证过程
及时我们可以达到100%的代码覆盖率(隐性覆盖率),但这并不意味着100%的功能覆盖率(显性覆盖率)。原因在于代码覆盖率并不是用来衡量设计内部的功能运转,或者模块之间的互动,或者功能时序的触发等。
类似地,我们即便达到了100%功能覆盖率,也可能只达到了90%的代码覆盖率。原因可能在于我们疏漏了去测试某些功能,或者一些实现的功能并没有被描述。
从上述关于代码覆盖率和功能覆盖率简单的论述就可以证明,如果想要得到全面的验证精度,我们就需要多个覆盖率种类的指标。
如果将上述两个分类的方式(隐性/显性、功能描述/设计实现)进行组合,那么可以将代码覆盖率、断言覆盖率以及功能覆盖率分别置入到不同的象限。
但是需要注意,目前有一个象限仍然处于研究阶段,没有隐性的可以从功能描述生成某种覆盖率的方法,这也是为什么功能覆盖率依然需要人为定义的原因。
跳转覆盖率(toggle):用来衡量寄存器跳转的次数(0->1,1->0);端口跳转覆盖率经常用来测试IP模块之间的基本连接性,例如检查一些输入端口是否没有连接,或者已经连接的两个端口的比特位数是否不匹配,又或者一些已经连接的输入是否被给定的固定值等,都可以通过跳转覆盖率来发现问题
行覆盖率(statement/line):用来衡量源代码哪些代码行被执行过,以此来指出哪些代码行没有被执行过
分支覆盖率(branch):分支覆盖率是用来对条件语句( if/else, case,?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。