目录
Verilog 编程规范
本节主要给大家介绍下编程规范,良好的编程规范是一个 FPGA 工程师必备的素质。
1、编程规范重要性
当前数字电路设计越来越复杂,一个项目需要的人越来越多,当几十号设计同事完成同一个项目时候,大家需要互相检视对方代码,如果没有一个统一的编程规范,那么是不可想象的。大家的风格都不一样,如果不统一的话,后续维护、重用等会有很大的困难,即使是自己写的代码,几个月后再看也会变的很陌生,也会看不懂(您可能不相信,不过笔者和同事交流发现大家都是这样的,时间长不看就忘记了),所以编程规范的重要性显而易见。
另外养成良好的编程规范,对于个人的工作习惯、思路等都有非常大的好处。可以让新人尽快融入项目中,让大家更容易看懂您的代码。
2、工程组织形式
工程的组织形式一般包括如下几个部分,分别是 doc、par、rtl 和 sim 四个部分。
XX 工程名
|--doc
|--par
|--rtl
|--sim
- doc:一般存放工程相关的文档,包括该项目用到的 datasheet(数据手册)、设计方案等。不过为了便于大家查看,我们开发板文档是统一汇总存放在资料盘下的;
- par:主要存放工程文件和使用到的一些 IP 文件;
- rtl:主要存放工程的 rtl 代码,这是工程的核心,文件名与 module 名称应当一致,建议按照模块的层次分开存放;
- sim:主要存放工程的仿真代码,复杂的工程里面,仿真也是不可或缺的部分,可以极大减少调试的工作量。
3、文件头声明
每一个 Verilog 文件的开头,都必须有一段声明的文字。包括文件的版权,作者,创建日期以及内容介绍等,如下表所示。
//
// Company:
// Engineer:
//
// Create Date: 2023/06/19 11:56:45
// Design Name:
// Module Name: shfit_reg4
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
//实验任务:
//本节的实验任务是设计一个 4 级串联寄存器电路。
建议一个.V 只包括一个 module,这样模块会比较清晰易懂。
4、输入输出定义
端口的输入输出有 Verilog 95 和 2001 两种格式,推荐大家采用 Verilog 2001 语法格式。下面是 Verilog 2001 语法的一个例子,包括 module 名字、输入输出、信号名字、输出类型、注释。
module flow_led(
// input sys_clk, //50MHz系统时钟(一个周期是20ns:1/50MHz=0.02us=20ns)
//differential system clocks //200MHz系统时钟(一个周期是5ns:1/200MHz=0.005us=5ns)
input sys_clk_p, //system clock positive
input sys_clk_n, //system clock negative
input sys_rst_n, //系统复位,低电平有效
output reg [3:0] led //4个LED输出信号
);
建议如下几点:
- ① 一行只定义一个信号;
- ② 信号全部对齐;
- ③ 同一组的信号放在一起。
5、parameter 定义
建议如下几点:
- ① module 中的 parameter 声明,不建议随处乱放;
- ② 将 parameter 定义放在紧跟着 module 的输入输出定义之后;
- ③ parameter 等常量命名全部使用大写。
//parameter define
parameter T = 5; //黑金FPGA-xc7a5tfgg484-2-时钟周期为5ns
parameter WIDTH = 25;
parameter COUNT_MAX = 25_000_000; //板载50M时钟=20ns,0.5s/20ns=25000000,需要25bit位宽.
6、wire/reg 定义
一个 module 中的 wire/reg 变量声明需要集中放在一起,不建议随处乱放。
因此,我们建议如下:
- ① 将 reg 与 wire 的定义放在紧跟着 parameter 之后;
- ② 建议具有相同功能的信号集中放在一起;
- ③ 信号需要对齐,reg 和位宽需要空 2 格,位宽和信号名字至少空四格;
- ④ 位宽使用降序描述,[6:0];
- ⑤ 时钟使用前缀 clk,复位使用后缀 rst;
- ⑥ 不能使用 Verilog 关键字作为信号名字;
- ⑦ 一行只定义一个信号。
//input define
//reg sys_clk; //时钟信号
reg sys_clk_p; //时钟信号
wire sys_clk_n;
reg sys_rst_n; //复位信号
//output define
wire [3:0] led;
7、信号命名
大家对信号命名可能都有不同的喜好,我们建议如下:
- ① 信号命名需要体现其意义,比如 fifo_wr 代表 FIFO 读写使能;
- ② 可以使用 “_” 隔开信号,比如 sys_clk;
- ③ 内部信号不要使用大写,也不要使用大小写混合,建议全部使用小写;
- ④ 模块名字使用小写;
- ⑤ 低电平有效的信号,使用_n 作为信号后缀;
- ⑥ 异步信号,使用_a 作为信号后缀;
- ⑦ 纯延迟打拍信号使用_dly 作为后缀。
8、always 块描述方式
always 块的编程规范,我们建议如下:
- ① if 需要空四格;
- ② 一个 always 需要配一个 begin 和 end;
- ③ always 前面需要有注释;
- ④ beign 建议和 always 放在同一行;
- ⑤ 一个 always 和下一个 always 空一行即可,不要空多行;
- ⑥ 时钟复位触发描述使用 posedge sys_clk 和 negedge sys_rst_n
- ⑦ 一个 always 块只包含一个时钟和复位;
- ⑧ 时序逻辑使用非阻塞赋值。
//计数器对系统时钟计数,计时0.2秒,仿真时间设为10
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
counter <= 24'd0;
else if (counter<24'd1000_0000)
//else if (counter<24'd9) //仅用于仿真
counter <= counter + 1'b1;
else
counter <= 24'd0;
end
9、assign 块描述方式
assign 块的编程规范,我们建议如下:
- ① assign 的逻辑不能太复杂,否则易读性不好;
- ② assign 前面需要有注释;
- ③ 组合逻辑使用阻塞赋值。
//计数到最大值时产生高电平使能信号
assign counter_en = (counter == (COUNT_MAX - 1'b1)) ? 1'b1 : 1'b0;
10、空格和 TAB
由于不同的解释器对于 TAB 翻译不一致,所以建议不使用 TAB,全部使用空格。
11、注释
添加注释可以增加代码的可读性,易于维护。我们建议规范如下:
- ① 注释描述需要清晰、简洁;
- ② 注释描述不要废话,冗余;
- ③ 注释描述需要使用 “//” ;
- ④ 注释描述需要对齐;
- ⑤ 核心代码和信号定义之间需要增加注释。
//用于产生 0.5 秒使能信号的计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)
counter <= 1'b0;
else if (counter_en) // counter_en 为 1 时,counter 清 0
counter <= 1'b0;
else
counter <= counter + 1'b1;
end
12、模块例化
模块例化我们建议规范如下:
① moudle 模块例化使用 u_xx 表示。
//例化计时模块
time_count #(
.MAX_NUM (TIME_SHOW)
) u_time_count(
.clk (sys_clk ),
.rst_n (sys_rst_n),
.flag (add_flag )
);
//例化数码管静态显示模块
seg_led_static u_seg_led_static (
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.add_flag (add_flag ),
.sel (sel ),
.seg_led (seg_led )
);
13、其他注意事项
其他注意事项如下:
- ① 代码写的越简单越好,方便他人阅读和理解;
- ② 不使用 repeat 等循环语句;
- ③ RTL 级别代码里面不使用 initial 语句,仿真代码除外;
- ④ 避免产生 Latch 锁存器,比如组合逻辑里面的 if 不带 else 分支、case 缺少 default 语句;
- ⑤ 避免使用太复杂和少见的语法,可能造成语法综合器优化力度较低。
良好的编程规范是大家走向专业 FPGA 工程师的必备素质,希望大家都能养成良好的编程规范。