SV系统验证—第六课:随机约束和分布(一)

目录

1. 随机约束和分布 

1.1 为什么需要随机?

1.2 我们要随机什么?

1.3 声名随机变量的类

1.4 什么是约束

 1.5 权重分布

1.6 集合成员和inside运算符

1.7 条件约束

1.8 双向约束

1.9 软约束

2. 约束快控制

2.1 打开或关闭约束

2.1.1 constraint_mode()

2.1.2 rand_mode()

2.2 内嵌约束

1. 随机约束和分布 

1.1 为什么需要随机?

  • 芯片体积增大,复杂度日渐提高,在20年前定向测试已经无法满足验证的需求,而随机测试的比例逐渐提高。
  • 定向测试能找到你认为可能存在的缺陷,而随机测试可以找到连你都没有想到的缺陷。
  • 随机测试的环境要求比定向测试复杂,它需要激励、参考模型和在线比较。上百次的仿真不再需要人为参与,以此来提高验证效率。
  • 随机测试相对于定向测试可以减少相当多的代码量,而产生的激励较定向测试也更多样。
  • 如果说定向测试是”站着测”,那么随机测试就是“躺着测”、“跪着测”、“跑着测”,各种花式测,只有想不到,没有测不到。
  • 如果随机没有约束,意味着什么?是绝对的自由吗?肯定不是,它是一匹脱缰的野马,没有拘束,产生有效激励的同时也产生了很多无效和非法的激励。
  • 那么我们想要的随机自由是一种合法的随机,需要限定激励的合法范围。同时,伴随测试的进行,约束甚至应该“变形”,变得更趋于为测试的数值范围或者期待的数值范围。
  • 随机的对象不只是一个数据,而是有联系的变量集。通常这些变量会被封装在一个数据类中,同时需要在类中声明数据之间的约束关系。因此约束之后要产生随机数据需要一个“求解器”,即在满足数据本身和数据之间约束关系时的随机数值解。
  • 约束不但可以指定数据的取值范围,还可以指定各个数值的随机权重分布。

1.2 我们要随机什么?

  • 器件配置:通过寄存器和系统信号
  • 环境配置:随机化验证环境,例如合理的时钟和外部反馈信号
  • 原始输入数据:例如MCDF数据包的长度、带宽,数据间的顺序
  • 延时:握手信号之间的时序关系,例如valid和ready,req和ack之间的时序关系
  • 协议异常:如果反馈信号给出异常,那么设计是否可以保持后续数据处理的稳定性呢?

1.3 声名随机变量的类

  • 随机化是为了产生更多可能的驱动,因此在软件世界"class"侧的运用更多,所以我们倾向于将相关数据有机整理在一个类的同时,也用rand关键词来表明它们的随机属性。
  • randc表示周期随机性,即所有可能的值都赋过值后随机值才可能重复。
  • 随机属性需要配合SV预定义的类随机函数 std::randomize() 使用。即只有通过声明rand变量,并且在后期通过对象调用 randomize() 函数才可以随机化变量。
  • 约束constraint也同随机变量一起在类中声明。

例子

// 定义一个名为Packet的类
class Packet;
    // 定义随机变量
    rand bit [31:0] src, dst, data[8];  // src、dst为32位宽的随机变量,data是一个包含8个元素的数组,每个元素都是32位宽的随机变量
    randc bit [7:0] kind;               // kind是一个8位宽的随机循环变量,意味着它的值会在随机化过程中遍历所有可能的值

    // 约束src的值范围
    constraint c {
        src > 10;                       // src必须大于10
        src < 15;                       // src必须小于15
    }
endclass

// 初始块开始
initial begin
    Packet p;                           // 声明一个Packet类型的变量p
    p = new();                          // 创建一个新的Packet对象并赋值给p
    assert (p.randomize()) else         // 尝试随机化Packet对象p
        $fatal(0, "Packet::randomize failed"); // 如果随机化失败,则输出致命错误信息并终止仿真
    transmit(p);                        // 调用transmit函数,传递参数p
end

rand和randc 

class Example;
    rand bit [7:0] a; // 普通随机变量a
    randc bit [7:0] b; // 随机循环变量b
endclass

在这个例子中,a可以随机取值为0到255之间的任何数字,包括重复值。而b也会随机取值为0到255之间的值,但在所有256个可能值都被取过后,才会开始新的循环并再次从头开始取值。

