想象你经营着一家无人后厨:墙上挂着的菜谱只写着“食材清单”和“制作步骤”,它本身并不是菜;只有厨师照菜谱点火下锅,才会端出一盘盘真菜。芯片验证里的 SystemVerilog 类,就是这种菜谱。验证平台最累的工作是“不停试菜”——生成大量合法、有边界、能触发漏洞的输入。SystemVerilog 给后厨请了一位自动配菜师:它能在几十万种食材组合里,按你写好的“忌口清单”快速挑出合格搭配,还能记录哪些口味从没被客人点过。这套机制,就是类的封装、继承与约束随机验证。学会它,你就能把验证平台从“手写一个个测试”升级成“自动产出海量有意义测试”的中央厨房。

岗位关联

对 AI 从业者而言,SystemVerilog 类与约束随机是理解“可控随机性与数据生成”的硬件版范本。掌握它,你便能在芯片验证、硬件建模乃至自动化测试设计中,构建既能大批量产生数据、又能严格满足业务规则的“智能生成器”。

目录
  1. 可视化演示
  2. 类的定义——菜谱模板
  3. 继承与多态——在菜谱上改良新菜
  4. 构造与封装——点火下锅与厨房规矩
  5. 随机变量——自动配菜师的标准动作
  6. 约束语法——客人的忌口清单
  7. 随机化流程——后厨的固定节奏
  8. 覆盖率机制——试吃地图与漏网之鱼
  9. 验证平台应用——中央厨房的质检流水线
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示SV类结构(属性/方法/实例化)、约束随机变量生成、类继承层次体系及功能覆盖率收集机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

SystemVerilog类结构 class封装属性(properties)与方法(methods) new构造函数实例化对象 class Transaction; rand bit[7:0] addr; endclass 类是SV面向对象验证的基础构建单元

类的定义——菜谱模板

后厨里最值钱的不是某一盘菜,而是那叠菜谱。菜谱只写“食材清单”和“制作步骤”,本身不能端上桌;厨师照着菜谱开火,才得到一盘真菜。SystemVerilog 的 class 正是这种菜谱:它定义了一类验证对象,但不占据真实仿真世界里的“盘子”。当你想产生一个具体的总线事务,就用 new() 按菜谱做一道菜,这个被做出来的实例就叫对象。

类里的数据叫属性,相当于食材清单。比如一个总线事务会有 addr、data,这些都是属性。类里的函数和任务叫方法,相当于烹饪步骤,比如 print() 打印报文内容、compare() 比较两个事务是否相同。把属性和方法打包在一起,最大的好处是“一盘菜跟着一张菜谱走”,不会出现食材和步骤散落各处的情况。

用公式表达类的组成,就是:

有人会问:那不等于结构体吗?区别正在于“方法”。结构体只装食材,菜谱还能写上“做法”。验证平台里的 Packet 类不仅能存地址和数据,还能自己打印、自己比较,甚至自己约束随机范围,这就是类最朴素的价值。

Object={attributes,methods}\displaystyle \text{Object} = \{ \text{attributes}, \text{methods} \}
这条公式说:对象是属性集合与方法集合的打包体,就像菜是“食材清单”和“制作步骤”共同作用的结果。
  1. Object(对象)类被 new() 之后产生的具体实例,真实存在于仿真内存中。
    Object\text{Object}
  2. attributes(属性)对象携带的数据,例如地址、数据、长度等。
    attributes={addr,data,len}\text{attributes} = \{ \text{addr}, \text{data}, \text{len} \}
  3. methods(方法)对象可以执行的行为,例如显示、比较、打包。
    methods={print(),compare()}\text{methods} = \{ \text{print()}, \text{compare()} \}
  4. 整体打包大括号表示把属性和方法合在一起,形成一个完整的可复用模板。
    {attributes,methods}\{ \text{attributes}, \text{methods} \}

继承与多态——在菜谱上改良新菜

无人后厨里,一道“基础炒饭”菜谱烤得金黄焦香,新厨师想推出“海鲜炒饭”,不需要把炒饭步骤重写一遍,只需在原菜谱上补充“加虾仁、加鱿鱼”。SystemVerilog 的继承就是这个意思。子类用 extends 继承父类,自动拥有父类的属性和方法,再额外添加自己的新成员。

