绿皮书学习摘要(二) 第6章~12章

  • 第6章   随机化
6.1  介绍

     ■ CRT(随机测试法);

     ■ CRT由两部分组成:使用随机的数据流为DUT产生输入的测试代码,以及伪随机数发生器(PRNG)的种子(seed),只要改变种子的值,就可以改变CRT的行为;

6.2  什么需要随机化
6.2.1  器件配置
6.2.2  环境配置
6.2.3  原始输入数据

     ■ 准备好相关的事务类;

6.2.4  封装后的输入数据
6.2.5  协议异常、错误(error)和违规(violation)
6.2.6  延时

      ■ 测试平台中的每一个测试都使用随机的有效的延时,以便发现设计中的Bug;

6.3  SV中的随机化

      ■ 受约束的随机激励是在事务级产生的,通常不会一次只产生一个值;

6.3.1  带有随机变量的简单类

      ■ rand bit [31:0] src ,dst ,data[8] ;// rand 修饰符,表示每次随机化这个时,这些变量都会赋一个值;(类似掷骰子,每掷一次,都会产生一个新的数字,或和现在一样的数字)

      ■ randc bit[7:0] kind ;  // randc类型,表示周期随机性,即所有可能的值都赋过后随机值才可能重复;(类似发牌洗牌后再按另一个顺序随机地抽出牌)

     //src的约束

       constraint c {src > 10 ;src < 15}

      ◆ 约束是一组用来确定变量的值的范围的关系表达式,表达式的值永远为

      ◆ 约束表达式是放在括号“{}”中,而没有放在begin ...end之间,这是由于这段代码是声明性质,而不是程序性质;

       

      // assert (p. randmize( ));

      ◆ randmize()函数在遇到约束方面的问题时返回0

      ◆ 使用断言(assert)来检查randomize()结果;

      ■  不能在类class的构造函数里随机化对象,因为在随机化前可能需要打开或关闭约束、改变权重,甚至添加新的约束。

      ■ 构造函数用来初始化对象的变量,不能在这里调用randomize(函数);

      ■ 类里的所有变量都应该是随机的(random)公有的(public),这样测试平台才能最大程度地控制DUT;

6.3.2  检查随机化(randomize)的结果

      ■  randomize( )函数为类class里所有的rand和randc类型的随机变量赋一个随机值,并保证不违背所有有效的约束

6.3.3  约束求解

     ■ 约束表达式的求解是有SV的约束求解器完成的;

6.3.4  什么可以被随机化

    ■ SV可以随机化整型变量,即由组成的变量,尽管只能随机化2值数据类型,但也可以是2值4值类型;

6.4  约束

■ 每个表达式里至少有一个变量必须是rand或randc类型的随机变量;

6.4.1  什么是约束

例 6.3  受约束的随机类

6.4.2  简单表达式

■ 变量可以在多个表达式里使用;

■ 在一个表达式中,最多只能使用一个关系操作符(<、<=、= =、>=、>)

例 6.4  不正确的排序约束

     class  order;

          rand bit[7:0] lo ,me ,hi ;

          constraint bad {lo<med<hi;}   // 错误

     endclass

例6.6 固定顺序的约束

     class order;

        rand bit[15:0]   lo, med , hi ;

        constraint  good   {lo<med ; med< hi;}

     endclass

6.4.3  等效表达式

      ■ 在约束块里只能包含表达式,所以在约束块里不能进行赋值

      ■ 相反,应该用关系运算符为随机变量赋一个固定的值,例如 len == 42;

      ■也可以在多个随机变量之间使用更复杂关系表达式,例如len = =header.addr_mode * 4 + payload.size( );

6.4.4  权重分布

      ■ dist操作符允许产生权重分布,这样某些值的选取机会比其他值更大一些;

      ■ dist操作符带有一个值的列表以及相应的权重,中间用:=:/分开;

      ■ 值或权重可以是常数变量

      ■ 值可以是一个值或值的范围,例如[lo:hi];

      ■ 权重百分比表示,权重的和也不必是100;

      ■ := 操作符表示值范围内的每一个值的权重是相同的, :/操作符表示权重要均分到值范围内的每一个值;

      ■ 可以使用权重变量来随时改变值的概率分布,甚至可以把权重设为0,从而删除一个值

