00328 Verilog 组合逻辑和时序逻辑


前言

本文介绍了 Verilog 组合逻辑和时序逻辑。

操作系统:Windows 11 家庭中文版

信号

时钟信号(Clock Signal)

时钟信号的名称

  • 在Verilog设计中,时钟信号一般用作电路的时序驱动,用于触发寄存器、状态机等时序逻辑模块。

  • 时钟信号通常在Verilog代码中以clkclock等名字出现。例如:

    input clk;     // clk就是时钟信号的名称

时钟信号的频率

  • 时钟信号是一个周期性的方波信号,频率决定了电路中数据变化或寄存器更新的速度。

  • 在设计文档或代码注释中,需要说明时钟信号的频率,比如“时钟频率为50MHz”。

  • 代码层面通常不会直接描述频率,但在仿真(testbench)中会用#延时来模拟,例如:

    // 50MHz时钟周期为20ns
    always #10 clk = ~clk; // 每10ns翻转一次,周期为20ns,频率为50MHz

重置信号(Reset Signal)

重置信号的名称

  • 重置信号用于初始化清零电路状态。

  • 常见的名字有rstresetrst_nreset_n等。例如:

    input rst_n;   // rst_n为重置信号的名称

重置类型

  • 重置信号的类型通常有两种:

    • 同步复位(Synchronous Reset):在时钟沿到来时,判断重置信号是否有效。如果有效,再做初始化操作。
    • 异步复位(Asynchronous Reset):不依赖时钟信号,只要复位信号有效,立即初始化。
  • 另外,重置信号还分为高有效低有效两种:

    • 高有效(Active High):重置信号为1时有效(如reset)。
    • 低有效(Active Low):重置信号为0时有效(如reset_nrst_n,末尾的_n表示低有效)。
  • 代码示例:

    同步低有效复位:

    always @(posedge clk) begin
      if (!rst_n) // 低有效
        q <= 0;
      else
        q <= d;
    end

    异步高有效复位:

    always @(posedge clk or posedge reset) begin
      if (reset) // 高有效
        q <= 0;
      else
        q <= d;
    end

组合逻辑和时序逻辑

组合逻辑(Combinational Logic)

概念

  • 组合逻辑是一种没有记忆能力的电路逻辑。
  • 输出仅依赖于当前输入,输入信号变化马上影响输出。
  • 没有时钟信号的参与。

典型例子

  • 与门(AND)、或门(OR)、非门(NOT)
  • 加法器、比较器、多路选择器等

Verilog描述方式

  • 使用assign语句,或者在always @(*)块中描述。
  • 没有触发器(如D触发器)。

组合逻辑Verilog代码示例

// 2输入与门
assign y = a & b;

// 4选1多路选择器
always @(*) begin
    case (sel)
        2'b00: y = a;
        2'b01: y = b;
        2'b10: y = c;
        2'b11: y = d;
    endcase
end

时序逻辑(Sequential Logic)

概念

  • 时序逻辑是一种有记忆能力的电路逻辑。
  • 输出不仅依赖当前输入,还依赖于以往的输入(历史状态)
  • 需要时钟信号重置信号参与。

典型例子

  • 寄存器、触发器、计数器、状态机等

Verilog描述方式

  • 使用always @(posedge clk)always @(negedge clk)等时钟沿敏感的块。
  • 通常用于描述同步逻辑(同步时钟驱动),并可加入复位逻辑。

时序逻辑Verilog代码示例

// D触发器,有同步复位
always @(posedge clk) begin
    if (rst)         // rst为高有效复位信号
        q <= 0;
    else
        q <= d;
end

// 简单的二进制计数器
always @(posedge clk or posedge rst) begin
    if (rst)
        count <= 0;
    else
        count <= count + 1;
end

区别与联系

项目 组合逻辑 时序逻辑
是否有记忆
输出依赖 仅当前输入 当前输入+历史状态
时钟参与
例子 与非门、多路选择器 寄存器、计数器、状态机
Verilog描述 assign、always @(*) always @(posedge/negedge clk)

联系

  • 时序逻辑的实现通常依赖组合逻辑和触发器的共同作用
    • 例如,状态机的状态转移部分是组合逻辑,状态寄存则是时序逻辑。

总结

  • 组合逻辑:“现在的输入决定现在的输出”,没有记忆能力。
  • 时序逻辑:“现在的输入+过去的状态决定现在的输出”,有记忆能力,需要时钟或复位信号。

D触发器

下面详细介绍一下D触发器(D Flip-Flop),包括它的原理、功能、符号、应用以及在Verilog中的实现方法。


基本概念

D触发器是一种最基本的时序逻辑存储单元,用于存储1位二进制信息。

“D”代表“Data”或“Delay”,也叫做数据触发器延迟触发器


