代码书写格式规范(未完待续)

为了使得代码格式更加的规范和方便阅读,对代码的格式也做一些要求:

这一章过于重要,以至于我一直都没有完成它,有太多的细节要思考和整理了

文件开头

最好使用Vivado生成文件,这样会在文件开头加上相应的注释信息,包括:模块名、项目名、日期、作者、功能解释、注释和版本等信息。下面是一个空白的例子(用Vivado新建工程会自动创建这个内容,同时填上相应的信息),不用Vivado的时候我的Vim也可以自动创建这段代码,看下面的动图演示:先输入title,然后按F12即可。

//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
//
// Create Date: 
// Design Name:
// Module Name: 
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////

title_snippets

下面是我之前写的一个模块的文件头,作为示例。

//////////////////////////////////////////////////////////////////////////////////
// Company: USTC
// Engineer: Yu Wang
//
// Create Date: 2019/05/24 10:49:15
// Design Name: GBT Board1V0
// Module Name: elink_gearbox_4b_10b_comma_alignment
// Project Name:
// Target Devices: xc7k410tffg900-2L
// Tool Versions: Vivado 2018.1
// Description:
// One must be careful about the data sequence of the 10b
// dataout. The input of the DECODER module is from MSB to LSB, so that the
// output should be compatible with it. If the data is sent from LSB first,
// then everything is OK. IF NOT, a bit-reverse is needed. The bit order is
// set by LSB_OR_MSB.
// One must know that what you send to oserdes_4in1out module will appear on
// the output of elink_selectio_1in4out_dly_ctrl moudle. So that do not care
// about the data sequence in the serial transmission.
// One must also note that if MSB came first, the bit order is B9, B8,..., B0
// and the output 10b data is 9876543210; otherwise is B0, B1,..., B9.
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////

模块名

  • 模块名必须和文件名一样!!!

  • 模块名应当采用有意义的单词,并用小写字母+下划线”_”的方式将单词连接在一起

  • 模块名应当采取 [对象]_[动作]的方式:如sram_ctrl.v, elink_selectio_1in4out_dly_ctrl.v

  • 模块名后面的括号另起一行!!!,以便后面的端口对齐,如果有parameter也要另起一行,方便对齐,如下面代码所示

    module usb_gbt_top
        #(
            parameter FEC_NUM = 16,
            parameter FEC_WIDTH = $clog2(FEC_NUM),
            parameter DEBUG_EN = 1,
        )(
            input CLK40M_PIN
            //...
        );
    
  • top层的名字一定要鲜明,包含top字样

输入输出端口

  • 采用Verilog-2001标准,也就是写成上面代码那样

  • 每行只声明一个端口,有必要的时候在后面写上一定的注释

  • 输入端口在最后加上_i

  • 输出端口在后面加上_o

  • 有必要时使用parameter,名字一定要大写

  • 不要按输入输出的顺序排序,也不要按字母排序!!!

  • 按照功能来排序,应当按照如下顺序:

    • 时钟信号
    • 复位信号
    • 使能信号
    • 控制信号
    • 地址信号
    • 数据
      • 数据本身
      • 数据valid
      • 数据ready
  • 当端口很多时,可以采用//----------的方式来区分每个功能分区的端口

  • 端口缩进!!!使用我的vim按F3会自动缩进,如下图所示

    auto_format

  • Tips:最后一个端口后面没有“,”

变量

  • 变量命名要有意义,按照[所属目标]_[具体内容]_[r或者w]方式定义,如reg usb_data_valid_r; wire usb_data_ready_w;

  • reg型变量后面加上后缀_r,wire型变量后面加上后缀_w,如上面那条写的那样

  • 数据位定义都是从高到低的:reg [7 : 0] uart_data_r; 不要reg [0 : 7] uart_data_r;

  • 低电平的信号后面一定要带_n

  • 用作时钟域同步的信号要加上_sync,如reg [1 : 0] trigger_sync_r;

  • [WIDTH - 1 : 0] 中的冒号和两边的值要加上空格隔开

  • 任何运算符和变量之间都要用空格隔开

  • [a : 0] 和 [ 0 +: a] 等价,在使用for循环的时候很有用

  • 如果有多个变量有相同的功能,直接定义成数组,不要每个变量都定义一个!!!

  • 模块内的parameter一律定义为localparam,我比较喜欢的一个方式如下所示,可以对的很齐

        localparam [1:0]
        IDLE     = 2'd0,
        DATA_OUT = 2'd1,
        TLAST    = 2'd2,
        DONE     = 2'd3;
    
  • 即使是parameter的名字也要有意义,这样状态机才不会搞混

  • 能用parameter解决的东西就不要用变量

  • 普通变量的名字应当是:[对象]_[动作]_[后缀]的方式,比如说:data_credits_tx_w。采用这样的方式可以第一时间反应过来这个变量是属于哪个对象的。
  • 状态机变量名应当是:[动作]_[对象],如:CLR_CREDIT,WAIT_DATA_DONE。采用这样的方式在读的时候可以很清楚这个状态是在做什么,符合语言的习惯

