Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
    • Parameterization and Generate Hub
    • Top-Level Integration Hub
      • Top-down vs bottom-up integration
      • Port list discipline
      • Parameter propagation across hierarchy
    • Synthesis Intent Hub
    • Style and Static Checks Hub
    • Assertions in RTL Hub
    • Formal Verification Basics Hub
    • Reusable SystemVerilog for RTL
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Top-Level Integration Hub
  1. Topics
  2. RTL Design
  3. Advanced RTL
  4. Top-Level Integration Hub
  5. Top-down vs bottom-up integration

Part 8 · Advanced · Advanced

Top-down vs bottom-up integration

Interface contracts first or proven blocks first, and the blend most programs use.

On this page

    Why this matters

    Leaves verified in isolation can fail together: a counter tested with one-cycle pulses over-counts three times when the new top feeds it a three-cycle level. The integration order decides who finds that first.

    After this lesson you can: choose top-down, bottom-up or a blend for a given situation, list a leaf's input assumptions before reuse, and fix a broken one in the integration shell.

    Two ways to start

    Top-down starts from architecture and partitions interfaces first. It is stronger when requirements are evolving and interface contracts are central.

    Bottom-up starts from validated leaf IP and composes toward subsystem behavior. It is faster when reusable blocks are mature and already verified.

    Top-down: architecture, then freeze boundary contracts, then build leaves to the contract. Bottom-up: verified leaf IP, then check each leaf assumption against the new top. Both paths end in integrate and system test.
    Both approaches meet at integration: top-down writes the boundary contract before the leaves exist, bottom-up must check each verified leaf's assumptions against the new top

    Read each column from the top. The two columns end in the same box, but they reach it with different evidence.

    • Top-down owns the contract from the start, so a leaf that breaks it fails its own test.

    • Bottom-up inherits contracts from old tests. Its risky step is the middle box: the leaf passed under assumptions the new top may not keep.

    The blend most programs use

    Most real programs blend both: architecture-led partitioning with proven IP reuse. The top defines the contracts, and the leaves arrive already verified.

    Situation

    Start

    Interfaces still moving, blocks not yet written

    Top-down: freeze the contracts first

    Mature verified blocks cover most of the function

    Bottom-up: compose the proven leaves

    New function around existing IP

    Blend: partition the new shell, reuse the leaves

    Either way, ownership of clocks, resets, and handshake timing is documented at each boundary. Unowned timing becomes an integration bug.

    A real case: a verified leaf, a broken assumption

    An event counter evt_counter was verified on its own. Its testbench drove evt_i as a one-cycle pulse per event, and every check passed. That pulse rule was never written down; it lived only in the testbench.

    The new top reuses the counter bottom-up to count ADC conversions. The ADC's conv_done is a level: it stays high for three cycles per conversion. Wired straight to evt_i, the counter adds one every cycle the level is high.

    Each slot is one clock cycle, and the counters change one slot after the input they count. cnt_naive is the leaf wired to the level; cnt_fixed is the same leaf behind the shell's edge detector, whose output is evt_pulse.

    • Slots 1 to 3: the first conversion holds conv_done high for three cycles. cnt_naive climbs 1, 2, 3; evt_pulse is high only in slot 1, so cnt_fixed becomes 1.

    • Slots 5 to 7: the second conversion does the same. cnt_naive ends at 6 for two conversions, three times too many; cnt_fixed ends at 2.

    • Nothing failed in the leaf. The counter does exactly what its own test proved. The bug is in the boundary.

    Fix it at the boundary

    The top owns that boundary, so the fix belongs in the integration shell, not in the verified leaf. A top-down contract would have said it first: events cross this boundary as one-cycle pulses. The shell then converts the ADC's level into that form.

    design.sv
    1
    2
    3
    4
    5
    // snippet: the shell turns the level into the pulse the leaf was verified with
    always_ff @(posedge clk_i or negedge rst_ni)
      if (!rst_ni) done_q <= 1'b0;
      else         done_q <= conv_done_i;
    assign conv_evt = conv_done_i && !done_q;   // high for one cycle at each rising edge

    Approach

    Where this bug is caught

    Top-down

    Before integration: the boundary contract says one pulse per event, so the ADC side must provide it or the shell must convert it

    Bottom-up

    At integration, only if someone lists the leaf's assumptions (pulse, clock, reset) and checks each against the new top

    Neither

    In the lab, as a count three times too high

    Common mistakes

    Common pitfall

    Treating a leaf's passing testbench as its contract. The testbench shows what was driven, not what is required. Write each input's timing rule (pulse or level, clock, reset) beside the port before reuse.

    Common pitfall

    Freezing the block partition before the interface contracts are stable forces rewiring later. Partition the interfaces first, and the block split follows.

    Interview

    Likely question: when is bottom-up the wrong choice? When the leaves were verified under assumptions the new top breaks, such as different clocking, reset polarity, or handshake timing. Name one, like a pulse input fed by a level, and show where the fix goes.

    Check yourself Requirements are still shifting and no leaf exists yet. Which start fits, and what is frozen first?

    Top-down. The interface contracts are frozen first, so later block work has stable boundaries.

    Check yourself Two verified filters cover the needed function exactly. What does the top still own?

    The top still owns the composition: clocks, resets, handshake timing, and the proof that the pair meets the subsystem contract.

    Check yourself Why put the edge detector in the shell instead of inside evt_counter?

    The counter is verified and may be reused elsewhere with real pulses. Changing it would void that evidence and change its behaviour for every other user. The shell owns this boundary, so it adapts the signal.

    Testbench

    The bench runs the three steps of the story in one simulation: the leaf alone with pulses, the naive top with the level, and the fixed top with the level. Two conversions are used each time. It requires counts of 2, 6 and 2, so it proves both that the mismatch is real and that the fix removes it.

    evt_counter.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    module evt_counter (
      input  logic       clk_i,
      input  logic       rst_ni,
      input  logic       evt_i,      // contract: one-cycle pulse per event
      output logic [7:0] count_o
    );
      always_ff @(posedge clk_i or negedge rst_ni)
        if (!rst_ni)    count_o <= 8'd0;
        else if (evt_i) count_o <= count_o + 8'd1;
    endmodule
    
    // Naive integration: the ADC's level goes straight into the pulse input.
    module sense_top_naive (
      input  logic       clk_i,
      input  logic       rst_ni,
      input  logic       conv_done_i,   // level, high for several cycles per conversion
      output logic [7:0] conv_count_o
    );
      evt_counter u_cnt (.clk_i(clk_i), .rst_ni(rst_ni), .evt_i(conv_done_i), .count_o(conv_count_o));
    endmodule
    
    // Fixed integration: the shell owns the boundary and makes one pulse per conversion.
    module sense_top (
      input  logic       clk_i,
      input  logic       rst_ni,
      input  logic       conv_done_i,
      output logic [7:0] conv_count_o
    );
      logic done_q;
      logic conv_evt;
      always_ff @(posedge clk_i or negedge rst_ni)
        if (!rst_ni) done_q <= 1'b0;
        else         done_q <= conv_done_i;
      assign conv_evt = conv_done_i && !done_q;
      evt_counter u_cnt (.clk_i(clk_i), .rst_ni(rst_ni), .evt_i(conv_evt), .count_o(conv_count_o));
    endmodule
    

    Remember

    • Top-down fixes the boundary contracts first; bottom-up composes proven leaves and must check their assumptions.

    • A leaf's passing testbench shows what was driven, not what is required: write pulse/level, clock and reset rules beside each port.

    • Fix a broken assumption at the boundary the top owns, not inside the verified leaf.

    Rule of thumb: unowned timing at a boundary becomes an integration bug.

    How this was checked

    Testbench run with Icarus Verilog 13.0 (2026-10-17): prints PASS. In one simulation it drives two events into the leaf alone (count 2), the naive top with a three-cycle level (count 6) and the fixed top with the same level (count 2), and ends in $fatal on any other count. The waveform comes from a separate simulation of the same leaf and edge detector.

    It is a finite digital check of one pulse-versus-level mismatch; clock, reset and handshake assumptions need the same per-port review, and timing signoff remains separate.

    Keep going

    • Interview: run a mock interview on this topic

    Related topics

    • Port list discipline

    • Parameter propagation across hierarchy

    • Level and pulse events in a peripheral

    Previous

    249 / 350

    Next: Port list discipline

    Up next

    Up nextPort list discipline

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Parameter propagation across hierarchy
    clkconv_doneevt_pulsecnt_naive0123456cnt_fixed012
    Two conversions, each with conv_done high for three cycles: the counter wired to the level reaches 6, the same counter behind a rising-edge detector reaches 2