Verilog syntax defines the lexical conventions and structural rules for writing hardware description code. Understanding these fundamental elements--from operators and number formats to identifiers and keywords--is essential for writing correct, readable RTL designs that synthesize properly.
10 min read | Beginner
What You'll Learn
- Use proper commenting styles and understand whitespace handling in Verilog
- Specify numbers in different bases (binary, hex, octal) with correct sizing
- Follow identifier naming rules and avoid reserved keywords
- Apply unary, binary, and ternary operators correctly in expressions
Lexical Conventions
Lexical conventions in Verilog are similar to C in the sense that it contains a stream of tokens. A lexical token may consist of one or more characters and tokens can be comments, keywords, numbers, strings or white space. All lines should be terminated by a semi-colon ;.
Verilog is case-sensitive, so var_a and var_A are different identifiers. This is a common source of bugs when referencing signals.
Comments
There are two ways to write comments in Verilog:
- A single line comment starts with
//and tells Verilog compiler to treat everything after this point to the end of the line as a comment. - A multiple-line comment starts with
/*and ends with*/and cannot be nested.
However, single line comments can be nested in a multiple line comment.
// This is a single line comment
// Creates an int variable called a
integer a; // Everything to the right of // is ignored by compiler
/*
This is a
multiple-line or
block comment
All of this text is ignored by the compiler
*/
/* This is /*
an invalid nested
block comment */ // ERROR: The first */ closes the comment
*/ // This becomes a syntax error
/* However,
// single-line comments inside block comments are okay
// because // doesn't end the block comment
*/
// This is also okay
///////////// Still a valid comment - multiple // in a row
Use single-line comments (//) for brief explanations and block comments (/* */) for disabling large sections of code during debugging.
Whitespace
White space is a term used to represent the characters for spaces, tabs, newlines and formfeeds, and is usually ignored by Verilog except when it separates tokens. In fact, this helps in the indentation of code to make it easier to read.
module dut; // 'module' is a keyword, 'dut' is an identifier
reg [8*6:1] name = "Hello!"; // The 2 spaces at the start are ignored (indentation)
// Multiple spaces between tokens are treated as single space
wire a; // Same as: wire a;
wire b; // Same as: wire b;
endmodule
However blanks (spaces) and tabs (from TAB key) are not ignored in strings. In the example below, the string variable called addr gets the value "Earth " because of preservation of spaces in strings.
module string_example;
// There is no space in the beginning of this line (whitespace ignored)
// But there IS a space inside the string (preserved)
reg [8*6:1] addr = "Earth "; // String contains 6 characters: E-a-r-t-h-[space]
reg [8*5:1] city = "Tokyo"; // String contains 5 characters (no trailing space)
endmodule
Operators
There are three types of operators: unary, binary, and ternary or conditional.
- Unary operators shall appear to the left of their operand
- Binary operators shall appear between their operands
- Conditional operators have two separate operators that separate three operands
// Unary operator: operates on one operand
x = ~y; // ~ is a unary operator (bitwise NOT), y is the operand
// Binary operator: operates on two operands
x = y | z; // | is a binary operator (bitwise OR), y and z are operands
// Ternary operator: conditional expression with 3 operands
x = (y > 5) ? w : z; // ?: is ternary operator
// Operand 1: (y > 5) - condition
// Operand 2: w - value if true
// Operand 3: z - value if false
If the expression (y > 5) is true, then variable x will get the value in w, else the value in z.
Real-World Application
Conditional Multiplexer Logic: The ternary operator synthesizes into multiplexer hardware and is extensively used in RTL design at companies like Intel and AMD for conditional signal routing. For example, in a CPU cache controller, hit = (tag_match) ? cache_data : memory_data; creates a 2:1 mux that selects between cached data and memory fetches based on tag comparison.
Number Format
We are most familiar with numbers being represented as decimals. However, numbers can also be represented in binary, octal and hexadecimal. By default, Verilog simulators treat numbers as decimals. In order to represent them in a different radix, certain rules have to be followed.
16 // Number 16 in decimal (default) 0x10 // Number 16 in hexadecimal (C-style notation - NOT standard Verilog) 10000 // Number 16 in binary (but Verilog treats this as decimal 10000!) 20 // Number 16 in octal (but Verilog treats this as decimal 20!)
The examples above show common representations, but in Verilog, you must use the base format specifiers shown below to indicate non-decimal bases.
Syntax
Sized Numbers
Sized numbers are represented as shown below, where size is written only in decimal to specify the number of bits in the number.
[size]'[base_format][number]
// Where:
// size - Number of bits (decimal only)
// base_format - 'b (binary), 'd (decimal), 'h (hex), 'o (octal)
// number - The value in the specified base
- base_format can be either binary ('b or 'B), decimal ('d or 'D), hexadecimal ('h or 'H), or octal ('o or 'O) and specifies what base the number part represents.
- number is specified as consecutive digits from 0, 1, 2 ... 9 for decimal base format and 0, 1, 2 .. 9, A, B, C, D, E, F for hexadecimal.
// BINARY representations
3'b010; // size=3 bits, binary, value=2 (010 in binary)
8'b1111_0000; // size=8 bits, binary, value=240 (underscore for readability)
// DECIMAL representations
3'd2; // size=3 bits, decimal, value=2
8'd234; // size=8 bits, decimal, value=234
8'D234; // Legal to use uppercase 'D'
// HEXADECIMAL representations
8'h70; // size=8 bits, hex, value=0x70 (decimal 112)
9'h1FA; // size=9 bits, hex, value=0x1FA (decimal 506)
32'hFACE_47B2; // size=32 bits, underscore separates for readability
// OCTAL representations
4'o12; // size=4 bits, octal, value=12 (octal) = 10 (decimal)
// All represent the same value (decimal 10):
4'hA = 4'd10 = 4'b1010 = 4'o12
Uppercase letters are legal for hexadecimal numbers:
16'hcafe; // lowercase letters - Valid
16'hCAFE; // uppercase letters - Valid
32'h1D40_CAFE; // underscore separator every 4 digits - Valid (improves readability)
Use underscores to separate long binary or hex numbers every 4 bits for readability: 32'hDEAD_BEEF is easier to parse than 32'hDEADBEEF.
Unsized Numbers
Numbers without a base_format specification are decimal numbers by default. Numbers without a size specification have a default number of bits depending on the type of simulator and machine (typically 32 bits).
integer a = 5423; // No base format -> treated as decimal 5423
integer b = 'h1AD7; // No size -> defaults to machine width (usually 32 bits)
// Value stored: 32'h0000_1AD7
reg [15:0] c = 16; // Unsized decimal 16 assigned to 16-bit register
// Stored as: 16'h0010
Avoid unsized numbers in RTL code! Always specify size explicitly to prevent unexpected bit-width mismatches during synthesis.
Negative Numbers
Negative numbers are specified by placing a minus - sign before the size of a number. It is illegal to have a minus sign between base_format and number.
-6'd3; // 6-bit negative number stored as two's complement of 3
// Binary representation: 111101 (two's complement)
-6'sd9; // Signed decimal: 's' indicates signed arithmetic
8'd-4; // ILLEGAL - minus sign must come before size
-'d4; // Legal (unary minus on an unsized number), but unsized values should be avoided in RTL
Real-World Application
Fixed-Point Arithmetic: In DSP (Digital Signal Processing) designs at companies like Qualcomm and Broadcom, negative number representation is critical for signed arithmetic operations. Audio codecs, image processing filters, and wireless baseband processors extensively use two's complement signed numbers for operations like FFT (Fast Fourier Transform) and FIR (Finite Impulse Response) filtering. Proper sizing and sign handling prevents overflow errors in production silicon.
Strings
A sequence of characters enclosed in a double quote " " is called a string. It cannot be split into multiple lines and every character in the string takes 1-byte to be stored.
"Hello World!" // String with 12 characters -> requires 12 bytes (96 bits)
"x + z" // String with 5 characters (spaces count!)
// String storage in register
reg [8*12:1] message = "Hello World!"; // 12 characters x 8 bits = 96 bits
// ILLEGAL - string cannot span multiple lines
"How are you
feeling today ?" // Syntax error: newline not allowed in string
To store a string in a register, size it as [8*N:1] where N is the number of characters. Each character requires 8 bits (1 byte).
Identifiers
Identifiers are names of variables so that they can be referenced later on. They are made up of alphanumeric characters [a-z][A-Z][0-9], underscores _ or dollar sign $ and are case sensitive. They cannot start with a digit or a dollar sign.
Valid Identifier Rules:
- Must start with a letter (
a-z,A-Z) or underscore (_) - Can contain letters, digits, underscores, and dollar signs (
$) - Cannot start with a digit or dollar sign
- Case-sensitive:
signalandSignalare different - Cannot be a reserved keyword
// VALID identifiers
integer var_a; // Contains alphabets and underscore
integer v$ar_a; // Dollar sign in middle is allowed
integer var23_g; // Alphanumeric characters with underscore
integer _internal; // Can start with underscore
wire clk_divider_2; // Descriptive name with numbers
// INVALID identifiers
integer $var_a; // Cannot start with $
integer 2var; // Cannot start with digit
integer 23; // Cannot be only numbers
integer my-signal; // Hyphens not allowed (use underscore instead)
Naming Convention Best Practice: Use descriptive names with underscores for readability: data_valid instead of dv, mem_write_enable instead of mwe. This improves code maintainability in large design teams.
Keywords
Keywords are special identifiers reserved to define the language constructs and are in lower case. A list of important keywords is given below.
Keywords are case-sensitive and must be lowercase. Writing MODULE or Module will be treated as identifiers, not the module keyword.
Verilog Revisions
Verilog has undergone a few revisions over the years and more additions have been made from 1995 to 2001 which is shown below.
Number Format Examples
| Verilog Notation | Decimal Value | Binary Representation | Description |
|---|---|---|---|
4'b1010 | 10 | 1010 | 4-bit binary |
4'd10 | 10 | 1010 | 4-bit decimal |
4'hA | 10 | 1010 | 4-bit hexadecimal |
4'o12 | 10 | 1010 | 4-bit octal |
8'hFF | 255 | 11111111 | 8-bit hex (all ones) |
-4'd5 | -5 | 1011 | 4-bit signed (two's complement) |
Common Beginner Mistakes
Mistake #1: Forgetting the base format specifier
reg [7:0] data = 11110000; // WRONG: Treated as decimal 11,110,000 (truncated)
Why it's problematic: Without the base format ('b, 'd, 'h, 'o), Verilog treats the number as decimal. The value 11110000 in decimal is far larger than 8 bits can hold, resulting in truncation and unexpected values.
Best Practice: Always specify the base: reg [7:0] data = 8'b1111_0000; (decimal 240)
Mistake #2: Mismatched size and value
reg [3:0] counter = 4'b11111; // 5-bit value assigned to 4-bit register
Why it's problematic: The value 4'b11111 has 5 bits but declares size as 4. The leftmost bit is truncated, resulting in 4'b1111 (15) instead of the intended value. This can cause silent bugs that are hard to debug.
Best Practice: Ensure bit count matches declared size: reg [3:0] counter = 4'b1111;
Mistake #3: Using hyphens instead of underscores in identifiers
wire data-valid; // SYNTAX ERROR
wire clk-divider; // SYNTAX ERROR
Why it's problematic: Verilog interprets hyphens as the subtraction operator, not as part of an identifier name. data-valid is parsed as "data minus valid" which is a syntax error in a declaration context.
Best Practice: Use underscores: wire data_valid; and wire clk_divider;
Mistake #4: Case sensitivity errors
wire Enable;
// ... 50 lines later ...
assign enable = 1'b1; // Creates NEW signal 'enable' instead of assigning to 'Enable'
Why it's problematic: Verilog is case-sensitive. Enable and enable are completely different signals. This creates hard-to-find bugs where you think you're driving a signal but it remains unconnected.
Best Practice: Use consistent naming conventions (all lowercase or camelCase) and enable compiler warnings for unused signals.
Practice Exercise
Challenge: Fix the Syntax Errors
The following Verilog code has 8 syntax errors related to identifiers, number formats, and operators. Find and fix all errors to make the code compile correctly:
Module counter_8bit; // Error 1: ?
reg clk;
reg [7:0] 8count; // Error 2: ?
reg [3:0] value = 10101; // Error 3: ?
wire reset-signal; // Error 4: ?
integer STEP = 4'd20; // Error 5: ?
reg [7:0] max_val = 8'hGG; // Error 6: ?
always @(posedge clk)
8count = ~8count; // Error 7: ? (same as Error 2)
wire output = (value > 5) : 1'b1 ? 1'b0; // Error 8: ?
endmodule
Hint
Look for:
- Keyword capitalization (should be lowercase)
- Identifiers starting with digits
- Missing base format specifiers
- Invalid characters in identifiers (hyphens)
- Invalid hex digits
- Incorrect ternary operator syntax
Solution
module counter_8bit; // FIX 1: 'module' must be lowercase (keyword)
reg clk;
reg [7:0] count_8; // FIX 2: Identifier cannot start with digit
reg [3:0] value = 4'b1010; // FIX 3: Must specify base and size (4 bits needed)
wire reset_signal; // FIX 4: Use underscore, not hyphen
integer STEP = 20; // FIX 5: 4'd20 does not fit in 4 bits and would be truncated to 4
// Or use: reg [4:0] STEP = 5'd20;
reg [7:0] max_val = 8'hFF; // FIX 6: 'GG' invalid hex (only 0-9, A-F allowed)
always @(posedge clk)
count_8 = ~count_8; // FIX 7: Use corrected identifier
wire output_w = (value > 5) ? 1'b1 : 1'b0; // FIX 8: Correct ternary syntax
// Also renamed to avoid 'output' keyword
endmodule
Explanation of Each Fix:
Module->module: Keywords must be lowercase8count->count_8: Identifiers cannot start with a digit10101->4'b1010: Need base format ('b) and correct size (4 bits for value 10)reset-signal->reset_signal: Hyphens not allowed, use underscore4'd20->20: The value 20 needs 5 bits, so a 4-bit size truncates it to 4. An integer is 32 bits wide, so the plain value 20 is correct8'hGG->8'hFF: 'G' is not a valid hex digit (0-9, A-F only)- Use corrected identifier
count_8 (value > 5) : 1'b1 ? 1'b0->(value > 5) ? 1'b1 : 1'b0: Ternary operator syntax iscondition ? true_val : false_val
Real-World Application: In production RTL development at companies like Apple and NVIDIA, syntax errors like these are caught by linters and code review tools before simulation. However, understanding these rules prevents errors during initial coding and speeds up the design cycle. Modern EDA tools (Synopsys VCS, Cadence Xcelium) provide detailed syntax error messages, but knowing the rules helps you write clean code on the first try.
Summary
In this tutorial, you learned:
- Verilog is case-sensitive and uses semicolons to terminate statements, with two comment styles: single-line (
//) and block (/* */) - Number format syntax is
[size]'[base][value]where base can be binary ('b), decimal ('d), hexadecimal ('h), or octal ('o) - Identifiers must start with a letter or underscore, can contain alphanumerics and
$, and cannot be reserved keywords - Three operator types exist: unary (left of operand), binary (between operands), and ternary (conditional
?:) - Whitespace is ignored except in strings, and negative numbers require the minus sign before the size specification
- All keywords are lowercase and reserved, while strings are enclosed in double quotes and cannot span multiple lines