SV randc 动态数组随机:循环取值、长度约束与元素填充
更新时间:2026-09-28。约束随机验证的整体框架在 SystemVerilog 约束随机验证:思想与基本套路 已经铺开,inside/dist 这些约束操作符见 SystemVerilog 约束:inside、dist 与求解顺序,动态数组本身的数据类型语义见 SystemVerilog 数据类型:packed 与 unpacked。这一篇只聚焦一个具体组合:randc 怎么循环取值、动态数组的长度与元素怎么分两步随机、两者怎么搭配发「不重复」的激励。
本文要回答的问题
randc和rand到底差在哪?「循环」两个字具体指什么行为?- 动态数组声明成
rand int arr[]之后,为什么直接randomize()元素值变了、长度却纹丝不动? - 想用 randc 从一组地址里不重复地挑激励,数组怎么配?周期耗尽后会发生什么?
一、randc 的循环取值语义
rand 变量每次 randomize() 都是独立抽样,两次取到同一个值完全合法;randc(random cyclic,循环随机)则保证:在一个周期(cycle)内,取值范围内每个值恰好出现一次,全部取完后自动开始下一个周期、重新洗牌。bit 宽为 N 的 randc 变量,周期长度就是 2^N 个候选值。
class pkt;
randc bit [3:0] id; // 0~15 共 16 个值为一周期
endclass
initial begin
pkt p = new();
repeat (20) begin
void'(p.randomize());
$display("id=%0d", p.id);
end
end
// 前 16 次输出必然是 0~15 的一个全排列;第 17~20 次是新周期的前 4 个理解 randc 的关键是把它想成「抽不放回的签筒」而不是「骰子」:签抽完一轮,全部扔回去重新摇。因此它天生适合做不重复激励——16 个从设备逐个访问一遍、中断源轮着拉高一回,都不用手写去重逻辑。而 rand 要自己维护一张「已用过」表才能等效。
| 对比项 | rand | randc |
|---|---|---|
| 取值模型 | 有放回抽样(骰子) | 无放回抽样,取尽后重置(签筒) |
| 相邻两次同值 | 允许 | 周期内不允许 |
| 周期长度 | 无概念 | 取值范围大小(2^N 或 constraint 限定后的集合) |
| 状态保持 | 无 | 仿真器内部记住本周期已取过的值 |
| 典型用途 | 数据 payload、长度、延迟 | 地址选择、ID 分配、枚举轮询 |
注意最后一行对应的约束细节:randc 的周期由约束收敛后的合法值集合决定。randc bit [7:0] x; constraint c { x inside {[0:9]}; } 的周期就是 10 个值,不是 256 个。
二、randc 循环示意
下面这张图把 randc 的「周期 — 全排列 — 重置」过程画出来:

