赞
踩
每个工程师的代码风格各不一样,当工程较大,面临需要多个工程师共同完成,或者面临当工程师离职后代码交接等问题。若代码规范未统一,阅读代码将是非常吃力的。下文是作者从各大论坛、网站、书籍收集整理的Verilog编程规范。
工程名体验了机台信息和板卡信息,工程命名规则如下:
机台_板卡
清晰的文档命名能够让我们思路非常的清晰,所以FPGA工程文件夹的目录要求层次鲜明,归类清晰。以Xilinx工程自带的文件架构为主,此外在工程主界面需创建DOC文件夹,用以存放工程的说明文档。
所有信号(signal),变量(variable)以及模块(module)的名字都用小写字母,常量名(参数(parameter)和宏(macro))用大写字母。不要依赖大小写给标识符增加语义。
说明: 这样做是为了和业界的习惯保持一致,避免了在大小写敏感的工具中可能会遇到的问题(Verilog语言是大小写敏感),同时也可以很容易地从代码中辨认出参数(parameter)。
示例:
不好的代码风格:
parameter width = 16;
input[width - 1 : 0] DataIn;
好的代码风格:
parameter WIDTH= 16;
input[WIDTH- 1 : 0] data_in;
说明: 这样可以增强程序的可读性,并能避免标识符过于冗长。
wire [9:0] addresscontrolenable;
reg [15:0] i,q;
好的:
wire [9:0] addr_ctl_en;
reg [15:0] fir_out_datai;
reg [15:0] fir_out_dataq;
在RTL源码的设计中任何元素包括端口、信号、变量、函数、任务、模块等的命名都不能取Verilog和VHDL语言的关键字。
文件名要和模块名相同,在一个文件中只用一个模块,在不同的层级上尽量使用统一的信号名,这样容易跟踪信号,网表调试也容易。
对于时钟信号使用前缀clk_,并使用有含义的缩写构成时钟信号,对于同一个时钟信号,在所有的模块中名字保持一致。
对于复位信号使用前缀rst_,并使用有含义的缩写构成复位信号,对于同一个复位信号,在所有的模块中名字保持一致。
示例:
clk_lbus_33m,clk_base_92m16,rst_lbus_n。
对于低电平有效的信号,使用后缀*_n;
示例:
rst_lbus_n,ad7680_cs_n;
用于仿真测试的文件的名字与被测试模块名字一一对应。添加后缀*_tb;
模块中所有用到的信号必须在信号声明部分进行声明;如果一个信号名没做声明Verilog将假定他为一位宽的wire变量。
系统级信号命名使用前缀sys_*,时钟和复位信号除外;
异步信号命名使用后缀*_a;
三态信号命名使用后缀*_z;
多周期路径第n个周期使用的信号使用后缀*_pn;
使用触发器或者SRL延迟n clk cycle的信号使用后缀*_ln;
模块之间的接口信号,命名分为两个部分,第一部分表明数据方向,其中数据发出方在前,数据接收方在后,第二部分为数据名称:若某个信号从一个模块传递到多个模块,其命名应视信号的主要路径而定,在不同的子模块中尽量采用相同的名字;端口和连接端口的信号尽可能采用相同的名字。
示例:
cpu_mmu_wr_req,该信号含义为CPU模块到MMU的写请求。
用于跨时钟域信号传递消除亚稳态的两级寄存器,前一个寄存器输出信号命名为*_meta,第二个寄存器使用*_meta作为输入,*_sync作为输出;
每个设计文件开头应包含如下注释内容:公司名称、作者、创建时间、文件名、所属项目、顶层模块、所需库、使用的仿真器和综合工具(运行平台和版本)、模块名称及实现功能和关键特性描述、文件创建和修改记录(包括修改版本号、修改时间、修改人名字、修改内容)。
1.使用//进行的注释行在//后加一个空格。并以分号结束;
2.使用/* */进行的注释,/和/各占用一行,并且顶头。
3.尽量在每个always块之间加一段注释,增加可持续性和便于调试;
4.注释应该与代码一致,修改程序的时候一定要修改相应的注释;
5.注释不应该重复代码已表明的内容,而是简介式点明程序的突出特征。
示例:
// Edge detector used to synchronize the input signal;
/*
Edge detector used to synchronize the input signal;
*/
1.端口定义按照功能块划分,每个功能块中按照输入、输出、双向的顺序,各个功能块之间要有空行或注释为间隔;
2.每行声明一个端口并有注释,注释在同一行;
3.用下述顺序声明端口,不同类型的端口声明使用一个空行间隔;
Input:
clocks
resets
enables
other control signals
data and address lines
Outputs:
clocks
resets
enables
other control signals
data and address lines
在模块端口声明之间定义时延单位和时延精度,和文件头及端口声明各有一个空行间隔,格式为:timescale 1ns / 100ps;
模块按照下列功能块顺序组织:
文件头
时延单位和时延精度
端口声明
参数声明
信号声明
逻辑功能
各个功能块之间要有空行或注释作为间隔;
每一行语句独立成行。尽管VHDL和Verilog都允许一行可以写多个语句,但每个语句独立成行可以增加可读性和可维护性。
1.用缩进提高行和嵌套语句的可读性;
2.缩进一般采用制表符Tab对语句对齐和缩进,Tab键采用4个字符宽度,可在编辑器中设置;
3.不要使用连续的空格来进行语句的对齐;
4.各种嵌套语句尤其是if…else语句,必须严格的逐层缩进对齐。
总线的有效位顺序定义从MSB到LSB,如data[4:0];
1.模块名、模块例化名统一,例化前加大写“Un_”区分,其中n表示多次例化标识;
2.使用名字相关的显示映射而不要采用位置相关的映射;
3.输入和输出每类端口之间一个空行来提高可读性;
4.模块例化时不允许存在未连接的信号;
这样可提高代码的可读性和方便debug连线错误。
分节书写,各节之间加1到多个空行。如每个always,initial语句都是一节。每节基本上完成一个特定的功能,即用于描述某几个信号的产生。在每节之前有几行注释对该节代码加以描述,至少列出本节中描述的信号的含义。
1.不同变量,以及变量与符号、变量与括号之间都应当保留一个空格;
2.Verilog关键字与其他任何字符串之间都应当保留一个空格;
3.逻辑运算符,算术运算符,比较运算符的两侧各留一个空格,与变量分割开来;
4.单操作数运算符例外,直接位于操作数前,不使用空格;
1.同一个层次的所有语句左端对齐;
2.Initial、always等语句块的begin关键词跟在本行的末尾,相应的end关键词与initial、always对齐。这样做的好处是避免因begin独占一行而造成行数太多;
3.Initial、always等语句块的关键词顶头书写;
使用适当的注释来解释所有的always进程、函数、端口定义、信号含义、变量含义或信号组、变量组以及常量的意义等。注释应该放在它所注释的代码附近,要求简明扼要,只要住够说明设计意图即可,避免过于复杂;保持20%-25%的注释率。注释描述代码的功能,而不是行为。
示例:
这样的注释纯粹是废话
//increment addr
addr = addr + 1’b1;
这样的注释就好得多
//Move the read addr to the next element
addr = addr + 1’b1;
无效代码删除掉,不要注释;
1.顶层模块应只是内部模块间的互联,尽量避免再做逻辑,如不能出现对reg变量赋值等。这样做的目的是为了能更有效的综合,因为在顶层模块中出现中间逻辑,综合工具不能把子模块中的逻辑综合到最优。
2.端口连接时避免使用表达式;
书写数值时每隔4个bit用下划线(“_”)隔开,如32’h0000_0000。
在设计中不要直接使用数字,作为例外,可以使用0和1。建议采用参数定义代替直接数字。同时,在定义常量时,如果一个常数依赖另一个常数,建议在定义该常量时用表达式表示出这种关系。用parameter或`define来定义变量,禁止使用defparams。
示例:
不好的编码风格:
wire [7:0] my_in_bus;
reg [7:0] my_out_bus;
好的编码风格:
`define MY_BUS_SRIE 8
wire [MY_BUS_SRIE - 1:0] my_in_bus;
reg [MY_BUS_SRIE - 1:0] my_out_bus;
保持每行小于或等于80个字符,如超出,则要换行。这样做都是为了提高代码的可读性,保留边空,方便打印,保持代码的清晰,美观和层次感。
代码需要拷贝时,注意修改相应的注释,以免产生错误的指导,导致理解上的错误。
避免使用产生任何latch。产生latch的情况有:组合逻辑中if语句缺乏else字句,case语句中各个条件所处理的变量不同;避免方法是:对所有输入条件都给出输出,在最终优先级的出发上使用else语句而不用else if。
1.对于组合模块,敏感表中必须包含被always所利用的所有信号,这通常意味着所有出现的赋值语句右边和条件表述式中的信号,可使用alwys @ *;
2.对于时序模块,敏感表必须包含时钟、异步复位信号;
3.确保敏感列表中不包含不必要的信息,否则会降低仿真性能;
对所有模块输出加一级寄存器。这样做使输出驱动强度和输入延迟可预测,使得模块的综合过程更简单。
用括号来表示执行的优先级,而不是依靠操作符本身的优先顺序;
有优先级的建议使用if语句,case语句用于描述平行逻辑,即须确保不同的条件是互斥的;
赋值或者条件判断时要注明比特宽度,注意表达式的位宽匹配;
禁止组合换(就是没有寄存器的反馈环路。这种结构在仿真和综合的时候都会有问题)组合环非常难以测试,因为他很难设计成一个已知的状态;
1.时序逻辑使用非阻塞赋值;
2.Latch使用非阻塞赋值;
3.组合逻辑使用阻塞赋值;
4.同一个always模块不允许同时有阻塞赋值和非阻塞赋值;
5.不要在不同的always中对同一变量赋值;
6.如果要用always语句同时进行时序和组合逻辑建模时,一定使用非阻塞赋值;
禁止使用内部三态电路,建议用多路选择电路代替内部三态电路;
RTL级代码禁止使用task。原因是task根据调用的情况不同,可综合成组合逻辑电路,也可综合成时序逻辑电路,增加了电路的歧义性;
1.case语句中如果不需要优先级,那么必须确保不同的条件是互斥的;
2.case语句中必须覆盖所有的条件,不管是指定还是用default语句,如果可能的话在default语句中吧x值赋给输出;
3.在每个case语句中都要使用begin/end结构,并使用缩格;
对于数值一律指定进制和宽度,否者默认数值宽度是32bit,会导致比预想的大得多的算数单元;
显示表明判决条件。
示例:
不好的编码:
if(signal_ctl)
……
好的编码:
if(signal_ctl == 1’b1)
……
关键路径和非关键路径逻辑放在不同模块;
尽可能在整个设计中只使用一个主时钟,同时只使用同一个时钟沿,主时钟走全局时钟网络,跨时钟域信号使用两级reg去除亚稳态。这样可以让综合器综合出更优的结果。
1.时钟信号选用全局时钟缓冲器BUFG;
2.避免使用门控时钟;
3.避免使用内部产生时钟信号;
4.不要用时钟或复位信号作数据或使能信号,也不能用数据信号作为时钟或复位信号;
5.时钟信号必须连接到全局时钟管脚上,禁止用计数器分频后的信号做其他模块的时钟,而要用改成时钟使能的方式,否则这种时钟满天飞的方式对设计的可靠性极为不利,也大大增加了静态时序分析的复杂性;
1.复位信号采用异步低电平有效信号,外部复位信号连接到芯片全局复位输入端(这些管脚提供较低的抖动),复位使初始化状态可预测,防止出现禁用状态,为避免释放复位时和时钟冲突,复位信号要同步化;
2.确保所有寄存器只被简单的复位信号控制,尽可能避免内部产生的条件复位信号,建议模块内部所有寄存器应在同一时间内被复位;
3.复位的条件表达式及命名要和always敏感列表中的描述相统一。
在设计中避免实例化具体的门级电路。门级电路可读性差,且难于理解和维护,如果使用特定工艺的门电路,设计将变得不可移植,如果必须实例化门电路,建议采用独立于工艺库的门电路。
使用函数
如果同一段代码需要重复多次,尽可能使用函数,以避免冗长的逻辑和子表达式。如果有可能,可以将函数通用化,以使得它可以复用。注意,内部函数的定义一般要添加注释,这样可以提高代码的可读性;
状态定义用parameter定义,不要使用define宏定义的方式。
define宏定义在编译时自动替换整个设计中所定义的宏,而parameter仅仅定义模块内部的参数,定义的参数不会与模块外的其他状态机混淆。
使用独热码来定义状态机状态。
使有限状态机FSM保持在层次中的自己所在的那一级,不允许综合工具在输出和下一个状态译码逻辑之间共享资源。
必须包含对所有状态都处理,不能出现无法处理的状态使状态机失控。
状态机要写成三段式的,第一个always块,描述对应当前状态的状态寄存器,非阻塞赋值;第二个always块,描述下一状态的状态寄存器,阻塞赋值;第三个always块,描述输出,阻塞赋值;
示例:
第一段:我要去哪儿?(时序电路,由时钟触发)
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
current_state <= IDLE; // 复位后状态机处于空闲态
end
else begin
current_state <= next_state; // 更新状态
end
end
第二段:我怎样去?(组合电路,条件符合立刻就变)
always @(*) begin
if("进入状态的条件") begin
next_state = "想要进入的状态";
end
else if("进入状态的条件") begin
next_state = "想要进入的状态";
end
......
......
......
end
第三段:去了干啥?(时序电路,时钟触发)
al
ways @(posedge clk or negedge rst_n) begin if(!rst_n) begin "复位状态机中使到的变量(寄存器)"; end else begin case(current_state) "状态1": begin "要干的事"; ...... end "状态2": begin "要干的事"; ...... end ...... ...... ...... default: begin "默认情况下要干的事"; end endcase end end
一个完备的状态机应该具备初始化(reset)状态和默认(default)状态。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。