语句

  • 每个语句都要独立成行,除非下面这种D触发器

  • always, for, while语句的begin在其下一行

  • initial, if, else语句的begin与其在同一行

  • end, endcase, endgenerate独占一行,绝对不要出现end else这样的情况

  • 同一个逻辑块内不要空行,不同的逻辑块之间空一行,甚至注释空行,以显示他们之间的关系

  • 每行尽量小于80个字符,增加代码可读性,尤其是喜欢用?:表达式的作者,换行的时候将运算符带着一起换行。如

         RAM_din_r <= (baseline_calibration_on_in == 1'b1) 
                           ? current_calibration_data_w 
                           : ADC_data_in;
    
        always @ (posedge clk_25)  // ADC threshold keep process
        begin
            if(!rstn)
            begin
                ADC_data_threshold_off_r <= 1'b1;
            end
            else if(ADC_data_threshold_valid_in == 1'b1 
                && ADC_data_threshold_ready_out == 1'b1 
                && ADC_data_threshold_chip_number_in == chip_number)
            begin
                ADC_data_threshold_off_r <= ADC_data_threshold_off_in;
                ADC_data_threshold_r[ADC_data_threshold_channel_number_in] <= ADC_data_threshold_in;
            end
        end
    
  • 用tab缩进代码,如果是用我的vim编辑代码直接按F3即可

  • 每个文件不要太大,如果一个module包含了太多的代码,那么说明还有很多的改进空间

  • 单比特信号不需要==1或者==0,如

    if(usb_data_en_i == 1'b1)
    // 应当写作
    if(usb_data_en_i)
    
    if(reset_n == 1'b0)
    // 应当写作
    if(!reset_n)
    
  • 行尾不要加空格,如果用我的vim,按F3之后会自动把行尾多余的空格去掉

  • 运算符和变量之间尽量加上空格

注释

  • 代码尽量写得没有注释也能看懂,比如说usb_data32b_r, usb_data32b_valid_r, usb_data32b_ready_w, addr_wr_acq_w,这些信号自身就说明了其含义
  • 端口加上注释解释,说明其用途,若一个模块有多个时钟域,还应当说明其时钟域
  • 有一些特殊的设计和考虑,一定要加上注释
  • 但是注释也不要乱加,如:// Data adder
  • 很多作者都喜欢用/**/来注释掉老的代码块,这是一个好习惯,也是一个坏习惯
    • 开始修改代码时,使用/**/注释,有助于调试和查看原有代码
    • 新代码调试完成后,应当把/**/注释掉的老代码删除,没必要留在文件里
    • 这些注释掉的代码可能永远都不会用了
    • 如果带担心老代码有些有用的内容,可以看我的代码管理小节

代码功能举例

简单的D触发器

下面的代码可以实现一个带使能的异步复位D触发器,注意在时序逻辑代码里,寄存器不一定在不同分支里都要赋值,若不赋值,综合器会理解为保持上一次的值。但是在组合逻辑里不行,组合逻辑里寄存器在每个分支里都必须赋值,否则会生成latch

always @ (posedge clk_100m or negedge reset_n)
begin
    if(!reset_n) begin
        data32b_r <= 32'b0;
        data32b_valid_r <= 1'b0;
    end
    else if(acq_data_ready_r) begin
        data32b_r <= acq_data_r;
        data_32b_valid_r <= 1'b1;
    end
    else begin
        data_32b_valid_r <= 1'b0;
    end
end

generate的例子

generate 普通代码

下面这段代码有些行已经超过了80个字符,是在没法换行了。

代码的目的是为了在一串10bits的数据流中找出是comma的那一段,也就是说实现一个1到10的串并转换之后,需要找到如何截断这10bit。

1转10最终的10bits数据会有10种可能性,因此需要选出其中对应为comma码的那一种,在下面的代码中,用for循环遍历一遍,找到comma的那一位

    // K28.1, K28.5, K28.7 detect
    // For easy reading the 10b coding data is arranged from LSB to MSB ie.
    // abcdei fghj.
    //--------------------------------------------------
    // Code   |  8bits data |     RD-         RD+      |
    //        |    MSB-LSB  |          LSB-MSB         |
    //--------------------------------------------------
    // k28.1  |  001_11100  |  001111_1001 110000_0110 |
    //--------------------------------------------------
    // k28.5  |  101_11100  |  001111_1010 110000_0101 |
    //--------------------------------------------------
    // k28.7  |  111_11100  |  001111_1000 110000_0111 |
    //--------------------------------------------------
    // In order to be compatible with the DECODER module  the
    // output data is from MSB to LSB
    // The following parameter only given the common 7bits of comma, and is
    // from MSB to LSB
    localparam COMMA_N_REVISE = 7'b001_1111;
    localparam COMMA_P_REVISE = ~COMMA_N_REVISE;
    localparam COMMA_N = 7'b111_1100;
    localparam COMMA_P = ~COMMA_N;

    reg [19:0] rx_data_20b_r;
    genvar bit_position_g;
    wire [6:0] rx_data_comma_7bx10_w[0:9];
    wire [9:0] comma_valid_bits_w;
    generate begin
        for (bit_position_g = 0; bit_position_g < 10; bit_position_g = bit_position_g + 1)
        begin
            assign rx_data10bx10_w[bit_position_g] = rx_data_20b_r[9+bit_position_g -: 10];
            if (LSB_OR_MSB == 1) begin
                assign rx_data_comma_7bx10_w[bit_position_g] = rx_data_20b_r[6+bit_position_g -: 7];
                assign comma_valid_bits_w[bit_position_g] = ((rx_data_comma_7bx10_w[bit_position_g] == COMMA_P)
                                                            || (rx_data_comma_7bx10_w[bit_position_g] == COMMA_N))
                                                            ? 1'b1 : 1'b0;
            end
            else begin
                assign rx_data_comma_7bx10_w[bit_position_g] = rx_data_20b_r[9+bit_position_g -: 7];
                assign comma_valid_bits_w[bit_position_g] = ((rx_data_comma_7bx10_w[bit_position_g] == COMMA_P_REVISE)
                                                            || (rx_data_comma_7bx10_w[bit_position_g] == COMMA_N_REVISE))
                                                            ? 1'b1 : 1'b0;
            end
        end
    end
    endgenerate

generate 模块调用

当一个模块要被用好几次时,直接用generate生成很多这样的模块,用多位宽的寄存器进行输入,非常方便,如下面的代码,数据控制模块会对应多个前端,重复很多遍就好了

    genvar ii;
    for (ii = 0; ii < FEC_NUM; ii = ii + 1)
    begin: fec_data_reconstruct
        //instance:../fec_if/fec_data_ctrl.v
        fec_data_ctrl#(
            .DATA_THRESHOLD(DATA_THRESHOLD)
        )fec_data_ctrl_mux(
            .clk_100m(clk_100m),
            .clk_200m(clk_200m),
            .reset_n(reset_n),
            .transmit_start_i(transmit_en_i),
            // Datain 16b
            .fec_data16b_i(fec_data16b_i[ii * 16 +: 16]),
            .fec_data16b_en_i(fec_data16b_en_i[ii]),
            // Dataout 32b
            .fifo_empty_o(fec_fifo_empty_w[ii]),
            .fifo_full_o(fec_fifo_full_w[ii]),
            .data_over_th_o(fec_data_over_th_w[ii]),
            .data32b_o(fec_data32b_int_w[ii * 32 +: 32]),
            .data32b_valid_o(fec_data32b_valid_int_w[ii]),
            .data32b_ready_i(data32b_ready_w[ii])
            );

        assign data32b_ready_w[ii] = (chn_select_r == ii) 
                                    ? (fec_data32b_tready_i && fec_usr_data_en_r) 
                                    : 1'b0;
    end
    endgenerate

再如下面的代码,可以通过是否将parameter DEBUG_EN置1来选择是否要将变量加入ila

    generate begin: data_mux_dbg
        if(DEBUG_EN == 1) begin
            (*mark_debug = "true"*) wire [31:0] fec_data32b_dbg_w;
            assign fec_data32b_dbg_w = fec_data32b_o;
            (*mark_debug = "true"*) wire fec_data32b_tvalid_dbg_w;
            assign fec_data32b_tvalid_dbg_w = fec_data32b_tvalid_o;
            (*mark_debug = "true"*) wire [2:0] c_state_dbg_w;
            assign c_state_dbg_w = c_state;
            (*mark_debug = "true"*) wire [FEC_NUM - 1 : 0] fec_fifo_empty_dbg;
            assign fec_fifo_empty_dbg = fec_fifo_empty_w;
            (*mark_debug = "true"*) wire [FEC_WIDTH - 1 : 0] chn_select_dbg;
            assign chn_select_dbg = chn_select_r;
        end
    end
    endgenerate

    generate begin:debug_sipo_10b
        if(DEBUG_EN == 1'b1)
        begin
            ila_rx_data_4b debug_rx_data_4b (
                .clk(clk_40m), // input wire clk
                .probe0(rx_data_4b_from_selectio_w) // input wire [0:0] probe0
                );

            (* mark_debug = "true" *) wire [9:0] rx_data_10b_from_gearbox_debug;
            assign rx_data_10b_from_gearbox_debug = rx_data_10b_from_gearbox_w;
        end
    end
    endgenerate

generate的活用

下面的代码是个优先编码器,是可综合的

module prio_encoder#
(
    parameter LINES = 16, // The LINES must be pow(2, n). If not enough, add 0
    parameter WIDTH = $clog2(LINES)
)(
    input [LINES - 1 : 0] data_i,
    output reg [WIDTH - 1 : 0] encoded_data_o
    );

    genvar gi;
    reg flag;
    integer i;
    always@(*)
    begin
        encoded_data_o = 1'b1 << WIDTH;
        flag = 1;
        for(i = 0; flag && (i < LINES); i = i + 1 ) begin
            if(data_i[i] == 1) begin
                encoded_data_o = i;
                flag = 0;
            end
        end
    end

endmodule

其他

  • always和@之间要有一个空格,@和自己的()之间没有空格
  • 所有的运算符和操作数之间都要有空格

results matching ""

    No results matching ""