众所周知,用于FPGA开发的硬件描述语言(HDL)主要有两种:
Verilog和VHDL
。其中,VHDL的出现时间要比Verilog早,而
Verilog由于其简单的语法
,跟C语言的相似性,目前被各大公司广泛使用。
其实在上大学时,我学习的就是VHDL语言。后来由于公司使用的都是Verilog,于是又重新学习了Verilog。好在有C语言基础,Verilog很快就上手了。
Verilog标准文档主要有3个版本:
-
Verilog-1995
-
Verilog-2001
-
Verilog-2005
都是由IEEE颁布。目前最新的Verilog标准是2005版,相比于前两个版本,2005更简洁,更灵活。
虽然一些官方的代码,如Xilinx一些IP核代码,为了兼容以前的综合工具,还是基于Verilog-2001标准,但我还是强烈建议你使用最新的Verilog-2005标准。
IEEE-2005
所以,本文是基于IEEE-2005语法标准,即《IEEE P1364-2005/D3:Draft Standard for Verilog ® Hardware Description Language》官方标准文档。
良好的代码规范可以提高代码的可读性、可复用性、简洁清晰,这也是一种职业素质的体现。
我们的目标是:
Write
Nowhere
,Read
Everywhere
都有哪些内容?
命名
命名主要包括文件和模块命名,端口命名,变量命名,参数命名等,概括来说就是:
有意义,简短易读
,重要的是
不要使用拼音
!
文件命名
文件名和模块名保持一致,一个文件只写一个模块。
文件命名
文件命名要有含义,且简短易读,文件名统一使用小写字母,并使用下划线分割文件名。
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硬件管脚上。
顶层端口命名
变量命名
-
时钟信号统一使用
clk
命名,如果是特定时钟频率,可以在后面添加时钟频率,如
clk_50m
-
复位信号统一使用rst命名,如果是低电平有效,后加_n表示,如
rst_n
-
标志位命名:
flag_rise/flag_fall/flag_clr
-
寄存器打拍信号命名添加
_reg
:
reg rxd_reg
-
移位寄存器命名添加后缀
_sreg
:
reg [3:0] busy_sreg
部分通用的缩写:
缩写
|
全拼
|
含义
|
rst
|
reset
|
复位
|
clk
|
clock
|
时钟
|
rd
|
read
|
读取
|
wr
|
write
|
写入
|
addr
|
address
|
地址
|
ack
|
acknowledge
|
响应
|
参数命名
Verilog中的参数类似于C语言中的define,主要有以下两类
localparam
和
parameter
,两者的区别是前者不可以在例化时进行参数传递,而后者可以在例化时进行参数传递。
其他的变量,文件名都是统一小写,只有参数定义有
全部大写的待遇
,当需要定义一些常量时,可以通过参数声明指定一个有意义的名称。如:
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新添加的功能。
端口命名
这一点有些朋友可能是按照功能进行划分,如连接同一芯片的放在一起。
输入输出分开放的好处是,在例化时可以很方便的区分哪些是输入哪些是输出。
空格和缩进让代码更清晰
-
运算符两端增加一个空格,可以让程序结构更清晰,可读性更高
-
缩进风格采用KR风格,即begin写在行尾,不占用单独一行,end单独占用一行
-
缩进统一使用4个空格来代替TAB键
-
if/else等语句只有一行时,可以省略begin-end
-
合理添加空行进行块区分,不同的always块进行换行隔开
以下是两种代码的书写规范,合理缩进,合理增加空格大大增加了可读性。
合理缩进
小括号增加可读性
在学校里有些考试题,为了考察学生对各种运算符优先级的掌握程度,出一些反人类的题目。
而做实际项目不像考试,追求的是可读性和易用性,所以当使用多个运算符时,为了增强可读性,避免歧义,不要吝啬使用小括号来表示运算的优先级。
运算符优先级
例化
例化可以认为是FPGA开发的灵魂所在了,例化的过程其实就是硬件模块的调用过程,比如我们用Verilog描述了一个3-8译码器的模块,可以在不同的地方去使用(例化)它,并分别命名为ut0/ut1/ut2等等,而且还可以在例化时指定参数,如串口发送和接收模块,通过指定不同的参数来实现不同波特率的兼容。
示例:
wire [7