What is Good Verilog Coding Style

Table of Content

Recently, I was asked by my boss to make some rules to regulate the verilog coding style in my team. For confidentiality reasons, I wouldn\'t be able to share the whole pages with you, but still I would like to point out some little tips that I personally strongly recommend you to adopt.

I. Example

To make it straightforward, a simple example is given as follows. I wish you to skim it shortly, and then I will explain my little tips one by one base on it. The code is a part of my Synchronous FIFO module.

//============================================================================== 
// Copyright (C) 2015 By Kellen.Wang 
// mail@kellen.wang, All Rights Reserved
//============================================================================== 
// Module : sync_fifo 
// Author : Kellen Wang 
// Contact: mail@kellen.wang 
// Date : Jan.17.2015
//============================================================================== 
// Description :
//============================================================================== 
module sync_fifo #( 
    parameter DEPTH = 32, 
    parameter DATA_W = 32 
) ( 
    input wire clk , 
    input wire rst_n , 
    input wire wreq , 
    input wire [DATA_W-1:0] wdata , 
    output wire full_flg , 
    input wire rreq , 
    output wire [DATA_W-1:0] rdata , 
    output wire empty_flg 
); 
`ifdef DUMMY_SYNC_FIFO 
assign full_flg = 1'd0; 
assign rdata = 32'd0; 
assign empty_flg = 1'd0; 
`else 
`include "get_width.inc"
//============================================================================== 
// Constant Definition :
//============================================================================== 
localparam DLY = 1'd1; 
localparam FULL = 1'd1; 
localparam NOT_FULL = 1'd0; 
localparam EMPTY = 1'd1; 
localparam NOT_EMPTY = 1'd0; 
localparam ADDR_W = get_width(DEPTH-1);
//============================================================================== 
// Variable Definition :
//============================================================================== 
reg [ADDR_W-1:0] waddr; 
reg [ADDR_W-1:0] raddr; 
wire [ADDR_W-1:0] waddr_nxt; 
wire [ADDR_W-1:0] raddr_nxt;
//============================================================================== 
// Logic Design :
//============================================================================== 
assign waddr_nxt = waddr + 1; 
assign raddr_nxt = raddr + 1; 
assign full_flg = (waddr_nxt == raddr)? FULL : NOT_FULL; 
assign empty_flg = (waddr == raddr)? EMPTY : NOT_EMPTY; 
assign iwreq = wreq & ~full_flg; 
assign irreq = 1'd1; 
always @(posedge clk or negedge rst_n) begin 
    if (!rst_n) begin 
        waddr <= #DLY 0; 
    end 
    else if(wreq & (full_flg == NOT_FULL)) begin 
        waddr <= #DLY waddr_nxt; 
    end 
end 
always @(posedge clk or negedge rst_n) begin 
    if (!rst_n) begin 
        raddr <= #DLY 0; 
    end 
    else if(rreq & (empty_flg == NOT_EMPTY)) begin 
        raddr <= #DLY raddr_nxt; 
    end 
end 
//synopsys translate_off 
`ifdef DEBUG_ON 
iError_fifo_write_overflow: assert property (@(posedge wclk) disable iff (!rst_n) (iwreq & !full_flg));
iError_fifo_read_overflow: assert property (@(posedge rclk) disable iff (!rst_n) (irreq & !empty_flg)); 
`endif 
//synopsys translate_on 
//============================================================================== 
// Sub-Module : 
//============================================================================== 
shell_dual_ram #( 
    .ADDR_W (ADDR_W ), 
    .DATA_W (DATA_W ), 
    .DEPTH (DEPTH ) 
) u_shell_dual_ram ( 
    .wclk (clk ), 
    .write (iwreq ), 
    .waddr (waddr ), 
    .wdata (wdata ), 
    .rclk (clk ), 
    .read (irreq ), 
    .raddr (raddr ), 
    .rdata (rdata ) 
); 
`endif 
// `ifdef DUMMY_SYNC_FIFO 
endmodule

II. Recommended Rules For Verilog Coding Style

A) Rule List

While coding for this module, I mainly followed these rules:

  • Verilog2001 standard styled I/O port definition
  • Dummy module macro
  • Replace logic values with parameters that properly named
  • Embedded assertions
  • Memory shell

B) Explanation of Rules

In the following chapters, I am going to explain the benefits from following these rules.

1) Verilog2001 Standard Styled I/O Port Definition

module sync_fifo #( 
    parameter DEPTH = 32, 
    parameter DATA_W = 32 
) ( 
    input wire clk , 
    input wire rst_n , 
    input wire wreq , 
    input wire [DATA_W-1:0] wdata , 
    output wire full_flg , 
    input wire rreq , 
    output wire [DATA_W-1:0] rdata , 
    output wire empty_flg 
); 

Compared with Verilog1995 standard I/O port definition, this new style integrated information of signal direction, type and bit width, reduced unnecessary typo chance, also shortened the length of the code. Besides this, another minor change is also worth mention. The parameters that used to customise the IP is topped ahead of I/O definition. In this way, the purpose of these parameters are more clearer. Also, when someone need to instantiation this module, he/she could just paste both these parameters & I/O definitions into the target place, and then giving proper values and connections to these variables at the new place in one go. In the example given above, I designed two configurable parameters for this FIFO module: DEPTH & DATA_W. So when I need to create a instantiation of this FIFO, I just need to do it in the old fashion, and then insert these two lines between the module name and the I/O port connection description, just like what shows below.

sync_fifo #( 
    .DEPTH (16 ), 
    .DATA_W (64 ) 
) myfifo ( 
    .clk (clk ), 
    .rst_n (rst_n ), 
    .wreq (wreq ), 
    .wdata (wdata ), 
    .full_flg (full_flg ), 
    .rreq (rreq ), 
    .rdata (rdata ), 
    .empty_flg (empty_flg)
); 

