SystemVerilog的随机约束(Random constraints)

0 概述

SystemVerilog允许用户以一种紧凑的、声明式的方式指定约束。然后由求解器处理约束,生成满足约束的随机值。

随机约束通常是在面向对象的数据抽象之上指定的,该抽象将要随机化的数据建模为包含随机变量和用户定义约束的对象。这些约束决定了可以分配给随机变量的合法值。

例子:

class Bus;
    rand bit[15:0] addr;
    rand bit[31:0] data;
    constraint word_align {addr[1:0] == 2’b0;}
endclass

Bus类用两个随机变量对简化的总线建模:addr和data,它们代表总线上的地址和数据值。word_align约束声明addr的随机值必须是按字对齐的(低2位为0,4byte=1word)。

例化:

Bus bus = new;
repeat (50) begin
    if ( bus.randomize() == 1 )
        $display ("addr = %16h data = %h\n", bus.addr, bus.data);
    else
        $display ("Randomization failed.\n");
end

调用 randomize() 会为所有随机变量产生约束范围内的随机值。

在上面的程序测试中,创建一个总线对象,然后随机化50次。检查每个随机化的结果是否成功。如果随机化成功,则打印addr和data的新随机值;如果随机化失败,就会打印一条错误消息。本例中,只有addr值受约束,而data值不受约束。无约束变量在其声明范围内被赋任何值。

约束编程是一种功能强大的方法,它允许用户构建通用的、可重用的对象,稍后可以对这些对象进行扩展或约束,以执行特定的功能。这种方法既不同于传统的过程式编程,也不同于传统的面向对象编程,如下例所示,它扩展了Bus类:

typedef enum {low, mid, high} AddrType;
class MyBus extends Bus;
    rand AddrType atype;
    constraint addr_range
    {
        (atype == low ) -> addr inside { [0 : 15] };
        (atype == mid ) -> addr inside { [16 : 127]};
        (atype == high) -> addr inside {[128 : 255]};
    }
endclass

MyBus类继承了Bus类的所有随机变量和约束,并添加了一个名为atype的随机变量,该随机变量用于使用另一个约束控制地址范围。addr_range约束使用 蕴涵词(implication,->) 来根据atype的随机值从三个范围约束中选择一个。
当一个MyBus对象被随机化时,addr、data和type的值将被计算出来,以便满足所有的约束。使用继承来构建分层约束系统使通用模型的开发成为可能,这些模型可以被约束来执行特定于应用程序的功能。

调用:

task exercise_bus (MyBus bus);
    int res;
    // EXAMPLE 1: restrict to low addresses
    res = bus.randomize() with {atype == low;};
    // EXAMPLE 2: restrict to address between 10 and 20
    res = bus.randomize() with {10 <= addr && addr <= 20;};
    // EXAMPLE 3: restrict data values to powers-of-two
    res = bus.randomize() with {data & (data - 1) == 0;};
endtask

这个例子说明了约束的几个重要属性:

  • 约束可以是任何带有整型变量和常量的SystemVerilog表达式(例如,bit, reg, logic, integer, enum, packed struct)。
  • 约束求解器必须能够处理广泛的方程,如代数分解、复杂的布尔表达式,以及混合整数和位表达式。在上面的例子中,二的幂约束是用算术方法表示的(data & (data - 1) == 0;)。它也可以通过使用移位操作符的表达式定义。例如,1 << n,其中n是一个5位随机变量。
  • 如果存在解,约束求解器必须找到它。只有当问题过度约束且没有满足约束的随机值组合时,求解器才会失败。
  • 约束的双向作用。在本例中,为 addr 选择的值取决于 atype 及其约束方式,而 atype 选择的值取决于 addr 及其约束方式。所有表达式操作符都是双向处理的,包括包含操作符(->)。
  • 约束只支持两种状态的值。四态值(X或Z)或四态运算符(例如===,!==)是非法的,将导致错误。

constraint_mode()方法可用于启用或禁用对象中的任何命名约束块。
rand_mode()方法启用或禁用任意随机变量。当一个随机变量被禁用时,它的行为与其他非随机变量完全相同。

