Verilog · Chapter 4 · Foundations
Lexical Conventions in Verilog
Before a simulator or synthesis tool can make sense of your design, a lexer breaks the source text into small pieces called tokens. Every Verilog file is built from a handful of token kinds, including whitespace, comments, identifiers, keywords, numbers, strings, and operators. This chapter is a working engineer's map of those categories. It shows where each one appears in real RTL, what the language actually requires, what the tools quietly forgive, and what they refuse to accept. You will learn the small set of lexical habits that separate clean, production-grade Verilog from code that happens to simulate but causes trouble later. Each category links to a dedicated sub-page that drills into it deeper.
Foundation20 min readVerilogLexicalSyntaxIdentifiersNumbersOperators
Chapter 4 · Page 1.4 · Foundations
1. The Engineering Problem
You open a Verilog file from a colleague and the first ten lines are this:
`timescale 1ns/1ps
`default_nettype none
// AXI4-Lite slave — controls the PHY reset state machine.
// Owner: alice@example.com · Spec: docs/phy_ctrl/spec.md
module phy_ctrl #(
parameter int CFG_W = 32,
parameter [CFG_W-1:0] RESET_VALUE = 32'hDEAD_BEEF
) (
input wire s_axi_aclk,Before any RTL meaning exists, the simulator and the synthesiser have to parse that text. They do it the same way every compiler does: a lexer scans the source one character at a time, recognises whitespace, comments, identifiers, keywords, numbers, strings, operators, and punctuation, and produces a sequence of tokens that the parser then assembles into modules, ports, and statements.
The lexer is the first thing your code talks to. If your code violates the lexer's rules — a number written as 32hDEADBEEF instead of 32'hDEADBEEF, an identifier starting with a digit, a `define placed inside a module block, an unescaped special character in a string — the simulator and synthesiser stop at the first failed lex, before any of your design intent gets considered.
This chapter is the working engineer's map of the lexer's vocabulary: every token category, what the spec requires, what the tools accept, what they don't, and the small set of lexical habits that distinguish production-grade Verilog from code that almost works.
2. Why Lexical Conventions Matter
Three reasons every working RTL engineer carries the lexical layer in their head:
- The lexer is the first gate. Every downstream tool — simulator, synthesiser, lint, formal — runs the same lexer on the same source. A lexical error stops every tool at the same point. A clean lexical layer is the cheapest verification you do.
- The lexer hides bugs in plain sight.
32'hDEAD_BEEFis the same as32'hDEADBEEF(underscores are spacers).32'd16is not the same as32d16(the apostrophe is mandatory). Every typo that the lexer accepts becomes a downstream functional bug that lives in the netlist. Knowing what the lexer will accept is how you avoid encoding bugs as legal numbers. - The lexer reveals project conventions. A glance at a colleague's file tells you whether they're using
`default_nettype none, whether they're using parametric widths, whether their comments are doxygen-style, whether their identifiers follow snake_case or camelCase. The lexical layer is the visible part of the project's coding standard.
A lint failure on lexical issues is the cheapest CI failure your project will ever have. Every tool flags lexical issues at parse time, before simulation even starts, before synthesis loads a library. Get the lexical layer right and every later stage runs faster.
3. Mental Model
The working test for any lexical question: what would the lexer's output token stream be for this input? If you can answer that, you know whether the input is legal and what the tools will see.
4. Visual Explanation — Lex → Parse → Elaborate → Generate
The Verilog source-to-netlist pipeline has the lexer at its front:
The Verilog compilation pipeline — where the lexer fits
data flowThe seven token categories the IEEE 1364 lexer recognises:
| Token category | What it is | Sub-page |
|---|---|---|
| Whitespace | Spaces, tabs, newlines, carriage returns — separators with no semantic value | 4.1 White Space Requirements |
| Comments | // single-line and /* multi-line */ — ignored by the parser | 4.2 Comment Implementation |
| Operators | +, -, *, &&, <<, ===, … — the arithmetic / logical / relational set | 4.3 Operator Usage |
| Numbers / Literals | 8'hA5, 32'b1010_0011, 1'b0, 100, 1.5e-9 — sized and unsized literals | 4.4 Number Representation |
| Strings | "hello, world" — character sequences inside double quotes | 4.5 String Handling |
| Identifiers | Names you choose — clk, data_q, payload[7:0] — letters / digits / _ / $ | 4.6 Identifier Declaration |
| Keywords | Reserved words — module, always, assign, wire, reg, … | 4.7 Keyword Usage |
Plus two cross-cutting families:
- System tasks and functions — names that begin with
$($display,$monitor,$random,$readmemh). Always testbench / tool-controlled. - Compiler directives — names that begin with a backtick (
`define,`include,`timescale,`default_nettype). Processed by a preprocessor pass that runs before the lexer-proper.
The rest of this chapter walks each category, links to the deep-dive sub-page, and notes the working-engineer detail every reviewer expects you to know.
5. Whitespace, Comments, and Newlines
Verilog is whitespace-insensitive. Spaces, tabs, and newlines separate tokens but carry no other semantic meaning — assign y=a&b; is identical to assign y = a & b; to the lexer. The convention that working RTL houses follow is to use whitespace generously for readability:
// Cramped — legal but unreadable
assign y=a&b;
always@(posedge clk)q<=d;
// Industry style — generous, scannable
assign y = a & b;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= 1'b0;
else q <= d;
endComments come in two flavours:
// Single-line comment — runs to end of line
assign y = a & b; // can also appear at end of line
/*
* Multi-line comment.
* Cannot nest — /* this would not work */
*/The lexer strips all comments before passing tokens to the parser; the parser never sees them. That makes comments free at runtime — write them generously.
Deep dive: 4.1 White Space Requirements and 4.2 Comment Implementation.
6. Identifiers and Keywords
6.1 Identifiers
Names you choose for signals, modules, parameters, instances. The IEEE 1364 rules:
- Must start with a letter (
a–z,A–Z) or underscore (_). - May contain letters, digits, underscores, and
$(the$is reserved for system tasks, so avoid it in user identifiers). - Case-sensitive —
Dataanddataare different identifiers. - No length limit in the spec; tools typically enforce a project-wide convention (≤ 60 characters is common).
- Cannot match a reserved keyword (next subsection).
wire clk; // legal — letter start
wire rst_n; // legal — underscore inside
wire data_q; // legal
wire _internal_w; // legal — underscore start
wire bus32; // legal — digit inside, not at start
// wire 2bus; // ILLEGAL — starts with a digit
// wire wire; // ILLEGAL — `wire` is a reserved keywordVerilog also supports escaped identifiers — names that start with \ and end with whitespace, allowing arbitrary characters in between:
wire \bus[31:0] ; // identifier is exactly "bus[31:0]"
wire \1bus ; // legal escaped identifier — backslash escapeEscaped identifiers exist mainly for tool-generated names (in gate-level netlists, where the original RTL's bus[31:0] slice gets flattened into a single net). Avoid them in handwritten RTL.
6.2 Keywords
The IEEE 1364 reserved-keyword list is fixed — about 115 words you cannot use as identifiers. The canonical groups:
- Module structure —
module,endmodule,function,endfunction,task,endtask,generate,endgenerate,primitive,endprimitive. - Net and variable types —
wire,tri,tri0,tri1,trireg,wand,wor,reg,integer,real,time,realtime,supply0,supply1. - Ports —
input,output,inout. - Behavioural —
always,initial,begin,end,if,else,for,while,case,casex,casez,endcase,default,repeat,forever,disable. - Continuous and procedural —
assign,deassign,force,release. - Logical —
and,or,nand,nor,not,xor,xnor,buf. - Strength and parameters —
strong0,strong1,weak0,weak1,pull0,pull1,highz0,highz1,parameter,localparam,defparam,specparam.
A keyword used as an identifier produces an immediate compile error. The working habit: prefix every internal signal with a noun (data_, count_, state_) rather than reaching for short generic words that might collide.
Deep dives: 4.6 Identifier Declaration and 4.7 Keyword Usage.
7. Numbers and Literals
Verilog numbers are the lexical layer with the most teeth — getting them wrong is the most common first-week bug. The format:
[size]'[base][value]Where:
- size — width in bits (e.g.
8,32). Optional — if omitted, the number defaults to a 32-bit unsigned integer (the 32-bit default trap, covered in 4.4). - base —
'b/'B(binary),'o/'O(octal),'d/'D(decimal),'h/'H(hexadecimal). The apostrophe is mandatory. - value — digits in the chosen base; underscores between digits are spacers (
32'hDEAD_BEEF).
8'hA5 // 8-bit hex → 10100101
4'b1010 // 4-bit binary → 1010
32'd16 // 32-bit decimal → 0...010000
1'b0 // 1-bit zero
1'b1 // 1-bit one
32'h0 // 32-bit zero — use this for parametric reset values
8'hxx // 8-bit unknown — all bits X
8'bzzzz_zzzz // 8-bit high-impedance — tri-state release
32'hDEAD_BEEF // underscores are spacers — same as 32'hDEADBEEF
{32{1'b0}} // replication — 32 copies of 1'b0 = 32-bit zero
{WIDTH{1'b1}} // parametric — N copies of 1; sizes correctly for any WIDTHThe X and Z literals are essential for testbench code and for explicit tri-state release:
8'bxxxx_xxxx // 8-bit unknown — every bit is X (test that consumers handle X)
1'bz // 1-bit high-impedance — release a tri-state lineCommon pitfalls:
// 32-bit default trap — unsized literal is 32-bit signed
wire [7:0] addr;
assign addr = 8 + 1; // legal — but `8 + 1` is 32-bit (sign-extended to fit)
assign addr = 8'd8 + 8'd1; // safer — both operands explicitly 8-bit unsigned
// Missing apostrophe — number is treated as identifier
wire foo;
assign foo = 32d16; // ❌ ERROR — `d` interpreted as part of identifier
assign foo = 32'd16; // ✅ legal — explicit decimal literal
// Missing base specifier — number is decimal
wire [7:0] mask = 'hFF; // ✅ legal — unsized 32-bit hex 0xFF, truncated to 8 bits
wire [7:0] mask = 'FF; // ❌ ERROR — no base specifierDeep dive: 4.4 Number Representation covers all twelve canonical literal forms, the 32-bit default trap, width-extension rules, and sign-vs-unsigned behaviour.
8. Strings
Strings are sequences of characters inside double quotes:
$display("hello, world");
$display("integer value = %0d", count_q);
$display("hex value = %h, time = %0t", data, $time);
$display("multi-line\nstring with\ttabs");Three things to know:
- Strings are testbench-only. No synthesisable RTL construct accepts a string. Strings appear in
$display,$monitor,$write,$readmemhformat strings, and parameter declarations used by behavioural models — never in synthesisable port-level signal names. - Escape sequences follow C conventions:
\n(newline),\t(tab),\\(backslash),\"(double quote),\<octal>(octal byte). - Format specifiers in
$display/$monitorecho the Cprintffamily:%d(decimal),%h(hex),%b(binary),%o(octal),%c(char),%s(string),%t(time),%0d/%0h(zero-pad-suppressed).
Deep dive: 4.5 String Handling covers escape sequences, format-specifier nuances, and the $sformatf family.
9. Operators
Verilog has a rich operator set — arithmetic, logical, bitwise, relational, equality, reduction, shift, conditional, concatenation, replication. The lexer recognises each one as a distinct token; the parser assigns precedence per the IEEE 1364 grammar (similar to C with a few additions for hardware).
// Arithmetic
+ - * / % **
// Logical (1-bit boolean result)
! && ||
// Bitwise (per-bit, vector-width result)
~ & | ^ ~^ ^~
// Relational
< > <= >=
// Equality
== != // 4-state, X/Z-propagating
=== !== // 4-state, X/Z-comparing (case-equal / case-not-equal)
// Reduction (apply operator across all bits of a vector → 1-bit result)
& | ^ ~& ~| ~^
// Shift
<< >> <<< >>>
// Conditional
?:
// Concatenation and replication
{} {n{}}The operators every working engineer carries reflexes for:
| Operator | What it does | Hardware shape |
|---|---|---|
& / | / ^ (bitwise) | Per-bit AND / OR / XOR across two vectors | An array of AND / OR / XOR gates |
& / | / ^ (reduction) | AND / OR / XOR across all bits of one vector | A tree of gates |
&& / || | Logical AND / OR — operands treated as boolean (zero vs non-zero) | A 1-bit result, used in if / ?: |
== vs === | Equality with vs without X/Z propagation | == produces X if either side has X; === is exact bit-pattern compare (sim-only) |
<<< / >>> | Arithmetic shifts — sign-extend on right-shift signed operand | Different gates than logical << / >> |
?: | Ternary — c ? a : b | A 2-to-1 multiplexer |
{a, b, c} | Concatenation — bit-glue | A wire bundle, zero-gate |
The single most useful idiom every RTL author writes daily:
{WIDTH{1'b0}} // parametric all-zero literal — sizes correctly
{WIDTH{1'b1}} // parametric all-one literal
{a[7:0], b[7:0]} // concatenate two 8-bit vectors → 16-bit value
{8{a[0]}} // sign-extend / replicate one bit eight timesDeep dive: 4.3 Operator Usage covers every operator's precedence, the 4-state X-propagation rules, signed vs unsigned operand handling, and the difference between bitwise / logical / reduction families.
10. System Tasks, Functions, and Compiler Directives
Two cross-cutting token families that don't fit the seven-category grammar but show up in every working file.
10.1 System tasks and functions
Names that start with $. Provided by the simulator (not by the user) and never synthesisable.
| Family | Examples | Use |
|---|---|---|
| Display | $display, $write, $strobe, $monitor, $sformatf | Print to simulation log |
| Time | $time, $realtime, $timeformat | Read or format simulation time |
| Control | $finish, $stop, $exit | End / pause simulation |
| File I/O | $readmemh, $readmemb, $writememh, $fopen, $fwrite | Memory init, log file dump |
| Random | $random, $urandom, $urandom_range, $dist_normal | Stimulus generation |
| Test control | $assertion, $cover, $past, $rose, $fell | Formal / assertion helpers (SystemVerilog) |
Every $ name is a tool feature; none of them survive synthesis. The working habit: use them freely in testbenches; never put them inside a synthesisable module.
10.2 Compiler directives
Names that start with a backtick (`). Processed by the preprocessor — a pass that runs before the lexer proper. Conceptually similar to C's #define / #include.
`timescale 1ns/1ps // simulation time-unit and precision
`default_nettype none // disable implicit-net declaration
`include "common_defines.vh" // textual include
`define RST_VAL 32'hDEAD_BEEF // textual macro
`ifdef SIMULATION
// testbench-only code
`endif
`undef RST_VAL // un-defineThe directives every working RTL house puts at the top of every file:
| Directive | Why |
|---|---|
`timescale 1ns/1ps | Set simulation time unit and precision. Without this, simulator behaviour is undefined for #delays. |
`default_nettype none | Disable the implicit-net rule. Catches typos at compile time instead of runtime. |
`include | Textually include header files (*.vh, *.svh) that contain shared `defines, parameter packs, and macro definitions. |
The directive every working RTL house avoids:
| Directive | Why avoid |
|---|---|
`define for constants | Use parameter or localparam instead — they respect scope, support types, and don't pollute the macro namespace globally. |
`define has its place — for compile-time flags (`ifdef SIMULATION), for textual macros that genuinely cannot be expressed as parameters — but constant values belong in parameters, not in defines.
11. Punctuation and Special Characters
The lexer recognises a small set of punctuation tokens beyond operators:
| Token | Use |
|---|---|
; | Statement terminator |
, | Separator (parameter / port list, concatenation) |
( / ) | Grouping; parameter / port list delimiters |
[ / ] | Vector range / array index |
{ / } | Concatenation, replication, block delimiter (in some contexts) |
: | Range in vector / bit-select ([31:0]); ternary middle |
. | Hierarchy separator (u_dut.internal.signal); named port connection (.clk(clk)) |
@ | Sensitivity-list marker (always @(posedge clk)) |
# | Delay or parameter-override marker (#10, mod #(.WIDTH(8))) |
' | Number-base separator (8'h1F) |
\ | Escaped-identifier marker |
These tokens have hard-coded grammatical roles — the lexer always returns each as the right token class regardless of where it appears.
12. Common Mistakes
Five lexical bugs that every Verilog beginner makes at least once:
- Forgetting the apostrophe in numbers.
32d16is an identifier (32d16);32'd16is the integer 16 in a 32-bit unsigned literal. Tools error on the first; lint catches the second. Always write the apostrophe. - The 32-bit default trap.
8 + 1is a 32-bit signed expression, not a 4-bit one. Sized literals avoid the trap:8'd8 + 8'd1. - Using a keyword as an identifier.
wire wire;is a compile error.wire wir;is a typo waiting to happen. The fix: explicit, descriptive identifier names that can't collide with reserved words. - Implicit nets enabled. Without
`default_nettype none, a typo (data_bsuinstead ofdata_bus) silently creates a one-bitwireand your 32-bit signal becomes garbage. Every file gets the directive at the top. $displayin synthesisable hierarchy. A$displayinside a synthesisable module passes simulation, gets silently dropped by the synthesiser, and your silicon does something different. Testbench code lives in testbench files.
13. Lexical Cheatsheet
The single page you keep open while writing Verilog:
// ────────────── Whitespace ──────────────
// Spaces, tabs, newlines — all equivalent. No semantic meaning.
// ────────────── Comments ──────────────
// single-line comment
/* multi-line
comment */
// ────────────── Identifiers ──────────────
clk // legal — letter start
rst_n // legal — underscore inside
_internal_w // legal — underscore start
bus32 // legal — digit inside
\bus[31:0] // escaped identifier — backslash then whitespace
// ────────────── Keywords (sample) ──────────────
module endmodule always initial begin end if else
wire reg input output inout assign
// ────────────── Numbers ──────────────
8'hA5 // 8-bit hex → 10100101
4'b1010 // 4-bit binary → 1010
32'd16 // 32-bit decimal → 16
32'h0 // 32-bit zero
32'hDEAD_BEEF // underscores are spacers
8'bxxxx_xxxx // 8-bit unknown
1'bz // 1-bit high-impedance
{WIDTH{1'b0}} // parametric all-zero
{8{a[0]}} // 8 copies of one bit
// ────────────── Operators ──────────────
+ - * / % ** // arithmetic
~ & | ^ ~^ ^~ // bitwise (per-bit on vectors)
& | ^ ~& ~| ~^ // reduction (across one vector → 1 bit)
! && || // logical (1-bit boolean)
< > <= >= // relational
== != // equality (X/Z-propagating)
=== !== // case-equal (exact bit-pattern, sim-only)
<< >> <<< >>> // shifts
? : // ternary (a mux)
{ , } // concatenation
{n{ }} // replication
// ────────────── Strings ──────────────
"hello, world" // double-quoted, testbench-only
"value = %0d" // printf-style format
// ────────────── System Tasks ──────────────
$display $monitor $strobe $write
$time $realtime
$finish $stop
$readmemh $readmemb
$random $urandom
// ────────────── Compiler Directives ──────────────
`timescale 1ns/1ps // every file
`default_nettype none // every file
`include "common.vh"
`define RST_VAL 32'h0
`ifdef SIMULATION ... `endif
// ────────────── Punctuation ──────────────
; , ( ) [ ] { } : . @ # ' \14. Debugging Lexical Issues
When a Verilog file fails to compile with a lexical error — and they all look similar — the workflow:
- Read the error message. Every simulator and synthesiser names the file and line number of the failed lex. Open the file, find the line, identify the offending token.
- Check for missing apostrophe.
32d16(identifier — not legal in most contexts) vs32'd16(literal 16). The most common lexical error. - Check for missing
`default_nettype none. If the error is "undeclared identifier," the typo passed the lexer but failed the parser. Add`default_nettype noneand every typo becomes a clear lexical error. - Check for keyword collision.
wire wire;orreg case;— both fail. Rename your identifier. - Check for unclosed comment / string.
/* multi-linewithout*/swallows the rest of the file."unterminated stringswallows the rest of the line. Modern editors highlight these in red; trust the editor. - Check for stray backtick. A backtick that isn't followed by a valid directive name (
`xyz) confuses the preprocessor. Watch for backticks in identifiers or strings. - Compile with
-Wall-style verbosity. Lint tools (SpyGlass, Verilator's--lint-only) emit warnings for legal-but-suspicious lexical patterns — implicit nets, ambiguous literals, narrow-vs-wide operand mismatches.
15. Design Review Notes
What a senior RTL reviewer flags on the lexical layer:
`default_nettype noneand`timescale 1ns/1psat the top of every file. Non-negotiable.parameterandlocalparamfor constants, not`define. Defines pollute the global macro namespace and don't respect scope.- Sized literals everywhere a vector signal is assigned.
data_bus <= 8'h00;notdata_bus <= 0;. The first is unambiguous; the second relies on width extension and can produce sizing warnings. - Underscored numbers for readability.
32'hDEAD_BEEFnot32'hDEADBEEF.32'b1010_0101_1100_0011not32'b1010010111000011. Group bits in nibbles. - Consistent identifier convention. Project-wide snake_case (
data_q,wr_addr) — never CamelCase mixed with snake_case in the same file. Lint enforces. _q/_d/_nsuffix convention._qfor register state,_dfor combinational next-state,_nfor active-low. Lint flags identifiers that don't follow.- No
$display/$random/$readmemhin synthesisable RTL. Testbench code in*_tb.vfiles only. - Comments name why, not what.
// increment counteris noise;// count cycles since reset for telemetryis useful.
16. Interview Insights
The lexical layer comes up in every interview as a sanity check that you've actually read real Verilog code. Reason through the answers.
17. Learning Roadmap
Each lexical category in this chapter is the entry point to a deeper sub-page.
17.1 Sub-pages — drill each category
- 4.1 — White Space Requirements — what spaces, tabs, and newlines actually do at the lexer level.
- 4.2 — Comment Implementation — single-line, multi-line, nesting rules, doxygen style.
- 4.3 — Operator Usage — every operator's precedence, X-propagation, signed-vs-unsigned semantics.
- 4.4 — Number Representation — every literal form, the 32-bit default trap, width extension, sized vs unsized.
- 4.5 — String Handling — escape sequences, format specifiers, the
$sformatffamily. - 4.6 — Identifier Declaration — naming rules, escaped identifiers, project conventions.
- 4.7 — Keyword Usage — the full 115-keyword list, deprecated keywords, SystemVerilog additions.
17.2 What comes next
- Chapter 5 — Variables & Data Types — nets vs variables, 4-state value system, scalar vs vector vs array. The type system every signal declaration draws from.
- Chapter 5.1 — Physical Data Types — the net family.
- Chapter 5.1.1 —
wireandtriNets — the two workhorse net keywords.
The lexical layer is the floor every later chapter stands on. Read 4.1–4.7 once before going deeper, then refer back to the sub-pages when a specific lexical question arises.
18. Exercises
Three exercises to lock the lexical vocabulary in.
Exercise 1 — Spot the lexical error
For each snippet below, identify the lexical error (or confirm the snippet is clean). Reason about which category the error falls in.
wire 1bus;
assign 1bus = data[0];parameter WIDTH = 8;
wire [WIDTH-1:0] data;
assign data = 8d255;module mux2 (
input wire sel,
input wire [7:0] a, b,
output wire [7:0] case
);
assign case = sel ? a : b;
endmodule`define RST_VAL 32'h0
always @(posedge clk) begin
state_q <= `RST_VAL;
/* multi-line comment starts here
and never closes ...
state_q <= state_d;
endWhat to produce. A one-line classification of each error (which lexical category) + a one-sentence fix.
Exercise 2 — Write the literals
Write the Verilog literal for each of the following values. Use the smallest legal width, base 'h where natural, and underscores for readability.
- The decimal value 255 as an 8-bit unsigned literal.
- The decimal value 65 535 as a 16-bit unsigned literal.
- A 32-bit all-zero reset value.
- A 32-bit all-ones value (sized parametrically as
WIDTH = 32). - An 8-bit all-X (unknown) value.
- A 4-bit high-impedance value.
- The hex value
0xDEADBEEFas a 32-bit literal, readable. - The bit pattern
1010_0101_1100_0011as a 16-bit binary literal, with nibble grouping.
Exercise 3 — Parse the directives
Given the file header below, identify (a) what each directive does, (b) which line would compile and which would not in a strict-lint configuration, and (c) which one line every working RTL house insists on putting at the top of every file.
`timescale 1ns/1ps
`default_nettype none
`include "common_defines.vh"
`define RST_VAL 8'h0
`define WIDTH 8
module wire_count #(
parameter WIDTH = 8
) (
input wire clk,
input wire rst_n,
input wire enable,
output wire [WIDTH-1:0] count_o
);
reg [WIDTH-1:0] count_q;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) count_q <= `RST_VAL;
else if (enable) count_q <= count_q + 1'b1;
end
assign count_o = count_q;
endmoduleHint. One `define is a code smell because it duplicates a parameter. One directive is genuinely useful for testbench-vs-RTL conditional compilation. One directive is the project's safety net against implicit-net typos.
19. Summary
Verilog's lexical layer is the first thing your code talks to — every simulator and synthesiser runs the same lexer before any RTL semantics enter the picture. The seven token categories — whitespace, comments, identifiers, keywords, numbers, strings, operators — plus the two cross-cutting families (system tasks and compiler directives) compose every line of every working file.
The day-to-day discipline:
`default_nettype noneand`timescale 1ns/1psat the top of every file. Non-negotiable.- Sized literals everywhere —
8'h00not0,32'hDEAD_BEEFnot3735928559. The 32-bit default trap costs hours of debug when you fall into it. parameterfor constants, not`define— types, scope, lint-visibility.`default_nettype nonecatches typos. Every undeclared identifier becomes a compile error instead of a silent garbage signal.- No
$display/$random/$readmemhin synthesisable RTL. Testbench code lives in testbench files. - Naming convention is part of the lexical layer.
_q/_d/_nsuffixes, snake_case, no keyword collisions.
Master the lexical layer once and never think about it again — every later chapter assumes you can read these tokens at glance. The seven sub-pages (4.1–4.7) drill each category deeper; Chapter 5 picks up with the type system that every signal declaration draws from.
Related Tutorials
- Introduction & Overview — Chapter 1; what Verilog is.
- Typical VLSI Design Flow — Chapter 2; where Verilog fits.
- RTL Designing — Chapter 3; the three RTL idioms.
- Variables & Data Types — Chapter 5; the type system above the lexer.
- Physical Data Types — Chapter 5.1; the net family.
wireandtriNets — Chapter 5.1.1; the two workhorse net keywords.