首页 > 技术知识 > 正文

前言

上篇文章中也说了,可以在Verilog中建模的数字电路主要分为两类:组合电路和时序电路。本文就是另一篇,时序逻辑的建模。

时序建模使用的verilog中最重要的构造之一-always块。

与组合逻辑相反,时序电路使用时钟并需要诸如触发器之类的存储元件。

结果,输出信号与电路时钟同步,并且不会立即发生变化。

我们使用always块编写在verilog中时序逻辑的代码。在verilog中描述时序逻辑电路时,这一点至关重要。

Verilog中的Always Block

编写Verilog时,我们使用过程块(procedural )来创建按时序执行的语句。程序块对于时序数字电路的建模特别重要。

相反,在我们的设计中,verilog连续赋值语句可同时(即并行)执行。这与底层电路的性质相匹配,底层电路由许多独立的逻辑门组成。

Always块是Verilog中最常用的过程块之一。每当敏感列表中的信号之一更改状态时,always块中的所有语句都将执行。

下面的verilog代码显示了always块的常规语法。

always @(<sensitivity_list>) begin // Code to be executed goes here end

使用此构造时,我们需要小心,因为verilog有一些独特的功能。

尤其是,初学者通常很难理解always块中更新信号的方式。

当我们使用always块时,我们可以并行或顺序更新信号值。这取决于我们使用的是非阻塞分配还是非阻塞分配,我们将在本文后面更深入地讨论。

为了成为有效的Verilog设计师,重要的一点是,我们必须对Always Block有所了解。

让我们更详细地查看Always块的一些关键功能。

敏感度列表

我们在always块中编写的任何代码都将连续运行。这意味着代码块中的语句将按顺序执行,直到到达最后一行为止。

一旦执行了序列的最后一行,程序便循环回到第一行。然后将再次按顺序执行always块中的所有语句。

但是,此行为并不代表实际电路,该电路将保持稳定状态,直到输入信号之一改变状态为止。

我们使用always中的敏感度列表来模拟此行为。

为此,将总是在敏感度列表中的信号之一更改状态后执行Always块中的代码。

触发器示例

让我们考虑如何以Always块为例,为基本的D型触发器建模。

与所有带时钟的触发器一样,D型触发器的输出仅在存在正时钟沿时才改变状态。

结果,我们将时钟信号包括在灵敏度列表中,以便always块仅在时钟信号上有上升沿时才执行。

下面的Verilog代码显示了我们如何使用Always模块对D型触发器建模。

always @(posedge clock) begin q <= d; end

在此代码示例中,我们使用posege宏来确定何时发生从0到1的过渡。

当此宏的值为真时,将执行always块中的单行代码。这行代码将D的值赋值给输出信号(Q)。

当我们在Verilog中使用posege宏时,所有其他状态更改都将被忽略。这正是我们对D型触发器的期望。

Verilog还具有功能相反的negedge宏。当我们使用此宏时,只要时钟从1变为0,都会执行always块。

我们也可以完全省略此宏。在这种情况下,只要灵敏度列表中的信号改变状态,代码就会执行。

在Verilog设计中,我们仅应将posege宏用于时钟信号。这是因为综合工具将尝试利用FPGA内的时钟资源来实现它。

灵敏度列表中的多个信号

在某些情况下,我们希望在灵敏度列表中包括多个信号。

一个常见的例子是当我们编写代码来模拟具有异步复位的触发器的行为时。

在这种情况下,无论何时复位或时钟信号改变状态,我们都需要触发器模型来执行操作。

为此,我们只需在灵敏度列表中列出两个信号,然后用逗号(或or)将它们分开即可。

下面的代码片段显示了我们如何编写这样的触发器。

always @(posedge clock, posedge reset) begin if (reset) begin q <= 1b0; end else begin q <= d; end end

由于此示例使用了高电平有效的复位,因此我们再次使用灵敏度列表中的posege宏。

高电平有效复位意味着复位只有在等于1时才有效。

然后,我们使用称为if语句的结构来确定是始终由复位信号触发还是由时钟信号触发。

当使用与verilog 1995兼容的代码时,我们必须使用or关键字或逗号代替敏感度列表中的信号。

下面的代码段显示了如何使用verilog 1995为异步可复位触发器建模。

always @(posedge clock or posedge reset) begin if (reset) begin q <= 1b0; end else begin q <= d; end end

Verilog中的阻塞和非阻塞分配

always块中可以使用两种不同类型的赋值运算符。

这是因为verilog具有两种不同的赋值类型-阻塞和非阻塞。

当我们编写具有非阻塞分配的代码时,我们使用<=符号,而阻塞代码使用=符号。

当我们在verilog中使用连续赋值时,我们只能使用阻塞分配。

但是,我们可以在程序块中使用两种类型的赋值。

阻塞赋值通常导致在综合之后实现组合逻辑电路。相反,无阻塞分配通常会在综合后产生时序电路。

阻塞赋值是这两种技术中最简单的一种。当我们在Verilog中使用阻塞分配来分配信号时,一旦执行代码行,信号就会更新其值。

我们通常使用这种类型的赋值在verilog中编写组合逻辑。但是,在某些情况下,我们可以使用它来创建时序电路。

