RTL Coding Style

标准的文件头

在每一个版块的开头一定要使用统一的文件头,其中包括作者名,模块名,创建日期,概要,更改记录,版权等必要信息。

建议使用以下的文件头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// **************************************************************
// COPYRIGHT(c) XDU ImgLab
// All rights reserved.
//
// Module name : Module_name of this file just as “tx_fifo”
// Full name : Full English name of this abbreviation
//
// Author : Athor/ID
// Email : Author’s email
// Abstract : Describe the function of the module briefly
// Character Set:  GB-2312
//
// Modification history
// ------------------------------------------------------------------------------------------------------
// Date       By        Version       Change Description            
// ----------------------------------------------------------------- 
// 22/10/10   David     v0.1          first try 
// *********************************************************************

标准的module格式

对于模块的书写采用统一的格式便于项目内部成员的理解和维护,其内容解释如下:

  • 端口声明时,定义顺序以功能区分,不以方向区分。时钟和复位信号端口必须定义在其他信号之前。
  • 端口定义必须采用ANSI风格(在模块端口位置完全声明端口名称、类型和方向)
  • 模块名、模块例化名统一,例化名前加小写u_以区分 ( 多次例化另加标识 ),三者关系:
    • 文件名 :xxx .v (小写)
    • 模块名 :xxx (小写)
    • 例化名 :u_xxx
  • 例化模块时,每行只例化一个端口,且不能使用表达式,所有输入端口都必须有驱动,未使用的输出接口必须例化,但可以不连接。
  • 消除顶层模块的胶连逻辑(集成各功能单元的top.v位置),各功能单元的top.v中允许存在合理的胶连逻辑。

一致的排版

一致的缩排

  • 统一的缩排取4个空格宽度(注意尽量别使用TAB键缩进,不同的IDE对TAB缩进的解释可能不同)

  • 输入输出信号的宽度定义与关键字之间,信号名与宽度之间要用空格分开;所有宽度定义对所有信号名对齐,代码风格统一如下:

    1
    2
    3
    4
    5
    6
    input   [3:0]    input_a   ;    // *****
    input input_b ; // *****

    output [127:0] output_a ;
    output [15:0] output_b ;
    output output_c ;

一致的begin end书写方式

always中,一定要用begin end区分,格式和代码风格统一如下:

