Part 7 · Advanced & Integration · Intermediate
Debugging Macro Problems
Reading expansion errors, silent ifdef skips, redefinition warnings, preprocessor dumps, `__FILE__/`__LINE__, and a safe log macro.
Why macro errors look wrong
The compiler never sees your macro — it sees the expanded text . So the error message describes code you never typed, reported at the line where the macro was used. Good tools print the expansion chain ('expanded from macro CHECK at file.sv:12'); your job is to mentally re-expand the macro and find which substituted argument broke the syntax.
`define CHECK(cond) if (!(cond)) $error("check failed");
// Use site — note the trailing semicolon problem:
if (enable)
`CHECK(a == b); // expands to: if (!(a == b)) $error(...);;
else // ERROR near 'else' — dangling else after ;;
do_other();
// The error points at 'else', but the bug is the macro design:
// the body should be a begin/end so the caller's semicolon
// completes the statement exactly once.A debugging checklist
Re-expand the macro by hand (or with the tool's preprocess dump) and read the result as plain code.
Check argument text for commas, semicolons, or unbalanced parentheses that split or terminate the expansion unexpectedly.
Check for the double-semicolon / dangling-else pattern when the macro is used inside if/else.
Verify the macro is actually defined at the use point — definition order across files follows compile order.
Silent failures and redefinitions
The most expensive macro bugs produce no error at all . A misspelled name in `ifdef simply evaluates false and the guarded code disappears. A macro redefined with a different body generates only a warning that scrolls past in a thousand-line compile log — and every use after the redefinition means something new.
SILENT MACRO FAILURE MODES
symptom actual cause
───────────────────────────────────────────────────────────────
assertions never fire `ifdef ASSRT_ON ← typo, was ASSERT_ON
feature missing from build +define+ flag dropped from one Makefile
constant has wrong value macro redefined later in compile order
code behaves differently `undef in an included file you forgot
in two different sims about, or different +define+ per tool
defense: tool preprocess-dump options write the fully expanded
source to a file (e.g. VCS -E and the Questa/Xcelium equivalents)
— grep the dump to see what the compiler ACTUALLY received.Making silent failures loud
// Force a compile error if a required flag is missing,
// instead of silently building the wrong configuration:
`ifndef TARGET_DEFINED
`ifdef FPGA_TARGET
`define TARGET_DEFINED
`endif
`ifdef ASIC_TARGET
`define TARGET_DEFINED
`endif
`endif
`ifndef TARGET_DEFINED
ERROR_no_target_selected_use_plus_define; // illegal token → compile stops
`endif`__FILE__, `__LINE__, and a safe log macro
The predefined macros `__FILE__ and `__LINE__ expand to the file name string and line number at the expansion site. Embedded in a log or assert macro, they stamp every message with its true source location — the single biggest quality-of-life upgrade for testbench logs.
// A safe ASSERT-style log macro, applying every defensive rule:
// - begin/end body → no dangling-else, caller's ; is harmless
// - (cond) parenthesized; msg passed through %s
// - `__FILE__/`__LINE__ give the real source location
`define TB_ASSERT(cond, msg) \
begin \
if (!(cond)) begin \
$error("[TB_ASSERT] %s (%s:%0d)", \
msg, `__FILE__, `__LINE__); \
err_count++; \
end \
end
// Usage — safe inside any if/else, semicolon and all:
if (phase_done)
`TB_ASSERT(rd_cnt == wr_cnt, "fifo count mismatch");
else
`TB_ASSERT(!fifo_empty, "premature drain");
// Log output:
// [TB_ASSERT] fifo count mismatch (tb/scoreboard.svh:87)Key takeaways
Compiler errors describe expanded text — re-expand the macro mentally or with a preprocess dump to find the real bug.
Misspelled `ifdef names fail silently — build loud guards that error when no valid configuration is selected.
Treat macro-redefinition warnings as errors; compile order determines which body wins.
Wrap statement macros in begin/end and stamp messages with `__FILE__/`__LINE__.
Common pitfalls
Debugging at the error line instead of the expansion — the use site shows the macro name, not the broken expanded code.
Passing an argument containing an unparenthesized comma — the preprocessor splits it into two arguments.
Bare-statement macro bodies — caller's semicolon creates ;; and breaks if/else nesting.
Trusting the source file over the preprocess dump — what you read and what the compiler received can differ.
Interview angle
Strong candidates can write a safe assert/log macro live: begin/end wrapping, parenthesized condition, `__FILE__/`__LINE__ in the message, and an explanation of the dangling-else hazard. Also expect: how would you find out why code under `ifdef never compiled in — answer includes checking +define+ flags in the build log and dumping preprocessed output.