Modular Design in Verilog and VHDL: How to Build Reusable IP

“Don’t reinvent the wheel.” Seems simple enough. Unfortunately, it is all too common that a new project, in general, will involve redoing the same things. You might say, “I need a SPI interface.” “I need a register space,” or “I need some module with a slight modification from previous designs.” Many times, this can be done quickly. After all, “I designed it before, it will be faster this time around.” Though, it may not be so quick for the next engineer.

Enter modular design.

Modular Design in Verilog and VHDL

Modular design is about stepping back and resisting the urge to solve only the problem directly in front of you. Instead of building a block for this SPI or this register map, you build a block that can serve many designs with minimal, if any, modification. The work shifts from rewriting logic to configuring and composing existing pieces, allowing designs to evolve without constant reinvention.

In Verilog and VHDL, where designs tend to live for years and outlast their original authors, this approach becomes especially important. A modular design is easier to understand, easier to verify, and far easier to reuse. More importantly, it allows the next engineer (who may be you in six months) to make progress without first untangling a pile of tightly coupled logic.

Read more about VHDL and Verilog here.

What Is Modular Design?

Modular design is the result of a designer intentionally not hard coding the specifics of a single use case into a design. Instead of optimizing for one narrow scenario, the design is structured so it can perform its task across many contexts. The specifics, such as configuration, parameters, and behaviors, are not embedded in the design itself, but are supplied at the time the design is used, usually in the form of parameters/generics and packages.

The goal of modular design is flexibility. By separating the core function from the details of how and where it is applied, a modular design can be reused, adapted, and extended without needing to be redesigned from scratch. This makes change cheaper, safer, and more predictable, since modifications tend to happen at the boundaries rather than in the core.

Modular design is less a method and more an attitude toward designing systems. It emphasizes restraint: resisting the urge to bake in assumptions, preferences, or edge cases too early. A modular designer assumes that requirements will change, and designs in a way that allows those changes to be absorbed with minimal disruption.

Read more from IEEE.

Do you need help with your design?

Contact BLT’s embedded experts.

Benefits of Reusable Blocks

Reusable blocks are a natural outcome of modular design. When a system is broken into well-defined pieces with clear responsibilities, those pieces can be used again without needing to be rewritten or rethought. Each block solves a specific problem, and because it avoids assumptions about its context, it can fit into many different designs.

One major benefit of reusable blocks is consistency. When the same block is used in multiple places, behavior stays predictable and familiar. Bugs are fixed once instead of repeatedly, and improvements automatically carry forward wherever the block is used. Over time, this leads to systems that are more stable and easier to maintain.

Reusable blocks reduce cognitive load. Designers and developers do not need to understand the entire system at once; they only need to understand how the blocks connect and what each one is tasked with. This makes systems easier to scale, easier to collaborate on, and easier to maintain as they grow.

Reusable blocks encourage long-term thinking. When you invest effort upfront to define clear boundaries and interfaces, that effort pays off as the system evolves. Instead of rebuilding for every new requirement, existing blocks are rearranged, extended, or replaced, allowing progress without constant reinvention.

Examples of Module Design Hierarchies

Module hierarchies are used to manage complexity by separating low-level signal handling from higher-level system behavior. Each level in the hierarchy focuses on a different scope of responsibility, allowing designs to scale without becoming tangled or fragile.

Lowest Level

At the lowest level are primitive or utility modules. These handle simple, reusable tasks such as clock division, reset synchronization, FIFOs, counters, or protocol-independent handshaking. These modules are intentionally small and parameterized, with no knowledge of the larger system they will be used in.

Mid Level

Above that are functional modules. These combine multiple low-level blocks to implement a specific function, such as a UART, SPI controller, memory interface, or DSP block. Functional modules define clear interfaces and timing expectations but still avoid assumptions about how their outputs will be consumed or where their inputs originate.

Adjacent to that is IC-specific interface and driver modules. These may use functional modules but are there to provide easy access from system modules to an IC. This could be as simple as a mailbox interface to retrieve and drive data on an IC, or a more complex driver of the IC with some interaction with the system.

Top Level

At the top level are system modules. These modules are responsible for wiring together functional blocks, managing shared resources, and defining overall data flow. A system module might connect multiple interfaces, arbitrate access to memory, or bridge between clock domains. Ideally, it contains very little logic of its own and focuses instead on composition and configuration.

Across this hierarchy, lower-level modules are reused widely and change rarely, while higher-level modules are more specific and evolve as system requirements change. The hierarchy allows FPGA designs to grow in both size and capability while remaining understandable, testable, and reusable.

Related Content

Tips for Naming and Organizing Modules

Good naming starts with intent. A module’s name should describe what the module does, not how it does it or where it happens to be used (i.e. no project name is appended). Names that focus on behavior and responsibility tend to age better than names tied to a specific feature or implementation detail. If a module’s name would stop making sense when the context changes, it is likely too specific.

Modules should be organized by responsibility rather than by type (i.e. cdc, protocol, memory, etc.). Grouping modules because they share a purpose makes it easier to find and reuse them. When organizing modules by technical category instead, relationships become less clear and reuse becomes accidental rather than intentional.

Consistency matters more than perfection. Using a predictable naming pattern makes a system easier to navigate, even if the pattern is not ideal. When naming and organizing modules consistently, people can infer structure without needing documentation, and the system becomes easier to extend without breaking its underlying design.

Modular Design Conclusion

Modular design is about designing for change. By avoiding unnecessary assumptions and separating core functionality from specific use cases, designs become more flexible and resilient over time. In FPGA development, this mindset is especially valuable, as complexity grows quickly and late changes are often expensive.

Reusable blocks, clear hierarchies, and thoughtful organization work together to keep designs understandable and scalable. Lower-level modules remain simple and stable, while higher-level modules express intent through composition rather than detail. The result is a system that can be adapted, extended, and reused without constant rework.

Modular Design