面试时被问到一个场景题:如果让你验证一个支持卷积、池化等操作的神经网络加速器IP,你会如何搭建验证环境?除了常规的数据通路正确性,像不同数据位宽(INT8/FP16)、各种卷积参数(stride, padding)、以及可能出现的溢出/饱和处理,该如何设计测试用例和覆盖点?感觉这种模块输入空间巨大,有点无从下手。
数字IC验证工程师,如何针对一个‘神经网络加速器’设计验证平台?重点要验证哪些功能点?
提问
回答 15

面试问这个其实是想看你的验证思维是否系统化。我的思路是先分层:把验证平台拆成 stimulus generator(产生各种卷积参数和输入数据)、reference model(用 Python 或 C++ 写一个行为级模型,支持同样的操作)、checker(比较 DUT 输出和 reference 输出,考虑误差容限)。重点验证的功能点我列几个:1. 数据格式转换是否正确,比如 INT8 输入时,内部累加可能是更高位宽,要检查饱和截断逻辑。2. 边界情况,比如 padding 为 0 或大于 kernel 尺寸,stride 大于输入尺寸。3. 数据流控制,比如背压、数据中断又恢复的场景。4. 配置寄存器读写,以及配置后加速器是否按预期工作。测试用例设计上,我会用约束随机生成卷积参数,但会加定向用例覆盖极端组合。覆盖点包括:所有支持的数据位宽、卷积参数(kernel size, stride, padding)的组合覆盖、数据路径上每个阶段的溢出/饱和状态、错误注入(比如配置非法参数)的恢复能力。

这题我实际项目里做过类似的。验证平台用 UVM 搭,重点在于 reference model 的构建。因为神经网络加速器的操作都是确定的数学运算,所以 reference model 可以用 Python 的 numpy 来写,非常方便。验证功能点除了你说的那些,还要特别注意内存访问模式是否正确,比如数据是行优先还是列优先,会不会出现地址不对齐导致的性能下降或错误。测试用例方面,不要傻傻地随机所有参数,那样空间太大。我们当时是分几个维度去约束随机:先固定数据位宽,随机卷积参数;再固定卷积参数,随机数据位宽。还会特意构造一些数据,让中间累加和刚好处于溢出临界点,来验证饱和逻辑。覆盖点除了功能覆盖,还要加一些断言,比如 FIFO 不会上溢下溢,状态机不会进入非法状态。

从验证计划的角度聊吧。首先得明确 DUT 的规格,哪些操作是支持的(比如是否支持 depthwise conv,是否支持激活函数)。验证重点:1. 计算正确性:这是根本,用大量随机数据对比参考模型。2. 性能:是否达到预期的吞吐率,数据流是否高效,这可能需要一些时序检查或性能计数器。3. 异常处理:输入非法配置、数据错误(比如 NaN, infinity 对于 FP16)、硬件错误(ECC 错误)时,加速器能否正确处理或报告。测试用例可以分类:正常功能用例、压力测试用例(连续发数据)、异常测试用例。覆盖点设计上,要有代码覆盖(确保所有 RTL 行都跑到)和功能覆盖(比如覆盖所有 stride 和 padding 的组合,覆盖从零到饱和的整个数据范围)。

我觉得关键点在于如何管理巨大的输入空间。一个实用方法是使用覆盖率驱动验证(CDV)。定义清晰的覆盖组:比如 cov_data_type: INT8, FP16; cov_conv_params: kernel sizes (1×1, 3×3, 5×5…), strides (1,2), paddings (valid, same, custom); cov_corner_cases: zero_input, all_ones, max/min_values。然后在 test 里用约束随机去 hit 这些覆盖点。验证平台里 checker 要设计得灵活,对于 FP16 需要比较浮点误差是否在可接受范围内(比如相对误差小于 1e-3)。另外,不要忘了验证配置接口,比如动态重配置(在运行中改变卷积参数)是否工作正常。

简单直接点说。环境:UVM,或者用 cocotb 搭个 Python 环境也行。重点验:1. 单个操作的正确性(卷积、池化)。2. 多个操作组合起来的正确性(比如 conv+relu+pool)。3. 数据精度和溢出处理,这是最容易出 bug 的地方。4. 内存一致性,确保读写的地址和数据没错。测试用例:多随机,但也要手写一些关键的,比如全零数据、全最大/最小值数据、随机但可预测模式的数据(比如递增数列)。覆盖点:把规格书里的每个可配置参数都作为覆盖点,还要覆盖数据路径上的关键信号(比如累加器溢出标志位)。

补充一个角度:形式验证。对于这种控制逻辑相对规整,但数据路径复杂的模块,可以结合形式验证和仿真。用形式验证来证明控制逻辑(比如状态机、FIFO 控制)永远不会死锁或出错。用仿真来验证数据路径的计算正确性。这样分工,效率更高。验证功能点方面,形式验证可以覆盖所有可能的控制流序列,这是仿真很难穷尽的。仿真的重点就放在数据计算和边界值上。

我可能会先问面试官这个加速器的架构细节,因为不同架构验证重点不同。比如是脉动阵列还是向量处理器?数据是逐层处理还是全流水?了解清楚后再谈验证计划。通用重点:计算单元功能、数据搬运、控制流。测试用例设计上,采用灰盒验证,利用一些内部可观测点(比如 FIFO 深度、状态机状态)来指导测试生成。比如当看到 FIFO 快满时,就施加背压,验证流控机制。覆盖点除了功能,还要考虑性能覆盖,比如是否覆盖了各种数据复用模式下的时序场景。

从场景题回答技巧看,要展现结构化思维。我会这样答:第一步,理解规格,列出所有需要验证的特性(Feature List)。第二步,根据特性制定验证计划,包括环境架构、测试用例分类、覆盖模型。第三步,搭建环境,重点实现可重用的 sequence(用于生成不同参数的数据)、reference model 和 scoreboard。第四步,执行仿真,分析覆盖,查漏补缺。重点验证功能点:1. 标准功能(各种卷积池化)。2. 配置和错误处理。3. 性能指标(吞吐、延迟)。4. 功耗相关特性(如果有)。测试用例就围绕这些点展开,随机为主,定向为辅。

分享个经验:验证这种模块,数据比对是个挑战。因为中间结果可能很多,或者输出是经过量化/截断的。我们的做法是在 reference model 里模拟整个数据流,包括量化舍入和饱和逻辑,然后与 DUT 的最终输出比对。对于 INT8,要特别注意累加器(通常是更高位宽,比如 INT32)的溢出和后续的饱和到 INT8 是否正确。测试用例要专门设计让累加和超过 INT32 范围的数据(虽然难,但可以构造)。覆盖点一定要包括“饱和事件”是否被触发。

我觉得可以更关注于‘如何高效验证’。输入空间大,所以要用智能的随机约束。比如,卷积参数不是完全独立随机的,padding 太大可能让输出尺寸为负,这种无效组合要在约束里排除。验证平台里最好能实时监控覆盖率,并动态调整随机权重(如果支持)。重点功能点:除了计算,还有数据预处理和后处理(如果需要),比如数据对齐、格式打包。测试用例可以分层次:单元测试(验单个卷积核)、集成测试(验整个数据流)、系统测试(配合 CPU/DDR 验)。
发表回答
登录后可在本页底部提交回答