继承不只是“复制粘贴”,它还意味着“是一种”的关系:海鲜炒饭是一种炒饭,子类对象也是一种父类对象。因此在验证平台里,你可以定义一个基类 Transaction,再派生出 EthFrame、AxiBurst,然后把子类对象交给一个只认识父类句柄的驱动器。驱动器看到的永远是“事务”,实际处理的是具体协议,这就是多态带来的复用威力。

用公式表示继承对类的影响:

子类里用 super 调用父类构造函数或父类方法,就像新菜谱先照老菜谱开火,再补上自己的特殊食材。继承让验证代码可以“增量开发”,一个公共基类维护起来,所有子类同时受益。

Cchild=Cparent+ΔC\displaystyle \mathcal{C}_{\text{child}} = \mathcal{C}_{\text{parent}} + \Delta\mathcal{C}
子类能力等于父类能力加上新增差分,旧菜谱的积累不会浪费。
  1. 父类被继承的基础模板,提供通用属性和方法。
    Cparent\mathcal{C}_{\text{parent}}
  2. 子类用 extends 声明的新模板,天然包含父类成员。
    Cchild\mathcal{C}_{\text{child}}
  3. 新增差分子类额外加入的属性和约束,比如新增 length 字段。
    ΔC\Delta\mathcal{C}
  4. 整体含义子类不是从零开始,而是在父类地基上增补。
    Cparent+ΔC\mathcal{C}_{\text{parent}} + \Delta\mathcal{C}

构造与封装——点火下锅与厨房规矩

菜谱写清了,但厨师不能直接把菜谱端给客人,得先“点火”。SystemVerilog 里 new() 就是这个点火动作。new() 用来创建对象并完成初始化,比如把 addr 设为默认值、打开文件、申请内存。构造函数的名字固定为 new,每个类都能定义自己的 new,就像不同菜谱有不同的备料时间。

封装则是后厨的铁规矩:客人不需要知道灶台温度,只需要成品。类的属性通常设为 local 或 protected,外部不许乱改,只能通过方法访问。这样做的价值在于:一旦地址范围变了,你只需要改类内部,不用满世界找 obj.addr = xxx。this 指针专门用来指明“当前操作的是哪盘菜”,避免参数名与属性名混淆。

构造函数可写成参数化形式:

句柄是通向对象的“托盘编号”。Packet p = new(); 中 p 不是对象本身,而是指向对象的句柄。多个句柄可以指向同一个对象,就像多个服务员用不同的单子指向同一道菜。封装保证了“菜怎么做”对外不可见,句柄则让验证环境里的各个组件都能安全地传递事务。

Tnew=f(init_addr,init_data)\displaystyle T_{\text{new}} = f(\text{init\_addr}, \text{init\_data})
构造函数像一份带参数的初始化任务单,传入什么初始值,就按什么方式下锅。
  1. 构造函数名new 是固定的入口,创建对象时自动执行。
    TnewT_{\text{new}}
  2. 输入参数外部传入的初始值,比如起始地址和数据。
    init_addr,init_data\text{init\_addr}, \text{init\_data}
  3. 映射关系函数 f 代表初始化逻辑,把参数填进对象属性。
    f(⋯ )f(\cdots)
  4. 整体含义不同参数组合产生不同初始状态的对象。
    Tnew=f(⋯ )T_{\text{new}} = f(\cdots)

随机变量——自动配菜师的标准动作

手工写测试向量,相当于厨师每次都要亲手抓一把米、掐表看火候。验证场景千万种,手写永远写不完。SystemVerilog 用随机变量解放了双手:在类里把属性声明为 rand 或 randc,再配合约束块,求解器就会自动生成满足条件的值。

rand 表示每次随机化时,这个变量都可以取新值;randc 更强一些,它保证在一个循环周期里尽量不重复,类似“周一到周日轮流值日”,一轮结束前不会重复。约束块 constraint 是自动配菜师的“忌口清单”,它不决定具体菜名,只负责剪掉不合法选项。

