def test_latency_add(self): l = Latency(sys=2) + Latency(sys2x=3) self.assertEqual(l.sys2x, 4 + 3) l = Latency(sys2x=2, sys4x=3) + Latency(sys4x=1) self.assertEqual(l.sys4x, 8) self.assertEqual(l.sys2x, 4) self.assertEqual(l.sys, 2)
def test_latency_get_sysNx(self): l = Latency() self.assertEqual(l.sys2x, 0) for n in [0, 1, 2, 4, 9]: l = Latency(sys=n) self.assertEqual(l.sys2x, 2 * n) self.assertEqual(l.sys3x, 3 * n)
def __init__(self, pads, *, ser_latency, des_latency, serdes_reset_cnt=0, **kwargs): super().__init__( pads, ser_latency=ser_latency + Latency(sys=Serializer.LATENCY), des_latency=des_latency + Latency(sys=Deserializer.LATENCY), **kwargs) self._out = self.out self.out = LPDDR4Output(nphases=self.nphases // 2, databits=self.databits) def ser(i, o): assert len(o) == len(i) // 2 self.submodules += Serializer( clkdiv="sys", clk="sys2x", i_dw=len(i), o_dw=len(o), i=i, o=o, reset_cnt=serdes_reset_cnt, ) def des(i, o): assert len(i) == len(o) // 2 self.submodules += Deserializer( clkdiv="sys", clk="sys2x", i_dw=len(i), o_dw=len(o), i=i, o=o, reset_cnt=serdes_reset_cnt, ) # handle ser/des for both the lists (like dq) and just Signal (like cs) def apply(fn, i, o): if not isinstance(i, list): i, o = [i], [o] for i_n, o_n in zip(i, o): fn(i=i_n, o=o_n) for name in vars(self.out): old = getattr(self._out, name) new = getattr(self.out, name) if name.endswith("_oe"): # OE signals need to be delayed self.comb += new.eq( delayed(self, old, cycles=Serializer.LATENCY)) elif name.endswith("_i"): # Deserialize inputs apply(des, o=old, i=new) else: # All other signals are outputs apply(ser, i=old, o=new)
def test_latency_error_on_get_fraction(self): l = Latency(sys=1, sys2x=1) with self.assertRaises(ValueError): l.sys # would have to be 1.5 l = Latency(sys3x=1, sys2x=1) for attr in "sys sys2x sys3x sys4x sys5x".split(): with self.assertRaises(ValueError): getattr(l, attr) l.sys6x # ok
def __init__(self, aligned_reset_zero=False, **kwargs): pads = LPDDR4SimulationPads() self.submodules += pads super().__init__(pads, ser_latency=Latency(sys=Serializer.LATENCY), des_latency=Latency(sys=Deserializer.LATENCY), phytype="LPDDR4SimPHY", **kwargs) # fake delays (make no nsense in simulation, but sdram.c expects them) self.settings.read_leveling = True self.settings.delays = 1 self._rdly_dq_rst = CSR() self._rdly_dq_inc = CSR() 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)), ]
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. # This allows us to use 8:1 DDR OSERDESE2/ISERDESE2 to (de-)serialize the data. super().__init__( pads, ser_latency=Latency( sys2x=1), # OSERDESE2 8:1 DDR (4 full-rate clocks) des_latency=Latency(sys2x=2), # ISERDESE2 NETWORKING phytype=self.__class__.__name__, serdes_reset_cnt=-1, **kwargs) self.settings.delays = 32 self.settings.write_leveling = True self.settings.write_latency_calibration = True self.settings.write_dq_dqs_training = True self.settings.read_leveling = True # Parameters ------------------------------------------------------------------------------- # Calculate value of taps needed to shift a signal by 90 degrees. # Using iodelay_clk_freq of 300MHz/400MHz is only valid for -3 and -2/2E speed grades. # Note: this should be named sys16x, but using sys8x due to a name hard-coded in BIOS assert iodelay_clk_freq in [200e6, 300e6, 400e6] iodelay_tap_average = 1 / (2 * 32 * iodelay_clk_freq) half_sys8x_taps = math.floor(self.tck / (4 * iodelay_tap_average)) assert half_sys8x_taps < 32, "Exceeded ODELAYE2 max value: {} >= 32".format( half_sys8x_taps) # Registers -------------------------------------------------------------------------------- self._half_sys8x_taps = CSRStorage(5, reset=half_sys8x_taps) # delay control self._rdly_dq_rst = CSR() self._rdly_dq_inc = CSR() self._rdly_dqs_rst = CSR() self._rdly_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() psync = PulseSynchronizer("sys", "sys2x") self.submodules += psync self.comb += [ psync.i.eq(i), o.eq(psync.o), ] return o 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) 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 # early. For this reason we add OE margin extending it 1 cycle before and 1 after. # For DQS we already have margin so there should be no need for this. def oe_delay_data(oe): oe_d = Signal() delay = TappedDelayLine(oe, 3) self.submodules += ClockDomainsRenamer("sys2x")(delay) self.comb += oe_d.eq(reduce(or_, delay.taps)) return oe_d def oe_delay_dqs(oe): delay = TappedDelayLine(oe, 2) self.submodules += ClockDomainsRenamer("sys2x")(delay) return delay.output # Serialization ---------------------------------------------------------------------------- # Clock 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 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=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() 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() 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): # DQS dqs_t = Signal() dqs_ser = Signal() 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=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" 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, tin=dqs_t, dinout=self.pads.dqs_p[byte], dinout_b=self.pads.dqs_n[byte], ) self.idelaye2( din=dqs_i, dout=dqs_i_dly, rst=self.get_rst(byte, rdly_dqs_rst), inc=self.get_inc(byte, rdly_dqs_inc), ) self.iserdese2_ddr( din=dqs_i_dly, dout=self.out.dqs_i[byte], clk="sys8x", ) # DMI for byte in range(self.databits // 8): dmi_t = Signal() dmi_ser = Signal() dmi_dly = Signal() self.oserdese2_ddr( din=self.out.dmi_o[byte], **(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", ) 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(), tin=dmi_t, dinout=self.pads.dmi[byte], ) # DQ for bit in range(self.databits): dq_t = Signal() dq_ser = Signal() dq_dly = Signal() dq_i = Signal() dq_i_dly = Signal() self.oserdese2_ddr( din=self.out.dq_o[bit], **(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", ) 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, dinout=self.pads.dq[bit], tin=dq_t) self.idelaye2(din=dq_i, dout=dq_i_dly, rst=self.get_rst(bit // 8, rdly_dq_rst), inc=self.get_inc(bit // 8, rdly_dq_inc)) self.iserdese2_ddr(din=dq_i_dly, dout=self.out.dq_i[bit], clk="sys8x")
def test_latency_set_sysNx(self): for n in [3, 6, 9]: l = Latency(sys3x=n) self.assertEqual(l.sys, n // 3)
def test_latency_get_sys(self): l = Latency() self.assertEqual(l.sys, 0) for n in [0, 1, 2, 4, 9]: l = Latency(sys=n) self.assertEqual(l.sys, n)
def test_latency_sys_init(self): for n in [0, 1, 2, 4, 9]: l = Latency(sys=n) self.assertEqual((l._sys.numerator, l._sys.denominator), (n, 1))
def test_latency_default_zero(self): l = Latency() self.assertEqual((l._sys.numerator, l._sys.denominator), (0, 1))