当前位置: 首页 > news >正文

Verilog 代码规范

本文是《FPGA入门到实战》专栏第18篇。前面学了大量语法和实战项目,本篇系统梳理企业 FPGA 工程中的代码规范标准。规范不是束缚,而是让代码在团队协作、版本维护、代码评审中高效运转的基础。对比学习规范写法和错误写法,将显著提升代码质量,减少 review 来回轮次。

Verilog 代码规范

  • 1. 信号与模块命名约定
    • 1.1 常用前缀规则
    • 1.2 命名规则
    • 1.3 时钟与复位命名
  • 2. 注释规范
    • 2.1 文件头注释
    • 2.2 段落注释
    • 2.3 行内注释
  • 3. 模块化与层次化设计原则
    • 3.1 单一职责原则
    • 3.2 接口设计规范
    • 3.3 顶层模块职责
  • 4. 可综合代码禁用写法清单
    • 4.1 禁止在可综合代码中使用 `initial`
    • 4.2 禁止使用 `#` 延时
    • 4.3 谨慎使用 `casex`
    • 4.4 禁止异步组合反馈环路
    • 4.5 禁止在 always 内对同一信号多处赋值
    • 4.6 禁用写法速查表
  • 5. 代码评审常见问题 Top 10
    • 问题1:敏感列表不完整
    • 问题2:组合 always 中缺少 default 导致 Latch
    • 问题3:宽度不匹配
    • 问题4:时钟边沿后立即采样(Setup violation 风险)
    • 问题5:parameter 被外部覆盖引发溢出
    • 问题6:复位未覆盖所有寄存器
    • 问题7:异步时钟域直接连线
    • 问题8:generate 块滥用
    • 问题9:顶层约束与端口不匹配
    • 问题10:仿真用的语法混入了综合代码
  • 附:规范代码模板
    • module_template.v

1. 信号与模块命名约定

1.1 常用前缀规则

企业级代码通常采用前缀体系,一眼看出信号的方向和类型:

前缀含义示例
i_模块输入(input)i_clki_rst_ni_data
o_模块输出(output)o_txo_valido_data
r_寄存器(reg,时序逻辑驱动)r_cntr_stater_shift
w_线网(wire,组合逻辑或连线)w_sumw_carryw_mux_sel
c_p_参数常量(localparam/parameter)p_CLK_FREQc_MAX_CNT
tb_Testbench 内部信号tb_clktb_data_in
// 规范写法示例 module uart_tx #( parameter p_CLK_FREQ = 100_000_000, parameter p_BAUD_RATE = 115200 )( input wire i_clk, input wire i_rst_n, input wire i_tx_start, input wire [7:0] i_tx_data, output reg o_tx, output reg o_tx_busy ); localparam c_BAUD_DIV = p_CLK_FREQ / p_BAUD_RATE - 1; reg [15:0] r_baud_cnt; wire w_baud_tick; assign w_baud_tick = (r_baud_cnt == c_BAUD_DIV); // ... endmodule

1.2 命名规则

规则说明示例
全小写加下划线(snake_case)信号名tx_busybaud_cnt
常量全大写parameter/localparamCLK_FREQMAX_CNT
模块名与文件名一致便于工具自动识别uart_tx.vmodule uart_tx
实例名用u_前缀区分模块和实例u_uart_txu_fifo
有意义的名称禁止abtmpx1r_data_latch而非r_d
总线按位宽明确标注避免歧义[7:0] r_rx_data

1.3 时钟与复位命名

// 时钟统一命名 i_clk // 单时钟系统 i_clk_100m // 多时钟系统(标明频率) i_clk_axi // 按总线命名 // 复位统一命名(低有效加 _n 后缀) i_rst_n // 系统复位,低有效 i_arst_n // 异步复位,低有效

2. 注释规范

2.1 文件头注释

每个.v文件开头必须有文件头注释:

