第六章. 随机化
6.1 哪些对象需要随机化

随机时需考虑设计输入的各个方面,器件配置,环境配置,原始输入数据,封装后的输出数据,协议异常,延时,事务状态,错误和违例等情况。

6.2 SV中的随机化
//带有随机变量的简单类
class Packet;rand bit [31:0] src, dst, data[8];randc bit [7:0] kind;//src的约束constraint c    {src > 10; src < 15;}
endclassPacket p;initialbeginp = new();assert (p.randmize());else $fatal(0, "Packet::randomize failed");transmit(p);end

使用rand/randc修饰符修饰的变量为随机变量,表示每次随机化这个类时(调用randmize()函数),这些变量都会被随机赋一个值(满足约束条件)。randc表示周期性随机,即当所有可能值没有全部被取到前不会出现数据重复的问题。约束是一组用来确定变量的值的范围的关系表达式。约束表达式式放在括号中,而没有放在begin-end块之间,这是由于这段代码是声明性质的,而不是程序性质的。随机化函数randomize()在遇到约束方面的问题时返回0。

不能在类的构造函数里随机化对象,因为在随机化前可能需要打开或者关闭约束,改变权重,添加新的约束。**构造函数是用来初始化对象的变量,不能调用randmize()函数。**类中的所有变量都应该设置为随机的(random)和公有的(public),这样测试平台才能最大程度地控制DUT。

randomize()函数为类里所有的randrandc类型的随机变量赋一个随机值,并保证不违背所有有效的约束。当你的代码中有矛盾的约束时,随机化过程会失败,所以一定要检测随机化的结果(可以使用立即断言)。如果不检查,变量可能会赋未知的值,导致仿真的失败

**约束表达式的求解是由SV的约束求解器完成的。**求解器能够选择满足约束的值,这个值由SV的PRNG(伪随机数生成器,即随机种子确定后生成随机数可通过公式计算出来,产生的每个随机数概率相同)从一个初始值(random seed)产生。如果SV仿真器每次使用相同的初始值,相同的测试平台,那么仿真的结果也是相同的。各种仿真器的求解器都是不同的,因此使用不同的仿真器时受约束的随机测试得到的结果也有可能不同,甚至同一个仿真器的不同版本的仿真记过也不相同。

6.2.2 哪些数据可以被随机化

可以随机化整型,随机只能产生二值数据类型(0/1),尽管随机变量类型为四值类型。可以使用整数和位矢量,但是不能使用随机化字符串,或在约束中指向句柄

6.3 约束

每个表达式里至少有一个变量必须是randrandc类型的随机变量。

//除非age随机化恰巧在允许的范围内,否则会报错
class Child;bit [31:0] age;constraint c_teenager { age > 12;age < 20;}
endclass

在一个表达式中最多只能出现一个关系操作符(<,<=,==,>=,>)

class order;rand bit [7:0] lo, med, hi;constraint bad {lo < med < hi; }    //错误约束constraint good {lo < med;         //正确约束med < hi;}
endclass

此时求解器会按照从左至右的顺序分隔成两个关系吧表达式:((lo < med) < hi)。lo < med的结果是0或1,然后判断hi大于0或1。

因为约束块里只能包含表达式,所以不能在约束块里进行赋值(=)。应该使用关系运算符为随机变量赋一个固定的值,如len == 42;

6.3.1 权重分布

关键字dist,值或者权重可以是常数或者变量。值可以是一个值或值的范围,例如[lo:hi]。权重不用百分比表示,权重的和也不必是100。权重符号有:=(表示值范围内的每一个值的权重都是相同的),:/(表示权重要均分到范围内每一个值)。要产生带权重的分布,正确的做法是使用dist操作符。

