开始新的验证任务

使用 toffee,你已经可以搭建出一个完整的验证环境,并且方便地去编写测试用例了。然而在实际的业务中,往往无法理解如何开始上手,并最终完成验证任务。实际编写代码后,会遇到无法正确划分 Bundle,无法正确理解 Agent 的高级语义封装,搭建完环境之后不知道做什么等问题。

在这一节中,将会介绍如何从头开始完成一个新的验证任务,以及如何更好地使用 toffee 来完成验证任务。

1. 了解待验证设计

拿到一个新的设计后,往往面对的是几十或数百个输入输出信号,如果直接看这些信号,很可能一头雾水,感觉无从下手。在这时,你必须坚信,输入输出信号都是设计人员来定义的,只要能够理解设计的功能,就能够理解这些信号的含义。

如果设计人员提供了设计文档,那么你可以阅读设计文档,了解设计的功能,并一步步地将功能与输入输出信号对应起来,并且要清楚地理解输入输出信号的时序,以及如何使用这些信号来驱动设计。一般来说,你还需要阅读设计的源代码,来找寻更细节的接口时序问题。

当大致了解了 DUT 的功能,并明白如何驱动起 DUT 接口之后,你就可以开始搭建验证环境了。

2. 划分 Bundle

搭建环境的第一件事,就是根据接口的逻辑功能,将其划分为若干个接口集合,我们可以每一个接口集合视作一个 Bundle。划分为的每个 Bundle 都应是独立的,由一个独立的 Agent 来驱动。

但是,往往实际中的接口是这样的:

|---------------------- DUT Bundle -------------------------------|

|------- Bundle 1 ------| |------ Bundle 2 ------| |-- Bundle 3 --|

|-- B1.1 --| |-- B1.2 --| |-- B2.1 --|

那么问题就出现了,例如究竟是应该为 B1.1, B1.2 各自创建一个 Agent,还是应该直接为 Bundle 1 建立一个 Agent 呢?

这还是取决于接口的逻辑功能,如果需要定义一个独立的请求,这个请求需要对 B1.1 和 B1.2 同时进行操作,那么就应该为 Bundle 1 创建一个 Agent,而不是为 B1.1 和 B1.2 分别创建 Agent。

即便如此,为 B1.1 和 B1.2 定义 1.2 也是可行的,这增添了 Agent 的划分粒度,但牺牲了操作的连续性,上层代码和参考模型的编写都会变得复杂。因此选择合适的划分粒度是需要对具体业务的权衡。最终的划分,所有的 Agent 加起来应该能覆盖 DUT Bundle 的所有接口。

实践中,为了方便 DUT 的连接,可以定义一个 DUT Bundle,一次性将所有的接口都连接到这个 Bundle 上,由 Env 将其中的子 Bundle 分发给各个 Agent。

3. 编写 Agent

当 Bundle 划分完成后,就可以开始编写 Agent 来驱动这些 Bundle 了,你需要为每个 Bundle 编写一个 Agent。

首先,可以从驱动方法开始写起,驱动方法实际上是对 Bundle 的一种高级语义封装,因此,高级语义信息应该携带了足以驱动 Bundle 的所有信息。如果 Bundle 中存在一个信号需要数字,但参数中并没有提供与这一信号相关的信息,那么这种高级语义封装就是不完整的。应尽量避免在驱动方法中对某个信号值进行假定,如果对这一信号在 Agent 中进行假定,DUT 的输出将会受到这一假定的影响,可能导致参考模型与 DUT 的行为不一致。

同时,这一高层封装也决定了参考模型的功能层级,参考模型会直接与高层语义信息进行交互,并不会涉及到底层信号。

如果参考模型需要用函数调用模式编写,那么应该将 DUT 的输出通过函数返回值来返回。如果参考模型需要用独立执行流模式编写,那么应该编写监测方法,将 DUT 的所有输出转换成高层语义信息,通过监测方法输出。

4. 封装成 Env

当所有的 Agent 编写完成后,或者挑选之前已有的 Agent,就可以将这些 Agent 封装成 Env 了。

Env 封装了整个验证环境,并确定了参考模型的编写规范。

5. 编写参考模型

参考模型的编写没有必要在 Env 编写完成之后再开始,可以与 Agent 的编写同时进行,并实时编写一些驱动代码,来检验编写的正确性。当然如果 Agent 的编写特别规范,编写完整 Env 后再编写参考模型也是可行的。

参考模型最重要的是选择合适的编写模式,函数调用模式和独立执行流模式都是可行的,但在不同的场景下,选择不同的模式会更加方便。

6. 确定功能点及测试点

编写好 Env 以及参考模型后,并不能直接开始编写测试用例,因为此时并没有测试用例的编写方向,盲目的编写测试用例,没有办法让待测设计验证完全。

首先需要列出功能点及测试点列表。功能点是待测设计支持的所有功能,例如对于一个算术逻辑单元(ALU)来说,功能点的形式可能是“支持加法”,“支持乘法”等。每个功能点需要对应一个或多个测试点,测试点通过将功能划分为不同的测试场景,来验证功能点是否正确。例如对于“支持加法”这个功能点,可能有“当输入都为正数时,加法正确”等测试点。

7. 编写测试用例

当功能点及测试点列表确定后,就可以开始编写测试用例了,一个测试用例需要能够覆盖一个或多个测试点,以验证功能点是否正确。所有的测试用例应该能够覆盖所有的测试点(功能覆盖率 100%),以及覆盖所有的设计行(行覆盖率 100%),这样一来就能保证验证的完备性。

如何保证验证的正确性呢?如果采用参考模型比对的方式,当比对失败时,toffee 会自动抛出异常,使得测试用例失败。如果采用直接比对的方式,应该在测试用例中使用 assert 来编写比对代码,当比对失败时,测试用例也会失败。最终,当所有的测试用例都通过时,意味着功能已验证为正确。

编写过程中,你需要使用 Env 中提供的接口来驱动 DUT,如果出现了需要多个驱动方法交互的情况,可以使用 Executor 来封装更高层的函数。也就是说驱动方法级的交互,是在测试用例的编写中完成的。

8. 编写验证报告

当行覆盖率和功能覆盖率都达到了 100% 之后,意味着验证已经完成。最终需要编写一个验证报告,来总结验证任务的结果。如果验证出了待测设计的问题,也应在验证报告中详细描述问题的原因。如果行覆盖率或者功能覆盖率没有达到 100%,也应在验证报告中说明原因,报告的格式应该遵循公司内部统一的规范。