diff --git a/litedram/init.py b/litedram/init.py index c0985d6..71ac799 100644 --- a/litedram/init.py +++ b/litedram/init.py @@ -683,9 +683,10 @@ def get_sdram_phy_c_header(phy_settings, timing_settings): r += "#define SDRAM_PHY_WRPHASE "+str(wrphase)+"\n" # Define Read/Write Leveling capability - if phytype in ["USDDRPHY", "USPDDRPHY", "K7DDRPHY", "V7DDRPHY", "S7LPDDR4PHY"]: + if phytype in ["USDDRPHY", "USPDDRPHY", 'K7DDRPHY', 'V7DDRPHY', 'K7LPDDR4PHY', 'V7LPDDR4PHY']: r += "#define SDRAM_PHY_WRITE_LEVELING_CAPABLE\n" - if phytype in ["USDDRPHY", "USPDDRPHY", "A7DDRPHY", "K7DDRPHY", "V7DDRPHY", "S7LPDDR4PHY"]: + if phytype in ["USDDRPHY", "USPDDRPHY", 'A7DDRPHY', 'K7DDRPHY', 'V7DDRPHY', 'A7LPDDR4PHY', + 'K7LPDDR4PHY', 'V7LPDDR4PHY']: r += "#define SDRAM_PHY_WRITE_LATENCY_CALIBRATION_CAPABLE\n" r += "#define SDRAM_PHY_READ_LEVELING_CAPABLE\n" if phytype in ["ECP5DDRPHY"]: @@ -702,7 +703,7 @@ def get_sdram_phy_c_header(phy_settings, timing_settings): r += "#define SDRAM_PHY_MODULES DFII_PIX_DATA_BYTES/2\n" r += "#define SDRAM_PHY_DELAYS 32\n" r += "#define SDRAM_PHY_BITSLIPS 8\n" - elif phytype in ["S7LPDDR4PHY"]: + elif phytype in ["A7LPDDR4PHY", "K7LPDDR4PHY", "V7LPDDR4PHY"]: r += "#define SDRAM_PHY_MODULES DFII_PIX_DATA_BYTES/2\n" r += "#define SDRAM_PHY_DELAYS 32\n" r += "#define SDRAM_PHY_BITSLIPS 16\n" diff --git a/litedram/phy/lpddr4/__init__.py b/litedram/phy/lpddr4/__init__.py index 5ce2577..b708b21 100644 --- a/litedram/phy/lpddr4/__init__.py +++ b/litedram/phy/lpddr4/__init__.py @@ -1,2 +1,2 @@ -from litedram.phy.lpddr4.s7phy import S7LPDDR4PHY +from litedram.phy.lpddr4.s7phy import A7LPDDR4PHY, K7LPDDR4PHY, V7LPDDR4PHY from litedram.phy.lpddr4.simphy import LPDDR4SimPHY diff --git a/litedram/phy/lpddr4/s7phy.py b/litedram/phy/lpddr4/s7phy.py index fe190fa..af67c57 100644 --- a/litedram/phy/lpddr4/s7phy.py +++ b/litedram/phy/lpddr4/s7phy.py @@ -6,13 +6,12 @@ from litex.soc.interconnect.csr import * from litedram.common import * from litedram.phy.dfi import * -from litedram.phy.lpddr4.utils import delayed as delayed +from litedram.phy.lpddr4.utils import delayed from litedram.phy.lpddr4.basephy import DoubleRateLPDDR4PHY, Latency class S7LPDDR4PHY(DoubleRateLPDDR4PHY): - def __init__(self, pads, *, iodelay_clk_freq, **kwargs): - # TODO: add `with_odelay` argument to avoid ODELAYE2, currently it won't work on Artix7 + def __init__(self, pads, *, iodelay_clk_freq, with_odelay, **kwargs): self.iodelay_clk_freq = iodelay_clk_freq # DoubleRateLPDDR4PHY outputs half-width signals (comparing to LPDDR4PHY) in sys2x domain. @@ -38,17 +37,18 @@ class S7LPDDR4PHY(DoubleRateLPDDR4PHY): # Registers -------------------------------------------------------------------------------- self._half_sys8x_taps = CSRStorage(5, reset=half_sys8x_taps) - # odelay control - self._cdly_rst = CSR() - self._cdly_inc = CSR() + # delay control self._rdly_dq_rst = CSR() self._rdly_dq_inc = CSR() self._rdly_dqs_rst = CSR() self._rdly_dqs_inc = CSR() - self._wdly_dq_rst = CSR() - self._wdly_dq_inc = CSR() - self._wdly_dqs_rst = CSR() - self._wdly_dqs_inc = CSR() + if with_odelay: + self._cdly_rst = CSR() + self._cdly_inc = CSR() + self._wdly_dq_rst = CSR() + self._wdly_dq_inc = CSR() + self._wdly_dqs_rst = CSR() + self._wdly_dqs_inc = CSR() def cdc(i): o = Signal() @@ -60,16 +60,17 @@ class S7LPDDR4PHY(DoubleRateLPDDR4PHY): ] return o - cdly_rst = cdc(self._cdly_rst.re) | self._rst.storage - cdly_inc = cdc(self._cdly_inc.re) rdly_dq_rst = cdc(self._rdly_dq_rst.re) rdly_dq_inc = cdc(self._rdly_dq_inc.re) rdly_dqs_rst = cdc(self._rdly_dqs_rst.re) rdly_dqs_inc = cdc(self._rdly_dqs_inc.re) - wdly_dq_rst = cdc(self._wdly_dq_rst.re) - wdly_dq_inc = cdc(self._wdly_dq_inc.re) - wdly_dqs_rst = cdc(self._wdly_dqs_rst.re) - wdly_dqs_inc = cdc(self._wdly_dqs_inc.re) + if with_odelay: + cdly_rst = cdc(self._cdly_rst.re) | self._rst.storage + cdly_inc = cdc(self._cdly_inc.re) + wdly_dq_rst = cdc(self._wdly_dq_rst.re) + wdly_dq_inc = cdc(self._wdly_dq_inc.re) + wdly_dqs_rst = cdc(self._wdly_dqs_rst.re) + wdly_dqs_inc = cdc(self._wdly_dqs_inc.re) # In theory we should only need to delay by 2 cycles, but sometimes it happened that # DQ/DMI were transmitted incomplete due to OE being asserted too late/released too @@ -90,27 +91,37 @@ class S7LPDDR4PHY(DoubleRateLPDDR4PHY): # Serialization ---------------------------------------------------------------------------- # Clock - clk_ser = Signal() clk_dly = Signal() + clk_ser = Signal() # Invert clk to have it phase shifted in relation to CS/CA, because we serialize it with DDR, # rising edge will then be in the middle of a data bit. - self.oserdese2_ddr(din=~self.out.clk, dout=clk_ser, clk="sys8x") - self.odelaye2(din=clk_ser, dout=clk_dly, rst=cdly_rst, inc=cdly_inc) + self.oserdese2_ddr(din=~self.out.clk, dout=clk_ser if with_odelay else clk_dly, clk="sys8x") + if with_odelay: + self.odelaye2(din=clk_ser, dout=clk_dly, rst=cdly_rst, inc=cdly_inc) self.obufds(din=clk_dly, dout=self.pads.clk_p, dout_b=self.pads.clk_n) for cmd in ["cke", "odt", "reset_n"]: + cmd_i = getattr(self.out, cmd) + cmd_o = getattr(self.pads, cmd) cmd_ser = Signal() - self.oserdese2_sdr(din=getattr(self.out, cmd), dout=cmd_ser, clk="sys8x") - self.odelaye2(din=cmd_ser, dout=getattr(self.pads, cmd), rst=cdly_rst, inc=cdly_inc) + self.oserdese2_sdr(din=cmd_i, dout=cmd_ser if with_odelay else cmd_o, clk="sys8x") + if with_odelay: + self.odelaye2(din=cmd_ser, dout=cmd_o, rst=cdly_rst, inc=cdly_inc) # Commands cs_ser = Signal() - self.oserdese2_sdr(din=self.out.cs, dout=cs_ser, clk="sys8x") - self.odelaye2(din=cs_ser, dout=self.pads.cs, rst=cdly_rst, inc=cdly_inc) + if with_odelay: + self.oserdese2_sdr(din=self.out.cs, dout=cs_ser, clk="sys8x") + self.odelaye2(din=cs_ser, dout=self.pads.cs, rst=cdly_rst, inc=cdly_inc) + else: + self.oserdese2_sdr(din=self.out.cs, dout=self.pads.cs, clk="sys8x") for bit in range(6): ca_ser = Signal() - self.oserdese2_sdr(din=self.out.ca[bit], dout=ca_ser, clk="sys8x") - self.odelaye2(din=ca_ser, dout=self.pads.ca[bit], rst=cdly_rst, inc=cdly_inc) + if with_odelay: + self.oserdese2_sdr(din=self.out.ca[bit], dout=ca_ser, clk="sys8x") + self.odelaye2(din=ca_ser, dout=self.pads.ca[bit], rst=cdly_rst, inc=cdly_inc) + else: + self.oserdese2_sdr(din=self.out.ca[bit], dout=self.pads.ca[bit], clk="sys8x") # DQS for byte in range(self.databits//8): @@ -120,20 +131,27 @@ class S7LPDDR4PHY(DoubleRateLPDDR4PHY): dqs_dly = Signal() dqs_i = Signal() dqs_i_dly = Signal() + # need to delay DQS if clocks are not phase aligned + dqs_din = self.out.dqs_o[byte] + if not with_odelay: + dqs_din_d = Signal.like(dqs_din) + self.sync.sys2x += dqs_din_d.eq(dqs_din) + dqs_din = dqs_din_d self.oserdese2_ddr( - din = self.out.dqs_o[byte], - dout_fb = dqs_ser, + din = dqs_din, + **(dict(dout_fb=dqs_ser) if with_odelay else dict(dout=dqs_dly)), tin = ~oe_delay_dqs(self.out.dqs_oe), tout = dqs_t, - clk = "sys8x", # TODO: if odelay is not avaiable need to use sys8x_90 - ) - self.odelaye2( - din = dqs_ser, - dout = dqs_dly, - rst = self.get_rst(byte, wdly_dqs_rst), - inc = self.get_inc(byte, wdly_dqs_inc), - init = half_sys8x_taps, # shifts by 90 degrees + clk = "sys8x" if with_odelay else "sys8x_90", ) + if with_odelay: + self.odelaye2( + din = dqs_ser, + dout = dqs_dly, + rst = self.get_rst(byte, wdly_dqs_rst), + inc = self.get_inc(byte, wdly_dqs_inc), + init = half_sys8x_taps, # shifts by 90 degrees + ) self.iobufds( din = dqs_dly, dout = dqs_i, @@ -160,17 +178,18 @@ class S7LPDDR4PHY(DoubleRateLPDDR4PHY): dmi_dly = Signal() self.oserdese2_ddr( din = self.out.dmi_o[byte], - dout_fb = dmi_ser, + **(dict(dout_fb=dmi_ser) if with_odelay else dict(dout=dmi_dly)), tin = ~oe_delay_data(self.out.dmi_oe), tout = dmi_t, clk = "sys8x", ) - self.odelaye2( - din = dmi_ser, - dout = dmi_dly, - rst = self.get_rst(byte, wdly_dq_rst), - inc = self.get_inc(byte, wdly_dq_inc), - ) + if with_odelay: + self.odelaye2( + din = dmi_ser, + dout = dmi_dly, + rst = self.get_rst(byte, wdly_dq_rst), + inc = self.get_inc(byte, wdly_dq_inc), + ) self.iobuf( din = dmi_dly, dout = Signal(), @@ -187,17 +206,18 @@ class S7LPDDR4PHY(DoubleRateLPDDR4PHY): dq_i_dly = Signal() self.oserdese2_ddr( din = self.out.dq_o[bit], - dout_fb = dq_ser, + **(dict(dout_fb=dq_ser) if with_odelay else dict(dout=dq_dly)), tin = ~oe_delay_data(self.out.dmi_oe), tout = dq_t, clk = "sys8x", ) - self.odelaye2( - din = dq_ser, - dout = dq_dly, - rst = self.get_rst(bit//8, wdly_dq_rst), - inc = self.get_inc(bit//8, wdly_dq_inc), - ) + if with_odelay: + self.odelaye2( + din = dq_ser, + dout = dq_dly, + rst = self.get_rst(bit//8, wdly_dq_rst), + inc = self.get_inc(bit//8, wdly_dq_inc), + ) self.iobuf( din = dq_dly, dout = dq_i, @@ -363,3 +383,23 @@ class S7LPDDR4PHY(DoubleRateLPDDR4PHY): o_O = dout, io_IO = dinout, ) + +# PHY variants ------------------------------------------------------------------------------------- + +class V7LPDDR4PHY(S7LPDDR4PHY): + """Xilinx Virtex7 LPDDR4 PHY (with odelay)""" + def __init__(self, pads, **kwargs): + S7LPDDR4PHY.__init__(self, pads, with_odelay=True, **kwargs) + +class K7LPDDR4PHY(S7LPDDR4PHY): + """Xilinx Kintex7 LPDDR4 PHY (with odelay)""" + def __init__(self, pads, **kwargs): + S7LPDDR4PHY.__init__(self, pads, with_odelay=True, **kwargs) + +class A7LPDDR4PHY(S7LPDDR4PHY): + """Xilinx Artix7 LPDDR4 PHY (without odelay) + + This variant requires generating sys8x_90 clock in CRG with a 90° phase shift vs sys8x. + """ + def __init__(self, pads, **kwargs): + S7LPDDR4PHY.__init__(self, pads, with_odelay=False, **kwargs)