版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第十章SystemVerilog仿真基础LOREMIPSUMDOLOR目录CONCENT10.1仿真简介10.2program10.3面向对象编程与类10.4随机化10.5随机化10.6实例10.1仿真简介GRADUATIONTHESIS10.1仿真简介10.1.1仿真入门,一个完整的测试平台的实现需要包含生成激励、对被测逻辑施加激励、捕获响应、检查响应是否正确以及根据总体设计目标衡量进度等步骤。系统进行逻辑设计完毕,需要对该逻辑代码进行验证,确保逻辑正确。对所设计的逻辑电路的输入进行激励,并观察该逻辑电路因为输入的激励而产生的输出,以此判断该逻辑电路是否工作正常。这个建模的行为就叫做仿真10.1仿真简介【例10-1】二四译码器的简单仿真测试平台moduledecode2to4sim;logica,b;logic[3:0]decode_out;
notN1(n_a,a);notN2(n_b,b);
and#2G1(decode_out[0],n_a,n_b);and#2G2(decode_out[1],a,n_b);and#2G3(decode_out[2],n_a,b);and#2G4(decode_out[3],a,b);
initialbegin$monitor($time,",a=%d,b=%d,n_a=%d,n_b=%d,decode_out=%4b",a,b,n_a,n_b,decode_out);a=0;b=0;#5b=1;#5a=1;#5b=0;#10$stop;endendmodule正常的逻辑电路不同,该测试平台没有端口声明,只声明了内部变量。门级原语主要用于逻辑电路的实现,同时在门级原语中显式定义了门级延时,如与门的门级延时为2个时间单位。10.1仿真简介例10-1的逻辑电路图10.1仿真简介10.1.2仿真器原理测试平台的所有升级事件都需要通过仿真器进行调度才能实现。仿真器主要是对测试平台进行编译,进行时序跟踪,并且在特定的时刻进行事件升级。仿真器原理图如下10.1仿真简介SystemVerilog仿真器SystemVerilog仿真器Kernel与VerilogHDL的Kernel不同,SystemVerilog将同一仿真时刻分为了五个区域:Preponed、Active、Observed、Reactive和Postponed区域。相当于在原VerilogHDL语言基础上为program增加了一个执行区间和采样区间。这样的安排的好处在于设计逻辑可以在测试平台改变其输入值之前能够确定其最终值,从而减少了设计逻辑和测试平台之间潜在的竞争冒险。10.1仿真简介10.1仿真简介10.1.3测试平台仿真的核心是设计一个用于判断被测逻辑是否准确的有效的测试平台。.测试平台的规模可大可小,具体会根据被测逻辑的复杂度而定。传统的测试方式是测试者根据硬件规格,设计出详细的验证计划,每个验证计划所采用的测试用例对应的是被测逻辑的某一个功能。直接测试的方式最大的好处在于测试者和项目组能够很清晰地看到测试验证进度。当被测逻辑的复杂度成倍增加时,那么直接测试所要求的测试用例以及所需的人力资源也会成本增加。随机激励测试采用可约束的随机激励向量并施加到被测逻辑上,通过观察被测逻辑的响应并进行比较,从而找出被测逻辑错误的一种方式。当采用随机激励时,测试平台需要采用功能覆盖率来衡量整个测试进度。整个测试大致分为四层:场景层、功能层、控制层和信号层。10.1仿真简介完整的测试平台分层结构示意图10.1仿真简介测试平台各层功能:(1)场景层是整个测试平台的最高层。所谓的场景层,就是要模拟各种会施加到被测逻辑的各种行为和场景。(2)功能层位于场景层之下,控制层之上,主要用于接收上层抽象的交易请求,如DMA等,然后转换为可被控制层驱动可被接受的命令,并传送给控制层。(3)控制层位于信号层之上,它主要用来运行驱动程序,并驱动被测逻辑执行单个命令,然后通过监控器来监控和收集被测逻辑的输出,并做适当处理,如进行分组等。(4)信号层包含着被测逻辑以及连接到被测逻辑的信号。(5)功能覆盖对于随机测试是一个必不可少的组成部分。它用于衡量整个仿真的进度。它可以从不同的层来进行衡量,因此把它独立于各层之外。10.2programGRADUATIONTHESIS10.2programprogram概念:SystemVerilog新增了一个新的类型:program,用于解决被测逻辑与测试平台之间的出现竞争的一个方案。program功能:该类型与module非常相似,它以关键字program开始,以endprogram结束,可以定义0个或者多个输入、输出以及双向端口,可以包含0个或者多个initial块、generate块、specparam语句、连续赋值语句、并发断言、timeunit声明等。program内的数据类型、数据声明、函数和任务的定义也和module相似,并且能够包含多个program块,这些program块既可以通过端口交互,也可以独立设计。10.2programmodule与program的关系10.3面向对象编程与类GRADUATIONTHESIS10.3面向对象编程与类10.3.1面向对象编程简介SystemVerilog采用面向对象编程,可以创建负载的数据类型,它通过调用例程,而不是对底层逻辑进行切换来创建测试平台和建模,因此相对于VerilogHDL,SystemVerilog可以让代码设计者进行更抽象的设计,因此测试平台可以变得更为健壮。SystemVerilog作为一门面向对象编程语言,主要的专业术语包括:类(Class)——类是面向对象编程的基本单元,包含了例程和变量。类似于VerilogHDL的module。对象(Object)——类的实例。类似于module的实例化。句柄(Handle)——对象指针。句柄相当于对象的地址,与其他面向对象编程语言的句柄相似,只是它只能是某一种类型,而不能是多种类型。属性(Property)——用于保存数据的变量。方法(Method)——用于操作变量的程序代码,包括任务、函数以及initial与always语句块。原型(Prototype)——用于显示名称、类型和参数列表以及返回类型的例程的头部。例程的主体包含可执行代码。10.3面向对象编程与类10.3.2类简介【例10-4】简单的数据显示类classInfo_show;bit[31:0]a,b;
functionvoiddisplay();$display("ais%h,bis%h,thesumofaandbis%h",a,b,a+b);endfunctionendclass一个类将以关键字class开始,以endclass结束。和module结构相似,任何类在关键字class后会紧接着出现类名,如例10-4中的类名为Info_show。在类里面,包含属性和方法10.3面向对象编程与类【例10-5】module调用package内的类示例packagemsg_show;classInfo_show;bit[31:0]a,b;
functionvoiddisplay();$display("ais%h,bis%h,thesumofaandbis%h",a,b,a+b);endfunctionendclassendpackage
moduletop;importmsg_show::*;//导入package内的所有的类Info_showIs;//句柄声明Is=new();//分配一个Info_show对象
//executecodeendmodule类可以在program、module以及package内定义,也可以在外面定义。如果一个文件内包含多个类,可以把相关的类统一集合到一个package内,非相关的类可以集合到另外的一个package内,并采用关键字import调用。10.3面向对象编程与类【例10-6】带参数列表的new()函数classop;logic[7:0]a,b,cmd;
functionnew(inputlogic[7:0]op1=3,op2=5);a=op1; b=op2;endfunctionendclass
initialbeginopc1;c1=new(.op1(10));endnew()函数为Info_show分配内存空间,并初始化其内部属性为默认值(二态变量为0,四态变量为X),并返回对象存储的地址。10.3面向对象编程与类【例10-7】句柄数组示例functionvoidcheck();Checksumcsm[20];//句柄数组声明foreach(csm[i])begincsm[i]=new();//构建对象 ...endendfunction句柄可以是单个指针,也可以是一个数组。如果是一个句柄数组,则数组中的每一个元素对应的是一个句柄,每个句柄对应的是一个或者多个对象。当需要使用句柄数组时,需要对句柄数组中的每一个元素构建对象.10.3面向对象编程与类一个句柄可以指向多个对象,并且当创建第二个对象时,前一个对象会自动释放,因此仿真过程中,当最后一个句柄不再指向一个对象时,SystemVerilog会自动释放内存。SystemVerilog语言的句柄只能指向一种类型的对象,不允许对句柄进行修改或者使用一种类型的句柄指向另外一种类型的对象。因此可以确保SystemVerilog的代码总是会使用有效的句柄。类的方法既可以定义在类里面,也可以定义在类的外面。把类方法定义在类外面的方式是把类的方法分成原型和执行代码,其中原型留在类的内部,而执行代码留在类的外部。【例10-9】采用类外定义类方法的形式修改例10-4classInfo_show;bit[31:0]a,b;
externfunctionvoiddisplay();endclassfunctionvoidInfo_show::display();$display("ais%h,bis%h,thesumofaandbis%h",a,b,a+b);endfunction10.3面向对象编程与类10.3.3静态变量与静态方法在SystemVerilog语言中,可以在类里面定义静态变量。该变量就是在普通的变量类型前增加关键字“static”来引导。一旦定义为静态变量,该变量在所有基于该类所构建的对象中可见——也仅限于此类的对象可见。通常静态变量在类声明时就已经初始化完毕。如果没有,则需要在第一个对象构建之前,确保对类中的静态变量进行初始化。有两种方式来读取类中的静态变量值,一种方式和module的例化相似,采用操作符“.”来实现。另外一种方式采用类名加操作符“::”来实现。可以定义一个配置的类,该类可以位于一个普通的类中,并被采用当成静态变量使用。如此,则可以共享该静态配置类,实现配置信息共享。在类中,不仅可以声明静态变量,还可以声明静态方法,其声明的方式和静态变量相同,也是在function或task前采用关键字“static”进行引导。10.3面向对象编程与类10.3.4this类中可能会出现不同层级的变量,不同层级的变量的有效范围不一样。如在for循环中的索引变量只能用于for循环,循环体外就无效。因此,如何明确表示所使用的变量为类级别的变量,通常采用this语句来表示。10.3.5类的内嵌类中可以内嵌其他的类,就像SystemVerilog中的module一样。当一个类被内嵌到另外一个类中,其代码采用“类名+句柄”的模式。10.3面向对象编程与类10.3.6对象的基本操作对象是类的实例。和module例化类似,当需要调用实例或者对对象内的属性进行赋值时,采用操作符“.”来实现。对象可以被复制。复制的方式有两种——浅复制和深复制。浅复制采用操作符“new”来实现。initialbeginAnd_opop;//声明一个类型为And_op的句柄opop=new();//构造一个类型为And_op的对象op.a=8'hAA;//为对象内变量a赋值8'hAAop.b=8'h11;//为对象内变量b赋值8'h11op.display();//调用对象内的例程end10.3面向对象编程与类浅复制产生对象不是相互独立的,因此,可能存在对象的属性被篡改的可能。如果需要产生的对象相互独立,需要设计专用的copy函数进行深度复制。对于复杂的类,最好创建专属的copy函数。这样可以通过调用所有内嵌对象的copy函数来形成一个深度复制。
functionStatisticscopy();//构建一个copy函数copy=new(); copy.startT=startT;endfunctionendclassChecksumc1,c2;//声明两个句柄initialbeginc1=new();//构建一个Checksum对象c1.stat.startT=10;//设置startT值为10c2=c1.copy;//把c1复制给c2c2.stat.startT=20;//设置startT值为20$display(c1.stat.startT);end10.3面向对象编程与类10.3.7类的继承与多态而面向对象编程引入类的概念后,可以通过对已有基类进行引申或扩展,从而完善整个测试平台。在此过程中,现有的基类被称为父类,而引申或扩展得出来的类被称为子类。从父类扩展生成子类的过程称为类的派生。子类会继承父类的数据成员、属性和方法,这就是类的继承。子类继承了父类的方法并可以修改,同时也可以添加新的方法;子类继承了父类的所有数据成员并可以重写父类中的数据成员,同时也可以添加新的数据成员;如果一个方法被重写,需要保持与父类中原有定义有一致的参数。其语法定义如下:classchild_class_nameextendsparent_class_name…endclass10.3面向对象编程与类类的继承:子类对象可以赋值给父类对象,但父类对象要赋值给子类对象时,则需要先通过$cast的方法判断赋值是否合法。通过子类对象可以直接访问子类重写的方法和属性,也可以通过super操作符访问父类中的属性和方法,以区分于本身重写的属性和方法。子类中新增的方法和属性对父类是不可见的,因此通过父类对象去引用子类中重写的方法和属性,只会调用父类的属性和方法。如果父类需要监督子类的行为,可以采用虚方法。虚方法可以重写父类中的所有方法。一旦一个方法被声明为虚方法,所有后续继承都将是一个虚方法,不管重写的时候是否使用virtual关键字。10.3面向对象编程与类多态:当一个父类派生出子类时,父类中的一些方法可能会被重写,这是个动态的过程,动态的选择方法的实现方式就叫多态。使用虚拟方法有一个缺点——一旦父类定义了一个虚方法,那么所有的子类定义相同方法时必须使用相同的格式,即:参数的数量和类型要相同,返回值也要相同。设计者无法在子类虚拟方法中添加或移除参数。因此需要提前布局。这也是类的多态性的要求。从而确保能够实现接口的重用——当虚方法使用时,声明父类对象的变量,既可以指向父类也可以指向子类。当指向父类的时候就调用父类的方法,当指向子类的时候就调用子类的方法。因此,当父类的对象指向不同的子类的时候,虚方法就表现出不同的实现方法,呈现多态。10.4随机化GRADUATIONTHESIS10.4随机化10.4.1随机化基础SystemVerilog兼容VerilogHDL的语法,可以采用VerilogHDL的随机数生成方法来生成随机数据。方法说明$random平坦分布,返回有符号的32位随机数$urandom平坦分布,返回无符号的32位随机数$urandom_range在一个范围内平坦分布$dist_exponential产生的随机数呈指数衰减方式分布$dist_normal产生的随机数呈钟形分布$dist_poisson产生的随机数呈钟形分布$dist_uniform产生的随机数呈平坦分布$urandom_range函数一般有两个参数,数值较大的参数必须显式出现,较小的参数可以不显示——如果不显示,默认为0。10.4随机化相关函数介绍$randomize()是一个内置的类方法,用于根据预设的约束,采用rand/randc限定符来产生随机数$randomize()可以调用参数,也可以不调用参数。$randomize()经常会被用来进行约束检查,确保随机数有产生并满足约束要求。Constraint语句是用于约束随机数产生的条件,下一章节将对约束进行详细讲述。在仿真语句中,首先创建一个Trans类的对象,并采用if(!Tr.randomize())条件语句来判断随机化是否成功,如果成功,则调用transmit任务进行数据传输,否则结束仿真。有时候只需要对类里面的某些变量进行随机化,而不需要对整个类中的所有随机变量进行随机化。此时,可以调用randomize函数并指明类中的具体变量,从而可以实现具体变量的随机化。pre_randomize()和post_randomize()函数来自动对随机化过程进行各种控制。如果希望对类中的某些变量进行覆盖,可以采用rand_mode来开关整个类或者类中某个变量的随机模式。10.4随机化10.4.2randcaserandcase是用于多分支时随机选择其中的一个分支语句执行。如在进行网络测试时,有各种不同的网络封包需要进行传输,这些封包需要进行随机传输,则采用randcase.randcase1:sendPacketA;6:sendPacketB;3:sendPacketC;endcase该randcase有三个可供选择的概率选项。每个选项被选中的概率是将分支表达式除以各分支表达式之和。randcase的分支表达式也可以是变量。每次执行randcase语句,其变量之和将会被确认,从而生成随机数。10.4随机化10.4.3randsequenceSystemVerilog采用randsequence语句生成类似于BNF语法的语句,能够产生正确的数据序列,该序列会按照具体的规则和要求生成,可以直接应用于系统,也可以保存在队列中并在稍后应用。【例10-23】randsequence简单示例 randsequence(main) main:valueopvalueequal {$display("%d%s%d%s",value[1],op.value[2],equal);}; bit[15:0]value:{return$urandom;}; stringop:AND:=3{returnAND;} |OR:=4{returnOR;} |XOR:=3{returnXOR;}; stringequal:{return"=";}; stringAND:{return"&";}; stringOR:{return"|";}; stringXOR:{return"^";}; endsequence10.4随机化10.4.4随机约束基础当设计规模很大且很复杂时,随机测试空间会变得近乎无限,如果只是简单地使用随机激励,则需要达到功能覆盖率所需的仿真时间会远远超出验证工程师的忍耐极限。因此需要对随机激励进行约束,使其按照约束规则来生成随机化的激励,从而使得激励结果更多地满足设计要求的区域或边界内,以快速实现功能覆盖率的要求。随机约束可以显示地在类中进行声明,如例10-19所示,声明以关键字constraint开始,并显式声明约束名称。在约束名称之后是具体的约束条件,以“{}”来界定。SystemVerilog允许在代码中使用randomize时添加一个额外的约束对对象进行随机化,这就是所谓的内联约束。该约束采用关键字“randomizewith”实现。当约束的内容过长时,可以把约束内容放置到类外,从而提高代码的可读性。其格式与类的方法外置的格式相似。在进行正常测试和错误注入测试的时候,验证工程师往往希望能够尽可能共享代码,尽量少对底层代码进行修改,因此,可以采用constraint_mode来开关类中某个随机变量的某个约束,从而实现在不同的情况下,对同一随机变量进行不同的约束。10.4随机化10.4.5权重分布对于被测逻辑来说,有些错误可能会存在于某一个特殊的角落,即使采用大量的测试用例,花费大量的时间,可能也很难快速找到该错误。因此,需要针对该类错误进行特殊的随机约束——采用权重分布的方式可以很好地解决此问题。权重分布采用关键字“dist”操作符来创建,从而使得约束条件内,值的选择几率会发生变化,某些值被选择的几率会比另外一些值更容易被选择。dist操作符会显式声明列表中的每一个值和权重,值和权重可以是变量,也可以是常量。值也可以是一个范围。值和权重之间采用操作符“:=”或者“:/”来分开。两个操作符的使用方法相同,但权重分布会有差异。10.4随机化10.4.6约束操作符(1)inside操作符当需要约束某个随机变量所生成的值在某个范围内时,可以采用inside操作符。一旦设置了该约束,SystemVerilog仿真器生成随机数需要满足约束要求,否则就会被报错。(2)蕴涵约束操作符在随机约束中,存在一类特殊的约束操作符——蕴涵约束操作符“->”。该操作符采用双操作数的方式,约束的基本格式是“A->B”。具体含义是“!A||B”。其真值表如下:A->BB=假B=真A=假真真A=真假真10.4随机化(3)等价约束操作符蕴涵约束操作符是单向操作符,而等价约束操作符为双向操作符,采用“<->”符号表示。其基本格式是“A<->B”,真值表如下。A<->BB=假B=真A=假真假A=真假真(4)循环操作符当对数组进行随机化操作时,需要使用循环操作符foreach,其使用方法和通用的foreach的方式相同。10.4随机化(5)条件操作符条件操作符和通用的条件操作符相似,都是采用if语句或者if…else语句来实现,表示在某个特定条件下进行特定的约束。有时候也把它归于蕴涵约束操作符。(6)Solve…before操作符在进行随机化约束时,可以采用solve…before操作符指导SystemVerilog仿真器进行约束随机化的顺序。10.5并行线程GRADUATIONTHESIS10.5并行线程SystemVerilog和VerilogHDL一样,也有两类线程。一类是顺序线程,采用begin…end实现,另外一类是并行线程,采用fork…join实现。这两类线程的用法和功能和Verilog相同,和VerilogHDL不一样的是,SystemVerilog的fork…join还存在两类变体:fork…join_any和fork…join_none。fork…join及其变体的区别10.5并行线程10.5.1Waitwait语句用于条件阻塞语句中。一旦wait语句中的表达式为真,则无阻塞地执行后续代码。如果wait语句中的表达式为假,则需要等待,直到该表达式为真。Wait语句不可用于综合,但经常用于仿真平台。在串并转换中,需要等待load信号有效,一旦有效,则把收到的信号全部发送到接收寄存器中。相应的代码如下:wait(load_active)input_Reg=Recv_Data_Buff;wait表达式也可以复杂,如一个等式或者不等式等:wait(^addr==1)$display(“Itworks!”);wait语句和fork语句可以相结合使用。forkcsm_check(addr1);csm_check(data1);csm_check(data2);join_none...waitfork;//等待所有的线程结束10.5并行线程10.5.2DisableDisable语句是用于停止活动的任务或者命名块。一旦该活动任务或者命名块被阻止,则系统将继续执行被阻止的任务或命名块的后续语句。经常可以采用disable语句用来停止单个线程。如采用disable语句实现对看门狗程序,程序如下:【例10-40】采用disable语句实现看门狗程序taskBMC_Initialization;forkbegin fork:timeout_check begin wait(bmc_done==1'b1) $display("@%0t:BMCisinitialedsuccess!",$time); end #WATCH_TIME_OUT$display("@%0t:BMCisinitialedfail",$time); join_any displaytimeout_check; endjoin_noneendtask10.5并行线程Mailbox用于两个线程之间的信息传递。从硬件的角度来看,mailbox就像一个FIFO,有源端和目的端。源端把数据传送给mailbox,mailbox把数据传送给目的端,目的端接收并从mailbox里面移除数据。Mailbox的大小可以显式设定,也可以是无限制的。如果mailbox是满的,源端需要等待mailbox为空时才能继续传送数据,而如果mailbox是空时,目的端需要等待mailbox里面存在数据才能继续读取数据.Mailbox内预先定义了多个方法便于用户进行直接使用。具体如下表所示。10.5.3Mailbox10.5并行线程方法使用描述new()返回一个mailbox句柄,默认情形下,new中参数为0,则表示mailbox是无边界的,调用put()方法不会被阻塞,如果new中参数为非0,则表示mailbox是有边界的。num()返回mailbox内元素的个数put()采用先进先出的方式输入数据。如果mailbox有边界,则当mailbox满的时候,该进行会被阻塞,直到mailbox有空间再接收数据try_put()尝试采用先进先出的方式输入数据,如果mailbox满,则返回0,否则,就返回一个值get()从mailbox里接收并移除一个数据。如果mailbox为空,则会被阻塞,直到另外一个进程有放置数据到该mailbox为止try_get()尝试从mailbox里接收并移除一个数据。如果mailbox为空,则返回0,否则,接收并移除该数据,同时判断数据类型是否匹配,如果匹配,返回一个正值,否则返回一个负值peek()从mailbox中复制一个数据,但不从mailbox中把该数据移除掉。因此,可以在获取数据前先观察mailbox内的内容。如果该mailbox为空,该方法将被阻塞,直到有数据进来为止try_peek()尝试从mailbox中无阻塞地复制数据,但不移除数据。如果mailbox为空,则返回0值,否则复制数据,并判断数据类型是否匹配,如果匹配,返回一个正值,否则返回一个负值10.5并行线程10.5.4命名事件命名事件是用于进程同步的一种数据类型。VerilogHDL语言也有此类型,但SystemVerilog对此类型进行了强化,解决了VerilogHDL语言存在的命名事件竞争冒险的风险。命名事件的声明格式如下:evente1,startFlag;event是命名事件的关键词,该代码声明了两个命名事件e1和s
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论