Please always remember to comment with the valid data ranges of the configurable parameters besides their definitions.

2) Dummy Module Macro

When working as a team, usually we will divide the whole system into different modules based on their relatively independent functions, and then distribute the workload to team members. After one module is done, someone in charge of it will upload it to the subversion managed server. Before all bugs get ruled out of this module under complete verification process, it is very likely that when other people in this team try to simulate their own work on the system and gets unpredictable result due to other people's bugs. In other scenarios, we wish to run prototype verification on FPGA before the whole system design & verification is done. Obviously we should avoid to build an environment specifically for this purpose. The perfect result would be that we could share the same environment for both complete & partially accomplished system synthesis. The best solution to all these problems is to add dummy macro in each module\'s top level design files. This is how it looks like in the code:

`ifdef DUMMY_SYNC_FIFO 
assign full_flg = 1'd0; 
assign rdata = 32'd0; 
assign empty_flg = 1'd0; 
`else 
... 
`endif //DUMMY_SYNC_FIFO

With the dummy module macro in the top level rtl design file of the module, you could easily switch this module from real one to dummy one or vice versa. In the case above, for example, you only need to add the following code to your definition file to make this module a dummy one.

`define DUMMY_SYNC_FIFO

3) Replace Logic Values with Parameters That Properly Named

Many people tend to use bare numbers instead of meaningful names in assignment statements. Not a mistake though, but really a very bad idea that is. Numbers don't tell its purpose, names do, please remember this. A slight change could make a huge difference. Think about the contrast between the following two examples:

assign full_flg = (waddr_nxt == raddr); 

Versus

localparam FULL = 1'd1; 
localparam NOT_FULL = 1'd0; 
assign full_flg = (waddr_nxt == raddr) ? FULL : NOT_FULL; 

Which one is more readable to you? Some may think that the first example is more simplified and could produce a better result. To these people I would say that, firstly, shorter code does not necessarily produce more efficient circuit, the EDA tool we use today has been greatly improved to optimise such logic; Secondly, unless the design you are doing has extremely high constraint to performance, otherwise readability is almost as important as performance. The other benefit of naming signal values is that, when you read the signal value in EDA tools, such as in Verdi simulation waveform viewing scope, you will read FULL/ NOT_FULL instead of 1/ 0. Explicitly, your waveform is more readable and friendly in this way.

4) Embedded Assertion

Some people think that only IC Verification Engineers need to learn assertion skill, but I don't think so. Properly designed embedded assertions could be used to detect internal errors of the module, to prevent mistaken operation upon the module, to improve the efficiency of the verification process. However, assertions are not synthesisable (Where I mean average industrial level synthesis tools, exclusive of those emulation targeted tools such as ZEBU), so we need to add some synthesis control macros to switch on its function only during simulation process.

//synopsys translate_off 
`ifdef DEBUG_ON 
iError_fifo_write_overflow: assert property (@(posedge wclk) disable iff (!rst_n) (iwreq & !full_flg));
iError_fifo_read_overflow: assert property (@(posedge rclk) disable iff (!rst_n) (irreq & !empty_flg)); 
`endif 
//synopsys translate_on

In this example, firstly, I used '//synopsys translate_off/on' comments to switch off and then on the synthesis function during the compiling process. Secondly, I added a 'DEBUG_ON' macro to switch on/off the assertion function before runtime. In the 'DEBUG_ON' macro, two assertions monitoring read/write overflow states will print corresponding error information during runtime when wrong states happen.

5) Memory Shell

Memories are frequently used in IC designs, but we don't write the code to implement them by hands (It is possible but kinda a waste to do it at RTL design level). Usually we use dedicated resources to create memory banks, either embedded rams in FPGAs or generated arrays using library cells. The different choices of memory resources bring us a tricky problem: We need to replace the code of memory instantiation for different purposes -- either for simulation, prototype verification or ASIC implementation under different standard IC process. These memory resources may have different port definitions, different protocol for data transfer. To avoid such change to RTL design files and to improve the compatibility of the memory I/O connection, we could use a memory shell to wrap the logics for all memory resources. In the memory shell, we could instantiate all the memory resources and then use different macros to enable anyone of them. At the same time, the protocol of the enabled memory resource will be finally changed to a unified protocol to connect with other modules in the system. In this way, from the view of the rest part of the system, no matter which memory resource is used, the behaviour of it remains the same.

shell_dual_ram #( 
    .ADDR_W (ADDR_W ), 
    .DATA_W (DATA_W ), 
    .DEPTH (DEPTH ) 
) u_shell_dual_ram ( 
    .wclk (clk ), 
    .write (iwreq ), 
    .waddr (waddr ), 
    .wdata (wdata ), 
    .rclk (clk ), 
    .read (irreq ), 
    .raddr (raddr ), 
    .rdata (rdata ) 
);

This shell_dual_ram is a memory shell. In this memory shell, many different type of memory resources are instantiated, including memory for FPGA implementation, memory model for simulation and memory array for ASIC implementation. However, no matter which memory is functioning, the ports are rewired to fit the I/O of the shell. Thus, from the view of the outskirts, the memory become static in all circumstances.

III. Conclusion

Good coding style could improve the readability of the code, reduce typo rate, shorten the debug process. But we all know that there is no the best way but always better ways. I will update this article once I find new rules. Any suggestions or comments regarding to good coding styles are welcomed. If you have problems irrelevant to this topic, you are also welcomed to leave it to me in Q&A column.

工程师,个人公众号:IF推理同好会,个人微信小游戏:IF迷你侦探
Posts created 32

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top