随机变量带权重的概率可以表示为:

实际写验证代码时,你不再逐条写 addr=0、addr=1,而是声明 rand bit [7:0] addr; 然后调用 randomize(),配菜师自己会按概率和约束出菜。自动配菜师最大的价值,是它不知疲倦:一百万组激励也不过是多吃几碗饭的事。

P(xi)=wi∑j=1nwj\displaystyle P(x_i) = \frac{w_i}{\sum_{j=1}^{n} w_j}
某个取值出现的概率,等于它的权重除以所有候选权重之和;权重越大,越常被选中。
  1. 目标取值x_i 是候选值,例如地址 0、1、2 中的一个。
    xix_i
  2. 单项权重w_i 表示这个候选值在约束中被分配的权重。
    wiw_i
  3. 权重总和分母把所有候选权重加起来,得到归一化因子。
    ∑j=1nwj\sum_{j=1}^{n} w_j
  4. 概率值比值越大,该值被选中的概率越高。
    wi∑j=1nwj\frac{w_i}{\sum_{j=1}^{n} w_j}

约束语法——客人的忌口清单

自动配菜师不能乱配菜,它要读懂客人的忌口清单。SystemVerilog 约束块提供了几种常用“忌口写法”。inside 用来限定范围,比如 addr inside {[16:240]} 就像“不要小于16,也不要大于240”。dist 用来表达偏好,比如 data dist {0:=20, [1:3]:/80} 表示 0 占 20 份权重,1 到 3 这三项共享 80 份权重,整体比例更偏向 [1:3]。

solve ... before ... 用来安排求解顺序,例如 solve addr before data 意思是先定地址、再定数据。约束块还可以动态开关,constraint_mode(0) 暂时关闭某条约束,constraint_mode(1) 再打开它。这些语法不是在“执行”代码,而是在描述“哪些组合是允许的”。

约束求解的合法结果集合可以写成:

写约束时最需要扭转的习惯是:约束不是 if,不是“先怎样再怎样”。它是给求解器的“考试范围”,求解器在其中找答案。这就像忌口清单不是做菜顺序,而是“别放香菜、别放花生”的最终底线。

S={x∈D∣C1(x)∧C2(x)∧⋯∧Cm(x)}\displaystyle S = \{ x \in D \mid C_1(x) \land C_2(x) \land \cdots \land C_m(x) \}
最终的合法取值集合,是所有约束条件同时成立的那些值。
  1. 候选域x \in D 表示变量的原始取值范围。
    x∈Dx \in D
  2. 单条约束C_i(x) 是第 i 条忌口条件,比如 addr inside。
    Ci(x)C_i(x)
  3. 且关系\land 表示多条约束必须同时满足,缺一不可。
    C1∧C2∧⋯C_1 \land C_2 \land \cdots
  4. 解集合只有同时满足全部约束的 x 才会留下。
    {x∈D∣⋯ }\{ x \in D \mid \cdots \}

随机化流程——后厨的固定节奏

自动配菜师虽然快,也不能乱来,每一步都有固定节奏。当类里的随机变量都声明好、约束块都写好后,调用 randomize() 就是按下“开始配菜”按钮。SystemVerilog 会自动执行一套标准流程:先调用 pre_randomize() 做准备工作,再求解约束生成随机值,最后调用 post_randomize() 做收尾工作。

pre_randomize() 常用于设置临时状态,比如把某些变量临时改为允许范围;post_randomize() 常用于派生计算,比如根据随机出的地址计算校验和、把结果打印到日志里。如果约束之间互相矛盾,根本没有合法解,randomize() 会返回 0 并输出错误信息,而不是硬塞给你一个不合法的值。

随机化的成功与否可以看作一个指示函数:

验证工程师阅读失败信息时,通常先看是“约束冲突”还是“后随机回调崩溃”。前者像忌口清单互相打架——既要求不放盐又要求必须有咸味;后者像菜已经做好但装盘时摔了。养成检查 randomize 返回值的习惯,你的后厨才不会在深夜里端出一锅坏菜。

