Skip to content

clock/intel, liblitedram: ALTPLL dynamic phase shift and SDRAM clock phase leveling at boot (SDR). - #2756

Merged
enjoy-digital merged 1 commit into
masterfrom
clock/intel-altpll-dpa
Nov 3, 2026
Merged

enjoy-digital merged 1 commit into
masterfrom
clock/intel-altpll-dpa

Conversation

@enjoy-digital

@enjoy-digital enjoy-digital commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

SDR SDRAM on Intel FPGAs (LiteDRAM GENSDRPHY/HalfRateGENSDRPHY) gets its SDRAM clock from an ALTPLL output, usually at a hand-picked static phase. That phase sets both the command setup/hold at the SDRAM and where read data lands at the FPGA's capture registers. Its working window depends on the frequency, the CAS latency, the board and the placement. Example on a DE10-Lite at 142.86 MHz CL3: two builds of different designs, with different VCOs, put the window at 45-326° and 38-251°. A table of phases per frequency doesn't hold up.

This PR lets the BIOS find the phase at boot.

Gateware (litex/soc/cores/clock/intel_common.py)

  • IntelClocking.expose_dpa(clkout, cd="sys"): the ALTPLL dynamic phase shift of one output (PHASESTEP/PHASEUPDOWN/PHASECOUNTERSELECT/PHASEDONE, SCANCLK from cd). Same name as the ECP5/Gowin APIs. Enabled on MAX 10, Cyclone IV and Cyclone 10 LP (altpll_dpa).
  • Quartus picks its own VCO from the multiply/divide ratios, so the step (1/8 VCO) isn't the one LiteX computed. To pin it:
    • with DPA exposed, the VCO search is limited to whole-picosecond steps;
    • the step is set with VCO_FREQUENCY_CONTROL = "MANUAL_PHASE" / VCO_PHASE_SHIFT_STEP;
    • dpa_steps() then matches the hardware: 142.86 MHz → VCO 1000 MHz, 125 ps, 56 steps. The fit report confirms the VCO and step.
  • IntelPLLPhaseStepper(pll): CSRs dir, step (one step per write: PHASESTEP held until PHASEDONE goes low, done when it is back high, with a timeout), busy, position, steps.

BIOS (liblitedram)

  • sdram_clk_phase_leveling() runs in sdram_init() when the SoC has a sdram_clk_phase module, and from sdram_cal.
  • For each step over one SDRAM clock, it runs the JEDEC init, then a silent write/read check under hardware control: linear words, then strided words over the whole SDRAM.
  • The phase then goes to the middle of the longest circular run of passing steps.

Usage (target):

pll.create_clkout(self.cd_sys2x_ps, sdram_freq, phase=180)
pll.expose_dpa(clkout=2)
...
self.sdram_clk_phase = IntelPLLPhaseStepper(self.crg.pll)

Tested

  • test/cores/test_clock.py:
    • DPA config: whole-ps step, dpa_steps() × step = period, the ALTPLL parameters, error cases;
    • the stepper's handshake in simulation against a modelled PHASEDONE;
    • 70 passed.
  • test/cores 483 passed, test/build 223 passed, test/software 57 passed. test/soc has the same 15 failures as master: CPU integration tests for toolchains not installed here, and test_serialboot_abort_recovers_and_loads.
  • DE10-Lite (10M50, IS42S16320, HalfRateGENSDRPHY at 142.86 MHz CL3 with phy/gensdrphy: Fix HalfRateGENSDRPHY read data pairing at CL3. litedram#460), Quartus 23.1. The bitstream deliberately starts at 0°, which fails at that frequency:
    SDRAM clock phase leveling (56 steps per clock, scan from step 0):
      |00000011111111111111111111111111111111110000000000000000|
      window: 34 steps from step 6, phase at step 23 (build-time phase + 148 deg).
    Memtest at 0x40000000 (2.0MiB)...
    Memtest OK
    
    • On reboot (PLL phase kept) and from sdram_cal, the scan starts at step 23, finds the same window and stays there. The 4 MiB memtest passes afterwards.
    • LiteDRAM's BIST run from the host on the same bitstream at every step gives the same window (steps 7-39), so the boot check agrees with BIST traffic.
  • Only MAX 10 was tested on hardware. Cyclone IV and Cyclone 10 LP use the same ALTPLL dynamic phase protocol (Cyclone IV handbook), but were not tested on a board.

Written by an AI agent (Claude Code), supervised by Florent Kermarrec.

🤖 Generated with Claude Code

…ock phase leveling (SDR).

IntelClocking.expose_dpa(clkout): ALTPLL PHASESTEP/PHASEUPDOWN/PHASECOUNTERSELECT/
PHASEDONE on one output (MAX 10, Cyclone IV, Cyclone 10 LP). Quartus chooses its own
VCO, so with DPA the VCO search is limited to whole-picosecond steps and the step is
pinned (VCO_FREQUENCY_CONTROL = MANUAL_PHASE): dpa_steps() is the hardware's step count.
IntelPLLPhaseStepper: CSR control (dir/step/busy/position/steps).

liblitedram: sdram_clk_phase_leveling(), run by sdram_init() and sdram_cal when the SoC
has a sdram_clk_phase module: JEDEC init and a write/read check at every step over one
SDRAM clock, the phase to the middle of the longest window.

Tested on a DE10-Lite (HalfRateGENSDRPHY, 142.86 MHz CL3): from a 0 degree build, window
of 34/56 steps, phase moved +148 degrees, memtest OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant