欢迎光临
我们一直在努力

SV学习记录(六)

目录

6.1 什么需要随机化

器件配置

环境配置

原始输入数据

封装后的输入数据

协议异常、error和violation

延迟

6.2 sv中的随机

带有随机变量的class

检查随机后的结果

约束求解器(constraint solver)

什么可以随机

6.3 约束

简单表达式

权重分布

集合(set)成员和inside操作符

条件约束

双向约束

选用合适的运算符

6.4 解的概率

->是逻辑表达式

双向传播

solve…before

6.5 多约束块控制

6.6 有效约束

6.7 内嵌约束

6.8 pre_randomize和post_randomize函数

构造浴缸型分布

关于void函数

6.9 随机数函数

6.10 约束技巧和技术

用变量约束

用非随机值

用约束检查取值

随机化部分变量

开/关约束

使用内嵌约束

使用外部约束

扩展类

6.11 随机化的常见错误

小心使用有符号变量

提高solver的性能小技巧

6.12 迭代和数组约束

数组大小

元素之和

数组约束的问题

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

产生具有不同元素值的数组

随机化handle array

6.13 原子激励vs场景激励

基于历史的原子发生器

随机序列

随机object数组

组合序列

6.14 随机控制

randcase

randcase决策树

6.15 随机数发生器

伪随机数发生器(PRNG)

多个发生器

层次化种子

6.16 随机器件配置


6.1 什么需要随机化

通常能想到的是输入数据随机化,但这只能测出数据通路上的问题。我们还需要关注控制点。

下面是我们设计输入的各个方面

器件配置

我们的设计被用户使用的时候,要接多少个输入设备,多少个输出设备,这都是不确定的,所以在测试过程中,要考虑如果有多个不相关通道接了大量设备会不会出问题。

环境配置

我们的器件通常会和其他许多器件一起工作,testbench需要模拟这种多器件工作的情形。包括objects的数量以及它们如何配置。

原始输入数据

最容易想到的,但需要涉及协议的各个层次和故障注入。

封装后的输入数据

许多器件会处理协议的不同层次。每个层次都有自己的控制域,我们需要测试不同的组合,通过约束产生有效控制域,同时允许故障注入。

协议异常、error和violation

你需要预测哪里可能会出错,设计规范的边界处行为。比如两个器件通信时,如果通信中断会怎么样?正确的设计应该能够正常处理

延迟

测试平台应该合理利用延迟,使用随机,合法的延迟检测出设计中的问题。一些设计可能对时钟敏感,可以通过配置上升沿前后移动来模拟clock jitter检测是否正常。另外,之前说过时钟发生器应该在测试平台之外,但是它的频率,偏移应该是可配置的。

再次注意,你要测试的是功能性错误,而非时域错误。

6.2 sv中的随机

最好的随机是事务层的随机,而非一次性的随机值。

带有随机变量的class

下面是一个带有随机变量和约束的class。

rand类型,在每次对class进行随机化的时候都会被随机赋一个值。

randc类型,有周期的rand,只有在所有可能性都被测试完毕后才会重复。如果现在有一个8元素array是randc类型的,那么每个元素都有自己的周期,所以一共有8个周期。

constraint是约束,描述选择变量值必须满足的要求。

randomize函数在执行失败,遇到问题时会返回0。

$fatal在打印错误信息后会结束仿真。此后不再额外写出else语句。

Sample 6.1 Simple random class
class Packet;
  // The random variables
  rand bit [31:0] src, dst, data[8];
  randc bit [7:0] kind;
  // Limit the values for src
  constraint c {src > 10;
  src < 15;}
endclass

Packet p;
initial begin
  p = new();// Create a packet
  assert (p.randomize())
  else $fatal(0, "Packet::randomize failed");
  transmit(p);
end

不要在构造函数new()中做随机化,因为你可能希望开启/关闭约束,构造函数只是为了初始化的。

class中的所有变量都需要被随机化并公有。

检查随机后的结果

