Description
本文主要是收集一些重要的Verilog coding style。一个好的coding style可以减少错误的发生,增加电路的效能,以及较好的可读性。
Text
The order of module signals
一个module signal顺序如下 (由左至右):
Input
clock signals(clk_*)
set/reset signals(set_*, rst_*)
enable/disble signals(en_*, dis_*)
read/write enable signals(we_*, re_*, rw_*)
control signals(i_*)
address signals(i_*)
data signals(i_*)
Output
clock signals(o_clk_*)
set/reset signals(o_set_*, o_rst_*)
enable/disable signals(o_en_*, o_dis_*)
control signals(o_*)
address signals(o_*)
data signals(o_*)
In/Out
control signals(io_*)
address signals(io_*)
data signals(io_*)
Naming Rule
以下的namign rule为个人使用的规则。
命名方式分类
底线分隔型:xxx_yyy_zzz
大写底线分隔型:XXX_YYY_ZZZ
首字大写型:AbcDefGhi
首字小写型:avcDefGhi
各种元素所使用的命名
文件名称:底线分隔型, Ex: xxx_yyy_zzz.v
module名称:底线分隔型, Ex: xxx_yyy_zzz
module instance名称:底线分隔型, Ex: xxx_yyy_zzz
local wire名称:底线分隔型:Ex: xxx_yyy_zzz
local reg名称:底线分隔型, Ex: xxx_yyy_zzz
input signal名称:前置i_的底线分隔型, Ex: i_xxx_yyy_zzz
output signal名称:前置o_的底线分隔型, Ex: o_xxx_yyy_zzz
input/output signal名称:前置io_的底线分隔型, Ex: io_xxx_yyy_zzz
常数名称:大写底线分隔型, `XXX_YYY_ZZZ
parameter参数名称:大写底线分隔型, Ex: XXX_YYY_ZZZ
block名称:大写底线分隔型, Ex: XXX_YYY_ZZZ
特殊讯号名称
单一的clock signal: clk
多个clock signal: clk_xxx
负缘触发的clock signal: clk_n, clk_xxx_n
单一的reset signal: rst
多个reset signal: rst_xxx
负缘触发的reset signal: rst_n , rst_xxx_n
单一的set signal: set
多个set signals: set_xxx
负缘触发的set signals: set_n, set_xxx_n
致能讯号: en_xxx
除能讯号: dis_xxx
Procedural Assignments
使用指引
在撰写sequential logic时,使用nonblocking assignment。
在撰写latches电路时,使用nonblocking assignment。
在always block中撰写conbinational logic时,使用blocking assignment。
在同一个always block中同时撰写sequential及combinational logic时,一律使用nonblocking assignment。
别在同一个always block中混合使用nonblocking及blocking assignment。
别在两个以上的always block中对同一个变量设定数值。
使用$strobe来显示由nonblocking assignment所给定的变量。
不要对assignment使用#0延迟设定。
在procedural assignment中,LHS一定要是reg型态。非procedural assignment一定是net的型态。
常用的style
Combination logic
在always block中,如果使用combination logic,应当使用blocking assignment.
// All inputs used within always block should be listed in sensitive list.
always @(in1 or in2 or in3)
begin
xxx = in1 ^ in2 & in3;
end
Sequential logic
在always block中,如果使用sequential logic,应当使用nonblocking assignment.
// Synchronous reset. The rst_n is NOT in sensitive list.
always @(posedge clk)
begin
if (~rst_n)
begin
xxx <= `INIT_VAL;
end
else
begin
xxx <= yyy;
end
end
// Asynchronous reset. The rst_n IS in sensitive list.
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
begin
xxx <= `INIT_VAL;
end
else
begin
xxx <= yyy;
end
end
Delay的建模
Combination logic
建模没有delay时,使用blocking assignment(ex: a = b;)
建模有惯性(inertial) delay时(即glitch不会传到后面的电路中)。使用delayed evaluation blocking assignments(#10 a = b;).
建模传输(transport)delay时(即glitch也会一并传到后面的电路中)。使用delayed assignment nonblocking assignments(ex: a <= #10 b;).
Sequential logic
建模没有delay时,使用non-blocking assignments (ex: q <= d; ).
建模有delay时,使用delayed assignment nonblocking assignments(ex: q <= #10 d;).
Finite State Machine
Moore FSM: 输出与输入没有直接关系。
由两个always block构成,一个是sequential block用来处理状态的变化。另一个为combinational block用来处理状态与输入之间的关系。注意,在sequential block中应全部使用nonblocking assignment。在combinational block中应使用blocking assignment。在某些简单的case中,combinational block也可直接由continuous assignment来取代。下面的范例是一般简单的FSM。
// Sequential always block.
always @(posedge clk or posedge rst)
begin
if (rst)
state <= STATE_IDLE;
else
state <= next;
end
// Combinational always block.
always @(state or input1 or input2 ... or inputN)
begin
next = STATE_IDLE;
outputs = OUTPUT_IDLE:
case (state)
STATE_IDLE:
begin
... // the logic to determine the next state.
next = STATE_?????;
end
STATE_?????:
begin
... // the logic to determine the next state.
next = STATE_?????;
outputs = ?????; // The output of this state.
end
endcase
end
针对simplified one-hot encoding的FSM范例:
// Sequential always block.
always @(posedge clk or posedge rst)
begin
if (rst)
state <= n'b0;
state[STATE_DEFAULT] <= 1'b0;
else
state <= next;
end
// Combinational always block.
always @(state or input1 or input2 ... or inputN)
begin
next = n'b0;
outputs = OUTPUT_DEFAULT:
case (1'b1) // synopsys full_case parallel_case
state[STATE_DEFAULT]:
begin
... // the logic to determine the next state.
next[STATE_?????] = 1'b1;
end
state[STATE_?????]:
begin
... // the logic to determine the next state.
next[STATE_?????] = 1'b1;
outputs = ?????; // The output of this state.
end
// synopsys translate_off
default:
$display("Bad state!!");
// synopsys translate_on
endcase
end
针对simplified one-hot with zero-idle encoding的FSM:
// Sequential always block.
always @(posedge clk or posedge rst)
begin
if (rst)
state <= n'b0;
else
state <= next;
end
// Combinational always block.
always @(state or input1 or input2 ... or inputN)
begin
next = n'b0;
outputs = OUTPUT_DEFAULT:
case (1'b1) // synopsys full_case parallel_case
~|state: // IDLE
begin
... // the logic to determine the next state.
next[STATE_?????] = 1'b1;
end
state[STATE_?????]:
begin
... // the logic to determine the next state.
next[STATE_?????] = 1'b1;
outputs = ?????; // The output of this state.
end
// synopsys translate_off
default:
$display("Bad state!!");
// synopsys translate_on
endcase
end
Mealy FSM: 输出与输入有直接关系。
Mealy FSM的作法与上面的范例相类似。唯一的不同在于outputs的指定,需加上与input相关的逻辑判断。例如:
case(state) // synopsys parallel_case full_case
...
STATE_?????:
begin
...
if (input1 & input2)
outputs = ?????;
else
outputs = ?????;
end
// synopsys translate_off
default:
$display("Bad FSM.");
// synopsys translate_on
default
endcase
Datapath
参考:Coding Guidelines for Datapath Synthesis.
有号数的计算:若有需要关于有号数的计算,应当利用Verilog 2001所提供的signed及$signed()机制。
input signed [7:0] a, b;
output signed [15:0] o;
assign o = a * b;
or
input [7:0] a, b;
output [15:0] o;
wire signed [15:0] o_sgn;
assugb o_sgn = $signed(a) * $signed(b);
assign o = $unsigned(o_sgn);
正负号的扩展:应多加利用Verilog的implicity signed extension,避免手动进行转换。
input signed [7:0] a, b;
input signed [8:0] o;
assign o = a + b; // Verilog会自动进行符号的扩展。
有号数与无号数的混合计算:不要在同一个verilog叙述中进行有号数与无号数的计算。应该要分成个别独立的叙述。在一个verilog叙述中只要有一个无号数的操作数,整个算式将被当成无号数进行计算。
input [7:0] a;
input signed [7:0] b;
output signed [15:0] o;
// Don't do this: assign o = a * b;
// The $signed({1'b0, a}) can convert the unsigned number to signed number.
assign o = $signed({1'b0, a}) * b;
input signed [7:0] a;
output signed [15:0] o;
// Don't do this: assign o = a * 8'b10111111;
// Use $signed() system task
assign o = a * $signed(8'b10111111);
// or sb keyword.
assign o = a * 8'sb10111111;
part-select运算过后的操作数是无号数。就算是选择的范围包含整个register或wire。
input signed [7:0] a;
input signed [7:0] b;
output signed [15:0] o1, o2;
// Don't do this: assign o1 = a[7:0];
assign o1 = a;
// Don't do this: assign o2 = a[6:0] * b;
assign o2 = $signed(a[6:0]) + b;
Verilog的位宽度规则:技巧就是要善用LHS来限制位宽度。利用中介的讯号线来作为限制宽度用的LHS操作数。
在没有特别设定的状况下,Verilog会依据LHS的操作数宽度来决定RHS操作数的宽度。
input [7:0] a;
input [7:0] b;
output [8:0] o;
assign o = a + b; // 9 bits.
对一个表示式而言,最大宽度的操作数决定了整体的宽度。
input signed [3:0] a;
input signed [7:0] b;
output [11:0] o;
wire signed [11:0] o_sgn;
// Don't do this: assign o = $unsigned(a * b); 这将会是一个8 bit的运算。因为在刮号内的bits数是依据最大操作数b的宽度决定的。
assign o_sgn = a * b; // 12 bits。因为bit数是依据LHS宽度决定的。
assgign o = $unsigned(o_sgn);
input [7:0] a;
input [7:0] b;
input [7:0] c;
input [7:0] d;
output o;
wire [15:0] tmp1;
wire [15:0] tmp2;
// Don't do this: assign o = (a + b) > (c * d); 因为(a + b)及(c * d)都会是8 bit的结果。
assign tmp1 = a + b; // 16 bits.
assign tmp2 = c * d; // 16 bits.
assign o = tmp1 > tmp2; // 1 bit.
一. 文件命名
规则 1: 每个文件中只包含一个设计单元
理由: 便于修正.
规则 2: 文件命名协定
<设计单元名称>.<扩展名>
理由: 便于理解设计单元constructs及文件内容.
如: spooler.v // spooler模块的同步Verilog代码描述
规则 3. 模拟和数字Verilog文件
每个单一文件必须包含: (1)模拟(analog)Verilog(用.va文件后缀); 或(2)数字Verilog(用.v文件后缀); 或(3)清晰的模\数混合Verilog(用.va文件后缀).
理由: 模拟的编译器也许不能操纵数字的架构; 反之亦然.
二. HDL编码项目命名
规则 4: 允许的字符集
命名中必须包含字母, 数字或下划线[A-Z, a-z, _] (见 规则5)
例外: 不允许使用连续的下划线.
理由: 双下划线在硬件竞争中是不工作的.
规则 5: 命名的首字符
命名必须以字母开头, 而不是数字或下划线 (见 规则4)
理由: 以数字或下划线开头的命名, 可能会引起工具冲突.
规则 6: 所有命名必须是唯一的无关项
理由: 在Verilog(语法敏感)和VHDL(语法不敏感)中的设计转换中, 很可能受语法不敏感的设计风格影响.
规则 7: 信号的一致性
理由: 和VHDL相比, Verilog和许多支持VHDL的工具都一样是语法敏感. 及时辨别信号的类型(如: 低电平有效的信号或时钟)有助于调试.
规则 8: 常量, 参数和标签区块要大写
理由: 提供了一种可以辨别那些在仿真中不需要经过数据转换的实体的机制.
规则 9: 信号, constructs和实例化标签要小写
理由: 从在仿真中数据不变化的实体中区别信号和constructs, 以及在设计中保持一致的视觉和感觉.
规则 10: 有意义的信号和变量名称
小写的名称必须包含信号和变量的意图.
理由: 描述是什么, 而不是怎样去做, 有助于理解设计.
如: data_bus, set_priority
规则 11: 有意义的常量名称
常量名称一定要描述常量的意图. 根据意图可以很明显地看出常量的类型, 及不是端口的名称. 常量需大写.
理由: 有意义的名称对于常量来说, 非常重要.
如: 好的名称: SBUS_DATA_BITS, MEMORY_WIDTH, CLK_PERIOD
如: 差的名称: ADDRESS_SIZE 并不明晰, 当它指的是数的位宽或地址空间的长度
规则 12: 有意义的construct名称
construct的名称如functions, modules, tasks等,必须根据它们要做什么而不是怎么样去做来命名. construct名称必须小写.
理由: 描述是什么, 而不是怎样去做, 有助于理解设计.
规则 13: 有意义的实例化标签
实例化标签要根据construct指定的要实现什么, 而不是怎样去做来命名.
理由: 描述是什么, 而不是怎样去做, 有助于理解设计.
如: addr_decode, bit_stuff, sbus_if
规则 14: 用下划线分隔包含许多单词的名称
理由: 增加可读性
如: ram_addr
规则 15: 低电平有效信号命名使用 _b
理由: 有意义, 一致性的名称, 有助于理解设计.
如: enable_data_b, reset_b
规则 16: 时钟信号命名使用 _clk
理由: 有意义, 一致性的名称, 有助于理解设计.
如: fifo_transmit_clk
例外: 明显包含时钟的信号名称(如: system_clock, clk32m).
规则 17: 未连接(Unconnected)的输出信号命名使用 _uc
理由: 当关于未连接信号的警告出现时, 如果该名称以_nc结尾, 这样该信号就会很明显地被知道是未连接, 而不是存在的错误.
规则 18: 信号捆绑(bundling)
不相干的信号不能被捆绑成总线.
理由: 易于理解module.
方针 19: 三态信号命名使用 _z
理由: 有意义, 一致性的名称, 有助于理解设计.
如: ram_data1_z
方针 20: 状态机信号命名使用 _next
理由: 有意义, 一致性的名称, 有助于理解设计.
如: transmit_next
方针 21: 测试模块信号命名使用 _test
理由: 有意义, 一致性的名称, 有助于理解设计.
如: parallel_clk_test
方针 22: 扫描(scan)使能(enable)信号命名使用 _se
理由: 有意义, 一致性的名称, 有助于理解设计.
方针 23: 模拟信号命名使用 _ana
理由: 当区别模拟信号时, 有助于理解设计. 尤其是当使用图像查看器时, 更加有用.
方针 24: 信号名称的多后缀优先性
从高到低:
1. _b
2. _z
3. _clk
4. _next
最高优先级的后缀被推荐为信号名称的最靠后的后缀.
如: ram_data1_z_b, receive_clk_b
方针 25: 参数化变量命名使用 _PP
理由: 有意义, 一致性的名称, 有助于理解设计.
如: NUM_COLUMNS_PP
方针 26: 信号名称的长度应该小于28个字符
短的名称可读性较强. 但28个字符不包括层次(hierarchy).
理由: 长的名称可能会引起工具的冲突.
方针 27: 避免缩略语除了常用已知的缩略语
理由: 使用有意义的名称.
例外: 一般所知的缩略语, 如RAM,和loop counters. loop counters可能被命名为单个字母, 如I或N, 因为它们代表索引. 一些后端工具会连接所有层次的名称, 以及给予有限的名称总长度. 在那种情况下, 缩略语是多层次名称所必须的. 但这些缩略语必须以注释的方式来阐明.
方针 28: 文档和附加命名协议
任何在module中使用的缩略语要记录在档案中是被推荐的. 任何在module中使用的协议都要添加到必要的协议中, 或推荐的SRS, 然后记录到档案中.
理由: 对于原始设计者来说,很明显的缩略语, 当再次被使用到时, 有可能不再那么清晰.
方针 29: 层次间名称的一致性
层次间或整个IP中, 信号或设计单元的名称应该保持一致.推荐关联多个实例化的名称因该有名称索引.
理由: 增加可读性, 消除歧义, 在综合中避免缓冲器置入.
方针 30: Verilog名称应该与档案中的名称相同
理由: 便于理解HDL模块.
注: 选译Motorala的Verilog HDL Coding - Semiconductor Reuse Standard
Verilog代码编写风格
一. 强调Verilog代码编写风格的必要性。
强调Verilog代码编写规范,经常是一个不太受欢迎的话题,但却是非常有必要的。
每个代码编写者都有自己的编写习惯,而且都喜欢按照自己的习惯去编写代码。与自己编写风格相近的代码,阅读起来容易接受和理解。相反和自己编写风格差别较大的代码,阅读和接受起来就困难一些。
曾有编程大师总结说,一个优秀的程序员,能维护的代码长度大约在1万行数量级。代码的整洁程度,很大程度上影响着代码的维护难度。
遵循代码编写规范书写的代码,很容易阅读、理解、维护、修改、跟踪调试、整理文档。相反代码编写风格随意的代码,通常晦涩、凌乱,会给开发者本人的调试、修改工作带来困难,也会给合作者带来很大麻烦。
(实际上英文Coding Style有另一层涵义,更偏重的是,某一个电路,用那一种形式的语言描述,才能将电路描述得更准确,综合以后产生的电路更合理。本文更偏重的是,编写Verilog代码时的书写习惯。)
二. 强调编写规范的宗旨。
缩小篇幅
提高整洁度
便于跟踪、分析、调试
增强可读性,帮助阅读者理解
便于整理文档
便于交流合作
三. 变量及信号命名规范。
1. 系统级信号的命名。
系统级信号指复位信号,置位信号,时钟信号等需要输送到各个模块的全局信号;系统信号以字符串Sys开头。
2. 低电平有效的信号后一律加下划线和字母n。如:SysRst_n;FifoFull_n;
3. 经过锁存器锁存后的信号,后加下划线和字母r,与锁存前的信号区别。如CpuRamRd信号,经锁存后应命名为CpuRamRd_r。
低电平有效的信号经过锁存器锁存后,其命名应在_n后加r。如CpuRamRd_n信号,经锁存后应命名为CpuRamRd_nr
多级锁存的信号,可多加r以标明。如CpuRamRd信号,经两级触发器锁存后,应命名为CpuRamRd_rr。
4. 模块的命名。
在系统设计阶段应该为每个模块进行命名。命名的方法是,将模块英文名称的各个单词首字母组合起来,形成3到5个字符的缩写。若模块的英文名只有一个单词,可取该单词的前3个字母。各模块的命名以3个字母为宜。例如:
Arithmatic Logical Unit模块,命名为ALU。
Data Memory Interface模块,命名为DMI。
Decoder模块,命名为DEC。
5. 模块之间的接口信号的命名。
所有变量命名分为两个部分,第一部分表明数据方向,其中数据发出方在前,数据接收方在后,第二部分为数据名称。
两部分之间用下划线隔离开。
第一部分全部大写,第二部分所有具有明确意义的英文名全部拼写或缩写的第一个字母大写,其余部分小写。
举例:CPUMMU_WrReq,下划线左边是第一部分,代表数据方向是从CPU模块发向存储器管理单元模块(MMU)。下划线右边Wr为Write的缩写,Req是Request的缩写。两个缩写的第一个字母都大写,便于理解。整个变量连起来的意思就是CPU发送给MMU的写请求信号。
模块上下层次间信号的命名也遵循本规定。
若某个信号从一个模块传递到多个模块,其命名应视信号的主要路径而定。
6. 模块内部信号:
模块内部的信号由几个单词连接而成,缩写要求能基本表明本单词的含义;
单词除常用的缩写方法外(如:Clock->Clk, Write->Wr, Read->Rd等),一律取该单词的前几个字母( 如:Frequency->Freq, Variable->Var 等);
每个缩写单词的第一个字母大写;
若遇两个大写字母相邻,中间添加一个下划线(如DivN_Cntr);
举例:SdramWrEn_n;FlashAddrLatchEn;
四. 编码格式规范。
1. 分节书写,各节之间加1到多行空格。如每个always,initial语句都是一节。每节基本上完成一个特定的功能,即用于描述某几个信号的产生。在每节之前有几行注释对该节代码加以描述,至少列出本节中描述的信号的含义。
2. 行首不要使用空格来对齐,而是用Tab键,Tab键的宽度设为4个字符宽度。行尾不要有多余的空格。
3. 注释。
使用//进行的注释行以分号结束;
使用/* */进行的注释,/*和*/各占用一行,并且顶头;
例:
// Edge detector used to synchronize the input signal;
4. 空格的使用:
不同变量,以及变量与符号、变量与括号之间都应当保留一个空格。
Verilog关键字与其它任何字符串之间都应当保留一个空格。如:
Always @ (……)
使用大括号和小括号时,前括号的后边和后括号的前边应当留有一个空格。
逻辑运算符、算术运算符、比较运算符等运算符的两侧各留一个空格,与变量分隔开来;单操作数运算符例外,直接位于操作数前,不使用空格。
使用//进行的注释,在//后应当有一个空格;注释行的末尾不要有多余的空格。
例:
assign SramAddrBus = { AddrBus[31:24], AddrBus[7:0] };
assign DivCntr[3:0] = DivCntr[3:0] + 4’b0001;
assign Result = ~Operand;
5. 同一个层次的所有语句左端对齐;Initial、always等语句块的begin关键词跟在本行的末尾,相应的end关键词与Initial、always对齐;这样做的好处是避免因begin独占一行而造成行数太多;
例:
always @ ( posedge SysClk or negedge SysRst begin
if( !SysRst DataOut <= 4'b0000;
else if( LdEn begin
DataOut <= DataIn;
End
else DataOut <= DataOut + 4'b0001;
end
6. 不同层次之间的语句使用Tab键进行缩进,每加深一层缩进一个Tab;
8. 在endmodule,endtask,endcase等标记一个代码块结束的关键词后面要加上一行注释说明这个代码块的名称;
9. 在task名称前加tsk以示标记。在function的名称前加func以示标记。例如:
task tskResetSystem;
……
endtask //of tskResetSystem
五.小结:
以上列出的代码编写规范无法覆盖代码编写的方方面面,还有很多细节问题,需要在实际编写过程中加以考虑。并且有些规定也不是绝对的,需要灵活处理。并不是律条,但是在一个项目组内部、一个项目的进程中,应该有一套类似的代码编写规范来作为约束。
总的方向是,努力写整洁、可读性好的代码。