FPGA 40 专题 verilog语法编程规范
在这里主要是给自己写一个备忘录,加强个人记忆。
详细可以参考地址1:https://www.runoob.com/w3cnote/verilog2-codestyle.html 进行学习
或者参考地址2:https://hitsz-cslab.gitee.io/diglogic/codingstyle/codingstyle/ 进行学习
1、信号变量、寄存器变量、模块名称的命名
在编写verilog代码的时候,和其它语言也是类似的,如 C/C++、python的函数、变量命名基本上是类似。
1.1 模块名称的命名
- 文件名字保持与设计的 module 名字一致,且使用小写英文来表示
1.2输入输出信号的命名
- 用小写字母定义wire、reg和input、inout、output信号;
(注:也有首字母大写的,我开始写代码的时候,就是首字母大写。 但是在我个人实际编写代码的时候,发现,全部用小写其实更加方便阅读,而且在编写代码的时候,频繁切换大小写也很烦,而且会提高拼写的错误风险,这个要在写代码写了一段时间后可能体会的更加深刻)
reg Data_To_Destination_Clock ;
reg data_to_destination_clock ; //推荐
也可以巧用数字代表英文字母,例如 2 代表 to, 4 代表 for, 可以减少变量命名过长
reg clk_for_test, sig_uart_to_spi ;
reg clk4test, sig_uart2spi ; //推荐
- 用大写字母定义parameter、localparam和宏定义(`define);
parameter、localparam 的使用和区别:
注: parameter可用作在顶层模块中例化底层模块时传递参数的接口,localparam的作用域仅仅限于当前module,不能作为参数传递的接口。参考链接: https://blog.youkuaiyun.com/qq_31799983/article/details/81113198
简单做个概括:
定义在模块的 parameter 常量,在其它.v文件进行模块例化(模块集成拼接)的时候,定义的parameter可以在其它模块中进行修改。(这个一般在定义计数器、定时器、或者模块的位宽等长度的时候,可以在其它模块集成的时候根据需要进行匹配和修改,这样就增加了编写模块的灵活性)
定义在模块的 localparam 常量,在其它.v文件进行模块例话(模块集成拼接)的时候,定义的localparam 只能在本地进行修改。(一般在编写状态机的状态的时候,用localparam来定义,这个一般是固定的,不能瞎改)
// 可以(需要)通过其它.v来修改的参数可以通过 parameter 定义常量
parameter TIME_CLK_COUNTER = 200 ; //常量
parameter DW = 8 ; //常量
reg [DW-1 : 0] wdata ; //变量
// 状态机用 localparam 、本地不需要变来变去的固定常量用 localparam
localparam s0 = 4'd0;
localparam s1 = 4'd1;
localparam s2 = 4'd2;
localparam s3 = 4'd3;
localparam s4 = 4'd4;
localparam s5 = 4'd5;
localparam s6 = 4'd6;
localparam s7 = 4'd7;
localparam s8 = 4'd8;
- 寄存器变量一般加后缀 _r, 延迟打拍的变量加后缀 _r1、_r2 ;
- 低电平有效的信号应使用后缀“_n”,如cs_n,rst_n;
- 常用其他尾缀:_d 可以表示延迟后的信号,_t 可以表示暂时存储的信号,_n 可以表示低有效的信号,_s 可以表示 slave 信号,_m 可以表示 master 信号等。
主要有两大好处:
1、 RTL 设计时容易根据变量类型对数据进行操作
2、是综合后网表的信号名字经常会改变,加入后缀容易在综合后网表中找到与 RTL 中对应的信号变量。
wire dout_en ;
reg dout_en_r ;
... //dout_en_r 的逻辑
assign dout_en = dout_en_r ;
1.3文件名:以some_module.v 文件规范写法
遵顼以上模块的命名规范写法为:
// 模板1
module some_module(
input wire clk ,
input wire ret_n,
input wire [3:0] addr,
output reg data
);
endmodule
// 模板2
module some_module(
clk ,
ret_n,
addr,
data
);
input wire clk ,
input wire ret_n,
input wire [3:0] addr,
output reg data
endmodule
模板1的话,在可读性方面会特别好,因为你明确的知道输入和输出信号的所有信息(如: 信号输入和输出信息、信号名称、信号位宽、信号的类型)。
模板2的话,在定义的时候只有信号名称,而其它的都没有给出,这个地方有点类似C/C++定义的函数。虽然这种方式模块定义比较简单,但是,在我们编写test_bench自己做验证的时候和模型实例化的时候会方便一些,这种看你怎么取舍,只是两种规范,反正都差不多。
1.4 always语句里面的常用编写格式
这里主要是一个 begin 和 end 之间的处理,在Verilog中,begin 和 end 是成对出现的。
因此,begin 的放置的位置会影响整个代码的风格,如下所示:
//always block 风格1
always@(posedge Clk or negedge Rst_n)
if(Rst_n== 1'b0)
cnt <= 0 ;
else if(Cin == 1'b1)
begin
if(cnt == 4'd9)
cnt <= 0 ;
else
cnt <= cnt + 1'b1 ;
end
else
cnt <= cnt ;
//always block 风格2
always@(posedge Clk or negedge Rst_n)
if(Rst_n== 1'b0)
cnt <= 0 ;
else if(Cin == 1'b1) begin
if(cnt == 4'd9)
cnt <= 0 ;
else
cnt <= cnt + 1'b1 ;
end
else
cnt <= cnt ;
其实上述代码就是一个begin 位置的事情,对于刚刚开始的学习Verilog 的新手来说,我自己开始用的就是 风格1,这样我就能很好的去找我想要的代码(尤其是我在新手阶段时,刚开始接触状态机的时候,感触会比较深刻),便于我开始的学习。但是,如果你写python或者c/c++用的多的时候,你就会觉得那个begin特别变扭,莫名奇妙多了个这么个玩意,就感觉不舒服。
后续在慢慢进阶的时候,就可以考虑 风格2 的方式了,这个时候,你看代码或者自己写的多了,基本上就知道那个部分对应的是哪里,这样写的话,阅读性上会比第一种好一些,这种也是在工作中用的比较多的一种方式,可以根据自己的情况来过渡。
1.5常用代码优化方式
使用圆括号确定程序的优先级或逻辑结构。为避免操作符优先级问题导致设计错误,建议多多使用圆括号。同时,圆括号的巧妙使用有时候也会优化逻辑综合后的结构。例如:
//往往被综合成串行的 3 个加法器
assign F = A + B + C + D ;
//往往被综合成并行的的 2 个加法器和 1 个级联的加法器,时序更加宽松
assign F = (A + B) + (C + D) ;
//不推荐
assign flag = cnt == 4'd2 && mode == 2'b01;
//推荐
assign flag = (cnt == 4'd2) && (mode == 2'b01);
1.6模块例化
模块例化时,端口信号尽量与连接信号隔开,并各自对齐。连接信号为向量时指明其位宽,方便阅读、调试。
ram u_ram(
.CLK_WR (clk),
.WR_EN (wren),
.ADDR_WR (addr),
.D (wdata[9:0]),
.Q (rdata[31:0])
);
这个地方和.v 文件编写规范(1.3文件名:以some_module.v 文件规范写法)的 模板2是非常类似的,这样在例化的时候非常方便。