如何避免组合逻辑程序中的意外锁存

描述

数字门级电路可分为两大类:组合逻辑和时序逻辑。锁存器是组合逻辑和时序逻辑的一个交叉点,在后面会作为单独的主题处理。

组合逻辑描述了门级电路,其中逻辑块的输出直接反映到该块的输入值的组合,例如,双输入AND门的输出是两个输入的逻辑与。如果输入值发生变化,输出值将反映这一变化,组合逻辑的RTL模型需要反映这种门级行为,这意味着逻辑块的输出必须始终反映该逻辑块当前输入值的组合。

SystemVerilog有三种在可综合RTL级别表示组合逻辑的方法:连续赋值语句、always程序块和函数。接下来几篇文章将探讨每种编码风格,并推荐最佳实践编码风格。

编译器

always和always_comb程序

组合逻辑的主要RTL建模构造是always过程,使用通用always关键字或RTL专用的always_comb关键字。这些always程序可以利用之前讨论的强大的运算符编程语句,而连续赋值语句仅限于使用SystemVerilog运算符。一个简单的组合逻辑加法器被建模为always程序和always_comb程序的例子如下:

编译器

可综合组合逻辑的always程序

综合编译器支持always和always_comb程序。

当使用通用always程序时,综合编译器会施加一些编码限制,RTL设计工程师必须了解并遵守这些限制。这些限制包括:

程序敏感列表应包括每个信号,其值可能影响组合逻辑的输出。下一节详细讨论了敏感列表。

程序敏感列表必须对每个信号的所有可能值变化敏感。它不能包含限制对特定变化敏感性的posedge或negedge关键字。

