Skip to content

Commit 1b7cc79

Browse files
Merge pull request #400 from enjoy-digital/fix-a7-ddr3-read-alignment
phy: Align Artix-7 DDR3 reads without altering CL
2 parents 2bea18e + 624444c commit 1b7cc79

2 files changed

Lines changed: 113 additions & 6 deletions

File tree

litedram/phy/s7ddrphy.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ def __init__(self, pads, with_odelay,
7070
rdphase = get_sys_phase(nphases, cl_sys_latency, cl + cmd_latency)
7171
wrphase = get_sys_phase(nphases, cwl_sys_latency, cwl + cmd_latency)
7272

73+
# Artix-7 requires read data one memory-clock phase into the ISERDESE2 word for reliable
74+
# read leveling. This was previously achieved by programming CL + 1 in the DDR3 MR0. Keep
75+
# the configured CL unchanged and express the compensation in the PHY schedule instead.
76+
if (memtype == "DDR3") and (not with_odelay):
77+
phase_cycles, rdphase = divmod(rdphase + 1, nphases)
78+
cl_sys_latency -= phase_cycles
79+
7380
# Registers --------------------------------------------------------------------------------
7481
self._rst = CSRStorage()
7582

@@ -121,12 +128,6 @@ def cdc(i):
121128
wdly_dq_bitslip_rst = cdc(self._wdly_dq_bitslip_rst.wr_stb)
122129
wdly_dq_bitslip = cdc(self._wdly_dq_bitslip.wr_stb)
123130

124-
# PHY settings -----------------------------------------------------------------------------
125-
if (memtype == "DDR3") and (not with_odelay):
126-
# DDR3 Write leveling is not possible on Artix7 due to the lack of ODELAYE2, adding +1
127-
# to cl in MR register increases sys_clk_freq range.
128-
cl += 1
129-
130131
# Some calibrations like write_leveling or latency calibration are not supported
131132
# for DDR2 chip.
132133
write_calibrations = (with_odelay and (memtype not in ["DDR2"]))

test/test_s7ddrphy.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#
2+
# This file is part of LiteDRAM.
3+
#
4+
# Copyright (c) 2026 Florent Kermarrec <florent@enjoy-digital.fr>
5+
# SPDX-License-Identifier: BSD-2-Clause
6+
7+
import unittest
8+
from types import SimpleNamespace
9+
10+
from migen import *
11+
12+
from litedram.common import get_default_cl
13+
from litedram.init import get_ddr3_phy_init_sequence
14+
from litedram.phy.s7ddrphy import A7DDRPHY, K7DDRPHY
15+
16+
17+
class TestS7DDRPHYSettings(unittest.TestCase):
18+
sys_clk_freqs = [50e6, 100e6, 125e6, 150e6, 175e6, 225e6]
19+
ddr3_cls = range(5, 15)
20+
read_pipeline_cycles = 6
21+
22+
@staticmethod
23+
def get_pads():
24+
return Record([
25+
("a", 15),
26+
("ba", 3),
27+
("ras_n", 1),
28+
("cas_n", 1),
29+
("we_n", 1),
30+
("clk_p", 1),
31+
("clk_n", 1),
32+
("dq", 8),
33+
("dm", 1),
34+
("dqs_p", 1),
35+
("dqs_n", 1),
36+
])
37+
38+
@staticmethod
39+
def get_rdphase(phy):
40+
return phy.settings.rdphase.reset.value
41+
42+
@staticmethod
43+
def get_mr0_cl(phy):
44+
init_sequence, _ = get_ddr3_phy_init_sequence(
45+
phy_settings = phy.settings,
46+
timing_settings = SimpleNamespace(tWTR=2),
47+
)
48+
mr0 = next(address for comment, address, _, _, _ in init_sequence
49+
if comment.startswith("Load Mode Register 0"))
50+
mr0_to_cl = {
51+
0b0010 : 5,
52+
0b0100 : 6,
53+
0b0110 : 7,
54+
0b1000 : 8,
55+
0b1010 : 9,
56+
0b1100 : 10,
57+
0b1110 : 11,
58+
0b0001 : 12,
59+
0b0011 : 13,
60+
0b0101 : 14,
61+
}
62+
mr0_cl = ((mr0 >> 2) & 0b1) | (((mr0 >> 4) & 0b111) << 1)
63+
return mr0_to_cl[mr0_cl]
64+
65+
def test_a7_default_cl_is_preserved(self):
66+
for sys_clk_freq in self.sys_clk_freqs:
67+
with self.subTest(sys_clk_freq=sys_clk_freq):
68+
phy = A7DDRPHY(self.get_pads(), sys_clk_freq=sys_clk_freq)
69+
tck = 1/(4*sys_clk_freq)
70+
expected_cl = get_default_cl("DDR3", tck)
71+
72+
self.assertEqual(phy.settings.cl, expected_cl)
73+
self.assertEqual(self.get_mr0_cl(phy), expected_cl)
74+
75+
def test_a7_read_alignment(self):
76+
for cl in self.ddr3_cls:
77+
for cmd_latency in range(3):
78+
with self.subTest(cl=cl, cmd_latency=cmd_latency):
79+
phy = A7DDRPHY(self.get_pads(),
80+
sys_clk_freq = 100e6,
81+
cl = cl,
82+
cmd_latency = cmd_latency,
83+
)
84+
rdphase = self.get_rdphase(phy)
85+
read_sys_latency = phy.settings.read_latency - self.read_pipeline_cycles
86+
87+
self.assertEqual(phy.settings.cl, cl)
88+
self.assertEqual(self.get_mr0_cl(phy), cl)
89+
self.assertEqual(
90+
rdphase + cl + cmd_latency,
91+
read_sys_latency*phy.settings.nphases + 1,
92+
)
93+
94+
def test_k7_read_alignment_is_unchanged(self):
95+
for cl in self.ddr3_cls:
96+
with self.subTest(cl=cl):
97+
phy = K7DDRPHY(self.get_pads(), sys_clk_freq=100e6, cl=cl)
98+
rdphase = self.get_rdphase(phy)
99+
read_sys_latency = phy.settings.read_latency - self.read_pipeline_cycles
100+
101+
self.assertEqual(phy.settings.cl, cl)
102+
self.assertEqual(self.get_mr0_cl(phy), cl)
103+
self.assertEqual(
104+
rdphase + cl,
105+
read_sys_latency*phy.settings.nphases,
106+
)

0 commit comments

Comments
 (0)