phy/lpddr4: make simphy serialization cleaner and easier to read

This commit is contained in:
Jędrzej Boczar 2021-05-28 14:44:38 +02:00
parent 47e8a59511
commit 721b6f874b
2 changed files with 100 additions and 88 deletions

View File

@ -6,7 +6,7 @@
from migen import * 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 from litedram.phy.lpddr4.basephy import LPDDR4PHY, DoubleRateLPDDR4PHY
@ -25,84 +25,11 @@ class LPDDR4SimulationPads(SimulationPads):
] ]
class _LPDDR4SimPHYMixin: class LPDDR4SimPHY(SimSerDesMixin, LPDDR4PHY):
"""Common serialization logic for simulation PHYs """LPDDR4 simulation PHY with direct 16:1 serializers
This mixin provides `do_serialization` method for constructing the boilerplate For simulation purpose two additional "DDR" clock domains are requires.
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.
""" """
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): def __init__(self, aligned_reset_zero=False, **kwargs):
pads = LPDDR4SimulationPads() pads = LPDDR4SimulationPads()
self.submodules += pads self.submodules += pads
@ -112,18 +39,54 @@ class LPDDR4SimPHY(_LPDDR4SimPHYMixin, LPDDR4PHY):
phytype = "LPDDR4SimPHY", phytype = "LPDDR4SimPHY",
**kwargs) **kwargs)
self.do_serialization( delay = lambda sig, cycles: delayed(self, sig, cycles=cycles)
clkdiv = "sys", sdr = dict(clkdiv="sys", clk="sys8x")
delay = lambda sig, cycles: delayed(self, sig, cycles=cycles), sdr_90 = dict(clkdiv="sys", clk="sys8x_90")
aligned_reset_zero = aligned_reset_zero, 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 """LPDDR4 simulation PHY basing of DoubleRateLPDDR4PHY
`DoubleRateLPDDR4PHY` performs a single serialization step between `sys` and `sys2x`, `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). 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): def __init__(self, aligned_reset_zero=False, **kwargs):
pads = LPDDR4SimulationPads() pads = LPDDR4SimulationPads()
@ -135,9 +98,43 @@ class DoubleRateLPDDR4SimPHY(_LPDDR4SimPHYMixin, DoubleRateLPDDR4PHY):
**kwargs) **kwargs)
self.submodules.half_delay = ClockDomainsRenamer("sys2x")(Module()) self.submodules.half_delay = ClockDomainsRenamer("sys2x")(Module())
delay = lambda sig, cycles: delayed(self.half_delay, sig, cycles=cycles)
self.do_serialization( sdr = dict(clkdiv="sys2x", clk="sys8x")
clkdiv = "sys2x", sdr_90 = dict(clkdiv="sys2x", clk="sys8x_90")
delay = lambda sig, cycles: delayed(self.half_delay, sig, cycles=cycles), ddr = dict(clkdiv="sys2x", clk="sys8x_ddr")
aligned_reset_zero = aligned_reset_zero, 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)),
]

View File

@ -320,6 +320,21 @@ class Deserializer(Module):
sd_clkdiv += self.o.eq(Cat(as_array(o_pre_d)[:-1], as_array(o_pre)[-1])) 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): class SimLogger(Module, AutoCSR):
"""Logger for use in simulation """Logger for use in simulation