SV学习记录(六)

📅 发布时间:2026/8/13 9:29:01
SV学习记录(六) 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 solversv中的约束求解器会根据约束选出可用值值来自PRNG伪随机数发生器有初始化的种子如果你的testbench和seed都相同结果也会是相同的。不同solver会产生不同的向量vendor所以不同仿真器的结果可能不同相同仿真器的不同版本结果也可能不同。什么可以随机整型变量就是常见的bit位。只能随机化为0/1。字符串不行handle变量不能写进constraint。截至2007书里写的年份实数随机化还比较困难不能出现x0.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语句但是可以用来等效起作用比如下面的都是合法的len42 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_byte1, w_word3, w_lwrd5; 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-elseSample 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它是逻辑表达式意思是他会把一个约束转换为对应的逻辑。对于下面的约束(x0) - y0等价于!(x0) || (y0)Sample 6.25 Class with implication class Imp1; rand bit x; rand bit [1:0] y; constraint c_xy { (x0) - y0; } endclass因此解的概率为双向传播如果我们加入新的条件y0此时由于x0会导致y0因此逻辑表达式会变为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; (x0) - y0; } endclass此时解的概率为solve...before它不改变解空间但会改变解出现的概率如字面意思如果我们写solve x before ysv会先以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 { (x0) - y0; 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); end6.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; } endclass6.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); end6.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 i0; (i4 ipayload.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上面的代码约束lengthpayload.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 随机化的常见错误小心使用有符号变量小心使用intbyte类有符号变量除非你真的确定应该是有符号的。对于下面的情况我们可能得到32,32也可能得到-63,127Sample 6.41 Signed variables cause randomization problems class SignedVars; rand byte pkt1_len, pk2_len; constraint total_len { pkt1_len pk2_len 64; } endclass你可能想那就不用有符号换为下面的情况也有问题比如一个是32h8000_0000和32h8000_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位的更快