记录几个SystemVerilog的语法——随机
1. 随机稳定性(random stability)
随机稳定性是指每个线程(thread)或对象(object)的random number generator(RNG)是私有的,一个线程返回的随机值序列与其他线程或对象的RNG是无关的。随机稳定性适用于以下情况:
- 系统随机方法调用:$urandom()和$urandom_range();
- 对象和进程设置种子方法:srandom();
- 对象随机化方法:randomize();
在用户代码发生小改动时,Testbench带有这个特性就可以有更稳定的RNG行为。另外,可以通过手动调整线程和对象的seed,这样就可以更精确控制随机值的产生。
随机稳定性包含以下特性:
- Initialization RNG(初始化RNG):每个module例化、interface例化、program例化和package都有一个初始化RNG。每一个初始化RNG都有默认的seed,它的值实现定义。一个初始化RNG用于创建静态进程和静态初始化器(initializer)。
- Thread stability(线程稳定性):每一个线程都有一个独立的RNG,该线程中调用随机化的所有系统都会使用这个RNG。当一个新的动态线程创建时,它RNG的seed值是从parent线程生成的下一次随机值,该方式称为hierarchical seeding。对于一个静态进程创建时,它RNG的seed值是从声明该进程的初始化RNG (来自module instance、interface instance、program instance或者package)的下一个随机值来的。程序和线程的随机稳定性可以通过保持之前创建线程和RNG的顺序一致即可。当给现存testcase增加新的线程时,可以加在代码块的末尾,这样来保持之前创建流程的随机数稳定性。
- Object stability(对象稳定性):每一个类的例化(object)都有一个独立的RNG,用于该类内部所有随机化方法。当一个object创建时,它RNG的seed是来源于创建该object的线程。当一个object是被静态初始化器创建时,它RNG的seed将会来源于该初始化RNG(来自module instance、interface instance、program instance或者package)的下一个随机值,因为这时候没有active线程提供seed的。对象稳定性可以通过保持之前创建object和RNG的顺序一致即可。为了保持RNG稳定性,新objects、线程和随机数的创建应该在现存objects创建之后进行。
- Manual seeding(手动设置种子):所有没有初始化的RNGs可以手动设置seed。结合hierarchical seeding,该机制可以用于在一个subsystem的root thread定义一个简单seed,然后这个seed会作用到整个subsystem的运行。
2. 线程稳定性(Thread stability)例子
$urandom系统调用返回的随机值与线程执行顺序无关。如下例子:
integer x, y, z;forkbeginprocess::self.srandom(100); // set a seed at the start of a threadx = $urandom;endbeginy = $urandom;process::self.srandom(200); // set a seed during a threadendbeginz = $urandom + $urandom ; // draw 2 values from the thread RNGendjoin
上述程序片段演示了一下几个特性:
- Thread locality(线程局部性):x、y和z的返回值与线程的执行顺序无关。这是一个很重要的特性,因为它允许开发独立、可控和可预测的子系统;
- Hierarchical seeding(分层设置种子):在创建线程时,将使用父线程的下一个随机值作为种子初始化它的随机状态。因此,这三个fork引起的线程都是从父线程中得到种子的;
每个线程都有一个唯一的种子值,仅由其父线程决定。线程执行的根(root)决定了它的子节点随机种子。这就允许设置根线程来自动和保留子节点线程的行为。
3. 对象稳定性(Object stability)例子
每个类中内置的randomize()可以确保对象的稳定性,这使得在一个实例中调用randomize()独立于其它实例中调用randomize(),并且独立于调用其它randomize()函数。例如:
class C1;rand integer x;endclassclass C2;rand integer y;endclassinitial beginC1 c1 = new();C2 c2 = new();integer z;void'(c1.randomize());// z = $random;void'(c2.randomize());end
- c1.x和c2.x返回的值是互相独立的;
- randomize()的调用是独立于$random系统调用。如果没有注释掉上述z=$random,那么分配给c1.x和c2.y的值也不会发生变化;
- 每个实例都有一个可以独立设置种子的唯一的随机值源。该随机种子可以从父线程创建实例时获取;
- Object可以在任何时候使用srandom()方法进行设置种子;
class C3function new (integer seed);//set a new seed for this instancethis.srandom(seed);endfunctionendclass
一旦对象被创建,就不能保证创建该对象的线程可以在另一个线程访问该对象之前更改该对象的随机状态。因此,对象最好从它们内部new()构造函数而不是从外部进行自我设置种子。
一个object的seed可以在任何线程里面改变。但是一个线程的seed只能在线程内部修改的。
4. 手动进行随机化设置例子
每个对象都维护自己内部的RNG,该RNG仅由其randomize()方法调用。这就允许对象独立于其它对象和调用其它系统随机化函数进行随机化。当一个对象被创建时,它的RNG将使用创建该对象的线程的RNG中的下一个值作为种子,这个过程称为分层对象设置种子(hierarchical object seeding)。
有时需要使用srandom()方法手动设置对象RNG的种子,这可以在类方法中完成,也可以在类定义外部完成。一个在类内部设置RNG种子的例子为:
class Packet;rand bit[15:0] header;...function new (int seed);this.srandom(seed);...endfunctionendclass
从外部设置RNG种子的例子为:
Packet p = new(200); // Create p with seed 200.p.srandom(300); // Re-seed p with seed 300.
在对象的new()函数中调用srandom()确保对象的RNG在任何类成员值随机化之前使用新设置的种子。在类方法内部设置会比较靠谱点。
5. 唯一性约束(uniqueness constraints)
可以使用unique关键字来约束一组变量的随机值,使得该组变量再随机之后,任何两个变量的值都不一样。能使用该特性的变量类型有整型scalar变量、整型unpacked数组变量的一个leaf element或一片(slice) leaf element或全部leaf element。Leaf element指的是unpacked数组变量拆解到最末尾非unpacked数组类型。例子如下:
rand byte a[5];rand byte b;rand byte excluded;constraint u { unique {b, a[2:3], excluded}; }constraint exclusion { excluded == 5; }
这上述这个例子中,a[2], a[3], b和excluded变量的值在随机之后将会都不一样。因为在exclusion 块中excluded被约束为5,所以a[2], a[3]和b的值将都不会是5。
6. 迭代约束(iterative constraints)
迭代约束允许数组变量使用循环变量、索引表达式或数组缩减法(array reduction methods)去约束。Foreach迭代约束语法如下:
foreach ( ps_or_hierarchical_array_identifier [ loop_variables ] ) constraint_setloop_variables ::= [ index_variable_identifier ] { , [ index_variable_identifier ] }
例子如下:
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; }endclass
C1块约束了A的每个约束在{2,4,8,16},C2块约束了A的每个元素值大于它index的两倍。
循环变量与数组索引之间的映射是由维度的基数所决定的,如下所示:
// 1 2 3 3 4 1 2 -> Dimension numbersint A [2][3][4]; bit [3:0][2:1] B [5:1][4];foreach( A [ i, j, k ] ) ...foreach( B [ q, r, , s ] ) ...
第一个foreach会使i从0到1迭代,j从0到2迭代,k从0到3迭代。第二个foreach会使q从5到1迭代,r从0到3迭代,s从2到1迭代。
对于动态数组和队列来说,数组大小也可以被约束的,如果数组大小约束和迭代约束同时发生,那么会先求解数组大小约束,然后求解迭代约束。还有一个就是要防止迭代超出数组大小,用户必须自己排除这些无效的索引。比如下面例子的k<A.size-1就是防止数组索引溢出。
class C;rand int A[] ;constraint c1 { A.size inside {[1:10]}; }constraint c2 { foreach ( A[ k ] ) (k < A.size - 1) -> A[k + 1] > A[k]; }endclass
数组缩减法可以根据unpacked数组的元素值产生一个整型值,如:
class C;rand bit [7:0] A[] ;constraint c1 { A.size == 5; }constraint c2 { A.sum() with (int'(item)) < 1000; }endclass
c2约束块将会翻译成:
( int'(A[0])+int'(A[1])+int'(A[2])+int'(A[3])+int'(A[4]) ) < 1000
7. soft约束
与soft约束相对立的是hard约束,求解器solver必须要满足hard约束,否则solver求解失败。Soft约束可以被其它hard约束块或更高优先级的soft约束块覆盖掉,因此,soft约束通常用于给随机变量指定默认值或分布。
Soft约束之所以可以被其它soft约束覆盖掉,是因为soft约束之间有不同的优先级,在越后面约束的soft约束优先级越高,因此,验证环境中子层次比父层次具有更高的优先级。另外,如果只对soft约束求解,那么该次调用求解可以永远不失败。如果soft约束求解没有失败,那么求解的结果相当于是对hard约束求解一样。
Soft约束可以被discard掉,也就是不使能,使用disable soft语法可以将比该定义disable的约束块低优先级的soft约束都失效掉。比如:
class A;rand int x;constraint A1 { soft x == 3; }constraint A2 { disable soft x; } // discard soft constraintsconstraint A3 { soft x inside { 1, 2 }; }endclassinitial beginA a = new();a.randomize();end
A2约束块要求求解器忽略随机变量x所有优先级较低的soft约束,从而导致A1约束块被忽略。因此,求解器只需要满足最后的A3约束。上述示例将导致随机变量x取值为1或2。需要注意的是,如果省略了A3约束块,那么变量x将处于无任何约束的状态。
8. 随机化方法
每一个类内部都自带有randomize()这个virtual方法,它的定义如下:
virtual function int randomize();
randomize()是一个virtual方法,它为类对象中所有active随机变量提供了随机值,当然随机值要符合active约束。如果随机化成功,那么返回1;反之,返回0。
每一个类也自带pre_randomize()和post_randomize()方法,注意这两个方法不是virtual的。pre_randomize()是在randomize()之前调用的,post_randomize()是在randomize()之后调用的。定义如下:
function void pre_randomize();function void pre_randomize();
如果randomize()失败,那么post_randomize()也不会被调用的。如果randomize()失败,那么随机变量要保持原来的值。
9. In-line约束
In-line约束是指用randomize with的方法去随机类中的变量。当randomize()调用时没有传参数,那么它内部所有active 随机变量都要随机新值。当randomize()调用时传参数了,那么这些传的参数是该object指定随机变量的全集,其余随机变量都当作state variables。如下:
class CA;rand byte x, y;byte v, w;constraint c1 { x < v && y > w );endclassCA a = new;a.randomize(); // random variables: x, y state variables: v, wa.randomize( x ); // random variables: x state variables: y, v, wa.randomize( v, w ); // random variables: v, w state variables: x, ya.randomize( w, x ); // random variables: w, x state variables: y, v
据以上可知,randomize()传参可以改变类内部的任何属性,设置没有定义为rand或randc的都可以。不过该机制对于randc是无效的,它不能将非随机变量变成randc,也不能将randc变成non-randc的。
如果调用randomize()里传null参数,那么表示该次调用没有任何随机变量需要随机赋新值的,那么该方法相当于是一个checker,它只是返回随机状态,这样就可以用于检查内部约束块是否正确。如:
success = a.randomize( null ); // no random variables
10. local范围解析
在使用randomize() with constraint block的时候,可以引用类属性或调用方法中(randomize with)的local变量。如果这些变量没有加限制,那么工具会先搜寻object 类的内部,然后才去搜去调用方法(包含method)的范围。这样的话,如果object类和调用方法的范围内有相同名字的变量,且想要用local的就比较麻烦了,因此SV提供了local::去修改搜寻顺序。当一个变量采用local::标识了,那么工具会去local 范围内找,忽略object类内部的变量。如下例子:
class C;rand integer x;endclassfunction int F(C obj, integer x);F = obj.randomize() with { x < local::x; };endfunction
在obj.randomize() 调用的in-line约束块中,没有限定x则会绑定到C类属性(即正在被随机化的对象的范围内),而限定名称local::x则会绑定到函数F()的参数(局部作用域)。
11. 控制随机变量和约束块
rand_mode()可以用于控制是否将随机变量处于active或inactive状态。当随机变量是inactive时,那么它就像是没有被rand或randc修饰的变量一样。Inactive变量不会被randomize()随机赋值的,它们的值会被solver当作state variables。所有的随机变量的初始态都是active的。rand_mode()方法的定义如下:
task object[.random_variable]::rand_mode( bit on_off );orfunction int object.random_variable::rand_mode();
这里object是指任何能够获取到定义该随机变量的对象句柄的表达式。random_variable是指要对其执行操作的随机变量的名称。在作为任务调用时,可以未指定random_variable,则该操作将应用于指定对象内的所有随机变量。task方式是用于设定variable的mode,参数传1为active,参数传0为inactive。function方式时用于返回随机变量的mode,返回1表示active,返回0表示inactive。
constraint_mode()可以用于控制一个约束块是否是active或inactive的。如果约束块是inactive的,那么randomize()在调用时不会考虑该约束块的了。所有约束块的初始状态时active。constraint_mode()方法的定义如下:
task object[.constraint_identifier]::constraint_mode( bit on_off );orfunction int object.constraint_identifier::constraint_mode();
这里object是指任何能够获取定义该约束块所对应的对象句柄的表达式。constraint_identifier是指要被操作的约束块名字。如果constraint_identifier省略掉,那么object内部的所有constraints都一块设定。task方式用于设定约束块的mode,传1表示active,传0表示inactive。function方式时用于返回约束块的mode,返回1表示active,返回0表示inactive。
12. 随机数系统方法
系统函数$urandom()在每次调用时会返回一个32-bit的unsigned的伪随机数。定义如下:
function int unsigned $urandom [ (int seed ) ] ;
seed是可选的,seed可以是任何整数表达式。如果seed一致,那么random number generator(RNG)会产生相同的随机数序列。RNG是确定的,每一次程序执行时,它会从同一个随机序列周期性的取值。不过我们可以通过指定$urandom的不同seed来使得它变得不确定,如将每一天的时间作为seed。
系统函数$urandom_range()会返回在指定范围内的无符号整数。定义如下:
function int unsigned $urandom_range(int unsigned maxval, int unsigned minval = 0 );
它将返回maxval到minval之间的数,如果minval省略掉,那么范围是maxval到0。如果maxval小于minval,那么会自动翻转,确保第一个参数不小于第二个参数。
srandom()可以用于手动设置object或thread的RNG的种子。srandom()方法使用给定种子的值初始化对象的RNG。定义如下:
function void srandom( int seed );
还有get_randstate()和set_randstate()是用于获取或设置object的RNG的状态。