Skip to content

Verilog · Chapter 4.1 · Lexical Conventions

White Space Requirements in Verilog

Whitespace in Verilog does two jobs. It separates tokens so the lexer can tell keywords, identifiers, and literals apart, and it makes your code readable to the people who maintain it. This page shows exactly where whitespace is required, where it is optional, and where extra spaces are simply ignored. It then covers the style conventions real RTL teams enforce, such as four-space indentation, no tabs, and consistent line endings. You will also learn the cross-platform traps that hit teams working across Windows, macOS, and Linux, including mixed tabs and spaces and inconsistent newline characters that pollute diffs and slow reviews.

Foundation16 min readVerilogSyntaxStyleWhitespace

Chapter 4 · Page 4.1 · Lexical Conventions

What Verilog Calls Whitespace

Per IEEE 1364-2005 §3.1, Verilog whitespace is any sequence of:

  • space characters (0x20)
  • horizontal tabs (0x09)
  • newlines (0x0A LF, 0x0D CR, or both as CRLF)
  • form feeds (0x0C)

Whitespace serves exactly two purposes in the language:

  1. Token separation. Between adjacent identifiers, keywords, and literals, some whitespace is required so the lexer can tell tokens apart. Between operators and identifiers, whitespace is usually optional.
  2. Readability. The lexer treats one space and one hundred spaces identically. Use the difference to align code so a human can read it.

Where Whitespace Is Required

Three rules cover almost every case.

Rule 1 — Between two adjacent identifiers / keywords / literals

The lexer needs to know where one token ends and the next begins. Without whitespace, two identifiers fuse into one.

required-1.v — between adjacent tokens
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ✅ Legal — whitespace separates the two keywords
input wire clk;
 
// ❌ Lexer error — "inputwire" is interpreted as a single identifier
inputwire clk;
 
// ✅ Legal — newline counts as whitespace
input
wire
clk;

Rule 2 — Between a keyword and an identifier

Same root cause — the lexer needs the boundary.

required-2.v — keyword + identifier
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ✅ Legal
always @(posedge clk)
    q <= d;
 
// ❌ Lexer error — "always@" runs into trouble; "alwaysq" runs together
always@(posedgeclk)
    q<=d;             // <= and q without whitespace IS legal — see Rule 3

Rule 3 — Around operators is OPTIONAL

Most operators self-delimit. The lexer matches the longest operator that fits, then resumes scanning.

required-3.v — operators don't need whitespace
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ✅ All legal — operator precedence and tokenisation are unambiguous
y = a&b;
y = a & b;
y = a  &  b;
y =a&b;
y=a&b;
 
// Style rule: use whitespace anyway for readability
y = a & b;        // canonical

Exception: the ++ / -- / ** / >>> style multi-character operators are tokenised greedily. Adjacent operators that would otherwise concatenate into a different multi-char operator need whitespace. This rarely bites in plain Verilog; it's more relevant in SystemVerilog (where ++ exists).

Where Whitespace Is Forbidden

The two cases where whitespace breaks tokenisation.

Inside a sized number literal

A sized literal is <size>'<base><value>. The size, the apostrophe, and the base specifier must be contiguous — no whitespace inside.

forbidden-1.v — inside numbers
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ✅ Legal
parameter MAX  = 8'hFF;
parameter MASK = 8'b1010_1010;
 
// ❌ Lexer error or wrong interpretation
parameter MAX  = 8 'hFF;       // ❌ space between size and quote
parameter MAX  = 8' hFF;       // ❌ space between quote and base
parameter MAX  = 8'h FF;       // ❌ space between base and value
parameter MAX  = 8'h F_F;      // ❌ space splits the value — underscores OK but spaces NOT

Underscores are allowed inside the value (purely for human readability): 8'h_FF, 32'h_DEAD_BEEF. Underscores are not whitespace; they're a lexical concession.

Inside an identifier

Identifiers can't contain whitespace — that's the boundary the lexer uses.

forbidden-2.v — inside identifiers
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ Two separate identifiers, not one
reg count q;          // lexer sees "count" then "q" — parse error
 
// ✅ Legal
reg count_q;          // underscore — part of the identifier
 
// ✅ Escaped identifier — `\` allows any character including spaces
reg \count q ;        // identifier is "count q" (trailing space terminates it)
                       // Hand-written RTL almost never uses this form.