如果你的代码有违约束,会报错。randomize会随机化所有rand, randc变量并检查是否满足约束。

前面通过SVA来检查设计是否有误。你也可以自己写程序来检查。

约束求解器(constraint solver)

sv中的约束求解器会根据约束选出可用值,值来自PRNG(伪随机数发生器),有初始化的种子,如果你的testbench和seed都相同,结果也会是相同的。

不同solver会产生不同的向量vendor,所以不同仿真器的结果可能不同,相同仿真器的不同版本结果也可能不同。

什么可以随机

整型变量,就是常见的bit位。只能随机化为0/1。字符串不行,handle变量不能写进constraint。

截至2007(书里写的年份),实数随机化还比较困难,不能出现x==0.333这类句子。

但是在2023中已经加入了real变量,能解决了。

6.3 约束

简单表达式

好的简单表达式只能有一个比较符,下面的是不好的

Sample 6.4 Bad ordering constraint
class order;
rand bit [7:0] lo, med, hi;
constraint bad {lo < med < hi;} // Gotcha!
endclass

约束中不能有assign语句,但是可以用==来等效起作用,比如下面的都是合法的

len==42
len == header.addr_mode* 4 + payload.size()

权重分布

如果我们想控制一些值出现的概率,可以用dist。

dist中有两个操作符,第一个是:=,这个操作符左侧是取值或取值范围,右侧是它对应的权重,如果是取值范围,如下的src,那么其中每个值的权重都是右边的值。

第二个是:/,区别只在于左侧是取值范围时,右侧的值并不是每个取指的权重,而是取值范围的权重,每个取值的取值权重平分它。

对于两种操作符,右侧的权重加和都不要求是100。

Sample 6.7 Weighted random distribution with dist
rand int src, dst;
constraint c_dist {
  src dist {0:=40, [1:3]:=60};
  // src = 0, weight = 40/220
  // src = 1, weight = 60/220
  // src = 2, weight = 60/220
  // src = 3, weight = 60/220
   
  dst dist {0:/40, [1:3]:/60};
  // dst = 0, weight = 40/100
  // dst = 1, weight = 20/100
  // dst = 2, weight = 20/100
  // dst = 3, weight = 20/100
}

等式的右侧不一定非得是上面展示的数字,也可以是变量,如下所示

Sample 6.8 Dynamically changing distribution weights
// Bus operation, byte, word, or longword
class BusOp;
  // Operand length
  typedef enum {BYTE, WORD, LWRD } length_e;
  rand length_e len;
   
  // Weights for dist constraint
  bit [31:0] w_byte=1, w_word=3, w_lwrd=5;
   
  constraint c_len {
  len dist {BYTE := w_byte,     // Choose a random
  WORD := w_word,     // length using
  LWRD := w_lwrd};   // variable weights
  }
endclass

对于上面的代码,我们可以通过调整三个w_*的值来控制len的取值概率,甚至可以通过将一个变量的值设为0来删除对应值的出现可能。

集合(set)成员和inside操作符

写约束时,如果你不喜欢写表达式,也可以用inside操作符实现相同的功能

这样很方便修改取值范围

Sample 6.9 Random sets of values
rand int c;             // Random variable
int lo, hi;             // Non-random variables used as limits
constraint c_range {
  c inside {[lo:hi]}; // lo <= c && c <= hi
}

如果你取值范围的上限/下限和变量的边界值一样,那么可以用$来直接表示

Sample 6.10 Specifying minimum and maximum range with $
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
}

同样,你也可以通过!操作符来选出目标集合之外的部分

Sample 6.11 Inverted random set constraint
constraint c_range {
!(c inside {[lo:hi]}); // c < lo or c > hi
}

你也可以在集合中使用数组

Sample 6.12 Random set constraint for an array
rand int f;
int fib[5] = '{1,2,3,5,8};
constraint c_fibonacci {
f inside fib;
}

