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.