在芯片验证中有一个很重要的思想:CDV(覆盖率驱动验证),通过分析覆盖率的达标情况来驱动验证活动的进行。为了实现CDV,我们就需要有一个能够随机的激励源。systemverilog为我们提供了很多随机的手段,比如:提供随机值的函数$random,$urandom,$urandom_range;对对象的随机obj.randomize等等,理解这些随机背后的机制有助于我们创建出更稳定的验证环境以及更快地解决环境中的随机问题。
一、随机的分类
按照随机的实现不同,我们可以将systemverilog中的随机分为两大类:
- Constrained random value generation:
这部分可以继续细分,主要由以下三部分构成:
–对对象的随机obj.randomize()
–对范围变量(scope variables)的随机std::randomize()
–生成随机数的系统函数$urandom(),$urandom_range()
这一类随机的最重要的特点是,它们是由EDA vendor实现的。也就是说,不同的EDA vendor可能会有不同的实现方式。对于用户而言,体现在验证环境在不同的仿真工具下运行就可能会得到不同的结果。顺便一提的是,$urandom(),$urandom_range()与std::randomize()的最大区别在于,前者只能产生32bit的无符号数,而后者可以对更大范围的数据进行随机。 - Probabilistic distribution functions:
不同于Constrained random value generation,Probabilistic distribution functions已经由IEEE的规范明确,甚至于你可以在systemverilog的LRM(language reference manual)的Annex N看到具体实现的C代码。这些函数包括:$dist_uniform,$dist_normal,$dist_exponential,$dist_poisson,$dist_chi_square,$dist_t,$dist_erlang,$random。实际上,$random只是有明确参数的$dist_uniform函数而已。
需要注意的是,$random与$urandom,$urandom_range虽然名称上相似,但是实现是完全不同的。对于$random,如果你提供了相同的随机种子,在synopsys公司的vcs和cadence公司的xcellium上会得到相同的随机结果。但是$urandom和$urandom_range则不会。
二、RNG介绍
RNG即Random Number Generator,由程序代码来模拟随机,所以有时也称为PRNG(P-Pseudo)。RNG和我们数字电路中的PRBS概念类似,它的输入是一个种子号(seed),运算的输出是一个新的种子号(new seed)。为了方便使用,RNG一般会将输出的种子号作为下一次的输入,在这样的机制下,我们只需要为RNG指定一个初始的种子号(initial seed),RNG自己就会不停地运转了。在RNG中,还有一个重要的概念:randstate,它表征了RNG当前的状态,不同于种子是以数字的形式呈现,randstate则是以字符串的形式体现出来的。可以说randstate和seed是一体两面的关系。
不管是Constrained random value generation还是Probabilistic distribution functions,它们都是由RNG来产生随机数据的。RNG存在于objects和threads(processes)中,为了让这些objects/threads的随机相互不影响,每个object和thread都拥有自己独立的RNG。这也意味着如果这些RNG的初始种子不变,即使环境存在一定的改动,那么现有的随机结果也不会被改变。
三、与RNG有关的函数
虽然大多数时候RNG对验证人员不可见,但是systemverilog还是开放了一些函数,让我们能干预RNG的行为。
$srandom(seed)
$srandom是systemverilog class的内建(in-built)函数,用户可以利用该函数来重新设定object中RNG的种子。下面是$srandom函数原型和示例:
function void srandom( int seed );
class eth_xaction;
int xaction_size;
function random_test();
randomize(xaction_size);
$display("After randomize, xaction_size=%0d", xaction_size);
endfunction
endclass
program testcase();
eth_xaction e;
initial begin
e = new();
$display("Random test part1:");
e.random_test();
e.random_test();
$display("Random test part2:");
e.srandom(100);
e.random_test();
e.random_test();
e.srandom(100);
e.random_test();
e.random_test();
end
endprogram
仿真结果如下:
Random test part1:
After randomize, xaction_size=-384116807
After randomize, xaction_size=1637914715
Random test part2:
After randomize, xaction_size=1547241734
After randomize, xaction_size=1905892576
After randomize, xaction_size=1547241734
After randomize, xaction_size=1905892576
此外,在process中也内建了srandom函数,用法类似:
program testcase();
process p;
initial begin
int a;
p = process::self();
p.srandom(100);
a = $urandom(); $display("After randomize, a=%0d", a);
p.srandom(100);
a = $urandom(); $display("After randomize, a=%0d", a);
end
endprogram
仿真结果:
After randomize, a=-158961
After randomize, a=-158961
$urandom(seed)
$urandom函数除了可以用于生成32bit的无符号数外,还可以通过指定种子的方式来干预RNG的行为。下面是$urandom函数原型和示例:
function int unsigned $urandom [ (int seed ) ] ;
program testcase();
int a;
initial begin
$display("Random test part1:");
a = $urandom(100); $display("After urandom, a=%0d", a);
a = $urandom(); $display("After urandom, a=%0d", a);
a = $urandom(); $display("After urandom, a=%0d", a);
$display("Random test part2:");
a = $urandom(100); $display("After urandom, a=%0d", a);
a = $urandom_range(10); $display("After urandom, a=%0d", a);
a = $urandom(); $display("After urandom, a=%0d", a);
end
endprogram
仿真结果如下:
Random test part1:
After urandom, a=-158961
After urandom, a=-1155446854
After urandom, a=1955256730
Random test part2:
After urandom, a=-158961
After urandom, a=3
After urandom, a=1955256730
需要注意的是,$urandom函数中100代表的是种子号;$urandom_range函数中的10则是随机范围的限定。在这个例子中,由于$urandom和$urandom_range函数位于同一个initial块中,它们会共享同一个RNG,因此调用$urandom_range函数会让RNG运转一次,但是不影响第三次随机的结果(a=1955256730)。
get_randstate()/set_randstate()
上面介绍的$srandom函数可以通过设定种子干预object/thread内RNG的状态,而使用get_randstate()/set_randstate()也可以达到类似效果,只不过这两个函数的返回值和参数是字符串。下面是关于这两个函数的简单例子:
class eth_xaction;
rand int xaction_size;
endclass
program testcase();
initial begin
eth_xaction e;
string e_randstate;
e = new();
$display("Class random test part1:");
e_randstate = e.get_randstate();
$display("e_randstate=%s", e_randstate);
e.randomize(); $display("After randomize, xaction_size=%0d", e.xaction_size);
e.randomize(); $display("After randomize, xaction_size=%0d", e.xaction_size);
$display("Class random test part2:");
e.set_randstate(e_randstate);
e.randomize(); $display("After randomize, xaction_size=%0d", e.xaction_size);
e.randomize(); $display("After randomize, xaction_size=%0d", e.xaction_size);
end
initial begin
process p;
string p_randstate;
int a;
p = process::self();
$display("Process random test part1:");
p_randstate = p.get_randstate();
$display("p_randstate=%s", p_randstate);
a = $urandom(); $display("After randomize, a=%0d", a);
a = $urandom(); $display("After randomize, a=%0d", a);
$display("Process random test part2:");
p.set_randstate(p_randstate);
a = $urandom(); $display("After randomize, a=%0d", a);
a = $urandom(); $display("After randomize, a=%0d", a);
end
endprogram
仿真结果如下:
Class random test part1:
e_randstate=Z11XZXX10XZ01Z1Z100Z10X01XX01Z1ZXXXXXXZXZZZZZXZXZXXXXZXZZZZZXZXZ
After randomize, xaction_size=-384116807
After randomize, xaction_size=1637914715
Class random test part2:
After randomize, xaction_size=-384116807
After randomize, xaction_size=1637914715
Process random test part1:
p_randstate=X111001Z01XXZ1ZXXX010XZ1X1100Z1XXXXZXXZXZZZXXZZZXXXZZZXXXZXZZZXZ
After randomize, a=992600595
After randomize, a=1764109936
Process random test part2:
After randomize, a=992600595
After randomize, a=1764109936
四、RNG Initialize & Hierarchical seeding
RNG Initialize
所有的module instance、interface instance、program instance和package都会有一个初始RNG。这个初始RNG会有一个默认的种子。IEEE把初始种子的设定权交给了EDA vendor,因此不同EDA工具有不同的设置方式:比如synopsys的vcs使用+ntb_random_seed=seed来设定初始种子;而Mentor的Questasim使用-sv_seed=seed来设定初始种子。
Hierarchical seeding
在前文中我们提到过每个object/thread都拥有属于自己的RNG,这些RNG的种子都是从它们的父节点获取的。当一个object/thread被创建时,专属RNG也会被创建,同时这个RNG的初始种子会被设定为父节点RNG下一次的随机结果。下面是关于hierarchical seeding的一个例子:
class eth_xaction;
rand int xaction_size;
endclass
program testcase();
eth_xaction e;
int a;
initial begin
//e = new();
for(int i=0; i<3; i++) begin
a = $urandom();
$display("a=%0d", a);
end
end
endprogram
仿真结果如下:
a=98710838
a=1474208060
a=-1098913923
当我们解除对 //e=new(); 这句话的注释后,仿真结果如下:
a=1474208060
a=-1098913923
a=816460770
可以看到,object e的创建消耗了父节点RNG的一次随机机会,导致a的随机结果往后延了1次。
五、Random stability
在验证趋于CDV的时代,错误可复现这个概念显得越来越重要。具体来说就是当我们的测试用例发现问题后,有时候我们会去修改我们的RTL代码或者验证环境来解决问题。但是对RTL代码或者验证环境的修改可能会导致原有测试激励发生变化,从而无法复现问题,进而无法说明该问题是被正确修复了还是无法复现了。因此我们希望RTL代码和验证环境能更加稳定,一些细微的修改不会影响环境中的随机结果。这个特性被称为Random stability。
Random stability in systemverilog
Thread stability
下面是systemverilog LRM提供的示例:
integer x, y, z;
fork //set a seed at the start of a thread
begin process::self.srandom(100); x = $urandom; end
//set a seed during a thread
begin y = $urandom; process::self.srandom(200); end
// draw 2 values from the thread RNG
begin z = $urandom + $urandom ; end
join
Tread stability($urandom/$urandom_range/std::randomize)体现在两方面:
- 每个thread的随机相互不影响:x、y、z的随机结果彼此独立;
- 每个thread的初始种子来自于parent thread:y、z的结果由parent thread决定。
Object stability
Object stability(randomize())和Tread stability的表现基本一致,这里不再展开详细描述。
在谈及Random stability时,我们会发现漏掉了Probabilistic distribution functions这类函数的描述。实际上对于$random()这类函数,它们在所有的thread中共享同一个RNG。这一点很重要,因为共享同一个RNG会让验证环境的稳定性变差。因此,在环境中应该更多地使用$urandom函数,而不是$random函数。
Random stability in UVM
在systemverilog中,虽然每个object/thread的随机相互不影响,但是它们的初始种子都来自于父节点的下一次随机。如果改变了这些object/thread的创建先后顺序,那么即使父节点的RNG不变,子节点获取的种子也会发生变化。UVM考虑到这部分仍然会对环境的稳定性造成影响,因此重新开发了一套随机机制,保证了uvm组件的RNG与组件创建的先后顺序无关。这套机制原理如下:
当uvm组件被创建时,会调用uvm_object中的reseed()函数,利用uvm组件的type_name、full_name以及uvm_global_random_seed来生成一个初始种子,并把这个种子通过$srandom()赋予这个uvm组件的RNG。相关的uvm源码如下:
uvm_object.svh
// Variable: use_uvm_seeding
//
// This bit enables or disables the UVM seeding mechanism. It globally affects
// the operation of the reseed method.
//
// When enabled, UVM-based objects are seeded based on their type and full
// hierarchical name rather than allocation order. This improves random
// stability for objects whose instance names are unique across each type.
// The <uvm_component> class is an example of a type that has a unique
// instance name.
static bit use_uvm_seeding = 1;
// Function: reseed
//
// Calls ~srandom~ on the object to reseed the object using the UVM seeding
// mechanism, which sets the seed based on type name and instance name instead
// of based on instance position in a thread.
//
// If the <use_uvm_seeding> static variable is set to 0, then reseed() does
// not perform any function.
extern function void reseed ();
......
// reseed
// ------
function void uvm_object::reseed ();
if(use_uvm_seeding)
this.srandom(uvm_create_random_seed(get_type_name(), get_full_name()));
endfunction
uvm_misc.svh
// Variable- uvm_global_random_seed
//
// Create a seed which is based off of the global seed which can be used to seed
// srandom processes but will change if the command line seed setting is
// changed.
//
int unsigned uvm_global_random_seed = $urandom;
function string uvm_instance_scope();
byte c;
int pos;
//first time through the scope is null and we need to calculate, afterwards it
//is correctly set.
if(uvm_instance_scope != "")
return uvm_instance_scope;
$swrite(uvm_instance_scope, "%m");
//remove the extraneous .uvm_instance_scope piece or ::uvm_instance_scope
pos = uvm_instance_scope.len()-1;
c = uvm_instance_scope[pos];
while(pos && (c != ".") && (c != ":"))
c = uvm_instance_scope[--pos];
if(pos == 0)
uvm_report_error("SCPSTR", $sformatf("Illegal name %s in scope string",uvm_instance_scope));
uvm_instance_scope = uvm_instance_scope.substr(0,pos);
endfunction
function int unsigned uvm_create_random_seed ( string type_id, string inst_id="" );
int newSeed;
if(inst_id == "")
inst_id = "__global__";
type_id = {uvm_instance_scope(),type_id};
newSeed = vc_uvmCreateRandomSeed ({type_id,"::",inst_id}, uvm_global_random_seed);
return newSeed;
endfunction
uvm_seed.svh
extern "C" int vc_uvmOnewayHash ( string string_in, int seed );
extern "C" int vc_uvmCreateRandomSeed (string string_in, int seed);
六、参考文献
1、SystemVerilog Randomization & Random Number Generation
2、SystemVerilog Random Stability
3、Random Stability
4、urandom_range(), urandom(), random() in verilog