Every parameterized module you write eventually hits the same wall: you can make the port widths flexible with a parameter, but the logic inside is still written for a fixed size. Add a pipeline stage and you rewrite the always block. Change the adder width and you manually duplicate instantiations. The module is parameterized in name only.
Generate blocks solve this. They let the elaborator write the repetitive hardware for you — instantiating N copies of a submodule, building an N-wide logic chain, or selecting between two architectures based on a parameter. This post covers the three generate constructs you will use in every serious parameterized design: for-generate, and if-generate, with practical examples for each.
1. Generate Blocks Run at Elaboration, Not Simulation
The critical mental model shift: a generate for loop is not a software loop. It does not execute at runtime. It runs once, before simulation or synthesis begins, during a phase called elaboration. The elaborator unrolls the loop and creates a separate piece of hardware for each iteration — each with its own signals, flip-flops, and connections. The result is identical to writing that hardware by hand.
The example below shows a pipeline register written both ways. The manual version does not scale — adding a stage means editing the module. The generate version scales to any depth by changing a single parameter.
2. For-Generate: Building Bit-Slice Logic
The most common use of for-generate is building N-wide combinational logic from a single-bit operation applied across every bit position. Instead of writing one assign per bit, you write one assign inside a generate loop and let the elaborator replicate it N times, once per index value.
A Gray code encoder is the canonical example: the MSB passes through unchanged, and every other output bit is the XOR of two adjacent binary input bits. The pattern is identical for every bit — only the index changes. With generate, this becomes a single parameterized module that works for any width.
: gen_xor label after begin is not optional decoration. It creates a hierarchical scope — Vivado uses it to reference generated instances in timing constraints (get_cells gen_xor[3]/...) and in simulation waveforms. Unnamed generate blocks make post-synthesis debug significantly harder.
3. For-Generate: Instantiating N Submodules
The second major use of for-generate is structural: instantiating a submodule N times and connecting the instances in a chain. This is how you build parameterized multi-bit structures from a single verified 1-bit building block. The elaborator creates N distinct instances, each with its own unique name derived from the generate block label and the loop index.
An N-bit ripple carry adder demonstrates the pattern clearly. A single full_adder module handles one bit. The generate loop chains N of them together, threading the carry signal from each instance into the next. Changing the parameter N changes the adder width — no other edit is needed.
4. If-Generate: Choosing the Architecture
If-generate selects between two (or more) hardware implementations based on a parameter evaluated at elaboration time. Only the selected branch is synthesized — the other branch does not exist in the netlist. This lets a single module file support multiple architectural variants without any runtime multiplexing overhead.
A common use is selecting between a registered and a combinational output path based on a REGISTERED parameter. The caller instantiates the same module name and simply overrides the parameter. Timing, area, and latency change without touching the module implementation.
genvar inside an always block, assign it to a port, or use it outside a generate loop. It exists only during loop unrolling. If you need a loop index at runtime — for example, in an FSM that iterates over N channels — you need a regular logic counter, not a genvar.
Final Thoughts: Parameters Without Generate Are Half-Finished
A parameter that only controls port widths is a weak abstraction. As soon as the internal logic needs to scale with that parameter — more pipeline stages, more submodule instances, more bit-slice operations — you need generate. Together, parameters and generate blocks form the complete tool for writing hardware that is genuinely reusable: change one number at the top, get a completely different but always-correct implementation out the bottom.
Start with for-generate any time you catch yourself copy-pasting logic with incrementing indices. Move to if-generate any time a module needs to support two architectures without two separate files. Name every generate block — your timing constraints and your future self will thank you.
Happy coding.
fpgawizard.com

