如何写出易于维护的Verilog代码?

简介: 如何写出易于维护的Verilog代码?

其实我大学时学习的是VHDL语言,后来由于公司都是使用的Verilog,又重新学习了Verilog,好在有C语言基础,Verilog很快就上手了。

640.jpg


Verilog标准文档主要有3个版本,分别是:

  • Verilog-1995
  • Verilog-2001
  • Verilog-2005


都是由IEEE颁布。目前最新的Verilog标准是2005版,相比于前两个版本,2005更简洁,更灵活。


虽然一些官方的代码,如Xilinx一些IP核代码,为了兼容以前的综合工具,还是基于Verilog-2001标准,但我还是强烈建议你使用最新的Verilog-2005标准。

640.jpg


所以,本文都是基于IEEE-2005语法标准,即《IEEE P1364-2005/D3:Draft Standard for Verilog ® Hardware

Description Language》官方标准文档。文末查看IEEE-2005官方标准文档获取方法。


良好的代码规范可以提高代码的可读性、可复用性、简洁清晰,这也是一种职业素质的体现。


我们的目标是:

Write Nowhere, Read Everywhere

640.jpg


都有哪些内容?

  • 命名
  • 文件命名
  • 端口命名
  • 变量命名
  • 参数命名
  • 结构
  • 整体结构
  • 端口声明
  • 空格和缩进让代码更清晰
  • 小括号增加可读性
  • 例化
  • 注释
  • 其他
  • IEEE-2005标准下载


命名

命名主要包括文件和模块命名,端口命名,变量命名,参数命名等,概括来说就是:有意义,简短易读,重要的是不要使用拼音


文件命名

文件名和模块名保持一致,一个文件只写一个模块。

640.jpg


文件命名要有含义,且简短易读,文件名统一使用小写字母,并使用下划线分割文件名。

  • 底层驱动类模块命名,使用drv_xxx类型,如:drv_led.vdrv_i2c.vdrv_ad7606.v
  • 串口发送一字节:uart_tx_byte.v
  • 异步和同步FIFO,并指示深度和宽度:fifo_async_512_16.vfifo_sync_256_64.v


TestBench文件名问源文件名后加_tb,如源文件drv_led.v,则对应的testbench文件命名为drv_led_tb.v


顶层模块统一命名为top.v,或top_project_name.v,如EEPROM读写示例工程顶层命名为top_eeprom_demo.v


端口命名

端口命名和文件命名一样,统一使用小写字母,并使用下划线进行分割。


如果是顶层模块,而且是连接到实际的FPGA管脚,后加_pad_i_pad_o_pad_io来命名,表示连接到实际的FPGA硬件管脚上。

640.jpg


变量命名

  • 时钟信号统一使用clk命名,如果是特定时钟频率,可以在后面添加时钟频率,如clk_50m
  • 复位信号统一使用rst命名,如果是低电平有效,后加_n表示,如rst_n
  • 标志位命名:flag_rise/flag_fall/flag_clr
  • 寄存器打拍信号命名添加_regreg rxd_reg
  • 移位寄存器命名添加后缀_sregreg [3:0] busy_sreg


部分通用的缩写:

缩写 全拼 含义
rst reset 复位
clk clock 时钟
rd read 读取
wr write 写入
addr address 地址
ack acknowledge 响应


更多的常用端口命名和变量命名缩写,可以参考以下文章:

https://blog.csdn.net/heartdreamplus/article/details/86171272


参数命名

Verilog中的参数类似于C语言中的define,主要有以下两类localparamparameter,两者的区别是前者不可以在例化时进行参数传递,而后者可以在例化时进行参数传递。


其他的变量,文件名都是统一小写,只有参数定义有全部大写的待遇,当需要定义一些常量时,可以通过参数声明指定一个有意义的名称。如:

parameter LED_ON     = 1'b0;
parameter LED_OFF    = !LED_ON;
parameter BAUD_RATE  = 115200;
parameter TIME_100MS = 25_000_000;


状态机的状态通常使用localparam来定义,并指定有意义的名称,统一使用Sn_NAME的格式,并指定位宽。如:

