网站前台模板设计,深圳网络推广软件,沭阳网站建设方案,常州设计公司排名OCP Open Closed Principle 开闭原则 文章目录 随机约束和分布为什么需要随机#xff1f;为什么需要约束#xff1f;我们需要随机什么#xff1f;声明随机变量的类什么是约束权重分布集合成员和inside条件约束双向约束 约束块控制打开或关闭约束内嵌约束 随机函数pre_random… OCP Open Closed Principle 开闭原则 文章目录 随机约束和分布为什么需要随机为什么需要约束我们需要随机什么声明随机变量的类什么是约束权重分布集合成员和inside条件约束双向约束 约束块控制打开或关闭约束内嵌约束 随机函数pre_randomize 和 post_randomize系统随机数函数随机化个别变量 数组约束约束数组的大小约束数组的元素产生唯一元素值的数组随机化句柄数组 随机控制参考资料 随机约束和分布
为什么需要随机
芯片复杂度越来越高在20年前 定向测试 已经无法满足验证的需求而 随机测试 的比例逐渐提高。定向测试能找到你认为可能存在的缺陷而 随机测试可以找到连你都没有想到的缺陷 。随机测试的环境要求比定向测试复杂它需要激励、参考模型和在线比较。上百次的仿真不再需要人为参与以此来提高验证效率。随机测试相对于定向测试可以减少相当多的代码量而产生的激励较定向测试也更多样。
为什么需要约束
如果随机没有约束产生有效激励的同时还 会产生大量的无效激励 。通过为随机添加约束这种 随机自由是一种合法的随机 产生有效的测试激励。约束不是一成不变的为了获取期望的测试范围或期待的数值范围约束需要“变形”。随机的对象不只是一个数据而是 有联系的变量集合 。通常这些变量集合会被封装在一个数据类里同时需要类中声明数据之间的约束关系。因此约束之后要产生一个随机数据的“求解器”即在满足数据本身和数据之间约束关系的随机数值解。约束不但 可以指定数据的取值范围 还 可以指定各个数值的随机权重分布 。
我们需要随机什么
器件配置 通过寄存器和系统信号。环境配置 随机化环境例如合理的时钟和外部反馈信号。原始输入数据 例如MCDF数据包的长度、带宽数据间的顺序。延时 握手信号之间的时序关系例如valid和readyreq和ack之间的时序关系。协议异常 如果反馈信号给出异常那么设计是否可以保持后续数据处理的稳定性。
声明随机变量的类
随机化是为了产生更多可能的驱动我们倾向于将相关数据有机整理在一个类的同时也用“rand”关键词来表明它的随机属性。“randc”关键词表示周期性随机即所有可能的值都赋过值后随机才可能重复也就好比54张扑克牌抽牌游戏rand代表每抽完一张放回去才可以下次抽牌randc代表没抽完一张不需要放回就抽取下一张如果抽完了那就全部放回再次同样规则抽取。rand和randc只能声明类的变量硬件域以及软件域的局部变量都不可以。随机属性需要配合SV预定义的随机函数std::randomize()使用。即通过声明rand变量并且在后期调用randomize()函数才可以随机化变量。约束constraint也同随机变量一起在class中声明。
class packet;rand bit [31:0] src, dst, data[8];randc bit [7:0] kind;constraint c {src 10;src 15;}
endclass//----------------------------------Packet p;
initial beginp new();//assert语句保证randomize成功否则会报fatal如果约束冲突如src15 and src10则会随机失败assert (p.randomize()) else $fatal(0, Packet::randomize failed);transmit(p);
end白话一刻 * class packet;定义一个名为packet的类。
* rand bit [31:0] src, dst, data[8];声明了三个随机变量分别是src源地址、dst目标地址和data一个包含8个元素的数组用于存储数据。每个变量的位宽度为32位。
* randc bit [7:0] kind;声明了一个随机且唯一的变量kind其位宽度为8位。randc意味着每次生成的kind值都是唯一的直到所有可能的值都被使用完。
* constraint c { ... }定义了一个约束c用于限制随机变量的取值范围或关系。这里它指定了src的值必须大于10且小于15。* Packet p;声明了一个packet类型的变量p。注意这里类名packet的首字母是大写的这通常表示它是一个用户定义的类型而不是SystemVerilog的内建类型。
* initial begin ... endinitial块在仿真开始时执行一次。 p new();创建一个新的packet对象并将其赋值给变量p。 assert (p.randomize()) else $fatal(0, Packet::randomize failed);调用p的randomize方法该方法会根据类的约束随机设置p的成员变量的值。assert语句检查randomize是否成功。如果randomize失败例如由于约束冲突则执行else部分的$fatal语句导致仿真终止并输出错误消息。 transmit(p);调用一个名为transmit的函数这个函数在提供的代码片段中未定义并将随机化的数据包p作为参数传递。总之这段代码定义了一个数据包类并展示了如何创建和随机化这个类的实例以及如何将这个实例传递给一个函数。这对于在仿真中生成随机化的数据包场景非常有用。 什么是约束
约束表达式的求解是有SV的约束求解器自动完成的。求解器能够选择满足约束的值这个值是由SV的PRNG伪随机数发生器从一个初始值seed产生。只要改变种子的值就可以改变CRT的行为。SV标准定义了表达式的含义以及产生的合法值但没有规定求解器计算约束的准确顺序。也就是不同仿真器对于同一个约束类和种子求解出的数值可能不同。什么可以被约束SV只能随机化二值数据类型但数据位可以是二值或四值的所以无法随机出x值和z值也无法随机出字符串。
class date;rand bit [2:0] month; //note:rand bit [4:0] day;rand int year;constraint c_data {month inside {[1:12]};day inside {[1:31]};year inside {[2010:2030]};}}
endclass请问month10day31year2020此组随机值可以产生吗
答案不能因为month的声明是3位所以不可能出现数值10这也是经常会犯的错误当你约束数据时一定要与声明数据的位数相匹配。
class stim;const bit [31:0] CONGEST_ADDR 42; //声明常数typedef enum {READ, WRITE, CONTROL} stim_e;randc stime_e kind;rand bit [31:0] len, src, dst;bit congestion_test;constraint c_stim {len 1000;len 0;if(congestion_test) (dst inside {[CONGEST_ADDR-100:CONGEST_ADDR100]};src CONGEST_ADDR;) else (src inside {0, [10:20], [100:200]};)}
endclass权重分布
关键词dist可以在约束中用来产生随机数值的权重分布这样某些值的选取机会要大于其他值。dist操作符带有一个值的列表以及相应的权重中间用 : 或 分开。值和权重可以是常数也可以是变量。权重不要百分比表示权重的和也不必是100。: 操作符表示值的范围内的每一个值的权重是相同的 操作符表示权重要平均分到范围内的每一个值。
rand int src, dst;constraint c_dist {src dist {0:40, [1:3]:60;}// src1, weight40/220// src2, weight60/220// src3, weight60/220// src4, weight60/220dst dist {0:/40, [1:3]:/60;}// dst1, weight40/100// dst2, weight20/100// dst3, weight20/100// dst4, weight20/100
}这段代码是使用SystemVerilog语言编写的用于定义随机数生成的约束。SystemVerilog通常用于硬件描述和验证。下面我将详细解释这段代码systemverilog
rand int src, dst;
这行代码声明了两个随机整数变量src 和 dst。rand 关键字表示这些变量在仿真期间可以被随机化。systemverilog
constraint c_dist { src dist {0:40, [1:3]:60;} dst dist {0:/40, [1:3]:/60;}
}
这部分定义了一个名为 c_dist 的约束用于控制 src 和 dst 变量的随机化分布。对于 src 变量systemverilog
src dist {0:40, [1:3]:60;}
{0:40} 表示当 src 变量取值为 0 时其权重是 40。
{[1:3]:60} 表示当 src 变量取值为 1、2 或 3 时其权重是 60。
权重可以理解为生成特定值的概率或可能性。权重越高生成该值的概率越大。对于 dst 变量systemverilog
dst dist {0:/40, [1:3]:/60;}
{0:/40} 表示当 dst 变量取值为 0 时其权重是 40。
{[1:3]:/60} 表示当 dst 变量取值为 1、2 或 3 时其权重是 60。
注意src 和 dst 的权重分布使用了不同的语法。src 使用了 : 运算符而 dst 使用了 :/ 运算符。这实际上是一个语法错误因为在SystemVerilog中权重分布应该使用统一的语法。通常你会看到 : 用于指定绝对权重而 :/ 用于指定相对权重。但在同一个约束中混合使用这两种语法是不正确的。此外注释部分解释了每个值的权重分布但它似乎有些混淆因为它似乎试图将权重与可能的取值范围相除来得到概率但这不是正确的解释。实际上权重是独立的数值它们会被归一化以表示生成特定值的相对概率。正确的解释应该是对于 src总权重是 40 60 * 3 220。因此src 取值为 0 的概率是 40/220取值为 1、2 或 3 的概率是 60/220。
对于 dst由于代码中的语法错误我们不能直接计算权重分布。如果假设 dst 也使用绝对权重并且语法被修正为 dst dist {0:40, [1:3]:60};那么总权重将是 100因为权重被错误地标记为相对权重。但实际上dst 的权重分布应该也使用绝对权重并且总和应该与 src 一致以便进行正确的概率计算。
为了修复这个问题你应该选择使用绝对权重或相对权重并确保 src 和 dst 的权重分布语法一致。如果你使用绝对权重代码应该类似于systemverilog
rand int src, dst; constraint c_dist { src dist {0:40, [1:3]:20}; // 总权重为 80 dst dist {0:40, [1:3]:20}; // 总权重也为 80以保持一致性
}
这样src 和 dst 就会有相同的权重分布每个值都有相同的概率被选中。集合成员和inside
inside是常见的约束运算符表示变量属于某个值的集合除非还存在其他约束 否则随机变量在集合里取值的概率是相等的集合里也可以是变量。可以使用 $ 符指定最大或最小值。
rand int c;
int lo, hi;
constraint c_range{c inside {[lo:hi]};
}//-------------------------------rand bit [6:0] b;
rand bit [5:0] e;
constraint c_range {b inside {[$:4], [20:$]};e inside {[$:4], [20:$]};
}条件约束
可以通过 - 或者 if-else来让一个约束表达式在特定条件有效。
constraint c_io {(i_space_mode) - addr[31] 1b1; //i_space_mode!0
}//--------------------------------------constraint c_io {if(i_space_mode) //i_space_mode!0addr[31] 1b1;else;
}双向约束
约束块不是自上而下的程序代码它们是声明性代码是并行的所有的约束同时有效。约束是双向的这表示它会同时计算所有的随机变量的约束增加或删除任何一个变量的约束都会直接或间接的影响所有相关的值的选取。约束块可以声明多个但是它们仍旧是并行的如果对同一变量进行约束取两者约束的交集也就是两个约束都会生效与写在一个约束块效果相同。子类会继承父类的约束。
约束块控制
打开或关闭约束 一个类可以包含多个约束块可以把不同约束块用于不同测试。 一般情况下各个约束块之间的约束内容是相互协调不违背的因此通过随机函数产生的随机数可以找到合适的解。 对于其他情况例如跟胡不同需求来选择使能哪些约束块禁止哪些约束块可以使用内建函数constraint_mode()打开或者关闭约束。
class packet;rand int length;constraint c_short {length inside {[1:32];}}constraint c_long {length inside {[1000:1032];}}
endclass//------------------------packet p;
initial beginp new ();//create a long packet by disabling c_shortp.c_short.constraint_mode(0);assert(p.randomize());transmit(p);//create a short packet by disabling all constraint and then enable only c_shortp.constraint_mode(0);p.c_short.constraint_mode(1);assert(p.randomize());transmit(p);
end内嵌约束
伴随着复杂的约束它们之间会相互作用最终产生难以预测的结果。用来使能和禁止这些约束的代码也会增加测试的复杂性。经常增加或修改类的约束也可能会影响整个团队的工作这需要考虑类的OCP原则开放封闭原则也就是哪些对外部开放哪些不对外开放。SV允许使用 randomize() with来增加额外的约束这和在类里增加约束是等效的但同时要注意类内部约束和外部约束之间应该是协调的如果出现违背随机数会求解失败求解失败不同的工具报告形式不同有的是error有的是warning。
class packet;rand int length;constraint c_short {soft length inside {[1:32];}}
endclass//------------------------packet p;
initial beginp new ();assert(p.randomize() with {length inside {[36:46];};length ! 40; });transmit(p);
end上述例子中randomize() with{}约束与c_short产生可冲突那会不会报错呢
答案是不会因为c_short约束前加了soft软约束关键字意义就在于当外部或子类的约束发生冲突时其优先级降低不会影响外部或子类的约束。
随机函数
pre_randomize 和 post_randomize 有时需要在调用randomize()之前或之后立即执行一些操作例如在随机前设置一些非随机变量上下限、条件值、权重,或者在随机化后需要计算数据的误差、分析和记录随机数据等。 SV提供两个预定义的void类型函数pre_randomize和post_randomize用户可以类中定义这两个函数分别在其中定义随机化前的行为和随机化后的行为。 如果某个类中定义了pre_randomize或post_randomize那么对象在执行randomize()之前或之后会分别执行这两个函数所以pre_randomize和post_randomize可以看做是randomize函数的回调函数callback function。 和之前学的语言类的知识关联起来了好像spring框架的前置和后置 系统随机数函数
SV提供了一些常用的系统随机函数这些系统随机函数可以直接调用来返回随机数值
$random()平均分布返回32位有符号随机数。$urandom()平均分布返回32位无符号随机数。$urandom_range()在指定范围内的平均分布。
随机化个别变量
在调用randomize()时可以传递变量的一个子集这样只会随机化类里的几个变量。只有参数列表里的变量才会被随机化其他变量会被当做状态量而不会被随机化。所有的约束仍然保持有效。注意类里所有没有被指定rand的变量也可以作为randomize()的参数而被随机化。注意未进行随机化的变量默认初始值为0。
class rising;byte low;rand byte med, hi;constraint up {lowmed; medhi;}
endclass//----------------------------------initial beginrising r;r new();r.randomize(); //随机化hi和med不改变lowr.randomize(med); //只随机化medr.randomize(low); //只随机化low
end数组约束
约束数组的大小
在约束随机标量的同时我们也可以对随机化数组进行约束。多数情况下数组的大小应该给定范围防止生成过大体积的数组或空数组。此外还可以在约束中结合数组的其他方法sum(), product(), and(), or(), 和xor()。
class dyn_size;rand logic [31:0] d[];constraint d_size {d,size() inside {[1:10];};}
endclass约束数组的元素
SV可以利用foreach对数组每一个元素进行约束和直接写出对固定大小数组的每一个元素相比foreach更简洁。针对动态数组foreach更适合于对非固定大小数组中每个元素的约束。
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产生唯一元素值的数组
如果想要产生一个随机数组它的每一个元素值都是唯一的如果使用randc数组数组中的每一个元素只会独立的随机化并不会按照我们期望的使得数组中的元素值是唯一的。
解决方案1
rand bit [7:0] data;
constraint c_data{foreach(data[i])foreach(data[j])if(i ! j) data[i] ! data[j];
}解决方案2
class randc_data;randc bit [7:0] data[64];
endclassclass data_array;bit [7:0] data_array [64];function void pre_randomize();randc_data rcd;rcd new();foreach (data_array[i]) beginassert(rcd.randomize());data_array[i] rcd.val;endendfunction
endclass“randc”关键词表示周期性随机即所有可能的值都赋过值后随机才可能重复也就好比54张扑克牌抽牌游戏rand代表每抽完一张放回去才可以下次抽牌randc代表没抽完一张不需要放回就抽取下一张如果抽完了那就全部放回再次同样规则抽取。 特别示例如下首先“”代表小于等于其次限定da.size为3/4/5实际不可能取到5原因是da.size的约束体现在“da[i] da[i1]”时约束的是i和i1为3/4/5。
rand bit [7:0] da[];
constraint c_da {da.size() inside {[3:5]};foreach(da[i]) da[i] da[i1];
}随机化句柄数组 随机句柄数组的功能是在调用其所在类的随机函数时随机函数会随机化数组中的每一个句柄所指向的对象。因此随机句柄数组的声明一定要添加rand来表示其随机化的属性同时在调用随机函数前要保证句柄数组中的每一个句柄元素都是非悬空的这需要早随机化之前为每一个元素句柄构建对象。 如果要产生多个随机对象那么你可能需要建立随机句柄数组。和整数数组不同你需要在随机化前分配所有的元素因为在随机求解器不会创建对象。使用动态数组可以按照需要分配最大数量的元素然后再使用约束减小数组的大小。在随机化时动态句柄数组的大小可以保持不变或减小但不能增加。
parameter MAX_SIZE 10;
class RandStuff;bit[1:0] value 1;
endclassclass RandArray;rand RandStuff array[];constraint c_array {array.size() inside {[1:MAX_SIZE]};}function new();//分配最大容量array new[MAX_SIZE];foreach (array[i]) array[i] new();endfunction
endclass//---------------------------RandArray ra;
initial begin// 构造数组和所有对象ra new();// 随机化数组但可能会减小数组assert(ra.randomize());foreach(ra.array[i]) $display(ra.array[i].value);
end问题1执行ra.randomize() with {array.size2}时array[0].value 和 array[0].value分别是多少
答案都是1首先value没有加rand所以randomize不会随机value仍然保持为1。
问题2为什么要分配最大容量
答案是只有创建对象并且分配最大容量才能保证随机化时可能会碰到句柄数组悬空无指向对象随机会报错。
总结句柄数组的随机首先查看句柄指向的对象内有没有rand变量其次对句柄数组按最大容量进行例化。
随机控制
产生事务序列的另一个方法是使用SV的randsequence结构。这对于随机安排组织原子atomic测试序列很有帮助。
initial beginfor (int i0; i15; i) beginrandsequence (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;endsequenceend
end这是一个伪代码或特定于某个工具如SystemVerilog的代码片段用于描述一种随机序列生成机制。我会逐步解释这段代码的含义。 initial begin initial 是 SystemVerilog 中的一个块它在仿真开始时就执行一次。begin … end 是定义该块范围的关键词。 for (int i0; i15; i) begin … end
这是一个循环结构它将执行其内部的代码块 15 次。每次迭代i 的值从 0 增加到 14。 randsequence (stream)
randsequence 是 SystemVerilog 中用于描述随机序列的关键字。它允许用户为某个事件流在这里是 stream定义多个可能的随机序列。 stream: cfg_read : 1 | io_read : 2 | mem_read : 5;
这定义了三个事件cfg_read、io_read 和 mem_read并分别为它们分配了权重。权重决定了这些事件在随机选择时出现的频率。例如mem_read 事件出现的频率是 cfg_read 的 5 倍是 io_read 的 2.5 倍。 cfg_read: (cfg_read_task;) | (cfg_read_task;) cfg_read;
这定义了当 cfg_read 事件被选择时要执行的任务或操作。这里cfg_read_task 被执行然后事件流可以选择再次进入 cfg_read 状态通过后面的 cfg_read或者退出这个状态因为有两个选择但只执行一个。 mem_read: (mem_read_task;) | (mem_read_task;) mem_read;
与 cfg_read 类似但这里是为 mem_read 事件定义的行为。 io_read: (io_read_task;) | (io_read_task;) io_read;
同样这是为 io_read 事件定义的行为。
总结
这段代码描述了一个循环该循环运行 15 次。在每次迭代中它都会根据定义的权重随机选择一个事件cfg_read、io_read 或 mem_read并执行相应的任务。
每个事件都有自己的任务cfg_read_task、mem_read_task 和 io_read_task并且在任务执行完毕后可以选择再次进入相同的事件或退出。
这种随机性在模拟或测试复杂系统的行为时非常有用因为它可以模拟多种可能的执行顺序或条件。
我们也可以使用randcase来建立随机决策树但它带来的问题是没有变量可供追踪调试。
initial beginint len;randcase:1: len $urandom_range(0,2); //10%8: len $urandom_range(3,5); //80%1: len $urandom_range(6,7); //10%endcase$display(len%0d, len);
end总结 randsequence和randcase是针对轻量级的随机控制的应用。而我们可以通过定义随机类取代上述随机控制的功能并且由于类的继承性使得后期维护代码时更加方便。randsequence的相关功能我们在协调激励组件和测试用例时可能会用到。randcase则对应着随机约束中的dist权重约束 if-else条件约束的组合。 参考资料
Wenhui’s Rotten PenSystemVerilogchipverify