3. Verilog HDL设计输入与代码规范

在 Quartus Prime 工程中,Verilog HDL 源文件既是功能描述,也是综合器、仿真器和时序分析工具的共同输入。同一段代码能否被 Quartus 正确综合成目标器件中的逻辑资源,取决于它是否遵循可综合风格;而一个项目能否被多人长期维护,则取决于模块化设计与命名、注释规范。本节围绕这三条主线展开,并给出可直接套用的代码骨架与检查要点。

一、模块化设计:先划分接口,再填充实现

模块化设计的核心是“一个模块只做一件事,并通过清晰的端口与外界通信”。在 Quartus Prime 中,顶层模块通常只负责端口分配与子模块例化,具体功能下沉到功能模块中。划分模块时可参考以下原则:

  • 按功能边界划分:如时钟分频、按键消抖、数据通路、状态机、显示驱动各自独立成模块,避免一个模块同时处理时钟域切换与算法运算。
  • 按时钟域划分:跨时钟域处理(如异步 FIFO、双触发器同步器)应单独封装,便于后续做时序约束与 CDC 检查。
  • 端口方向与位宽明确:输入输出方向、位宽、是否有寄存器输出都在端口声明中体现,避免在实例化时靠猜测连接。
  • 参数化:位宽、分频系数、计数器上限等用 parameter 定义,使模块可复用。

下面是一个可综合的模块骨架示例,包含参数化、端口声明与例化方式:

