成都线下班 2月24号开班
重庆线下班 3月24号开班
广东线下班 5月24号开班
线上班
企业培训
FPGA证书报名
FPGA工程师招聘
近期优惠活动
在线客服
微信二维码

微信客服

扫码添加客服微信

FPGA证书报名

FPGA工程师应聘

FPGA工程师必懂的10个Verilog编码技巧:写出高效可靠的硬件代码

引言:为什么你的Verilog代码总是“跑不通”?

在FPGA开发中,50%的时序问题源于糟糕的编码习惯。本文从企业级代码规范出发,提炼出10个立竿见影的Verilog编码技巧,配合真实工程案例,助你规避常见陷阱,提升代码质量。
文末免费领取《Verilog代码规范检查表》PDF(含Xilinx/Altera双版本),一键排查代码隐患!

一、命名规范:让代码自文档化

技巧1:模块/信号命名遵循「功能+方向」原则

verilog

// ❌ 糟糕示例
wire a, b, c; 

// ✅ 优化示例
wire [7:0] adc_data_in;  // 来自ADC的8位输入数据
wire       uart_tx_en;    // UART发送使能信号

企业规范

  • 输入信号后缀_in,输出信号后缀_out
  • 时钟信号统一命名clk_<功能>,复位信号rst_<作用域>_n(低有效)

二、时序逻辑设计:锁存器是魔鬼!

技巧2:always块必须完整覆盖条件分支

verilog

// ❌ 产生锁存器!
always @(*) begin
    if (sel)
         out = a;
    // 缺少else分支!
end 

// ✅ 安全写法:补全default
always @(*) begin
    if (sel)
         out = a;
    else
            out = 8'hFF; // 明确默认值
end

🔍 对比动图
锁存器生成 vs 完整条件覆盖
(左:不完整条件导致锁存器;右:补全分支生成纯组合逻辑)

三、复位策略:同步复位 vs 异步复位

技巧3:全局使用同步复位,局部谨慎用异步

verilog

// ✅ 同步复位标准写法
always @(posedge clk) begin
    if (rst_n) begin
        cnt <= 8'b0;
    end else begin
        cnt <= cnt + 1;
    end
end

 // ⚠️ 异步复位仅用于特殊场景(如时钟恢复)
always @(posedge clk or negedge async_rst_n) begin
    if (!async_rst_n)
         state <= IDLE;
    else
         state <= next_state;
end

关键点

  • Xilinx FPGA优先用同步复位(减少布线资源占用)
  • 异步复位需做复位同步器消除亚稳态

四、赋值方式:阻塞 vs 非阻塞

技巧4:组合逻辑用阻塞(=),时序逻辑用非阻塞(<=)

verilog

// ❌ 混合赋值导致仿真与综合不一致
always @(posedge clk) begin
    a = b + c;  // 阻塞赋值
    d <= a;      // 非阻塞赋值
end

// ✅ 严格区分使用场景
// 时序逻辑
always @(posedge clk) begin
    data_ff <= data_in;  // 非阻塞
end

// 组合逻辑
always @(*) begin
    sum = a + b;         // 阻塞
end

五、状态机设计:三段式是金科玉律

技巧5:用parameter定义状态,三段式结构清晰

verilog

// ✅ 标准三段式状态机
localparam S_IDLE = 3'b001,
           S_RUN  = 3'b010,
           S_DONE = 3'b100;

 // 第一段:状态寄存器
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= S_IDLE;
    else
         state <= next_state;
end

// 第二段:下一状态逻辑
always @(*) begin
    case(state)
        S_IDLE: next_state = start ? S_RUN : S_IDLE;
        S_RUN:  next_state = (cnt == 10) ? S_DONE : S_RUN;
        S_DONE: next_state = S_IDLE;
        default:next_state = S_IDLE;
    endcase
end

// 第三段:输出逻辑
always @(posedge clk) begin
    if (state == S_RUN)
        cnt <= cnt + 1;
    else
         cnt <= 0;
end

六、case语句:优先级陷阱

技巧6:casex/casez谨慎使用,优先用full case

verilog

// ❌ casex可能导致意外匹配
casex (sel)
    4'b1??? : out = a;c// 可能匹配到sel=1000以外的值
endcase

// ✅ 添加default并限定匹配范围
case(sel[3:1])  // 明确匹配位宽
    3'b100: out = a;
    default:out = 8'h00;
endcase

企业规范

  • 综合前设置full_caseparallel_case避免锁存器
  • 使用uniquepriority关键字(SystemVerilog)

七、模块化设计:拒绝“巨无霸”模块

技巧7:单一模块不超过200行代码

verilog

// ✅ 功能拆分示例
module uart_tx (
    input  clk,
    input  [7:0] data_in,
    output txd
);

module uart_rx (
    input  clk,
    input  rxd,
    output [7:0] data_out
);

// 顶层模块仅例化
module top;
    uart_tx u_tx(...);
    uart_rx u_rx(...);
endmodule

优势

  • 便于复用和单元测试
  • 降低综合时间(增量编译)

八、注释规范:不是“写什么”,而是“为什么”

技巧8:注释解释设计意图,而非重复代码

verilog

// ❌ 无效注释
reg [3:0] cnt; // 定义4位计数器 

// ✅ 有价值注释
// 波特率分频计数器:115200bps @ 50MHz
// 分频数 = 50e6 / (115200 * 16) ≈ 27
reg [4:0] baud_cnt; // 需覆盖0-26(5位足够)

九、仿真与综合:提前发现不一致

技巧9:关键路径添加translate_off注释

verilog

// 仿真专用代码(不参与综合)
// synopsys translate_off
initial begin
    $dumpfile("wave.vcd");
    $dumpvars(0, tb_module);
end
// synopsys translate_on

工具链命令

  • Xilinx: set_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} -value "-verilog_define SIMULATION" -objects [get_runs synth_1]

十、代码优化:资源与速度的平衡

技巧10:用位宽裁剪替代除法

verilog

// ❌ 直接使用除法运算符
reg [31:0] div = data_in / 10; // 综合成DSP48单元 

// ✅ 位操作近似计算(误差<1%)
// 除以10 ≈ 右移3位(÷8) + 调整补偿
wire [31:0] div_opt = (data_in >> 3) + (data_in >> 5) - (data_in >> 7);

🔍 对比动图
除法器资源占用对比
(左:直接调用除法器消耗18个DSP;右:位操作仅用逻辑资源)

🎁 福利领取

立即获取《Verilog代码规范检查表》PDF,包含:

  • Xilinx/Altera编码规范差异对照表
  • 可打印的代码审查清单(Code Review Checklist)
  • 20个常见错误案例及修复方法

🚀 延伸学习:Verilog七日进阶训练营

如果你希望:
✅ 掌握企业级FPGA代码规范
✅ 亲手完成千兆以太网加速器项目
✅ 获得华为/中兴资深工程师代码评审

立即免费试听 👉 [Verilog七日进阶训练营试听链接]

通过规范编码,让你的代码一次编写,终身受用