Verilog · Chapter 4.6 · Lexical Conventions
Identifier Declaration in Verilog
Every reg, wire, module, parameter, instance, and port in Verilog carries a name, and those names live under a strict set of lexer rules. This lesson covers the two identifier forms, simple and escaped, along with the rules for the first character and allowed characters, the length limit, and the fact that Verilog is case sensitive. It walks through the reserved-keyword list you cannot use as names, the collision between the dollar-sign prefix and built-in system task names, and how hierarchical references reach into instances. It also covers the industry naming conventions, such as snake case for signals, all caps for parameters, and an active-low suffix, that keep RTL readable and help it survive code review across teams and companies.
Foundation14 min readVerilogIdentifiersNamingSyntax
Chapter 4 · Page 4.6 · Lexical Conventions
The Two Forms of Identifier
IEEE 1364 §3.7 defines exactly two ways to name anything in Verilog:
- Simple identifiers — what you write 99% of the time. Letters, digits,
_,$. First character must be a letter or underscore. - Escaped identifiers — backslash-prefixed, whitespace-terminated. Used almost exclusively by EDA tools to name nets after backslash, brackets, or other characters that aren't legal in simple form.
reg [7:0] data_q; // ✅ simple identifier
reg [7:0] \reg[7] ; // ✅ escaped identifier (note trailing space)Both are valid Verilog. Hand-written RTL uses the simple form exclusively; escaped identifiers exist for synthesis-tool round-tripping (where bit-blasted nets carry names like \data[7] ).
Simple Identifier Rules
| Rule | Allowed | Disallowed |
|---|---|---|
| First character | a–z A–Z _ | 0–9 $ |
| Subsequent characters | a–z A–Z 0–9 _ $ | Anything else (-, ., space, /, + …) |
| Length | Up to 1024 chars (IEEE-mandated minimum) | — |
| Case | Case-sensitive (rst ≠ Rst ≠ RST) | — |
| Reserved | Cannot collide with a Verilog keyword | (wire, reg, module …) |
| Prefix | Cannot begin with $ (system-task namespace) | $myname |
// ✅ legal
reg data_q;
reg [3:0] count_4b;
wire rst_n;
parameter WIDTH = 8;
module fifo_8x16 (...);
reg _internal; // leading underscore is fine
reg a$b; // dollar sign legal mid-name (but discouraged)
// ❌ illegal — would not parse
reg 8th_bit; // starts with a digit
reg data-q; // hyphen not in the character class
reg $myname; // dollar-prefix reserved for system tasks
reg wire; // keywordThe First-Character Rule
The lexer needs to distinguish identifiers from numeric literals. The mechanism is the first-character rule: identifiers start with a letter or _; numeric literals start with a digit. There's no ambiguity at the lexer level — 8data parses as the numeric literal 8 followed by the identifier data, which is a syntax error in most contexts.
The dollar-sign exclusion is similar — $ introduces the system-task namespace ($display, $time, $random, $finish, $assertoff, …). User identifiers cannot start with $ to keep that namespace unambiguous.
Length Limit
IEEE 1364 mandates a minimum implementation length of 1024 characters. Every tool you'll meet honours that — and most allow longer names without complaint. In practice, the cost of long names is human readability, not tool support; the convention is to keep identifiers under 40 characters.
Case Sensitivity
Verilog is fully case-sensitive:
reg rst; // one identifier
reg Rst; // a different identifier
reg RST; // a third identifierThis is opposite to VHDL (case-insensitive) and is a frequent source of porting bugs. Adopt one casing convention per project and lint for it.
Escaped Identifiers
The escaped form starts with \ and ends at the next whitespace character (space, tab, newline). Every printable ASCII character between the \ and the terminating whitespace is part of the identifier name — including characters that would otherwise be operators or punctuation.
wire \data[7] = some_signal; // identifier is data[7]
wire \and = a & b; // identifier is "and" (would be a keyword as simple form)
wire \net.7 = x; // identifier is "net.7" (dot legal inside escaped form)
wire \1st_bit = sig; // identifier is "1st_bit" (digit-leading OK here)Key behaviour points:
- The
\and the terminating whitespace are not part of the identifier.\datamatches the same name asdata. - The terminating whitespace must be there — the lexer keeps reading non-whitespace into the name.
- Two consecutive backslashes do not escape — the second backslash is just another character in the name.
- Escaped identifiers can be referenced by either form: declare
\rst-nand write\rst-nagain to reference it. There is no simple-form equivalent — once a name needs escaping, every reference needs it too.
In hand-written RTL you almost never need escaped identifiers. They show up in three places: bit-blasted post-synthesis netlists, third-party IP that names ports after a vendor's house style, and gate-level back-annotated SDF files.
Reserved Words
A name that matches any Verilog keyword cannot be used as a simple identifier. The keyword list is closed (IEEE 1364-2005 Annex B); some common collisions:
| Often-mistyped | Why it collides |
|---|---|
wire / reg | Net / variable type keywords |
module / endmodule | Block boundary keywords |
input / output / inout | Port direction keywords |
assign | Continuous-assignment keyword |
always / initial | Procedural-block keywords |
posedge / negedge | Event-control keywords |
if / else / case / default | Control-flow keywords |
begin / end | Block-delimiter keywords |
function / task / endfunction / endtask | Subprogram keywords |
parameter / localparam | Compile-time-constant keywords |
signed / unsigned | Signed-ness modifier keywords |
and / or / not / xor | Gate-primitive keywords |
time / integer / real | Variable-type keywords |
force / release | Testbench-override keywords |
Common workarounds when a domain word collides with a keyword:
time_qinstead oftimefunc_idinstead offunctioninput_datainstead ofinputcase_idinstead ofcase
You can use the escaped form (\time ) to bypass this, but doing so in RTL is a code-smell — the next reader has to wonder why a keyword name was forced.
The $ Prefix and System Tasks
The $ character is legal inside an identifier but reserved at the start. The reservation is what gives system tasks a private namespace:
$display("..."); // system task
$time; // system function
$random; // system function
$assertoff; // system task
$realtime; // system functionUser identifiers may contain $ in the middle (a$b, clk$gated) but the lexical convention in industry is to avoid $ in user names entirely — readers expect $-prefixed tokens to be PLI / VPI / built-in.
Hierarchical References
Verilog gives every named scope a path: module instance → sub-module instance → register / wire. Dot-separated hierarchical references navigate that path:
// In the testbench
initial begin
$display("data_q = %h", tb.dut.regfile.r0);
force tb.dut.alu.result = 32'h_DEAD_BEEF;
#100 release tb.dut.alu.result;
endEach segment of the dot path is itself an identifier — same lexer rules apply per segment. Hierarchical references work into and across all named scopes (modules, generate blocks, begin : label blocks) but cannot reach into anonymous blocks.
Important: hierarchical references in synthesisable RTL are typically disallowed (most synthesis tools reject them outright). They are testbench / debug-only tooling.
Industry Naming Conventions
These are not language rules — they are the conventions that survive code review at ARM, Apple, NVIDIA, Intel, Synopsys, and most ASIC houses.
| Pattern | Used for | Example |
|---|---|---|
snake_case | Wires, regs, modules, ports, instances | addr_valid_q, axi_master, u_fifo |
ALL_CAPS_SNAKE | Parameters and localparam | DATA_WIDTH, FIFO_DEPTH, RESET_VAL |
| ALL_CAPS macros | `define macros | `define DEBUG_LEVEL 2 |
_n suffix | Active-low signals | rst_n, enable_n, cs_n |
_q suffix | Registered (flop output) signal | data_q, count_q |
_d suffix | Pre-registered (D-input) signal | data_d, count_d |
_i / _o suffix | Module port direction (some shops) | data_i, valid_o |
u_ prefix | Module instance name | u_fifo, u_alu |
i_ prefix (alt) | Module instance name (alt shops) | i_fifo |
g_ prefix | Generate-block label | g_byte_lanes |
tb_ prefix | Testbench artifact | tb_top, tb_clk |
Mixing conventions is the leading cause of code-review friction. Pick one set for the project and lint for them. The u_ / _n / _q triad is the most widespread combination in modern RTL.
module pipe_register #(
parameter DATA_WIDTH = 32 // ALL_CAPS for parameter
) (
input wire clk,
input wire rst_n, // _n for active-low
input wire [DATA_WIDTH-1:0] data_d, // _d for pre-flop
output reg [DATA_WIDTH-1:0] data_q // _q for post-flop
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
data_q <= {DATA_WIDTH{1'b0}};
else
data_q <= data_d;
end
endmodule
module top;
wire [31:0] payload_d, payload_q;
pipe_register #(.DATA_WIDTH(32)) u_pipe ( // u_ prefix for instance
.clk (clk),
.rst_n (rst_n),
.data_d (payload_d),
.data_q (payload_q)
);
endmoduleCommon Mistakes
Five identifier-related production bugs.
- Trailing-whitespace amnesia on escaped identifiers.
\rst_n(no trailing space) does not terminate — the next non-whitespace character is appended to the name. Always write a literal space before the next token. - Case-mismatch porting bugs.
rstandRstare distinct in Verilog but identical in VHDL. RTL ported from VHDL inherits the wrong assumption — lint case-collisions early. - Keyword shadowing in escaped form.
\regis a legal identifier with the namereg. Tools handle it correctly but every human reader does a double-take. Never use escaped form to alias a keyword. - Hierarchical references in synthesisable code. Many tools accept them and silently produce wrong gates (or refuse compilation). Keep hierarchical references in testbench files (
initial,$display,force/release). - The
$prefix collision. A user identifier$mysigis rejected by every Verilog lexer. Mid-name$(e.g.clk$gated) is legal but ambiguous with PLI conventions in some toolchains — avoid in new code.
Worked Example — Naming Discipline in a Tiny Module
A small synchronous register module that applies every naming convention above, demonstrating how a 30-line module communicates intent through naming alone.
//-----------------------------------------------------------
// sync_reg — synchronous register with synchronous load enable
// conventions:
// parameters : ALL_CAPS
// ports : snake_case
// active-low signals : _n suffix
// pre-flop / post-flop pairs : _d / _q suffix
// local constants : ALL_CAPS via localparam
//-----------------------------------------------------------
module sync_reg #(
parameter WIDTH = 8,
parameter [WIDTH-1:0] RESET_VAL = {WIDTH{1'b0}}
) (
input wire clk, // clock
input wire rst_n, // async active-low reset
input wire load_en, // synchronous load enable
input wire [WIDTH-1:0] data_d, // input (D)
output reg [WIDTH-1:0] data_q // output (Q)
);
localparam STATE_RESET = 1'b0; // local constants ALL_CAPS
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
data_q <= RESET_VAL;
else if (load_en)
data_q <= data_d;
end
endmoduleWhat's right about this:
WIDTHandRESET_VAL(ALL_CAPS) signal compile-time constants.clk/rst_n(snake_case +_nsuffix) signal runtime signals, active-low for reset.data_d/data_qpair signals combinational input / registered output — the convention makes timing-paths self-documenting.load_en(snake_case) reads as a verb-phrase, signalling intent.- No escaped identifiers; no keyword collisions; everything one-pass-readable.
Interview Insights
Summary
Verilog identifiers come in two lexical forms — simple (the everyday name) and escaped (for tool-generated edge cases). The simple form requires a letter or _ first character, allows letters / digits / _ / $ afterward, is fully case-sensitive, and reserves both the keyword list and the $-prefix namespace. The escaped form (\ … whitespace) lifts every character-class restriction and is reserved for post-synthesis netlists and back-annotated tool output. Layered on top of the lexer rules are the industry naming conventions — snake_case for signals, ALL_CAPS for parameters, _n for active-low, _d / _q for D/Q flop pairs, u_ for instance names — which are not language requirements but are the difference between RTL that passes code review and RTL that doesn't. With identifiers covered, the lexical-conventions chapter wraps up — the next sub-topic (4.7 lex-keywords) drills into the complete keyword list and the reservation rules across the IEEE 1364 / 1800 boundary.
Beat coverage notes: all required beats present. ⏱ Timing Observation, 🧠 Simulator Scheduling Insight omitted — identifiers are pure lexer rules; no timing or scheduling angle. The 🏗 Synthesis Concern documents the post-synthesis escaped-identifier names that appear in gate-level netlists.
Related Tutorials
- Lexical Conventions — Chapter 4 overview; the parent layer.
- Number Representation — Chapter 4.4; the literal-syntax companion to identifier syntax.
- String Handling — Chapter 4.5; the other lexer-level data carrier.
- Operator Usage — Chapter 4.3; the operator-vs-identifier disambiguation the lexer relies on.
- RTL Designing — Chapter 3; where the naming conventions earn their keep across a full module.