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).