例6.8  动态改变权重

6.4.5  集合(set)成员和inside运算符

    ■ 可以使用inside运算符产生一个值的集合,SV在值的集合里随机取值时,各个值的选取机会是相等;在集合里也可以使用变量

例6.9 随机值的集合

rand int c ; // 随机变量

int lo,hi;   //作为上限和下限的非随机变量

constraint c_range {

   c inside {[lo:hi]} ;   //lo <=c 并且c<=hi

}

  

   ■ 可以使用$来代表取值范围里的最小值和最大值;

rand bit[6:0] b ;// 0< =b <=127

rand bit[5:0] e;  //0 <=e <=63

constraint c_range {

      b inside {[$:4],[20:$]};    //0<= b <=4 || 20<= b <=127

      e inside {[$:4], [20:$]};     //0<=e <=4 || 20<=e<=63

}

■ 若想选择一个集合之外的值,只需要用取反操作符!对约束取反;

constraint c_range{

!(c inside {[lo:hi]});   //c<lo或c>hi

}

6.4.6  在集合里使用数组

■ 把集合里的值保存到数组里后,就可以使用这些值;

6.4.7 条件约束

■ SV支持两种关系操作:—> 和if-else

■ —> 操作符可以产生和case操作符效果类似的语句块,它可以用于枚举类型的表达式;

  ■ 在约束块中用{ }把多个表达式组成一个块,而在程序性代码里使用begin...end关键词;

6.4.8 双向约束

   ■ 约束块是声明性的代码,是并行的,所有的约束表达式同时有效

   ■ SV的约束是双向的,即它会同时计算所有的随机变量的约束

6.4.9 使用合适的数学运算来提高效率
6.5  解的概率
6.5.1 没有约束的类

■ 一个类class里面的随机数有很多个解,要想得到某一种类型的解,可能需要上千次的随机化;

6.5.2 关系操作

   ■ 某一变量的值依赖于另一变量的值;

6.5.3 关系操作和双向约束

6.5.4 使用solve...before 约束引导概率分布

■solve...before约束不会改变解的个数,只会改变各个值的概率分布

■ 除非对某些值出现的概率不满意,否则不要使用solve...before;

6.6  控制多个约束块

■  使用内建的constraint_mode( )函数打开关闭约束;

■ handle.constrain.constrain_mode( )控制一个约束块,handle.constraint_mode()控制对象的所有约束

eg:p.c_short.constraint_mode(0) ;//禁止c_short约束产生长包

    p.constraint_mode(0);   //禁所有的约束

6.7  有效性约束

    ■ 设置多个约束以保证随机激励的正确性,是一总很好的随机化技术,被称为“有效性约束”;   

■ 例如,总线的“读-修改-写”命令只允许操作长字数据长度;

6.8  内嵌约束

■ SV允许使用randomize()with来增加额外的约束;

■ 在使用randomize()with语句时,常犯的错误是使用()括号内嵌的约束,而没有使用{},即,约束快应该使用{},所以内嵌的约束块也应该使用{ }

eg:Transaction t;

    assert(t.randomize( )with {addr >=50;addr <= 1500;data< 10;});

6.9  pre_randomize和post_randomize函数

■ 用于在随机化之前做一些动作,比如设置类class里的一些非随机变量(例如上下限权重),或者随机化之后需要计算随机数据的误差校正位;

■ pre_randomize和post_randomize是void类型函数,没有返回值,不消耗时间;如果想在pre_randomizepost_randomize函数里调用调试程序,则调试程序必须是函数类型;

6.9.1 构造鱼缸型分布

  ■ 某些应用中可能需要一些非线性随机分布,如短包长包比中等长度的包更容易发现缓冲器缓冲溢出类型的错误;

      eg: $dist_exponential非线性分布函数;

  