rand int src, dst;
constraint c_dist {//0,1,2,3的权重依次为40/220,60/220,60/220,60/220src dist {0:=40, [1:3]:=60};//0,1,2,3的权重依次为40/100,20/100,20/100,20/100dst dist {0:/40, [1:3]:/60};}

6.3.2 集合set成员和inside运算符

使用inside运算符产生一个值的集合。如果不存在其他约束,SV在值的集合里取随机值,各个值的选取机会是相等的。在集合里可以使用变量。

rand int c;
int lo, hi;
constraint c_range {c inside {[lo:hi]};     //lo <= c 且 c <= hi
}

可选的范围由lo和hi决定,可以采用这种方法使约束参数化,实现不修改约束,测试平台就可以改变激励发生器的行为。如果lo>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 <= 127e inside {[$:4], [20:$]};    //0 <= e <= 4 || 20 <= b <= 63
}
//如果想选择一个集合之外的值,只需要用取反操作符!对约束取反
constraint c_range {! (c inside {[lo:hi]}); // c < lo || c > hi
}

6.3.3 在集合中使用数组

rand int f;
int fib[5] = '{1,2,3,5,8};
constraint c_fibonacci {f inside fib;
}

集合中的每一个值取出的概率都是相同的,即使值在数组中出现多次

class Weighted;rand int val;int array[] = '{1,2,3,3,5,8,8,8,8,8};
endclass

6.3.4 条件约束

SV支持两种关系操作符:->if-else

6.3.5 约束的双向性(并行性)

SV中的约束是双向的,约束块不像自上而下执行的程序性代码,他们是声明性的代码,是并行的,所有的约束表达式同时有效。

6.3.6 替换数学运算符来提高效率

约束求解器可以有效的处理简单的数学运算,如加,减,位移和移位。约束求解器对于32位数值的乘法,除法和取模运算的运算量是非常大的。SV中任何没有显式声明位宽的常数都是作为32位数值对待的。可以利用移位运算代替除法和取模运算,以提高约束求解器的效率。

6.4 解的概率

SV并不保证随机约束求解器能给出准确的解,但可以干预解的概率分布。

6.4.1 没有约束的类

class Unconstrained;rand bit x;rand bit [1:0] y;
endclass

经过千次随机化,x,y的每个值的取值概率是相同的。

6.4.2 关系操作

class Imp1;rand bit x;rand bit [1:0] y;constraint c_xy {(x==0) -> y==0;}
endclass

x=0,y=0的概率为1/2,x=1,y=0,x=1,y=1,x=1,y=2,x=1,y=3的概率各为1/8。此求解器将x=0时,y为0,1,2,3的四种情况都归到了x=0,y=0的情况中。

6.4.3 关系操作和双向约束

class Imp2;rand bit x;rand bit [1:0] y;constraint c_xy {y > 0;(x==0) -> y==0;}
endclass

因约束是双向的,所以x的值为1,y值为1,2,3,每种组合的概率各为1/3。

6.4.4 Solve…Before

使用solve...before可以改变值出现的概率,除非对某些值出现的概率不满意,否则不要使用solve...before块,过度使用slove...before会降低计算的速度,也会使你的约束让人难以理解。(不推荐使用)

6.5 控制多个约束块

一个类可以包含多个约束块,可以把不同的约束块用于不同的测试,例如一种约束用来限制数据的长度,用于产生小的事务,另一种约束用来产生大的事务。在运行期间,可以使用内建的constraint_mode()函数打开或者关闭约束,可以用handle.constraint_mode()控制一个约束块,用handle.constraint_mode()控制对象的所有约束。

class Packet;rand int length;constraint c_short {length inside {[1:32]}; }constraint c_long  {length inside {[1000:1023]}; }
endclassPacket P;
initial
beginp = new();//关闭c_short,产生长事务p.c_short.constraint_mode(0);assert (p.randomize());transmit(p);//打开c_short,产生短事务p.constraint_mode(0);p.c_short.constraint_mode(1);assert (p.randomize());transmit(p);
end
6.6 有效性约束

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

class Transcationrand enum {BYTE, WORD, LWRD, QWRD} length;rand enum {READ, WRITE, RMW, INTR} opc;constraint valid_RMW_LWRD {(opc == RMW) -> length == LWRD;}
endclass
6.7 内嵌约束

