System Verilog 断言, SVA

一、引言

1.1 研究背景与目的

在当今的数字集成电路设计领域,随着芯片规模和复杂度的飞速增长,硬件设计验证面临着前所未有的挑战。传统的验证方法在面对复杂的设计时,往往难以确保设计的正确性和可靠性。SystemVerilog Assertion(SVA)作为一种强大的硬件验证技术应运而生,它为硬件设计验证提供了一种高效、准确的方式。

本研究旨在深入探讨 SystemVerilog Assertion 的概念、原理、语法结构以及在实际硬件设计验证中的应用,通过详细的分析和实例展示,帮助读者全面理解和掌握 SVA 技术,从而提高硬件设计验证的效率和质量,确保设计满足预期的功能和性能要求。

1.2 研究意义

在硬件设计流程中,验证环节至关重要。据统计,在大型芯片设计项目中,验证工作往往占据了整个项目周期的 70% 以上的时间和资源。如果在设计后期才发现问题,修复成本将呈指数级增长。SVA 的出现,为解决这些问题提供了有力的工具。

从设计验证的角度来看,SVA 能够精确地描述硬件设计的行为和属性,将复杂的设计意图转化为可验证的断言。通过在设计中插入断言,工程师可以在仿真和形式验证过程中实时监测设计的运行情况,一旦发现违反断言的情况,立即发出警报,从而快速定位和解决问题。这大大提高了验证的效率和覆盖率,减少了潜在的设计错误,提高了芯片的可靠性和稳定性。

从行业发展的角度来看,随着半导体技术的不断进步,芯片的集成度越来越高,功能越来越复杂。SVA 作为一种先进的验证技术,已经成为行业的标准和趋势。掌握 SVA 技术,不仅有助于工程师提升自身的专业技能,也符合整个行业对高质量硬件设计的需求,推动了芯片设计行业的发展。

1.3 研究方法与范围

本研究主要采用文献研究法和案例分析法。通过广泛查阅相关的学术文献、技术报告以及行业标准,深入了解 SVA 的理论基础、技术原理和应用现状。同时,结合实际的硬件设计案例,详细分析 SVA 在不同场景下的应用,通过具体的代码实现和仿真结果,展示 SVA 的使用方法和优势。

本研究的范围主要聚焦于 SystemVerilog Assertion 本身,包括其基本概念、断言类型、语法结构、操作符以及在实际应用中的各种技巧和注意事项。同时,也会涉及到与 SVA 紧密相关的硬件设计验证流程、仿真工具和形式验证工具的使用,但不会深入探讨硬件设计的具体实现细节和其他验证方法。

二、SystemVerilog Assertion 基础

2.1 定义与概念

SystemVerilog Assertion(SVA)是 SystemVerilog 硬件描述语言的一个重要扩展,它提供了一种形式化的方式来描述和验证硬件设计的属性和行为。断言是对设计属性的一种描述,它定义了在特定条件下设计应该满足的行为。如果在仿真或形式验证过程中,设计的行为不符合断言所定义的属性,那么断言就会失败,从而提示设计中可能存在的问题。

例如,在一个简单的加法器设计中,我们可以使用 SVA 来断言:当两个输入信号有效时,输出信号应该等于两个输入信号之和。这样,在验证过程中,仿真器或形式验证工具就会自动检查设计是否满足这个断言,大大提高了验证的效率和准确性。

在硬件设计验证流程中,SVA 扮演着至关重要的角色。传统的验证方法主要依赖于仿真,通过输入各种测试向量来观察设计的输出是否正确。然而,这种方法存在一定的局限性,很难保证覆盖到所有可能的情况。而 SVA 可以在设计中插入各种断言,实时监测设计的行为,不仅可以在仿真中使用,还可以用于形式验证,从而弥补了传统验证方法的不足,提高了验证的覆盖率和可靠性。

2.2 断言类型

2.2.1 立即断言

立即断言(Immediate Assertion)是基于仿真事件语义执行的断言。它的执行方式类似于过程语句,当程序执行到立即断言所在的程序块时,断言会立即被执行。立即断言必须放在过程块(如 initial、always 块)中定义。

例如,以下是一个使用立即断言检查信号值的示例:

 

initial begin

// 等待信号bus.cb.request小于等于1

wait(bus.cb.request <= 1);

// 重复2次,等待bus.cb信号的变化

repeat(2) @bus.cb;

// 立即断言:检查bus.cb.grant是否等于2'b01

