SystemVerilog随机约束

本文首发于微信公众号“芯片学堂”,作者JKZHAN

上一篇文章《暗藏玄机的SV随机化》介绍了SystemVerilog的各种随机化方法,本文将在其基础上引入SystemVerilog的随机约束方法(constraints)。通过使用随机约束,我们可以将随机限制在一定的空间内,有针对性地提高功能覆盖率。

SV随机约束的应用,就像是我们用陈述性(declarative)的语句告诉仿真器我们要的随机数要满足哪些条件,然后仿真器的约束解算器(constraint solver)就会去找到能够满足我们所有描述语句的解,再从这些解中随机选出来一个值作为随机的结果。

01 约束解算器(Constraint Solver)

约束解算器是SV仿真器重要的一部分,它被专门用来求解约束。这里说的求解,就是指的仿真程序通过某种算法,找到能够满足我们所有约束条件的随机值的过程。如果随机被过约束(over-constraint)了,或者存在随机值的组合情况不能满足约束,则约束解算器就会解算失败。在实际应用中,仿真程序就会报错,然后打印出来告诉我们是哪段约束没有解算成功。

理解约束解算器的重要性,可以想想如果没有约束解算器会怎么样?举一个简单的栗子,现在我们有一个约束条件:变量A的随机值总是小于变量B的随机值。如果没有约束功能,代码可以这么实现:

do begin
    A = $urandom; 
    B = $urandom;
end while (! (A<B));

使用约束语句代码是这样的:

class ictalking;
        rand bit [7:0] A, B;
    constraint c_ab { A > B; }
endclass

可以看得出来,如果没有约束解算器,我们在描述约束的时候就会变得比较绕,通常会花掉很多时间去重复执行相同的一段代码,有时候甚至会跑了半天都撞不出来一个满足约束的随机值,更别说那些复杂的约束了。有了约束解算器,我们就可以在其框架内加入各种约束语句,它总能帮我们快速找到那个解。

02 约束的关系和控制

约束的解算顺序:约束的解算顺序可以使用solve-before来控制。约束解算器会优先求解before之前的约束,因此使用solve-before会影响随机数组合的概率分布情况。

class ictalking;
  rand bit [7:0] A, B;
  constraint c_a { A > B; }
  constraint c_order {solve A before B;}
  // 顺序约束可以写在同一个约束块中,
  // 也可以分开写在不同的约束块中(如本例)
endclass

硬约束和软约束:当我们在不同的层次对随机变量附加约束的时候,软约束可以被后面指定的约束给覆盖。典型的应用场景是在UVM的sequence_item(或者叫transaction)定义时,我们可以通过软约束指定默认的随机约束,这样方便我们后面在继承或者例化的时候可以使用更高优先级的约束对其覆盖。

class ictalking;
    rand int count;
  constraint c_count {
    soft count inside {[666:888]}; // 指定软约束需要使用关键字soft
  }
endclass

ictalking ict = new();
ict.randomize() with { count inside {[123:456]}; }

约束的控制开关:默认情况下,所有的约束一写上默认使能,即约束解算器就会按照这些约束开始算。但SV提供约束条件的控制方法constraint_mode(),可以很方便的控制约束是否启用,以及查询约束的启用状态。

// 继续上面的例子

int con_status;
ictalking ict_obj1 = new();
ictalking ict_obj2 = new();

ict_obj1.c_count.constraint_mode(0); // 不启用ict_obj1中的约束c_count
ict_obj2.c_count.constraint_mode(1); // 启用ict_obj2中的约束c_count
ict_obj1.count.rand_mode(0); // 顺便提一嘴,随机变量类似的可以使用rand_mode开关随机功能

con_status = ict_obj1.c_count.constraint_mode(); // 获得ict_obj1中约束c_count的启用状态 

03 五花八门的约束代码

SV中的约束非常灵活,下面给出一些常用的约束代码,不包含完整的testbench代码,可以作为参考和总结。

  • 范围约束:使用inside指定随机数的范围或者枚举值