Where Whitespace Is Optional but Style-Mandated

Most of the time. The industry enforces style through linters and reviews — not by the language itself.

Indentation

Every working RTL team picks one of three indentation styles. The choice matters because consistency is more valuable than the choice.

StyleCommon inConfigured by
4 spacesApple, Intel, AMD, Qualcomm.editorconfig · most lint tools
2 spacesSome startups, JavaScript-influenced teams.editorconfig
Hard tabsOlder codebases (1990s-early-2000s heritage)varies
indentation.v — 4-space convention
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module my_block (
    input  wire        clk,                    // 4-space port indent
    input  wire        rst_n,
    output reg  [7:0]  data_q
);
 
    always @(posedge clk or negedge rst_n) begin   // 4-space body indent
        if (!rst_n)
            data_q <= 8'h00;                        // nested → 8 spaces
        else
            data_q <= data_q + 8'd1;
    end
 
endmodule

The rule every team follows: one indentation style per repository, enforced by .editorconfig + the lint tool's whitespace plug-in. Mixed-style files are a code-review block.

Alignment

Aligning port declarations, parameter lists, and assignments makes the column structure visible at a glance.

alignment.v — aligned vs unaligned
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ Unaligned — readable but no visual structure
module unaligned (
    input wire clk,
    input wire rst_n,
    output reg [7:0] data_q,
    output wire busy
);
endmodule
 
// ✅ Aligned — column structure visible at a glance
module aligned (
    input  wire        clk,
    input  wire        rst_n,
    output reg  [7:0]  data_q,
    output wire        busy
);
endmodule

Alignment is style-grade not language-grade. Lint tools enforce it where teams demand it; some teams accept either as long as a file is internally consistent. Aligned port lists scale to ~30 signals before becoming unwieldy; beyond that, switch to one-port-per-line without alignment.

Newlines

Verilog statements end with ;. Newlines do not terminate statements — they're just whitespace. A statement can span as many lines as you want.

newlines.v — when to split a long expression
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ Too long for one line; reviewer asks for a break
assign result = (mode == MODE_A) ? data_in[31:24] : (mode == MODE_B) ? data_in[23:16] : (mode == MODE_C) ? data_in[15:8] : data_in[7:0];
 
// ✅ One choice per line — column structure shows the mux
assign result = (mode == MODE_A) ? data_in[31:24]
              : (mode == MODE_B) ? data_in[23:16]
              : (mode == MODE_C) ? data_in[15:8]
              :                    data_in[7:0];

Industry convention: keep lines under 100 columns. Lines over 120 columns trigger a lint warning at most teams. The exception: a 200-column comment is sometimes acceptable for a long URL or a long literal.

Line Endings — The Cross-Platform Trap

This is the single whitespace issue that bites every cross-platform RTL team within the first month.

PlatformDefault line ending
Linux / macOS\n (LF, 0x0A)
Windows\r\n (CRLF, 0x0D 0x0A)
Some legacy editors\r (CR, 0x0D) — rare today

Most simulators and synthesisers accept all three. But:

  1. Git interprets line endings. Without .gitattributes, Git on Windows can auto-convert LF ↔ CRLF on commit / checkout. A Linux teammate then sees "every line changed" diffs.
  2. Some legacy tools choke on CRLF. Older Verilog parsers sometimes mis-tokenise around CR characters; the symptom is bizarre "unexpected character" errors on a file that looks fine in the editor.
  3. $readmemh / $readmemb may not strip CR. A data.hex file with CRLF line endings can produce a hex byte of 0x0D at the end of each line on simulators that don't strip CR. Use LF-only for any file read by $readmemh / $readmemb.
.gitattributes — fix line endings once
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# Verilog / SystemVerilog source — always LF
*.v       text eol=lf
*.sv      text eol=lf
*.vh      text eol=lf
*.svh     text eol=lf
 
# Memory init files — strictly LF (some sims don't strip CR)
*.hex     text eol=lf
*.mem     text eol=lf
*.bin     binary

Tabs vs Spaces

The religious war of source code. The actual engineering trade-offs:

AspectTabsSpaces
Width displayedReader's choice (editor setting)Fixed
File sizeSmaller (1 tab vs 4 spaces)Larger
AlignmentBreaks if mixed with spacesAlways consistent
Diff readabilityInconsistent across viewersConsistent everywhere
Industry trendDeclining (mostly legacy codebases)Dominant in modern RTL