相反,使用非阻塞技术的信号赋值后不会立即更新。

要理解这一点有些棘手,所以让我们更深入地考虑一下。

当我们使用非阻塞赋值编写Verilog代码时,我们的代码仍会顺序执行。但是,我们分配的信号不会以这种方式更新。

为了说明为什么会发生这种情况,让我们考虑下面的计数器电路。

FPGA的设计艺术(22)Verilog中如何对时序逻辑进行建模?

always @(posedge clock) begin q_dff1 <= ~q_dff2; q_dff2 <= q_dff1; end

我们还可以查看诸如vivado之类的综合工具的输出,以查看生成的电路图。下面的电路图显示了该电路。

FPGA的设计艺术(22)Verilog中如何对时序逻辑进行建模?1

我们可以看到电路中有两个触发器,而非门是使用LUT1实现的。

现在让我们看一下如果在代码中使用阻塞分配将得到的电路。

下面的Verilog代码显示了我们如何(错误地)尝试使用阻塞分配对该电路进行建模。

always @(posedge clock) begin q_dff1 = ~q_dff2; q_dff2 = q_dff1; end

综合后,将产生以下所示的电路。 FPGA的设计艺术(22)Verilog中如何对时序逻辑进行建模?2

电路图显示了一个触发器,其输出通过查找表并返回输入。

从中我们可以看到,使用阻塞导致从电路中删除了第二个触发器。

考虑到到目前为止我们已经了解的有关阻塞赋值的知识,这样做的原因应该很明显。

由于q_dff2的值立即赋值给与q_dff1相同的值,因此电路模型并不意味着该信号路径中应该存在触发器。

此示例实际上向我们展示了Verilog中阻塞分配和非阻塞分配之间最重要的区别之一。

当我们使用非阻塞分配时,综合工具将始终在电路中放置一个触发器。这意味着我们只能使用非阻塞分配来建模时序逻辑。

相反,我们可以使用阻塞赋值来创建时序电路或组合电路。

但是,我们仅应使用阻塞分配来在Verilog中对组合逻辑电路进行建模。这样做的主要原因是我们的代码将更易于理解和维护。

always块中的组合逻辑

到目前为止,我们仅考虑了使用Always Block的时序电路建模。

尽管这是最常见的用例,但我们也可以使用这种方法对组合逻辑进行建模。

例如,下面的代码显示了我们如何使用Always模块来对AND-OR电路进行建模,这在后文中以verilog进行的连续赋值中进行了讨论。

// Using verilog 2001 style coding always @(a, b, c) begin logic_out = (a & b) | c; end // Using verilog 1995 style coding always @(a or b or c) begin logic_out = (a & b) | c; end

我们看到这段代码与我们在连续赋值中看到的示例几乎相同。

唯一的主要区别是它被封装在Always块中。我们也从语句中删除了verilog Assign关键字,因为我们不再需要它。

从这个例子中我们还可以看出,组合电路的灵敏度表比时序电路更复杂。

在对组合逻辑电路建模时,实际上可以使用两种方法来编写灵敏度列表。

我们可以使用的第一种方法是列出电路的每个输入,并用or或关键字或用逗号分隔。这是我们在上面的示例代码中使用的方法。

除此之外,我们还可以使用*字符告诉Verilog工具自动确定要包括在灵敏度列表中的信号。

该技术是优选的,因为它具有易于维护的优点。但是,此方法是作为verilog 2001标准的一部分引入的,这意味着它不能与verilog 1995代码一起使用。

下面的代码片段显示了我们将如何使用这两种方法。

// Sensitivity list with all signals listed always @ (a, b, c) // Sensitivity list using the special character always @ (*)

我们仅在某些情况下使用Always模块对组合逻辑电路进行建模,在这种情况下,它可以简化复杂组合逻辑的建模。

多路复用器

当我们想对一个多路复用器进行建模时,使用always块对组合逻辑进行建模可能会很有用。

在这种情况下,我们可以使用称为case语句的构造来对多路复用器建模。与我们在verilog中的建模组合逻辑的文章中讨论的方法相比,这为大型多路复用器的建模提供了一种更简单,更直观的方法。

下面的代码片段显示了我们如何使用case语句为简单的四对一多路复用器建模。

always @(*) case (addr) begin 0 : begin // This branch executes when addr = 0 mux_out = a; end 1 : begin // This branch executres when addr = 1 mux_out = b; end 2 : begin // This branch executes when addr = 2 mux_out = c; end 3 : begin // This branch executes when addr = 3 mux_out = d; end endcase end
<

case语句很容易理解,因为它使用变量来选择要执行的多个分支之一。

在case语句中,我们可以根据需要包含任意数量的不同分支。

此外,我们使用默认分支来捕获未明确列出的所有值。

为了将其用作多路复用器,我们将变量用作地址引脚。

然后,我们可以根据正在执行的分支将多路复用器的输出分配给所需的值。

总结

总结很简单, 我们必须说明,或者制定一套有利于理解且利于阅读的原则,我们是为了设计逻辑来实现功能,而不是扣无聊的语法,因此,我们有必要做到:

组合逻辑使用阻塞赋值 时序逻辑使用非阻塞赋值 仿真的时候可以根据功能需求而定

猜你喜欢