result=[solve(C1,C2,…,Cm)≠∅]\displaystyle \text{result} = \left[ \text{solve}(C_1, C_2, \ldots, C_m) \neq \emptyset \right]
如果约束集合有解,随机化成功;如果解集为空,随机化失败并返回 0。
  1. 约束集合C_1, C_2, \ldots, C_m 是所有需要同时满足的约束。
    C1,C2,…,CmC_1, C_2, \ldots, C_m
  2. 求解动作solve(\cdots) 让约束求解器寻找合法赋值。
    solve(⋯ )\text{solve}(\cdots)
  3. 非空判断\neq \emptyset 表示“解不为空”。
    ≠∅\neq \emptyset
  4. 指示结果中括号是一种判断记号,成立为 1,否则为 0。
    [⋯ ]\left[ \cdots \right]

覆盖率机制——试吃地图与漏网之鱼

自动配菜师做了十万道菜,但菜单上的口味真的都被客人尝过了吗?覆盖率就是那张“试吃地图”。SystemVerilog 用 covergroup 收集功能覆盖率:先把关心的信号放进 coverpoint,再把信号取值范围切成一个个 bin,最后在合适时机调用 sample() 做一次“尝菜记录”。

比如你要确认地址的高位、低位、边界值都被测到,就定义 coverpoint addr { bins low = {[0:15]}; bins high = {[240:255]}; }。仿真过程中,每产生一个合法事务,就调用一次 sample(),对应 bin 被标记命中。回归结束后,调用 get_coverage() 看整体百分比。覆盖率不是“代码执行了多少行”,而是“功能点被验证了多少”。

覆盖率计算的直观形式:

后厨最怕的不是菜做多了,而是某个口味从没上过桌。覆盖率告诉你哪些 bin 还是空白,于是你可以回头微调约束,让配菜师更偏重补测那些区域。覆盖率驱动的验证,本质上是一张动态更新的“试吃地图”,把随机性引导到真正危险的地方。

cov=∣Bhit∣∣Btotal∣×100%\displaystyle \text{cov} = \frac{|B_{\text{hit}}|}{|B_{\text{total}}|} \times 100\%
覆盖率等于已被命中的 bin 数除以总 bin 数,再乘以 100%。
  1. 总 bin 数B_{\text{total}} 是你在 covergroup 里划分的所有小格子。
    BtotalB_{\text{total}}
  2. 命中 bin 数B_{\text{hit}} 是至少被采样过一次的格子。
    BhitB_{\text{hit}}
  3. 竖线含义| \cdot | 表示取集合元素个数。
    ∣B∣|B|
  4. 百分比乘以 100% 把比例变成更直观的百分比。
    ×100%\times 100\%

验证平台应用——中央厨房的质检流水线

把前面所有零件拼起来,就是一条完整的中央厨房质检流水线。验证平台的核心是“事务”:一次读、一次写、一个网络包,都可以建模成一个大类对象。事务类里既装随机变量,也装约束块;它的 new() 负责初始化,randomize() 负责批量造菜,covergroup 负责记录口味。

在这条流水线上,驱动器(driver)拿到随机好的事务对象,把它按协议时序发送给被测设计;序列发生器(sequence)负责连续不断地“下单”,让配菜师按不同偏重点产出激励。验证工程师要做的事,从“手写一万条测试”变成“写一个事务类、几条约束、一组覆盖率 bin”,剩下的交给自动化引擎。

一次随机激励的产生可抽象为:

当约束、随机化和覆盖率三者闭环时,中央厨房就有了自我改进能力:覆盖率低的区域,自动调整下一步随机方向;约束冲突时报错,快速定位“忌口清单”里的矛盾。SystemVerilog 类与约束随机,就这样把验证从“手工作坊”推进到了“自动化流水线”。

stimulusi∼Uniform(S)\displaystyle \text{stimulus}_i \sim \text{Uniform}(S)
第 i 条激励是从合法解集 S 中按均匀或带偏好分布抽样得到的。
  1. 激励序号i 表示这是第几条测试激励。
    stimulusi\text{stimulus}_i
  2. 解集合S 是所有满足约束的事务集合。
    SS
  3. 分布记号\sim 表示从某个分布中抽取。
    ∼\sim
  4. 均匀抽取Uniform(S) 表示每次在合法集合里平等地挑一个。
    Uniform(S)\text{Uniform}(S)