约束越来越多时,他们会互相作用,最终产生难以预测的结果。SV允许使用randomize() with来增加额外的约束,这和在类里增加约束是等效的。

class Transaction;rand bit [31:0] addr, data;constraint c1 { addr inside{[0:100], [1000:2000]}; }
endclassTransaction t;initial begint = new();assert(t.randomize() with {addr >= 50; addr <= 1500;data < 10;});driveBus(t);arrser(t.randomize() with {addr == 2000; data > 10;});driveBus(t);
end

此代码和在现有的约束上增加额外的约束是等效的。如果约束之间存在冲突,可以使用constraint_mode()函数禁止冲突的约束。**在with{}语句中,SV使用了类作用域,使用了addr而不是t.addr。**在使用randmize() with语句时常犯的错误是使用()包括内嵌的约束,而没有使用{}。约束块内应该使用{},所以内嵌约束也应该使用{},{}用于声明性的代码

6.8 pre_randomize和post_randomize函数

有时需要在调用randmize()函数之前或之后立即执行一些操作,如在随机化之前设置类里的非随机变量(如上下限,权重),或者随机化之后需要计算随机数据的误差校正位。SV可以使用两个特殊的void类型的pre_randomize和post_randomize函数来完成这些功能。

6.9 随机数函数

下面是一些常用的随机数函数:

$random()–平均分布,返回32位有符号随机数

$urandom()–平均分布,返回32位无符号随机数

$urandom_range(low,upper)–发挥[low, upper]范围(包括上下限)内的无符号数

$dist_exponential()–指数衰落

$dist_uniform()–平均分布

a = $urandom_range(3,10);       //值的范围是3~10
a = $urandom_range(10,3);      //值的范围是3~10
b = $urandom_range(5);         //值的范围是0~5
6.10 约束的技巧和技术

6.10.1 使用变量的约束

//可以通过改变max_size的值来改变约束上限
class bounds;rand int size;int max_size = 100;constraint c_size {size inside {[1:max_size]};}
endclass//带有权重变量的dist约束
typedef enum {READ8, READ16, READ32}    read_e;
class ReadCommands;rand read_e read_cmd;int read8_wt = 1, read16_wt = 1, read32_wt = 1;constraint c_read {read_cmd dist {READ8 := read8_wt,READ16 := read16_wt,READ32 := read32_wt };}
endclass

6.10.2 使用非随机值

如果用一套约束在随机化的过程中已经产生了几乎所有想要的激励向量,但是还缺少几种激励向量,可以采用先调用randomize函数,然后再把随机变量的值设置为固定的期望值的方法来解决。

class Packet;rand bit [7:0] length, payload[];constraint c_valid {length > 0;payload.size == length;}
endclassPacket p;
initial
beginp = new();assert(p.randomize());p.length.rand_mode(0);p.length = 42;assert (p.randomize());
end

6.10.3 用约束检查值的有效性

在随机化一个对象并改变它的变量的值后,可以通过检查值是否遵守约束来检查对象是否仍然有效,在调用handle.randomize(null)函数时,SV会把所有的变量当作非随机变量,仅仅检查这些变量是否满足约束条件。

6.10.4 随机化个别变量

可以在调用randomize函数只传递变量的一个子集,这样就只会随机化类里的几个变量。只有参数列表里的变量才会被随机化,其他变量会被当作状态变量而不会被随机化。

class Rising;byte low;rand byte med, hi;constraint up {low < med;med < hi;}
endclassinitial
beginRising r;r = new();r.randomize();     //随机化med,hi,但不改变lowr.randomize(med);  //随机化medr.randomize(low);   //随机化low
end

6.10.5 打开或关闭约束

可以使用constraint_mode打开或关闭约束

class Instruction;typedef enum {NOP, HALT, CLR, NOT} opcode_e;rand opcode_e opcode;...constraint c_no_oprands {opcode == NOP || opcode == HALT; }constraint c_one_operand {opcode == CLR || opcode == NOT; }
endclassInstruction instr;
initial
begininstr = new();instr.constraint_mode(0);                   //关闭所有约束instr.c_no_operands.constraint_mode(1);     //打开c_no_operands约束assert (instr.randomize());instr.constraint_mode(0);                 //关闭所有约束instr.c_one_operand.constraint_mode(1);     //打开c_one_operands约束assert (instr.randomize());
end

