Verilog · Chapter 4 · Foundations
Lexical Conventions
Chapter 3 taught you to write RTL. This chapter makes the source text itself stop looking mysterious. Verilog reads your file as a sequence of small pieces — keywords, names you invented, numbers, operators and punctuation — and once you can see those pieces, a line of RTL turns from a strange programming sentence into a readable description of hardware. You will learn what a token is, what whitespace and comments do, how names work and why capitalisation matters, how a number carries its width and base, and which punctuation marks actually change meaning. The goal is not to memorise grammar; it is to read Verilog without guessing.
Foundation26 min readVerilogSyntaxIdentifiersLiteralsReading Code
Chapter 4 · Page 1.4 · Foundations
1. How Verilog Reads Your Code
Here is a line you already understand from Chapter 3:
assign y = sel ? b : a;You read that as a multiplexer. Verilog does not — not at first. Before any of it means hardware, the tool has to break the text into pieces:
assign y = sel ? b : a ;Nine pieces. Each one is a token — a chunk of source text the language recognises as a single meaningful unit. assign is one token, not six letters. sel is one token. So is the lone ;.
That is the whole idea of this chapter:
Verilog first reads your file as a sequence of tokens. Only then does it work out what hardware you described.
Once you can see the tokens, the line stops being a wall of symbols and becomes:
- a keyword the language owns —
assign - names you invented —
y,sel,b,a - operators and punctuation —
=,? :,;
And that reads as: continuously drive y from a choice between b and a. A mux.
2. The Pieces Verilog Reads
Verilog recognises a small number of token kinds. You have already met most of them without naming them.
| Kind | What it is | Example |
|---|---|---|
| Keyword | A word the language reserves and gives meaning to | module, assign, always |
| Identifier | A name you chose for something | clk, data_in, count_q |
| Number | A literal value, usually with a width and a base | 8'hA5, 1'b0, 42 |
| Operator | Combines or compares values | &, |, +, ? : |
| Punctuation | Structural marks — they hold the code together | ; , ( ) [ ] |
| String | Characters inside double quotes, used in simulation output | "count = %0d" |
Two more things appear in source files but are not part of the hardware description:
- Whitespace — spaces, tabs and newlines, which separate tokens.
- Comments — notes for humans, which the tools ignore.
And two families carry a marker character that tells you immediately what they are:
- System tasks and functions begin with
$—$display,$finish. These are services the simulator provides. - Compiler directives begin with a backtick —
`default_nettype,`timescale. These are instructions to the tool.
Everything in the rest of this chapter is one of the above. Here is the Chapter 3 mux again, with every piece labelled:
`default_nettype none // compiler directive
module mux2 ( // keyword, identifier, punctuation
input wire sel, // keywords, identifier, punctuation
input wire a,
input wire b,
output wire y
);
assign y = sel ? b : a; // keyword, identifiers, operators, punctuation
endmodule // keywordNothing new has been added to the module. You are simply reading it a second way.
3. Whitespace
Spaces, tabs and newlines separate one token from the next. Beyond that separation, Verilog does not care how much of it you use.
assign y=a&b;
assign y = a & b;
assign y =
a & b;All three produce the same tokens, so all three describe the same AND gate. Whitespace is not hardware.
But whitespace is needed where two tokens would otherwise run together. a&b is fine because & cannot be part of a name, so the tool can see three tokens. Remove the space in input wire a and you get inputwire, which is a single identifier and not what you meant.
The practical conclusion is not "formatting is optional." It is:
The tool needs whitespace only to separate tokens. You need it to be able to read your own design six months later.
Write the spaced version. Deep dive: 4.1 White Space Requirements.
4. Comments
Comments are notes to humans. The tools skip over them, so they never become hardware.
// A single-line comment runs to the end of the line.
assign y = a & b; // it can also sit after code
/* A block comment can
run across several lines. */A block comment starts at /* and ends at the first */ it finds. That is worth knowing because it means block comments do not nest — if you wrap a region that already contains */, the comment ends earlier than you expected and the rest becomes code again.
Because comments are free, use them for what the code cannot say — why, not what:
// Wraps at 15 by design; the downstream block expects modulo-16 timing.
count_q <= count_q + 4'b0001;Compare that with // increment count, which tells the reader something they could already see.
Deep dive: 4.2 Comment Implementation.
5. Identifiers — the Names You Choose
An identifier is a name you invent: a signal, a port, a module, an instance.
wire clk; // a clock signal
wire rst_n; // an active-low reset
wire [7:0] data_in;
reg [3:0] count_q;clk, rst_n, data_in and count_q are all identifiers. So is mux2, the module name. Verilog attaches no meaning to the name itself — rst_n is reset because of what the code does with it, not because of what it is called. Naming conventions are for humans.
What each of these names is — a net, a variable, a vector — is Variables & Data Types (Chapter 5). Here we only care about how the name is spelled.
5.1 What makes a name legal
- It must start with a letter or an underscore (
_). - After that it may contain letters, digits, underscores and
$. - It may not start with a digit, and it may not start with
$— that first-character$belongs to the system-task namespace ($display). - It may not be a keyword.
- The standard requires tools to support names of at least 1024 characters, so length is never your practical problem.
wire data_in; // legal
wire _internal; // legal — underscore start
wire bus32; // legal — digits after the first character
wire count_q; // legal
// wire 2bus; // illegal — starts with a digit
// wire wire; // illegal — `wire` is a keywordDeep dive: 4.6 Identifier Declaration.
6. Verilog Is Case-Sensitive
This one catches almost everyone once, so it gets its own section.
data
Data
DATAThose are three separate identifiers. Not three spellings of one name — three names.
Here is how that becomes a real bug:
wire data_ready;
assign valid = Data_Ready; // ❌ different name entirelydata_ready and Data_Ready have nothing to do with each other. Depending on how the file is set up, this either fails to compile or quietly creates a second signal that nobody drives — and valid never behaves as intended.
This is exactly why Chapter 3 put `default_nettype none at the top of every design file: with it, an undeclared name like Data_Ready becomes an error you see immediately instead of a silently invented one-bit wire.
Capitalisation is not cosmetic. It changes which name the tool sees.
Pick one convention — lowercase with underscores is the common one for signals — and stay inside it.
7. Escaped Identifiers
Occasionally you will open a machine-generated file and meet a name like this:
wire \data[7] ;That is an escaped identifier. It starts with a backslash and ends at the next whitespace. Everything between them is the name — here, literally data[7], square brackets included.
The point of the form is that it can contain characters an ordinary name cannot. You will see it in synthesised netlists and other tool output, where a vector has been split into individual bits and the tool needs a legal name for each one.
Two things to notice: the terminating space really is required, and the backslash and that space are not part of the name. You will rarely write these by hand — recognising them is enough.
Deep dive: 4.6 Identifier Declaration.
8. Numbers — Width, Base, Value
This is the highest-value section in the chapter. Hardware is built from fixed-width signals, so Verilog numbers carry more information than an ordinary integer does.
The full form has three parts:
8'hA5
│ ││
│ │└── value — the digits, in the chosen base
│ └─── base — b, o, d or h
└───── size — how many bits wideRead 8'hA5 as: eight bits wide, written in hexadecimal, value A5. The apostrophe separates the width from the base.
The four bases:
| Base | Letter | Example | Meaning |
|---|---|---|---|
| Binary | b | 4'b1010 | 4 bits: 1010 |
| Octal | o | 6'o52 | 6 bits: 101010 |
| Decimal | d | 8'd42 | 8 bits: 00101010 |
| Hexadecimal | h | 8'hA5 | 8 bits: 10100101 |
Why width matters is a hardware question, not a syntax one:
reg [3:0] count_q;
count_q <= 4'b0000; // four bits of zero — matches the registerWriting 4'b0000 says plainly "this is a four-bit value." That intent survives code review and refactoring in a way a bare 0 does not.
8.1 Underscores are free
Long literals are hard to read, so Verilog lets you drop underscores between digits purely as visual separators:
32'b1010_1100_0011_0101The underscores add nothing to the value and change no bits. They just group the digits.
8.2 Leaving the size off
You can write a number without a size:
assign count_d = 4'd1; // explicitly four bits — say this
// assign count_d = 1; // unsized — the tool decides the width for youUnsized literals default to 32 bits. When such a value meets a narrower signal the extra bits are dropped, which is a quiet way to lose a value you thought you had written. Expression sizing has real rules and they matter — 4.4 Number Representation covers them properly.
For now, one habit avoids the whole category of problem:
Give every literal in your RTL an explicit width. Write
8'd0, not0.
8.3 x and z
Two more values can appear in a literal:
8'hxx // eight bits, all unknown
1'bz // one bit, high-impedancexmeans unknown — the value is not known in the model. It shows up when a signal has not been driven or reset yet, and it is enormously useful in simulation because it makes "I never set this" visible instead of silently reading as 0.zmeans high-impedance — nothing is actively driving the signal, as when a tri-state driver is released.
Be careful with what these are. They are values in Verilog's logic model, used to describe uncertainty and undriven lines. x is not a third voltage sitting between 0 and 1 on real silicon; it is the model's way of saying this is not determined. The full four-value picture belongs to Variables & Data Types (Chapter 5).
Deep dive: 4.4 Number Representation.
9. Strings
A string is characters between double quotes. In this course you will meet them almost entirely in simulation output:
$display("count = %0d", count_q);"count = %0d" is a string. $display is a system task — the simulator prints the line for you.
Chapter 3's distinction applies here too. All of that line is Verilog. None of it means your chip contains text:
The string is a message for the person watching the simulation, not data stored in hardware.
String literals do appear elsewhere — as parameter values, or naming a file for a memory-initialisation task, for instance — so "strings are testbench-only" would be too strong a rule. But for now, when you see a quoted string, ask what it is for, and the answer is usually simulation output.
Formatting codes like %0d and %h belong to System Tasks & Functions (Chapter 8). Deep dive: 4.5 String Handling.
10. Punctuation You Are Already Using
Hardware-first learners often understand the circuit long before the punctuation. Every mark below has already appeared in code you have read.
| Mark | What it does | Where you saw it |
|---|---|---|
; | Ends a statement or declaration | assign y = a & b; |
, | Separates items in a list | between ports in a port list |
( ) | Groups — port lists, expressions, event controls | module mux2 ( ... ), @(posedge clk) |
[ ] | Vector ranges and bit selects | wire [7:0] data_in; |
{ } | Concatenation and replication | {4{1'b0}} |
: | The range separator, and the middle of ? : | [7:0], sel ? b : a |
. | Named port connection, and hierarchy | .clk(clk) |
@ | Introduces an event control | always @(posedge clk) |
# | Delay in simulation code | #10 |
' | Separates a literal's width from its base | 8'hA5 |
` | Begins a compiler directive | `default_nettype none |
Two of these are worth separating carefully, because they look similar and mean nothing alike:
- The apostrophe
'is part of a number:8'hA5. - The backtick
`starts a directive:`timescale.
Note also that { } is not a block delimiter in Verilog. A group of statements is wrapped in begin and end, as you saw in Chapter 3's always blocks. Braces mean concatenation.
Operators get their own treatment in 4.3 Operator Usage and, in depth, in Verilog Operators and Operands (Chapter 10).
11. Keywords
Some words belong to the language itself. You cannot use them as names, because the tool already knows what they mean.
module endmodule
input output inout
wire reg
assign
always initial
begin end
if else case endcaseThese are not names anyone invented for a particular design — module means module in every Verilog file ever written. Trying to use one as a name fails immediately:
// wire wire; // illegal — `wire` is a keywordYou do not need to memorise the list. Two things are enough at this stage: recognise keywords when you read them, and give your own signals descriptive names (data_valid, count_q) that are never going to collide with one.
Deep dive: 4.7 Keyword Usage.
12. Compiler Directives
A word beginning with a backtick is a compiler directive — an instruction about how the source should be processed, not a description of hardware.
`default_nettype noneYou met this in Chapter 3. It switches off the rule that would otherwise invent a one-bit wire for any undeclared name, which is what turns a typo into an error you can see.
Others you will meet include `timescale (which sets the time unit that makes #10 mean something) and `define (which gives a name to a piece of text). They are the subject of Compiler Directives (Chapter 7); here, only the shape matters.
The thing to carry forward is the category:
A directive tells the tool how to read your file. It is not a gate, a wire or a register.
13. Mistakes That Start in the Text
Four lexical slips that produce real bugs.
13.1 A case mismatch
wire data_ready;
assign valid = Data_Ready; // a different nameCovered in §6. With `default_nettype none this is an error; without it, it can pass quietly.
13.2 A missing apostrophe
assign addr = 32d16; // ❌ not a number — reads as a name
assign addr = 32'd16; // ✅ the value 16, 32 bits wideThe apostrophe is what makes a literal a literal. Without it the tool sees an identifier that nobody declared.
13.3 The wrong base
assign x = 8'd10; // decimal ten → 0000_1010
assign y = 8'h10; // hexadecimal ten → 0001_0000 (sixteen)Same digits, different values. d and h are one keystroke apart and this is an easy thing to skim past in review.
13.4 A block comment that ends early
/* disable this block temporarily
assign y = a & b; /* inner comment */
assign z = c | d;
*/The comment ends at the first */. So assign z = c | d; is live code again, and the trailing */ is a syntax error. Block comments do not nest.
14. Exercises
Work these out before reading the answers.
Exercise 1 — Name the pieces
For each token in this line, say whether it is a keyword, an identifier, an operator, or punctuation.
assign y = sel ? b : a;Exercise 2 — Why does this fail?
wire enable_n;
assign gated = Enable_n & data;What is wrong, and why is it not obvious from reading quickly?
Exercise 3 — Read the literals
For each, state the width, the base, and the value in binary.
4'b1010
8'hA5
8'd42Exercise 4 — Hardware, or not?
Which of these describe hardware, and which exist only for humans or for the simulator?
// (a)
assign y = a & b;
// (b)
// assign y = a | b;
// (c)
$display("y = %b", y);
// (d)
`default_nettype noneExercise 5 — Fix the line
Each line has one lexical mistake.
// (a)
assign count = 8d5;
// (b)
wire 4bit_bus;
// (c)
assign y = a & bAnswers
Exercise 1.
| Token | Kind |
|---|---|
assign | keyword |
y | identifier |
= | operator |
sel, b, a | identifiers |
? and : | operator (the two halves of the conditional operator) |
; | punctuation |
Read together they say: continuously drive y from a choice between b and a — a multiplexer.
Exercise 2. enable_n was declared; Enable_n is a different identifier, because Verilog is case-sensitive. It is easy to miss because the two look alike when you are scanning rather than reading. With `default_nettype none at the top of the file the tool reports the undeclared name; without it, Enable_n may be created silently as a one-bit wire that nothing drives.
Exercise 3.
| Literal | Width | Base | Binary |
|---|---|---|---|
4'b1010 | 4 bits | binary | 1010 |
8'hA5 | 8 bits | hexadecimal | 1010_0101 |
8'd42 | 8 bits | decimal | 0010_1010 |
Each hex digit is exactly four bits, which is why hexadecimal is so convenient for describing wide buses.
Exercise 4.
- (a) Describes hardware — a continuous assignment, combinational AND logic.
- (b) Not hardware — it is commented out, so the tools skip it entirely.
- (c) Not hardware — a system task that prints during simulation.
- (d) Not hardware — a compiler directive telling the tool how to read the file.
Only (a) becomes part of the design. This is Chapter 3's point again: all four lines are Verilog, but only one of them is describing your circuit.
Exercise 5.
- (a)
8d5is missing the apostrophe, so it reads as a name rather than a number →8'd5. - (b) An identifier cannot start with a digit → for example
bus_4bit, orfour_bit_bus. - (c) The statement is missing its terminating semicolon →
assign y = a & b;.
15. Summary
You can now read Verilog source as structure rather than as a wall of symbols.
- Verilog reads your file as tokens — keywords, identifiers, numbers, operators, punctuation and strings.
- Whitespace separates tokens. It is for your readability; it describes no hardware.
- Comments are for humans and never become hardware. Use them to say why.
- Identifiers are the names you choose. They start with a letter or underscore, and they are case-sensitive —
dataandDataare different names. - Numbers carry a width, a base and a value —
8'hA5is eight bits, hexadecimal, A5. Size your literals explicitly. xmeans unknown andzmeans high-impedance in Verilog's logic model — not physical voltage levels.- Strings are usually simulation output, not data in your chip.
- An apostrophe belongs to a number; a backtick starts a directive. They are not interchangeable.
When you next open an unfamiliar Verilog file, read it in this order: find the keywords to see the structure, find the names to see the signals, read the numbers with their widths, notice the punctuation, skip the comments — and then ask the question this whole course keeps coming back to:
What hardware does this describe, and when does it change?
Lexical knowledge is not the goal. It exists so that question becomes easy to answer.
Deep Dives — Chapter 4 Sub-Topics
Each of these takes one category from this chapter to full working depth.
- 4.1 White Space Requirements
- 4.2 Comment Implementation
- 4.3 Operator Usage
- 4.4 Number Representation
- 4.5 String Handling
- 4.6 Identifier Declaration
- 4.7 Keyword Usage
Related Tutorials
- RTL Designing — Chapter 3; the RTL these conventions let you read.
- Variables & Data Types — Chapter 5; what the names you declare actually are.
- Compiler Directives — Chapter 7; the backtick family in full.
- Verilog Operators and Operands — Chapter 10; every operator in depth.
Standards & specifications
- Governing standard
- IEEE Std 1364 (Verilog)(opens IEEE in a new tab)
Defines the Verilog language and its simulation semantics, including the event scheduling model. Synthesis support is defined by tools, not by this standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the Verilog HDL curriculum.
