392 lines
13 KiB
Python
392 lines
13 KiB
Python
#
|
|
# This file is part of LiteDRAM.
|
|
#
|
|
# Copyright (c) 2016-2019 Florent Kermarrec <florent@enjoy-digital.fr>
|
|
# Copyright (c) 2018 John Sully <john@csquare.ca>
|
|
# Copyright (c) 2018 bunnie <bunnie@kosagi.com>
|
|
# SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
import math
|
|
from functools import reduce
|
|
from operator import add
|
|
from collections import OrderedDict
|
|
|
|
from migen import *
|
|
|
|
from litex.soc.interconnect import stream
|
|
|
|
# Helpers ------------------------------------------------------------------------------------------
|
|
|
|
burst_lengths = {
|
|
"SDR": 1,
|
|
"DDR": 4,
|
|
"LPDDR": 4,
|
|
"DDR2": 4,
|
|
"DDR3": 8,
|
|
"DDR4": 8
|
|
}
|
|
|
|
def get_cl_cw(memtype, tck):
|
|
f_to_cl_cwl = OrderedDict()
|
|
if memtype == "DDR2":
|
|
f_to_cl_cwl[400e6] = (3, 2)
|
|
f_to_cl_cwl[533e6] = (4, 3)
|
|
f_to_cl_cwl[677e6] = (5, 4)
|
|
f_to_cl_cwl[800e6] = (6, 5)
|
|
f_to_cl_cwl[1066e6] = (7, 5)
|
|
elif memtype == "DDR3":
|
|
f_to_cl_cwl[800e6] = ( 6, 5)
|
|
f_to_cl_cwl[1066e6] = ( 7, 6)
|
|
f_to_cl_cwl[1333e6] = (10, 7)
|
|
f_to_cl_cwl[1600e6] = (11, 8)
|
|
elif memtype == "DDR4":
|
|
f_to_cl_cwl[1333e6] = (9, 9)
|
|
f_to_cl_cwl[1600e6] = (11, 9)
|
|
f_to_cl_cwl[1866e6] = (13, 10)
|
|
f_to_cl_cwl[2133e6] = (15, 11)
|
|
f_to_cl_cwl[2400e6] = (16, 12)
|
|
f_to_cl_cwl[2666e6] = (18, 14)
|
|
else:
|
|
raise ValueError
|
|
for f, (cl, cwl) in f_to_cl_cwl.items():
|
|
if tck >= 2/f:
|
|
return cl, cwl
|
|
raise ValueError
|
|
|
|
def get_sys_latency(nphases, cas_latency):
|
|
return math.ceil(cas_latency/nphases)
|
|
|
|
def get_sys_phase(nphases, sys_latency, cas_latency):
|
|
return sys_latency*nphases - cas_latency
|
|
|
|
# PHY Pads Transformers ----------------------------------------------------------------------------
|
|
|
|
class PHYPadsReducer:
|
|
"""PHY Pads Reducer
|
|
|
|
Reduce DRAM pads to only use specific modules.
|
|
|
|
For testing purposes, we often need to use only some of the DRAM modules. PHYPadsReducer allows
|
|
selecting specific modules and avoid re-definining dram pins in the Platform for this.
|
|
"""
|
|
def __init__(self, pads, modules, with_cat=False):
|
|
self.pads = pads
|
|
self.modules = modules
|
|
self.with_cat = with_cat
|
|
|
|
def __getattr__(self, name):
|
|
if name in ["dq"]:
|
|
r = Array([getattr(self.pads, name)[8*i + j]
|
|
for i in self.modules
|
|
for j in range(8)])
|
|
return r if not self.with_cat else Cat(r)
|
|
if name in ["dm", "dqs", "dqs_p", "dqs_n"]:
|
|
r = Array([getattr(self.pads, name)[i] for i in self.modules])
|
|
return r if not self.with_cat else Cat(r)
|
|
else:
|
|
return getattr(self.pads, name)
|
|
|
|
class PHYPadsCombiner:
|
|
"""PHY Pads Combiner
|
|
|
|
Combine DRAM pads from fully dissociated chips in a unique DRAM pads structure.
|
|
|
|
Most generally, DRAM chips are sharing command/address lines between chips (using a fly-by
|
|
topology since DDR3). On some boards, the DRAM chips are using separate command/address lines
|
|
and this combiner can be used to re-create a single pads structure (that will be compatible with
|
|
LiteDRAM's PHYs) to create a single DRAM controller from multiple fully dissociated DRAMs chips.
|
|
"""
|
|
def __init__(self, pads):
|
|
if not isinstance(pads, list):
|
|
self.groups = [pads]
|
|
else:
|
|
self.groups = pads
|
|
self.sel = 0
|
|
|
|
def sel_group(self, n):
|
|
self.sel = n
|
|
|
|
def __getattr__(self, name):
|
|
if name in ["dm", "dq", "dqs", "dqs_p", "dqs_n"]:
|
|
return Array([getattr(self.groups[j], name)[i]
|
|
for i in range(len(getattr(self.groups[0], name)))
|
|
for j in range(len(self.groups))])
|
|
else:
|
|
return getattr(self.groups[self.sel], name)
|
|
|
|
# BitSlip ------------------------------------------------------------------------------------------
|
|
|
|
class BitSlip(Module):
|
|
def __init__(self, dw, i=None, o=None, rst=None, slp=None, cycles=1):
|
|
self.i = Signal(dw) if i is None else i
|
|
self.o = Signal(dw) if o is None else o
|
|
self.rst = Signal() if rst is None else rst
|
|
self.slp = Signal() if slp is None else slp
|
|
assert cycles >= 1
|
|
|
|
# # #
|
|
|
|
value = Signal(max=cycles*dw, reset=cycles*dw-1)
|
|
self.sync += If(self.slp, value.eq(value + 1))
|
|
self.sync += If(self.rst, value.eq(value.reset))
|
|
|
|
r = Signal((cycles+1)*dw, reset_less=True)
|
|
self.sync += r.eq(Cat(r[dw:], self.i))
|
|
cases = {}
|
|
for i in range(cycles*dw):
|
|
cases[i] = self.o.eq(r[i+1:dw+i+1])
|
|
self.comb += Case(value, cases)
|
|
|
|
# TappedDelayLine ----------------------------------------------------------------------------------
|
|
|
|
class TappedDelayLine(Module):
|
|
def __init__(self, signal=None, ntaps=1):
|
|
self.input = Signal() if signal is None else signal
|
|
self.taps = Array(Signal.like(self.input) for i in range(ntaps))
|
|
for i in range(ntaps):
|
|
self.sync += self.taps[i].eq(self.input if i == 0 else self.taps[i-1])
|
|
self.output = self.taps[-1]
|
|
|
|
# DQS Pattern --------------------------------------------------------------------------------------
|
|
|
|
class DQSPattern(Module):
|
|
def __init__(self, preamble=None, postamble=None, wlevel_en=0, wlevel_strobe=0, register=False):
|
|
self.preamble = Signal() if preamble is None else preamble
|
|
self.postamble = Signal() if postamble is None else postamble
|
|
self.o = Signal(8)
|
|
|
|
# # #
|
|
|
|
# DQS Pattern transmitted as LSB-first.
|
|
|
|
self.comb += [
|
|
self.o.eq(0b01010101),
|
|
If(self.preamble,
|
|
self.o.eq(0b00010101)
|
|
),
|
|
If(self.postamble,
|
|
self.o.eq(0b01010100)
|
|
),
|
|
If(wlevel_en,
|
|
self.o.eq(0b00000000),
|
|
If(wlevel_strobe,
|
|
self.o.eq(0b00000001)
|
|
)
|
|
)
|
|
]
|
|
if register:
|
|
o = Signal.like(self.o)
|
|
self.sync += o.eq(self.o)
|
|
self.o = o
|
|
|
|
# Settings -----------------------------------------------------------------------------------------
|
|
|
|
class Settings:
|
|
def set_attributes(self, attributes):
|
|
for k, v in attributes.items():
|
|
setattr(self, k, v)
|
|
|
|
|
|
class PhySettings(Settings):
|
|
def __init__(self, phytype, memtype, databits, dfi_databits,
|
|
nphases,
|
|
rdphase, wrphase,
|
|
cl, read_latency, write_latency, nranks=1, cwl=None,
|
|
cmd_latency=None, cmd_delay=None):
|
|
self.set_attributes(locals())
|
|
self.cwl = cl if cwl is None else cwl
|
|
self.is_rdimm = False
|
|
|
|
# Optional DDR3/DDR4 electrical settings:
|
|
# rtt_nom: Non-Writes on-die termination impedance
|
|
# rtt_wr: Writes on-die termination impedance
|
|
# ron: Output driver impedance
|
|
# tdqs: Termination Data Strobe enable.
|
|
def add_electrical_settings(self, rtt_nom, rtt_wr, ron, tdqs=False):
|
|
assert self.memtype in ["DDR3", "DDR4"]
|
|
self.set_attributes(locals())
|
|
|
|
# Optional RDIMM configuration
|
|
def set_rdimm(self, tck, rcd_pll_bypass, rcd_ca_cs_drive, rcd_odt_cke_drive, rcd_clk_drive):
|
|
assert self.memtype == "DDR4"
|
|
self.is_rdimm = True
|
|
self.set_attributes(locals())
|
|
|
|
class GeomSettings(Settings):
|
|
def __init__(self, bankbits, rowbits, colbits):
|
|
self.set_attributes(locals())
|
|
self.addressbits = max(rowbits, colbits)
|
|
|
|
|
|
class TimingSettings(Settings):
|
|
def __init__(self, tRP, tRCD, tWR, tWTR, tREFI, tRFC, tFAW, tCCD, tRRD, tRC, tRAS, tZQCS):
|
|
self.set_attributes(locals())
|
|
|
|
# Layouts/Interface --------------------------------------------------------------------------------
|
|
|
|
def cmd_layout(address_width):
|
|
return [
|
|
("valid", 1, DIR_M_TO_S),
|
|
("ready", 1, DIR_S_TO_M),
|
|
("we", 1, DIR_M_TO_S),
|
|
("addr", address_width, DIR_M_TO_S),
|
|
("lock", 1, DIR_S_TO_M), # only used internally
|
|
|
|
("wdata_ready", 1, DIR_S_TO_M),
|
|
("rdata_valid", 1, DIR_S_TO_M)
|
|
]
|
|
|
|
def data_layout(data_width):
|
|
return [
|
|
("wdata", data_width, DIR_M_TO_S),
|
|
("wdata_we", data_width//8, DIR_M_TO_S),
|
|
("rdata", data_width, DIR_S_TO_M)
|
|
]
|
|
|
|
def cmd_request_layout(a, ba):
|
|
return [
|
|
("a", a),
|
|
("ba", ba),
|
|
("cas", 1),
|
|
("ras", 1),
|
|
("we", 1)
|
|
]
|
|
|
|
def cmd_request_rw_layout(a, ba):
|
|
return cmd_request_layout(a, ba) + [
|
|
("is_cmd", 1),
|
|
("is_read", 1),
|
|
("is_write", 1)
|
|
]
|
|
|
|
|
|
class LiteDRAMInterface(Record):
|
|
def __init__(self, address_align, settings):
|
|
rankbits = log2_int(settings.phy.nranks)
|
|
self.address_align = address_align
|
|
self.address_width = settings.geom.rowbits + settings.geom.colbits + rankbits - address_align
|
|
self.data_width = settings.phy.dfi_databits*settings.phy.nphases
|
|
self.nbanks = settings.phy.nranks*(2**settings.geom.bankbits)
|
|
self.nranks = settings.phy.nranks
|
|
self.settings = settings
|
|
|
|
layout = [("bank"+str(i), cmd_layout(self.address_width)) for i in range(self.nbanks)]
|
|
layout += data_layout(self.data_width)
|
|
Record.__init__(self, layout)
|
|
|
|
# Ports --------------------------------------------------------------------------------------------
|
|
|
|
def cmd_description(address_width):
|
|
return [
|
|
("we", 1), # Write (1) or Read (0).
|
|
("addr", address_width) # Address (in Controller's words).
|
|
]
|
|
|
|
def wdata_description(data_width):
|
|
return [
|
|
("data", data_width), # Write Data.
|
|
("we", data_width//8), # Write Data byte enable.
|
|
]
|
|
|
|
def rdata_description(data_width):
|
|
return [("data", data_width)] # Read Data.
|
|
|
|
class LiteDRAMNativePort(Settings):
|
|
def __init__(self, mode, address_width, data_width, clock_domain="sys", id=0):
|
|
self.set_attributes(locals())
|
|
|
|
self.flush = Signal()
|
|
self.lock = Signal()
|
|
|
|
self.cmd = stream.Endpoint(cmd_description(address_width))
|
|
self.wdata = stream.Endpoint(wdata_description(data_width))
|
|
self.rdata = stream.Endpoint(rdata_description(data_width))
|
|
|
|
# retro-compatibility # FIXME: remove
|
|
self.aw = self.address_width
|
|
self.dw = self.data_width
|
|
self.cd = self.clock_domain
|
|
|
|
def get_bank_address(self, bank_bits, cba_shift):
|
|
cba_upper = cba_shift + bank_bits
|
|
return self.cmd.addr[cba_shift:cba_upper]
|
|
|
|
def get_row_column_address(self, bank_bits, rca_bits, cba_shift):
|
|
cba_upper = cba_shift + bank_bits
|
|
if cba_shift < rca_bits:
|
|
if cba_shift:
|
|
return Cat(self.cmd.addr[:cba_shift], self.cmd.addr[cba_upper:])
|
|
else:
|
|
return self.cmd.addr[cba_upper:]
|
|
else:
|
|
return self.cmd.addr[:cba_shift]
|
|
|
|
def connect(self, port):
|
|
return [
|
|
self.cmd.connect(port.cmd),
|
|
self.wdata.connect(port.wdata),
|
|
port.rdata.connect(self.rdata),
|
|
port.flush.eq(self.flush),
|
|
self.lock.eq(port.lock),
|
|
]
|
|
|
|
class LiteDRAMNativeWritePort(LiteDRAMNativePort):
|
|
def __init__(self, *args, **kwargs):
|
|
LiteDRAMNativePort.__init__(self, "write", *args, **kwargs)
|
|
|
|
|
|
class LiteDRAMNativeReadPort(LiteDRAMNativePort):
|
|
def __init__(self, *args, **kwargs):
|
|
LiteDRAMNativePort.__init__(self, "read", *args, **kwargs)
|
|
|
|
|
|
# Timing Controllers -------------------------------------------------------------------------------
|
|
|
|
class tXXDController(Module):
|
|
def __init__(self, txxd):
|
|
self.valid = valid = Signal()
|
|
self.ready = ready = Signal(reset=txxd is None)
|
|
ready.attr.add("no_retiming")
|
|
|
|
# # #
|
|
|
|
if txxd is not None:
|
|
count = Signal(max=max(txxd, 2))
|
|
self.sync += \
|
|
If(valid,
|
|
count.eq(txxd - 1),
|
|
If((txxd - 1) == 0,
|
|
ready.eq(1)
|
|
).Else(
|
|
ready.eq(0)
|
|
)
|
|
).Elif(~ready,
|
|
count.eq(count - 1),
|
|
If(count == 1,
|
|
ready.eq(1)
|
|
)
|
|
)
|
|
|
|
|
|
class tFAWController(Module):
|
|
def __init__(self, tfaw):
|
|
self.valid = valid = Signal()
|
|
self.ready = ready = Signal(reset=1)
|
|
ready.attr.add("no_retiming")
|
|
|
|
# # #
|
|
|
|
if tfaw is not None:
|
|
count = Signal(max=max(tfaw, 2))
|
|
window = Signal(tfaw)
|
|
self.sync += window.eq(Cat(valid, window))
|
|
self.comb += count.eq(reduce(add, [window[i] for i in range(tfaw)]))
|
|
self.sync += \
|
|
If(count < 4,
|
|
If(count == 3,
|
|
ready.eq(~valid)
|
|
).Else(
|
|
ready.eq(1)
|
|
)
|
|
)
|