6.10.6 使用内嵌约束

如前问所述,SV可以使用randmize() with{}内嵌约束来改变约束。

6.10.7 外部约束

函数的函数体可以在函数的外部定义,同样,约束的约束体也可以在类的外部定义,可以在一个文件里定义一个类,这个类只有一个空的约束,然后在每个不同的测试里定义这个约束的不同版本以产生不同的激励。

class Packet;rand bit [7:0] length;rand bit [7:0] payload[];constraint c_valid {length > 0;payload.size() == length;}
endclass//类外部定义约束 test.sv文件
program automatinc test;include "packet.sv" constraint Packet::c_external {length == 1;}...
endprogram

外部约束和内嵌约束相比具有很多优点,外部约束可以放在另一个文件里,从而在不同的测试里可以复用外部约束外部约束对类的所有实例都起作用,而内嵌约束仅仅影响一次randomize()调用。需要注意,外部约束只能增加约束,而不能改变已有的约束。和内嵌约束一样,因为外部约束可能分布在多个文件里,所以可能导致潜在的问题。

6.10.8 扩展类

通过拓展类(继承),可以在测试平台中使用一个已有的类,然后切换到增加了约束,子程序和变量的拓展类。

6.11 随机化的常见错误

除非必要,不要在随机约束里使用有符号类型

class SignedVars;rand byte pkt1_len, pk2_len;constraint total_len {pkt1_len+pk2_len == 64;}
endclass//为了避免得到负的包长这样无意义的值,应该使用无符号随机变量
class Vars32;rand bit [7:0] pkt_1_len, pk2_len; //无符号类型constraint total_len {pkt1_len + pk2_len == 9'd64;}
endclass

6.11.1 提高求解器性能的技巧

避免使用复杂的运算,例如除法,乘法和取模

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

如果需要进行这些操作,使用宽度小于32位的变量可以得到更高的运算性能。

6.12 迭代和数组约束

数组的大小可以用size()函数进行约束,此函数可以约束动态数组和队列的元素个数。

//使用inside约束可以设置数组大小的上限和下限
class dyn_size;rand logic [31:0] d[];constraint d_size {d.size() inside {[1:10]}; }
endclass

6.12.1 元素的和

