Part 7 · Advanced & Integration · Intermediate
`define & Text Macros
Object-like and function-like macros, argument substitution, multi-line macros, stringification, and macros vs parameters.
Text substitution, not variables
A `define creates a text macro : every later use of the macro name (prefixed with a backtick) is replaced by the macro body, character for character, before compilation. There is no type, no scope, no evaluation — just substitution. An object-like macro has no arguments; a function-like macro takes arguments that are pasted into the body.
// Object-like macro — simple text replacement
`define DATA_WIDTH 32
`define RESET_VECTOR 32'h0000_1000
logic [`DATA_WIDTH-1:0] data_bus; // becomes: logic [32-1:0] data_bus;
// Function-like macro — arguments substituted into body
// NOTE: no space allowed between macro name and (
`define MIN(a, b) (((a) < (b)) ? (a) : (b))
int smaller = `MIN(x + 1, y);
// expands to: (((x + 1) < (y)) ? (x + 1) : (y))
// parentheses around each argument prevent precedence bugs
// Default argument values (SystemVerilog extension)
`define CLK_GEN(period = 10) \
always #((period)/2) clk = ~clk;
`CLK_GEN() // uses period = 10
`CLK_GEN(4) // period = 4Why the parentheses matter
Without parentheses around arguments, `MIN(a & 1, b) would expand the bitwise-and unparenthesized into the comparison, and operator precedence silently changes the meaning. Defensive parenthesization — around each argument and around the whole body — is the first habit of macro writing.
Multi-line macros and stringification
A macro body ends at the newline unless the line ends with a backslash line continuation . Multi-line macros are how UVM builds entire functions from one macro call. Stringification (`") turns macro text into a string literal with argument substitution still applied — which a normal quoted string would not do.
// Multi-line macro: backslash continues the body onto the next line
`define REPORT_FIELD(obj, field) \
$display("%s.%s = %0d at %0t", \
obj.get_name(), \
`"field`", \
obj.field, $time);
// `"field`" stringifies the ARGUMENT: `REPORT_FIELD(txn, addr)
// prints "txn.addr = 256 at 100" — the literal text "addr" appears
// because `" substitutes macro args inside the string, unlike plain ""
// Token pasting with `` joins macro text into one identifier
`define MAKE_GETTER(name) \
function int get_``name(); \
return name; \
endfunction
`MAKE_GETTER(count) // declares: function int get_count();MACRO EXPANSION PIPELINE
source text `REPORT_FIELD(txn, addr)
│
▼ preprocessor finds macro, binds args: obj=txn, field=addr
body lookup $display("%s.%s = %0d at %0t",
obj.get_name(), `"field`", obj.field, $time);
│
▼ substitute args, apply `" stringification
expanded text $display("%s.%s = %0d at %0t",
txn.get_name(), "addr", txn.addr, $time);
│
▼
COMPILER sees only the expanded text — never the macroMacros vs parameters
When each wins
Parameters are typed, scoped, and overridable per instance — always prefer them for sizing module ports and arrays.
Macros are global across the whole compilation — one `define DATA_WIDTH affects every file compiled after it, which is both their power and their danger.
Macros can generate code (functions, declarations, whole classes) — parameters cannot; this is why UVM utils macros exist.
Macros work in places parameters cannot reach: inside string literals via stringification, in `ifdef conditions, and across module boundaries without plumbing.
Parameters participate in elaboration and show up in hierarchy debug; macros vanish after preprocessing and leave no trace in the elaborated design.
// GOOD: parameter for per-instance configuration
module fifo #(parameter int DEPTH = 16, parameter int WIDTH = 32) (
input logic [WIDTH-1:0] wdata,
output logic [WIDTH-1:0] rdata
);
endmodule
fifo #(.DEPTH(64), .WIDTH(8)) small_fifo (.*); // overridable per instance
// GOOD: macro for cross-cutting code generation
`define CHECK_FATAL(cond, msg) \
if (!(cond)) $fatal(1, "CHECK failed: %s", msg);
// BAD: macro where a parameter belongs — every fifo in the
// design is now forced to the same width, silently
`define FIFO_WIDTH 32Key takeaways
Macros are preprocessor text substitution — no types, no scope, global effect from definition point onward.
Parenthesize every macro argument and the full body to avoid precedence surprises.
Use `" for stringification with argument substitution and `` for token pasting.
Prefer parameters for sizing and per-instance config; reserve macros for code generation and cross-cutting concerns.
Common pitfalls
Space between macro name and ( in the definition — turns a function-like macro into an object-like macro whose body starts with (.
Unparenthesized arguments — `MIN(a | b, c) silently miscompares due to operator precedence.
Trailing semicolon inside the macro body plus one at the call site — double semicolon breaks if/else chains.
Redefining a macro mid-codebase — later files see the new body, earlier files saw the old one, and compile order changes behavior.
Interview angle
Classic questions: difference between a macro and a parameter (text substitution vs typed elaboration-time constant); why UVM uses macros for factory registration (code generation that parameters cannot do); what `" does that a normal quote does not (argument substitution inside the string). Be ready to write a safe multi-line macro with line continuations on a whiteboard.