1
2
3
4
5
6
7
8
9
10
always @ (postedge clk or negedge rst_n) begin
if (rst_n == 1'b0)
syn_rst<= 1'b0;
else begin
if (a == b)
syn_rst<= 1'b1;
else
syn_rst<= 1'b0;
end
end

if else中仅有一个语句行时,不要使用begin end;如果有多个语句行时,注意缩进四个空格。

一致的信号命名风格

全称 缩写 中文含义
acknowledge ack 应答
address addr(ad) 地址
arbiter arb 仲裁
check chk 校验,如CRC校验
clock clk 时钟
config cfg 配置信息
control ctrl 控制
count cnt 计数
data in din(di) 数据输入
data out dout(do) 数据输出
decode de 译码
decrease dec 减一
delay dly 延迟
disable dis 不使能
error err 错误(指示)
enable en 使能
frame frm
generate gen 生成,如CRC生成
grant gnt 申请通过
increase inc 加一
input in(i) 输入信号
length len (帧、包)长
output out(o) 输出信号
priority pri 优先级
pointer ptr 指针
rd enable ren 读使能
read rd 读(操作)
ready rdy 应答信号或准备好
receive rx (帧数据)接收
request req (服务、仲裁)请求
reset rst 复位信号
souce scr 源(端口)
ststistics stat 统计
timer tmr 定时器
temporary tmp 临时
transmit tx 发送(帧数据)相关
valid vld(v) 有效、校验正确
wr enable wen 写使能
write wr 写操作
  1. 端口、信号、变量名的所有字母小写;函数名、宏定义、参数定义用大写;
  2. 使用简称、缩略词(加上列表);
  3. 基于含义命名(避免以数字命名的简单做法),含义可分段(最多分三段),每一小段之间加下划线”_”,如tx_data_val;命名长度一般限制在20个字符以内;
  4. 低电平有效信号,加后缀”_n”,如 rst_n;
  5. 无条件寄存的寄存信号在原信号上加dly1、dly2… 如原信号 data_in,寄存一拍data_in_dly1,寄存两拍data_in_dly2;
  6. 不能用 ”reg”作为最后的后缀名,因为综合工具会给寄存器自动加上reg后缀, 如果命名里就用reg作为后缀名则扰乱了网表的可读性。

统一的表达式书写

括号的使用

如果一个表达式的分组情况不是很明显时,加上括号有助于理解。

例如下面的代码加上括号就清晰很多。

if (&a==1’b1&&!flag==1’b1||b==1’b1)

改为:

if ((&a==1’b1)&&(!flag==1’b1)||(b==1’b1))

适当的使用空格

一般表达式在运算符的两侧要各留出一个空格,但定义比较长的表达式,去掉优先级高的运算符前的空格,使其与运算对象紧连在一起,可以更清晰的显示表达式结构。

还是上面的例子

if ((&a==1’b1)&&(!flag==1’b1)||(b==1’b1))

改为

if ( (&a==1’b1) && (!flag==1’b1) || (b==1’b1) )

”<=”, ”=”,运算符前后都要加空格。

赋值要指明比特宽度

赋值或者条件判断时要注明比特宽度,注意表达式的位宽匹配。如:

reg [4:0] signal_a;

错误情况:

1
2
3
4
5
signal_a <= 5;

if(signal_a == 5)

signal_a <= signal_b[3:0]+4;

正确情况:

1
2
3
4
5
signal_a <= 5'd5;

if(signal_a == 5'd5)

signal_a <= {1'b0, signal_b[3:0]+5'd4;

因为工具默认是32位宽,如果不注明位宽,工具检查会报warning,而且这样增加了设计的严谨性。

(tips:参数化代码设计中可直接加上不指定位宽的常数)

书写规范

每行只写一条语句,便于后续验证中行覆盖率的检查。

统一的语句书写―条件判断结构书写方式

条件的完整性

if else搭配使用,对于缺省的条件要写”else;”;

if else的条件判别式要全面,比如”if(a == 1’b0)”;

case中的缺省条件要写”default”;

”if else”结构:适用于复杂条件判断的语句

对于复杂的条件判断,使用” ? : “如果不仔细分析条件的每一条路径,就让读代码的人搞不清它是到底要做什么。例如:

C = (!Ic&&!rc)?1'b0:(Ic?rc:Ic);

改为:

1
2
3
4
5
6
7
8
always @(Ic or rc) begin    //if else
if ( (Ic==0) && (rc==0) )
c = 1'b0;
else if (Ic==1)
c = rc;
else
c = Ic;
end

简单的条件判断,我们可以使用三目符;当涉及复杂的条件判断,使用IF-ELSE结构以获得清晰的结构便于理解和维护。

IF-ELSE结构 VS CASE结构

  • IF-ELSE综合的结果可能是与或非门,也可能是一组多路选择器;而CASE综合结果一般会是多路选择器,但对于可以优化的CASE综合工具会综合出更简单的结构。

  • 所有对于可以写出平行结构的条件,优先写成case结构,例如地址译码等。条件之间有重复和嵌套的情况则是写成IF-ELSE结构。

Finite State Machine

  • 不允许有模糊不清的状态机模式,所有的状态机必须清晰明了。

  • 要求将状态机的时序部分和组合逻辑部分分开,建议采用三段式结构。

  • 状态机的状态名必须使用参数编码,并以“ST_”做前缀命名,状态信号必须用fsm_<cs/ns/ls>命名。(注:cs为current state,ns为next state,ls为last state)

统一格式的always程序块的书写

always 中的变量的赋值方式―阻塞与非阻塞赋值

当进行时序逻辑建模时,always块中使用非阻塞赋值,“<=”;

参考如下代码:

1
2
3
4
5
6
always @(posedge clk or negedge rst_n) begin 
if (rst_n == 1'b0)
myreg <= 1'b0;
else
myreg <= 1'b1;
end

当进行组合逻辑建模时,always块中使用阻塞赋值,“=”;

1
2
3
4
5
6
7
8
9
always @(addr) begin
case (addr)
2'b00 : cs0_n = 1'b0;
2'b01 : cs0_n = 1'b1;
2'b10 : cs0_n = 1'b0;
2'b11 : cs0_n = 1'b1;
default: cs0_n = 1'b1;
endcase
end

always中变量赋值的唯一性

组合always块一定要注意敏感量列表中的触发项完整且不冗余;如果不是这样,综合的电路会与实际设计不符合,会报warning;这边建议使用always @(*)的写法描述组合always块。

不要再多个always模块中对同一个reg型变量进行赋值;

建议不要在一个always块里给多个变量赋值。如果将一组条件相同的变量写在一个always块中更有利于可读性的提高和功能的实现时候,可有例外情况,但请尽量多加注释,以增加可读性,并注意在组合always块中不要出现LATCH;

always复位的书写

异步复位和同步复位模块内要统一,异步复位的条件表达式及命名要和always敏感列表中的描述相统一,所有的复位有效电平必须统一。

合理的注释

  • 代码中建议采用英文作详细的注释;
  • 注释应该与代码一致,修改程序的时候一定要修改相应的注释;
  • 注释不应重复代码已经表明的内容,而是简明地点明程序的突出特征;
  • 注释应该提取程序的线索和关键词,它整合程序中分散的信息并它帮助理解程序中不能表明的部分。
  • 注释中可以加入TODO、FIXME等标签来提示代码中的待办事项;

重用化设计

层次结构与模块划分

  • 层次设计的原理以简单为主―尽量避免不必要的层次;层次结构设计得好,在综合中就不需要太多的优化过程;
  • 模块的划分根据层次设计来决定―模块化对于布线有很大帮助,模块化的设计中要尽量减少全局信号的使用;
  • 通用的部分尽量提取出来作为一个共用模块;

参数传递

  • 需要传递参数的模块,在多次例化的时候统一都传递参数,不要例化同一个模块,有的传参数,有的不传。
  • 大模块间信号加前缀:“模块A缩写”_“模块B缩写”表示模块A到模块B的信号。

模块划分的技巧

  • 将不同的时钟域分离开来;
  • 按照不同的设计目标划分成块,分块时应在数据流方向上进行切分;
  • 在同一模块中实现逻辑资源和算术资源的共享;