读图时抓住一个容易误判的点:新周期开始后可以立刻取到上个周期刚出过的值(比如第 16 次取到 7,第 17 次又取到 7,完全合法)。randc 只保证「周期内不重复」,不保证「跨周期相邻两次不同」。如果你的测试意图是「任意相邻两次激励必须不同」,randc 单独不够,要再补一条 id != $prev(id) 之类的历史约束,或者干脆用队列手工洗牌。
三、动态数组:先随机长度,再随机元素
把动态数组声明成 rand 是合法的,但它有个非常反直觉的行为:
class burst_txn;
rand int len; // 长度单独放
rand byte data[]; // 元素随机
constraint c_len {
len inside {[1:16]};
data.size() == len; // 关键:长度由约束驱动
}
endclass核心规则一句话:randomize() 会随机数组的每一个元素,但绝不会替你改变 size()。数组当前是 0 长,随机一百次也还是 0 长。正确做法是把长度做成另一个 rand 变量(或直接在约束里给 data.size() 定范围),求解器会先解出 size、为数组分配相应元素个数,再逐个填随机值——两步在一个 randomize() 调用里完成,但次序是定死的:size 约束先行,元素填充随后。
initial begin
burst_txn t = new();
repeat (5) begin
void'(t.randomize());
$display("len=%0d data=%p", t.len, t.data);
end
end对 t.data 里每个元素再加约束也照常工作,比如 constraint c_val { foreach (data[i]) data[i] inside {[8'h10:8'h7F]}; }。foreach 展开在 size 确定之后生效,两者不冲突。还有一个实用写法是用 std::randomize 就地随机一个局部数组,不用包 class:
byte payload[];
std::randomize(payload) with { payload.size() inside {[4:32]};
foreach (payload[i]) payload[i] != 8'hFF; };with 子句等价于临时约束块,写一次性随机很顺手。另外提醒一句:数组很大时(比如 10 万元素的 payload),每轮 randomize 都要解 10 万个变量,求解器耗时线性增长,性能敏感的回归里建议用大 rand 标量加循环 $urandom 填值,把求解器留给真正需要约束的字段。随机稳定性与种子管理见 SystemVerilog 随机稳定性与种子管理。
四、randc 与动态数组配合:不重复激励的两种写法
需求很典型:从 64 个地址里随机挑 N 个不重复的发一笔 burst。两种惯用做法——
做法一:randc 索引抽数组。 数组是静态查找表,randc 变量做索引:
class addr_pick;
randc bit [5:0] idx; // 周期 64,索引天然不重复
int unsigned addr_table [64]; // 编译期或 pre_randomize 里填好
function int unsigned get();
return addr_table[idx];
endfunction
endclass优点是零去重代码;局限是周期固定为 64,想要「只抽 10 个」就抽 10 次 randomize 即可,反正前 10 次互不重复。
做法二:rand 动态数组 + unique 约束。 SV 提供 unique 关键字直接要求元素互异:
class burst_addr;
rand int unsigned addrs[];
constraint c {
addrs.size() inside {[4:8]};
foreach (addrs[i]) addrs[i] inside {[32'h1000:32'h1FFF]};
unique {addrs}; // 元素两两不等
}
endclassunique 让求解器自己保证互异,结果长度还能随机。代价是求解复杂度比 randc 高——unique 本质是 O(n²) 的不等式约束,元素上百时明显拖慢。一般规律:固定全集轮询用 randc,变长子集用 unique,后者规模控制在几十个元素以内。
五、常见坑速查
| 现象 | 根因 | 处理 |
|---|---|---|
| 数组随机后长度为 0 / 长度不变 | 只声明 rand byte data[],没给 data.size() 约束 | 约束 data.size() == len 或 data.size() inside {...} |
| randc 结果「居然重复了」 | 跨了周期,新周期重新洗牌 | 预期内行为;要相邻不同需额外约束或手工洗牌 |
| randc 改了 constraint 后行为怪异 | 周期集合变化但内部状态未清 | 用 randomize() with 临时约束;或重建对象 |
unique 随机极慢 | 元素太多,不等式约束爆炸 | 改 randc 索引法,或缩小 size 范围 |
| randomize 返回 0(求解失败) | size 约束与元素约束互相矛盾 | 检查返回值!assert(t.randomize()),别用 void' 吞掉 |
| foreach 约束对空数组报错 | size 为 0 时某些仿真器展开告警 | 保证 size 下界 ≥1,或接受空数组语义 |
最后一条特别值得强调:randomize() 求解失败不会自动中止仿真,它只返回 0 然后变量保持原值——激励静默地变成上一轮的旧值,测试可能「跑过」但完全没覆盖到新场景。养成 assert(obj.randomize()) else $fatal; 的写法,是约束随机里最便宜的一道保险。
这一篇在知识体系里的位置
- 上游:SystemVerilog 约束随机验证:思想与基本套路 给出 CRV 的全景;SystemVerilog 约束:inside、dist 与求解顺序 讲清求解器与约束操作符,本篇的 size 先行正是求解顺序的一个具体表现。
- 类型基础:SystemVerilog 数据类型:packed 与 unpacked 解释动态数组的内存语义。
- 进阶方向:更复杂的约束技巧见 SystemVerilog 高级随机约束技巧;种子与复现问题见 SystemVerilog 随机稳定性与种子管理。
总结
SV randc 动态数组随机这条组合技的要点是各管一段:randc 管「周期内不重复的循环取值」,靠仿真器记状态、取尽自动重置;动态数组的元素交给 randomize() 填,但长度必须用 data.size() 约束显式驱动,否则永远停在初始长度;要发不重复激励,固定全集用 randc 索引、变长子集用 unique,并且永远用 assert(obj.randomize()) 兜住求解失败——守住这几条,randc 与动态数组就能稳定产出既随机又不重复的激励。