有时,在随机化之前或之后立即执行操作是可取的。这是通过两个内置方法完成的,pre_randomize()和post_randomize(),它们在随机化之前和之后被自动调用。这些方法可以被所需的功能覆盖(函数重写)。
默认情况下,pre_randomize()和post_randomize()会调用它们覆盖的父类方法。当pre_randomize()或post_randomize()被重写时,必须小心调用父类的方法,除非类是基类(没有父类)。否则,基类方法不会被调用。

随机激励生成能力和面向对象的基于约束的验证方法使用户能够快速开发覆盖复杂功能的测试,并更好地确保设计的正确性。

1 随机变量

可以使用rand和randc类型修饰符关键字将类变量声明为随机的。

1.1 rand关键字

使用rand关键字声明的变量是标准随机变量。它们的值均匀地分布在它们的范围内。

rand bit [7:0] y;`  

这是一个8位无符号整数,取值范围是0 ~ 255。如果不受约束,则该变量应被赋值为0 - 255范围内的任意值,概率相等。在本例中,连续调用随机化时重复相同值的概率是1/256。

1.2 randc关键字

用randc关键字(random-cyclic)声明的变量是随机循环变量,它以其声明范围的随机排列循环遍历所有值。随机循环变量只能是位类型或枚举类型,并且可以限制为最大大小。

randc bit [1:0] y;

在这里插入图片描述

2 Constraint block

随机变量的值是使用使用约束块声明的约束表达式确定的。约束块是类成员,类似于任务、函数和变量。约束块名称在类中必须是唯一的。

约束的声明性性质对约束表达式施加了以下限制:

  • 函数是允许的,但有一定的限制。
  • 不允许带有附加作用的操作符,如++和–。
  • randc变量不能在排序约束中指定。
  • dist表达式不能出现在其他表达式中。

2.1 设置成员(membership)

约束支持整数值集和集合成员运算符。

如果没有任何其他约束,所有值(单个值或值范围)被 inside 操作符选中的概率都是相等的。

inside 操作符的否定形式表示表达式位于集合:!(expression inside { set })。

例子:

rand integer x, y, z;
constraint c1 {x inside {3, 5, [9:15], [24:32], [y:2*y], z};}

rand integer a, b, c;
constraint c2 {a inside {b, c};}

integer fives[4] = '{ 5, 10, 15, 20 };
rand integer v;
constraint c3 { v inside fives; }

在SystemVerilog中,inside 操作符是双向的;因此,上面的第二个例子等价于a == b || a == c 。


2.2 分布(distribution)

除了集合成员之外,约束还支持称为分布的加权值集。分布有两个属性:它们是集合成员关系的关系测试,它们为结果指定一个统计分布函数。

定义分布表达式的语法如下:
在这里插入图片描述

如果表达式的值包含在集合中,则分布操作符dist的计算结果为true;否则,它的计算结果为false。

如果没有任何其他约束,则表达式匹配列表中任何值的概率与其指定的权重成比例。如果某些表达式的约束导致这些表达式的分布权值不能满足,则实现只需要满足这些约束即可。该规则的一个例外是权值为0,它被视为一个约束。

dist_item ::=
    value_range := expression
    | value_range :/ expression

分布集是由逗号分隔的整数表达式和范围组成的列表。列表中的每个术语都可以有一个权重,可以使用:=或:/操作符指定权重。如果没有指定权重,则默认权重为:= 1。权重可以是任何完整的SystemVerilog表达式。

:=操作符将指定的权重赋给item,如果item是一个范围,则赋给该范围内的每个值。

:/运算符将指定的权重赋给item,如果item是一个范围,则赋给整个范围。当取值范围内有n个值时,每个值的权重为range_weight / n。

例子:

x dist {100 := 1, 200 := 2, 300 := 5}

表示x = 100,200,300加权比为1-2-5。如果添加了指定x不能为200的附加约束x != 200;,则x = 100或300,加权比为1-5。

考虑混合比例,例如1-2-5,比实际的概率更容易,因为混合比例不必归一化到100%。将概率转换为混合比例是很简单的。

当权重应用于范围时,它们可以应用于范围中的每个值,也可以应用于整个范围。例如:

x dist { [100:102] := 1, 200 := 2, 300 := 5}

表示x等于100 101 102 200或300加权比为1-1-2-5。

x dist { [100:102] :/ 1, 200 := 2, 300 := 5}

表示x等于100 101 102 200或300中的1加权比为1/3-1/3-1/3- 5。

一般情况下,分布保证两个属性:set membership和monotonic weighting。换句话说,增加权重会增加选择这些值的可能性。

限制:

  • dist操作不能应用于randc变量。
  • dist表达式要求该表达式至少包含一个rand变量。

2.3 蕴涵词 (implication)

约束Constraint为声明 条件(断言)关系 提供了两种构造:蕴涵( implication)和if…else。

蕴涵操作符(- >)可用于声明一个隐含约束的表达式。

定义隐含约束的语法如下:

在这里插入图片描述

与隐含操作符a -> b等价的布尔值是(!a || b)。这表示如果表达式为真,则生成的随机数受约束(或约束集)的约束。否则,生成的随机数是无约束的。

constraint_set表示任何有效的约束或未命名的约束集。如果表达式为真,则约束集中的所有约束也必须满足。

mode == little -> len < 10;
mode == big -> len > 100;

在这个例子中,mode的值意味着len的值应该被限制在小于10 (mode == little),大于100 (mode == big),或不受约束(mode != little和mode != big)。

bit [3:0] a, b;
constraint c { (a == 0) -> (b == 1); }

a和b都是4位;因此,a和b有256种组合。约束c说,a == 0意味着b == 1,因此消去了15种组合:{0,0},{0,2},…{0,15}。因此,a == 0的概率是1/(256-15)或1/241。


2.4 if…else 约束

if…else 约束的语法:

在这里插入图片描述

if (mode == little)
    len < 10;
else if (mode == big)
    len > 100;

等效于

mode == little -> len < 10 ;
mode == big -> len > 100 ;

2.5 迭代(iterative)约束

iterative 约束允许使用循环变量和索引表达式以参数化的方式约束数组变量。

语法:
在这里插入图片描述

foreach构造指定对数组元素的迭代。它的参数是一个标识符,它指定任何类型的数组(fixed-size, dynamic, associative,或 queue),后跟一个用方括号括起来的循环变量列表。每个循环变量对应于数组的一个维度。

例子:

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

约束1:C1将数组A中的每个元素限定在集合[2,4,8,16]内。C2约束数组中的每个元素。
约束2:A大于索引的两倍。

注意:循环变量的数量不能超过数组变量的维数。每个循环变量的作用域是foreach约束结构,包括它的constraint_set。每个循环变量的类型隐式声明为与数组索引的类型一致。空循环变量表示不对数组的该维度进行迭代。与默认参数一样,末尾的逗号列表可以省略;因此,foreach(arr [j])是foreach(arr [j,,,,])的简写。任何循环变量与th具有相同的标识符都将是错误的。

iterative 约束允许包含断言:

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

约束1:约束c1将数组A的大小限制在1到10之间。
约束2:约束c2约束每个数组的值大于前一个,即按升序排序的数组。

注意:在foreach中,只涉及常数、状态变量、对象句柄比较、循环变量或迭代数组的大小的断言表达式的行为是防止创建约束,而不是作为逻辑关系。例如,上面约束c2的含义只涉及一个循环变量和正在迭代的数组的大小;因此,它只允许在k < A.size() - 1,在本例中,它阻止了约束中的越界访问。

2.6 Global 约束

当类的对象成员声明为rand时,其所有约束和随机变量与其他类变量和约束同时被随机化。包含来自其他对象的随机变量的约束表达式称为全局约束。

例子:

class A; // leaf node
    rand bit [7:0] v;
endclass
class B extends A; // heap node
    rand A left;
    rand A right;
    constraint heapcond {left.v <= v; right.v <= v;}
endclass

在这里插入图片描述

这个例子使用全局约束来定义有序二叉树的合法值。A类表示一个8位值的leaf nodev。B类扩展了A类,表示一个值为v的堆节点,一个左子树,一个右子树。两个子树都声明为rand,以便与其他类变量同时对它们进行随机化。名为heapcond的约束块有两个全局约束,将左子树和右子树的值与heap node的值相关联。当类B的实例被随机化时,求解器同时求解B及其左右子节点,这些子节点可以是leaf node或更多的heap node。

2.7 变量排序

解算器必须确保随机值的选择是在合法值组合上给出统一的值分布(即,所有合法值组合有相同的概率成为解)。这个重要的属性保证了所有合法值组合都是同等可能的,这使得随机化能够更好地探索整个设计空间。

然而,有时需要迫使某些组合更频繁地出现。考虑1位控制变量s约束32位数据值d的情况:

class B;
    rand bit s;
    rand bit [31:0] d;
    constraint c { s -> d == 0; }
endclass

约束条件c说" s蕴含d = 0 "虽然这看起来像是s决定了d,但实际上s和d是一起决定的。{s,d}有233种可能的组合,但是s只对{1,0}成立。因此,s为真的概率是1/233,差不多为0。

约束提供了一种对变量排序的机制,使s可以独立于d选择。这种机制定义了变量求值的部分排序,并使用solve关键字指定。

class B;
    rand bit s;
    rand bit [31:0] d;
    constraint c { s -> d == 0; }
    constraint order { solve s before d; }
endclass

在这种情况下,顺序约束指导解决解决为d s之前解决。年代现在选择的效果是真实的概率为50%,然后选择d s的价值。因此,d = = 0将发生50%的时间,和d != 0应当发生另外的50%

可变排序可以用来迫使所选的corner情况比其他情况更频繁地出现。然而“solve…before…”约束不会改变解空间,因此不会导致求解器失败。

2.8 静态约束块

通过在约束块的定义中包含static关键字,可以将约束块定义为static。

如果一个约束块被声明为静态的,那么对constraint_mode()的调用将影响所有对象中指定约束的所有实例。因此,如果一个静态约束被设置为OFF,那么它对于这个特定类的所有实例都是关闭的。

2.9 约束中的函数

有些属性难以处理,或者不可能在一个表达式中表达。例如,计算已填充数组中1的数量的自然方法是使用循环:

function int count_ones ( bit [9:0] w );
    for( count_ones = 0; w != 0; w = w >> 1 )
    count_ones += w & 1'b1;
endfunction

这样的函数可以用来约束其他随机变量为1位:

constraint C1 { length == count_ones( v ) ; } 

如果不能调用函数,则该约束要求展开循环,并将其表示为单个位的和:

constraint C2
{
    length == ((v>>9)&1) + ((v>>8)&1) + ((v>>7)&1) + ((v>>6)&1) + ((v>>5)&1) +
    ((v>>4)&1) + ((v>>3)&1) + ((v>>2)&1) + ((v>>1)&1) + ((v>>0)&1);
}

与count_ones函数不同,更复杂的属性(需要临时状态或无界循环)可能无法转换为单个表达式。因此,调用函数的能力增强了约束语言的表达能力,并降低了出错的可能性。上面的两个约束,C1和C2,是不完全等价的;C2是双向的(长度可以约束v,反之亦然),而C1不是。

2.10 约束保护(Constraint guard)

约束保护是断言表达式,它的作用是防止约束的创建,而不是作为解算器要满足的逻辑关系。这些断言表达式在约束解出之前被求值,其特征是只涉及以下项:

  • 常量
  • 状态变量
  • 对象句柄比较(两个句柄或一个句柄与常量null的比较)

除了上述,迭代约束也将循环变量和被迭代数组的大小视为状态变量。

将这些断言表达式作为约束保护,可以防止求解器生成求值错误,从而在一些看似正确的约束上失败。这使用户能够编写约束,以避免由于对象句柄不存在或数组下标出界而导致的错误。例如,下面所示的单链列表(SList)的排序约束旨在分配一个按升序排序的随机数字序列。但是,约束表达式在最后一个元素上失败,而在下一个元素上失败。N由于不存在句柄而导致计算错误。

class SList;
    rand int n;
    rand Slist next;
    constraint sort { n < next.n; }
endclass

可以通过编写一个断言表达式来避免上述错误条件:

constraint sort { if( next != null ) n < next.n; }

在上面的排序约束中,if阻止在next == null时创建约束,在这种情况下,这将避免访问一个不存在的对象。蕴含 implication (- >)和if…else都可以用作保护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lu-ming.xyz

觉得有用的话点个赞吧 :)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值