The painfull verilog preprocessor pitfall

Just a little note about how includes and `defines work in verilog which is VERY different from how they behave in most programming languages. This may not really hurt in a small project, but can become a real PITA in a big project with a dozen of third-party blocks.

TL;DR: Macro defines are have a global scope in verilog and propagate from file to file during one tool invocation.

Show me the code!

Suppose we have a following example:

1.vh

`define A 1

1.v

`include "1.vh"
 
module top;
top2 t();
 
`ifdef A
    initial begin
        $display("Yarr! (1)\n");
    end
`endif
 
endmodule

2.v

module top;
 
`ifdef A
    initial begin
        $display("Yarr! (2)\n");
    end
`endif
 
//This will warn us if we're redefining a macro
`define A B
 
endmodule

Now let’s try to compile it. I will use three different tools:
1. Icarus Verilog (OpenSource)
2. Verilator (OpenSource)
3. Cadence ncvlog/ncsim (Luckily I have access to these tools at work)

Let’s start from Icarus Verilog.

0 ✓ necromant @ silverblade ~/Work/test $ vvp a.out 
Yarr! (2)
 
Yarr! (1)
 
0 ✓ necromant @ silverblade ~/Work/test $ iverilog 2.v 1.v 
0 ✓ necromant @ silverblade ~/Work/test $ vvp a.out 
Yarr! (1)

Oops? How did that happen? 1.v included 1.vh that had A defined. This define persisted when compiling 2.v. However if we change the order of compilation – everything will work the other way. What’s even worse – iverilog will never warn us about redefining a macro (At least Icarus Verilog version 0.9.7 that is shipped in debian repositories)

Verilator (v. 3.900 2017-01-15) does exactly the same, but at least it spits out a warning when redefining a macro.

0 ✓ necromant @ silverblade ~/Work/test $ verilator --cc 1.v 2.v 
%Warning-REDEFMACRO: 2.v:10: Redefining existing define: A, with different value: B 
%Warning-REDEFMACRO: Use "/* verilator lint_off REDEFMACRO */" and lint_on around source to disable this message.
%Warning-REDEFMACRO: 1.vh:3: Previous definition is here, with value: 1 
%Error: Exiting due to 2 warning(s)
%Error: Command Failed /usr/bin/verilator_bin --cc 1.v 2.v

Finally, let’s test Cadence tools. The tools from cadence are really pricey, but you can always give them a spin for free to test out things out on edaplayground.

I will not use irun for this test (which is nothing, but a bunch of awful hacks IMO), but revert to plain old three-step invocation. Cadence tool called ncvlog compiles stuff into a library before elaboration, so we can choose between invoking ncvlog for each file or feed all files to it at once. Surprisingly this will result in a totally different behavior. See for yourself.

0 ✓ necromant @ silverblade ~/Work/test $ ncvlog -q 1.v  
0 ✓ necromant @ silverblade ~/Work/test $ ncvlog -q 2.v
0 ✓ necromant @ silverblade ~/Work/test $ ncelab -q top
0 ✓ necromant @ silverblade ~/Work/test $ ncsim -q top
ncsim> run
Yarr! (1)
 
ncsim: *W,RNQUIE: Simulation is complete.
ncsim> exit

Okay, no preprocessor macro propagation! Sweet! What if I supply all files at once?

0 ✓ necromant @ silverblade ~/Work/test $ ncvlog -q 1.v 2.v
`define A B
              |
ncvlog: *W,MACRDF (2.v,9|14): text macro 'A' redefined - replaced with new definition.
0 ✓ necromant @ silverblade ~/Work/test $ ncelab -q top
0 ✓ necromant @ silverblade ~/Work/test $ ncsim -q top
ncsim> run
Yarr! (1)
 
ncsim: *W,RNQUIE: Simulation is complete.
ncsim> exit

What can be done about it?

Hint: There’s no silver bullet. Life is pain.

This behavior should never be a problem for a small project. But if you are developing a SoC and licensed/downloaded from opencores a bunch of third-party IPs – chances are they may use a lot of macros to enable/disable different functionality. Even worse, they may depend on defines to be propagated via a file-list.

The ability of some tools (e.g. cadence) to isolate the scope of macro visibility for certain groups of files is NOT the solution (and is likely an undocumented feature): When synthesizing you’ll need to pass the full filelist to the tool and you’ll end up with different RTL for synthesis and simulation which is even a bigger problem that can lead to infinite amount of pain.

  • Watch out for warnings on macro redefinitions, pick the tools that actually DO warn about them and treat them as errors! (This won’t save you from a hidden `ifdef in a third-party code, though)
  • Use scripts to catch and check defines / ifdef in third-party code and check the codebase for possible collisions regularly.

Leave a Reply