Every master we've put on the fabric — CPU,
The prerequisite taught you the diva's personality. This lesson builds her handler: the memory controller, the hardware block that accepts a chaotic stream of requests from the fabric and turns it into a legal, ruthlessly optimised command schedule. It is one of the most consequential blocks on the die — the difference between a naive controller and a good one is often 2× on delivered bandwidth, for free, forever.
Open the hood and the controller has three organs:
ACT, a per-bank counter loads PRE,
another The classic answer is FR-FCFS — first-ready, first-come-first-served:
The effect: if the open row has three hits scattered through the queue, the scheduler serves all three before paying the conflict that closes the row — even though a conflicting request arrived earlier. Requests are reordered for row-hit rate and spread across banks for parallelism. It is out-of-order execution, reinvented for memory.
Underneath sits a bet called the page policy. Open-page:
after an access, leave the row open, gambling that the next request wants the same row
(great for streaming and DMA traffic — hits galore). Closed-page:
precharge immediately after each access, gambling that the next request wants a
different row, so the
Step through a few cycles of two banks under FR-FCFS. Compare the width of a hit
(RD) with the width of the conflict at the end — that ratio is why the
scheduler exists:
Here are both schedulers on the same seeded request trace against one bank: plain in-order FCFS versus FR-FCFS. Same requests, same DRAM timings — only the order differs:
Look at the two "served order" lines: FR-FCFS has quietly sorted the stream into runs of
equal rows. Nudge the 0.45 down (more locality) or up (more chaos) and watch
the gap move.
A real controller serves several requestors at once, and they want opposite things. The CPU wants latency: its next hundred instructions are stalled on one load. The GPU and the DMA engines want bandwidth: nobody is blocked on any single beat, but the aggregate rate must be huge. A scheduler that maximises pure row-hit rate will happily serve a thousand-beat GPU streak while the CPU's lone, row-conflicting load ages in the queue. So practical schedulers layer policy on top of FR-FCFS: priority classes per requestor, age counters that escalate starving requests, sometimes per-class bandwidth quotas. Remember this shape — a scheduler is a policy, and every policy chooses losers.
Finally, the road back. Because the controller reorders, read data emerges in
schedule order, not request order. Every request carried an
For decades the memory controller didn't live on the processor at all — it sat in the "northbridge", a separate chipset package a few centimetres of motherboard away. Every cache miss took a round trip: off the CPU, across a board-level bus, through the northbridge's controller, out to DRAM, and all the way back. In 2003 AMD's Opteron/Athlon 64 pulled the controller onto the CPU die, and memory latency dropped by tens of nanoseconds overnight — one of the clearest single-change performance wins of that era, and a genuine competitive crisis for Intel, who followed in 2008 with Nehalem. Since then the controller has been a first-class citizen of the SoC, co-designed with the cache hierarchy and fabric — which is exactly why it gets a lesson in a chip-design course: it's your block now, not the motherboard's.
Here is a seductive wrong idea: "delivered bandwidth is the metric, so schedule purely for row hits and long bursts." Build that, and your benchmark bandwidth number is spectacular — while the product stutters. The GPU's endless streaming keeps whole banks' rows open and hit-friendly, so the pure-bandwidth scheduler keeps choosing it; the CPU's single pointer- chasing load — a row conflict, of course — waits, and with it waits every instruction behind it. The display controller, which must refresh the panel on a hard deadline, misses its window and the screen glitches. Nothing is broken in RTL; the policy is broken. A scheduler is a resource allocator, and every allocator has losers — the engineering is choosing them on purpose: priority for latency-critical clients, real-time guarantees for the display, age counters so no request starves, and only then row-hit greed. When a datasheet brags about peak bandwidth, ask the embarrassing question: at what worst-case latency, for whom?