Standard for Formatting of Field-Programmable Gate Array Codes
FPGA Code Formatting Standards: Write RTL That Other Engineers Can Actually Read
Bad formatting does not cause synthesis errors. It does not break timing. It does not generate wrong bitstreams. So why does it matter so much? Because unreadable code creates bugs. Not logic bugs. Human bugs. The kind where someone misreads a signal name, connects the wrong port, or copies a block of code without noticing it does the opposite of what they think it does.
Formatting is not about aesthetics. It is about reducing cognitive load. Every engineer on your team should be able to read any file in the project within thirty seconds and understand what is happening. That only works if every file follows the same rules.
This guide covers the formatting standards that actually scale across large FPGA projects. These are not opinions pulled from thin air. They are patterns forged in real verification environments where sloppy formatting cost real money.
Why Consistent Formatting Saves Money
Think about the last time you opened someone else’s RTL file. The indentation was random. Some lines had four spaces, others had tabs, others had nothing. Signal names were camelCase in one module and snake_case in the next. Port lists wrapped at different column widths on every single file.
You spent ten minutes just figuring out where one block ended and another began. That is ten minutes of engineering time wasted on formatting instead of design. Multiply that by every file, every review, every debug session, and the number gets ugly fast.
Consistent formatting eliminates that friction. When every file looks the same, your eyes go straight to the logic. You stop thinking about structure and start thinking about function. That is the entire point.
Naming Conventions That Actually Work
Naming is the first layer of formatting. If your names are inconsistent, no amount of indentation will save you.
Signal and Port Names
Use snake_case for everything. Always. No exceptions.
verilog1module uart_tx (
2 input wire clk,
3 input wire rst_n,
4 input wire [7:0] data_in,
5 input wire data_valid,
6 output reg tx_out,
7 output reg tx_busy
8);
9
Never mix styles in the same file. dataValid next to tx_busy is a red flag. It tells the reader that two different people wrote this code, or one person wrote it at two in the morning.
Clock and reset signals get short names. clk, rst_n, clk_en. Do not call them system_clock_input or active_low_global_reset. Everyone knows what clk and rst_n mean. Long names add noise.
Active-low signals should end with _n or _b. Not both. Pick one and stick with it across the entire project. rst_n or rst_b. Mixing them in the same file is confusing.
Module and Entity Names
Module names use PascalCase. UartTx, FifoSync, ClockDivider. This makes them stand out from signals and ports immediately.
verilog1module ClockDivider #(
2 parameter DIV_RATIO = 10
3) (
4 input wire clk,
5 input wire rst_n,
6 output reg clk_out
7);
8
The parameter name DIV_RATIO uses snake_case because it is a parameter, not a module. Keep the convention consistent. Modules get PascalCase. Everything else gets snake_case.
Macro and Define Names
Use ALL_CAPS with underscores. This is not optional. It is the universal convention in every HDL project ever written.
verilog1`define DATA_WIDTH 16
2`define ADDR_WIDTH 8
3`define FIFO_DEPTH 256
4
When you see DATA_WIDTH in the middle of a file, you know instantly that it is a constant, not a signal. That visual distinction saves your brain from having to scroll up and check the definition every time.
Indentation and Line Structure
This is where most FPGA code goes wrong. The nesting in HDL is deep. Always blocks inside generate blocks inside module bodies. If your indentation is off by even one level, the entire structure becomes unreadable.
The Two-Space Rule
Indent with two spaces. Not four. Not tabs. Two spaces.
Tabs cause problems because different editors display them with different widths. What looks aligned in your editor looks like a mess in mine. Two spaces look the same everywhere.
verilog1always @(posedge clk) begin
2 if (rst_n) begin
3 counter <= 0;
4 end else begin
5 if (enable) begin
6 counter <= counter + 1;
7 end
8 end
9end
10
Every begin adds two spaces. Every end removes two spaces. The alignment is obvious. You can count the nesting depth by counting the indentation level. No guesswork.
One Statement Per Line
Never put two statements on the same line unless they are trivially short and obviously related.
Bad.
verilog1assign sum = a + b; assign carry = (a & b) | (a & cin) | (b & cin);
2
Good.
verilog1assign sum = a + b;
2assign carry = (a & b) | (a & cin) | (b & cin);
3
The good version lets you scan each assignment independently. The bad version forces you to parse two complete expressions on one line. Your eyes skip over it. You miss the typo. You introduce a bug.
Align Assignments in Declarations
When you declare multiple signals, align the bit widths and names.
verilog1reg [7:0] data_in;
2reg [7:0] data_out;
3reg [15:0] counter;
4reg enable;
5wire full;
6wire empty;
7
The bit widths line up. The names line up. You can see at a glance which signals are wide and which are single-bit. This is especially useful in port lists.
verilog1module fifo_sync #(
2 parameter DATA_WIDTH = 8,
3 parameter ADDR_WIDTH = 4
4) (
5 input wire clk,
6 input wire rst_n,
7 input wire [DATA_WIDTH-1:0] data_in,
8 input wire wr_en,
9 input wire rd_en,
10 output wire [DATA_WIDTH-1:0] data_out,
11 output wire full,
12 output wire empty
13);
14
Every port is on its own line. Every type is aligned. When you add a new port, you add one line. When you remove a port, you delete one line. No cascading edits. No merge conflicts.
Commenting Without Cluttering
Comments are good. Too many comments are worse than no comments. The rule is simple. Comment the why, not the what.
Block Comments for Intent
Put a block comment above every always block that explains what the block does, not how it does it.
verilog1// Free-running counter that wraps at DIV_RATIO
2always @(posedge clk or negedge rst_n) begin
3 if (!rst_n)
4 counter <= 0;
5 else if (counter == DIV_RATIO - 1)
6 counter <= 0;
7 else
8 counter <= counter + 1;
9end
10
The comment tells you the intent. The code tells you the implementation. Together they are complete. Separately they are both useless.
Inline Comments for Non-Obvious Logic
Use inline comments only when the code does something that is not immediately clear.
verilog1// Gray code conversion prevents multi-bit metastability
2assign gray_out = binary_in ^ (binary_in >> 1);
3
That comment is necessary. Without it, the next engineer might “simplify” this line and break the CDC logic.
Do not write comments like this.
verilog1counter <= counter + 1; // Increment counter by one
2
That is noise. The code says exactly what it does. The comment adds nothing. Delete it.
TODO and FIXME Tags
Use // TODO: and // FIXME: for things that need attention. These are searchable. When you run a grep for TODO: across the project, you get a clean list of everything that is incomplete.
verilog1// TODO: Add overflow protection when DIV_RATIO > 256
2// FIXME: This assumes clk is 50 MHz. Parameterize it.
3
Make these visible in your editor. Most editors let you configure a TODO highlight pattern. Turn it on. It turns your comments into an actionable task list.
Vertical Spacing and Grouping
White space is free. Use it.
Separate Logical Blocks With Blank Lines
Do not cram everything together. A blank line between logical sections tells the reader “this is a new thought.”
verilog1// Reset logic
2always @(posedge clk or negedge rst_n) begin
3 if (!rst_n) begin
4 state <= IDLE;
5 count <= 0;
6 end
7end
8
9// State machine
10always @(posedge clk) begin
11 case (state)
12 IDLE: if (start) state <= RUNNING;
13 RUNNING: if (done) state <= IDLE;
14 endcase
15end
16
17// Counter
18always @(posedge clk) begin
19 if (state == RUNNING)
20 count <= count + 1;
21end
22
Three always blocks. Three logical functions. Blank lines between them. You can see the structure without reading a single line of code.
Group Related Parameters
If you have multiple parameters that serve the same purpose, group them together at the top of the module.
verilog1module spi_master #(
2 // Bus configuration
3 parameter CLK_DIV = 4,
4 parameter CPOL = 0,
5 parameter CPHA = 0,
6 // Data configuration
7 parameter DATA_WIDTH = 8,
8 parameter FIFO_DEPTH = 16
9) (
10
The comments separate the groups. The parameters within each group are sorted alphabetically. This makes it easy to find what you are looking for and easy to add new parameters in the right place.
File-Level Structure
Every RTL file should follow the same top-to-bottom structure. When an engineer opens any file, they should know exactly where to find each section.
The Standard File Order
Copyright and license header at the top. Even if your project is internal, include a one-line header that says who owns the code and when it was created.
Then the module declaration with parameters.
Then the port list.
Then internal signals and registers.
Then instantiations.
Then always blocks and assign statements.
Then the endmodule at the bottom.
verilog1//=============================================================================
2// File: uart_tx.v
3// Description: UART transmitter with configurable baud rate
4// Author: jdoe
5// Created: 2024-03-15
6//=============================================================================
7
8module uart_tx #(
9 parameter CLK_FREQ = 50_000_000,
10 parameter BAUD_RATE = 115200
11) (
12 input wire clk,
13 input wire rst_n,
14 input wire [7:0] data_in,
15 input wire data_valid,
16 output reg tx_out,
17 output reg tx_busy
18);
19
20 // Internal signals
21 reg [15:0] baud_counter;
22 reg [3:0] bit_index;
23 reg [7:0] tx_shift_reg;
24
25 // Baud rate generator
26 always @(posedge clk or negedge rst_n) begin
27 if (!rst_n)
28 baud_counter <= 0;
29 else if (baud_counter == CLK_FREQ / BAUD_RATE - 1)
30 baud_counter <= 0;
31 else
32 baud_counter <= baud_counter + 1;
33 end
34
35 // Transmission logic
36 always @(posedge clk or negedge rst_n) begin
37 if (!rst_n) begin
38 tx_out <= 1;
39 tx_busy <= 0;
40 bit_index <= 0;
41 tx_shift_reg <= 0;
42 end else begin
43 // ... transmission code ...
44 end
45 end
46
47endmodule
48
Every file looks like this. Every engineer knows where to look. No surprises. No hunting.
Common Formatting Mistakes That Create Real Bugs
Trailing whitespace. It looks harmless. It causes diff noise. It confuses version control. Most editors can strip it on save. Turn that feature on.
Inconsistent use of parentheses. if (a & b) versus if a & b. Pick one. The parentheses make precedence explicit and prevent subtle bugs when someone adds a new condition later.
Mixing blocking and non-blocking assignments in the same always block. This is not a formatting issue strictly speaking, but it is a structural one. One always block should use one type of assignment. If you see <= and = in the same block, something is wrong.
Long lines that wrap silently. If a line exceeds 120 characters, break it. Do not let the editor wrap it visually. A wrapped line hides the actual structure. Break the line at a logical boundary. Align the continuation with the opening parenthesis or assignment operator.
verilog1assign next_state = (current_state == IDLE && start_signal) ?
2 RUNNING :
3 (current_state == RUNNING && done_signal) ?
4 IDLE :
5 current_state;
6
The ternary operator chain is broken at each ? and :. The alignment shows the logic flow. You can read it top to bottom without scrolling horizontally.
Enforcing Standards Without Starting a War
Nobody likes being told their formatting is wrong. The trick is to automate enforcement so no human has to be the bad guy.
Use a linter that runs on every save. Most editors support this. The linter flags violations automatically. The engineer fixes them before commit. No code review comments about indentation. No arguments about tabs versus spaces. The tool decides. The tool is always right.
Set up a pre-commit hook that runs the linter and refuses to commit if there are violations. This catches formatting drift before it reaches the repository. Once it is in version control, it is ten times harder to fix.
Share the configuration file. The .editorconfig or the linter config goes in the repo root. Every engineer gets the same rules automatically. No one has to remember anything. The environment enforces the standard.
Formatting is not about making code pretty. It is about making code predictable. When every file follows the same rules, your team spends less time reading and more time designing. That is the real return on investment.
ChipApex is a global distributor of electronic components: ICs, semiconductors, passives & interconnects. Source active & obsolete parts with wholesale pricing, fast RFQ response, and worldwide delivery.Official website address:chipapex.com