AI 应用场景

网络报文批量生成

阶段:约束随机激励生成

解决的问题:传统手写报文只能覆盖几十种典型组合,边界字段、保留位、异常长度很难遍历。基于类与约束随机,只需定义 EthFrame 类,把地址、长度、类型声明为 rand,再用约束块限定合法范围,就可以自动生成海量又合规的报文集。

具体案例:验证以太网 MAC 时,定义 class EthFrame; rand bit [47:0] dst; rand bit [15:0] len; constraint c { len inside {[46:1500]}; } endclass,调用 randomize() 一万次,自动得到覆盖短帧、长帧、临界长度的激励流。

覆盖率驱动的回归收敛

阶段:功能覆盖率分析与定向补测

解决的问题:回归测试跑了一整晚,报告显示有些状态机分支从未进入。人工翻日志效率太低,而 covergroup 能把关心的信号切成 bin,并实时统计命中率,告诉工程师下一步该往哪个方向加约束。

具体案例:验证 FIFO 模块时,用 coverpoint depth { bins low = {[0:3]}; bins mid = {[4:7]}; bins high = {[8:15]}; } 收集水深覆盖,发现 high 命中率低,于是修改约束让 depth 更偏向 [8:15],两轮回归后覆盖率从 72% 提升到 98%。

异常注入与错误路径测试

阶段:约束求解与失败处理

解决的问题:错误路径往往需要构造非法地址、保留位组合,定向写这些向量费时且容易漏。利用 dist 权重,可以让非法地址以小概率混入正常激励,同时依赖 post_randomize() 记录每次异常注入的现场。

具体案例:在寄存器测试中,把 addr 约束为正常地址范围,再用 constraint bad_addr { addr dist {正常:=90, 保留地址:=10}; } 让异常地址以 10% 概率出现,自动完成错误路径灌入。

Python 演示

python56 行
import randomfrom dataclasses import dataclass @dataclassclass Packet:    addr: int = 0    data: int = 0    seq: int = 0 class ConstrainedRand:    def __init__(self, addr_lo=16, addr_hi=240):        self.addr_lo = addr_lo        self.addr_hi = addr_hi        self.address = 0        self.data_val = 0        self.solve_count = 0        self.history = []     def pre_randomize(self):        self.address = max(self.address, self.addr_lo)     def post_randomize(self):        self.history.append((self.address, self.data_val))        self.solve_count += 1     def randomize(self):        self.pre_randomize()        for _ in range(100):            self.address = random.randint(self.addr_lo, self.addr_hi)            self.data_val = random.choices(                [0, 1, 2, 3, 4, 5, 6, 7],                weights=[10, 5, 5, 5, 5, 5, 5]            )[0]            if self.address % 2 == 0 and self.data_val != 0:                self.post_randomize()                return True        return False     def coverage(self):        if not self.history:            return 0.0        hit = len(set(addr for addr, _ in self.history))        return min(100.0, hit * 100.0 / 256) def main():    gen = ConstrainedRand()    packets = []    for _ in range(1000):        if gen.randomize():            packets.append(Packet(gen.address, gen.data_val, gen.solve_count))    print("generated:", len(packets))    print("covered addr bins:", round(gen.coverage(), 2), "%")    print("post_randomize calls:", gen.solve_count) if __name__ == "__main__":    main()
目的:用 Python 模拟 SystemVerilog 的约束随机化流程,随机生成满足约束的地址和数据,并统计地址覆盖率。
  1. class ConstrainedRand:定义一个“约束随机生成器”类,相当于 SystemVerilog 中的验证对象模板。
  2. def pre_randomize(self):模拟调用 randomize() 前的预随机回调,保证地址下限合法。
  3. self.address = random.randint(self.addr_lo, self.addr_hi)生成一个合法范围内的随机地址。
  4. self.data_val = random.choices(...)[0]按权重随机选择数据,模拟 dist 约束表达偏好。
  5. if self.address % 2 == 0 and self.data_val != 0:用条件模拟约束求解,过滤掉非法组合。
  6. self.post_randomize()随机化成功后记录历史,模拟后随机回调。
  7. return False允许范围内找不到合法解时返回失败,对应 randomize() 返回 0。
  8. def coverage(self):计算地址被覆盖的 bin 比例,模拟功能覆盖率。

