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 (
0x0ALF,0x0DCR, or both as CRLF) - form feeds (
0x0C)
Whitespace serves exactly two purposes in the language:
- 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.
- 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.
// ✅ 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.
// ✅ 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 3Rule 3 — Around operators is OPTIONAL
Most operators self-delimit. The lexer matches the longest operator that fits, then resumes scanning.
// ✅ 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; // canonicalException: 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.
// ✅ 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 NOTUnderscores 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.
// ❌ 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.
| Style | Common in | Configured by |
|---|---|---|
| 4 spaces | Apple, Intel, AMD, Qualcomm | .editorconfig · most lint tools |
| 2 spaces | Some startups, JavaScript-influenced teams | .editorconfig |
| Hard tabs | Older codebases (1990s-early-2000s heritage) | varies |
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
endmoduleThe 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.
// ❌ 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
);
endmoduleAlignment 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.
// ❌ 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.
| Platform | Default 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:
- 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. - 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.
$readmemh/$readmembmay not strip CR. Adata.hexfile with CRLF line endings can produce a hex byte of0x0Dat the end of each line on simulators that don't strip CR. Use LF-only for any file read by$readmemh/$readmemb.
# 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 binaryTabs vs Spaces
The religious war of source code. The actual engineering trade-offs:
| Aspect | Tabs | Spaces |
|---|---|---|
| Width displayed | Reader's choice (editor setting) | Fixed |
| File size | Smaller (1 tab vs 4 spaces) | Larger |
| Alignment | Breaks if mixed with spaces | Always consistent |
| Diff readability | Inconsistent across viewers | Consistent everywhere |
| Industry trend | Declining (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.
$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.
// ✅ Idiomatic
`timescale 1ns/1ps
`default_nettype none
`define WIDTH 8
// ❌ Legal but unreadable
`timescale 1ns/1ps `default_nettype none `define WIDTH 8Common Mistakes
Five real failure modes that hit beginners and experienced engineers alike.
- Mixing tabs and spaces in the same file. Visual alignment looks fine in one editor; broken in another. The fix: set up
.editorconfigand have your editor "show whitespace" mode on. - 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).
- 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. - 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. - Embedded newlines in string literals. Some simulators accept, some don't. Always use
\nescapes 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.
| Setting | Value |
|---|---|
| Indentation | 4 spaces |
| Tabs in source | forbidden — flagged by lint |
| Line ending | LF only |
| Maximum line length | 100 columns (soft), 120 columns (lint flag) |
| Trailing whitespace | forbidden — flagged by lint |
| Newline at end of file | required |
| Blank lines between modules | 2 |
| Blank lines between functions / tasks | 1 |
Blank line inside a module before endmodule | 1 |
Spaces around =, <=, ==, !=, etc. | 1 each side |
Spaces inside (...), [...], {...} | none |
`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
endmoduleMemorise 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.
Related Tutorials
- Lexical Conventions — Chapter 4 overview; the parent layer this sub-topic lives in.
- RTL Designing — Chapter 3; the layer above the lexical rules.