6.9.2  关于void函数

     ■Pre_randomize和post_randomize函数,只能调用其他函数不能调用消耗时间的,所以在执行randomize()函数的期间无法产生一段延时;

6.10  随机数函数
  • $random( ) ---平均分布,返回32位符号随机数
  • $urandom( )---平均分布,返回32位符号随机数;
  • $urandom_range( ) ---在指定范围内的平均分布;
  • $dist_exponential( ) --指数衰落;
  • $dist_normal ( ) ---钟型分布;
  • $dist_poisson( ) ---钟型分布;
  • $dist_uniform( )---平均分布;

例6.32  使用$urandom_range函数

  a=$urandom_range(3,10); //值的范围是3~10

  a=$urandom_range(10 , 3) ;    //值的范围是3~10

  b=$urandom_range(5);    //值的范围是0~5

6.11  约束的技巧和技术
6.11.1  使用变量的约束
6.11.2  使用非随机值

    ■ 如果只有几个变量需要修改,可以使用rand_mode函数把这些变量设置为非随机变量;

6.11.3  用约束检查值的有效性

■ 调用handle.randomize(null)函数时,systemverilog会把所有的变量当作非随机变量,仅仅检查这些变量是否满足约束条件;

6.11.4  随机化个别变量

6.11.5  打开或关闭约束

     ■ 可以使用条件操作符(—>或if - else)来构建由非随机变量控制的约束;

     ■ 针对复杂的约束,可以对没一种指令建立一套独立的约束,在使用时关闭其他所有的约束;

6.11.6  在测试过程中使用内嵌约束
6.11.7  在测试过程中使用外部约束

■ 函数的函数体可以在函数的外部定义,同样,约束的约束体也可以在类的外部定义;

6.11.8  扩展类

■扩展类里定义的约束的名字与基类里的约束名字相同,那么扩展的约束将取代基类的约束;

6.12  随机化的常见错误
6.12.1  小心使用有符号变量

■ 除非必要,要在随机约束里使用有符号类型;//bit

6.12.2  提高求解器性能的技巧

   ■ 如果需要除以乘以2的幂次方,则使用右移左移操作;

6.13  迭代和数组约束

   ■ 用foreach约束和一些数组函数,可以改变值的分布形态;

6.13.1  数组的大小

■ $size( )函数,可以约束动态数组队列的元素个数

■ 使用inside约束可以设置数组大小的下限和是上限

6.13.2  元素的和

■ 例如,可以使用sum()函数约束随机数组只有四个位有效,从而产生在十个周期内发送四个数据的脉冲信号。

这里要注意数组元素个数的保存精度。因为但比特元素的和正常情况下也是单比特。

6.13.3  数组约束的问题

■ 数组求和约束的三种方法:bad_sum1、bad_sum2、bad_sum3、bad_sum4

6.13.4  约束数组和队列的每一个元素

■ 使用foreach约束动态数组和队列;

6.13.5  产生具有唯一元素值的数组

6.13.6  随机化句柄数组

6.14  产生原子激励和场景

6.15  随机控制
6.16  随机数发生器
6.16  随机器件配置

     ■ 测试DUT的一个重要工作是测试DUT内部设置和环绕DUT的系统配置,测试应该随机化环境,这样才能保证尽可能测试足够多的模式;

  •    第七章      线程及线程间的通信

■ 时序逻辑通过时钟来激活,组合逻辑的输出则随着输入的变化而变化;

■ 所有的并发活动,在Verilog的寄存器传输级上是通过initialalways块语句、实例化连续赋值语句来模拟的;

■ systemverilog的调度器通过选择下一个要运行的线程来完成这种同步。

■ 每个线程总是会跟相邻的线程通信,常见的线程间通信有标准的Verilog事件事件控制wait语句sv信箱旗语等;

■ 线程的量级比进程小,其代码和存储区可共享。

7.1  线程的使用

■ 测试平台隶属于程序块,代码总是以initial块启动,从时刻0开始执行;