// ============================================================ // 文件名 : uart_tx.v // 模块名 : uart_tx // 描述 : UART 发送模块(8N1,三段式 FSM 实现) // 参数 : p_CLK_FREQ - 系统时钟频率(Hz),默认 100MHz // p_BAUD_RATE - 波特率,默认 115200 // 端口 : i_clk - 系统时钟(上升沿有效) // i_rst_n - 异步复位(低有效) // i_tx_start - 发送触发(高脉冲,1拍有效) // i_tx_data - 待发送 8 位数据 // o_tx - 串行数据输出 // o_tx_busy - 发送忙标志(高有效) // 版本 : v1.0 - 初始版本 // ============================================================

2.2 段落注释

用分隔线和标题划分代码段,便于快速定位:

// ── 波特率分频计数器 ────────────────────────────────────────── always @(posedge i_clk or negedge i_rst_n) begin // ... end // ── 状态寄存器(第一段)────────────────────────────────────── always @(posedge i_clk or negedge i_rst_n) begin // ... end

2.3 行内注释

对非显而易见的代码加行内注释:

// 好的行内注释(解释为什么,而不是重复代码在做什么) localparam c_BAUD_DIV = p_CLK_FREQ / p_BAUD_RATE - 1; // -1:计数从0开始 assign w_baud_tick = (r_baud_cnt == c_BAUD_DIV); // 每个波特周期产生1拍脉冲 // 不好的行内注释(重复代码本身) r_cnt <= r_cnt + 1; // r_cnt 加 1 ← 废话注释,删掉

3. 模块化与层次化设计原则

3.1 单一职责原则

每个模块只做一件事,接口清晰:

// 好的设计:职责分明 uart_tx u_tx (...); // 只管发送 uart_rx u_rx (...); // 只管接收 baud_gen u_baud(...); // 只管波特率 uart_fifo_tx u_buf (...); // 只管发送缓冲 // 不好的设计:一个模块包揽所有 uart_everything u_uart(...); // 发送+接收+波特率+FIFO 全在一起,难以复用和测试

3.2 接口设计规范

模块接口遵循valid-ready握手协议(AXI-Stream 标准):

// 标准握手接口 output reg o_valid, // 数据有效 input wire i_ready, // 下游准备好接收 output reg [7:0] o_data, // 数据 // 握手条件:valid && ready 时数据传输成功 assign data_transfer = o_valid && i_ready;

3.3 顶层模块职责

顶层模块(top)只做例化和连线,不含业务逻辑:

// 好的顶层:只有例化和 assign 连线 module top ( input wire i_clk, i_rst_n, // ... ); // 只有连线和例化,无 always 块 uart_tx u_tx (.i_clk(i_clk), .i_rst_n(i_rst_n), ...); uart_rx u_rx (.i_clk(i_clk), .i_rst_n(i_rst_n), ...); assign w_tx_data = w_rx_data; // 简单连线 endmodule

4. 可综合代码禁用写法清单

4.1 禁止在可综合代码中使用initial

// 错误:initial 只能用于仿真 initial begin r_cnt = 0; // 综合工具会忽略或报错 end // 正确:用复位初始化 always @(posedge i_clk or negedge i_rst_n) begin if (!i_rst_n) r_cnt <= 8'd0; else r_cnt <= r_cnt + 1; end

4.2 禁止使用#延时

// 错误:延时在综合中被忽略,只能用于仿真 assign o_out = #5 i_in; // 综合后等于 assign o_out = i_in // 错误:always 中的延时 always @(*) begin #2 r_data = i_data; // 综合无效,且可能引发仿真/综合不一致 end

4.3 谨慎使用casex

// 风险:casex 将 x(未知)和 z(高阻)都作为无关位 // 仿真时 x 态蔓延可能引发意外匹配,仿真与综合行为不一致 casex (sel) 4'b1xxx: ... // 危险:仿真中 sel=4'bxxxx 也会匹配 // 推荐:用 casez(只把 z/? 作为无关位) casez (sel) 4'b1???: ... // 明确:? 表示无关,x 不被特殊处理

4.4 禁止异步组合反馈环路

// 错误:a 和 b 相互驱动,形成振荡 assign a = b & en; assign b = a | clr; // b 依赖 a,a 依赖 b → 组合回路! // 正确:用寄存器打断回路 always @(posedge i_clk) begin r_b <= r_a | i_clr; end assign w_a = r_b & i_en;

4.5 禁止在 always 内对同一信号多处赋值