1.4 什么是约束

  • 约束表达式的求解是由sV的约束求解器(constraint solver)完成的。
  • 求解器能够选择满足约束的值,这个值是有SV的PRNG(伪随机数发生器Pseudo random number generator)从一个初始值(seed)产生。只要改变种子的值,就可以改变CRT的行为。
  • SV标准定义了表达式的含义以及产生的合法值,但没有规定求解器计算约束的准确顺序。这即是说,不同仿真器对于同一个约束类和种子值求解出的数值可能是不相同。
  • 什么可以被约束?SV只能随机化2值数据类型,但位可以是2值或4值。这即是说,无法随机化出×值和Z值,也无法随机化字符串。
class data;
	rand bit [2:0] month;
	rand bit [4:0] day;
	rand int year;
	constraint c_data {
		month inside {[1:12]};
		day inside {[1:31]};
		year inside {[2010:2030]};
	}
endclass

Q:

下面哪些解是约束求解器合理的随机数值组合?
A. month = 2, day = 31, year = 2017
B. month = 1, day = 30, year = 2020
C. month = 10, day = 31, year = 2022
D. month = 7, day = 31, year = 2045
A:
C. month是3bit,能表示的范围是[0:7]
D. 年限不符

// 定义一个名为 Stim 的类,用于生成测试激励
class Stim;

    // 定义一个常量 CONGEST_ADDR,表示拥塞测试中使用的特定地址
    const bit [31:0] CONGEST_ADDR = 42;

    // 定义一个枚举类型 stim_e,表示激励的种类(操作类型)
    typedef enum {READ, WRITE, CONTROL} stim_e;

    // 声明一个随机循环变量 kind,其类型为 stim_e。
    // randc 表示在随机化时会遍历所有可能值,不重复直到所有值都被使用过一次。
    randc stim_e kind; // enumerated var

    // 声明三个 32 位的随机变量:
    // len:数据包长度
    // src:源地址
    // dst:目标地址
    rand bit [31:0] len, src, dst;

    // 声明一个非随机变量 congestion_test,用于控制是否启用拥塞测试约束
    bit congestion_test;

    // 定义约束块 c_stim,对随机变量施加限制条件
    constraint c_stim {

        // 数据包长度必须大于 0 且小于 1000
        len < 1000;
        len > 0;

        // 根据 congestion_test 的值应用不同的约束
        if (congestion_test) {
            // 如果是拥塞测试,则:
            // 目标地址(dst)必须位于 CONGEST_ADDR ± 100 的范围内
            dst inside {[CONGEST_ADDR-100 : CONGEST_ADDR+100]};
            // 源地址(src)必须等于 CONGEST_ADDR
            src == CONGEST_ADDR;
        }
        else {
            // 如果不是拥塞测试,则:
            // 源地址(src)只能是以下范围之一:
            // - 等于 0
            // - 在 2 到 10 之间(包括 2 和 10)
            // - 在 100 到 107 之间(包括 100 和 107)
            src inside {0, [2:10], [100:107]};
        }

    } // end of constraint block

endclass // end of class Stim

 1.5 权重分布

  • 关键词dist可以在约束中用来产生随机数值的权重分布,这样某些值的选取机会要比其他值更大些
  • dist操作符带有一个值的列表以及相应的权重,中间用 : = 或 : / 分开。值或权重可以是常数或者变量,可以使用权重变量来随时改变值的概率分布,甚至可以把权重设为0,从而删除一个值
  • 权重不用百分比表示,权重的和也不必是100
  • : = 操作符表示值范围内的每一个值的权重是相同的,: / 操作符表示权重要平均分到值范围内的每一个值
rand int src, dst;
constraint c_dist {
	src dist {0:=40, [1:3]:=60};	// 产生40个单位的0,1-3每个数产生60个,共产生220个
	// src = 1, weight = 40/220
	// src = 2, weight = 60/220
	// src = 3, weight = 60/220
	// src = 4, weight = 60/220
	dst dist {0:/40, [1:3]:/60};	// 产生40个单位的0,1-3每个数总共产生60个,共产生100个数
	// dst = 0, weight = 40/100
	// dst = 1, weight = 20/100
	// dst = 2, weight = 20/100
	// dst = 3, weight = 20/100
}

1.6 集合成员和inside运算符

  • inside表示变量应该属于某一个值的集合,除非还存在其他约束,否则随机变量在集合里取值的概率是相等的。集合里也可以使用变量