书中举了一个例子,我截取了部分关键代码,对于下面这种数组,有重复值,这时候随机化选值从概率上看会怎么样呢?答案是12358被选中的概率基本相等。

Sample 6.14 Repeated values in inside constraint
class Weighted;
rand int val;
int array[] = '{1,1,2,3,5,8,8,8,8,8};
constraint c {val inside array;}
endclass

下面的代码展示另一种使用方法:定义好总的选择域,但是每次从队列中选,随机化之前把队列初始化为我们想选的值的队列。

Sample 6.16 Class to choose from an array of possible values
class Days;
  typedef enum {SUN, MON, TUE, WED,
  THU, FRI, SAT} days_e;
  days_e choices[$];
  rand days_e choice;
  constraint cday {choice inside choices;}
endclass

Sample 6.17 Choosing from an array of values
initial begin
  Days days;
  days = new();
   
  days.choices = {Days::SUN, Days::SAT};
  assert (days.randomize());
  $display("Random weekend day %s\\n", days.choice.name);
   
  days.choices = {Days::MON, Days::TUE, Days::WED,
      Days::THU, Days::FRI};
  assert (days.randomize());
  $display("Random week day %s", days.choice.name);
end

下面有一个问题,如果你想动态地向集合中添加/删除元素,要谨慎使用inside,因为每次修改集合中的元素个数,solver都会重新计算约束,可能是很大的计算量。

比如你想把集合中的元素每个都只输出一次,一种方法是删除约束中剩下的量,下面是另一种方法,用randc(有周期的随机)

Sample 6.18 Using randc to choose array values in random order
class RandcInside;
  int array[];                 // Values to choose
  randc bit [15:0] index;       // Index into array

  function new(input int a[]); // Construct & initialize
  array = a;
  endfunction
   
  function int pick;           // Return most recent pick
  return array[index];
  endfunction
   
  constraint c_size {index < array.size();}
endclass

