From b11b0c59b36581e551624b8c2d0703b28fa9535c Mon Sep 17 00:00:00 2001 From: rowanG077 Date: Fri, 14 Jul 2023 17:00:45 +0200 Subject: [PATCH] phy/ecp5rgmii.py: Add support for dynamic link speeds --- liteeth/phy/ecp5rgmii.py | 181 +++++++++++++++++++++++++++++++++++---- 1 file changed, 163 insertions(+), 18 deletions(-) diff --git a/liteeth/phy/ecp5rgmii.py b/liteeth/phy/ecp5rgmii.py index 4af82c54..aa1f70b7 100644 --- a/liteeth/phy/ecp5rgmii.py +++ b/liteeth/phy/ecp5rgmii.py @@ -17,10 +17,20 @@ from liteeth.common import * from liteeth.phy.common import * +# LiteEth PHY RGMII LINK Status -------------------------------------------------------------------- + +class LiteEthOneHotLinkSpeed(LiteXModule): + def __init__(self): + # Encode link status as one hot + self.link_10M = Signal() + self.link_100M = Signal() + self.link_1G = Signal() + self.tx_clk_en = Signal() + # LiteEth PHY RGMII TX ----------------------------------------------------------------------------- class LiteEthPHYRGMIITX(LiteXModule): - def __init__(self, pads): + def __init__(self, pads, link_speed=None): self.sink = sink = stream.Endpoint(eth_phy_description(8)) # # # @@ -28,11 +38,55 @@ def __init__(self, pads): tx_ctl_oddrx1f = Signal() tx_data_oddrx1f = Signal(4) + valid = Signal() + data = Signal(8) + ready = Signal() + + if link_speed is not None: + ready_next = Signal() + nibble_select = Signal() + + # If link speed is not 1G we need to take care + # that the DDR lines only transfer 4-bit of data at a time + # This means we need to provide back pressure on non-1G link speeds + self.comb += ready_next.eq(~ready & sink.valid) + + self.sync += If(link_speed.link_1G, + data.eq(sink.data) + ).Elif(nibble_select, + data.eq(Cat(sink.data[0:4], sink.data[0:4])) + ).Else( + data.eq(Cat(sink.data[4:8], sink.data[4:8])) + ) + + self.sync += valid.eq(sink.valid), + + self.sync += If(link_speed.link_1G, + ready.eq(1), + ).Elif(link_speed.tx_clk_en, + ready.eq(ready_next) + ).Else( + ready.eq(0) + ) + + self.sync += If(link_speed.tx_clk_en & sink.valid, + nibble_select.eq(~nibble_select) + ).Else( + nibble_select.eq(nibble_select) + ) + + else: + self.comb += [ + ready.eq(1), + valid.eq(sink.valid), + data.eq(sink.data) + ] + self.specials += [ DDROutput( clk = ClockSignal("eth_tx"), - i1 = sink.valid, - i2 = sink.valid, + i1 = valid, + i2 = valid, o = tx_ctl_oddrx1f, ), Instance("DELAYG", @@ -46,8 +100,8 @@ def __init__(self, pads): self.specials += [ DDROutput( clk = ClockSignal("eth_tx"), - i1 = sink.data[i], - i2 = sink.data[4+i], + i1 = data[i], + i2 = data[4+i], o = tx_data_oddrx1f[i], ), Instance("DELAYG", @@ -57,12 +111,12 @@ def __init__(self, pads): o_Z = pads.tx_data[i], ) ] - self.comb += sink.ready.eq(1) + self.comb += sink.ready.eq(ready) # LiteEth PHY RGMII RX ----------------------------------------------------------------------------- class LiteEthPHYRGMIIRX(LiteXModule): - def __init__(self, pads, rx_delay=2e-9, with_inband_status=True): + def __init__(self, pads, rx_delay=2e-9, with_inband_status=True, link_speed=None): self.source = source = stream.Endpoint(eth_phy_description(8)) if with_inband_status: @@ -123,19 +177,42 @@ def __init__(self, pads, rx_delay=2e-9, with_inband_status=True): o2 = rx_data[i+4], ) ] - self.sync += rx_data_reg.eq(rx_data) - rx_ctl_reg_d = Signal(2) - self.sync += rx_ctl_reg_d.eq(rx_ctl_reg) + # 1G has DDR, 100m and 10m are SDR on Rising edge only + valid_reg = Signal() + + if link_speed is not None: + self.sync += If(link_speed.link_1G, + rx_data_reg.eq(rx_data), + valid_reg.eq(rx_ctl[0]) + ).Elif(rx_ctl[0], + rx_data_reg.eq(Cat(rx_data[0:4], rx_data_reg[0:4])), + valid_reg.eq(~valid_reg) + ) + else: + self.sync += [ + rx_data_reg.eq(rx_data), + valid_reg.eq(rx_ctl[0]) + ] + + valid_reg_d = Signal() + self.sync += valid_reg_d.eq(valid_reg) last = Signal() - self.comb += last.eq(~rx_ctl_reg[0] & rx_ctl_reg_d[0]) + self.comb += last.eq(~valid_reg & valid_reg_d) self.sync += [ - source.valid.eq(rx_ctl_reg[0]), + source.valid.eq(valid_reg), source.data.eq(rx_data_reg) ] self.comb += source.last.eq(last) + if link_speed is not None: + self.sync += If(rx_ctl == 0b00, + link_speed.link_10M.eq(rx_data[1:3] == 0b00), + link_speed.link_100M.eq(rx_data[1:3] == 0b01), + link_speed.link_1G.eq(rx_data[1:3] == 0b10), + ) + if with_inband_status: self.sync += [ If(rx_ctl == 0b00, @@ -148,7 +225,7 @@ def __init__(self, pads, rx_delay=2e-9, with_inband_status=True): # LiteEth PHY RGMII CRG ---------------------------------------------------------------------------- class LiteEthPHYRGMIICRG(LiteXModule): - def __init__(self, clock_pads, pads, with_hw_init_reset, tx_delay=2e-9, tx_clk=None): + def __init__(self, clock_pads, pads, with_hw_init_reset, tx_delay=2e-9, tx_clk=None, link_speed=None): self._reset = CSRStorage() # # # @@ -167,12 +244,74 @@ def __init__(self, clock_pads, pads, with_hw_init_reset, tx_delay=2e-9, tx_clk=N tx_delay_taps = int(tx_delay/25e-12) # 25ps per tap assert tx_delay_taps < 128 + rising_edge = Signal() + falling_edge = Signal() + + # Without loop-clocking we need to divide the 125Mhz clock + # into either 25Mhz or 2.5Mhz. + if link_speed is not None and tx_clk is not None: + with_switch = Signal() + counter = Signal(6) + counter_switch = Signal(5) + counter_max = Signal(6) + + # clock divider for 100M and 10M + self.sync += If(link_speed.link_10M, + with_switch.eq(0), + counter_switch.eq(25), + counter_max.eq(49) + ).Elif(link_speed.link_100M, + with_switch.eq(1), + counter_switch.eq(2), + counter_max.eq(4) + ).Else( + with_switch.eq(1), + counter_switch.eq(0), + counter_max.eq(0) + ) + + self.sync += If(counter >= counter_max, + counter.eq(0) + ).Else( + counter.eq(counter + 1) + ) + + at_switch = Signal() + self.comb += at_switch.eq(counter == counter_switch) + + self.sync += If(with_switch & at_switch, + rising_edge.eq(1), + falling_edge.eq(0), + ).Elif(counter < counter_switch, + rising_edge.eq(1), + falling_edge.eq(1), + ).Else( + rising_edge.eq(0), + falling_edge.eq(0) + ) + + self.sync += link_speed.tx_clk_en.eq(at_switch) + + # When loop clocking we don't need to divide the tx clock since it will + # allready be at the correct speed. + elif link_speed is not None: + self.comb += [ + link_speed.tx_clk_en.eq(1), + rising_edge.eq(1), + falling_edge.eq(0) + ] + else: + self.comb += [ + rising_edge.eq(1), + falling_edge.eq(0) + ] + eth_tx_clk_o = Signal() self.specials += [ DDROutput( clk = ClockSignal("eth_tx"), - i1 = 1, - i2 = 0, + i1 = rising_edge, + i2 = falling_edge, o = eth_tx_clk_o, ), Instance("DELAYG", @@ -207,10 +346,16 @@ def __init__(self, clock_pads, pads, with_hw_init_reset=True, rx_delay = 2e-9, with_inband_status = True, tx_clk = None, + dyn_link_speed = False ): - self.crg = LiteEthPHYRGMIICRG(clock_pads, pads, with_hw_init_reset, tx_delay, tx_clk) - self.tx = ClockDomainsRenamer("eth_tx")(LiteEthPHYRGMIITX(pads)) - self.rx = ClockDomainsRenamer("eth_rx")(LiteEthPHYRGMIIRX(pads, rx_delay, with_inband_status)) + + link_speed = None + if dyn_link_speed: + link_speed = LiteEthOneHotLinkSpeed() + + self.rx = ClockDomainsRenamer("eth_rx")(LiteEthPHYRGMIIRX(pads, rx_delay, with_inband_status, link_speed)) + self.crg = LiteEthPHYRGMIICRG(clock_pads, pads, with_hw_init_reset, tx_delay, tx_clk, link_speed) + self.tx = ClockDomainsRenamer("eth_tx")(LiteEthPHYRGMIITX(pads, link_speed)) self.sink, self.source = self.tx.sink, self.rx.source if hasattr(pads, "mdc"):