DRAM Refresh vs On-Chip ECC in Embedded Linux Memory Budgets
An embedded board's memory budget is usually written as one number: gigabytes, price, maybe a power figure. Two maintenance mechanisms quietly rewrite that number after the fact. DRAM refresh takes bandwidth on a schedule the kernel cannot see, and error correction takes capacity, pins, or clock cycles depending on how it is built. This piece walks through where each cost lands, why boot firmware owns the decision, and what to measure before a design freezes.
Two Maintenance Burdens Compete
Refresh is continuous. Every DRAM cell stores a bit as charge on a capacitor, and that charge leaks away. The memory controller must read and rewrite rows on a fixed interval or the data disappears. That work happens whether the CPU is busy or idle, and it competes with real traffic for the same bus.
Error correction is conditional in time but unconditional in cost. Check bits occupy storage, and the logic that computes and verifies them sits somewhere on the read and write paths. On modules with sideband chips, ECC also costs board area, extra signal pins, and another part on the bill of materials.
A planner with a fixed budget usually has to pick which cost to accept first. Adding ECC to a density that already runs hot on refresh can push both overheads into the same design. Embedded Linux exposes both, but only indirectly, through timing behavior and through what the boot logs report.
Refresh Is Not Free Bandwidth
The refresh interval is a datasheet parameter, typically quoted in the low tens of milliseconds at ordinary temperatures and shortened at high temperatures. Each refresh command occupies the bus for a burst of row activations, and the controller has to schedule those around demand traffic. The scheduler can hide some of it. It cannot hide all of it.
Overhead scales with density in a way that surprises people. More rows per bank means more refresh commands per interval to cover the same silicon. A part with twice the rows does not simply refresh twice as long per command; the total row-activation work grows, and the fraction of bus time consumed grows with it.
Consider a concrete case: a 16 Gb DDR4 x16 part organized as 8 banks with 16k rows per bank. At a typical 64 ms refresh interval, the controller must issue 128k refresh commands (one per row) distributed over that window. Each command consumes a row-activation cycle, so the total bus time spent on refresh is roughly the time for 128k activations. If each activation takes on the order of 45 ns, the aggregate is about 5.8 ms out of every 64 ms, or around 9% of bus time. That is bandwidth unavailable to the application, and it is a best-case figure that assumes perfect scheduling.
From Linux there is no knob for this. The kernel issues loads and stores and sees latency; it does not see the refresh cycle that stalled a request. That invisibility is the real problem. A workload that misses a deadline under load may be losing time to a maintenance operation no driver can observe, which makes the usual profiling tools report symptoms without a cause.
Inline ECC Reshapes the Budget
Sideband ECC is the classic arrangement: a wider bus, an extra chip or two, and check bits stored in separate devices. It works, and it costs pins and board space that small embedded designs often cannot spare. Inline ECC moves the check bits into the same package, spending a slice of nominal capacity to store them.
That slice is not free. A module advertised at a given size may expose noticeably less usable memory once the check-bit region is carved out, and the exact fraction depends on the code and the geometry the vendor chose. Common inline ECC schemes reserve roughly one-eighth to one-quarter of the raw capacity for check bits, so a 4 GB package might yield only 3.5 GB or 3 GB of usable RAM. The vendor datasheet or the memory controller documentation should state the exact fraction for a given mode.
The trade-off shifts with process node. Shrinking cells makes them more susceptible to disturbance and retention loss, which argues for stronger protection, while the area cost of check bits and logic becomes relatively larger on a small die. A decision that made sense on an older node deserves a fresh look on a newer one.
Firmware Owns the Real Decision
By the time Linux boots, the refresh rate and the ECC mode have usually been programmed by boot firmware, whether that is a vendor bootloader, U-Boot, or a small stage-one blob. The kernel driver generally reads what the memory controller reports and configures itself to match. It rarely second-guesses the setting.
The device tree carries the geometry the driver needs: bank layout, the address ranges reserved for check bits, and any region the firmware has hidden. If those numbers disagree with what the controller is actually doing, the kernel may map memory that overlaps a protected region, or skip a region that holds usable RAM.
Mismatches tend to surface as silent corruption rather than a clean failure. A wrong reserved-memory node can leave the kernel writing into space the controller treats as check bits. Nothing crashes immediately. The symptom appears later as a corrupted filesystem or a process that dies for no visible reason. This site has argued before that recovery paths deserve an audit, and the same logic applies to memory maps nobody re-read after a firmware update.
Measuring the Actual Overhead
Most memory controllers expose performance counters, and some expose refresh-related events specifically. Where those exist, reading them under a fixed synthetic load gives a direct view of how much bus time maintenance consumes. Where they do not, a latency histogram from a tight pointer-chasing loop still shows the tail, and the tail is where refresh stalls appear.
Run the same workload in both configurations, with ECC enabled and disabled, on identical boards and identical clocks. Compare throughput, worst-case latency, and usable memory as reported at boot. The differences are the real budget numbers, not the ones in the marketing table.
Correctable error counts need a separate log. A rate that climbs over weeks on the same board points at a marginal cell, a thermal problem, or a refresh setting that is too relaxed for the operating temperature. Keeping those two signals apart matters, because refresh stalls and ECC corrections call for opposite fixes. A related piece on memory bandwidth as a cost driver makes a similar point about measuring the resource rather than the headline figure.
Thermal Effects and Refresh Rate
Temperature changes the refresh calculus more than most other variables. DRAM retention time degrades as the cell temperature rises, and datasheets often specify a doubled refresh rate above a threshold, typically around 85 °C for commercial parts. That threshold is not a suggestion: if the product will operate near it, the controller must be configured for the higher rate, or the design must guarantee the temperature stays below it.
The firmware setting for refresh rate is often a register value that encodes the interval in clock cycles. A bootloader that leaves it at the power-on default may be assuming a benign thermal environment. An integrator who adds a fanless enclosure or a higher ambient rating needs to revisit that register, because the default may no longer be safe.
Thermal effects also interact with ECC. Higher temperatures can increase the rate of correctable errors, and a marginal cell that was fine at room temperature may start flipping bits in a hot enclosure. That is why logging correctable errors with a timestamp matters: a cluster of errors that correlates with a temperature rise points at a thermal fix rather than a memory replacement.
Practical Steps for Integrators
Read the DRAM datasheet's refresh rows before choosing a density. Look for the refresh interval at the temperatures the product will actually see, and check whether the part requires double-rate refresh above a threshold. That single table often decides whether a density is viable.
Verify the ECC mode in the boot log rather than assuming it. Confirm the reserved-memory nodes in the device tree match the controller's reported geometry, and re-check both after every firmware revision, since a vendor update can change defaults without a changelog entry.
Benchmark both configurations on real boards under a load that resembles the product. Measure usable memory, throughput, and tail latency, then write the numbers into the design record so a later density change has a baseline to compare against.
Log correctable errors to persistent storage with a timestamp and a boot counter. A slow drift is easy to miss in a session log and obvious in a file that survives reboots. This site's piece on metered resources that hide in contracts covers the same habit of tracking a cost before it becomes a surprise.
Revisit the whole choice at each density or node change. Refresh overhead, check-bit fractions, and error rates all move with the silicon, and a configuration validated two generations ago is not evidence about the current one.