localparam S0_IDLE  = 4'd0;
localparam S1_START = 4'd1;
localparam S2_DOING = 4'd2;
localparam S3_END   = 4'd3;
localparam S_FINISH = 4'd4;
localparam S_ERROR  = 'd5;//auto adaptive


结构

整体结构

建议Verilog模块文件按照如下结构进行书写。

/* 1.文件头: 说明版权信息,文件功能,作者,版本历史等 */
/* 2.端口声明: input/output/inout */
/* 3.宏定义: `define */
/* 4.参数定义: localparam/parameter */
/* 5.寄存器定义: reg */
/* 6.线网类型定义: wire */
/* 7.互联定义: assign */
/* 8.时序逻辑描述: always */


示例:

/* 1.file head */
/***********************************************************
Copyright 2021 'wechat:mcu149'. All rights reserved.
FileName: drv_led.v
Function: led driver
Author  : mcu149
SVN     :
    SVN Revision: 1490
    SVN Date: 2021-06-05 19:49:49
Revision:
    2020-09-09: Rev 1.0
    2020-10-01: Rev 1.1
************************************************************/
/* 2.input/output/inout */
module drv_led(
    //Inputs
    input rst_n,
    input clk_50m,
    input en,
    //Outputs
    output led1,
    output reg led2 = 0      //define initial value is 0
);
/* 3.define */
/* 4.parameter/localparam */
parameter TIME_500MS = 32'd25_000_000;
/* 5.reg */
reg [31:0] cnt = 0;
/* 6.wire */
wire flag_toggle = (cnt == TIME_500MS);
/* 7.assign */
assign led1 = (en == 0) ? 0 : cnt[20];
/* 8.always block */
always @ (posedge clk_50m) begin
    if(!rst_n)
        cnt <= 0;
    else if(flag_toggle)
        cnt <= 0;
    else 
        cnt <= cnt + 1;
end
always @ (posedge clk_50m) begin
    if(!rst_n)
        led2 <= 0;
    else if(!en) 
        led2 <= 0;
    else if(en) begin
        if(flag_toggle)
            led2 <= !led2;
    end
end
endmodule


端口声明

输入端口放在一起,输出端口放在一起,双向端口放在一起。如果某个输出信号需要确定初始值,可以在端口定义时直接进行指定,这也是Verilog-2005新添加的功能。

640.jpg


这一点有些朋友可能是按照功能进行划分,如连接同一芯片的放在一起。


输入输出分开放的好处是,在例化时可以很方便的区分哪些是输入哪些是输出。


空格和缩进让代码更清晰

  • 运算符两端增加一个空格,可以让程序结构更清晰,可读性更高
  • 缩进风格采用KR风格,即begin写在行尾,不占用单独一行,end单独占用一行
  • 缩进统一使用4个空格来代替TAB键
  • if/else等语句只有一行时,可以省略begin-end
  • 合理添加空行进行块区分,不同的always块进行换行隔开


以下是两种代码的书写规范,合理缩进,合理增加空格大大增加了可读性。

640.jpg


小括号增加可读性

在学校里有些考试题,为了考察学生对各种运算符优先级的掌握程度,出一些反人类的题目。


而做实际项目不像考试,追求的是可读性和易用性,所以当使用多个运算符时,为了增强可读性,避免歧义,不要吝啬使用小括号来表示运算的优先级。

640.jpg


例化


例化可以认为是FPGA开发的灵魂所在了,例化的过程其实就是硬件模块的调用过程,比如我们用Verilog描述了一个3-8译码器的模块,可以在不同的地方去使用(例化)它,并分别命名为ut0/ut1/ut2等等,而且还可以在例化时指定参数,如串口发送和接收模块,通过指定不同的参数来实现不同波特率的兼容。


  • 例化和端口声明顺序保持一致,输入端口放在一起,输出端口放在一起
  • 多比特信号,在例化时需要指定位宽,以增加可读性
  • 顶层模块只进行模块例化,不写任何控制语句


示例:

