phy/lpddr4: make simphy serialization cleaner and easier to read
This commit is contained in:
parent
47e8a59511
commit
721b6f874b
|
@ -6,7 +6,7 @@
|
|||
|
||||
from migen import *
|
||||
|
||||
from litedram.phy.utils import delayed, Serializer, Deserializer, Latency, SimPad, SimulationPads
|
||||
from litedram.phy.utils import delayed, Serializer, Deserializer, Latency, SimPad, SimulationPads, SimSerDesMixin
|
||||
from litedram.phy.lpddr4.basephy import LPDDR4PHY, DoubleRateLPDDR4PHY
|
||||
|
||||
|
||||
|
@ -25,84 +25,11 @@ class LPDDR4SimulationPads(SimulationPads):
|
|||
]
|
||||
|
||||
|
||||
class _LPDDR4SimPHYMixin:
|
||||
"""Common serialization logic for simulation PHYs
|
||||
class LPDDR4SimPHY(SimSerDesMixin, LPDDR4PHY):
|
||||
"""LPDDR4 simulation PHY with direct 16:1 serializers
|
||||
|
||||
This mixin provides `do_serialization` method for constructing the boilerplate
|
||||
serialization/deserialization paths for a simulation PHY. This can serve as a
|
||||
reference for implemeing PHYs for concrete FPGAs.
|
||||
|
||||
To make the (de-)serialization work in simulation two additional clock domains
|
||||
are required: `sys8x_ddr` and `sys8x_90_ddr`. These correspond to `sys8x` and
|
||||
`sys8x_90`, are phase aligned with them and at twice their frequency. These
|
||||
clock domains are requried to implement DDR (de-)serialization at 8x sys clock.
|
||||
For simulation purpose two additional "DDR" clock domains are requires.
|
||||
"""
|
||||
def _add_name(self, prefix, kwargs):
|
||||
name = prefix + "_" + kwargs.pop("name", "")
|
||||
kwargs["name"] = name.strip("_")
|
||||
|
||||
def _serialize(self, **kwargs):
|
||||
self._add_name("ser", kwargs)
|
||||
ser = Serializer(o_dw=1, **kwargs)
|
||||
self.submodules += ser
|
||||
|
||||
def _deserialize(self, **kwargs):
|
||||
self._add_name("des", kwargs)
|
||||
des = Deserializer(i_dw=1, **kwargs)
|
||||
self.submodules += des
|
||||
|
||||
def do_serialization(self, clkdiv, delay, aligned_reset_zero):
|
||||
def add_reset_cnt(phase, kwargs):
|
||||
if aligned_reset_zero and phase == 0:
|
||||
kwargs["reset_cnt"] = 0
|
||||
|
||||
def ser_sdr(phase=0, **kwargs):
|
||||
add_reset_cnt(phase, kwargs)
|
||||
clk = {0: "sys8x", 90: "sys8x_90"}[phase]
|
||||
self._serialize(clk=clk, clkdiv=clkdiv, i_dw=len(kwargs["i"]), **kwargs)
|
||||
|
||||
def ser_ddr(phase=0, **kwargs):
|
||||
add_reset_cnt(phase, kwargs)
|
||||
# for simulation we require sys8x_ddr clock (=sys16x)
|
||||
clk = {0: "sys8x_ddr", 90: "sys8x_90_ddr"}[phase]
|
||||
self._serialize(clk=clk, clkdiv=clkdiv, i_dw=len(kwargs["i"]), **kwargs)
|
||||
|
||||
def des_ddr(phase=0, **kwargs):
|
||||
add_reset_cnt(phase, kwargs)
|
||||
clk = {0: "sys8x_ddr", 90: "sys8x_90_ddr"}[phase]
|
||||
self._deserialize(clk=clk, clkdiv=clkdiv, o_dw=len(kwargs["o"]), **kwargs)
|
||||
|
||||
# Clock is shifted 180 degrees to get rising edge in the middle of SDR signals.
|
||||
# To achieve that we send negated clock on clk (clk_p).
|
||||
ser_ddr(i=~self.out.clk, o=self.pads.clk, name='clk')
|
||||
|
||||
ser_sdr(i=self.out.cke, o=self.pads.cke, name='cke')
|
||||
ser_sdr(i=self.out.odt, o=self.pads.odt, name='odt')
|
||||
ser_sdr(i=self.out.reset_n, o=self.pads.reset_n, name='reset_n')
|
||||
|
||||
# Command/address
|
||||
ser_sdr(i=self.out.cs, o=self.pads.cs, name='cs')
|
||||
for i in range(6):
|
||||
ser_sdr(i=self.out.ca[i], o=self.pads.ca[i], name=f'ca{i}')
|
||||
|
||||
# Tristate I/O (separate for simulation)
|
||||
for i in range(self.databits//8):
|
||||
ser_ddr(i=self.out.dmi_o[i], o=self.pads.dmi_o[i], name=f'dmi_o{i}')
|
||||
des_ddr(o=self.out.dmi_i[i], i=self.pads.dmi[i], name=f'dmi_i{i}')
|
||||
ser_ddr(i=self.out.dqs_o[i], o=self.pads.dqs_o[i], name=f'dqs_o{i}', phase=90)
|
||||
des_ddr(o=self.out.dqs_i[i], i=self.pads.dqs[i], name=f'dqs_i{i}', phase=90)
|
||||
for i in range(self.databits):
|
||||
ser_ddr(i=self.out.dq_o[i], o=self.pads.dq_o[i], name=f'dq_o{i}')
|
||||
des_ddr(o=self.out.dq_i[i], i=self.pads.dq[i], name=f'dq_i{i}')
|
||||
|
||||
# Output enable signals
|
||||
self.comb += self.pads.dmi_oe.eq(delay(self.out.dmi_oe, cycles=Serializer.LATENCY))
|
||||
self.comb += self.pads.dqs_oe.eq(delay(self.out.dqs_oe, cycles=Serializer.LATENCY))
|
||||
self.comb += self.pads.dq_oe.eq(delay(self.out.dq_oe, cycles=Serializer.LATENCY))
|
||||
|
||||
|
||||
class LPDDR4SimPHY(_LPDDR4SimPHYMixin, LPDDR4PHY):
|
||||
"""LPDDR4 simulation PHY with direct 16:1 serializers"""
|
||||
def __init__(self, aligned_reset_zero=False, **kwargs):
|
||||
pads = LPDDR4SimulationPads()
|
||||
self.submodules += pads
|
||||
|
@ -112,18 +39,54 @@ class LPDDR4SimPHY(_LPDDR4SimPHYMixin, LPDDR4PHY):
|
|||
phytype = "LPDDR4SimPHY",
|
||||
**kwargs)
|
||||
|
||||
self.do_serialization(
|
||||
clkdiv = "sys",
|
||||
delay = lambda sig, cycles: delayed(self, sig, cycles=cycles),
|
||||
aligned_reset_zero = aligned_reset_zero,
|
||||
)
|
||||
delay = lambda sig, cycles: delayed(self, sig, cycles=cycles)
|
||||
sdr = dict(clkdiv="sys", clk="sys8x")
|
||||
sdr_90 = dict(clkdiv="sys", clk="sys8x_90")
|
||||
ddr = dict(clkdiv="sys", clk="sys8x_ddr")
|
||||
ddr_90 = dict(clkdiv="sys", clk="sys8x_90_ddr")
|
||||
|
||||
if aligned_reset_zero:
|
||||
sdr["reset_cnt"] = 0
|
||||
ddr["reset_cnt"] = 0
|
||||
|
||||
# Clock is shifted 180 degrees to get rising edge in the middle of SDR signals.
|
||||
# To achieve that we send negated clock on clk (clk_p).
|
||||
self.ser(i=~self.out.clk, o=self.pads.clk, name='clk', **ddr)
|
||||
|
||||
self.ser(i=self.out.cke, o=self.pads.cke, name='cke', **sdr)
|
||||
self.ser(i=self.out.odt, o=self.pads.odt, name='odt', **sdr)
|
||||
self.ser(i=self.out.reset_n, o=self.pads.reset_n, name='reset_n', **sdr)
|
||||
|
||||
# Command/address
|
||||
self.ser(i=self.out.cs, o=self.pads.cs, name='cs', **sdr)
|
||||
for i in range(6):
|
||||
self.ser(i=self.out.ca[i], o=self.pads.ca[i], name=f'ca{i}', **sdr)
|
||||
|
||||
# Tristate I/O (separate for simulation)
|
||||
for i in range(self.databits//8):
|
||||
self.ser(i=self.out.dmi_o[i], o=self.pads.dmi_o[i], name=f'dmi_o{i}', **ddr)
|
||||
self.des(o=self.out.dmi_i[i], i=self.pads.dmi[i], name=f'dmi_i{i}', **ddr)
|
||||
self.ser(i=self.out.dqs_o[i], o=self.pads.dqs_o[i], name=f'dqs_o{i}', **ddr_90)
|
||||
self.des(o=self.out.dqs_i[i], i=self.pads.dqs[i], name=f'dqs_i{i}', **ddr_90)
|
||||
for i in range(self.databits):
|
||||
self.ser(i=self.out.dq_o[i], o=self.pads.dq_o[i], name=f'dq_o{i}', **ddr)
|
||||
self.des(o=self.out.dq_i[i], i=self.pads.dq[i], name=f'dq_i{i}', **ddr)
|
||||
|
||||
# Output enable signals
|
||||
self.comb += [
|
||||
self.pads.dmi_oe.eq(delay(self.out.dmi_oe, cycles=Serializer.LATENCY)),
|
||||
self.pads.dqs_oe.eq(delay(self.out.dqs_oe, cycles=Serializer.LATENCY)),
|
||||
self.pads.dq_oe.eq(delay(self.out.dq_oe, cycles=Serializer.LATENCY)),
|
||||
]
|
||||
|
||||
|
||||
class DoubleRateLPDDR4SimPHY(_LPDDR4SimPHYMixin, DoubleRateLPDDR4PHY):
|
||||
class DoubleRateLPDDR4SimPHY(SimSerDesMixin, DoubleRateLPDDR4PHY):
|
||||
"""LPDDR4 simulation PHY basing of DoubleRateLPDDR4PHY
|
||||
|
||||
`DoubleRateLPDDR4PHY` performs a single serialization step between `sys` and `sys2x`,
|
||||
so this PHY wrapper has to do the serialization between `sys2x` and `sys8x` (SDR/DDR).
|
||||
|
||||
For simulation purpose two additional "DDR" clock domains are requires.
|
||||
"""
|
||||
def __init__(self, aligned_reset_zero=False, **kwargs):
|
||||
pads = LPDDR4SimulationPads()
|
||||
|
@ -135,9 +98,43 @@ class DoubleRateLPDDR4SimPHY(_LPDDR4SimPHYMixin, DoubleRateLPDDR4PHY):
|
|||
**kwargs)
|
||||
|
||||
self.submodules.half_delay = ClockDomainsRenamer("sys2x")(Module())
|
||||
delay = lambda sig, cycles: delayed(self.half_delay, sig, cycles=cycles)
|
||||
|
||||
self.do_serialization(
|
||||
clkdiv = "sys2x",
|
||||
delay = lambda sig, cycles: delayed(self.half_delay, sig, cycles=cycles),
|
||||
aligned_reset_zero = aligned_reset_zero,
|
||||
)
|
||||
sdr = dict(clkdiv="sys2x", clk="sys8x")
|
||||
sdr_90 = dict(clkdiv="sys2x", clk="sys8x_90")
|
||||
ddr = dict(clkdiv="sys2x", clk="sys8x_ddr")
|
||||
ddr_90 = dict(clkdiv="sys2x", clk="sys8x_90_ddr")
|
||||
|
||||
if aligned_reset_zero:
|
||||
sdr["reset_cnt"] = 0
|
||||
ddr["reset_cnt"] = 0
|
||||
|
||||
# Clock is shifted 180 degrees to get rising edge in the middle of SDR signals.
|
||||
# To achieve that we send negated clock on clk (clk_p).
|
||||
self.ser(i=~self.out.clk, o=self.pads.clk, name='clk', **ddr)
|
||||
|
||||
self.ser(i=self.out.cke, o=self.pads.cke, name='cke', **sdr)
|
||||
self.ser(i=self.out.odt, o=self.pads.odt, name='odt', **sdr)
|
||||
self.ser(i=self.out.reset_n, o=self.pads.reset_n, name='reset_n', **sdr)
|
||||
|
||||
# Command/address
|
||||
self.ser(i=self.out.cs, o=self.pads.cs, name='cs', **sdr)
|
||||
for i in range(6):
|
||||
self.ser(i=self.out.ca[i], o=self.pads.ca[i], name=f'ca{i}', **sdr)
|
||||
|
||||
# Tristate I/O (separate for simulation)
|
||||
for i in range(self.databits//8):
|
||||
self.ser(i=self.out.dmi_o[i], o=self.pads.dmi_o[i], name=f'dmi_o{i}', **ddr)
|
||||
self.des(o=self.out.dmi_i[i], i=self.pads.dmi[i], name=f'dmi_i{i}', **ddr)
|
||||
self.ser(i=self.out.dqs_o[i], o=self.pads.dqs_o[i], name=f'dqs_o{i}', **ddr_90)
|
||||
self.des(o=self.out.dqs_i[i], i=self.pads.dqs[i], name=f'dqs_i{i}', **ddr_90)
|
||||
for i in range(self.databits):
|
||||
self.ser(i=self.out.dq_o[i], o=self.pads.dq_o[i], name=f'dq_o{i}', **ddr)
|
||||
self.des(o=self.out.dq_i[i], i=self.pads.dq[i], name=f'dq_i{i}', **ddr)
|
||||
|
||||
# Output enable signals
|
||||
self.comb += [
|
||||
self.pads.dmi_oe.eq(delay(self.out.dmi_oe, cycles=Serializer.LATENCY)),
|
||||
self.pads.dqs_oe.eq(delay(self.out.dqs_oe, cycles=Serializer.LATENCY)),
|
||||
self.pads.dq_oe.eq(delay(self.out.dq_oe, cycles=Serializer.LATENCY)),
|
||||
]
|
||||
|
|
|
@ -320,6 +320,21 @@ class Deserializer(Module):
|
|||
sd_clkdiv += self.o.eq(Cat(as_array(o_pre_d)[:-1], as_array(o_pre)[-1]))
|
||||
|
||||
|
||||
class SimSerDesMixin:
|
||||
"""Helper class for easier (de-)serialization to simulation pads."""
|
||||
def ser(self, *, i, o, clkdiv, clk, name="", **kwargs):
|
||||
assert len(o) == 1
|
||||
kwargs = dict(i=i, i_dw=len(i), o=o, o_dw=1, clk=clk, clkdiv=clkdiv,
|
||||
name=f"ser_{name}".strip("_"), **kwargs)
|
||||
self.submodules += Serializer(**kwargs)
|
||||
|
||||
def des(self, *, i, o, clkdiv, clk, name="", **kwargs):
|
||||
assert len(i) == 1
|
||||
kwargs = dict(i=i, i_dw=1, o=o, o_dw=len(o), clk=clk, clkdiv=clkdiv,
|
||||
name=f"des_{name}".strip("_"), **kwargs)
|
||||
self.submodules += Deserializer(**kwargs)
|
||||
|
||||
|
||||
class SimLogger(Module, AutoCSR):
|
||||
"""Logger for use in simulation
|
||||
|
||||
|
|
Loading…
Reference in New Issue