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

  • Architecture Metrics Reference
  • Workload Taxonomy for Architects
  • Architecture Glossary
  • Computer Architecture Cheatsheet
  • Pipeline Fundamentals14
  • Memory Hierarchy15
  • NoC and Interconnect Architecture6
  • Coherency and Memory Ordering9
    • Cache Coherence for the Architect
    • Memory Ordering Models
      • Models: SC, TSO, ARMv8, RVWMO
      • Where Reordering Comes From
      • Fences, Acquire/Release and Dependencies
      • Litmus Tests and Lock-Free Sync
  • Performance Analysis9
  • Accelerator Architectures6
  • SoC Architecture Tradeoffs9
  • Architecture Interview Prep14
Loading lesson

‹Memory Ordering Models
  1. Topics
  2. Computer Architecture
  3. Coherency and Memory Ordering
  4. Memory Ordering Models
  5. Where Reordering Comes From

Computer Architecture · All levels

Where Reordering Comes From

Store buffers, forwarding and speculation: the hardware behind weak outcomes.

On this page

    The FIFO store buffer

    A retired store waits in a FIFO buffer until it wins cache ownership. Later loads from any core can run before that store reaches memory.

    The buffer is small (a few entries), so the reorder window is short. Short is enough: a racing load only needs to slip into that window.

    Forwarding hides the buffer locally

    A core's own load checks its buffer first and takes the buffered value on a match. The core never sees its own store late.

    Other cores have no such path, so they still read the old memory value. That split is exactly the SB weak outcome.

    Other reorder points

    Invalidation queues delay the processing of incoming invalidates. A load can briefly read a line that a remote store already won.

    Speculative and out-of-order loads run ahead of program order. They expose LdLd and LdSt reorderings, which is the reader side of MP.

    Source

    Pairs it breaks

    Pattern it enables

    Fix

    FIFO store buffer

    StLd

    SB

    Drain on a full fence (MFENCE, DMB, FENCE).

    Invalidation queue

    StLd visibility

    SB-like stale reads

    Acknowledge before the fence retires.

    Speculative loads

    LdLd, LdSt

    MP reader side

    Full fence or acquire load.

    Non-atomic store paths

    global order

    IRIW (historic only)

    Multi-copy-atomic store release.

    Testbench

    The model below is a two-core FIFO store buffer of depth 4 with same-core forwarding. It runs the SB schedule buffered, then reruns it with stores retiring straight to memory as the SC control.

    design.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
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    module tb_sb_storebuf;
      localparam int DEPTH = 4;
      int mem_x, mem_y;
      int a_addr[4], a_data[4], a_cnt;
      int b_addr[4], b_data[4], b_cnt;
      int errors, r1, r2;
    
      task automatic a_push(input int a, input int d);
        a_addr[a_cnt] = a; a_data[a_cnt] = d; a_cnt = a_cnt + 1;
      endtask
      task automatic b_push(input int a, input int d);
        b_addr[b_cnt] = a; b_data[b_cnt] = d; b_cnt = b_cnt + 1;
      endtask
      function automatic int a_load(input int a);
        int i;
        for (i = a_cnt - 1; i >= 0; i = i - 1)
          if (a_addr[i] == a) return a_data[i];
        if (a == 0) return mem_x;
        return mem_y;
      endfunction
      function automatic int b_load(input int a);
        int i;
        for (i = b_cnt - 1; i >= 0; i = i - 1)
          if (b_addr[i] == a) return b_data[i];
        if (a == 0) return mem_x;
        return mem_y;
      endfunction
      task automatic drain_all;
        int i;
        for (i = 0; i < a_cnt; i = i + 1) begin
          if (a_addr[i] == 0) mem_x = a_data[i];
          else mem_y = a_data[i];
        end
        for (i = 0; i < b_cnt; i = i + 1) begin
          if (b_addr[i] == 0) mem_x = b_data[i];
          else mem_y = b_data[i];
        end
        a_cnt = 0; b_cnt = 0;
      endtask
    
      initial begin
        errors = 0;
        mem_x = 0; mem_y = 0; a_cnt = 0; b_cnt = 0;
        a_push(0, 7);
        if (a_load(0) !== 7) errors = errors + 1;
        if (mem_x !== 0) errors = errors + 1;
        if (b_load(0) !== 0) errors = errors + 1;
        drain_all();
        if (mem_x !== 7) errors = errors + 1;
        mem_x = 0; mem_y = 0;
        a_push(0, 1);
        b_push(1, 1);
        r1 = a_load(1);
        r2 = b_load(0);
        drain_all();
        if (r1 !== 0 || r2 !== 0) errors = errors + 1;
        if (mem_x !== 1 || mem_y !== 1) errors = errors + 1;
        mem_x = 0; mem_y = 0;
        mem_x = 1;
        r1 = mem_y;
        mem_y = 1;
        r2 = mem_x;
        if (r1 !== 0 || r2 !== 1) errors = errors + 1;
        if (r1 == 0 && r2 == 0) errors = errors + 1;
        if (errors == 0) $display("PASS: forwarding holds, buffered SB shows r1=0 r2=0, direct SB shows r1=0 r2=1");
        else $display("FAIL: %0d checks wrong", errors);
        $finish;
      end
    endmodule

    Common pitfall

    Depth 0 in the testbench is the SC control, not a design proposal. Removing the buffer would trade the whole performance win for order; fences keep the win and restore order where it matters.

    Interview

    Likely probe: does a bigger buffer break more code. No: it only widens the window for the same SB shape. Fence placement, not depth, decides correctness.

    Check yourself Core A stores x = 1 then loads y = 0 while B stores y = 1 then loads x = 0. Where did each load read from?

    Each load missed the other core's still-buffered store and read the old memory value. That is the SB weak outcome.

    Check yourself Draining both buffers before the loads gives r1 = 0, r2 = 1. Why is r2 = 1 now?

    B's load runs after A's store reached memory. Draining before the loads restores SC-visible order for this schedule.

    Related topics

    • Models: SC, TSO, ARMv8, RVWMO

    Previous

    46 / 86

    Next: Fences, Acquire/Release and Dependencies

    Up next

    Up nextFences, Acquire/Release and Dependencies

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Litmus Tests and Lock-Free Sync