// 模块:脉冲计数器,统计 din 上升沿个数
module pulse_counter #(
    parameter CNT_WIDTH = 16,          // 计数器位宽
    parameter SYNC_STAGES = 2          // 输入同步级数
)(
    input  wire                  clk,   // 系统时钟
    input  wire                  rst_n, // 低电平复位
    input  wire                  din,   // 异步输入脉冲
    output reg  [CNT_WIDTH-1:0]  cnt    // 计数值
);

    reg [SYNC_STAGES-1:0] din_sync;
    reg                   din_dly;

    // 输入同步,降低亚稳态风险
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            din_sync <= {SYNC_STAGES{1'b0}};
        else
            din_sync <= {din_sync[SYNC_STAGES-2:0], din};
    end

    // 边沿检测
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            din_dly <= 1'b0;
        else
            din_dly <= din_sync[SYNC_STAGES-1];
    end

    wire pulse = din_sync[SYNC_STAGES-1] & ~din_dly;

    // 计数
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            cnt <= {CNT_WIDTH{1'b0}};
        else if (pulse)
            cnt <= cnt + 1'b1;
    end

endmodule

顶层例化时使用命名端口连接,避免位置连接带来的错位风险:

pulse_counter #(
    .CNT_WIDTH  (16),
    .SYNC_STAGES(2)
) u_pulse_counter (
    .clk   (sys_clk),
    .rst_n (sys_rst_n),
    .din   (key_in),
    .cnt   (pulse_cnt)
);

二、可综合风格:写综合器能“看懂”的代码

可综合风格不是语法限制,而是对综合结果的可预测性要求。Quartus Prime 的 Analysis & Synthesis 会把 Verilog 映射为 LUT、寄存器、进位链、RAM 等资源,以下写法会直接影响映射结果:

  • 时序逻辑统一用非阻塞赋值:always @(posedge clk) 中使用 <=,组合逻辑使用 always @(*) 配合阻塞赋值 =,避免同一 always 块混用。
  • 避免不完整敏感列表:组合逻辑用 always @(*) 或 always_comb,防止仿真与综合不一致。
  • 避免锁存器:组合逻辑中所有分支都要赋值,或使用 default 兜底,否则综合出 latch。
  • 复位策略一致:同一模块内统一使用异步复位或同步复位,不要混用;复位极性在端口名中体现(如 rst_n)。
  • 不使用不可综合结构:如 initial 赋初值(除 FPGA 配置寄存器外)、#delay、while 无限循环、real 类型等。
  • 时钟只走时钟网络:不要用组合逻辑产生的信号直接当时钟,应改用时钟使能。
注意:在 Quartus Prime 中,若组合逻辑遗漏赋值导致 latch,编译报告会出现 “Warning: inferred latch” 提示。该警告不应被忽略,应回到代码补齐分支或改为时序逻辑。

三、命名与注释规范:让代码可读、可查、可维护

命名规范的目标是“看到名字就知道方向和含义”。推荐约定如下:

对象命名约定示例
模块名小写字母加下划线,功能_类型uart_tx、fifo_ctrl
低电平有效信号后缀 _nrst_n、cs_n
时钟信号前缀 clk_,注明频率或来源clk_50m、clk_uart
寄存器后缀 _r 或 _regdata_r、state_reg
组合信号后缀 _c 或直接语义名pulse_c、valid
参数全大写加下划线CNT_WIDTH、BAUD_RATE

注释应说明“为什么这样做”,而不是重复代码字面含义。推荐在每个模块头部写清功能、端口、时序与复位说明;在关键分支、状态机状态、跨时钟域处理处写行内注释。例如:

//============================================================
// 模块功能:将 50MHz 时钟分频为 1Hz 使能脉冲
// 端口说明:
//   clk_50m : 输入时钟,50MHz
//   rst_n   : 异步复位,低有效
//   en_1hz  : 输出使能,宽度为 1 个 clk_50m 周期
// 备注:采用计数器方式,不产生新时钟,避免时钟树问题
//============================================================
module clk_div_en #(
    parameter DIV_CNT = 25_000_000   // 50MHz / 1Hz / 2
)(
    input  wire clk_50m,
    input  wire rst_n,
    output reg  en_1hz
);
    reg [24:0] cnt;

    always @(posedge clk_50m or negedge rst_n) begin
        if (!rst_n) begin
            cnt    <= 25'd0;
            en_1hz <= 1'b0;
        end
        else if (cnt == DIV_CNT - 1) begin
            cnt    <= 25'd0;
            en_1hz <= 1'b1;      // 计数满,输出一个周期使能
        end
        else begin
            cnt    <= cnt + 1'b1;
            en_1hz <= 1'b0;
        end
    end
endmodule

四、从代码到 Quartus Prime 工程的可检查闭环

写完代码后,建议按以下顺序在 Quartus Prime 中验证:先执行 Analysis & Synthesis,查看 Messages 中是否有 latch、多驱动、位宽不匹配等警告;再打开 RTL Viewer,核对综合出的寄存器、加法器、比较器是否与设计意图一致;随后用 University Program VWF 或 ModelSim 做功能仿真,观察复位释放后 cnt 是否从 0 开始、en_1hz 是否每 25,000,000 个时钟周期出现一次;最后在 Pin Planner 中分配引脚并做时序约束。若 RTL Viewer 中出现了预期之外的锁存器或组合环路,应回到代码修正,而不是靠约束掩盖。

Verilog 设计输入与代码规范检查链 模块划分 → 可综合写法 → 命名注释 → Quartus 综合与 RTL 核对 模块化设计 顶层:端口分配 + 例化 功能模块:单一职责 参数化:位宽 / 分频系数 命名端口连接,避免位置错位 可综合风格 时序:非阻塞 <= 组合:always @(*) + = 分支完整,避免 latch 复位策略统一,时钟走专用网络 命名与注释 rst_n / clk_50m / data_r 参数全大写:CNT_WIDTH 模块头注释:功能/端口/复位 注释解释“为什么”,不重复代码 Quartus Prime 中的验证闭环 Analysis & Synthesis RTL Viewer 结构核对 功能仿真 VWF / ModelSim 引脚与时序 约束 若出现 inferred latch、多驱动或位宽不匹配,回到代码修正,而非用约束掩盖

把上述规范落到日常开发中,可以用一份简短的提交前检查表约束自己:模块是否单一职责、端口是否命名连接、时序逻辑是否全部使用非阻塞赋值、组合逻辑是否分支完整、复位极性是否在信号名中体现、参数是否大写、模块头是否写清功能与端口。坚持这套检查,Quartus Prime 的综合报告会明显更干净,RTL Viewer 中的结构与设计意图也会更容易对应。