常见误区

误区我以为 `class` 就是比 `struct` 多一堆括号,方便把 `addr`、`data` 放在一起,真正干活还是要靠外部模块写信号。

正解类不仅装数据,更装方法、构造函数、继承关系和随机约束。它更像一张能自己“做菜”的菜谱,而不是一个只有食材的保鲜盒。类是验证平台的最小功能单元,随机化和覆盖率都寄生在类里。

为何常见:从 Verilog 的 struct 和 module 过渡时,很容易把 class 当成结构体升级版,忽略了它作为“模板”和“行为体”的本质。

误区我给地址加了 `rand`,它每次应该均匀出现在 0 到 255 之间,不会偏向任何值。

正解rand 只保证“可随机化”,最终概率由约束块和 dist 权重共同决定。如果不写约束,默认可能均匀;一旦写了 inside 或 dist,求解器就会按这些条件重新分配概率。randc 才是针对循环不重复的机制。

为何常见:把“随机”等同于“均匀”,没有意识到 SystemVerilog 的随机化是“在约束引导下的随机”,这正是它能定向生成边界值的原因。

误区我认为 `constraint` 里的 `addr inside` 和 `data dist` 像 `if` 一样,仿真器从上到下执行,先判断地址再判断数据。

正解约束块是声明式描述,不是过程式代码。它把“允许哪些取值”告诉求解器,求解器同时满足所有约束。solve before 只是控制求解器确定变量的先后顺序,不改变约束本身的含义。

为何常见:多数人先学了 C 或 Verilog 的过程式写法,一看到花括号就默认是“按顺序执行”,忽略了约束求解器的工作方式。

误区我写 `obj.randomize();` 不检查返回值,认为即使约束冲突,也会随便给一个差不多能用的值,仿真照样往下跑。

正解randomize() 返回 0 表示约束求解失败,此时随机变量可能未被更新或处于不确定状态,继续使用会引入不合法激励,甚至掩盖 bug。正确做法是检查返回值,或使用 assert(obj.randomize()) 让失败在仿真时立刻暴露。

为何常见:初学者习惯于“调用函数必然成功”,忽略了约束求解这个特殊环节可能无解。

误区回归测试跑了很多轮,`line coverage` 已经 95%,我以为功能覆盖率也差不多达标了。

正解代码行覆盖率只能说明语句被执行过,不能说明关心的事务场景被验证过。比如地址边界、FIFO 满空状态、错误中断路径,都需要用 covergroup 里的 coverpoint 和 bin 显式建模。功能覆盖率回答的是“功能点是否被测试到”,而不是“代码是否被执行到”。

为何常见:EDA 工具默认会输出行覆盖率,名字里都带“coverage”,容易让人混淆两者的统计对象。

面试题

Q1. 在 SystemVerilog 中,class 最重要的作用是?

单选题 · easy

  • A.定义数据与方法组合的对象模板← 正确答案
  • B.把多个信号连接成总线端口
  • C.自动生成测试平台的仿真时钟
  • D.取代 module 完成硬件综合

答案:A。class 相当于菜谱模板,把属性和方法打包起来,用于创建可复用的对象。B 是 DUT 端口连接,C 是时钟生成,D 是硬件描述,都不是 class 的核心职责。

Q2. 下列哪个语句可以在仿真中创建类对象?

单选题 · easy

  • A.Packet p = new();← 正确答案
  • B.Packet p = create();
  • C.Packet p = malloc();
  • D.Packet p = build();

答案:A。SystemVerilog 里创建对象统一使用构造函数 new()。create、malloc、build 都不是 SystemVerilog 的关键字,强行使用会编译报错。

