Verilog · Chapter 4.2 · Lexical Conventions
Comment Implementation in Verilog
Verilog supports two comment forms, a line comment and a block comment, and the syntax matches C exactly. The rules themselves are simple, but the discipline around them is what separates clean RTL from messy code. This lesson covers the precise lexical rules for each form, the block-comment nesting trap that bites almost every beginner at least once, and the comment-hygiene habits that working teams enforce so code stays readable and searchable. It also explains pragmas, the specially formatted comments that lint, synthesis, and coverage tools read as instructions, such as lint waivers, synthesis directives, and coverage controls. By the end you will know not just how to write comments, but how professional RTL teams use them to communicate intent and guide their tools.
Foundation14 min readVerilogSyntaxStyleComments
Chapter 4 · Page 4.2 · Lexical Conventions
The Two Comment Forms
Verilog inherits C's comment syntax exactly. Two forms; nothing more.
// Line comment — runs from `//` to the end of the line.
assign y = a & b; // can also be at the end of a code line.
/* Block comment — runs from /* to the next */
and can span multiple lines. */
/*
* Conventional multi-line block: padded leading-`*` on every line,
* used at the top of every file for copyright, author, change log.
*/Per IEEE 1364-2005 §3.3, both forms are stripped by the simulator's tokeniser before the parser sees the source. The tools never see comments. Use them generously.
Lexical Rules
The exact rules every comment obeys.
Line comments
- Begin with
//. - End at the next newline (
\n,\r, or\r\n). - The newline itself is not part of the comment — it's preserved as a whitespace token.
- A line comment can appear anywhere a token can appear.
// Top of file
module my_block ( // after the module name
input wire clk, // after a port declaration
input wire rst_n, // ...
output reg data_q
); // after the port list
// Standalone block — separates sections of code
always @(posedge clk) begin
data_q <= 1'b0; // after a statement
end // after `end`
endmodule // after `endmodule`Block comments
- Begin with
/*. - End at the first
*/(greedy from left). - Everything in between — including newlines — is part of the comment.
- A block comment can appear anywhere whitespace can appear.
// Block comments can split an expression — used for inline documentation
assign sum = a /* operand A */ + b /* operand B */ ;
// Or wrap a parameter
my_block #(
.WIDTH (/* parameterised */ 8),
.DEPTH (/* memory depth */ 1024)
) u_dut ( ... );In practice, inline block comments in expressions are rare — they hurt readability. Use them at the file and block level, not inside a single statement.
The Nesting Trap
This is the one beginner trap every Verilog engineer hits once. Block comments do not nest.
// ❌ BUG — looks like a nested comment, isn't
/* outer comment
/* inner comment */ // this `*/` ends the OUTER block comment
still part of the outer */ // this text is now CODE (and is a syntax error)
// Compiler error: "syntax error near 'still'"
// Reason: the first `*/` closed the only block comment that was open.The simulator's tokeniser scans left-to-right. The opening /* opens one comment. The first */ it encounters closes that comment. The text after the intended closing */ is then parsed as code, producing a confusing error message that doesn't point at the actual problem.
Two safe alternatives
// ✅ Pattern 1 — use line comments for the inner annotation
/* outer comment block
// inner comment that doesn't close the outer
still part of the outer */
// ✅ Pattern 2 — temporarily comment-out a block of code with `ifdef
`ifdef NEVER_DEFINED
// ... any code here, including `/* */` blocks ...
/* this block stays inside the ifdef */
`endifPattern 2 is what every working RTL engineer reaches for when "commenting out" a region of code that itself contains block comments. The `ifdef NEVER_DEFINED is conventional — readers recognise it as a "disabled block" marker.
Comment Hygiene — The Working Discipline
The lexical rules are the easy part. The discipline of what to write in a comment is what separates good RTL from textbook RTL.
Comments explain why, not what
The code already says what it does. A comment that restates the code is noise.
// ❌ Bad — restates the code
assign y = a & b; // assigns a AND b to y
// ❌ Bad — adds nothing
count_q <= count_q + 1; // increment count_q
// ✅ Good — explains the engineering decision
// Increment only on the leading edge of `valid` to avoid double-counting
// retried transactions that re-assert mid-burst.
count_q <= count_q + 1;Comments document intent, not history
The Git log carries history. Comments carry intent.
// ❌ Bad — historical commentary in source
assign mask = 8'hAA; // changed from 0xFF on 2024-03-15 per Alice's review
// ✅ Good — the intent, not the timeline
assign mask = 8'hAA; // even-bit positions only — odd positions are reserved
// for vendor extensions (see spec §3.4.2).Comments cite specs
When the RTL implements a specific clause of a public spec, cite it.
// ✅ Good — anchored to the spec
// AXI4 §A3.3.1 — WLAST asserts on the last beat of a write burst.
// The counter loads AWLEN+1, decrements on each accepted beat, and
// asserts WLAST when it reaches 1.
assign w_last = (beats_remaining_q == 5'd1);The cited spec section makes the comment auditable — a reviewer can verify the implementation against the named clause.
File-level header comments
Every working RTL file starts with a header. The format every team converges on:
/* ───────────────────────────────────────────────────────────────────
* Module : counter_4b
* Description : 4-bit synchronous up-counter with async-low reset.
* Author : VLSI Mentor (rtl@vlsimentor.com)
* Created : 2026-04-15
*
* Interfaces : clk / rst_n / en input · q[3:0] output
* Clock domain : single (clk)
* Reset : async-low (rst_n)
*
* Spec ref : project-spec §2.3.1
* Lint waivers : (none)
* ─────────────────────────────────────────────────────────────────── */
`timescale 1ns/1ps
`default_nettype none
module counter_4b ( ... );
...
endmoduleSome teams add a copyright notice. Some add a change log (despite the "don't put history in comments" rule — the change log is the exception every team accepts). Some add a tool-version stamp. The exact fields vary; the existence of a structured header doesn't.
Tool Pragmas Hidden Inside Comments
This is the part of comments most beginners don't realise exists. Several EDA tools encode pragmas — directives the tool reads — inside specially-formatted comments. The Verilog parser sees them as comments; the tool sees them as instructions.
Lint waivers
A lint waiver tells the lint tool "I know this rule fires here; suppress it." Most lint tools (SpyGlass, Conformal, Meridian) use a comment pragma.
// spyglass disable_block W116
// W116 fires on combinational `for` loops, but we know the unroll is safe here.
for (i = 0; i < 8; i = i + 1) begin
masked[i] = data[i] & enable_mask[i];
end
// spyglass enable_block W116Without the waiver, the lint tool would flag every run. With the waiver, it stays silent for just that block. Waivers must include a justification comment — un-justified waivers are a code-review block at every modern team.
Synthesis pragmas
Synthesis tools accept pragmas that fine-tune the synthesised hardware.
// synopsys translate_off
// This block runs in simulation but is invisible to synthesis.
initial $display("debug build");
// synopsys translate_on
reg [3:0] data_q;
// synopsys keep_signal_name "data_q"
// Tells DC not to rename `data_q` during optimisation — useful when
// the signal name shows up in waveforms and post-silicon debug.
// synopsys async_set_reset rst_n
// Marks rst_n as the async reset, even when sensitivity-list rules
// would otherwise produce a different inference.Other tools use different syntax (// genus, // dc, etc.). The pragma is not portable between tools — every tool reads only its own pragmas and ignores comments it doesn't recognise.
Coverage directives
Coverage tools use pragmas to exclude regions from coverage analysis.
// VCS coverage off
// This assertion is only used in debug builds — don't count it in coverage.
assert property (@(posedge clk) !overflow_q);
// VCS coverage onWhy pragmas are inside comments
Two reasons:
- Lexical compatibility. Pragmas inside comments mean the file remains parseable by any simulator or synthesiser, even ones that don't recognise the pragma. The unrecognising tool just ignores the comment.
- Cross-tool portability. A file with
// spyglass disable_block W116lints clean in SpyGlass and is invisible to every other tool. No#ifdefgymnastics needed.
Comments in Generated Files
When tools generate Verilog (synthesis output, IP-generator output, behavioural-model exports), the generated file usually has a header comment identifying the source.
//-----------------------------------------------------------------------------
// Generated by: Synopsys Design Compiler X-2024.06
// Generation time: 2026-04-15 14:32:51 PDT
// Source: rtl/counter_4b.v
// SDC constraints: constraints/counter_4b.sdc
// Library: tsmc5_svt.db
//
// DO NOT EDIT THIS FILE BY HAND — re-run synthesis instead.
//-----------------------------------------------------------------------------
module counter_4b ( ... );
DFFRX1 \count_q_reg[0] ( .D(...), .CK(clk), .RN(rst_n), .Q(...) );
...
endmoduleThe "do not edit by hand" warning is non-negotiable — the file gets regenerated on every synthesis run, so any hand edit gets blown away. Treat generated files as build artifacts, not as source.
Common Mistakes
Six failure modes every Verilog engineer encounters.
- Unclosed block comments.
/* ...without a*/swallows the rest of the file. Every editor highlights this; pay attention to the highlighting. - Nested block comments. Covered above — the inner
*/closes the outer. - Restating-the-code comments. Noise in the source. Removed on every review.
- Comment drift. The code changes, the comment doesn't. The comment now lies. Lies are worse than no comment at all — they actively mislead. Every code review should ask "does the comment still match the code?"
- Un-justified lint waivers. A
// spyglass disable_block W116with no explanation is a maintenance hazard. The reviewer (or the future you) has to re-derive why the waiver is safe. Always include a justification. - History in comments. "Changed by Alice on 2024-03-15" belongs in
git log, not in the source. Comments are read by every reader; history is read by the engineer specifically auditing changes.
Interview Insights
Summary
Verilog has exactly two comment forms — line (//) and block (/* */) — both inherited unchanged from C. The lexical rules are simple; the discipline is the harder part: comments explain why not what, document intent not history, cite specs when implementing them, and follow the team's file-header convention. The non-obvious layer is that EDA tools encode pragmas inside specially-formatted comments — lint waivers, synthesis pragmas, coverage directives — every working RTL engineer learns to read alongside the language itself. The next sub-topic covers identifiers — naming rules, the case-sensitivity gotcha, and the rare-but-real escaped-identifier form.
Beat coverage notes: all required beats present. ⏱ Timing Observation, 🧠 Simulator Scheduling Insight callouts omitted — comments are pure-text-rules; no timing or scheduling angle. The 🚀 RTL Design Insight callout is folded into the comment-hygiene section's "intent vs history" framing.
Related Tutorials
- Lexical Conventions — Chapter 4 overview; the parent layer this sub-topic lives in.
- White Space Requirements — Chapter 4.1; the sibling sub-topic on whitespace rules.
- RTL Designing — Chapter 3; the layer above the lexical rules.