rand int c;
int lo, hi;
constraint c_range {
	c inside {[lo:hi]};	// lo <= c && c <= hi
}
  • 使用“$”指定最大值和最小值
rand bit [6:0] b;	// 0~127
rand bit [5:0] e;	// 0~63
constraint c_range {
	b inside {[$:4], [20,$]};	// 0 <= b <= 4 || 20 <=b <= 127
	e inside {[$:4], [20,$]};	// 0 <= e <= 4 || 20 <=e <= 127
}

1.7 条件约束

  • 可以通过 -> 或者 if-else 来让一个约束表达式在特定时刻有效
class BusOp;
	...
	constraint c_io {
		(io_space_mode) -> addr[31] == 1'b1;
	}
endclass
    class BusOp;
    	...
    	constraint  c_len_rw {
    		if (op == READ)
    			len inside {[BYTE:LWRD]};
    		else
    			len == LWRD;
    	}
    endclass
    

    1.8 双向约束

    • 约束是声明性代码,是并行的,所有的约束表达式同时有效(单个约束块里多个约束条件和多个约束块都是并行的)
    • 子类继承父类后,子类的约束不能和父类冲突

    1.9 软约束

     在 SystemVerilog 中,soft 约束是一种特殊的约束类型,它允许约束求解器(constraint solver)优先考虑这些约束,但不会强制必须满足它们。如果不能满足 soft 约束,约束求解器会尝试找到一个尽可能接近满足所有 soft 约束的解决方案,而不是直接报错或失败。

    关键点

    • 优先级较低:与硬约束相比,soft 约束的优先级较低。硬约束是必须被满足的条件,而软约束则更像是“尽量满足”的建议。
    • 覆盖性:可以在运行时通过其他约束覆盖 soft 约束。例如,在某些情况下,你可能希望临时忽略某些默认值,这时可以使用更具体的约束来覆盖 soft 约束。
    • 灵活性:增加了验证环境的灵活性,使得生成随机数据更加灵活和适应不同的测试需求。
    constraint cstr{
        soft ch_id == -1;
        soft pkt_id == -1;
        soft data_size == -1;
        soft data_nidles == -1;
        soft pkt_nidles == -1;
        soft ntrans == 10;
    }

    在这个约束块中,所有的约束都是 soft 类型,这意味着:

    • 如果没有其他约束或者外部条件改变这些变量的值,那么这些变量将保持为 -1 或者 10(对于 ntrans),这是默认的行为。
    • 但是,如果有更高优先级的约束(比如在调用 randomize 方法时传递了特定的约束条件),这些 soft 约束可以被覆盖。
    assert(req.randomize with {
        local::ch_id >= 0 -> ch_id == local::ch_id; 
        local::pkt_id >= 0 -> pkt_id == local::pkt_id;
        local::data_nidles >= 0 -> data_nidles == local::data_nidles;
        local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
        local::data_size >0 -> data.size() == local::data_size; 
    })

    这里,如果 local::ch_id、local::pkt_id 等变量被设置为非负值,则这些具体的值将会覆盖相应的 soft 约束,从而生成符合这些条件的事务对象。 

    2. 约束快控制

    2.1 打开或关闭约束

    • 一个类可以包含多个约束块。可以把不同约束块用于不同测试。
    • 一般情况下,各个约束块之间的约束内容是互相协调不违背的,因此通过随机函数产生随机数时可以找到合适的解。
    • 对于其它情况,例如根据不同需要,来选择使能哪些约束块,禁止哪些约束块的要求,可以使用内建的constraint_mode()函数打开或者关闭约束。

    2.1.1 constraint_mode()

    class Packet;
    	rand int length;
    	constraint c_short {length inside {[1:32]};}
    	constraint c_long {length inside {[1000:1023]};}
    endclass
    
    module test;
    	Packet P;
    	initial begin
    		p = new();
    		p.c_short.constraint_mode(0);//关闭c_short约束
    		assert (p.randomize());
    		transmit(p);
    		p.constraint_mode(0);//关闭类中所有约束
    		p.c_short.constraint_mode(1);//打开c_short约束
    		transmit(p);
    	end 
    endmodule
    
    • constraint_mode(0):关闭约束
    • constraint_mode(1):打开约束
    • 非激活状态下的约束在调用randomize()函数时将失效

    Q:

    如果在随机化对象时,不禁止其中的任何一个约束块(即 c_shortc_long 都生效),那么调用 randomize() 函数后,下列哪一个数值可能是 p.length 的值?

    选项:

    • A. 0
    • B. 1
    • C. 1000
    • D. 报错
    • 若PRNG伪随机数发生器找不到合适的解,即约束冲突,里面所有的变量默认都是0,此次随机失败
    • 上述代码,不关闭两个约束,在tb创建对象后,然后打印p.length,编译不会报错,仿真结果为0;不关闭两个约束,在tb创建对象后,调用randomize()类随机函数,编译不会报错,因为randomize()返回值为0,仿真时提示有错 A)
    // 定义一个名为 Packet 的类,用于表示数据包
    class Packet;
    
        // 声明一个随机整型变量 length,表示数据包长度
        rand int length;
    
        // 定义约束块 c_short:限制 length 的取值范围为 [1:32]
        constraint c_short {
            length inside {[1:32]};
        }
    
        // 定义约束块 c_long:限制 length 的取值范围为 [1000:1023]
        constraint c_long {
            length inside {[1000:1023]};
        }
    
    endclass : Packet
    
    
    // 定义测试模块 test
    module test;
    
        // 声明一个指向 Packet 类的句柄 p
        Packet p;
    
        // 初始过程块,在仿真开始时执行一次
        initial begin
    
            // 实例化一个新的 Packet 对象,并将其地址赋给句柄 p
            p = new();
    
            // 关闭约束块 c_short,即暂时不参与随机化
            p.c_short.constraint_mode(0); // 关闭短包约束
    
            // 对对象进行随机化,此时只受 c_long 约束影响
            assert (p.randomize()) 
                else $error("Randomization failed!");
    
            // 调用 transmit 函数发送当前生成的数据包
            transmit(p);
    
            // 关闭整个 Packet 类的所有约束
            p.constraint_mode(0); // 关闭所有约束
    
            // 启用之前被关闭的约束块 c_short
            p.c_short.constraint_mode(1); // 打开短包约束
    
            // 再次进行随机化,此时只有 c_short 约束生效
            assert (p.randomize()) 
                else $error("Randomization failed!");
    
            // 再次调用 transmit 发送新生成的数据包
            transmit(p);
    
        end // end of initial block
    
    endmodule : test
    第一次随机化p.c_short.constraint_mode(0)<br>p.randomize()只有 c_long 生效 → length ∈ [1000, 1023]
    第二次随机化p.constraint_mode(0)<br>p.c_short.constraint_mode(1)<br>p.randomize()只有 c_short 生效 → length ∈ [1, 32]

    2.1.2 rand_mode()

    • 用来打开或关闭随机变量
    • 非激活状态下,给表示该变量不被声明为rand或者randc,不能通过randomize()函数随机化赋值
    class  Packet();
        rand  int   src, dst;
    endclass
    
    int   r;
    Packet  packet_a = new;
    packet_a.rand_mode(0);          //将Packet类中的两个随机变量设置为非激活态,即src与dst变为非随机变量,不能通过randomize函数赋值
    packet_a.src.rand_mode(1);     //将Packet类中的src设置为激活态,可以通过randomize函数进行赋值
    r=packet_a.dst.rand_mode();    //读取dst变量的激活状态
    
    

    2.2 内嵌约束

    • SV允许使用randomize()with来增加额外的约束,这和在类里增加约束是等效的,但同时要注意类内部约束和外部约束之间不要冲突,如果出现互相违背的情况,那么随机数值求解会失败
    class Transaction;
    	rand bit [31: 0] addr, data;
    	constraint c1 {
    		soft addr inside {[0:100], [1000:2000]};
    	}
    endclass
    
    module test;
    	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
    endmodule
    

    Q:

    如果assert(t.randomize() with {addr inside [200:300]; data inside [10:20];});,那么哪个值是合理的随机数值?
    A. 报错,10
    B. 报错,报错
    C. 200,10
    D. 0,10

    A:

    选C。
    对于任何一个约束冲突、不满足,此次随机的addr、data都没办法做求解,全部失败。但是添加了soft关键词,软约束,当外部的约束或其他的约束和原本的约束叠加在一起的时候有冲突,那么soft处的优先级更低,其他地方的优先,没报错

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

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

      余额充值