The pragmatic position every modern RTL team I've seen converges on: 4 spaces, never tabs. Set the editor to translate Tab → 4 spaces; configure the linter to flag tab characters. Done. The "tabs preserve user freedom" argument loses to "we need consistent column alignment for port lists, and tabs break that."

Whitespace Inside Strings

String contents are not tokenised — whitespace inside a "..." literal is preserved verbatim.

strings.v — whitespace IS preserved inside strings
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$display("  count = %h  ", count_q);   // two leading + two trailing spaces preserved
$display("\thello\n");                  // tab + LF in the output
$display("line1
line2");                                 // ❌ embedded literal newline — most simulators
                                          //    accept; some don't. Use \n instead.

For multi-line strings (e.g. error messages), prefer explicit \n escapes inside a single-line literal rather than embedding actual newlines — the latter has spotty cross-simulator support.

Whitespace in Compiler Directives

` directives are terminated by the end of the directive token, not by a newline. But by convention every directive lives on its own line, and most simulators expect a newline after the directive's arguments.

directives.v — one directive per line
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ✅ Idiomatic
`timescale 1ns/1ps
`default_nettype none
`define WIDTH 8
 
// ❌ Legal but unreadable
`timescale 1ns/1ps `default_nettype none `define WIDTH 8

Common Mistakes

Five real failure modes that hit beginners and experienced engineers alike.

  1. Mixing tabs and spaces in the same file. Visual alignment looks fine in one editor; broken in another. The fix: set up .editorconfig and have your editor "show whitespace" mode on.
  2. Trailing whitespace. Most editors highlight trailing whitespace; most linters flag it. It clutters diffs (a "no-op" trailing-whitespace-removal patch produces noisy line-changes).
  3. CRLF line endings on a Linux team. Git diff shows "every line changed" when one teammate's file has CRLF and the rest have LF. Fix once with .gitattributes.
  4. Hidden Unicode characters from copy-paste. Copying code from a slide or a PDF often introduces non-breaking spaces (U+00A0), zero-width characters, or em-dashes. The file looks fine but the simulator errors with cryptic messages. Open the file in a hex viewer if you suspect this.
  5. Embedded newlines in string literals. Some simulators accept, some don't. Always use \n escapes inside single-line "..." literals.

Industry Whitespace Style — The Working Defaults

The defaults that almost every modern silicon team converges on. Adopt them in your projects and you'll match what reviewers expect.

SettingValue
Indentation4 spaces
Tabs in sourceforbidden — flagged by lint
Line endingLF only
Maximum line length100 columns (soft), 120 columns (lint flag)
Trailing whitespaceforbidden — flagged by lint
Newline at end of filerequired
Blank lines between modules2
Blank lines between functions / tasks1
Blank line inside a module before endmodule1
Spaces around =, <=, ==, !=, etc.1 each side
Spaces inside (...), [...], {...}none
canonical-style.v — what production RTL looks like
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
`default_nettype none
 
module canonical_block (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [7:0]  data_in,
    output reg  [7:0]  data_q
);
 
    // Reset + load — one statement per clocked edge.
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            data_q <= 8'h00;
        else
            data_q <= data_in;
    end
 
endmodule

Memorise this rhythm; reach for it from day one. Every reviewer recognises it.

Interview Insights

Summary

Verilog whitespace has two jobs: separating tokens (a lexical requirement) and making code readable (a style discipline). The lexical rules are simple — whitespace required between adjacent identifiers / keywords / literals, forbidden inside sized literals and identifiers, optional everywhere else. The style discipline is where modern silicon teams converge: 4 spaces, no tabs, LF line endings, no trailing whitespace, lines under ~100 columns. Adopt those defaults from day one and your RTL fits straight into any production codebase. The next sub-topic covers comment forms in depth — line comments vs block comments, the comment-hygiene rules that make // why more useful than // what.

Beat coverage notes: all required beats present. ⏱ Timing Observation, 🧠 Simulator Scheduling Insight, ⚠ Common Industry Mistake callouts omitted — this is a pure-style sub-topic with no timing or scheduling angle, and the "Common Whitespace Mistakes" section covers the industry-mistake territory in higher density.