al: assert (bus.cb.grant == 2'b01)

else $error("Grant not asserted");

end

在这个例子中,当程序执行到al: assert (bus.cb.grant == 2'b01)这一行时,会立即对bus.cb.grant的值进行检查。如果bus.cb.grant不等于2'b01,则断言失败,执行else后面的语句,输出错误信息"Grant not asserted"。

立即断言与 if 语句有相似之处,但也存在明显的区别。它们都可以对条件进行判断,但断言更加紧凑,且断言里面的逻辑跟 if 语句是相反的。在断言中,设计者期望括号内的表达式为真,否则输出错误;而在 if 语句中,通常是当条件为真时执行相应的语句块。例如,使用 if 语句实现相同的功能如下:

 

initial begin

wait(bus.cb.request <= 1);

repeat(2) @bus.cb;

if(bus.cb.grant != 2'b01)

$display("Error, grant != 1");

end

可以看到,if 语句的逻辑是当bus.cb.grant不等于2'b01时,输出错误信息,这与断言的逻辑相反。此外,断言还可以结合$fatal、$error、$warning和$info等函数给出不同级别的消息提示,方便调试和错误定位。

2.2.2 并发断言

并发断言(Concurrent Assertion)是一种基于时钟周期的断言,它可以在整个仿真过程中连续运行,不断检查信号的值。并发断言需要在断言内指定一个采样时钟,变量的采样是在时钟边沿的 Preponed 区域中完成,表达式的求值是在 Observed 区域中完成。

例如,以下是一个检查仲裁器request信号的并发断言示例:

 

interface arb_if(input bit clk);

logic [1:0] grant, request;

logic rst;

property request_2state;

@(posedge clk) disable iff(rst);

$isunknown(request) == 0;

endproperty

assert_request_2state: assert property(request_2state);

endinterface

在这个例子中,request_2state属性定义了一个并发断言,它在时钟clk的上升沿对request信号进行采样,并检查request信号是否为未知值(X或Z)。disable iff(rst)表示当rst信号有效时,断言暂时失效。如果在任何一个时钟上升沿,request信号为未知值,则断言失败。

并发断言的特性使其非常适合用于验证设计中的时序逻辑和状态机。它可以实时监测信号在时钟周期内的变化,及时发现设计中的时序错误和异常情况。与立即断言不同,并发断言不需要放在过程块中,可以在模块(module)、接口(interface)、程序(program)等定义中使用,并且可以在形式验证和仿真工具中通用,为硬件设计验证提供了更强大的功能和灵活性。

2.3 断言层次结构

2.3.1 布尔表达式

布尔表达式是组成断言的最小单元,它由信号、变量以及常用的逻辑操作符(如&&、||、!、^等)组成。通过布尔表达式,可以构建简单的断言来验证设计中的基本逻辑关系。

例如,以下是一个简单的布尔表达式断言:

 

example_assert: assert property(@(posedge clk) a && b == 1);

在这个断言中,@(posedge clk)指定了采样时钟为clk的上升沿。在每个clk的上升沿,会对a && b == 1这个布尔表达式进行求值。如果表达式为真,则断言通过;如果表达式为假,则断言失败。这个断言用于验证在clk的上升沿,信号a和b的逻辑与结果是否等于 1。

布尔表达式断言虽然简单,但在硬件设计验证中起着基础的作用。它可以用于验证单个信号的状态、多个信号之间的简单逻辑关系等,是构建更复杂断言的基础。

2.3.2 序列(Sequence)

序列是比布尔表达式更高一层的单元,它用于表示在一段时间内发生的一组值的规范。在硬件设计中,功能往往由多个逻辑事件的组合来表示,序列可以很好地描述这些事件序列,使断言更加易读和复用性高。

序列具有以下特性:

  • 可带参数:可以通过参数化来提高序列的通用性,使其适用于不同的信号和场景。
  • 可以在 property 中调用:序列通常作为属性的一部分,用于构建更复杂的断言逻辑。
  • 可以使用局部变量:在序列中可以定义和使用局部变量,方便对中间结果进行存储和处理。
  • 可以定义时钟周期:每个序列都可以指定自己的时钟周期,用于确定事件发生的时间间隔。
  • 可以使用序列方法:如ended、matched、triggered等,这些方法只能在序列中使用,用于判断序列的匹配情况和结束状态。

以下是一些序列的示例:

 

// 简单序列:在时钟上升沿检查a是否为1

sequence seq_1;

@(posedge clk) a == 1;

endsequence

// 带逻辑关系的序列:在时钟上升沿检查a或b是否为1

sequence seq_2;

@(posedge clk) a || b;

endsequence

// 带参数的序列:形参a和b,在时钟上升沿检查a或b是否为1

sequence seq_3 (a, b);

@(posedge clk) a || b;

endsequence

// 带时序关系的序列:在时钟上升沿检查a是否为1,若a为1,则检查两个周期之后b是否为1

sequence seq_4;

@(posedge clk) a ##2 b;

endsequence

在这些示例中,seq_1是一个简单的序列,它在clk的上升沿检查信号a是否为 1;seq_2是带逻辑关系的序列,检查a或b是否为 1;seq_3是带参数的序列,通过参数a和b来灵活指定检查的信号;seq_4是带时序关系的序列,使用##2表示在a为 1 后的两个时钟周期检查b是否为 1。这些序列可以根据具体的设计需求进行组合和使用,为断言提供了丰富的表达能力。

2.3.3 属性(Property)

属性是比序列更高一层的单元,也是构成断言最常用的模块。属性用于描述设计中需要验证的行为和性质,它可以包含布尔表达式、序列以及其他属性。属性中最重要的性质是可以使用蕴含操作符(|->和|=>),用于表示条件和结果之间的逻辑关系。

属性可以调用序列,通过将序列组合在属性中,可以构建出复杂的断言逻辑。以下是一个属性调用序列的示例:

 

sequence seq_1;

@(posedge clk) a || b;

endsequence

property p;

seq_1;

endproperty

a_1: assert property(p);

在这个例子中,seq_1定义了一个序列,在clk的上升沿检查a或b是否为 1。p属性调用了seq_1,a_1断言使用了p属性。当a_1断言执行时,会在每个clk的上升沿检查seq_1序列是否匹配,如果匹配则断言通过,否则断言失败。

属性与序列的相同点在于,任何在序列中的表达式都可以放到属性中,反之亦然。但它们也有明显的不同之处:只有在属性中才能使用蕴含操作符;属性中可以例化其他属性和序列,而序列中不能例化属性;属性需要用cover、assert、assume等关键字进行实例化,而序列直接调用即可。属性的这些特点使其在描述复杂的设计行为和验证逻辑时具有更大的优势,是 SVA 中实现高级验证功能的关键组成部分。

三、为什么使用 SystemVerilog Assertion

3.1 验证需求的演变

随着半导体工艺技术的不断进步,硬件设计的复杂度呈现出指数级增长。现代芯片集成了数以亿计的晶体管,实现了复杂的功能,如多核处理器、高速通信接口、复杂的数字信号处理算法等。这种复杂度的提升对硬件设计验证提出了前所未有的挑战。

在早期的硬件设计中,由于设计规模较小,功能相对简单,传统的验证方法,如基于测试向量的仿真,能够满足验证需求。工程师可以通过手动编写测试向量,覆盖各种可能的输入组合,观察设计的输出是否符合预期。然而,随着设计复杂度的增加,这种方法变得越来越难以应对。

例如,在一个具有多个输入和输出端口、复杂时序逻辑和状态机的设计中,要穷举所有可能的输入组合几乎是不可能的。即使能够生成大量的测试向量,仿真时间也会变得非常漫长,难以在合理的时间内完成验证。而且,传统的仿真方法很难发现一些潜在的设计错误,如时序违规、状态机死锁等,这些错误可能在特定的条件下才会出现,难以通过随机的测试向量覆盖到。

为了应对这些挑战,验证技术需要不断演进。SystemVerilog Assertion(SVA)应运而生,它提供了一种更加形式化、高效的验证手段。SVA 允许工程师在设计中插入断言,明确描述设计的行为和属性,将复杂的设计意图转化为可验证的规则。通过断言,不仅可以在仿真过程中实时监测设计的运行情况,还可以利用形式验证工具对设计进行全面的分析,检查是否存在违反断言的情况,从而大大提高了验证的覆盖率和准确性,满足了现代复杂硬件设计的验证需求。

3.2 SVA 的优势

3.2.1 形式化验证能力

SVA 提供了强大的形式化验证能力,这是其相较于传统验证方法的重要优势之一。形式化验证是一种基于数学推理的验证方法,它通过构建数学模型,对设计的所有可能状态进行遍历和分析,从而证明设计是否满足特定的属性和规范。

在使用 SVA 进行形式化验证时,工程师首先需要使用 SVA 断言语言描述设计的属性和行为。这些断言可以精确地定义设计在不同条件下应该满足的逻辑关系和时序要求。例如,在一个总线协议的设计中,可以使用 SVA 断言来描述总线请求、授权、数据传输等各个阶段的时序和逻辑关系,确保总线操作的正确性。

然后,形式验证工具会根据这些断言,对设计进行分析。工具会构建设计的状态空间模型,并使用各种算法对状态空间进行遍历,检查是否存在任何违反断言的情况。如果发现违反断言的情况,工具会生成反例,展示出导致错误的具体输入和状态序列,帮助工程师快速定位和解决问题。

这种形式化验证的方法具有高度的准确性和全面性。与传统的仿真验证方法不同,形式化验证不需要依赖大量的测试向量来覆盖各种可能的情况,而是通过数学推理来证明设计的正确性。它可以检查到设计中所有可能的状态和输入组合,避免了因测试向量覆盖不全而遗漏错误的情况。因此,SVA 的形式化验证能力能够大大提高验证的效率和质量,减少设计中的潜在风险,为硬件设计的可靠性提供了有力的保障。

3.2.2 多层次验证支持

SVA 在不同设计层次的验证中都发挥着重要作用,为硬件设计的全面验证提供了有力支持。

在模块级验证中,SVA 可以用于验证单个模块的功能正确性。工程师可以针对模块的输入输出信号、内部状态机等定义各种断言,检查模块在不同输入条件下的行为是否符合设计规范。例如,在一个加法器模块中,可以使用 SVA 断言检查输入信号和输出信号之间的数学关系,确保加法器的计算结果正确。通过在模块级使用 SVA 断言,可以及时发现模块内部的设计错误,减少错误在后续设计阶段的传播。

在芯片级验证中,SVA 可以用于验证多个模块之间的交互和协同工作。随着芯片复杂度的增加,模块之间的接口和通信变得越来越复杂,容易出现时序不匹配、信号冲突等问题。使用 SVA 断言可以描述模块之间的接口协议和时序要求,验证芯片在整体运行过程中的正确性。例如,在一个包含多个处理器核和外围设备的芯片中,可以使用 SVA 断言验证处理器核与内存控制器、总线仲裁器等模块之间的通信是否正常,确保芯片的整体功能正确。

在系统级验证中,SVA 同样具有重要价值。系统级验证关注的是整个系统在不同工作场景下的行为和性能。通过使用 SVA 断言,可以验证系统的各种功能需求,如系统的启动、运行、停止等过程是否符合设计要求,以及系统在不同负载条件下的性能表现是否满足预期。例如,在一个通信系统中,可以使用 SVA 断言验证数据包的传输、接收和处理是否正确,确保系统在各种通信场景下的可靠性。

SVA 的多层次验证支持能力使得它能够贯穿整个硬件设计流程,从模块级到芯片级再到系统级,全面保障设计的正确性。它为不同层次的验证提供了统一的验证手段,提高了验证的效率和一致性,有助于及时发现和解决设计中的问题,降低设计成本和风险。

3.2.3 错误捕捉与调试信息

SVA 在错误捕捉和提供调试信息方面表现出色,能够帮助工程师快速定位和解决硬件设计中的问题。

当设计行为违反了 SVA 断言所定义的属性时,断言会立即失败,并给出详细的错误提示信息。这些信息包括断言失败的时间、位置以及具体的错误条件,为工程师提供了清晰的线索,便于快速定位问题所在。例如,在一个状态机设计中,如果使用 SVA 断言检查状态机的状态转移是否符合设计规范,当出现非法的状态转移时,断言会报告失败,并指出是在哪个时钟周期、哪个状态下发生了错误的转移,工程师可以根据这些信息迅速定位到状态机代码中的问题点。

此外,SVA 还可以结合仿真工具,生成波形文件,直观地展示断言失败时信号的变化情况。通过查看波形文件,工程师可以更深入地了解设计的运行状态,进一步分析问题的原因。例如,在一个时序逻辑设计中,如果断言检测到时序违规,仿真工具生成的波形文件可以显示出相关信号的时序关系,帮助工程师判断是由于信号延迟、时钟偏差还是其他原因导致了时序错误。

在实际应用中,SVA 的错误捕捉和调试信息功能大大提高了硬件设计验证的效率。它能够在设计开发的早期阶段及时发现问题,避免问题在后续设计流程中扩大化,从而降低了修复问题的成本。同时,详细的错误提示和波形分析功能也有助于工程师更快地理解问题,提高调试的准确性和速度,加快硬件设计的开发进程。

3.2.4 与其他验证方法的结合

SVA 与其他验证方法的有机结合,能够充分发挥各自的优势,进一步提高硬件设计验证的效率和覆盖率。

与仿真验证结合时,SVA 断言可以在仿真过程中实时监测设计的行为。在传统的仿真验证中,工程师通过输入测试向量来观察设计的输出是否正确,但这种方法很难保证覆盖到所有可能的情况。而 SVA 断言可以在仿真的每一个时钟周期对设计的属性进行检查,一旦发现违反断言的情况,立即发出警报。这样,即使测试向量没有覆盖到某些特殊情况,SVA 断言也有可能捕捉到设计中的错误。同时,仿真工具可以利用 SVA 断言提供的信息,生成更加详细的仿真报告,帮助工程师更好地理解设计的运行情况。

与形式化验证结合时,SVA 断言作为形式化验证的输入,定义了设计需要满足的属性和规范。形式化验证工具通过对 SVA 断言的分析,构建设计的数学模型,并对模型进行全面的验证。这种结合方式能够充分发挥形式化验证的优势,即对设计的所有可能状态进行穷举分析,确保设计的正确性。同时,SVA 断言的使用也使得形式化验证更加直观和易于理解,因为断言是基于设计的实际行为和属性来定义的,与工程师的设计思路更加贴近。

SVA 与其他验证方法的结合,为硬件设计验证提供了一种综合性的解决方案。通过不同验证方法之间的互补,可以提高验证的全面性和准确性,减少设计中的潜在风险,确保硬件设计能够满足日益复杂的功能和性能要求。

3.3 传统验证方法的不足

与 SystemVerilog Assertion(SVA)相比,传统的 Verilog 检查方式存在诸多不足,这些不足在硬件设计复杂度不断增加的情况下愈发明显。

在控制复杂时序方面,传统 Verilog 检查面临着巨大挑战。硬件设计中的时序关系往往错综复杂,涉及到多个信号在不同时钟周期的变化和相互作用。传统的 Verilog 检查主要依赖于基于事件驱动的仿真,通过编写测试向量来模拟各种输入情况,观察输出结果是否符合预期。然而,对于复杂的时序逻辑,很难通过有限的测试向量覆盖到所有可能的情况。例如,在一个包含多个状态机和复杂数据通路的设计中,不同状态机之间的状态转移和数据传输可能存在微妙的时序依赖关系,传统的 Verilog 检查很难全面验证这些关系是否正确。而 SVA 可以通过断言语言精确描述时序属性,利用形式化验证工具对所有可能的时序状态进行分析,有效弥补了传统方法在这方面的不足。

从维护性角度来看,传统 Verilog 检查的代码往往冗长且难以维护。在传统的验证方式中,验证逻辑通常与测试向量紧密耦合在一起,随着设计的不断修改和完善,测试向量和验证逻辑需要频繁调整。这使得验证代码变得越来越复杂,可读性和可维护性较差。例如,当设计中的某个模块功能发生变化时,不仅需要修改测试向量以适应新的功能,还需要调整与之相关的验证逻辑,这一过程容易引入新的错误。相比之下,SVA 将验证逻辑以断言的形式独立出来,与设计代码分离。断言具有清晰的语法结构和语义,易于理解和修改。当设计发生变化时,只需对相应的断言进行调整,而不会影响到其他部分的验证逻辑,大大提高了验证代码的维护性。

传统 Verilog 检查在验证覆盖率方面也存在局限性。由于其主要依赖于仿真,而仿真的覆盖率受到测试向量的数量和质量的限制。要达到较高的覆盖率,需要生成大量的测试向量,这不仅增加了验证的时间和成本,还难以保证覆盖到所有的边界情况和潜在的错误。而 SVA 结合形式化验证,可以对设计进行全面的分析,从数学上证明设计是否满足断言所定义的属性,能够有效提高验证的覆盖率,发现传统方法难以检测到的设计缺陷。

综上所述,传统的 Verilog 检查方式在控制复杂时序、维护性和验证覆盖率等方面存在明显不足,而 SVA 通过其强大的断言功能和与形式化验证的结合,为硬件设计验证提供了更高效、准确和可维护的解决方案。

四、SystemVerilog Assertion 的使用方法

4.1 基本语法与结构

SystemVerilog Assertion(SVA)的基本语法围绕着断言(assert)、属性(property)和序列(sequence)这几个关键概念展开。

断言是用于验证设计属性的语句,其基本语法格式为:

 

assertion_name: assert property (property_expression) [else action_block];

其中,assertion_name是断言的名称,用于标识该断言;property_expression是属性表达式,定义了需要验证的属性;else action_block是可选部分,当断言失败时执行的操作,通常用于输出错误信息。

属性是描述设计行为和性质的单元,它可以包含布尔表达式、序列以及其他属性。属性的定义语法如下:

 

property property_name;

// 属性定义内容

endproperty

例如,以下是一个简单的属性定义,用于检查信号a在时钟clk的上升沿是否为高电平:

 

property p1;

@(posedge clk) a;

endproperty

在这个例子中,@(posedge clk)指定了采样时钟为clk的上升沿,a是一个布尔表达式,表示信号a为高电平。

序列是描述在一段时间内发生的一组值的规范,它可以用于构建属性。序列的定义语法如下:

 

sequence sequence_name;

// 序列定义内容

endsequence

例如,以下是一个序列定义,用于描述在时钟clk的上升沿,信号a为高电平后,经过两个时钟周期,信号b为高电平:

 

sequence s1;

@(posedge clk) a ##2 b;

endsequence

这里,##2表示延迟两个时钟周期。

通过将序列和属性结合,可以构建出复杂的断言。例如,将上述序列和属性结合,创建一个断言来验证设计是否满足该序列和属性的要求:

 

sequence s1;

@(posedge clk) a ##2 b;

endsequence

property p1;

s1;

endproperty

a1: assert property(p1);

在这个例子中,断言a1使用了属性p1,而属性p1调用了序列s1。当a1断言执行时,会在每个clk的上升沿检查序列s1是否匹配,如果匹配则断言通过,否则断言失败。

4.2 建立断言的步骤

4.2.1 确定验证目标

确定验证目标是建立断言的首要步骤,它直接关系到断言的有效性和针对性。验证目标的确定需要紧密结合设计需求和规范。例如,在设计一个简单的寄存器文件时,其设计需求可能包括正确的读写操作、地址译码的准确性以及寄存器值的正确保存等。

基于这些设计需求,我们可以确定以下验证目标:

  • 验证写操作时,数据是否能正确写入指定地址的寄存器中。
  • 验证读操作时,从指定地址读取的数据是否与之前写入的数据一致。
  • 验证地址译码逻辑是否正确,确保每个地址都能准确对应到相应的寄存器。

在确定验证目标时,需要明确、具体地描述每个目标,以便后续构建断言。例如,对于 “验证写操作时,数据是否能正确写入指定地址的寄存器中” 这一目标,可以进一步细化为:在写使能信号有效时,将特定数据写入指定地址,然后在接下来的时钟周期中,检查该地址对应的寄存器值是否等于写入的数据。这样明确的验证目标为后续构建断言提供了清晰的方向,确保断言能够准确地验证设计的功能正确性。

4.2.2 构建布尔表达式

布尔表达式是组成断言的基础单元,它用于描述设计中信号之间的逻辑关系。在确定了验证目标后,我们可以根据目标构建相应的布尔表达式。

例如,对于上述寄存器文件设计中 “验证写操作时,数据是否能正确写入指定地址的寄存器中” 的验证目标,假设设计中有信号write_en(写使能信号)、write_addr(写地址信号)、write_data(写数据信号)和register_file[write_addr](表示地址为write_addr的寄存器)。我们可以构建如下布尔表达式:

 

(write_en) && (register_file[write_addr] == write_data)

这个布尔表达式表示当write_en为高电平时,地址为write_addr的寄存器中的值应该等于write_data。在断言中,这个布尔表达式将用于检查写操作的正确性。

再比如,对于 “验证地址译码逻辑是否正确,确保每个地址都能准确对应到相应的寄存器” 的验证目标,假设存在信号read_addr(读地址信号)和decoded_signal(译码后的信号,用于选择相应的寄存器),可以构建布尔表达式:

 

(decoded_signal[read_addr] == 1)

该表达式表示当读取地址为read_addr时,对应的decoded_signal位应该为 1,以此来验证地址译码的正确性。通过构建这些布尔表达式,我们可以将抽象的验证目标转化为具体的可验证的逻辑条件,为后续定义属性和断言提供基础。

4.2.3 定义序列

序列用于描述在一段时间内发生的一组值的规范,它可以更精确地表达设计中的时序关系。定义序列时,需要考虑到验证目标中的时序要求,包括信号的先后顺序、时间间隔等。

例如,在验证一个简单的 FIFO(先进先出队列)时,我们可能有如下验证目标:当写入数据时,数据应该按照顺序依次进入 FIFO,并且在读取数据时,读出的数据顺序与写入顺序一致。基于这个目标,我们可以定义以下序列:

首先,定义一个表示写入操作的序列write_seq:

 

sequence write_seq;

@(posedge clk) write_en &&!full ##1 fifo_data[write_ptr] == write_data;

endsequence

在这个序列中,@(posedge clk)指定了时钟上升沿,write_en &&!full表示写使能信号有效且 FIFO 未满,##1表示延迟一个时钟周期,fifo_data[write_ptr] == write_data表示在延迟一个周期后,将write_data写入到write_ptr指向的 FIFO 位置。

然后,定义一个表示读取操作的序列read_seq:

 

sequence read_seq;

@(posedge clk) read_en &&!empty ##1 fifo_data[read_ptr] == read_data;

endsequence

这里,read_en &&!empty表示读使能信号有效且 FIFO 非空,##1延迟一个周期后,检查读出的数据read_data是否等于read_ptr指向的 FIFO 位置的数据。

此外,序列还可以带参数,以提高复用性。例如,定义一个通用的信号变化序列signal_change_seq:

 

sequence signal_change_seq(sig);

@(posedge clk) $rose(sig) || $fell(sig);

endsequence

这个序列可以用于检测任意信号sig的上升沿或下降沿,通过传入不同的信号参数,实现对不同信号变化的检测,增强了序列的通用性和灵活性,满足了不同验证场景下对信号时序关系的描述需求。

4.2.4 定义属性

属性是对设计行为和性质的高层次描述,它可以将布尔表达式和序列组合起来,形成更复杂的验证逻辑。定义属性时,需要使用蕴含操作符(|->和|=>)来表达条件和结果之间的关系。

例如,继续以上述 FIFO 的验证为例,我们可以定义一个属性fifo_property,用于验证 FIFO 的读写操作是否正确:

 

property fifo_property;

@(posedge clk) disable iff (rst);

write_seq |-> ##[1:$] read_seq;

endproperty

在这个属性中,@(posedge clk) disable iff (rst)表示在时钟上升沿进行检查,并且当复位信号rst有效时,断言失效。write_seq |-> ##[1:$] read_seq表示如果write_seq序列匹配(即发生了写操作),那么在接下来的 1 个时钟周期到无穷个时钟周期内,read_seq序列必须匹配(即发生读操作),以此来验证写入的数据能够被正确读出。

再比如,在验证一个状态机时,我们可以定义一个属性来检查状态转移是否符合设计规范。假设状态机有状态信号state,定义如下属性:

 

property state_transition_property;

@(posedge clk) disable iff (rst);

$rose(state_change_signal) |-> next_state == expected_next_state;

endproperty

这里,$rose(state_change_signal)表示状态变化信号的上升沿,当状态变化信号上升时,|->操作符表示接下来要检查next_state是否等于expected_next_state,即验证状态转移是否正确。通过这样的属性定义,可以清晰地描述状态机的行为规范,为验证提供准确的依据。

4.2.5 断言实例化

断言实例化是将定义好的属性应用到设计中的过程,通过实例化断言,我们可以在仿真或形式验证过程中对设计进行实时监测。断言实例化使用assert关键字,其基本语法为:

 

assertion_name: assert property (property_name) [else action_block];

例如,对于上述定义的 FIFO 属性fifo_property,我们可以进行如下断言实例化:

 

fifo_assertion: assert property (fifo_property)

else $error("FIFO read - write operation error");

在这个断言实例化中,fifo_assertion是断言的名称,用于标识该断言。当fifo_property属性不满足时,即 FIFO 的读写操作出现错误,else后面的$error("FIFO read - write operation error")语句将被执行,输出错误信息"FIFO read - write operation error",方便工程师定位和解决问题。

又如,对于状态机的属性state_transition_property,断言实例化如下:

 

state_assertion: assert property (state_transition_property)

else $display("State transition error at time %0t", $time);

当状态机的状态转移不符合state_transition_property属性时,断言失败,执行else语句,输出错误信息"State transition error at time %0t",并显示错误发生的时间$time,有助于工程师分析状态机的运行情况,排查错误原因,确保状态机的设计符合预期的功能和时序要求。

4.3 断言中的时钟定义

4.3.1 时钟关联方式

在 SystemVerilog Assertion 中,准确关联时钟是确保断言正确验证设计时序的关键。有两种主要的方式将检查和时钟关联起来。

第一种方式是将时钟定义在序列中。例如:

 

sequence s1;

@(posedge clk) a ##2 b;

endsequence

在这个序列s1中,@(posedge clk)明确指定了时钟为clk的上升沿。这意味着序列s1中的信号a和b的检查是在clk的上升沿进行的。当clk的上升沿到来时,首先检查信号a的状态,然后在两个时钟周期后的clk上升沿检查信号b的状态。这种方式适用于特定序列对时钟有明确依赖的情况,能够精确控制序列中信号检查的时间点。

第二种方式是在属性中指定时钟,并保持序列独立于时钟。例如:

 

sequence s2;

a ##2 b;

endsequence

property p1;

@(posedge clk) s2;

endproperty

这里,序列s2没有直接定义时钟,而属性p1通过@(posedge clk)指定了时钟。在这种方式下,序列s2可以被多个不同时钟的属性调用,提高了序列的可重用性。当属性p1被断言实例化时,会在clk的上升沿对序列s2进行检查。这种方式更灵活,尤其适用于多个属性共享同一序列,但需要在不同时钟域下进行检查的场景。

4.3.2 推荐方法及优势

通常推荐在属性中指定时钟,并保持序列独立于时钟,这种方法具有以下优势:

  • 提高序列的可重用性:独立于时钟的序列可以被不同时钟域的属性调用,减少了重复定义序列的工作量。例如,在一个复杂的设计中,可能存在多个模块,每个模块有不同的时钟,但某些基本的信号序列关系是相同的。通过定义独立于时钟的序列,可以在不同模块的属性中复用这些序列,提高代码的简洁性和可维护性。
  • 增强断言的灵活性:在属性中指定时钟,使得断言可以根据不同的验证需求灵活地选择时钟。例如,在验证一个具有多种工作模式的设计时,不同工作模式可能使用不同的时钟。通过在属性中指定时钟,可以方便地针对不同工作模式下的设计行为进行断言验证,而不需要修改序列的定义。

在使用这种方法时,需要注意以下几点:

  • 确保时钟的一致性:在属性中指定时钟后,要确保该时钟与设计中相关信号的实际时钟一致,否则可能导致断言验证结果错误。例如,如果设计中的信号a和b是在clk1时钟域下工作,而在属性中错误地指定了clk2时钟,那么断言的验证结果将无法准确反映设计的实际行为。
  • 避免时钟冲突:当在一个设计中使用多个属性和序列时,要避免不同属性中指定的时钟之间发生冲突。例如,在同一模块中,不应出现两个属性分别指定了相互冲突的时钟来验证同一组信号,这可能导致仿真或形式验证工具无法正确处理,产生错误的验证结果。

4.4 常用操作符与系统函数

4.4.1 操作符

在 SystemVerilog Assertion 中,常用的操作符用于构建布尔表达式、序列和属性,以描述设计的行为和逻辑关系。以下是一些常用操作符及其示例:

  • 逻辑操作符:&&(逻辑与)、||(逻辑或)、!(逻辑非)。例如:
 

assert property(@(posedge clk) (a && b) || (!c));

这个断言检查在时钟clk的上升沿,a和b同时为真,或者c为假。

  • 延时操作符:##n表示在n个时钟周期后,##[min:max]表示在min到max个时钟周期的范围内延迟。例如:
 

sequence s1;

@(posedge clk) a ##2 b; // a为真两个时钟周期后b为真

endsequence

sequence s2;

@(posedge clk) a ##[1:3] b; // a为真后的1到3个时钟周期内b为真

endsequence

  • 重复操作符:[*n]表示连续重复n次,[*m:n]表示连续重复m到n次,[=n]表示非连续重复n次。例如:
 

sequence s3;

@(posedge clk) a[*3]; // a连续为真3个时钟周期

endsequence

sequence s4;

@(posedge clk) a[*1:5]; // a连续为真1到5个时钟周期

endsequence

sequence s5;

@(posedge clk) a[=3]; // a在某个时间段内非连续地为真3次

endsequence

  • 序列操作符:and(逻辑与)、or(逻辑或)、intersect(交集)。and和or操作符用于组合序列,intersect用于描述两个序列匹配且长度一样,结束时间必须一致。例如:
 

sequence s6;

@(posedge clk) a ##1 b;

endsequence

sequence s7;

@(posedge clk) c ##1 d;

endsequence

property p1;

s6 and s7; // s6和s7序列同时匹配,起点相同,结束时间由长序列决定

endproperty

property p2;

s6 or s7; // s6或s7序列只要有一个匹配即可

endproperty

property p3;

s6 intersect s7; // s6和s7序列匹配且长度一样,结束时间一致

endproperty

4.4.2 系统函数

SystemVerilog Assertion 提供了丰富的系统函数,用于检测信号的变化、获取信号的历史值等。以下是一些常用系统函数的功能和用法:

  • 边沿检测函数:$rose(signal)检测信号signal的上升沿,$fell(signal)检测信号的下降沿。例如:
 

assert property(@(posedge clk) $rose(a) |-> b);

这个断言表示当在时钟clk的上升沿检测到a的上升沿时,信号b应该为真。

  • 电平检测函数:$stable(signal)检测信号signal是否保持稳定,$changed(signal)检测信号是否发生变化。例如:
 

assert property(@(posedge clk) $stable(a) |-> c);

五、实际应用案例分析

5.1 状态机验证

5.1.1 状态机描述

我们以一个简单的交通信号灯状态机为例,该状态机用于控制一个十字路口的交通信号灯。它包含四个状态:红灯(RED)、黄灯(YELLOW)、绿灯(GREEN)和绿灯闪烁(GREEN_FLASH)。状态机的输入信号为clk(时钟信号)、rst(复位信号)和switch(切换信号),输出信号为light(信号灯状态)。

状态转移条件如下:

  • 初始状态为红灯(RED),当rst信号有效时,状态机复位到红灯状态。
  • 在红灯状态下,当switch信号有效时,状态转移到绿灯(GREEN)状态。
  • 在绿灯状态下,经过一定时间(假设为n个时钟周期),状态转移到绿灯闪烁(GREEN_FLASH)状态。
  • 在绿灯闪烁状态下,闪烁m次后,状态转移到黄灯(YELLOW)状态。
  • 在黄灯状态下,经过一定时间(假设为k个时钟周期),状态转移回红灯(RED)状态。
5.1.2 SVA 验证实现

使用 SystemVerilog Assertion(SVA)对上述状态机进行验证,主要验证状态转移的正确性。以下是验证代码实现:

 

module traffic_light_fsm (

input clk,

input rst,

input switch,

output reg [2:0] light

);

typedef enum reg [2:0] {

RED = 3'b001,

YELLOW = 3'b010,

GREEN = 3'b100,

GREEN_FLASH = 3'b101

} state_t;

state_t current_state, next_state;

// 状态转移逻辑

always @(posedge clk or posedge rst) begin

if (rst)

current_state <= RED;

else

current_state <= next_state;

end

always @(*) begin

next_state = current_state;

case (current_state)

RED: begin

if (switch)

next_state = GREEN;

end

GREEN: begin

// 假设绿灯持续n个时钟周期后转移到绿灯闪烁状态

// 这里省略具体的计数逻辑,假设存在一个标志信号green_timeout

if (green_timeout)

next_state = GREEN_FLASH;

end

GREEN_FLASH: begin

// 假设绿灯闪烁m次后转移到黄灯状态

// 这里省略具体的闪烁计数逻辑,假设存在一个标志信号flash_done

if (flash_done)

next_state = YELLOW;

end

YELLOW: begin

// 假设黄灯持续k个时钟周期后转移回红灯状态

// 这里省略具体的计数逻辑,假设存在一个标志信号yellow_timeout

if (yellow_timeout)

next_state = RED;

end

default: begin

next_state = RED;

end

endcase

end

// 输出信号灯状态

always @(*) begin

light = current_state;

end

// SVA验证

// 验证从红灯到绿灯的状态转移

assert property (@(posedge clk) disable iff (rst)

$rose(switch) && (current_state == RED) |-> next_state == GREEN);

// 验证从绿灯到绿灯闪烁的状态转移

assert property (@(posedge clk) disable iff (rst)

green_timeout && (current_state == GREEN) |-> next_state == GREEN_FLASH);

// 验证从绿灯闪烁到黄灯的状态转移

assert property (@(posedge clk) disable iff (rst)

flash_done && (current_state == GREEN_FLASH) |-> next_state == YELLOW);

// 验证从黄灯到红灯的状态转移

assert property (@(posedge clk) disable iff (rst)

yellow_timeout && (current_state == YELLOW) |-> next_state == RED);

endmodule

在上述代码中,使用assert property语句定义了四个断言,分别验证了从红灯到绿灯、从绿灯到绿灯闪烁、从绿灯闪烁到黄灯以及从黄灯到红灯的状态转移。@(posedge clk)指定了时钟上升沿,disable iff (rst)表示当复位信号rst有效时,断言失效。|->操作符表示如果左边的条件满足,则检查右边的条件。

5.1.3 验证结果分析

在仿真过程中,如果状态机的状态转移符合设计规范,即满足所有断言条件,那么断言不会触发任何错误信息。例如,当switch信号有效且当前状态为红灯时,状态机正确地转移到绿灯状态,对应的断言通过。

然而,如果状态机出现错误的状态转移,例如在红灯状态下,switch信号有效,但状态机没有转移到绿灯状态,而是转移到了其他状态,那么对应的断言就会失败,并输出错误信息。通过分析错误信息,可以快速定位到状态机中状态转移逻辑的问题。例如,可能是状态转移条件判断错误,或者是状态机的状态编码出现问题。这种通过断言来验证状态机的方式,大大提高了验证的效率和准确性,能够及时发现并解决状态机设计中的潜在问题,确保状态机的正确性和可靠性。

5.2 握手信号验证

5.2.1 握手过程描述

以常见的req-ack握手协议为例,其握手过程如下:

  • 当发送方有数据需要发送时,将req信号置为高电平,表示请求发送数据。此时接收方的ack信号为低电平。
  • 接收方检测到req信号为高电平后,经过一定的处理时间,将ack信号置为高电平,表示已准备好接收数据。
  • 发送方检测到ack信号为高电平后,将req信号置为低电平,表示数据已发送。
  • 接收方检测到req信号为低电平后,将ack信号置为低电平,表示数据接收完成,完成一次握手过程。
5.2.2 SVA 验证代码

使用 SVA 对req-ack握手信号进行验证,代码如下:

 

module req_ack_handshake (

input clk,

input rst,

output reg req,

input ack

);

// SVA验证

// 验证req上升沿到ack上升沿的时序

assert property (@(posedge clk) disable iff (rst)

$rose(req) |-> ##[1:$] $rose(ack));

// 验证ack上升沿到req下降沿的时序

assert property (@(posedge clk) disable iff (rst)

$rose(ack) |-> ##[1:$] $fell(req));

// 验证req下降沿到ack下降沿的时序

assert property (@(posedge clk) disable iff (rst)

$fell(req) |-> ##[1:$] $fell(ack));

// 简单的激励生成

initial begin

req = 0;

#100;

req = 1;

wait (ack);

req = 0;

wait (!ack);

#50;

$finish;

end

endmodule

在上述代码中,定义了三个断言:

  • 第一个断言验证req信号上升沿后,在接下来的 1 个时钟周期到无穷个时钟周期内,ack信号会出现上升沿。
  • 第二个断言验证ack信号上升沿后,在接下来的 1 个时钟周期到无穷个时钟周期内,req信号会出现下降沿。
  • 第三个断言验证req信号下降沿后,在接下来的 1 个时钟周期到无穷个时钟周期内,ack信号会出现下降沿。
5.2.3 激励修改与结果对比

假设对激励进行如下修改:在req信号拉高后,等待一段时间后直接将req信号拉低,而不等待ack信号拉高。修改后的激励代码如下:

 

initial begin

req = 0;

#100;

req = 1;

#50;

req = 0;

wait (!ack);

#50;

$finish;

end

此时,第一个断言$rose(req) |-> ##[1:$] $rose(ack)会失败,因为req信号拉高后,没有等待ack信号拉高就将req信号拉低,不符合握手协议的时序要求。断言失败会输出错误信息,提示在req信号上升沿后,预期的ack信号上升沿未出现。

通过对比修改激励前后的断言结果,可以清晰地看到断言能够准确地检测出握手信号时序的错误。这表明 SVA 在验证握手信号时具有很高的准确性和可靠性,能够及时发现设计中不符合握手协议的情况,帮助工程师快速定位和解决问题,确保设计的正确性和稳定性。

六、结论与展望

6.1 研究总结

SystemVerilog Assertion(SVA)作为硬件设计验证领域的重要技术,为提高验证效率和准确性提供了有力支持。本研究深入探讨了 SVA 的相关内容,包括其定义、断言类型、断言层次结构、使用方法以及在实际应用中的案例分析。

SVA 是 SystemVerilog 硬件描述语言的扩展,通过断言来描述和验证硬件设计的属性和行为。它包括立即断言和并发断言两种类型,立即断言基于仿真事件语义执行,常用于过程块中对信号值的即时检查;并发断言基于时钟周期,能在整个仿真过程中连续监测信号,适用于验证时序逻辑和状态机等。

断言层次结构从布尔表达式开始,它是构建断言的基础,用于描述信号间的简单逻辑关系。序列则用于表示一段时间内的事件序列,可带参数、调用局部变量等,增强了断言对复杂事件的描述能力。属性是构成断言最常用的模块,它能调用序列,并使用蕴含操作符表达条件和结果的逻辑关系,使断言能够准确描述设计的行为和性质。

在使用方法上,建立断言需要经过确定验证目标、构建布尔表达式、定义序列、定义属性和断言实例化等步骤。在断言中,准确的时钟定义至关重要,推荐在属性中指定时钟以提高序列的可重用性和断言的灵活性。同时,SVA 还提供了丰富的操作符和系统函数,如逻辑操作符、延时操作符、边沿检测函数等,用于构建复杂的断言逻辑。

通过状态机验证和握手信号验证的实际案例分析,展示了 SVA 在硬件设计验证中的有效性。在状态机验证中,SVA 能够准确验证状态转移的正确性,及时发现状态机设计中的问题;在握手信号验证中,SVA 可以严格检查握手信号的时序关系,确保通信协议的正确执行。

6.2 未来发展趋势

随着硬件设计复杂度的不断增加以及对验证质量要求的日益提高,SVA 在未来有望呈现以下发展趋势:

在与新兴技术融合方面,随着人工智能和机器学习技术的快速发展,SVA 可能会与这些技术相结合。例如,利用机器学习算法自动生成断言,根据设计的历史数据和运行情况智能地调整断言策略,提高断言的覆盖率和有效性。同时,在物联网、5G 通信等新兴领域,硬件设计面临着更高的性能和可靠性要求,SVA 将在这些领域的硬件验证中发挥关键作用,推动相关技术的发展和应用。

在工具支持方面,未来的仿真和形式验证工具将对 SVA 提供更强大的支持。工具将能够更高效地处理复杂的断言逻辑,缩短验证时间,提高验证效率。同时,工具可能会提供更直观、易用的图形化界面,方便工程师创建、调试和管理断言,降低使用门槛,使 SVA 能够被更广泛的工程师所掌握和应用。

从行业应用来看,SVA 将在更多的行业领域得到应用和推广。除了传统的半导体和集成电路行业,在汽车电子、航空航天等对硬件可靠性要求极高的行业,SVA 的形式化验证能力和错误捕捉功能将具有巨大的应用价值,有助于提高这些行业的硬件设计质量和安全性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值