工作原理

  • 输入端:有D(数据输入)、CLK(时钟输入)。
  • 输出端Q(输出),有时还有(Q的反相)。
  • 功能:在时钟信号的特定沿(通常为上升沿posedge或下降沿negedge)到来时,将D端的数值锁存到Q输出端,并保持直到下一个时钟沿到来。
  • 记忆能力:D触发器只在时钟的有效沿采样并保存输入,其余时间输出不变。

真值表

时钟 D Q(下一个时刻)
0 0
1 1
其他 X Q(保持原值)

(↑ 表示时钟上升沿,X表示无关)


逻辑符号(图示)

      +-----+
 D ---|     |--- Q
      | DFF |
CLK --|     |
      +-----+

典型应用

  • 作为寄存器的基本单元,用来存储数据
  • 构建计数器移位寄存器
  • 实现同步状态机的状态寄存
  • 用于边沿检测数据同步

Verilog实现

最常见的同步D触发器代码:

// 同步复位的D触发器
always @(posedge clk) begin
    if (rst)         // rst为高有效同步复位
        q <= 0;
    else
        q <= d;
end

带异步复位的D触发器:

// 异步复位的D触发器
always @(posedge clk or posedge rst) begin
    if (rst)         // rst为高有效异步复位
        q <= 0;
    else
        q <= d;
end

只有时钟,无复位的D触发器:

always @(posedge clk) begin
    q <= d;
end
  • 其中,clk是时钟信号,rst是复位信号,d是输入数据,q是输出数据。

总结

  • D触发器是最基本的时序逻辑单元,工作在时钟同步下,用来存储1位数据。
  • 只在时钟有效沿采样输入,输出保持稳定,具有“记忆”能力。
  • 是寄存器、状态机等复杂时序电路的基础单元。

解释=和<=

下面详细解释一下在Verilog中=<=的区别,这也是Verilog初学者和实际设计中非常重要的知识点。


=<= 的基本区别

= :阻塞赋值(Blocking Assignment)

  • 英文名:Blocking Assignment

  • 使用场合:一般用于组合逻辑(如always @(*)块中),也可用于仿真中的临时变量赋值。

  • 执行方式顺序执行。等号左边的变量会立即被赋值,后续语句能看到这个改变,和C语言赋值类似。

  • 语法示例

    a = b + c;
    d = a + 1; // 这里a已经是新值了
  • 常见用法

    always @(*) begin
        y = a & b;
        z = y | c; // 此时z用到的y,已经是上一句赋的新值
    end

<= :非阻塞赋值(Non-blocking Assignment)

  • 英文名:Non-blocking Assignment

  • 使用场合:一般用于时序逻辑(如always @(posedge clk)块中)。

  • 执行方式并行执行。所有<=赋值的目标变量都会在本时钟周期的末尾同时更新,赋值语句不会阻塞后面的语句。

  • 语法示例

    always @(posedge clk) begin
        a <= b + c;
        d <= a + 1; // 此时a还是旧值,d用到的是a的旧值
    end
  • 常见用法

    always @(posedge clk) begin
        q <= d; // 时钟沿到来时,所有q的赋值同时生效
    end

实际设计中的区别

组合逻辑建议用=

  • 组合逻辑(如always @(*))建议使用=,因为赋值顺序很重要,且不需要时序更新。

