Skip to content

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:

  1. Simple identifiers — what you write 99% of the time. Letters, digits, _, $. First character must be a letter or underscore.
  2. 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.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

RuleAllowedDisallowed
First charactera–z A–Z _0–9 $
Subsequent charactersa–z A–Z 0–9 _ $Anything else (-, ., space, /, + …)
LengthUp to 1024 chars (IEEE-mandated minimum)
CaseCase-sensitive (rstRstRST)
ReservedCannot collide with a Verilog keyword(wire, reg, module …)
PrefixCannot begin with $ (system-task namespace)$myname
simple-ids.v — what passes the lexer
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ✅ 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;                 // keyword

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reg rst;             // one identifier
reg Rst;             // a different identifier
reg RST;             // a third identifier

This 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.

escaped-ids.v — when you actually see these
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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. \data matches the same name as data.
  • 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-n and write \rst-n again 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-mistypedWhy it collides
wire / regNet / variable type keywords
module / endmoduleBlock boundary keywords
input / output / inoutPort direction keywords
assignContinuous-assignment keyword
always / initialProcedural-block keywords
posedge / negedgeEvent-control keywords
if / else / case / defaultControl-flow keywords
begin / endBlock-delimiter keywords
function / task / endfunction / endtaskSubprogram keywords
parameter / localparamCompile-time-constant keywords
signed / unsignedSigned-ness modifier keywords
and / or / not / xorGate-primitive keywords
time / integer / realVariable-type keywords
force / releaseTestbench-override keywords

Common workarounds when a domain word collides with a keyword:

  • time_q instead of time
  • func_id instead of function
  • input_data instead of input
  • case_id instead of case

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$display("...");        // system task
$time;                  // system function
$random;                // system function
$assertoff;             // system task
$realtime;              // system function

User 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:

hierarchy.v — dot-notation across scopes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;
end

Each 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.

PatternUsed forExample
snake_caseWires, regs, modules, ports, instancesaddr_valid_q, axi_master, u_fifo
ALL_CAPS_SNAKEParameters and localparamDATA_WIDTH, FIFO_DEPTH, RESET_VAL
ALL_CAPS macros`define macros`define DEBUG_LEVEL 2
_n suffixActive-low signalsrst_n, enable_n, cs_n
_q suffixRegistered (flop output) signaldata_q, count_q
_d suffixPre-registered (D-input) signaldata_d, count_d
_i / _o suffixModule port direction (some shops)data_i, valid_o
u_ prefixModule instance nameu_fifo, u_alu
i_ prefix (alt)Module instance name (alt shops)i_fifo
g_ prefixGenerate-block labelg_byte_lanes
tb_ prefixTestbench artifacttb_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.

naming-applied.v — every convention in one place
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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)
    );
endmodule

Common Mistakes

Five identifier-related production bugs.

  1. 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.
  2. Case-mismatch porting bugs. rst and Rst are distinct in Verilog but identical in VHDL. RTL ported from VHDL inherits the wrong assumption — lint case-collisions early.
  3. Keyword shadowing in escaped form. \reg is a legal identifier with the name reg. Tools handle it correctly but every human reader does a double-take. Never use escaped form to alias a keyword.
  4. 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).
  5. The $ prefix collision. A user identifier $mysig is 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.

rtl/sync_reg.v — naming discipline applied
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//-----------------------------------------------------------
// 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
 
endmodule

What's right about this:

  • WIDTH and RESET_VAL (ALL_CAPS) signal compile-time constants.
  • clk / rst_n (snake_case + _n suffix) signal runtime signals, active-low for reset.
  • data_d / data_q pair 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.

  • 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.