// 错误:r_cnt 在同一 always 内被赋值两次(可能产生意外行为) always @(posedge i_clk) begin r_cnt <= r_cnt + 1; if (i_clr) r_cnt <= 0; // 哪个生效?Verilog 规定后者,但容易出错 end // 正确:用 if-else 明确优先级 always @(posedge i_clk) begin if (i_clr) r_cnt <= 0; else r_cnt <= r_cnt + 1; end

4.6 禁用写法速查表

禁用写法原因替代方案
initial不可综合用复位初始化
#延时综合忽略用时序逻辑控制
casex仿真/综合不一致casez或普通case
fork...join不可综合用时序状态机
wait(条件)不可综合用时序逻辑轮询
task内含时钟部分工具不支持仅在 Testbench 中使用
组合逻辑回路振荡/X态寄存器打断回路
多驱动综合报错每个 reg 只在一个 always 中驱动

5. 代码评审常见问题 Top 10

问题1:敏感列表不完整

// 错误:a、b 不在敏感列表,仿真行为与综合不一致 always @(sel) begin if (sel) y = a; // a 变化时不触发 always,仿真中 y 不更新! else y = b; end // 正确: always @(*) begin // 或 always @(sel, a, b) if (sel) y = a; else y = b; end

问题2:组合 always 中缺少 default 导致 Latch

// 错误:sel=2/3 时 y 未赋值 → Latch always @(*) begin case (sel) 2'd0: y = a; 2'd1: y = b; // 缺少 default! endcase end

问题3:宽度不匹配

// 错误:8位 + 8位可能溢出,结果被截断 wire [7:0] sum; assign sum = a + b; // a、b 均为 8 位,结果应为 9 位 // 正确: wire [8:0] sum; assign sum = {1'b0, a} + {1'b0, b}; // 扩位后相加

问题4:时钟边沿后立即采样(Setup violation 风险)

// Testbench 中错误:在时钟上升沿同时改变信号,触发 setup violation always @(posedge clk) data = new_data; // 正好在沿上变化,存在建立时间风险 // 正确:沿后延迟 1~2ns 再改变 always @(posedge clk) #2 data = new_data; // 仅用于 Testbench

问题5:parameter 被外部覆盖引发溢出

// 设计时应加参数范围校验(Verilog-2001 不支持,System Verilog 可用) // 或在注释中明确约束 parameter CLK_FREQ = 100_000_000; // 范围:1MHz ~ 200MHz

问题6:复位未覆盖所有寄存器

// 错误:r_state 在复位分支中未赋值 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin r_cnt <= 0; // 忘记 r_state <= IDLE; end // ... end

问题7:异步时钟域直接连线

// 错误:直接跨时钟域驱动,产生亚稳态 module cdc_bad ( input wire i_clk_a, i_clk_b, input wire i_data_a, output wire o_data_b ); assign o_data_b = i_data_a; // 危险!i_data_a 是 clk_a 域的信号 endmodule

问题8:generate 块滥用

// 不必要的 generate:for 循环就够用 generate genvar i; for (i = 0; i < 8; i = i + 1) begin assign w_out[i] = w_in[i] & i_en; end endgenerate // 更简洁: assign w_out = w_in & {8{i_en}};

问题9:顶层约束与端口不匹配

XDC 中的端口名必须与顶层 module 的端口名完全一致(区分大小写),否则实现时会报约束找不到端口的错误。

问题10:仿真用的语法混入了综合代码

// 错误:$display、$finish、`timescale 出现在可综合模块中 module uart_tx (...); `timescale 1ns/1ps // 综合时会被忽略,但容易误导 initial $display("start"); // 不可综合 endmodule

附:规范代码模板

module_template.v

