4. 随机化与约束

在 SystemVerilog 验证平台中,随机化不是“随便给值”,而是让激励在可控范围内自动搜索。要真正用好它,必须理解随机变量声明、约束块、分布控制、求解顺序以及约束的继承与动态开关。本节围绕这些机制展开,并给出可直接运行的示例。

rand 与 randc:两种随机语义

类中通过 rand 和 randc 修饰符声明随机变量。rand 每次随机化独立取值,同一值可以连续出现;randc 是循环随机,在一个周期内遍历所有可能值后才重复,适合地址、通道号等需要均匀覆盖的场景。

class packet;
  rand  bit [3:0] len;      // 0~15,可重复
  randc bit [1:0] ch;       // 0~3,周期内不重复
  constraint c_len { len inside {[1:12]}; }
endclass

注意:randc 只对整型、枚举等离散类型有意义,且每个变量独立维护循环状态。若约束把 randc 变量的取值空间压缩到很小,循环覆盖会很快耗尽,此时应改用 rand 加 dist 控制分布。

约束块:把合法空间写清楚

约束块用 constraint 名字 { ... } 定义,内部可写范围、集合、关系、条件与蕴含。常用形式包括:

  • inside:集合成员判断,支持区间 [a:b] 与枚举列表。
  • dist:带权分布,:= 表示每项权重相同,:/ 表示权重按比例分配。
  • -> 与 if...else:条件约束,只有前提成立时才施加后续限制。
  • solve...before:指定求解顺序,影响分布结果。
constraint c_dist {
  len dist { [1:4] := 40, [5:8] := 30, [9:12] := 30 };
}
constraint c_cond {
  (len > 8) -> ch inside {0, 1};
}

上例中,len 在 1~4、5~8、9~12 三段按 40/30/30 权重抽取;当 len 大于 8 时,ch 被限制为 0 或 1。若约束之间冲突,随机化会失败,randomize() 返回 0,此时应检查约束是否过紧或存在互斥条件。

solve...before:控制求解顺序

默认情况下,求解器会同时满足所有约束,但分布可能不符合预期。例如希望先决定 len,再让 ch 依赖 len 取值,可写:

constraint c_order {
  solve len before ch;
}

这样 len 先按 dist 分布确定,ch 再在剩余合法空间内随机。若不加 solve...before,求解器可能优先满足 ch 的循环特性,导致 len 的分布被间接扭曲。因此,当变量之间存在依赖或分布要求时,显式指定顺序是必要的。

约束继承与动态约束

子类继承父类时,父类约束自动生效。子类可以新增约束,也可以对父类约束进行“放宽”或“收紧”,但不能直接删除父类约束。若需要临时改变约束,可使用 constraint_mode() 开关约束块,或用 rand_mode() 关闭某个随机变量。

class base_pkt;
  rand int addr;
  constraint c_addr { addr inside {[0:255]}; }
endclass

class ext_pkt extends base_pkt;
  rand int burst;
  constraint c_burst { burst inside {1,2,4,8}; }
  constraint c_addr_ext { addr inside {[16:240]}; } // 收紧
endclass

动态约束的典型用法是在测试运行时按场景切换:

ext_pkt p = new();
p.c_addr.constraint_mode(0);   // 关闭父类地址约束
p.c_addr_ext.constraint_mode(1); // 启用子类收紧约束
if (!p.randomize()) $error("randomize failed");

此外,randomize() with { ... } 可在调用点附加内联约束,适合一次性场景,但不应滥用,否则会降低约束的可读性与复用性。

约束调试与结果判断

随机化失败时,先确认 randomize() 返回值,再检查约束是否互斥。例如同时要求 addr inside {[0:15]} 和 addr inside {[16:240]},交集为空,必然失败。可用 constraint_mode() 逐块关闭定位冲突源。成功时,可通过打印或覆盖率收集观察分布是否符合 dist 预期。

建议:对关键随机变量同时使用 dist 与覆盖率,避免“能随机但覆盖不均”。randc 适合小空间均匀遍历,rand + dist 适合大空间加权分布。
随机化与约束求解关系图 随机变量声明 rand bit [3:0] len randc bit [1:0] ch rand:可重复;randc:周期不重复 约束块 inside / dist / -> solve...before 定义合法空间与分布 约束继承 父类约束自动生效 子类可新增/收紧 不可直接删除父类约束 动态约束控制 constraint_mode(0/1) 开关约束块 rand_mode(0/1) 关闭/开启随机变量 约束求解器 randomize() 返回 1 成功 / 返回 0 失败 成功:检查分布/覆盖率 失败:定位互斥约束

实践要点与常见陷阱

第一,randc 变量不要与过强约束同时使用,否则循环空间被压缩,覆盖效果反而变差。第二,dist 的权重是相对值,不是百分比,:= 与 :/ 的语义不同,混用时要明确意图。第三,solve...before 只影响求解顺序,不改变约束本身,若分布仍不符合预期,应检查是否存在其他隐含约束。第四,子类收紧父类约束时,要确保交集非空,否则随机化必然失败。第五,动态关闭约束后,务必在合适时机恢复,避免后续用例在“无约束”状态下运行。

随机化失败不会自动报错,必须显式检查 randomize() 返回值。建议在验证平台中封装统一的随机化检查函数,失败时打印约束名与变量值,便于快速定位。