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
    • Flip-Flops and Registers
    • Counters and Timers
    • Shift Registers and LFSRs
    • Pipelining
    • Stalls, Bubbles and Hazards
      • Structural Hazard and Stall Logic
      • Pipeline Flush on Branch Resolve
      • Retiming-Friendly Coding Notes
      • Data Hazards and Forwarding
      • A Round-Robin Arbiter from a Priority Encoder
    • Retiming
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Stalls, Bubbles and Hazards
  1. Topics
  2. RTL Design
  3. Sequential Design
  4. Stalls, Bubbles and Hazards
  5. Structural Hazard and Stall Logic

Part 3 · Sequential · Intermediate

Structural Hazard and Stall Logic

Two stages share one port: detect the conflict, stall the loser, lose nothing.

On this page

    Why this matters

    When two stages need the same memory port in one cycle, one must wait. Stall the front by clearing its valid bit and you silently throw its item away.

    After this lesson you can: write the arbiter and hold logic for a shared resource, and explain how a conflict becomes a bubble downstream.

    One resource, two users

    A structural hazard appears when two stages need the same hardware in the same cycle. A common case is one memory port shared by instruction fetch and data access.

    Only one stage can use the port, so the other waits. Fixed priority gives the port to the older (later) stage and stalls the front.

    Two stages compete for one shared memory port.
    Fetch and Memory both want the single memory port. When both request it, Memory wins and Fetch stalls.

    The full listing sits under Testbench below. The arbiter and front_stage are arbitration and storage fragments: holding a register alone does not define a downstream transfer. The front_boundary wrapper connects them for a port that completes every granted request at the rising edge; a variable-latency memory would need a separate response contract.

    From storage hold to a transfer boundary

    In the wrapper, fetch_want is the stored valid bit. Downstream consumes only when issue_valid = fetch_grant, so a conflict inserts a downstream bubble even though the front still holds valid work. Upstream may replace the stored item only when new_ready = !valid || !mem_want. While new_valid && !new_ready, it must retain its offer. An empty stage can accept an item even while Memory owns the port.

    Edge (pre-edge state)

    Upstream offer

    Memory wants port

    new_ready / issue_valid

    Transfers; state after edge

    0: empty

    A = 0x96

    0

    1 / 0

    Accept A; hold A

    1: A

    B = 0x3C

    1

    0 / 0

    No front transfer; downstream bubble; retain A and offer B

    2: A

    B (unchanged)

    1

    0 / 0

    Another bubble; retain A and B

    3: A

    B (unchanged)

    0

    1 / 1

    Deliver A once and accept B; hold B

    4: B

    None

    0

    1 / 1

    Deliver B once; become empty

    5: empty

    None

    0

    1 / 0

    No transfer; downstream bubble

    Three ways to deal with a structural hazard

    Fix

    Cost

    When

    Stall the loser

    Lost cycles, simple logic

    Conflicts are rare

    Duplicate the resource (two ports, separate I/D memories)

    Area

    Conflicts are frequent

    Time-multiplex at double rate

    Clock/timing pressure

    Resource is cheap to speed up

    Common mistakes

    Common pitfall

    When a stage is stalled it must hold both its data and its valid bit. Clearing valid on a stall silently throws the item away; letting valid through with new data overwrites it. The front_stage module holds both.

    A stall signal that fans out to many registers is a high-fanout, often timing-critical net. Pipeline-friendly designs prefer the valid/ready local handshake over a global stall; see valid/ready flow control.

    Check yourself With fixed priority to the back stage, can the front stage starve forever?

    Yes, if the back stage requests the port every cycle. If that is possible, use round-robin or a rule that guarantees the front stage a turn (see the arbiter lesson).

    Testbench

    The testbench first checks every arbitration combination and deliberately changes storage inputs during stalls to isolate hold behavior. Those changing inputs are not a legal ready/valid source. A second, directed boundary check follows the table above, retains the blocked B offer, and scores accepted versus delivered items with a queue and a source stability monitor.

    stall_demo.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
    37
    38
    39
    40
    41
    42
    43
    module stall_demo (
      input  logic       clk, rst_n,
      input  logic       fetch_want,        // front stage has a request ready
      input  logic       mem_want,          // back stage has a request ready
      output logic       fetch_grant,
      output logic       mem_grant,
      output logic       stall_front        // freeze everything upstream of the back stage
    );
      // fixed priority: the back (older) stage always wins
      assign mem_grant   = mem_want;
      assign fetch_grant = fetch_want && !mem_want;
      assign stall_front = fetch_want && mem_want;     // front wanted the port and lost
    endmodule
    
    module front_stage (
      input  logic       clk, rst_n,
      input  logic       stall,
      input  logic [7:0] new_item,
      input  logic       new_valid,
      output logic [7:0] item,
      output logic       valid
    );
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n)      valid <= 1'b0;
        else if (!stall) valid <= new_valid;          // when stalled: HOLD both valid and data
      always_ff @(posedge clk)
        if (!stall && new_valid) item <= new_item;
    endmodule
    
    module front_boundary (
      input  logic       clk, rst_n, mem_want,
      input  logic       new_valid,
      input  logic [7:0] new_item,
      output logic       new_ready, issue_valid,
      output logic [7:0] issue_item
    );
      logic valid, mem_grant, conflict;
      assign new_ready = !valid || !mem_want;
      stall_demo arb (.clk, .rst_n, .fetch_want(valid), .mem_want,
                      .fetch_grant(issue_valid), .mem_grant, .stall_front(conflict));
      front_stage storage (.clk, .rst_n, .stall(!new_ready), .new_item,
                           .new_valid, .item(issue_item), .valid);
    endmodule
    

    Remember

    • Fixed priority gives the port to the older stage and stalls the front.

    • A stalled stage holds both its data and its valid bit.

    • A global stall is a high-fanout net; local valid/ready handshakes scale better.

    Keep going

    • Practice: Structural hazard detection unit

    • Code lab: Pipeline stall logic

    • Interview: run a mock interview on this topic

    Related topics

    • Arbitration

    • Data hazards and forwarding

    Previous

    81 / 350

    Next: Pipeline Flush on Branch Resolve

    Up next

    Up nextPipeline Flush on Branch Resolve

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Retiming-Friendly Coding Notes
    • Data Hazards and Forwarding
    • A Round-Robin Arbiter from a Priority Encoder