3. 时序逻辑基础:always块、reg类型、时钟与复位、D触发器的Verilog描述

好,咱们今天聊点硬核的。时序逻辑,说白了就是带记忆的电路。组合逻辑只管当前输入,时序逻辑可不一样——它记得过去的状态。我刚开始学的时候,总觉得这玩意儿玄乎,后来才发现,其实就是几个核心概念串起来的事。

3.1 always块——时序逻辑的“心脏”

在Verilog里,描述时序逻辑全靠always块。组合逻辑也用always,但写法完全不同。时序逻辑的always块,敏感列表里通常只放时钟和复位。

我个人的习惯是:时钟沿触发,复位异步清零。这样写出来的代码,综合工具一眼就能认出来是寄存器。

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        q <= 1'b0;
    else
        q <= d;
end

你看,这个写法很经典。posedge clk表示时钟上升沿触发,negedge rst_n表示低电平复位。注意这里的rst_n,我习惯用_n后缀表示低有效,这是很多公司的编码规范。

小提示:敏感列表里不要写多余信号。我曾经见过有人把数据也写进敏感列表,结果综合出来的电路完全不是那么回事。记住:时序逻辑的敏感列表,只有时钟和复位。

3.2 reg类型——别被名字骗了

很多新手会犯一个错误:以为reg就是寄存器。其实不是。reg只是表示一个变量类型,它可以在always块里被赋值。组合逻辑里也可以用reg,只不过综合出来的是连线。

说白了:在时序逻辑里,reg综合成触发器;在组合逻辑里,reg综合成连线。关键看你怎么用。

我举个例子:

reg [3:0] counter;  // 这个reg,在时序逻辑里就是4位计数器

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        counter <= 4'b0;
    else
        counter <= counter + 1'b1;
end

这个counter,综合出来就是4个D触发器。为什么?因为它在时钟沿赋值,有记忆功能。

注意:在同一个always块里,不要对同一个reg变量既用阻塞赋值(=)又用非阻塞赋值(<=)。我曾经调试过一个bug,折腾了两天才发现是赋值方式混用了。时序逻辑统一用非阻塞赋值,这是铁律。

3.3 时钟与复位——设计的“脉搏”

时钟是时序逻辑的脉搏。没有时钟,触发器就不知道什么时候该干活。复位则是让电路回到初始状态。

我遇到过不少项目,复位设计没做好,导致上电后状态不确定。这里分享几个经验:

  • 时钟要干净:不要用组合逻辑产生时钟,除非你很清楚自己在做什么。门控时钟容易出毛刺。
  • 复位要可靠:异步复位虽然方便,但要注意复位释放时的时序。同步复位更安全,但需要额外的逻辑。
  • 时钟域要统一:一个模块尽量只用一个时钟。跨时钟域处理是另一个话题,但能避免就避免。

来看一个带同步复位的写法:

always @(posedge clk) begin
    if (!rst_n)
        q <= 1'b0;
    else
        q <= d;
end

这个写法,复位信号不在敏感列表里。它只在时钟沿判断复位条件。综合出来是带同步复位的D触发器。

核心要点:异步复位响应快,但释放时容易出问题。同步复位更稳定,但需要复位信号持续至少一个时钟周期。选哪种?看你的应用场景。我一般用异步复位、同步释放的方式,兼顾两者优点。

3.4 D触发器的Verilog描述——从理论到代码

D触发器是时序逻辑的基本单元。一个D触发器,就是一个1位寄存器。它的Verilog描述,其实就是我们刚才写的那个always块。

咱们来拆解一下:

信号 含义 说明
clk 时钟 上升沿或下降沿触发
rst_n 复位 低电平有效,异步清零
d 数据输入 时钟沿到来时采样
q 数据输出 保持到下一个时钟沿

一个完整的D触发器描述:

module dff (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       d,
    output reg        q
);

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        q <= 1'b0;
    else
        q <= d;
end

endmodule

你看,就这么几行代码。但背后对应的是实实在在的硬件电路。每个reg变量,在综合后都会变成一个或多个D触发器。

嗯,这里要注意:D触发器的输出变化,只发生在时钟沿。其他时间,输出保持不变。这就是“记忆”的本质。

3.5 知识体系梳理

为了让你更直观地理解本章的知识结构,我画了一张图:

时序逻辑基础知识体系 always块 reg类型 时钟与复位 D触发器 敏感列表:posedge/negedge 非阻塞赋值 <= 变量类型,非硬件 综合成触发器或连线 异步复位 vs 同步复位 时钟域统一原则 1位寄存器单元 时钟沿采样,保持输出 核心总结 时序逻辑 = always块 + reg类型 + 时钟/复位 + D触发器 每个reg变量在综合后对应一组D触发器

这张图把本章的核心知识点串起来了。你看,四个模块相互关联,缺一不可。always块是载体,reg类型是变量,时钟复位是控制,D触发器是最终实现。

我的经验:刚开始写时序逻辑时,先画个波形图。把时钟、复位、输入、输出的时序关系画清楚,再动手写代码。这样能避免很多低级错误。我早期有个项目,就是因为没画波形图,结果复位时序搞反了,板子调了三天。

好了,这一章的内容就这些。记住:时序逻辑的核心就是“在时钟沿干活,用复位定初态”。把这个原则刻在脑子里,写出来的代码就不会跑偏。


公众号:蓝海资料掘金营,微信deep3321