DDR Memory Init - MarekBykowski/readme GitHub Wiki
DDR memory β how it works, init & training
How DRAM is organised, why bringing it up needs a controller + PHY, what read / write / gate leveling actually does, and how retention skips all of it on a warm reset. Grounded in the Axxia (Lionfish / gen2) code under board/axxia/common.
1. How DDR works (the basics first)
- A grid of leaky capacitors. Each cell is one capacitor + transistor holding a bit as a charge. Charge leaks, so every row must be refreshed (~every 64 ms) or the data is lost. Cells are organised into banks β rows β columns.
- Reach a bit in 3 commands: ACTIVATE opens a row into the row buffer (sense amps) β READ/WRITE picks a column (a burst of bits) β PRECHARGE closes the row. Plus REFRESH in the background.
- Two buses: CK + Command/Address says what & where (which bankΒ·rowΒ·col, and ACTIVATE/READ/WRITE); DQ + DQS carries the actual bits with their own strobe. DDR = Double Data Rate: one transfer on each clock edge (β and β) β 2Γ per clock.
That last point is why init and training exist: at GHz the valid window for each bit is tiny, so the controller must be told the DRAM's timing and the PHY must be tuned to the board.
2. Memory init = controller + PHY
Two hardware blocks, two jobs:
| Controller (Denali) | PHY (Synopsys) | |
|---|---|---|
| Role | the protocol / brain | the electrical / muscles |
| Does | issues DDR commands, enforces JEDEC timing (CAS latency, tRCD, refresh), banks/ECC | drives the real pins: clock, strobe, data; impedance & delay-line calibration; tunes timing to the board |
| Domain | cycles & commands (digital) | picoseconds & volts (analog) |
The orchestrator is ncp_sysmem_init_synopphy() (board/axxia/common/ncp_sysmem_init_synopphy.c:3183), run per memory interface:
smId β smNode (0β0x22, 1β0x0f, 2β0x23, 3β0x24)
β ncp_sm_denali_2041_init_56xx() init the CONTROLLER from the DRAM params
β‘ ncp_sm_ddr4_phy_init() PHY: PLL / ZCAL / DCAL, then DRAM reset + JEDEC init
β’ ncp_sm_ddr4_phy_training() the leveling pipeline (see Β§3)
β£ ncp_sm_ddr4_post_phy_training_mc_setup() hand the tuned link to the controller
The shared timing struct ctm is computed once (ncp_sm_common_setup_56xx) and passed to both the controller and the PHY, so their timing agrees.
3. Training / leveling β sample in the middle of the data "eye"
At ~GHz the valid window (the eye) is only a few hundred picoseconds, and the board routes CK / DQS / DQ at slightly different lengths (deliberately β "fly-by"). So the SoC can't assume when data lands; it must measure and set delays so every read/write is sampled dead-centre. Each step finds one delay knob:
- Write leveling β align DQS to CK at each DRAM chip (compensate the fly-by skew on writes).
- Gate leveling (DQS gate) β open the read "listen" window only while the read strobe is actually toggling (too early = noise, too late = miss).
- Read / Write eye centering (deskew) β nudge each data bit's sampling point to the centre of its eye.
- VREF β pick the voltage threshold that best separates 0 from 1.
Mechanically each step is the same: write the PIR (PHY Initialization Register) selecting one step, then poll PGSR0 for that step's done bit β in ncp_sm_ddr4_phy_training() (β¦ncp_sysmem_init_synopphy.c:1433). Order: WL β QSGATE β WLADJ β SRD β RDDSKW β WRDSKW β RDEYE β WREYE β VREF.
4. Retention reset β skip it on a warm boot
Training takes time and its result is board-specific, so on a warm / retention reset the DRAM is held in self-refresh (it keeps its own contents and calibration) and the destructive reset + full training is skipped. A persistent scratch bit flags it:
// board/axxia/common/axxia_initialize.c:86
ncr_read32(NCP_REGION_ID(0x156,0x00), 0x00dc, &value);
ddrRecovery = (value & 0x1); // 1 = DDR retention recovery
// axxia_initialize.c:163
if (0 == ddrRecovery) { // cold boot only:
... sysmem_init(); // full DRAM reset + PHY training
} // recovery: minimal re-init, RAM survives
Running the full sysmem_init() (with DRAMRST/DRAMINIT, β¦ncp_sysmem_init_synopphy.c:3000) on a recovery boot would wipe RAM β which is exactly what the ddrRecovery gate prevents.
Code map (gen2)
| What | Where |
|---|---|
| orchestrator (per interface) | board/axxia/common/ncp_sysmem_init_synopphy.c:3183 |
| controller init (Denali 2041) | board/axxia/common/ncp_sm_denali_2041_init_56xx.c |
| PHY init (PLL/ZCAL/DCAL, DRAM reset+init) | β¦/ncp_sysmem_init_synopphy.c:2288 |
| training / leveling | β¦/ncp_sysmem_init_synopphy.c:1433 |
| retention gate | board/axxia/common/axxia_initialize.c:163 |
| entry from init path | sysmem_init() β board/axxia/common/sysmem_asic.c:390 |
See also: SYSCACHE_ONLY_MODE β where, in SYSCACHE builds, this whole init moves from SPL into U-Boot (init_mem_axxia).