wire [7:0] rx_data;
wire rx_done;
wire rx_err;
/* 串口接收1字节 */
uart_rx_byte #(
    .BAUD_RATE(32'd115200),
    .EN_PARITY(2'd0),  //0:无校验位, 1:奇校验, 2:偶校验
    .EN_STOP_2(1'b0)   //1:使能2位停止位
)uart_rx_byte_0(
    // Inputs
    .clk(clk_32m),
    .rst_n(rst_n),
    .rxd(uart_rxd),
    // Outputs
    .data_rx(rx_data[7:0]),    //指定位宽
    .done(rx_done),
    .err(rx_err)
);


注释

不要相信什么代码就是最好的注释!我不否认有些人的代码写的就是很规范,命名合理,格式清晰。


但是我觉得你还没有达到那种程度,不能保证每一个人都能读懂没有注释的代码。注释不仅是为了给别人看,更多的也是为了给自己看,好记性不如烂笔头


注释统一使用/**/注释的方式,或者使用与//混合使用,看个人习惯!

  • 每个变量定义后需要注释变量的功能
  • 每个always块功能需要注释
  • 状态机状态含义需要注释
  • 条件语句的后面需要添加注释
  • 代码修改,注释也要随之修改


其他

  • 合理使用generate for可以批量化定义和例化模块,减少代码量,提高可读性
  • testbench中使用task和function可以提高效率
  • 移位操作替换为拼接补0操作,更易读
  • 时序逻辑统一使用非阻塞赋值,即<=符号
  • 一行字符不要超过80个,过长通过换行来处理
  • 先有顶层设计,然后划分子模块功能,或者自底向上方法
  • 模块化设计,提高模块可复用程度
  • 能用case的尽量不要用if/else
  • reg类型变量,根据需要看是否锁存
目录
相关文章
|
2月前
|
Java 程序员 数据处理
这个工具,让程序可读性提升 1000%
优树搭是一款划时代的后端图形化编程工具,旨在解决编程中的代码难读问题。它采用树形结构和多槽位设计,使程序逻辑清晰易懂,无需编译即可实时查看效果。优树搭支持在线开发,具备图灵完备特性,并提供详细的调试信息,极大提升了开发效率。此外,它兼容多种编程语言,可与低代码和零代码平台整合,适用于各种复杂应用场景。官网现已开启公测,欢迎体验并提出宝贵建议。官网地址:https://www.youshuda.cn
这个工具,让程序可读性提升 1000%
|
5月前
|
算法 安全 编译器
【简洁的代码永远不会掩盖设计者的意图】如何写出规范整洁的代码
【简洁的代码永远不会掩盖设计者的意图】如何写出规范整洁的代码
52 1
|
6月前
|
前端开发 测试技术
代码注释怎么写:让你的代码更易维护
在编程中,有一种无声的艺术,那就是代码注释。这可能看起来微不足道,但其实非常关键。它不仅有助于他人理解你的代码,也是自我表达的一种方式。
|
6月前
|
算法 小程序 Linux
如何写出高效清晰的嵌入式C程序
如何写出高效清晰的嵌入式C程序
34 0
|
存储 前端开发 安全
Controller层代码技巧,开发人员可以编写出更高效、可维护的代码
Controller层代码技巧,开发人员可以编写出更高效、可维护的代码
178 0
|
程序员 测试技术
《重构2》第十章-简化条件逻辑
《重构2》第十章-简化条件逻辑
340 0
|
设计模式 安全 测试技术
经验总结 | 重构让你的代码更优美和简洁
最近,笔者有幸对高德打车订单Push项目进行了重构,与大家分享一下代码重构相关的工作经验,希望对大家有所启发。
231 1
经验总结 | 重构让你的代码更优美和简洁
|
XML 算法 Java
一篇文章教你如何写出【✨无法维护✨】的代码?
一篇文章教你如何写出【✨无法维护✨】的代码?
191 0
一篇文章教你如何写出【✨无法维护✨】的代码?
|
安全 前端开发 Java
如何写出健壮的代码?
关于代码的健壮性,其重要性不言而喻。那么如何才能写出健壮的代码?阿里文娱技术专家长统将从防御式编程、如何正确使用异常和 DRY 原则等三个方面,并结合代码实例,分享自己的看法心得,希望对同学们有所启发。
2886 0
如何写出健壮的代码?