1 # This file is Copyright (c) 2016-2019 Florent Kermarrec <florent@enjoy-digital.fr>
2 # This file is Copyright (c) 2018 John Sully <john@csquare.ca>
3 # This file is Copyright (c) 2018 bunnie <bunnie@kosagi.com>
4 # This file is Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
8 from functools
import reduce
9 from operator
import add
10 from collections
import OrderedDict
13 from nmigen
.hdl
.rec
import *
14 from nmigen
.utils
import log2_int
16 import gram
.stream
as stream
18 # Helpers ------------------------------------------------------------------------------------------
29 def get_cl_cw(memtype
, tck
):
30 f_to_cl_cwl
= OrderedDict()
32 f_to_cl_cwl
[400e6
] = (3, 2)
33 f_to_cl_cwl
[533e6
] = (4, 3)
34 f_to_cl_cwl
[677e6
] = (5, 4)
35 f_to_cl_cwl
[800e6
] = (6, 5)
36 f_to_cl_cwl
[1066e6
] = (7, 5)
37 elif memtype
== "DDR3":
38 f_to_cl_cwl
[800e6
] = ( 6, 5)
39 f_to_cl_cwl
[1066e6
] = ( 7, 6)
40 f_to_cl_cwl
[1333e6
] = (10, 7)
41 f_to_cl_cwl
[1600e6
] = (11, 8)
42 elif memtype
== "DDR4":
43 f_to_cl_cwl
[1600e6
] = (11, 9)
46 for f
, (cl
, cwl
) in f_to_cl_cwl
.items():
51 def get_sys_latency(nphases
, cas_latency
):
52 return math
.ceil(cas_latency
/nphases
)
54 def get_sys_phases(nphases
, sys_latency
, cas_latency
):
55 dat_phase
= sys_latency
*nphases
- cas_latency
56 cmd_phase
= (dat_phase
- 1)%nphases
57 return cmd_phase
, dat_phase
59 # PHY Pads Transformers ----------------------------------------------------------------------------
64 Reduce DRAM pads to only use specific modules.
66 For testing purposes, we often need to use only some of the DRAM modules. PHYPadsReducer allows
67 selecting specific modules and avoid re-definining dram pins in the Platform for this.
69 def __init__(self
, pads
, modules
):
71 self
.modules
= modules
73 def __getattr__(self
, name
):
75 return Array([getattr(self
.pads
, name
)[8*i
+ j
]
78 if name
in ["dm", "dqs", "dqs_p", "dqs_n"]:
79 return Array([getattr(self
.pads
, name
)[i
] for i
in self
.modules
])
81 return getattr(self
.pads
, name
)
83 class PHYPadsCombiner
:
86 Combine DRAM pads from fully dissociated chips in a unique DRAM pads structure.
88 Most generally, DRAM chips are sharing command/address lines between chips (using a fly-by
89 topology since DDR3). On some boards, the DRAM chips are using separate command/address lines
90 and this combiner can be used to re-create a single pads structure (that will be compatible with
91 LiteDRAM's PHYs) to create a single DRAM controller from multiple fully dissociated DRAMs chips.
93 def __init__(self
, pads
):
94 if not isinstance(pads
, list):
100 def sel_group(self
, n
):
103 def __getattr__(self
, name
):
104 if name
in ["dm", "dq", "dqs", "dqs_p", "dqs_n"]:
105 return Array([getattr(self
.groups
[j
], name
)[i
]
106 for i
in range(len(getattr(self
.groups
[0], name
)))
107 for j
in range(len(self
.groups
))])
109 return getattr(self
.groups
[self
.sel
], name
)
111 # BitSlip ------------------------------------------------------------------------------------------
113 class BitSlip(Elaboratable
):
114 def __init__(self
, dw
, rst
=None, slp
=None, cycles
=1):
117 self
.rst
= Signal() if rst
is None else rst
118 self
.slp
= Signal() if slp
is None else slp
119 self
._cycles
= cycles
121 def elaborate(self
, platform
):
124 value
= Signal(range(self
._cycles
*dw
))
126 m
.d
.sync
+= value
.eq(value
+1)
127 with m
.Elif(self
.rst
):
128 m
.d
.sync
+= value
.eq(0)
130 r
= Signal((self
._cycles
+1)*dw
, reset_less
=True)
131 m
.d
.sync
+= r
.eq(Cat(r
[dw
:], self
.i
))
133 for i
in range(self
._cycles
*dw
):
134 cases
[i
] = self
.o
.eq(r
[i
:dw
+i
])
135 m
.d
.comb
+= Case(value
, cases
)
139 # DQS Pattern --------------------------------------------------------------------------------------
141 class DQSPattern(Elaboratable
):
142 def __init__(self
, preamble
=None, postamble
=None, wlevel_en
=0, wlevel_strobe
=0, register
=False):
143 self
.preamble
= Signal() if preamble
is None else preamble
144 self
.postamble
= Signal() if postamble
is None else postamble
146 self
._wlevel
_en
= wlevel_en
147 self
._wlevel
_strobe
= wlevel_strobe
148 self
._register
= register
150 def elaborate(self
, platform
):
153 with m
.If(self
.preamble
):
154 m
.d
.comb
+= self
.o
.eq(0b00010101)
155 with m
.Elif(self
.postamble
):
156 m
.d
.comb
+= self
.o
.eq(0b01010100)
157 with m
.Elif(self
._wlevel
_en
):
158 with m
.If(self
._wlevel
_strobe
):
159 m
.d
.comb
+= self
.o
.eq(0b00000001)
161 m
.d
.comb
+= self
.o
.eq(0b00000000)
163 m
.d
.comb
+= self
.o
.eq(0b01010101)
166 o
= Signal
.like(self
.o
)
167 m
.d
.sync
+= o
.eq(self
.o
)
172 # Settings -----------------------------------------------------------------------------------------
175 def set_attributes(self
, attributes
):
176 for k
, v
in attributes
.items():
180 class PhySettings(Settings
):
181 def __init__(self
, phytype
, memtype
, databits
, dfi_databits
,
184 rdcmdphase
, wrcmdphase
,
185 cl
, read_latency
, write_latency
, nranks
=1, cwl
=None):
186 self
.set_attributes(locals())
187 self
.cwl
= cl
if cwl
is None else cwl
188 self
.is_rdimm
= False
190 # Optional DDR3/DDR4 electrical settings:
191 # rtt_nom: Non-Writes on-die termination impedance
192 # rtt_wr: Writes on-die termination impedance
193 # ron: Output driver impedance
194 def add_electrical_settings(self
, rtt_nom
, rtt_wr
, ron
):
195 assert self
.memtype
in ["DDR3", "DDR4"]
196 self
.set_attributes(locals())
198 # Optional RDIMM configuration
199 def set_rdimm(self
, tck
, rcd_pll_bypass
, rcd_ca_cs_drive
, rcd_odt_cke_drive
, rcd_clk_drive
):
200 assert self
.memtype
== "DDR4"
202 self
.set_attributes(locals())
204 class GeomSettings(Settings
):
205 def __init__(self
, bankbits
, rowbits
, colbits
):
206 self
.set_attributes(locals())
207 self
.addressbits
= max(rowbits
, colbits
)
210 class TimingSettings(Settings
):
211 def __init__(self
, tRP
, tRCD
, tWR
, tWTR
, tREFI
, tRFC
, tFAW
, tCCD
, tRRD
, tRC
, tRAS
, tZQCS
):
212 self
.set_attributes(locals())
214 # Layouts/Interface --------------------------------------------------------------------------------
216 def cmd_layout(address_width
):
218 ("valid", 1, DIR_FANOUT
),
219 ("ready", 1, DIR_FANIN
),
220 ("we", 1, DIR_FANOUT
),
221 ("addr", address_width
, DIR_FANOUT
),
222 ("lock", 1, DIR_FANIN
), # only used internally
224 ("wdata_ready", 1, DIR_FANIN
),
225 ("rdata_valid", 1, DIR_FANIN
)
228 def data_layout(data_width
):
230 ("wdata", data_width
, DIR_FANOUT
),
231 ("wdata_we", data_width
//8, DIR_FANOUT
),
232 ("rdata", data_width
, DIR_FANIN
)
235 def cmd_description(address_width
):
238 ("addr", address_width
)
241 def wdata_description(data_width
):
243 ("data", data_width
),
244 ("we", data_width
//8)
247 def rdata_description(data_width
):
248 return [("data", data_width
)]
250 def cmd_request_layout(a
, ba
):
259 def cmd_request_rw_layout(a
, ba
):
260 return cmd_request_layout(a
, ba
) + [
267 class gramInterface(Record
):
268 def __init__(self
, address_align
, settings
):
269 rankbits
= log2_int(settings
.phy
.nranks
)
270 self
.address_align
= address_align
271 self
.address_width
= settings
.geom
.rowbits
+ settings
.geom
.colbits
+ rankbits
- address_align
272 self
.data_width
= settings
.phy
.dfi_databits
*settings
.phy
.nphases
273 self
.nbanks
= settings
.phy
.nranks
*(2**settings
.geom
.bankbits
)
274 self
.nranks
= settings
.phy
.nranks
275 self
.settings
= settings
277 layout
= [("bank"+str(i
), cmd_layout(self
.address_width
)) for i
in range(self
.nbanks
)]
278 layout
+= data_layout(self
.data_width
)
279 Record
.__init
__(self
, layout
)
281 # Ports --------------------------------------------------------------------------------------------
283 class gramNativePort(Settings
):
284 def __init__(self
, mode
, address_width
, data_width
, clock_domain
="sys", id=0):
285 self
.set_attributes(locals())
289 self
.cmd
= stream
.Endpoint(cmd_description(address_width
))
290 self
.wdata
= stream
.Endpoint(wdata_description(data_width
))
291 self
.rdata
= stream
.Endpoint(rdata_description(data_width
))
293 self
.flush
= Signal()
295 # retro-compatibility # FIXME: remove
296 self
.aw
= self
.address_width
297 self
.dw
= self
.data_width
298 self
.cd
= self
.clock_domain
300 def get_bank_address(self
, bank_bits
, cba_shift
):
301 cba_upper
= cba_shift
+ bank_bits
302 return self
.cmd
.addr
[cba_shift
:cba_upper
]
304 def get_row_column_address(self
, bank_bits
, rca_bits
, cba_shift
):
305 cba_upper
= cba_shift
+ bank_bits
306 if cba_shift
< rca_bits
:
308 return Cat(self
.cmd
.addr
[:cba_shift
], self
.cmd
.addr
[cba_upper
:])
310 return self
.cmd
.addr
[cba_upper
:]
312 return self
.cmd
.addr
[:cba_shift
]
315 class gramNativeWritePort(gramNativePort
):
316 def __init__(self
, *args
, **kwargs
):
317 LiteDRAMNativePort
.__init
__(self
, "write", *args
, **kwargs
)
320 class gramNativeReadPort(gramNativePort
):
321 def __init__(self
, *args
, **kwargs
):
322 LiteDRAMNativePort
.__init
__(self
, "read", *args
, **kwargs
)
325 # Timing Controllers -------------------------------------------------------------------------------
327 class tXXDController(Elaboratable
):
328 def __init__(self
, txxd
):
329 self
.valid
= Signal()
330 self
.ready
= ready
= Signal(reset
=txxd
is None)
331 #ready.attr.add("no_retiming") TODO
334 def elaborate(self
, platform
):
337 if self
._txxd
is not None:
338 count
= Signal(range(max(self
._txxd
, 2)))
339 with m
.If(self
.valid
):
341 count
.eq(self
._txxd
-1),
342 self
.ready
.eq((self
._txxd
- 1) == 0),
345 m
.d
.sync
+= count
.eq(count
-1)
346 with m
.If(count
== 1):
347 m
.d
.sync
+= self
.ready
.eq(1)
351 class tFAWController(Elaboratable
):
352 def __init__(self
, tfaw
):
353 self
.valid
= Signal()
354 self
.ready
= Signal(reset
=1)
355 #ready.attr.add("no_retiming") TODO
358 def elaborate(self
, platform
):
361 if self
._tfaw
is not None:
362 count
= Signal(range(max(self
._tfaw
, 2)))
363 window
= Signal(self
._tfaw
)
364 m
.d
.sync
+= window
.eq(Cat(self
.valid
, window
))
365 m
.d
.comb
+= count
.eq(reduce(add
, [window
[i
] for i
in range(self
._tfaw
)]))
366 with m
.If(count
< 4):
367 with m
.If(count
== 3):
368 m
.d
.sync
+= self
.ready
.eq(~self
.valid
)
370 m
.d
.sync
+= self
.ready
.eq(1)