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
    • Caches
    • Memory Bandwidth and Latency
      • Peak, Protocol and Workload Bandwidth
      • Little's Law, MLP and the Waterfall
      • Row Buffers and Controller Scheduling
      • Prefetch Accuracy, Coverage and Pollution
      • Counters, QoS and Debug
    • Virtual Memory and the TLB
  • NoC and Interconnect Architecture6
  • Coherency and Memory Ordering9
  • Performance Analysis9
  • Accelerator Architectures6
  • SoC Architecture Tradeoffs9
  • Architecture Interview Prep14
Loading lesson

‹Memory Bandwidth and Latency
  1. Topics
  2. Computer Architecture
  3. Memory Hierarchy
  4. Memory Bandwidth and Latency
  5. Row Buffers and Controller Scheduling

Computer Architecture · All levels

Row Buffers and Controller Scheduling

Row buffers, bank conflicts, FR-FCFS scheduling, write drains and refresh.

On this page

    Rows, banks and the buffer

    Each DRAM bank keeps one open row in its row buffer. A request to the open row is a hit, to a closed bank a miss, and to a different row a conflict that pays precharge plus activate.

    Banks work in parallel, so consecutive requests to different banks overlap. Consecutive requests to one bank serialize on its row cycle.

    Case

    Bank state

    Extra cost

    Row hit

    Row already open

    None: serve from the buffer

    Row miss

    Bank closed

    One activate

    Row conflict

    Another row open

    Precharge plus activate

    Scheduling around conflicts

    FR-FCFS serves ready commands first and oldest first among them. It harvests row hits by pulling forward requests to open rows.

    Page policy picks the default: open page keeps rows open for locality, closed page closes them to bound conflict cost. Adaptive policies switch on measured hit rates.

    Policy

    Wins when

    Risk

    Open page

    Bursty reuse of few rows

    Conflicts stall streaming mixes

    Closed page

    Random access across rows

    Throws away easy row hits

    FR-FCFS reorder

    Queue holds parallel ready work

    Hot rows starve cold streams

    Diagram
    Turnaround loss on the random trace below (stated parameters):
    base cycles: 16 accesses x 10 = 160
    miss:        1 x 20 = 20
    conflicts:   15 x 40 = 600
    turnarounds: 15 x 15 = 225
    total:       160 + 20 + 600 + 225 = 1005 cycles
    turnaround share: 225 / 1005 = 0.2239 ~ 22% of cycles.

    Write drains batch writes to cut turnarounds, but long drains block reads. Refresh steals whole-bank windows on a fixed schedule and cannot be reordered away.

    Testbench

    The model below counts hits, conflicts and turnarounds for three 16-access traces; it models the accounting only, and timing parameters stay in the DRAM course.

    row_model.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
    module row_model #(
      parameter int TRACE = 0,
      parameter int N = 16,
      parameter int BASE_CYC = 10,
      parameter int MISS_ADD = 20,
      parameter int CONFLICT_ADD = 40,
      parameter int RW_TURN = 15
    ) (
      output logic [31:0] hits,
      output logic [31:0] misses,
      output logic [31:0] conflicts,
      output logic [31:0] turns,
      output logic [31:0] total_cyc
    );
      function automatic int get_row(int i);
        if (TRACE == 0) return 5;
        else if (TRACE == 1) return (i % 2);
        else return i;
      endfunction
      function automatic bit get_write(int i);
        if (TRACE == 2) return ((i % 2) != 0);
        else return 1'b0;
      endfunction
      always_comb begin
        int open_row;
        int r;
        bit w;
        bit prev_w;
        hits = 0; misses = 0; conflicts = 0; turns = 0;
        open_row = -1; prev_w = 1'b0;
        for (int i = 0; i < N; i++) begin
          r = get_row(i);
          w = get_write(i);
          if (open_row == -1) misses++;
          else if (r == open_row) hits++;
          else conflicts++;
          open_row = r;
          if ((i > 0) && (w != prev_w)) turns++;
          prev_w = w;
        end
        total_cyc = N*BASE_CYC + misses*MISS_ADD + conflicts*CONFLICT_ADD + turns*RW_TURN;
      end
    endmodule
    
    module tb_row_model;
      logic [31:0] h0, m0, c0, t0, cy0;
      logic [31:0] h1, m1, c1, t1, cy1;
      logic [31:0] h2, m2, c2, t2, cy2;
      int errors = 0;
      row_model #(.TRACE(0)) s0 (.hits(h0), .misses(m0), .conflicts(c0), .turns(t0), .total_cyc(cy0));
      row_model #(.TRACE(1)) s1 (.hits(h1), .misses(m1), .conflicts(c1), .turns(t1), .total_cyc(cy1));
      row_model #(.TRACE(2)) s2 (.hits(h2), .misses(m2), .conflicts(c2), .turns(t2), .total_cyc(cy2));
      initial begin
        #1;
        if (m0 !== 32'd1 || h0 !== 32'd15 || c0 !== 32'd0 || t0 !== 32'd0 || cy0 !== 32'd180) errors++;
        if (m1 !== 32'd1 || h1 !== 32'd0 || c1 !== 32'd15 || t1 !== 32'd0 || cy1 !== 32'd780) errors++;
        if (m2 !== 32'd1 || h2 !== 32'd0 || c2 !== 32'd15 || t2 !== 32'd15 || cy2 !== 32'd1005) errors++;
        if (!(cy0 < cy1 && cy1 < cy2)) errors++;
        if (errors == 0) $display("PASS: streaming 180c beats strided 780c beats random 1005c with matching counts");
        else $display("FAIL: %0d row accounting checks wrong", errors);
        $finish;
      end
    endmodule

    Streaming takes 180 cycles, strided 780 and random 1005 for the same 1024 bytes. At 1 GHz that is about 5.69, 1.31 and 1.02 GB/s in the same order.

    Common mistakes

    Common pitfall

    Tuning the controller while the NoC starves reads moves the bottleneck without adding bytes. Prove the controller is first-saturated before touching its policy.

    Interview

    Likely probe: when is a 61 percent row-hit rate with a 43-cycle average queue a controller problem rather than a DRAM problem? Answer: hits are decent yet requests wait, so scheduling and queueing, not the array, bind delivery.

    Check yourself A stream touches one row repeatedly, then strides across two rows. What changes in the counts?

    Hits collapse into conflicts: 1 miss with 15 hits becomes 1 miss with 15 conflicts, and cycles rise from 180 to 780.

    Check yourself Why does FR-FCFS need a starvation guard?

    It favors ready row hits, so a steady hot stream can delay cold requests without bound. Cap reordering distance or age priorities.

    Related topics

    • Memory Controller Scheduling

    • Prefetch accuracy, coverage and pollution

    Previous

    27 / 86

    Next: Prefetch Accuracy, Coverage and Pollution

    Up next

    Up nextPrefetch Accuracy, Coverage and Pollution

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Peak, Protocol and Workload Bandwidth
    • Counters, QoS and Debug