Q3. 约束块中 addr inside {[0:255]} 表达的含义是?

单选题 · mid

  • A.addr 的取值范围限定为 0 到 255← 正确答案
  • B.addr 只能取 0 或者 255 两个值
  • C.addr 只能取 0 到 255 之间的奇数
  • D.addr 必须大于 255 才能通过校验

答案:A。inside {[0:255]} 是 SystemVerilog 的范围约束语法,表示闭区间,addr 在 0 到 255 之内都合法。B 是两个离散值,C 是奇偶约束,D 方向完全相反。

Q4. randc 变量与 rand 变量的主要差异是?

单选题 · mid

  • A.randc 在一个循环周期内尽量不重复← 正确答案
  • B.randc 不需要满足任何约束条件
  • C.randc 只适用于整型变量
  • D.randc 会自动调用 post_randomize

答案:A。randc 是循环随机变量,在周期内会避免重复取值,像轮流值日表;rand 不做这种保证。约束对 randc 同样生效,post_randomize 也不会自动跳过。

Q5. 子类构造函数中调用父类构造函数的正确方式是?

单选题 · mid

  • A.使用 super.new() 调用父类构造函数← 正确答案
  • B.直接使用 this.new() 调用父类构造函数
  • C.父类构造函数不能被任何子类调用
  • D.必须先调用子类自身再调用父类构造函数

答案:A。super 专门用来访问父类成员,super.new() 是调用父类构造函数的标准写法。this 指向当前对象,不能用来调父类构造;D 的调用顺序根本不存在。

Q6. 约束块 constraint_mode(0) 的作用是?

单选题 · hard

  • A.暂时关闭该约束块,使其不参与求解← 正确答案
  • B.永久删除该约束块并释放内存
  • C.将该约束块转换成随机生成模式
  • D.将该约束块提升为最高优先级

答案:A。constraint_mode(0) 相当于暂时把这条忌口清单收起来,之后随机化不再考虑它;constraint_mode(1) 可以再打开。它不会删除约束块,也不改变优先级。

Q7. solve addr before data 约束的直接效果是?

单选题 · extreme

  • A.求解器先为 addr 定值,再为 data 定值← 正确答案
  • B.仿真器按书写顺序逐个执行随机化
  • C.约束块在编译阶段就被提前求值
  • D.addr 与 data 的取值完全相互独立

答案:A。solve ... before ... 告诉约束求解器变量的求解顺序,用来影响概率分布,而不是让仿真器“执行语句”。求解顺序改变的是概率分配,不改变约束本身,更不能让两个变量完全独立。

Q8. 类中可以包含哪些成员?

多选题 · easy

  • A.数据属性与对象句柄← 正确答案
  • B.任务、函数等子程序← 正确答案
  • C.约束块与随机变量← 正确答案
  • D.独立于类的模块端口

答案:ABC。类是完整的打包模板,数据属性、方法、约束块和随机变量都能放进去。模块端口属于 module 级别的接口概念,不属于类的成员。

Q9. 关于 dist 约束语法,正确的描述有?

多选题 · mid

  • A.:= 为取值列表中的每个值指定权重← 正确答案
  • B.:/ 将权重均匀分给区间内的每个值← 正确答案
  • C.它常用来产生有偏好的概率分布← 正确答案
  • D.它等价于一条 if 判断语句

答案:ABC。dist 是权重分布约束::= 和 :/ 都用于设置权重,前者逐项生效,后者在区间内平分权重,因此能制造偏向性概率。它是声明式条件,和 if 没有等价关系。

Q10. 收集功能覆盖率通常需要哪些要素?

多选题 · hard

  • A.定义 covergroup 并声明 coverpoint← 正确答案
  • B.为每个 coverpoint 划分 bin← 正确答案
  • C.在合适时机调用 sample()← 正确答案
  • D.把被测模块替换为纯行为模型

答案:ABC。功能覆盖率三要素是 covergroup 容器、coverpoint/bin 划分和 sample 采样。不采集数据就无法更新覆盖率。替换被测模块是建模策略,不是覆盖率收集的必要条件。