■ Verilog对语句有两种分组方式---使用begin...endfork...join

   ◆ begin ...end以顺序方式执行;

   ◆ fork ...join中的语句则以并发方式执行,必须等fork...join内的所有语句都执行完之后,才能继续块内后续的处理;

■ SV引入新的创建线程的方法---使用fork...join_none和fork...join_any语句;

■ 测试平台通过已有的结构如事件@事件控制waitdisable语句,以及新的语句元素(如旗语信箱),来实现线程间通信同步以及对线程的控制

7.1.1  使用fork ... join和begin ... end

■ 由于fork...join块里的代码都是并发执行的,所以带短时延的语句执行的比带长延时的语句

■ fork...join里面的语句执行未完,join后面的语句被阻塞;

7.1.2  使用fork...join_none来产生线程

■ 块内语句并行执行,join_none后面的语句同该模块同时执行;

■ join_none块后的那个语句的执行早于fork...join_none内的任何语句;

7.1.3  使用fork...join_any来产生线程

■ 块内语句并行执行,当块内有一条语句执行完后,join_any后面语句开始执行;

7.1.4  在类中创建线程
7.1.5  动态线程

■ 在SV中,可以动态地创建线程,而且不用等到它们都执行完毕

7.1.6  线程中的自动变量

■ 在并发线程中务必使用自动变量来保存数值;

例7.9 不良代码:在循环中内嵌fork...join_none

   program  no_auto;

       initial begin

           for(int j = 0 ;j <3 ; j++)

             fork

                $write(j);     //漏洞--得到的是最终的索引值

             join_none

       # 0 $display(“\n”);

      end

endprogram

7.1.7  等待所有衍生线程

■ 在SV中,当程序中的initial块全部执行完毕,仿真器就退出了;

■ 有些线程运行时间比较长,可以使用wait fork语句来等待所有子线程结束;

例 7.14  使用wait fork等待所有子线程结束

    task run_threads;

       ...                  创建一些事务

   fork  

       check_trans(tr1) ;     //产生第一个线程

       check_trans(tr2) ;    //产生第二个线程

       check_trans(tr3) ;    //产生第三个线程

   join_none

  ....                   //完成其他的工作

  //在这里等待上述线程结束

   wait fork;

endtask

7.1.8  在线程间共享变量

   ■ 在包含所有变量使用的最小范围内声明所有的变量;

   ■ 对索引变量的声明应放在for循环内部而不是在程序或整个作用域的层级上;

   ■ 尽可能使用foreach语句;

7.2  停止线程

   ■ Verilog中的disable语句可以用于停止SV中的线程;

7.2.1 停止单个线程

例 7.16  停止一个线程

     fork:timeout_block

        begin

            wait (bus.cb.addr = tr.addr);

            $display(“@ %0t:Addr match %d” , $time , tr.addr);

        end

     join_any

     disable timeout_block;

     end

7.2.2 停止多个线程

      ■ SV引入disable fork语句能够停止从当前线程中衍生出来的所有子线程

      ■ 应该使用fork...join把目标代码包围起来以限制disable fork语句的作用范围;

   

例 7.17  限制disable fork的作用范围

   initial begin

         check_trans( tr0) ;      //线程0

   //创建一个线程来限制disable fork的作用范围

    fork         //线程1

         begin

             check_trans(tr1);   //线程2

             fork                //线程3

                check_trans(tr2);   //线程4

             join

        // 停止线程1-4,单独保留线程0

        # (TIME_OUT/2) disable fork;

       end

  join

end

          

例7.18  可以使用带标号的disable来停止线程

   initial  begin

         check_trans(tr0) ;    //线程0

         fork

             begin:threads_inner

                    check_trans(tr1);

                    check_trans(tr2);

            end

         //停止线程2和3,单独保留线程0

   #(TIME_OUT/2) disable threads_inner;

     join

end

              

7.2.3  禁止被多次调用的任务
7.3  线程间的通信

■ 所有数据交换和控制的同步被称为线程间的通信(IPC),在SV中可使用事件旗语信箱来完成;

7.4  事件

