Verilog 组合逻辑与时序逻辑划分实践指南

二牛学FPGA
文章2026-04-25
67

Quick Start

  • 打开 Vivado(或 Quartus),创建新工程,选择目标器件(如 XC7A35T)。
  • 新建一个 Verilog 源文件,命名为 top.v
  • 编写一个简单的计数器模块:包含时钟输入 clk、复位 rst_n 和输出 cnt[3:0]
  • always @(posedge clk or negedge rst_n) 块中实现时序逻辑:if (!rst_n) cnt <= 4'd0; else cnt <= cnt + 1'b1;

前置条件

  • 已安装 Vivado 2020.1 及以上版本(或 Quartus Prime 18.0 及以上版本)。
  • 具备 Verilog 基础语法知识,熟悉 always 块与赋值语句。
  • 了解 FPGA 基本架构(查找表、触发器、布线资源)。

目标与验收标准

  • 掌握组合逻辑与时序逻辑的划分原则,能独立判断何时使用 assignalways @(*)always @(posedge clk)
  • 完成一个可综合的计数器设计,并通过仿真验证其功能正确性。
  • 验收标准:仿真波形中 cnt 在每个时钟上升沿递增,复位时清零,无毛刺或竞争风险。

实施步骤

步骤 1:理解组合逻辑与时序逻辑的本质区别

组合逻辑的输出仅取决于当前输入,无记忆特性,典型实现方式为 assign 连续赋值或 always @(*) 过程赋值(使用阻塞赋值 =)。时序逻辑依赖时钟边沿触发,具有状态保持能力,必须使用 always @(posedge clk)always @(negedge clk) 块,且内部赋值采用非阻塞赋值 <=

原因与机制分析:组合逻辑在硬件上映射为查找表(LUT)和门电路,信号传播延迟由路径决定;时序逻辑则通过触发器(FF)在时钟边沿采样并锁存数据,避免了组合逻辑中常见的毛刺与竞争问题。正确划分二者是保证设计时序收敛与功能稳定的基础。

步骤 2:设计计数器模块

创建一个新的 Verilog 源文件 top.v,编写如下代码:

module top (
    input  wire       clk,
    input  wire       rst_n,
    output reg  [3:0] cnt
);

// 时序逻辑:计数器递增
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        cnt <= 4'd0;
    else
        cnt <= cnt + 1'b1;
end

endmodule

落地路径:上述代码中,always 块的敏感列表包含时钟上升沿和异步复位下降沿,确保复位立即生效,计数行为仅在时钟边沿更新。这是典型的时序逻辑写法,非阻塞赋值 <= 保证了多个赋值在同一时钟周期内正确同步。

步骤 3:添加组合逻辑示例(可选)

为了对比,可以在同一模块中添加一个组合逻辑输出,例如 even_flag 指示计数器是否为偶数:

wire even_flag;
assign even_flag = (cnt[0] == 1'b0);

这里 assign 语句直接根据当前 cnt 的最低比特位计算输出,不依赖时钟,属于纯组合逻辑。

步骤 4:编写测试激励并仿真

新建一个仿真文件 tb_top.v

module tb_top;
    reg  clk;
    reg  rst_n;
    wire [3:0] cnt;

    top uut (
        .clk   (clk),
        .rst_n (rst_n),
        .cnt   (cnt)
    );

    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10ns 时钟周期
    end

    initial begin
        rst_n = 0;
        #20 rst_n = 1;
        #200 $finish;
    end

    initial begin
        $monitor("Time=%0t, cnt=%d", $time, cnt);
    end
endmodule

在 Vivado 或 ModelSim 中运行仿真,观察波形。确认复位期间 cnt 保持为 0,释放后每个时钟上升沿递增 1。

验证结果

仿真通过后,应得到如下波形特征:

  • 复位信号 rst_n 为低电平时,cnt 立即清零。
  • 复位释放后,cntclk 的每个上升沿加 1。
  • even_flag(如果添加)在 cnt 为偶数时输出高电平,无延迟。

若出现 cnt 变化与时钟不同步、或复位后未清零,请检查敏感列表与赋值类型。

排障指南

  • 问题:仿真中 cnt 不递增 —— 可能原因:时钟未翻转或复位未释放。检查 clk 生成逻辑与 rst_n 时序。
  • 问题:综合后出现锁存器(latch)警告 —— 通常是因为组合逻辑块中未覆盖所有分支。确保 always @(*) 中每个条件都有赋值,或使用 default 语句。
  • 问题:时序违例 —— 检查时钟频率是否过高,或组合逻辑路径过长。可插入流水线寄存器拆分关键路径。

扩展:复杂场景下的划分策略

在实际工程中,组合逻辑与时序逻辑的边界往往模糊。例如,状态机中的下一状态计算属于组合逻辑,而状态寄存属于时序逻辑。推荐采用“三段式”状态机写法,将组合逻辑(次态与输出)与时序逻辑(现态更新)分离,提高可读性与综合质量。

风险边界:切勿在同一个 always 块中混用阻塞与非阻塞赋值,或将组合逻辑写入时钟敏感列表中,这会导致仿真与综合结果不一致。始终遵循“组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值”的黄金法则。

参考

  • IEEE Std 1364-2001 Verilog HDL 标准
  • Vivado Design Suite User Guide: Synthesis (UG901)
  • Clifford E. Cummings, “Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!”

附录:完整代码清单

// top.v
module top (
    input  wire       clk,
    input  wire       rst_n,
    output reg  [3:0] cnt,
    output wire       even_flag
);

// 时序逻辑:计数器
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        cnt <= 4'd0;
    else
        cnt <= cnt + 1'b1;
end

// 组合逻辑:偶数指示
assign even_flag = (cnt[0] == 1'b0);

endmodule
分类
技术分享
标签
Verilog时序逻辑组合逻辑
浏览 67
分享:

相关推荐

同频道 · 相近分类

暂无相关推荐

作者

二牛学FPGA查看主页

同分类阅读

文章

延伸阅读与实操

  • 文章 + 课程联动深度文章常对应体系课章节,可一键选课。
  • 学习产出可参考笔记与作业案例在学习产出广场持续更新。

探索全站