@@ -86,6 +86,40 @@ def test_mta4atf51264hz_tfaw_uses_x16_ck_minimum(self):
8686 module = cls (clk_freq = 100e6 , rate = "1:4" )
8787 self .assertEqual (cls .speedgrade_timings ["2133" ].tFAW [0 ], 28 )
8888
89+ def test_ddr4_x16_tfaw_uses_ck_minimum (self ):
90+ # JEDEC DDR4 (JESD79-4) fixes the tFAW nCK floor at 28 for x16
91+ # organisation (vs 20 nCK for x4/x8). PRs #392 and #393 corrected
92+ # the two DDR4 x16 outliers in this repo (MT40A512M16 bare-chip
93+ # and MTA4ATF51264HZ SODIMM); this regression test locks the
94+ # invariant in across every DDR4 device whose per-device
95+ # organisation is x16 (`ngroups == 2`, `ngroupbanks == 4`), so
96+ # any future addition or accidental (20, ...) regression fails
97+ # loudly at test time.
98+ names = [
99+ "EDY4016A" ,
100+ "MT40A256M16" ,
101+ "MT40A512M16" ,
102+ "MTA4ATF51264HZ" ,
103+ ]
104+ for name in names :
105+ cls = getattr (litedram .modules , name )
106+ with self .subTest (module = name ):
107+ # Every listed class has ngroups=2 (x16 per-device shape).
108+ self .assertEqual (cls .ngroups , 2 , f"{ name } : expected x16 shape (ngroups=2)" )
109+ # Every listed class exposes at least one speedgrade; check
110+ # each speedgrade's tFAW nCK floor without hard-coding which
111+ # speedgrade names exist (defensive against future edits).
112+ for sg_name , sg in cls .speedgrade_timings .items ():
113+ if sg_name == "default" :
114+ continue
115+ tfaw = sg .tFAW
116+ self .assertIsNotNone (tfaw , f"{ name } [{ sg_name } ]: tFAW missing" )
117+ self .assertIsInstance (tfaw , tuple , f"{ name } [{ sg_name } ]: tFAW not a tuple" )
118+ self .assertGreaterEqual (
119+ tfaw [0 ], 28 ,
120+ f"{ name } [{ sg_name } ]: JEDEC DDR4 x16 requires tFAW nCK floor >= 28" ,
121+ )
122+
89123 def test_ddr3_x16_trrd_uses_ck_minimum (self ):
90124 # JEDEC DDR3 fixes the tRRD nCK floor at 6 for x16 organisation
91125 # (vs 4 for x4/x8). Every DDR3 part below is an x16 device, so the
0 commit comments