Day 8 · Hierarchy & Reuse

Design for Reuse

Video 4 of 4 · ~8 minutes · Week 2 Capstone

Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF

HierarchyParametersGenerateReuse

🌍 Where This Lives

In Industry

ARM licenses Verilog IP. Cadence and Synopsys sell Verilog IP. OpenCores distributes free Verilog IP. The IP economy exists because well-designed modules drop into other designers' projects without modification. Engineers' reputations in the industry are built on one thing: has your module been reused? When a module at a large company gets used by 10 different chips, its author is untouchable.

In This Course

Your Week 1-2 module library now includes ~10 tested modules. Week 3 will build UART and SPI by combining them. Capstone and senior-design projects will reach back into this library for years. Today's practices determine whether that library is usable or merely available.

Your Growing Module Library

ModuleDayParametersTested?
sync_2ff5WIDTH
debounce5CLKS_STABLE
edge_detect5
mod_n_counter5N
sipo_shift5WIDTH
piso_shift5WIDTH
traffic_fsm7
pattern_detector7PATTERN, WIDTH
fifo8DEPTH, WIDTH
button_array8N, CLKS_STABLE
These are the building blocks for Week 3. UART TX = piso_shift + mod_n_counter (baud) + FSM. UART RX = sync_2ff + sipo_shift + oversampler FSM. SPI = both + CS debounce. Everything composes.

⚠️ “Works for Me” vs. “Reusable”

❌ “Works for me”

Hard-coded widths. Assumes particular clock rate. Testbench works only with “my” input pattern. Undocumented. Signal names reflect current project, not purpose. Sub-modules tangled with the top. Only original author can use it — and only in this project.

✓ Reusable

Parameterized over everything that varies. Clock-rate independence where possible. Testbench covers the module's contract (not a specific application). Documented inputs, outputs, behavior, timing, assumptions. Named for function. Self-contained — no external dependencies. A stranger can drop it in and it works.

The receipt: A reusable module passes a simple test: someone who didn't write it can instantiate it correctly by reading only the module header and a short comment block. If they need to read the implementation, the module has failed at reuse.

👁️ I Do — Anatomy of a Reusable Module

//-----------------------------------------------------------------------------
// debounce.v — counter-based mechanical switch debouncer
//
// Takes an asynchronous, potentially bouncing input, and produces
// a clean version that only changes after CLKS_STABLE consecutive
// cycles of input persistence.
//
// Parameters:
//   CLKS_STABLE : cycles to wait before accepting a new value
//                 (default 500_000 = 20ms @ 25MHz)
//
// Ports:
//   i_clk    : synchronous clock
//   i_reset  : synchronous reset (active high)
//   i_noisy  : input from external switch (async OK, but sync first
//              externally if clock-domain-crossing applies)
//   o_clean  : stable debounced output
//
// Latency: up to CLKS_STABLE cycles after a stable input change.
// Usage:   debounce #(.CLKS_STABLE(N)) u_deb (...);
//-----------------------------------------------------------------------------
module debounce #(parameter CLKS_STABLE = 500_000) (
    input  wire i_clk, i_reset, i_noisy,
    output reg  o_clean
);
    // ... implementation ...
endmodule
My thinking: The header comment is the “IP datasheet.” A reuser sees parameters, ports, behavior, latency, and usage — everything they need to instantiate correctly without reading implementation.

🤝 The Reusability Checklist

  • ☐ Every dimension that could vary is a parameter
  • ☐ All parameters have sensible defaults
  • ☐ Module is self-contained — no globals, no external definitions
  • Header comment describes purpose, parameters, ports, timing, usage
  • ☐ Signal names describe function, not the current project's context
  • ☐ Self-checking testbench covers the module's contract, not just happy-path inputs
  • ☐ Testbench runs under a Makefile target with one command
  • ☐ Synthesizable; make stat produces expected cell count
  • ☐ If the module is part of a pipeline, its latency is documented

🧪 You Do — Audit One of Your Modules

Pick a module from your Week 1-2 labs. Score it against the reusability checklist. For each unchecked box, name the work you'd do to check it.

Most common gaps (I've seen them all):
  • No header comment → add datasheet block
  • Hard-coded 25_000_000 or 250_000 → parameterize CLKS_PER_SEC
  • Signal names like tmp, x, cnt → rename for purpose
  • Testbench that only tests one specific case → add boundary + random
  • No Makefile target → add make sim

🔧 Before / After Comparison

BEFORE: “works for me”

module btn (
    input clk, rst, x,
    output y
);
    reg [18:0] cnt;
    // ... 20 ms @ 25 MHz ...
    // (hard-coded to my project)
endmodule

AFTER: reusable

// Header comment: datasheet
module debounce #(
    parameter CLKS_STABLE = 500_000
) (
    input  wire i_clk, i_reset, i_noisy,
    output reg  o_clean
);
    localparam W = $clog2(CLKS_STABLE);
    reg [W-1:0] r_count;
    // ... logic with docs ...
endmodule
Same functionality. Different reach. “After” version works on any clock rate, any window, with any name in any project. The change took 5 minutes; the reuse payoff lasts years.

🤖 Check the Machine

Ask AI: “Here's my debouncer. Refactor it to be reusable: add a parameter for the debounce window, auto-size the counter width, add a header comment block in a datasheet style, and rename signals with the i_/o_/r_ convention.”

TASK

AI refactors to reusable style.

BEFORE

Predict: parameter added, $clog2 used, renamed signals, datasheet comment.

AFTER

Strong AI documents latency + timing assumptions. Weak AI only does syntactic rename.

TAKEAWAY

“Reusable” means semantic changes + docs, not just renaming.

Key Takeaways

 A module is reusable when a stranger can use it from the header alone.

 Everything that could vary is a parameter; everything else is documented.

 The header comment is the IP datasheet. Write it as such.

 Your 10-module library becomes Week 3's UART + SPI backbone.

If reading the implementation is necessary to use it, it isn't reusable.

Pre-Class Self-Check

Q1: Name three things that should be parameterized in a debouncer module.

CLKS_STABLE (window in cycles), possibly RESET_VAL (initial output), possibly the input-width if you want a multi-bit debouncer. Clock rate itself stays external — the module is in units of “clocks,” not milliseconds.

Q2: What goes in a header comment block that doesn't go in the Verilog code itself?

Purpose, assumptions, timing (latency), usage example, required context. Code answers “how”; comments answer “what for” and “how to use.”

Pre-Class Self-Check (cont.)

Q3: Why is signal naming part of reusability?

Signals with project-specific names (sw1_press, fan_on) can't be used in other projects. Signals with functional names (i_noisy, o_clean) work everywhere.

Q4: What single question do you ask to tell if a testbench tests a module's contract vs. a specific application?

“If I doubled the parameter values, would this testbench still be relevant?” If no, the testbench is application-specific. If yes, it tests the module itself.

🔗 End of Week 2

Week 3: Real Communication Protocols

UART · SPI · Integration Projects

▸ WHY THIS MATTERS NEXT

You've now learned the alphabet of hardware design: combinational logic, sequential logic, synchronization, verification, state machines, and reuse. Week 3 is where you compose words and sentences. UART TX + RX — to actually talk to your computer over a serial cable. SPI master — to drive peripherals. Your Week 1-2 module library isn't the byproduct; it's the material of Week 3's designs. You're not going to learn many new primitives next week — you're going to build real things out of the ones you already have. See you Day 9.