程序应在零仿真时间内执行,并且不应包含任何形式的传播延迟(包括使用#,@或者wait等控制语句)。

在组合逻辑程序中赋值的变量不应被任何其他程序或连续赋值语句所赋值。(允许在同一程序中进行多次赋值。)

 

对所有RTL组合逻辑进行零延迟建模。
最佳实践指南7-3

 

综合器不允许@或wait等时间控制延迟,并将忽略#延迟。忽略#延迟可能会导致在仿真中验证的RTL模型与综合中忽略的门级实现不匹配。

使用通用always程序建模

 

使用RTL专用的always_comb程序对组合逻辑进行建模。不要在RTL模型中使用通用的always程序。
最佳实践指南7-4

 

RTL专用的always_comb会自动执行上面列出的编码限制。敏感列表是推断出来的,不允许@或wait时间控制,并且在always_comb程序中赋值的变量不能由其他程序或连续赋值。

虽然不推荐always程序用于RTL建模,但本文中讨论了如何正确使用通用always程序对组合逻辑进行建模,因为这种通用程序在传统的Verilog模型中很常见。

组合逻辑敏感列表。通用always程序需要一个敏感列表,以告知仿真器何时处理程序中的编程语句。敏感列表使用@(信号列表)形式指定,如下例所示:

编译器

敏感列表中的每个信号用逗号(,)分隔,如上例所示,或用or关键字分隔,如@(a or b or mode)。使用逗号(,)与或关键字or相比,没有任何优点或缺点。一些工程师更喜欢逗号(,)分隔的列表,因为or关键字可能会被误认为是逻辑or操作,而不仅仅是列表中信号之间的分隔符。

完整的敏感列表。对于组合逻辑,组合块的输出是该块输入的当前值的直接反映,为了对这种行为进行建模,当任何信号的值发生变化而影响程序输出的值时,always程序需要执行其编程语句。组合always程序的输入是程序中的语句读取值的任何信号,在上面的加法器示例中,程序的输入——程序中读取的信号为:a、b和mode。

程序输入与模块输入。组合逻辑程序的输入可能与包含该程序的模块的输入端口不一致。模块可能包含多个程序块和连续赋值语句,因此,每个程序块都有输入端口。模块也可能包含内部信号,在程序块或连续赋值语句之间传递数值。这些内部信号将不包括在模块端口列表中。

不完整的敏感列表-一个建模故障。

gotcha是一个编程术语,用于描述语法合法但性能不符合预期的代码。一般的always程序允许犯这种类型的编码错误。如果组合逻辑程序的一个或多个输入被无意中从敏感列表中忽略,RTL模型也将被编译,甚至可能看起来是正确的仿真。然而,完整的验证表明,组合逻辑块的输出在一定时间段内不反映当前输入值的组合。考虑下面的代码片段:

编译器

如果mode改变,result的输出将不会更新为新的操作结果,直到a或b改变值。在mode更改和a或b更改之间的时间内,result值不正确。

这种编码错误在只读取少数信号值的小型组合逻辑块中是很明显的,但对于更大、更复杂的逻辑块来说,读取10、20甚至几十个信号并不罕见。当涉及这么多信号时,很容易在不经意间忽略敏感列表中的一个信号。在设计开发过程中修改always块也很常见,比如在逻辑中添加另一个信号,但忘记将其添加到敏感列表中。这种编码错误的一个严重危害是,许多综合编译器仍将这种不正确的RTL模型实现为门级组合逻辑,可能带有一条容易忽略的警告消息,尽管综合编译器的实现可能是设计者的意图,但他并不是RTL仿真期间所验证的设计功能。因此,设计功能没有被充分验证,这可能会导致实际ASIC或FPGA运行时会出现错误。

过时的always@ 程序。IEEE 1364-2001标准(通常称为Verilog-2001)试图通过添加特殊标记来解决不完整敏感度列表的问题,该标记将自动推断出完整的敏感列表,例如:

编译器

也可以用括号括起来,如@( )。与在组合逻辑敏感列表中显式列出信号相比,@ 标记提供了更好的编码风格。然而,这个标记有两个问题。首先,综合编译器对组合逻辑建模施加了一些限制。使用@ 可以推断出一个敏感度列表,但不强制执行用于组合逻辑建模的其他综合规则。@ 的第二个问题是没有推断出完整的敏感度列表。如果一个组合逻辑程序调用一个函数,但没有将函数中使用的所有信号作为函数参数传入,则会推断出一个不完整的敏感列表。

 

使用SystemVerilog中专用的always_comb程序自动推断正确的组合逻辑敏感列表。不要使用过时的@*推断敏感列表。
最佳实践指南7-5

 

Always_comb程序将推断出准确的敏感列表,而不存在显式列表的危害,或者@ 的推断问题。always_comb过程也会强制执行综合编译器精确建模组合逻辑行为所需的编码限制。

在20世纪80年代推出的最初的Verilog语言只有通用的always程序。虽然非常有用,但当用于RTL建模时,该过程的通用性有严重的局限性。作为一个通用程序,always可用于仿真组合逻辑、时序逻辑、锁存逻辑和各种验证过程。当综合编译器遇到always过程时,编译器无法知道设计工程师打算对哪种类型的功能进行建模。相反,综合编译器必须分析过程的内容,并试图推断设计者的意图。综合很可能推断出不同于工程师预期的功能类型。

通用always程序的另一个限制是,它不强制执行综合编译器为表示组合逻辑行为所需的RTL编码规则。使用通用always程序的模型可能看起来仿真正确,但可能无法综合成预期的功能,因此在综合模型之前,必须重写RTL模型并在仿真中重新验证功能,从而导致工程时间损失。

使用RTL专用的always_comb程序建模

SystemVerilog引入了RTL专用的always程序,如always_comb,以解决通用always程序的局限性。下面的示例对前面显示的算术逻辑单元功能进行建模,但使用always_comb而不是always,

编译器

在编写RTL模型时,always_comb程序有很多好处:

自动推断出完整的敏感列表。该列表是完全完整的,避免了@*推断不完整敏感列表的极端情况。

不允许在always_comb过程中使用#、@或wait等延迟语句的执行,这是对使用零延迟程序的综合指南的强制。在always comb中使用这些时间控件是一个错误,在RTL模型的编译和布线过程中会发现这一错误。

在“always_comb”程序中赋值的任何变量都不能从另一个程序或连续赋值语句中赋值,这是综合编译器要求的限制。在RTL模型的编译和布线过程中,会发现违反此综合规则的编码错误。

Always_comb的语义规则符合综合编译器对组合逻辑RTL模型的编码限制。这些规则有助于确保因为验证无法综合的设计而浪费工程时间。

在仿真开始时自动评估。always_comb过程还有一个语义规则,是专门针对仿真使用。组合逻辑的行为是,输出值代表该逻辑块的输入值的组合。对于通用always程序,为了触发程序内赋值语句的执行,敏感列表中的信号必须发生值更改。如果敏感列表中的信号在仿真开始时均未改变值,则组合逻辑程序的输出不会更新,以匹配该过程的输入值。组合逻辑程序将继续具有不正确的输出值,直到敏感列表中的信号改变值。这个问题是一个RTL仿真故障,门级实现不会有这个问题。

RTL专用的always_comb程序解决了这个仿真故障。always_comb程序将在仿真开始时自动触发一次,以确保程序中分配的所有变量准确反映仿真时间零点时程序输入的值。

使用阻塞(组合逻辑)赋值

 

在为组合逻辑建模时,只使用阻塞赋值(=)。
最佳实践指南7-6

 

SystemVerilog有两种形式的赋值运算符:阻塞赋值(=)和非阻塞赋值(<=)。这些赋值类型影响仿真更新赋值语句左侧值的顺序,相对于仿真时那一刻的任何其他仿真活动。阻塞赋值(=)立即更新左侧的变量,使新值可供begin-end语句组中的后续语句使用。“即时更新”有效地仿真了组合逻辑数据流中的值传播行为。

下面的代码片段演示了通过组合逻辑程序块中的多个赋值的组合逻辑数据流。

编译器

在这个过程中,变量sum立即更新为a+b的运算结果。sum的这个新值流到下一个语句,在那里新值被用于计算prod的新值。prod的这个新值然后流到下一行代码,并用于计算result的值。

赋值语句的阻塞行为对于该数据流在零延迟RTL模型中正确仿真至关重要。每行代码中的阻塞赋值都会阻塞下一行的求值,直到当前行用新值更新其左侧变量,对后续每行求值代码的阻塞才能确保每一行使用前一行分配的新变量值。

如果在上面的代码段中不适当地使用了非阻塞赋值,在这些变量被更新为新值之前,则每个赋值都会使用其右侧变量的先前值 。显然这不是组合逻辑行为!然而,当使用非阻塞赋值时,综合编译器仍可能创建组合逻辑,导致在RTL仿真中验证的行为与综合后的实际门级行为不匹配。

避免组合逻辑程序中的意外锁存

RTL建模中的一个常见问题是推断代码中的锁存行为。SystemVerilog语言规则要求过程赋值的左侧必须是某种类型的变量,Net(网络)数据类型不允许出现在程序赋值的左侧。这种对使用变量的要求可能会导致无意的锁存,这是纯组合逻辑的目的。当触发非时钟always程序(即组合逻辑程序)且不对该程序使用的变量进行赋值时,就会发生锁存行为。最常见的两种情况是:

1.决策语句分配给每个分支中的不同变量,如下面的代码段所示,

编译器

2.决策语句不会对决策表达式的每个可能值执行分支。下面的代码片段说明了这个问题。

编译器

在仿真中,这个简单的例子似乎正确地仿真组合逻辑加法器、减法器和乘法器。但是,如果操作码输入的值应为2’b11,则本例不会对result变量进行任何赋值。因为result是一个变量,所以它会保留其以前的值,保留值的行为就像锁存器一样,尽管其目的是让always_comb程序表现为组合逻辑。

即使使用always_comb程序,也会推断出锁存器。然而,综合编译器和lint checker将报告一个警告或非致命错误,即在always_comb程序中推断出了锁存器。此警告是always_comb优于常规always程序的几个优点之一。always-comb程序记录了设计工程师的意图,当程序中的代码与该意图不一致时,软件工具可以报告这一不匹配意图。

审核编辑:郭婷

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分