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 ------------------------------------------------------------------------------------------
30 def get_cl_cw(memtype
, tck
):
31 f_to_cl_cwl
= OrderedDict()
33 f_to_cl_cwl
[400e6
] = (3, 2)
34 f_to_cl_cwl
[533e6
] = (4, 3)
35 f_to_cl_cwl
[677e6
] = (5, 4)
36 f_to_cl_cwl
[800e6
] = (6, 5)
37 f_to_cl_cwl
[1066e6
] = (7, 5)
38 elif memtype
== "DDR3":
39 f_to_cl_cwl
[800e6
] = (6, 5)
40 f_to_cl_cwl
[1066e6
] = (7, 6)
41 f_to_cl_cwl
[1333e6
] = (10, 7)
42 f_to_cl_cwl
[1600e6
] = (11, 8)
43 elif memtype
== "DDR4":
44 f_to_cl_cwl
[1600e6
] = (11, 9)
47 for f
, (cl
, cwl
) in f_to_cl_cwl
.items():
53 def get_sys_latency(nphases
, cas_latency
):
54 return math
.ceil(cas_latency
/nphases
)
57 def get_sys_phases(nphases
, sys_latency
, cas_latency
):
58 dat_phase
= sys_latency
*nphases
- cas_latency
59 cmd_phase
= (dat_phase
- 1) % nphases
60 return cmd_phase
, dat_phase
62 # PHY Pads Transformers ----------------------------------------------------------------------------
68 Reduce DRAM pads to only use specific modules.
70 For testing purposes, we often need to use only some of the DRAM modules. PHYPadsReducer allows
71 selecting specific modules and avoid re-definining dram pins in the Platform for this.
74 def __init__(self
, pads
, modules
):
76 self
.modules
= modules
78 def __getattr__(self
, name
):
80 return Array([getattr(self
.pads
, name
)[8*i
+ j
]
83 if name
in ["dm", "dqs", "dqs_p", "dqs_n"]:
84 return Array([getattr(self
.pads
, name
)[i
] for i
in self
.modules
])
86 return getattr(self
.pads
, name
)
89 class PHYPadsCombiner
:
92 Combine DRAM pads from fully dissociated chips in a unique DRAM pads structure.
94 Most generally, DRAM chips are sharing command/address lines between chips (using a fly-by
95 topology since DDR3). On some boards, the DRAM chips are using separate command/address lines
96 and this combiner can be used to re-create a single pads structure (that will be compatible with
97 LiteDRAM's PHYs) to create a single DRAM controller from multiple fully dissociated DRAMs chips.
100 def __init__(self
, pads
):
101 if not isinstance(pads
, list):
107 def sel_group(self
, n
):
110 def __getattr__(self
, name
):
111 if name
in ["dm", "dq", "dqs", "dqs_p", "dqs_n"]:
112 return Array([getattr(self
.groups
[j
], name
)[i
]
113 for i
in range(len(getattr(self
.groups
[0], name
)))
114 for j
in range(len(self
.groups
))])
116 return getattr(self
.groups
[self
.sel
], name
)
118 # BitSlip ------------------------------------------------------------------------------------------
121 class BitSlip(Elaboratable
):
122 def __init__(self
, dw
, rst
=None, slp
=None, cycles
=1):
125 self
.rst
= Signal() if rst
is None else rst
126 self
.slp
= Signal() if slp
is None else slp
127 self
._cycles
= cycles
129 def elaborate(self
, platform
):
132 value
= Signal(range(self
._cycles
*dw
))
134 m
.d
.sync
+= value
.eq(value
+1)
135 with m
.Elif(self
.rst
):
136 m
.d
.sync
+= value
.eq(0)
138 r
= Signal((self
._cycles
+1)*dw
, reset_less
=True)
139 m
.d
.sync
+= r
.eq(Cat(r
[dw
:], self
.i
))
141 for i
in range(self
._cycles
*dw
):
142 cases
[i
] = self
.o
.eq(r
[i
:dw
+i
])
143 m
.d
.comb
+= Case(value
, cases
)
147 # DQS Pattern --------------------------------------------------------------------------------------
150 class DQSPattern(Elaboratable
):
151 def __init__(self
, preamble
=None, postamble
=None, wlevel_en
=0, wlevel_strobe
=0, register
=False):
152 self
.preamble
= Signal() if preamble
is None else preamble
153 self
.postamble
= Signal() if postamble
is None else postamble
155 self
._wlevel
_en
= wlevel_en
156 self
._wlevel
_strobe
= wlevel_strobe
157 self
._register
= register
159 def elaborate(self
, platform
):
162 with m
.If(self
.preamble
):
163 m
.d
.comb
+= self
.o
.eq(0b00010101)
164 with m
.Elif(self
.postamble
):
165 m
.d
.comb
+= self
.o
.eq(0b01010100)
166 with m
.Elif(self
._wlevel
_en
):
167 with m
.If(self
._wlevel
_strobe
):
168 m
.d
.comb
+= self
.o
.eq(0b00000001)
170 m
.d
.comb
+= self
.o
.eq(0b00000000)
172 m
.d
.comb
+= self
.o
.eq(0b01010101)
175 o
= Signal
.like(self
.o
)
176 m
.d
.sync
+= o
.eq(self
.o
)
181 # Settings -----------------------------------------------------------------------------------------
185 def set_attributes(self
, attributes
):
186 for k
, v
in attributes
.items():
190 class PhySettings(Settings
):
191 def __init__(self
, phytype
, memtype
, databits
, dfi_databits
,
194 rdcmdphase
, wrcmdphase
,
195 cl
, read_latency
, write_latency
, nranks
=1, cwl
=None):
196 self
.set_attributes(locals())
197 self
.cwl
= cl
if cwl
is None else cwl
198 self
.is_rdimm
= False
200 # Optional DDR3/DDR4 electrical settings:
201 # rtt_nom: Non-Writes on-die termination impedance
202 # rtt_wr: Writes on-die termination impedance
203 # ron: Output driver impedance
204 def add_electrical_settings(self
, rtt_nom
, rtt_wr
, ron
):
205 assert self
.memtype
in ["DDR3", "DDR4"]
206 self
.set_attributes(locals())
208 # Optional RDIMM configuration
209 def set_rdimm(self
, tck
, rcd_pll_bypass
, rcd_ca_cs_drive
, rcd_odt_cke_drive
, rcd_clk_drive
):
210 assert self
.memtype
== "DDR4"
212 self
.set_attributes(locals())
215 class GeomSettings(Settings
):
216 def __init__(self
, bankbits
, rowbits
, colbits
):
217 self
.set_attributes(locals())
218 self
.addressbits
= max(rowbits
, colbits
)
221 class TimingSettings(Settings
):
222 def __init__(self
, tRP
, tRCD
, tWR
, tWTR
, tREFI
, tRFC
, tFAW
, tCCD
, tRRD
, tRC
, tRAS
, tZQCS
):
223 self
.set_attributes(locals())
225 # Layouts/Interface --------------------------------------------------------------------------------
228 def cmd_layout(address_width
):
230 ("valid", 1, DIR_FANOUT
),
231 ("ready", 1, DIR_FANIN
),
232 ("we", 1, DIR_FANOUT
),
233 ("addr", address_width
, DIR_FANOUT
),
234 ("lock", 1, DIR_FANIN
), # only used internally
236 ("wdata_ready", 1, DIR_FANIN
),
237 ("rdata_valid", 1, DIR_FANIN
)
241 def data_layout(data_width
):
243 ("wdata", data_width
, DIR_FANOUT
),
244 ("wdata_we", data_width
//8, DIR_FANOUT
),
245 ("rdata", data_width
, DIR_FANIN
)
249 def cmd_description(address_width
):
252 ("addr", address_width
)
256 def wdata_description(data_width
):
258 ("data", data_width
),
259 ("we", data_width
//8)
263 def rdata_description(data_width
):
264 return [("data", data_width
)]
267 def cmd_request_layout(a
, ba
):
277 def cmd_request_rw_layout(a
, ba
):
278 return cmd_request_layout(a
, ba
) + [
285 class gramInterface(Record
):
286 def __init__(self
, address_align
, settings
):
287 rankbits
= log2_int(settings
.phy
.nranks
)
288 self
.address_align
= address_align
289 self
.address_width
= settings
.geom
.rowbits
+ \
290 settings
.geom
.colbits
+ rankbits
- address_align
291 self
.data_width
= settings
.phy
.dfi_databits
*settings
.phy
.nphases
292 self
.nbanks
= settings
.phy
.nranks
*(2**settings
.geom
.bankbits
)
293 self
.nranks
= settings
.phy
.nranks
294 self
.settings
= settings
296 layout
= [("bank"+str(i
), cmd_layout(self
.address_width
))
297 for i
in range(self
.nbanks
)]
298 layout
+= data_layout(self
.data_width
)
299 Record
.__init
__(self
, layout
)
301 # Ports --------------------------------------------------------------------------------------------
304 class gramNativePort(Settings
):
305 def __init__(self
, mode
, address_width
, data_width
, clock_domain
="sys", id=0):
306 self
.set_attributes(locals())
310 self
.cmd
= stream
.Endpoint(cmd_description(address_width
))
311 self
.wdata
= stream
.Endpoint(wdata_description(data_width
))
312 self
.rdata
= stream
.Endpoint(rdata_description(data_width
))
314 self
.flush
= Signal()
316 # retro-compatibility # FIXME: remove
317 self
.aw
= self
.address_width
318 self
.dw
= self
.data_width
319 self
.cd
= self
.clock_domain
321 def get_bank_address(self
, bank_bits
, cba_shift
):
322 cba_upper
= cba_shift
+ bank_bits
323 return self
.cmd
.addr
[cba_shift
:cba_upper
]
325 def get_row_column_address(self
, bank_bits
, rca_bits
, cba_shift
):
326 cba_upper
= cba_shift
+ bank_bits
327 if cba_shift
< rca_bits
:
329 return Cat(self
.cmd
.addr
[:cba_shift
], self
.cmd
.addr
[cba_upper
:])
331 return self
.cmd
.addr
[cba_upper
:]
333 return self
.cmd
.addr
[:cba_shift
]
336 class gramNativeWritePort(gramNativePort
):
337 def __init__(self
, *args
, **kwargs
):
338 gramNativePort
.__init
__(self
, "write", *args
, **kwargs
)
341 class gramNativeReadPort(gramNativePort
):
342 def __init__(self
, *args
, **kwargs
):
343 gramNativePort
.__init
__(self
, "read", *args
, **kwargs
)
346 # Timing Controllers -------------------------------------------------------------------------------
348 class tXXDController(Elaboratable
):
349 def __init__(self
, txxd
):
350 self
.valid
= Signal()
351 self
.ready
= ready
= Signal(reset
=txxd
is None)
352 # ready.attr.add("no_retiming") TODO
355 def elaborate(self
, platform
):
358 if self
._txxd
is not None:
359 count
= Signal(range(max(self
._txxd
, 2)))
360 with m
.If(self
.valid
):
362 count
.eq(self
._txxd
-1),
363 self
.ready
.eq((self
._txxd
- 1) == 0),
366 m
.d
.sync
+= count
.eq(count
-1)
367 with m
.If(count
== 1):
368 m
.d
.sync
+= self
.ready
.eq(1)
372 class tFAWController(Elaboratable
):
373 def __init__(self
, tfaw
):
374 self
.valid
= Signal()
375 self
.ready
= Signal(reset
=1)
376 # ready.attr.add("no_retiming") TODO
379 def elaborate(self
, platform
):
382 if self
._tfaw
is not None:
383 count
= Signal(range(max(self
._tfaw
, 2)))
384 window
= Signal(self
._tfaw
)
385 m
.d
.sync
+= window
.eq(Cat(self
.valid
, window
))
386 m
.d
.comb
+= count
.eq(reduce(add
, [window
[i
]
387 for i
in range(self
._tfaw
)]))
388 with m
.If(count
< 4):
389 with m
.If(count
== 3):
390 m
.d
.sync
+= self
.ready
.eq(~self
.valid
)
392 m
.d
.sync
+= self
.ready
.eq(1)