■ Verilog事件可以实现线程的同步,在Verilog中,一个线程总是要等待一个带@操作符的事件。这个操作符是边沿敏感的,所以它总是阻塞着,等待事件的变化;

■ 在verilog中,其它线程可以通过—>操作符来触发事件,解除对第一个线程的阻塞

■ 在Verilog中,当一个线程在一个事件上发生阻塞的同时,正好另一个线程触发了这个事件,则竞争的可能性便开始了;

■ 在SV中,事件event成为同步对象的句柄,可以传递给子程序;

■ sv引入triggered( )函数,用于查询某个事件是否已被触发,包括当前时刻,线程可以在等待这个函数的结果,而不用在@操作符上阻塞;

■ 事件的触发有两种方式,边沿触发以及电平触发。

边沿触发只在触发的那一瞬间有效。电平触发相当于只要事件触发后,triggered就一直有效果。

7.4.1 在事件的边沿阻塞
7.4.2 等待事件的触发

■ 可以使用电平敏感wait(e1 .triggered( ))来替代边沿敏感的阻塞语句@e1;如果事件在当前时间步已经被触发,则不会引起阻塞。否则,会一直等到事件被触发为止;

7.4.3 在循环中使用事件

 ■ 可以使用事件来实现两个线程的同步;

 ■ 如果在循环中使用wait(handshake.triggered( )),一定确保在下次等待之前时间可以向前推进,否则代码将进入一个零时延循环,原因是wait会在单个事件触发器上反复执行;

◆ wait(handshake . triggered( ));

◆ @handshake;

■ 如果需要在同一时刻发送多个通告,则就不应该使用事件,而应该使用其他内嵌排队机制的线程通信(IPC)方法,如旗语和信箱;

7.4.4 传递事件

  ■ SV中的事件event可以像参数一样,传递给子程序;

  

function new(event done);    //从测试平台中传来事件

