当前位置 博文首页 > zhoujianjayj的博客:Verilog代码规范(一)

    zhoujianjayj的博客:Verilog代码规范(一)

    作者:[db:作者] 时间:2021-08-30 22:26

    入行挺久的,但是一直没有写过一些技术文档之类的文章。几年前有过类似的想法,但是觉得自己道行太浅,难免班门弄斧。

    最近因为某些原因在学习和整理一些内容,也顺便发出来和大家一起交流。希望可以和大家一起学习,大家也可以提供宝贵的意见。

    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    长长的分割线,下面是正文

    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    代码规范(一)

    代码规范很多都是team的统一要求,有些并非是强制,team内遵循某个约定,以方便阅读、管理。

    代码规范的好处:

    1. 方便他人阅读,交流和理解;以及后期的继承和维护;
    2. 方便自己阅读,修改和管理;
    3. 有效减少从设计,仿真和综合的迭代时间和迭代次数;
    4. 良好的代码风格和代码规范,让design变得愉快,相反,会很痛苦;(亲生子和继子)

    一、信号命名

    1. 信号命名常用前后缀

    • 采用有意义且有效的名字,尽量采用连贯的缩写,比如:address →addr; clock→clk;
    • 长信号名用下划线“_”分割单词;
    • 特殊信号加后缀表示其特性,如:低电平有效信号: _n;时钟信号:_clk;输入信号:i_或者_i ; 输出信号:o_或者_o;
    • 多级寄存器的信号(同一时钟域),第一级以“_d1”结尾,后面依次“_d2”,"_d3",其他类型的信号禁止使用该后缀命名;
    • 不建议使用reg作为信号结尾;
    • 不使用vdd, vss, vcc, vref开头为普通信号命名;

    2. define和parameter定义的常量和参数使用大写;

    3. 所有的模块名,信号名,变量名和端口名**小写;**但类似于FlopClockConstant,以单词开头大写的命名规则可以参考;

    ?

    二、模块格式

    1. 文件名与模块名

    1. 文件名为"<模块名>.v“,即文件名与设计单元(模块名)相同,都用小写;(强制)

    2. 顶层文件要加上_top后缀。而testbench文件则加上_tb后缀;

    ?

    2. 模块例化

    1. 使用u_xxx或者xxx_inst,多次例化可以使用xxx_inst0,等

    module counter (mclk,rst_n,cnt);
    	  input           mclk;
    	  input           rst_n;
    	  output [7:0]    cnt;
    
        counter u_counter0();
        counter counter_inst0(); 
    
    

    2. 元件连接使用端口名连接,不使用端口顺序连接;(强制)

    //端口名连接
    counter8 counter8_inst(
    	.clk(ref_clk),
    	.rst_n(rst_n),
    	.carry_in(ci),
    	.carry_out(ci_o),
    	.dout(data_out)
    	);
    
    //端口顺序连接,不推荐使用,不便理解,容易犯错,非常不便于维护;
    counter8 counter8_inst(ref_clk,rst_n,ci,ci_o,data_out);
    

    3. 模块的参数例化也需要列写完整;(强制)

    ?

    3. 缩进与对齐

    1. 两个空格或者四个空格;不建议使用tab键进行缩进,因为不同的vim设置,tab所代表的space值不同,有的tab=4space,有的tab=2space

    个人做法:在vim设置中加上如下设置,则tab自动转换成4个space;

    set expandtab  #将tab替换成space
    set tabstop=4  #将tab设置成4个space
    

    ?2. 对齐有利于修改,维护,阅读和理解:

    (1)有利于修改:如果现在需要例化这个模块,并且端口完全上浮上去。那么对于端口对齐的模块的端口信息转换成模块例化就非常简单,只需要进行简单的复制粘贴即可,如下代码段;

    input               clk_csr  ;
    input               rst_csr_n;
    input               clk_rx   ;
    input               rst_rx_n ;
    input               test_mode;

    相反对于第二个模块的端口信息,想要做成模块例化就很难,如下的代码;

    output pread;
    output [31:0] paddr;
    output pwrite;
    output [31:0] pecsr_wdata;
    output [3:0] pbyte_en;

    ?(2)?阅读和理解:缩进便于理解代码层次,如下代码段所示:

    always @(posedge clk_rx or negedge rst_rx_n)
    begin
        if(~rst_rx_n) begin
            o_rx_bypass <= 1'b0;
        end
        else begin
        case (o_rx_bypass)
            2'b00:begin
                o_rx_bypass <= o_rx_bypass;
            end
            2'b01:begin
                if(en) beign
                    o_rx_bypass <= o_rx_bypass_sync;
                end
                else begin
                    o_rx_bypass <= o_rx_bypass;
                end    
            end
            2'b10:begin
                o_rx_bypass <= o_rx_bypass;
            end
            2'b11:begin
                o_rx_bypass <= o_rx_bypass;
            end
            default:;
        endcase
        end
    end
    

    反例,如果是if else的嵌套结构,则不缩进更加没法看;(这个例子比较好的一点是其end后面加了很多注释,有利于理解if...else的结构)

    always @(*)
    begin
    case(valid)
    2'b00:
    begin if(flag) valid_data = data[0]; 
    else valid_data = 1'b0;
    end
    2'b01: begin if(flag) valid_data = data[1]; 
    else valid_data = 1'b0;
    end
    2'b10: begin if(flag) valid_data = data[2]; 
    else valid_data = 1'b0;
    end
    2'b11: begin if(flag) valid_data = data[3]; 
    else valid_data = 1'b0;
    end
    defalut: valid_data = 1'b0; 
    endcase
    end
    

    4. 关于注释

    为什么要注释?别人好阅读+自己好阅读

    1. 一定要写好注释,注意逻辑分段等,方便后期阅读;常见的注释有:

    (1) 文件头说明,包括:版权声明,作者信息说明,版本号,日期,修改记录,模块功能(越详细越好,甚至可以放上时序图,状态机),等等;

    (2) 信号说明,包括信号分段描述,信号基本功能描述(如复位低电平)等;

    信号基本功能描述有如:

    • 复位电平是高复位还是低复位;
    • disable/*_en, 其0或1具体代表说什么含义;
    • 时钟频率是多少?
    • 多bit,如type[1:0],mode[1:0],建议说明每个type或者mode的具体含义;
    • 信号的来源模块或者去到哪个模块(from/to)便于顶层连接和维护

    (3) 代码分段说明,包括代码块起点/结束描述,代码段基本功能描述等;

    • 功能描述可以便于理解代码块功能;
    • 起点和结束点描述可以准确定位代码的开始结束,便于阅读和理解;
    • 好的代码分段说明,可以有利于开发的思路分析;

    (4) 尽量去掉确实不需要的注释代码,过多冗余代码不利于阅读

    ?

    5. 代码块分段

    一般的代码结构:

    1. 文件头声明

    • 模块声明module前如果有下面的语句详情参考上述注释中文件头相关的内容
    • 关于`default_nettype的使用:模块声明module前如果有`default_nettype none的声明,则务必在模块endmodule后面添加`default_nettype wire;

    2. 端口信号声明

    • 端口信号分段声明,如时钟复位端+tcsr信号段+bus信号段+memory信号段+intr信号+debug信号段等等;
    • 建议按照交互模块进行分类;不建议按照输入输出划分;

    3. 参数声明(parameter)

    • localparam:本模块使用,不可传入;可以避免外部修改或者覆盖本模块的参数设置;
    • parameter:可以从上层传递进来;如果不需要外部传递,不建议用paramter定义参数;

    4. 内部信号声明

    • 可以按照信号功能分段声明(参考端口信号声明建议);

    5. 逻辑块+例化模块

    两种方式比较建议:

    • 逻辑块按照逻辑块进行分类,例化模块再进行例化,类似于logic1+ logic2+logic3 + instance1+ instance2+instance3 ;
    • 逻辑块和例化一起进行,逻辑块和当前例化的模块相关,类似于logic1+instance1+logic2+instance2+logic3+instance3;

    ?

    6. 关于宏定义

    同一个宏定义需要在多个文件里调用的话,**建议使用调用文件。把”`define”定义放在一个单独的文件里面。**自己也不可以随便添加宏定义;

    (1)方便宏定义的统一管理和修改;

    (2)防止不同地方定义的宏的值不一样,可能会造成冲突和覆盖;

    (3)尽量避免使用不必要的宏定义;

    ?

    7. 关于dummy文件

    在做项目的时候,一个大的系统会被分割成很多细小的部分,由不同的人负责,设计完成后上传到具有版本管理功能的服务器上。

    有时候有的人忘记在上传代码之前进行严格测试,或者根本传错了版本,就会造成其他人仿真报错。

    最好的办法就是将这些有问题的模块临时替换成dummy模块。

    dummy模块不仅可以隔离问题模块,还可以显著加速仿真过程,可谓一举两得。一般有两种方式传统上大家在完成设计之后会另外建立一个只有接口代码的空文件,例如dummy_sync_fifo.v,当需要将sync_fifo变成dummy的时候,就将文件清单中的文件名改掉,但这样的方式会增加文件,容易造成管理的混乱,反复修改文件清单显然也不是一个好的做法。

    推荐的方式是在模块的顶层文件中写一个宏控制的综合控制逻辑,当DUMMY_SYNC_FIFO宏被定义的时候,综合工具就只会将整个模块综合成没有任何逻辑的dummy模块了。

    cs