// ============================================================ // 文件名 : module_template.v // 模块名 : module_template // 描述 : <模块功能一句话描述> // 参数 : p_PARAM_A - <说明,单位,默认值,有效范围> // 端口 : i_clk - 系统时钟,上升沿有效 // i_rst_n - 异步复位,低有效 // i_xxx - <输入描述> // o_xxx - <输出描述> // 版本 : v1.0 - 初始版本 // ============================================================ module module_template #( parameter p_PARAM_A = 8, parameter p_PARAM_B = 100 )( input wire i_clk, input wire i_rst_n, input wire i_valid, input wire [p_PARAM_A-1:0] i_data, output reg o_valid, output reg [p_PARAM_A-1:0] o_data ); // ── 本地参数 ──────────────────────────────────────────── localparam c_MAX_CNT = p_PARAM_B - 1; // ── 内部信号声明 ───────────────────────────────────────── reg [7:0] r_cnt; wire w_cnt_done; // ── 组合逻辑 ───────────────────────────────────────────── assign w_cnt_done = (r_cnt == c_MAX_CNT); // ── 计数器 ─────────────────────────────────────────────── always @(posedge i_clk or negedge i_rst_n) begin if (!i_rst_n) r_cnt <= 8'd0; else if (w_cnt_done) r_cnt <= 8'd0; else if (i_valid) r_cnt <= r_cnt + 1'b1; end // ── 输出寄存器 ─────────────────────────────────────────── always @(posedge i_clk or negedge i_rst_n) begin if (!i_rst_n) begin o_valid <= 1'b0; o_data <= {p_PARAM_A{1'b0}}; end else begin o_valid <= w_cnt_done; o_data <= i_data; end end endmodule
http://www.cnnetsun.cn/news/1658307.html

相关文章:

  • GitHub 加速计划:消除语言壁垒的智能界面本地化方案
  • Python 8天极速入门笔记(大模型工程师专用):第五篇-函数(def定义,大模型代码复用核心)
  • Redis 从入门到精通(二):数据类型详解
  • Open UI5 源代码解析之781:LightBox.js
  • 新手友好:通过快马生成的示例代码轻松踏入OpenCode世界
  • 高斯混合模型参数估计 使用EM算法对一维或者高维的高斯混合模型GMM进行密度函数参数估计
  • 3分钟快速获取百度网盘提取码的完整指南
  • 计算机毕业设计springboot在线运营工单处理系统 基于SpringBoot的客户服务工单流转与协同处理平台 SpringBoot框架下的智能运维服务请求跟踪管理系统
  • 3个核心步骤实现Koikatu HF Patch的无缝集成解决方案
  • 保姆级教程:在Windows下用VSCode和STM32CubeProgrammer给Pixhawk4飞控烧写Bootloader
  • Inter字体技术解析与实用指南:解锁现代排版潜能
  • 拯救受损二维码:用QRazyBox实现高效恢复的4个实战策略
  • 从ZJUCTF那道‘简单’的PHP反序列化题,聊聊魔术方法链的实战利用(附完整EXP)
  • 别再只会上传一句话木马了!用DVWA File Upload模块,深入理解PHP文件上传漏洞的5个关键点
  • 新手必看:在快马平台上通过实例代码轻松理解rag核心原理
  • 从BLDC方波到PMSM FOC:如何让你的电机告别“颗粒感”实现丝滑旋转?
  • AI Coding Agent从入门到精通(非常详细),给个文件系统就够了,收藏这篇就够了!
  • 收藏!CRUD时代终结,后端开发者如何转型AI Agent架构师?
  • 保姆级教程:用Dio和GitCode API v5为你的Flutter应用添加用户与仓库搜索功能
  • 探索智能前沿:在快马平台用ai实现openclaw配置的预测与诊断
  • 别让FPGA拖后腿!深入拆解PCIe枚举过程与Xilinx器件配置优化全攻略
  • 2025届毕业生推荐的降AI率网站横评
  • UGUI开发中那些烦人的黄线到底是什么?手把手教你关闭Navigation可视化
  • # 实时数据查询优化实战
  • 【26年最新英语六级真题】2015-2025年12月英语六级历年真题及答案PDF电子版+六级核心词汇
  • OpenClaw 的模型服务是否支持自定义认证插件?
  • Pycharm调试卡在collecting data?3步搞定Gevent兼容模式(附原理详解)
  • javaweb网上零食商城销售系统
  • FPGA新手避坑:用Quartus Prime 23.1的FIFO IP核实现跨时钟域传输(附仿真代码)
  • python-langchain框架(1-8-2 缓存机制——验证缓存的效果)