时序逻辑建议用<=

  • 时序逻辑(如always @(posedge clk)必须使用<=,这样才能模拟真实硬件中所有寄存器在同一时钟沿“同时”更新。
  • =写时序逻辑可能会导致仿真与综合不一致,引发难以发现的bug。

举例说明

例1:阻塞赋值(=)在组合逻辑

always @(*) begin
    a = b;
    c = a;
end
  • 这里c会等于b,因为a已经被赋值。

例2:非阻塞赋值(<=)在时序逻辑

always @(posedge clk) begin
    a <= b;
    c <= a;
end
  • 这里c会等于前一个时钟周期的a(即a的旧值),因为所有赋值在同一时钟沿后一起生效。

总结口诀

  • 组合逻辑用=,时序逻辑用<=
  • 阻塞赋值顺序执行,非阻塞赋值“同时”执行。
  • 时序逻辑用=会出错,组合逻辑用<=容易误会。

补充说明

  • 在同一个always块中混用=<=会导致代码难以理解,甚至出现bug,建议不要混用。
  • 仿真时,=<=的行为差别也会体现在信号波形上。

汇总表格

赋值符号 名称 场景 行为 典型块
= 阻塞赋值 组合逻辑 顺序执行 always @(*)
<= 非阻塞赋值 时序逻辑 并行(同时)执行 always @(posedge clk)

组合逻辑和时序逻辑谁是顺序执行?


组合逻辑和时序逻辑的执行方式

组合逻辑

  • 本质:没有记忆功能,只有输入输出映射。
  • 执行方式并行(或同时)执行。组合逻辑的输出在输入发生变化后马上同步变化,没有“先后顺序”。
  • Verilog实现:常用assignalways @(*),通常用阻塞赋值(=)
    • 例:assign y = a & b;
  • 在硬件中,组合逻辑电路的所有部分实际上是并行工作的。

时序逻辑

  • 本质:有记忆功能,依赖时钟(clk)沿来驱动。
  • 执行方式按时钟节拍顺序执行,也就是“时序执行”。
    • 只有在时钟的上升沿或下降沿时,电路的状态才会更新。
  • Verilog实现:用always @(posedge clk)/always @(negedge clk),用非阻塞赋值(<=)
    • 例:always @(posedge clk) q <= d;
  • 在硬件中,时序逻辑只有在时钟沿到来时才“执行一次”,更新寄存器/触发器的内容。

顺序执行和时序执行

  • 如果你理解“顺序执行”为依次、按顺序执行,在Verilog中主要指阻塞赋值(=)的语句顺序执行,这通常出现在组合逻辑的描述中。
  • 如果你理解“时序执行”为依赖时钟的节拍来同步执行,那就是时序逻辑的特征。

结论

  • 组合逻辑:在硬件上是并行执行,在Verilog代码块内部可以用=实现语句顺序执行,但这是代码描述方式,不代表硬件行为有先后顺序。
  • 时序逻辑:是按时钟节拍(时序)执行,所有寄存器/触发器在同一个时钟沿“同时”更新数据。

常见混淆

  • 有些初学者容易把Verilog代码的语句“写的顺序”误认为硬件有先后,其实组合逻辑电路是并行的,所有输入一变输出就变,没有先后
  • 时序逻辑才会体现“时序性”,即每个时钟沿发生一次状态更新,这种“节拍”决定了时序逻辑的先后性。

总结

  • 如果问:“谁是按时序(时钟)执行的?”——时序逻辑
  • 如果问:“谁的代码是顺序执行的?”——组合逻辑常用阻塞赋值=,语句顺序执行,但硬件上还是并行逻辑。
  • 真正体现“时序、节拍”特征的是时序逻辑

代码的顺序

在Verilog中,哪些代码可以任意打乱撰写顺序(即代码的先后顺序不会影响最终硬件功能)?


组合逻辑代码的顺序

组合逻辑(通常用assignalways @(*)

核心点:
assign语句,以及always @(*)块中使用阻塞赋值(=)描述的组合逻辑,代码的先后顺序在硬件实现时是可以打乱的,因为它们最终描述的都是电路的连线关系,所有组合逻辑是并行实现的。

示例1:assign 语句

assign y = a & b;
assign z = y | c;

你可以写成:

assign z = y | c;
assign y = a & b;

无论顺序如何,综合出来的电路都是一样的

示例2:always @(*) 块的组合逻辑

always @(*) begin
    y = a & b;
    z = y | c;
end

你可以写成:

always @(*) begin
    z = y | c;
    y = a & b;
end

对于功能来说,这样写是没问题的,但不推荐,因为如果z依赖y的赋值,顺序会影响仿真值(即y还没赋值时z就用它了),但不会影响最终综合后的硬件连线。


时序逻辑代码的顺序

时序逻辑(如always @(posedge clk)

always @(posedge clk)块中,所有用非阻塞赋值(<=)的语句,其顺序可以任意打乱不会影响硬件功能

示例

always @(posedge clk) begin
    a <= b;
    c <= d;
    e <= f;
end

你可以写成:

always @(posedge clk) begin
    e <= f;
    c <= d;
    a <= b;
end

结果完全一样,因为所有<=赋值都是在时钟沿来时同时更新的。


特殊说明

  • 组合逻辑always @(*)块内,如果你用阻塞赋值(=)变量有依赖关系时,顺序会影响仿真结果(但综合硬件最终是并行的,只要组合逻辑能穷举清楚)。
    • 比如:

      always @(*) begin
          a = b;
          c = a;
      end

      always @(*) begin
          c = a;
          a = b;
      end

      仿真值会不一样,但硬件综合后,只要赋值关系唯一,结果一样。

  • 时序逻辑always @(posedge clk)块内,非阻塞赋值(<=)语句可以随意打乱顺序,但阻塞赋值(=)不行,用阻塞赋值会导致仿真和综合都出错。

总结表格

代码类型 赋值方式 顺序能否任意打乱 推荐写法 说明
assign assign 可以 任意 纯组合逻辑,描述电路连接关系
always @(*) = 基本可以(无依赖) 有依赖时按依赖顺序 顺序影响仿真,不影响最终硬件
always @(posedge clk) <= 可以 任意 全部在同一时钟沿“同时”赋值

总结

  • assign语句always块中用<=的时序逻辑代码,其语句顺序可以任意打乱,不影响硬件功能和综合结果。
  • always @(*)中用=的组合逻辑代码如果变量有依赖,建议按逻辑顺序写,否则仿真结果会错,虽然综合硬件没问题。
  • 实际写代码时,养成规范化书写的习惯,便于阅读和维护。

结语

第三百二十八篇博文写完,开心!!!!

今天,也是充满希望的一天。


文章作者: LuYF-Lemon-love
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LuYF-Lemon-love !
  目录