initial begin
  RandcInside ri;
   
  ri = new('{1,3,5,7,9,11,13});
  repeat (ri.array.size()) begin
      assert(ri.randomize());
      $display("Picked %2d [%0d]", ri.pick(), ri.index);
  end
end

条件约束

如果有一些约束知在某些条件下生效,你可以用->或if-else

Sample 6.19 Constraint block with implication operator
class BusOp;
  …
  constraint c_io {
  (io_space_mode) ->
  addr[31] == 1Õb1;
  }

Sample 6.20 Constraint block with if-else operator
class BusOp;

constraint c_len_rw {
if (op == READ)
  len inside {[BYTE:LWRD]};
else
  len == LWRD;
}

双向约束

所有的约束都是同时生效,解也是同时计算出的,而不是先解出一个再求解另一个

选用合适的运算符

在sv中,加、减、位运算都是好的,乘、除、模运算都是很慢的。

比如,你现在想生成一个靠近页边界的地址值,页大小是4096 bytes,那么下面两个是等效的,但6.23更好

Sample 6.22 Expensive constraint with mod and unsized variable
rand bit [31:0] addr;
constraint slow_near_page_boundary {
addr % 4096 inside {[0:20], [4075:4095]};
}

Sample 6.23 Efficient constraint with bit extract
rand bit [31:0] addr;
constraint near_page_boundry {
addr[11:0] inside {[0:20], [4075:4095]};
}

6.4 解的概率

->是逻辑表达式

注意,->不同于if,它是逻辑表达式,意思是他会把一个约束转换为对应的逻辑。

对于下面的约束,(x==0) -> y==0 等价于 !(x==0) || (y==0)

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

因此解的概率为

双向传播

如果我们加入新的条件y>0,此时由于x=0会导致y=0,因此逻辑表达式会变为x不能等于0的样子

虽然从代码上看,是x约束y,但注意约束是双向传播的,任何变量的变化都会反向影响其他变量的变化

Sample 6.26 Class with implication and constraint
class Imp2;
  rand bit x;      
  rand bit [1:0] y;
  constraint c_xy {
      y > 0;
      (x==0) -> y==0;
  }
endclass

此时解的概率为

solve…before

它不改变解空间,但会改变解出现的概率,如字面意思,如果我们写solve x before y,sv会先以1/2的概率选择x,再选择y,如果写solve y before x,会先以1/4的概率选择y,再选择x。

Sample 6.27 Class with implication and solve…before
class SolveBefore;
  rand bit x;      
  rand bit [1:0] y;
  constraint c_xy {
      (x==0) -> y==0;
      solve x before y;
  }
endclass

一般情况下我们都不用solve…before,只有当你对解空间不满意,想更多地探索边界才会用。

6.5 多约束块控制

如果你想控制某个/某几个约束块生效,那么可以用一个函数。函数的标准形式为handle.constraint.constraint_mode(),它用来控制特定约束块; handle.constraint_mode()用来控制所有约束块,写0表示禁能1表示使能

Sample 6.28 Using constraint_mode
class Packet;
  rand int length;
  constraint c_short {length inside {[1:32]}; }
  constraint c_long {length inside {[1000:1023]}; }
endclass

Packet p;
initial begin
  p = new();
   
  // Create a long packet by disabling short constraint
  p.c_short.constraint_mode(0);
  assert (p.randomize());
   
  transmit(p);
   
  // Create a short packet by disabling all constraints
  // then enabling only the short constraint
  p.constraint_mode(0);
  p.c_short.constraint_mode(1);
  assert (p.randomize());
  transmit(p);
end

6.6 有效约束

好的约束技巧是用约束块确保随即约束的有效性。

下面的约束描述了一个事务的正确行为,如果你想违反事务来测试,只需要把它设置无效。

Sample 6.29 Checking write length with a valid constraint
class Transaction;
rand enum {BYTE, WORD, LWRD, QWRD} length;
rand enum {READ, WRITE, RMW, INTR} opc;
constraint valid_RMW_LWRD {
(opc == RMW) -> length == LWRD;
}
endclass

6.7 内嵌约束

如果有一些约束只是少数几次测试用到,大部分时候用不到的,那么反复编写他们的disable代码会比较麻烦,sv中提供了一种不把约束块写在class内部的方法:randomize() with,他后面可以接一次性的约束。

randomize() with的作用域是class,所以你看到它后面写的是addr而不是t.addr。

Sample 6.30 The randomize() with statement
class Transaction;
rand bit [31:0] addr, data;
constraint c1 {addr inside{[0:100],[1000:2000]};}
endclass

Transaction t;

initial begin
t = new();
 
// addr is 50-100, 1000-1500, data < 10
assert(t.randomize() with {addr >= 50; addr <= 1500;
    data < 10;});
     
driveBus(t);
 
// force addr to a specific value, data > 10
assert(t.randomize() with {addr == 2000; data > 10;});
driveBus(t);
end

6.8 pre_randomize和post_randomize函数

有时候我们需要在randomize之前或之后立即进行一些操作,比如随机化之前设置类中的一些非随机变量,又比如随机化之后计算随机数据的误差校正位。

构造浴缸型分布

有一些应用中我们需要非线性分布,比如浴缸型分布,它的特点是两端的概率大,中间的概率小。

$dist_exponential函数会随机在e指数函数上选取一个点,计算完毕后随机选择把它放在左边还是右边

关于void函数

pre和post这两个函数只能调用其他函数,不能调用消耗时间的任务,所以执行randomize期间无法产生一段延迟。如果想调试随机化过程中出现的问题,可以调用预先写好的void函数输出。

6.9 随机数函数

常见函数

6.10 约束技巧和技术

用变量约束

前面提到过两种,这里简单回顾,不贴出代码。

1.通过用变量设置inside的上下限

2.通过变量设置dist取值的出现概率

用非随机值

如果在产生激励的时候已经产生了大部分的激励,但是还缺少部分,这时候可以先写randomize,再通过rand_mode手动设置某个变量的值。

Sample 6.35 rand_mode disables randomization of variables
// Packet with variable length payload
class Packet;
  rand bit [7:0] length, payload[];
  constraint c_valid {length > 0;
  payload.size() == length;}
 
  function void display(string msg);
      $display("\\n%s", msg);
      $write("Packet len=%0d, payload size=%0d, bytes = ",
          length, payload.size());
      for(int i=0; (i<4 && i<payload.size()); i++)
      $write(" %0d", payload[i]);
      $display;
  endfunction
endclass

Packet p;
initial begin
  p = new();
   
  // Randomize all variables
  assert (p.randomize());
  p.display("Simple randomize");
   
  p.length.rand_mode(0);   // Make length nonrandom,
  p.length = 42;           // set it to a constant value
  assert (p.randomize()); // then randomize the payload
  p.display("Randomize with rand_mode");
end

上面的代码约束length=payload.size,通过手动设置取消length的随机属性,再手动赋值,就可以控制payload数组的大小为42。

用约束检查取值

如果你手动改变了一些变量的值,可以通过handle.ran domize(null) 函数将所有变量都视为非随机,这样就能检查取值是否合法。

随机化部分变量

如果你只想随机化一部分变量,也可以,甚至可以单独随机化没有被标记为rand类型的变量。

这种方法不太常用,有时候可以用于探索边界。

Sample 6.36 Randomizing a subset of variables in a class
class Rising;
  byte low;        
  // Not random
  rand byte med, hi; // Random variable
  constraint up
  { low < med; med < hi; } // See Section 6.4.2
endclass

initial begin
  Rising r;
  r = new();
  r.randomize();    
  // Randomize med, hi; low untouched
  r.randomize(med); // Randomize only med
  r.randomize(low); // Randomize only low
end

开/关约束

如果你想得到两类完全不同的测试,常见办法是为每一类测试建立独自的约束,在随机化的时候启用/禁用。

Sample 6.38 Turning constraints on and off with constraint_mode
class Instruction;
rand opcode_e opcode;
  constraint c_no_operands {
  opcode == NOP || opcode == HALT;}
  constraint c_one_operand {
  opcode == CLR || opcode == NOT;}
endclass

Instruction instr;
initial begin
  instr = new();
   
  // Generate an instruction with no operands
  instr.constraint_mode(0); // Turn off all constraints
  instr.c_no_operands.constraint_mode(1);
  assert (instr.randomize());
   
  // Generate an instruction with one operand
  instr.constraint_mode(0); // Turn off all constraints
  instr.c_one_operand.constraint_mode(1);
  assert (instr.randomize());
end

使用内嵌约束

前面已经介绍过,这里提一下它的问题。

1.由于内嵌约束分散在不同地方,所以当你在class内部的约束中加入新的时,可能会和某部分的内嵌约束冲突

2.这种内嵌约束难以重用

使用外部约束

外部约束和内嵌约束类似,都是可以增加约束,但它有一些优点。

1.可重用‘

2.可以对所有这个class的objects生效,而非仅仅在一次randomize中生效

如果外部约束声明了但是从来没使用会发生什么?不知道

Sample 6.39 Class with an external constraint
// packet.sv
class Packet;
rand bit [7:0] length;
rand bit [7:0] payload[];
constraint c_valid {length > 0;
payload.size() == length;}
constraint c_external;
endclass

Sample 6.40 Program defining an external constraint
// test.sv
program test;
constraint Packet::c_external {length == 1;}

endprogram

扩展类

未来会学到。有了它可以基于已有的类切换到增加了很多东西的扩展类。

6.11 随机化的常见错误

小心使用有符号变量

小心使用int,byte类有符号变量,除非你真的确定应该是有符号的。

对于下面的情况,我们可能得到32,32;也可能得到-63,127

Sample 6.41 Signed variables cause randomization problems
class SignedVars;
  rand byte pkt1_len, pk2_len;
  constraint total_len {
  pkt1_len + pk2_len == 64;
  }
endclass

你可能想那就不用有符号,换为下面的情况

也有问题,比如一个是32'h8000_0000和32'h8000_0040,相加后舍弃高位也可能符合结果

Sample 6.42 Randomizing unsigned 32-bit variables
class Vars32;
  rand bit [31:0] pkt1_len, pk2_len; // unsigned type
  constraint total_len {
  pkt1_len + pk2_len == 64;
  }
endclass

最好的办法是需要几位就给几位,尽量不要给32位这种很大的

Sample 6.43 Randomizing unsigned 8-bit variables
class Vars8;
  rand bit [7:0] pkt1_len, pkt2_len; // 8-bits wide
  constraint total_len {
  pkt1_len + pkt2_len == 9Õd64;  
  }
endclass

提高solver的性能小技巧

1.不要用乘除,如果是2次幂的操作,用移位符

2.2次幂的取模操作可以替换为带掩码的位与

3.位宽小于32位的更快

6.12 迭代和数组约束

如何对数组进行约束?你可能想到foreach,但是它的性能比较差。我们可以用以下几个方面

数组大小

用.size约束数组大小。记得要加上界,否则可能会特别大。

Sample 6.44 Constraining dynamic array size
class dyn_size;
rand logic [31:0] d[];
constraint d_size {d.size() inside {[1:10]}; }
endclass

元素之和

如果我们要在10周期内串行发送4bit数,那么就需要一个信号来指示当前周期的数据是否有效,下面的例子用的是strobe,通过控制和为4控制了有效个数。

Sample 6.45 Random strobe pattern class
parameter MAX_TRANSFER_LEN = 10;
class StrobePat;
rand bit strobe[MAX_TRANSFER_LEN];
constraint c_set_four { strobe.sum() == 4Õh4; }
endclass

initial begin
  StrobePat sp;
  int count = 0;       // Index into data array
   
  sp = new();
  assert (sp.randomize());
   
  foreach (sp.strobe[i]) begin
      @bus.cb;
      bus.cb.strobe <= sp.strobe[i];
      // If strobe is enabled, drive out next data word
      if (sp.strobe[i])
      bus.cb.data <= data[count++];
  end
end

数组约束的问题

本节讲的问题前面出现过,这里简单表述一下。如果以一个数组的和作为约束点,要求和小于1024,数组的元素个数在1-8之间。

1.如果是有符号变量,那么产生的数值可能有正有负

2.如果以无符号为变量,每个变量为8bits,那么和也会是8bits,所以产生的数值可能正常,但计算出的和并不正常

3.如果用无符号32bit,那么会有大变量导致和溢出,反而使和变小

4.如果用10bit,那么每个元素的大小经常会大于255,这也是我们不想要的。

最后,单独从和的宏观角度进行约束是不够的,还是需要对单个元素进行约束。

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

最简单的约束方法就是用foreach

Sample 6.55 Simple foreach constraint: good_sum5
class good_sum5;
rand uint len[];
constraint c_len {foreach (len[i])
                  len[i] inside {[1:255]};
                  len.sum < 1024;
                  len.size() inside {[1:8]};}
endclass

也可以用foreach约束元素之间的关系,但是这么做的话要注意边界元素

Sample 6.57 Creating ascending array values with foreach
class Ascend;
  rand uint d[10];
  constraint c {
  foreach (d[i])       // For every element
      if (i>0)         // except the first
        d[i] > d[i-1]; // compare with previous element
  }
endclass

产生具有不同元素值的数组

怎么样能够得到一个随机数组,它的元素值各不相同?你可能会想到用randc标记数组,但是这样随机化得到的数组元素值基本相同。你也可能想到在约束中写循环判断,但是这种方法的复杂度极高。

下面是一个标准方法。用一个随机标量去随机化,得到结果以后赋值给数组。

Sample 6.59 Creating unique array values with a randc helper class
class randc8;
randc bit [7:0] val;
endclass

class LittleUniqueArray;
  bit [7:0] ua [64];     //     Array of unique values

  function void pre_randomize;
      randc8 rc8;
      rc8 = new();
      foreach (ua[i]) begin
          assert(rc8.randomize());
          ua[i] = rc8.val;
      end
  endfunction
endclass

随机化handle array

handle array的随机化特点是在randomize之前要手动为每个handle创建object,也即array[i] = new()。object的内容不会被随机化。

Sample 6.63 Constructing elements in a random array
parameter MAX_SIZE = 10;
class RandStuff;
rand int value;
endclass

class RandArray;
  rand RandStuff array[];   // DonÕt forget rand!

  constraint c {array.size() inside {[1:MAX_SIZE]}; }
   
  function new();
      array = new[MAX_SIZE];   // Allocate maximum size
      foreach (array[i])
      array[i] = new();
  endfunction;
endclass

RandArray ra;
initial begin
ra = new();               // Construct array and all objects
assert(ra.randomize());   // Randomize and maybe shrink array
foreach (ra.array[i])
$display(ra.array[i].value);
end

6.13 原子激励vs场景激励

基于历史的原子发生器

你可以写一个post_randomize函数复制历史事务用于下一次随机化

这种方法对较小规模的测试比较好。

随机序列

下面的代码就是随即序列,它是程序性代码,和类中写数据和约束的代码不一样。

stream表示序列由以下三个事务组成,每个事务由各自的权重。

cfg_read是对这个事务的描写,它的行为是可能执行一次cfg_read_task;也可能是执行一次cfg_read_task再执行一次cfg_read。

Sample 6.64 Command generator using randsequence
initial begin
  for (int i=0; i<15; i++) begin
      randsequence (stream)
          stream : cfg_read := 1 |
                    io_read := 2 |
                    mem_read := 5;
          cfg_read : { cfg_read_task; } |
                      { cfg_read_task; } cfg_read;
          mem_read : { mem_read_task; } |
                      { mem_read_task; } mem_read;
          io_read : { io_read_task; } |
                      { io_read_task; } io_read;
      endsequence
  end // for
end

task cfg_read_task;

endtask

使用它有几个问题。

1.随机序列是程序性代码,有别于其他代码,需要单独维护

2.如果你想加一个事务进去,必须要改源代码,不能用扩展的方式加

随机object数组

最后一种产生随机序列的形式是随机化整个对象数组。你可以建立指向数组的前一个和后一个对象的约束,solver会同时求解所有的约束。由于整个序列同时产生,你可以在发送第一个事务前就知道所有数据的校验和、事务的总数等信息。

组合序列

你可以把多个序列组合在一起,得到一个更真实地事务流。

6.14 随机控制

randcase

Sample 6.65 Random control with randcase and $urandom_range
initial begin
int len;
randcase
  1: len = $urandom_range(0, 2); // 10%: 0, 1, or 2
  8: len = $urandom_range(3, 5); // 80%: 3, 4, or 5
  1: len = $urandom_range(6, 7); // 10%: 6 or 7
endcase
$display("len=%0d", len);
end

randcase比随即约束更难修改,如果是一个大类中的小部分,那么用随机约束要更好。

Sample 6.66 Equivalent constrained class
class LenDist;
rand int len;
constraint c
{len dist {[0:2] := 1, [3:5] := 8, [6:7] := 1}; }
endclass

LenDist lenD;
initial begin
lenD = new();
assert (lenD.randomize());
$display("Chose len=%0d", lenD.len);
end

小心使用randcase,因为它不留下任何线索,如果你想注入错误,就需要一个变量告诉scoreboard。如果在class中使用这个变量,就必须声明成rand,并用约束在不同测试中改变。

randcase决策树

Sample 6.67 Creating a decision tree with randcase
initial begin
// Level 1
randcase
    one_write_wt: do_one_write();
    one_read_wt: do_one_read();
    seq_write_wt: do_seq_write();
    seq_read_wt: do_seq_read();
endcase
end

// Level 2
task do_one_write;
randcase
    mem_write_wt: do_mem_write();
    io_write_wt: do_io_write();
    cfg_write_wt: do_cfg_write();
endcase
endtask

task do_one_read;
randcase
    mem_read_wt: do_mem_read();
    io_read_wt: do_io_read();
    cfg_read_wt: do_cfg_read();
endcase
endtask

6.15 随机数发生器

伪随机数发生器(PRNG)

state就是种子

Sample 6.68 Simple pseudorandom number generator
reg [31:0] state = 32Õh12345678;
function logic [31:0] my_random;
logic [63:0] s64;
s64 = state * state;
state = (s64 >> 16) + state;
my_random = state;
endfunction

多个发生器

如果所有线程共享一个generator,那么他们会污染彼此的数据,随机稳定性变差。

sv中为每一个object和thread配备了单独的发生器。

层次化种子

每个测试的PRNG的种子都来源于它的父种子,每个测试之间的种子都是互相独立的。

任何对于代码的修改都会改变测试的事务流,如果你在测试一半的时候因为修改代码导致某个事务不再出现了,这就不太好。解决办法是在原有代码下面新增代码,新开一个线程,这样虽然也会改变,但是会减小危害。

Sample 6.70 Test code after modification
function void build();
  pci_gen gen0, gen1;
  atm_gen agen;    
  gen0 = new();
  gen1 = new();
  gen = new();  
   
  fork
      gen0.run();
      gen1.run();
      gen.run();  
  join
endfunction : build

6.16 随机器件配置

将“测试平台的硬件配置”封装成一个独立的随机类,然后在仿真流程的不同阶段(生成、构建、运行)使用这个随机结果,从而高效地验证 DUT 在不同配置模式下的行为。

比如,下面定义了4个以太网口的配置

Sample 6.71 Ethernet switch configuration class
class eth_cfg;
rand bit [ 3:0] in_use;       // Ports used in test
rand bit [47:0] mac_addr[4]; // MAC addresses
rand bit [ 3:0] is_100;       // 100mb mode
rand uint run_for_n_frames;   // # frames in test
 
// Force some addr bits when running in unicast mode
constraint local_unicast {
foreach (mac_addr[i])
mac_addr[i][41:40] == 2'b00;
}
 
constraint reasonable {       // Limit test length
run_for_n_frames inside {[1:100]};
}
endclass : eth_cfg

下面是把以太网口配置放到了environment中,通过随机化得到哪个口被使用,build是把要使用的gen和drv器件生成,run则是启动。

注意,build也可以不管使不使用都生成,这样run的时候也不用判断,坏处是内存消耗多一点

Sample 6.72 Building environment with random configuration
class Environment;
eth_cfg cfg;
eth_src gen[4];
eth_mii drv[4];
 
function new();
cfg = new();             // Construct the cfg
endfunction
 
function void gen_cfg;
assert(cfg.randomize()); // Randomize the cfg
endfunction
 
// Use random configuration to build the environment
function void build();
    foreach (gen[i])
      if (cfg.in_use[i]) begin
        gen[i] = new();
        drv[i] = new();
        if (cfg.is_100[i])
          drv[i].set_speed(100);
        end
endfunction

task run();
    foreach (gen[i])
      if (cfg.in_use[i]) begin
        // Start the testbench transactors
        gen[i].run();
        …
    end
endtask
     
task wrap_up();
    // Not currently used
endtask
endclass : Environment

下面是在测试中使用的示例

ample 6.73 Simple test using random configuration
program test;
  Environment env;
   
  initial begin
      env = new();   // Construct environment
      env.gen_cfg;   // Create random configuration
      env.build();   // Build the testbench environment
      env.run();     // Run the test
      env.wrap_up(); // Clean up after test & report
  end
endprogram

约束随机测试(CRT)是产生验证复杂设计所需激励的唯一办法。

赞(0)
未经允许不得转载:171主机测评 » SV学习记录(六)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址