7.4.5 等待多个事件

 ■ 在所有线程按顺序完成的情况下,可以在父线程中使用for循环来等待每个事件;

 ■ 线程不按顺序完成,则创建一个新线程并从中衍生子线程,然后保证每个线程阻塞在每个发生器的一个事件event上;(可以使用wait fork

      

例 7.27 使用wait fork等待多个线程

例7.28 通过对触发事件进行计数来等待多个线程

例 7.29 使用线程计数来等待多个线程

7.5  旗语

   ■ 使用旗语可以实现对同一资源的访问控制;

   ■ 旗语可被视为一个互斥体,用于实现对同一资源访问控制

   ■ 当测试平台中存在一个资源,如一条总线,对应这多个请求方,而实际物理设计中又只允许单一驱动时,便可使用旗语

   ■ 在SV中,一个线程如果请求“钥匙”而得不到,则会一直阻塞,多个阻塞的线程会以先进先出(FIFO)的方式进行排队

7.5.1 旗语的操作

   ■ 旗语有三种基本操作;

       ◆ 使用new方法可以创建一个带单个或多个钥匙的旗语;

       ◆ 使用get可以获取一个或多个钥匙,而put则可以返回一个或多个钥匙;

       ◆ 如果试图获取一个旗语而希望不被阻塞,可以使用try_get( )函数,返回1表示有足够多的钥匙,而返回0则表示钥匙不够;

    

     例7.30 用旗语实现对硬件资源的访问控制

         program automatic test (bus_ifc . TB bus) ;

             semaphore sem;      //创建一个旗语

             ....

           task sendTrans ;

                sem.get(1);  //获取总线钥匙

                @bus.cb ;   //把信号驱动到总线上

                bus.cb.addr <= t.addr;

              ...

              sem.put(1);     //处理完成时把钥匙返回

           endtask

        endprogram

7.5.2  带多个钥匙的旗语

      ■ 多个不同的请求混在一起,可以自己编写一个,这样谁取得优先权会比较清楚;

  ■ 旗语就是一个类。这个类应该是Driver封装好的。

7.6  信箱

     ■ 对信箱的最简单理解是把它看成一个具有源端收端FIFO源端把数据放进信箱收端则从信箱中获取数据

     ■ 信箱可以有容量上的限制,也可以没有;

     ■ 信箱满放则阻塞,信箱空取则阻塞;

     ■ 信箱mailbox是一种对象,必须调用new函数来进行实例化,例化是有一个可选的参数size,用以限制信箱中的条目,如果size是0或者没有指定,则信箱是无线大的,可以容纳任意多的条目;

     ■ 使用put 任务task可以把数据放入信箱里,而使用get任务task,则可以移除数据。

     ■ 如果信箱为,则put会阻塞,信箱为,则get会阻塞

     ■ peek 任务可以获取对信箱里数据拷贝不移除它

     ■ 可以在信箱里放入句柄,但是对象

     ■ 务必在一个信箱里只放一种类型的数据;

   “每个句柄都指向了不同的对象”的发生器称为“蓝图模式

    

■ 在访问信箱时,可以使用try_get( )try_peek( )函数避免出现阻塞,如果函数执行成功, 则会返回一个非零值,否则返回0;

7.6.1 测试平台里的信箱
7.6.2 定容信箱

     ■ 缺省情况下,信箱类似容量不限的FIFO;

     ■ 任何大于0的容量便可创建一个“定容信箱”;

7.6.3 在异步线程间使用信箱通信
7.6.4 使用定容信箱和探视(peek)来实现线程的同步;
7.6.5 使用信箱和事件来实现线程的同步
7.6.6 使用两个信箱来实现线程的同步

■ 对两个线程进行同步另一种方式是再使用一个信箱,把消费方的完成信息发回给生产方;

7.6.7  其他的同步技术

■ 通过变量或旗语来阻塞线程,同样可以实现握手;

■ 事件是最简单的结构,其次是通过变量阻塞;

■ 旗语相当于第二个信箱,但没有信息交换;

7.7  构筑带线程并可实现线程间通信的测试程序
7.7.1 基本的事务处理器
7.7.2 配置类

■ 配置类允许在每次仿真时对系统的配置进行随机化

7.7.3 环境类
7.7.4 测试程序
7.8 结束语

■ 线程间可以使用事件旗语信箱,以及经典的@事件控制wait语句来实现通信和同步;

■ disable命令可以用来中止线程

  • 第八章  面对对象编程的高级技巧指南

■ 继承通过增加新的特性提供了可重用性,并且不需要修改基类;

8.1 继承简介
8.1.1  事务基类

                        源地址

                        

                        目的地址

    变量

                        八个数据字

                         CRC变量

事务基类                 显示内功子程序

              子程序

                   

计算CRC子程序

例 8.1  事务(Transaction)基类

class Transaction;

    rand bit[31:0] src ,dst ,data[8] ;  //随机变量

    bit[31:0] crc ;   //计算得到的CRC值

    

    virtual function void calc_crc;

         crc = src^dst^data.xor;

    endfunction

    

    virtual function void display (input string prefix = “ ”);

         $display (“%s Tr : src = %h , dst = %h , crc = %h”,prefix , src ,dst ,crc );

    endfunction

endclass

8.1.2  Transaction类的扩展

例 8.2  扩展的Transaction类

      class Badtr extends Transaction;

           rand bit bad_crc;

       virtual function void calc_crc;

    super.calc_crc( );   //计算正确的CRC (通过使用super前缀调用基类中的calc_crc函数)

           if(bad_crc)crc = ~crc;  //产生错误的CRC位

       endfunction

      

       virtual function void display(input string prefix = “”);

           $write (“%sBadTr:bad_crc = %b,”,prefix , bad_crc);

           super.display( );

       endfunction

■ 可以调用上一层类class的成员,但SV不允许用类似super.super.new方式进行多层调用

■  将类中的子程序定义成虚拟virtual的,这样就可以在扩展类重定义。这一点适用于所有的任务task和函数function,除了new函数;

8.1.3  更多的OOP术语

■ OOP中类的变量称为属性(property);

■ OOP中的任务task或者函数function称为方法(method);

■ 基类是不从任何其他类派生得到的类;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值