//可以用sum()函数约束随机数组只有四个位有效
parameter MAX_TRANSFER_LEN = 10;class StrobePat;rand bit strobe[MAX_TRANSFER_LEN];constraint c_set_four { strobe.sum() == 4'h4}
endclass

6.12.2 对每个元素进行约束

SV中可以用foreach对数组的每一个元素进行约束,和直接写出对固定大小的数组的每一个元素的约束相比,使用foreach要更简洁。

class goo_sum5;rand unit len[];constraint c_len {foreach(len[i])len[i] inside {[1:255]};len.sum < 1024;len.size() inside {[1:8]};}
endclass//使用foreach产生递增的数组元素的值
class Ascend;rand unit d[10];constraint c {foreach (d[i])       if(i>0)d[i]>d[i-1];       //i=0时i-1=-1???}
endclass

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

class UniqueSlow;rand bit [7:0] ua[64];constraint c {foreach (ua[i])foreach (ua[j])if(i!=j)ua[i] != ua[j];}
endclass//更好的办法是使用randc辅助类产生唯一的元素值
class randc8;randc bit [7:0] val;
endclassclass LittleUniqueArray;bit [7:0] ua [64];function void pre_randomize;randc8 rc8;rc8 = new();foreach(ua[i])beginassert(rc8.randomize());ua[i] = rc8.val;endendfunction
endclass

6.12.4 随机化句柄数组

如果需要产生多个随机对象,那么你可能需要建立随机句柄数组。和整数数组不同,你需要在随机化前分配所有的元素,因为随机求解器不会创建对象。

parameter MAX_SIZE = 10;class RandStuff;rand int value;
endclassclass RandArray;array = new [MAX_SIZE];constraint c {array.sise() inside {[1:MAX_SIZE]}; }function new();array = new [MAX_SIZE];foreach (array[i])array[i] = new();endfunctionRandArray ra;initialbeginra = new();assert (ra.randomize());foreach (ra.array[i])$display(ra.array[i].value);end
endclass
6.13 产生原子激励和场景

产生事务序列的一个方法是SV的randsequence结构。

initial
beginfor (int i = 0; i < 15; i++)beginrandsequence(stream)stream : cfg_read := 1 | io_read := 2 | mem_read := 5;cfg_read : {cfg_read_task;} | {cfg_read_task} cfg_read;mem_read : {meme_read_task;} | {mem_read_task;} mem_read;io_read : {io_read_task;} | {io_read_task;} io_read;endsequenceend
end

随机sequence序列会从三种操作中选取一种。

6.14 随机控制

6.14.1 使用randcase$urandom_range的随机控制

initial
beginint len;randcase1: len = $urandom_range(0, 2);8: len = $urandom_range(3, 5);1: len = $urandom_range(6, 7);endcase
end

使用randcase的代码会比随机约束的代码更难修改和重载。修改随机代码结果的位移方法是修改代码或使用权重变量。

6.14.2 可以使用randcase建立决策树

initial
begin//一层决策randcaseone_write_wt: do_one_write();one_read_wt:  do_one_read();seq_write_wt: do_seq_write();seq_read_wt:  do_seq_read();endcase//二层决策task do_one_write;randcasemem_write_wt: do_mem_write();io_write_wt: do_io_write();cfg_write_wt: do_cfg_write();endcaseendtask
end
6.15 随机数发生器

6.15.1 伪随机数发生器

V使用一种简单的PRNG(伪随机数发生器),通过$random函数访问,这个发生器有一个内部状态,可以通过$random的种子来设置。下面是一个简单的PRNG,它并不是SV使用的PRNG,这个PRNG有一个32位的内部状态,要计算下一个随机值,先计算出状态的64位平方值,取中间的32位数值,然后加上原来的32位数值。

reg [31:0] state = 32'h12345678;
function logic [31:0] my_random;logic [63:0] s64;s64 = state * state;state = (s64>>16) + state;
endfunction

6.15.2 随机稳定性,多个随机发生器

V在整个仿真过程中使用一个PRNG,但如果SV仍然使用这种方案,测试平台通常会有几个激励发生器同时运行,为被测设计产生数据,如果两个码流共享一个PRNG,他们获得的都是随机数的一个子集。由于是从一个PRNG获取随机数据,所以改变其中一个类的随机数值个数会影响到另一个类获取的数值在SV中,每个对象和线程都有一个独立的PRNG,改变一个对象不会影响其他对象获得的随机数。

6.15.3 随机稳定性和层次化种子

SV的每个对象都有自己的PRNG和独立的种子。当启动一个新的对象或线程时,**子PRNG的种子由父PRNG产生,**所以在仿真开始时的一个种子可以产生多个随机激励流,他们之间又是相互独立的。

6.16 随机器件配置

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

参考文献:
SystemVerilog验证 测试平台编写指南(原书第二版)张春 麦宋平 赵益新 译

SV绿皮书笔记(六)相关推荐

  1. SV绿皮书笔记(九)暂时完结

    第九章. 功能覆盖率 9.1覆盖率类型 功能覆盖率:功能覆盖率和设计意图是紧密相连的.用来衡量哪些设计特性已经被测试程序测试过的一个指标. 代码覆盖率:包括行覆盖率,路径覆盖率,翻转覆盖率,状态机覆盖 ...

  2. SV绿皮书笔记(四)

    第四章. 连接设计和测试平台 4.1 测试平台和DUT之间通信 DUT和测试平台(Test)通常是分开的模块(module,描述硬件),可以在顶层(top)中将DUT和Test例化,然后根据对应信号进 ...

  3. SV绿皮书笔记(七)

    第七章. 线程以及线程间的通信 在实际硬件中,时序逻辑通过时钟沿来激活,而组合逻辑的输出则随着输入的变化而变化.所有这些并发的活动在V的寄存器传输级上是通过initial和always块语句,实例化和 ...

  4. SV绿皮书笔记(五)

    第五章. 面向对象编程基础 5.1 OOP概述 V属于过程性编程语言(代码逐行执行,无数据结构,类似C语言),V中没有结构,只有位向量和数组.而在对总线事务建模时往往需要数据结构,使用过程性语言不够便 ...

  5. SV绿皮书笔记(二)

    第二章. 数据类型 2.1 基本数据类型的两个属性 双状态/四状态:根据存储数据类型中的每一bit位的可能数分为双状态类型和四值状态类型. 双状态:可能值0,1,双状态默认初始值为0.双状态值具有更低 ...

  6. SV绿皮书笔记(三)

    第三章. 过程语句和子程序 3.1 循环语句 continue:在循环中跳出本轮循环剩下的语句直接进入下一轮循环. break:用于终止并跳出循环 bit[127:0] cmd; int file, ...

  7. SV绿皮书笔记(一)

    第一章. 验证导论 1.1 验证概述 验证的目的:保证设计文件能够完成预期的功能,寻找设计漏洞. 验证计划:参考设计文档,描述需要验证哪些特性,采用什么样的技术. 验证的流程:设计文档(自然语言)→设 ...

  8. Ethernet/IP 学习笔记六

    Ethernet/IP 学习笔记六 EtherNet/IP defines two primary types of communications: explicit and implicit (Ta ...

  9. 吴恩达《机器学习》学习笔记六——过拟合与正则化

    吴恩达<机器学习>学习笔记六--过拟合与正则化 一. 过拟合问题 1.线性回归过拟合问题 2.逻辑回归过拟合问题 3.过拟合的解决 二. 正则化后的代价函数 1.正则化思想 2.实际使用的 ...

最新文章

  1. [转]Hadoop家族学习路线图
  2. 基于Spring Boot和Spring Cloud实现微服务架构学习--转
  3. Scala声明类及创建类的对象
  4. 华为云OCR文字识别 免费在线体验!
  5. OpenCV学习笔记:基础结构
  6. 三星内存编码_内存条上面的编码的含义
  7. Spring AOP动态代理实现,解决Spring Boot中无法正常启用JDK动态代理的问题
  8. 泛微E8调整附件大小和属性
  9. Oracle项目管理平台的多层级组织架构设置方法
  10. android强制开启深色模式bug,强制深色模式软件
  11. app android切图工具,小白自学APP切图:APP切图工具Cutterman的参数设置
  12. 动态主题模型(Dynamic Topic Models, DTM)
  13. python弹幕点歌_GitHub - smilecc/blive-raspberry: 完全重构的树莓派B站直播弹幕点播台...
  14. Java的“跨平台”特性
  15. python docx_python-docx 入门
  16. python数据可视化案例2017年6省gdp_吴裕雄 数据挖掘与分析案例实战(5)——python数据可视化...
  17. 5G安全的全球统一认证体系和标准演进
  18. 时空复杂度之珠心算测验
  19. android开发笔记之高德地图使用
  20. html5 加速球 效果,css 渐隐渐现、echarts 圆环图、百度地图覆盖物、echarts水球图(360加速球效果)...

热门文章

  1. python将英语中的复数名词变成单数名词
  2. my fav html
  3. C语言 带参数宏定义中 # 和 ## 知识点总结、代码分析
  4. 技术探究 通用场景描述(Universial Scene Description, USD)
  5. UT010034: Stream not in async mode
  6. 2019年11月14日
  7. 晶硅太阳能发电 原理
  8. 简历修订中,下载打开需密码
  9. android视频播放器流程图,Android OpenGL ES 10.1 视频播放器
  10. 前瞻: 拥抱量子计算时代!详解2020年全球十大杰出量子计算公司