rand int temp_var;
constraint c_var_1 {temp_var inside {[2000:2021]}; }      // 限定范围
constraint c_var_2 {temp_var inside {2008, 2016, 2019}; } // 限定枚举值
constraint c_var_3 {! (temp_var inside {[1:2007]});       // 反向限定范围
  • 条件约束:SV中有两种写条件约束的方式:implication(有些地方会翻译成蕴藏或者关联等等)和 if-else,用来指定在某些条件下才做进一步的约束。
rand bit mode;
rand int count;
constraint c_var_1 { mode == 1 -> count < 2021; } // 使用implication操作符->
constraint c_var_2 { if (mode == 1) {count < 2021;} else {count > 6000;} } // 使用if-else
  • 权重约束:约束可以指定随机值的权重,主要有两种方式:dist和randcase。dist一般用在constraint约束块中,但randcase一般用在程序执行块中,比如某个函数、任务或者initial块等。
rand bit mode;
rand int count;
constraint c_var_1 {
    mode == READ  -> count inside {[2008:2016]};
    mode == WRITE -> count inside {[2017:2021]};
  mode dist {READ := 4, WRITE := 6}; // mode随机成READ的概率为40%,WRITE为60%
}

initial begin
  repeat (100) begin
    randcase
        2: $display("In 1st branch."); // 在100次循环中,执行分支1的概率是20%
        7: $display("In 2nd branch."); // 在100次循环中,执行分支2的概率是70%
        1: $display("In 3rd branch."); // 在100次循环中,执行分支3的概率的10%
    endcase
  end
end
  • 唯一约束:唯一约束使用unique关键字来限定变量之间的值是唯一的,即两两之间互不相等。
rand int a, b, c;
rand int array[5];
int q[$] = `{200, 53, 656};

constraint c_unique {
  unique {a, b, c};     // 该约束要求a和b和c两两之间互不相等
  unique {a, b, array}; // 该约束要求a和b和array中的所有值互不相等
  unique {array};       // 该约束要求array数组内的5个值互不相等
  unique {a, q};        // 该约束要求a随机出来的值不等于q中的任一值
}
  • 循环约束:在对队列或者数组进行随机化的时候,可以使用foreach来对其循环施加约束。
rand int q[$];
constraint c_foreach {
  q.size() inside {[3:8]};
  foreach (q[i]) {
    if (i > 0) q[i] > q[i-1]; // 约束q队列的下一个值总比上一个值大
  }
}
  • 缩位约束:缩位约束会相对复杂一些,但是很好理解。
    • 缩位运算:比如对于数组arr[5],arr.sum就是arr[0] + arr[1] + ... + arr[4]。除sum之外,还有product、and、or、xor,分别表示乘积、与、或、异或运算。
    • 这些缩减运算的方法的返回值类型,跟队列或者数组的元素类型一样,因此当类型不匹配的时候,需要做类型转换的操作。
    • 缩减运算的方法可以可选的加with条件,用来筛选队列或者数组中的元素。另外关键字item表示当前的数组元素,item.index表示当前数组元素的索引。
rand bit qbit[$];
rand int qint[$];

constraint c_qbit {
  qbit.size() inside {[4:6]};
  (qbit.sum with (int'(item))) == 3; // 将当前元素item转为int类型,并约束所有元素有且只有3个为1
}

constraint c_qint {
  qint.size() inside {[5:9]};
  (qint.sum with ((item.index < 3) ? item : 0)) == 45; // 约束qint队列的前三个加起来等于45
}
  • 静态约束:静态约束使用的关键字static跟静态变量是一样的。静态约束表示的是所有的对象实例都使用的同一个约束,所以使用constraint_mode()进行开关控制的时候具有全局性。
class ictalking;
  rand int count;
  static constraint c_count {count > 34;}
endclass

module testbench;
  initial begin
        ictalking ict_obj1 = new();
        ictalking ict_obj2 = new();
        ict_obj1.c_count.constraint_mode(0); // 关掉的之后ict_obj2中的c_count也会失效
    ...
  end
endmodule
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 本文首发于微信公众号“芯片学堂”,作者JKZHAN 我习惯将验证空间理解为:验证中原则上需要覆盖的芯片所有有可能出...
    芯片学堂阅读 5,767评论 0 1
  • 1.数据类型 VerilogHDL中有2种变量类型:wire和reg,这两种变量是4值类型的(即有四种状态)。 S...
    一条摸水鱼阅读 14,885评论 0 3
  • 有些特定场景下,我们可能需要将约束块或者随机变量开启使能或者关闭来构造工程师们想要的激励而不影响其他已经调试过的c...
    小可爸爸阅读 1,390评论 0 2
  • 本文首发于微信公众号“芯片学堂”,作者JKZHAN 回调(callback)是一种在很多编程语言中都会使用的编程技...
    芯片学堂阅读 1,618评论 0 1
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,603评论 28 53