From 8f51da9db528008944c6ec00bf0d531ccbc04759 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Tue, 29 Oct 2019 17:45:59 +0800 Subject: [PATCH 1/5] update .gitignore & setup.py --- .gitignore | 4 ++++ setup.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 610c399..11fffae 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ *.pyc /*.egg-info /.eggs + +# tests +*.vcd +*.gtkw diff --git a/setup.py b/setup.py index f83315c..5a30776 100644 --- a/setup.py +++ b/setup.py @@ -23,10 +23,11 @@ def local_scheme(version): #long_description="""TODO""", license="BSD", setup_requires=["setuptools_scm"], - install_requires=["nmigen"], + install_requires=["nmigen~=0.1.rc1"], packages=find_packages(), project_urls={ "Source Code": "https://github.com/m-labs/nmigen-stdio", "Bug Tracker": "https://github.com/m-labs/nmigen-stdio/issues", }, ) + From ce8775b0f86865c7f7cf6e6af3860226180a6067 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Wed, 30 Oct 2019 17:25:18 +0800 Subject: [PATCH 2/5] eth: WIP - RGMII interface --- nmigen_stdio/eth/__init__.py | 2 + nmigen_stdio/eth/endpoint.py | 28 ++++ nmigen_stdio/eth/rgmii.py | 293 +++++++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+) create mode 100644 nmigen_stdio/eth/__init__.py create mode 100644 nmigen_stdio/eth/endpoint.py create mode 100644 nmigen_stdio/eth/rgmii.py diff --git a/nmigen_stdio/eth/__init__.py b/nmigen_stdio/eth/__init__.py new file mode 100644 index 0000000..ae6b9c6 --- /dev/null +++ b/nmigen_stdio/eth/__init__.py @@ -0,0 +1,2 @@ +from .endpoint import * +from .rgmii import * diff --git a/nmigen_stdio/eth/endpoint.py b/nmigen_stdio/eth/endpoint.py new file mode 100644 index 0000000..63ffb7a --- /dev/null +++ b/nmigen_stdio/eth/endpoint.py @@ -0,0 +1,28 @@ +# TODO: Replace layout with 2 components: "endpoint" layout & "payload" layout +from nmigen import * +from nmigen.hdl.rec import Direction + + +__all__ = ["PHYEndpoint"] + + +class _EndpointBase(Record): + def init_record(self, payload_layout, param_layout=[]): + full_layout = [ + ("stb" , 1, Direction.FANOUT), # Externally driven: Data transfer is enabled? + ("rdy" , 1, Direction.FANIN), # Internally driven: Transfer can start? + ("eop" , 1, Direction.FANOUT), # Last data received? + ("payload", payload_layout), # nested layout of the payload + ("param" , param_layout) # nested layout of the params, if any + ] + Record.__init__(self, full_layout) + + +class PHYEndpoint(_EndpointBase): + def __init__(self, *, data_width): + payload_layout = [ + ("data" , data_width, Direction.FANOUT), + ("last_be", data_width//8, Direction.FANOUT), + ("error" , data_width//8, Direction.FANOUT) + ] + self.init_record(payload_layout) diff --git a/nmigen_stdio/eth/rgmii.py b/nmigen_stdio/eth/rgmii.py new file mode 100644 index 0000000..61d9901 --- /dev/null +++ b/nmigen_stdio/eth/rgmii.py @@ -0,0 +1,293 @@ +from nmigen import * +from nmigen.lib.io import Pin +from .endpoint import * + + +__all__ = ["EthRGMIIRX", "EthRGMIITX"] + + +class EthRGMIIRX(Elaboratable): + def __init__(self, *, data_width=8, device=None, pins=None): + self.data_width = data_width + + supported_devices = ["lattice_ecp5"] + if device is not None and device not in supported_devices: + raise ValueError("Invalid FPGA device name {!r}; must be one of {}" + .format(device, supported_devices)) + self._device = device + if self._device is not None and pins is None: + raise ValueError("Pins parameter is missing for this FPGA device {}" + .format(self._device)) + self._pins = pins + + self.rx_ctl = Signal() + self.rx_ctl_u = Signal.like(self.rx_ctl) + self.rx_data = Signal(data_width) + self.rx_data_u = Signal.like(self.rx_data) + + self.source = PHYEndpoint(data_width=data_width) + + + def _add_primitives(self, module): + """Add submodules as required by certain devices + """ + # Lattice ECP5: + # (with reference to: https://github.com/enjoy-digital/liteeth/blob/master/liteeth/phy/ecp5rgmii.py) + if self._device == "lattice_ecp5": + # Add a DELAYF and a IDDRX1F primitive on latching rx_ctl: + # * DELAYF: dynamic input delay module, setting DDR SCLK input edge aligned to delayed data output + # * IDDRX1F: generic DDR input 1(IN):2(OUT) gearbox + # (see Section 8 of FPGA-TN-02035-1.2, + # "ECP5 and ECP5-5G High-Speed I/O Interface") + rx_ctl_in = Signal() + module.submodules += [ + Instance("DELAYF", + p_DEL_MODE="SCLK_ALIGNED", + i_A=self._pins.rx_ctl, + i_LOADN=1, + i_MOVE=0, # set DIRECTION as constant + i_DIRECTION=0, # increase delay + o_Z=rx_ctl_in), + Instance("IDDRX1F", + i_D=rx_ctl_in, + i_SCLK=ClockSignal("eth_rx"), + i_RST=ResetSignal("eth_rx"), + o_Q0=self.rx_ctl_u) + ] + # Add a DELAYF and a IDDRX1F primitive on latching each of the 4 pins in rx_data: + rx_data_in = Signal(self.data_width//2) + for i in range(self.data_width//2): + module.submodules += [ + Instance("DELAYF", + p_DEL_MODE="SCLK_ALIGNED", + i_A=self._pins.rx_data[i], + i_LOADN=1, + i_MOVE=0, # set DIRECTION as constant + i_DIRECTION=0, # increase delay + o_Z=rx_data_in[i]), + Instance("IDDRX1F", + i_D=rx_data_in[i], + i_SCLK=ClockSignal("eth_rx"), + i_RST=ResetSignal("eth_rx"), + o_Q0=self.rx_data_u[i], + o_Q1=self.rx_data_u[i+4]) + ] + + + def elaborate(self, platform): + m = Module() + + if self._pins is not None: + self._add_primitives(m) + m.domains.eth_rx = cd_eth_rx = ClockDomain() + m.d.comb += cd_eth_rx.clk.eq(self._pins.rx_clk) + + # If the user doesn't give pins, create rx_ctl & rx_data DDR Pins + else: + rx_ctl_i = Signal() + rx_ctl_iddr = Pin(1, dir="i", xdr=2) + m.d.comb += self.rx_ctl_u.eq(rx_ctl_iddr.i0) + # TODO: add DDR logic (latch i0 on posedge, i1 on negedge) + rx_data_i = Signal(self.data_width//2) + rx_data_iddr = Pin(self.data_width//2, dir="i", xdr=2) + for i in range(self.data_width//2): + m.d.comb += [ + self.rx_data_u[i].eq(rx_data_iddr.i0[i]), + self.rx_data_u[i+4].eq(rx_data_iddr.i1[i]) + ] + # TODO: add DDR logic (latch i0 on posedge, i1 on negedge) + + source = self.source + + # + m.d.sync += [ + self.rx_ctl.eq(self.rx_ctl_u), + self.rx_data.eq(self.rx_data_u) + ] + + rx_ctl_d = Signal() + m.d.sync += [ + rx_ctl_d.eq(self.rx_ctl), + source.stb.eq(self.rx_ctl), + source.payload.data.eq( + Cat(self.rx_data[:self.data_width//2], + self.rx_data[self.data_width//2:]) + ) + ] + m.d.comb += source.eop.eq(~self.rx_ctl & rx_ctl_d) + + return m + + +class EthRGMIITX(Elaboratable): + def __init__(self, *, data_width=8, device=None, pins=None): + self.data_width = data_width + + supported_devices = ["lattice_ecp5"] + if device is not None and device not in supported_devices: + raise ValueError("Invalid FPGA device name {!r}; must be one of {}" + .format(device, supported_devices)) + self._device = device + if self._device is not None and pins is None: + raise ValueError("Pins parameter is missing for this FPGA device {}" + .format(self._device)) + self._pins = pins + + self.tx_ctl = Signal() + self.tx_ctl_u = Signal.like(self.tx_ctl) + self.tx_data = Signal(data_width) + self.tx_data_u = Signal.like(self.tx_data) + + self.sink = PHYEndpoint(data_width=data_width) + + + def _add_primitives(self, module): + """Add submodules as required by certain devices + """ + # Lattice ECP5: + # (with reference to: https://github.com/enjoy-digital/liteeth/blob/master/liteeth/phy/ecp5rgmii.py) + if self._device == "lattice_ecp5": + # Add a ODDRX1F and a DELAYF primitive on latching rx_ctl: + # * ODDRX1F: generic DDR output 2(IN):1(OUT) gearbox + # * DELAYF: dynamic output delay module, setting DDR SCLK input edge aligned to delayed data input + # (see Section 8 of FPGA-TN-02035-1.2, + # "ECP5 and ECP5-5G High-Speed I/O Interface") + tx_ctl_out = Signal() + module.submodules += [ + Instance("ODDRX1F", + i_D0=self.sink.stb, + i_D1=self.sink.stb, + i_SCLK=ClockSignal("eth_tx"), + i_RST=ResetSignal("eth_tx"), + o_Q=tx_ctl_out), + Instance("DELAYF", + p_DEL_MODE="SCLK_ALIGNED", + i_A=tx_ctl_out, + i_LOADN=1, + i_MOVE=0, # set DIRECTION as constant + i_DIRECTION=0, # increase delay + o_Z=self._pins.tx_ctl) + ] + # Add a ODDRX1F and a DELAYF primitive on latching each of the 4 pins in tx_data: + tx_data_out = Signal(self.data_width//2) + for i in range(self.data_width//2): + module.submodules += [ + Instance("ODDRX1F", + i_D0=self.sink.data[i], + i_D1=self.sink.data[4+i], + i_SCLK=ClockSignal("eth_tx"), + i_RST=ResetSignal("eth_tx"), + o_Q=tx_data_out[i]), + Instance("DELAYF", + p_DEL_MODE="SCLK_ALIGNED", + i_A=tx_data_out[i], + i_LOADN=1, + i_MOVE=0, # set DIRECTION as constant + i_DIRECTION=0, # increase delay + o_Z=self._pins.tx_data[i]) + ] + + + def elaborate(self, platform): + m = Module() + + if self._pins is not None: + self._add_primitives(m) + # If the user doesn't give pins, create rx_ctl & rx_data DDR Pins + else: + tx_ctl_o = Signal() + tx_ctl_oddr = Pin(1, dir="o", xdr=2) + m.d.comb += [ + tx_ctl_oddr.o0.eq(self.sink.stb), + tx_ctl_oddr.o1.eq(self.sink.stb) + ] + # TODO: add DDR logic (latch o0 on posedge, o1 on negedge) + tx_data_o = Signal(self.data_width//2) + tx_data_oddr = Pin(self.data_width//2, dir="o", xdr=2) + for i in range(self.data_width//2): + m.d.comb += [ + tx_data_oddr.o0[i].eq(self.tx_data_u[i]), + tx_data_oddr.o1[i].eq(self.tx_data_u[i+4]) + ] + # TODO: add DDR logic (latch o0 on posedge, o1 on negedge) + + # + sink = self.sink + m.d.comb += sink.rdy.eq(1) + + return m + + +class EthRGMII(Elaboratable): + def __init__(self, *, data_width=8, device=None, pins=None, **kwargs): + supported_devices = ["lattice_ecp5"] + if device is not None and device not in supported_devices: + raise ValueError("Invalid FPGA device name {!r}; must be one of {}" + .format(device, supported_devices)) + self._device = device + if self._device is not None and pins is None: + raise ValueError("Pins parameter is missing for this FPGA device {}" + .format(self._device)) + self._pins = pins + + self.rx = EthRGMIIRX(data_width=data_width, + device=device, + pins=pins, **kwargs) + self.tx = EthRGMIITX(data_width=data_width, + device=device, + pins=pins, **kwargs) + self.sink = None + self.source = None + + + def _add_primitives(self, module): + """Add submodules as required by certain devices + """ + # Lattice ECP5: + # (with reference to: https://github.com/enjoy-digital/liteeth/blob/master/liteeth/phy/ecp5rgmii.py) + if self._device == "lattice_ecp5": + # Add a ODDRX1F and a DELAYF primitive on latching tx_clk: + tx_clk_out = Signal() + module.submodules += [ + Instance("ODDRX1F", + i_D0=1, + i_D1=0, + i_SCLK=ClockSignal("eth_tx"), + i_RST=ResetSignal("eth_tx"), + o_Q=tx_clk_out), + Instance("DELAYF", + p_DEL_MODE="SCLK_ALIGNED", + i_A=tx_clk_out, + i_LOADN=1, + i_MOVE=0, # set DIRECTION as constant + i_DIRECTION=0, # increase delay + o_Z=self._pins.tx_clk) + ] + + + def elaborate(self, platform): + m = Module() + + m.submodules.rx = rx = self.rx + m.submodules.tx = tx = self.tx + self.source = rx.source + self.sink = tx.sink + + m.domains.eth_tx = cd_eth_tx = ClockDomain() + cd_eth_tx.clk.eq(ClockSignal("eth_rx")) + + if self._pins is not None: + self._add_primitives(m) + # If the user doesn't give pins, create rx_ctl & rx_data DDR Pins + else: + tx_clk_o = Signal() + tx_clk_oddr = Pin(1, dir="o", xdr=2) + m.d.comb += [ + tx_clk_oddr.o0.eq(1), + tx_clk_oddr.o1.eq(0) + ] + # TODO: add DDR logic (latch o0 on posedge, o1 on negedge) + + # TODO: implement reset + + return m From 4e796881f04e4c9ea6cda45b6b81add7fc376be0 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Mon, 6 Jan 2020 17:16:53 +0800 Subject: [PATCH 3/5] eth: remove device-dependent code; use cross-platform DDR on tx --- nmigen_stdio/eth/endpoint.py | 7 +- nmigen_stdio/eth/rgmii.py | 169 ++++++++++++----------------------- 2 files changed, 61 insertions(+), 115 deletions(-) diff --git a/nmigen_stdio/eth/endpoint.py b/nmigen_stdio/eth/endpoint.py index 63ffb7a..0c0e0f4 100644 --- a/nmigen_stdio/eth/endpoint.py +++ b/nmigen_stdio/eth/endpoint.py @@ -3,7 +3,7 @@ from nmigen.hdl.rec import Direction -__all__ = ["PHYEndpoint"] +__all__ = ["Endpoint", "PHYEndpoint"] class _EndpointBase(Record): @@ -18,6 +18,11 @@ def init_record(self, payload_layout, param_layout=[]): Record.__init__(self, full_layout) +class Endpoint(_EndpointBase): + def __init__(self, payload_layout, param_layout=[]): + self.init_record(payload_layout, param_layout) + + class PHYEndpoint(_EndpointBase): def __init__(self, *, data_width): payload_layout = [ diff --git a/nmigen_stdio/eth/rgmii.py b/nmigen_stdio/eth/rgmii.py index 61d9901..f1293d1 100644 --- a/nmigen_stdio/eth/rgmii.py +++ b/nmigen_stdio/eth/rgmii.py @@ -1,5 +1,8 @@ +import warnings + from nmigen import * from nmigen.lib.io import Pin + from .endpoint import * @@ -7,17 +10,8 @@ class EthRGMIIRX(Elaboratable): - def __init__(self, *, data_width=8, device=None, pins=None): - self.data_width = data_width - - supported_devices = ["lattice_ecp5"] - if device is not None and device not in supported_devices: - raise ValueError("Invalid FPGA device name {!r}; must be one of {}" - .format(device, supported_devices)) - self._device = device - if self._device is not None and pins is None: - raise ValueError("Pins parameter is missing for this FPGA device {}" - .format(self._device)) + def __init__(self, *, pins=None): + self.data_width = 8 self._pins = pins self.rx_ctl = Signal() @@ -73,76 +67,45 @@ def _add_primitives(self, module): o_Q1=self.rx_data_u[i+4]) ] + self.source = PHYEndpoint(data_width=self.data_width) def elaborate(self, platform): m = Module() - if self._pins is not None: - self._add_primitives(m) - m.domains.eth_rx = cd_eth_rx = ClockDomain() - m.d.comb += cd_eth_rx.clk.eq(self._pins.rx_clk) - - # If the user doesn't give pins, create rx_ctl & rx_data DDR Pins - else: - rx_ctl_i = Signal() - rx_ctl_iddr = Pin(1, dir="i", xdr=2) - m.d.comb += self.rx_ctl_u.eq(rx_ctl_iddr.i0) - # TODO: add DDR logic (latch i0 on posedge, i1 on negedge) - rx_data_i = Signal(self.data_width//2) - rx_data_iddr = Pin(self.data_width//2, dir="i", xdr=2) - for i in range(self.data_width//2): - m.d.comb += [ - self.rx_data_u[i].eq(rx_data_iddr.i0[i]), - self.rx_data_u[i+4].eq(rx_data_iddr.i1[i]) - ] - # TODO: add DDR logic (latch i0 on posedge, i1 on negedge) + if self._pins is None: + raise NotImplementedError("Currently does not support simulation") - source = self.source + rx_ctl = Signal() + rx_data = Signal(8) - # - m.d.sync += [ - self.rx_ctl.eq(self.rx_ctl_u), - self.rx_data.eq(self.rx_data_u) + m.d.eth_rx += [ + rx_ctl.eq(self.rx_ctl_u), + rx_data.eq(self.rx_data_u) ] rx_ctl_d = Signal() - m.d.sync += [ - rx_ctl_d.eq(self.rx_ctl), - source.stb.eq(self.rx_ctl), - source.payload.data.eq( - Cat(self.rx_data[:self.data_width//2], - self.rx_data[self.data_width//2:]) - ) + m.d.eth_rx += [ + rx_ctl_d.eq(rx_ctl), + self.source.stb.eq(rx_ctl), + self.source.payload.data.eq(rx_data) ] - m.d.comb += source.eop.eq(~self.rx_ctl & rx_ctl_d) + m.d.comb += self.source.eop.eq(~rx_ctl & rx_ctl_d) return m class EthRGMIITX(Elaboratable): - def __init__(self, *, data_width=8, device=None, pins=None): - self.data_width = data_width - - supported_devices = ["lattice_ecp5"] - if device is not None and device not in supported_devices: - raise ValueError("Invalid FPGA device name {!r}; must be one of {}" - .format(device, supported_devices)) - self._device = device - if self._device is not None and pins is None: - raise ValueError("Pins parameter is missing for this FPGA device {}" - .format(self._device)) - self._pins = pins - - self.tx_ctl = Signal() - self.tx_ctl_u = Signal.like(self.tx_ctl) - self.tx_data = Signal(data_width) - self.tx_data_u = Signal.like(self.tx_data) + def __init__(self, *, clk_domain=None, pins=None): + """A generic transmitter module for RGMII. - self.sink = PHYEndpoint(data_width=data_width) + Note that this module does not implement any DDR modules, but provides + :class:`nmigen.lib.io.Pins` objects representing the signals to be used as + the inputs of those DDR modules. Using these signals are recommended. - - def _add_primitives(self, module): - """Add submodules as required by certain devices + This module also has `o_clk`, representing the clock to be used as + the clock of the DDRs. However, the user should consider whether + using `o_clk` on all the TX-related DDRs is appropriate, especially because + some platforms require a skew between the TX clock and the TX data pins. """ # Lattice ECP5: # (with reference to: https://github.com/enjoy-digital/liteeth/blob/master/liteeth/phy/ecp5rgmii.py) @@ -187,47 +150,44 @@ def _add_primitives(self, module): o_Z=self._pins.tx_data[i]) ] + self.tx_clk_oddr = Pin(1, "o", xdr=2) + self.tx_ctl_oddr = Pin(1, "o", xdr=2) + self.tx_data_oddr = Pin(self.data_width//2, "o", xdr=2) + + self.sink = PHYEndpoint(data_width=self.data_width) def elaborate(self, platform): m = Module() - if self._pins is not None: - self._add_primitives(m) - # If the user doesn't give pins, create rx_ctl & rx_data DDR Pins - else: - tx_ctl_o = Signal() - tx_ctl_oddr = Pin(1, dir="o", xdr=2) - m.d.comb += [ - tx_ctl_oddr.o0.eq(self.sink.stb), - tx_ctl_oddr.o1.eq(self.sink.stb) - ] - # TODO: add DDR logic (latch o0 on posedge, o1 on negedge) - tx_data_o = Signal(self.data_width//2) - tx_data_oddr = Pin(self.data_width//2, dir="o", xdr=2) - for i in range(self.data_width//2): - m.d.comb += [ - tx_data_oddr.o0[i].eq(self.tx_data_u[i]), - tx_data_oddr.o1[i].eq(self.tx_data_u[i+4]) - ] - # TODO: add DDR logic (latch o0 on posedge, o1 on negedge) + if self._pins is None: + raise NotImplementedError("Currently does not support simulation") + + m.d.comb += [ + # DDR output for tx_clk + # (Note: often the device requires this output to be delayed + # from the CTL/DATA clocks; the user should implement + # a delay on the `o_clk` of this Pin, or + # a delay on the output of a DDR module they write) + self.tx_clk_oddr.o_clk.eq(self.o_clk), + self.tx_clk_oddr.o0.eq(1), + self.tx_clk_oddr.o1.eq(0), + # DDR output for tx_ctl + self.tx_ctl_oddr.o_clk.eq(self.o_clk), + self.tx_ctl_oddr.o0.eq(self.sink.stb), + self.tx_ctl_oddr.o1.eq(self.sink.stb), + # DDR output for tx_data[3:0] + self.tx_data_oddr.o_clk.eq(self.o_clk), + self.tx_data_oddr.o0.eq(self.sink.payload.data[:self.data_width//2]), + self.tx_data_oddr.o1.eq(self.sink.payload.data[self.data_width//2:]) + ] - # - sink = self.sink - m.d.comb += sink.rdy.eq(1) + m.d.comb += self.sink.rdy.eq(1) return m class EthRGMII(Elaboratable): - def __init__(self, *, data_width=8, device=None, pins=None, **kwargs): - supported_devices = ["lattice_ecp5"] - if device is not None and device not in supported_devices: - raise ValueError("Invalid FPGA device name {!r}; must be one of {}" - .format(device, supported_devices)) - self._device = device - if self._device is not None and pins is None: - raise ValueError("Pins parameter is missing for this FPGA device {}" - .format(self._device)) + def __init__(self, *, data_width=8, pins=None, **kwargs): self._pins = pins self.rx = EthRGMIIRX(data_width=data_width, @@ -270,24 +230,5 @@ def elaborate(self, platform): m.submodules.rx = rx = self.rx m.submodules.tx = tx = self.tx - self.source = rx.source - self.sink = tx.sink - - m.domains.eth_tx = cd_eth_tx = ClockDomain() - cd_eth_tx.clk.eq(ClockSignal("eth_rx")) - - if self._pins is not None: - self._add_primitives(m) - # If the user doesn't give pins, create rx_ctl & rx_data DDR Pins - else: - tx_clk_o = Signal() - tx_clk_oddr = Pin(1, dir="o", xdr=2) - m.d.comb += [ - tx_clk_oddr.o0.eq(1), - tx_clk_oddr.o1.eq(0) - ] - # TODO: add DDR logic (latch o0 on posedge, o1 on negedge) - - # TODO: implement reset return m From b650bb3765b2c8a8dc43f8068000a1923c69c2d6 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Thu, 30 Jan 2020 15:49:25 +0800 Subject: [PATCH 4/5] eth: use cross-platform DDR on rx; improve docstrings --- nmigen_stdio/eth/endpoint.py | 23 ++- nmigen_stdio/eth/rgmii.py | 325 ++++++++++++++++++----------------- 2 files changed, 187 insertions(+), 161 deletions(-) diff --git a/nmigen_stdio/eth/endpoint.py b/nmigen_stdio/eth/endpoint.py index 0c0e0f4..78c9052 100644 --- a/nmigen_stdio/eth/endpoint.py +++ b/nmigen_stdio/eth/endpoint.py @@ -9,8 +9,8 @@ class _EndpointBase(Record): def init_record(self, payload_layout, param_layout=[]): full_layout = [ - ("stb" , 1, Direction.FANOUT), # Externally driven: Data transfer is enabled? - ("rdy" , 1, Direction.FANIN), # Internally driven: Transfer can start? + ("stb" , 1, Direction.FANOUT), # Data transfer is valid? + ("rdy" , 1, Direction.FANIN), # Transfer can start? ("eop" , 1, Direction.FANOUT), # Last data received? ("payload", payload_layout), # nested layout of the payload ("param" , param_layout) # nested layout of the params, if any @@ -24,6 +24,25 @@ def __init__(self, payload_layout, param_layout=[]): class PHYEndpoint(_EndpointBase): + """A Record representation of an endpoint accepting an Ethernet packet. + + Attributes + ---------- + stb : Signal() + Output to indicate whether or not the data transfer is valid. + rdy : Signal() + Input to indicate whether or not the endpoint is ready for RX/TX. + eop : Signal() + Output to indicate whether or not this is the last packet. + payload : :class:`Record` + A Record representation of the packet payload. See below for its attributes. + payload.data : Signal(data_width) + Packet data. + payload.last_be : Signal(data_width//2) + Last word byte enable. + payload.error : Signal(data_width//2): + Packet error. + """ def __init__(self, *, data_width): payload_layout = [ ("data" , data_width, Direction.FANOUT), diff --git a/nmigen_stdio/eth/rgmii.py b/nmigen_stdio/eth/rgmii.py index f1293d1..ce31c5b 100644 --- a/nmigen_stdio/eth/rgmii.py +++ b/nmigen_stdio/eth/rgmii.py @@ -10,81 +10,92 @@ class EthRGMIIRX(Elaboratable): - def __init__(self, *, pins=None): + """A generic receiver module for RGMII. + + Note that this module does not implement any DDR modules, but provides + :class:`nmigen.lib.io.Pins` objects representing the signals to be used as + the outputs of those DDR modules. Using these signals are recommended. + + This module also has `i_clk`, representing the clock to be used as + the clock of the DDRs. However, the user should consider whether + using `i_clk` on all the RX-related DDRs is appropriate, especially because + some platforms require a skew between the RX clock and the RX data pins. + + Parameters + ---------- + clk_domain : str or None + Clock domain to be used as the DDR clock. Optional and defaults to None, + meaning the user must connect the DDR clock signal to an external signal. + + Attributes + ---------- + data_width : int + Number of bits per Ethernet packet. Equals to 8. + source : :class:`PHYEndpoint` + Representation of the status and data of the packet received. See :class:`PHYEndPoint`. + i_clk : Signal() + Clock signal used as input for the DDR outputs. + If clk_domain has been specified, it is the clock signal of that domain. + Otherwise, it can be externally driven by other signals. + rx_clk_oddr : :class:`Pin` + DDR input representation for the RX CLK. See below for its attributes. + rx_ctl_oddr : :class:`Pin` + DDR input representation for the RX CTL. See below for its attributes. + rx_data_oddr : :class:`Pin` + DDR input representation for the RX DATA. See below for its attributes. + Its i0 represents the first 4 bits, and i1 represents the last 4 bits. + + Each of the DDR inputs above contains these attributes: + i_clk : Signal() + Clock signal for the DDR. Internally driven. + i0 : Signal + Input value at the rising edge of i_clk. Internally driven. + i1 : Signal + Input value at the falling edge of i_clk. Internally driven. + """ + def __init__(self, *, clk_domain=None): self.data_width = 8 - self._pins = pins - - self.rx_ctl = Signal() - self.rx_ctl_u = Signal.like(self.rx_ctl) - self.rx_data = Signal(data_width) - self.rx_data_u = Signal.like(self.rx_data) - - self.source = PHYEndpoint(data_width=data_width) - - - def _add_primitives(self, module): - """Add submodules as required by certain devices - """ - # Lattice ECP5: - # (with reference to: https://github.com/enjoy-digital/liteeth/blob/master/liteeth/phy/ecp5rgmii.py) - if self._device == "lattice_ecp5": - # Add a DELAYF and a IDDRX1F primitive on latching rx_ctl: - # * DELAYF: dynamic input delay module, setting DDR SCLK input edge aligned to delayed data output - # * IDDRX1F: generic DDR input 1(IN):2(OUT) gearbox - # (see Section 8 of FPGA-TN-02035-1.2, - # "ECP5 and ECP5-5G High-Speed I/O Interface") - rx_ctl_in = Signal() - module.submodules += [ - Instance("DELAYF", - p_DEL_MODE="SCLK_ALIGNED", - i_A=self._pins.rx_ctl, - i_LOADN=1, - i_MOVE=0, # set DIRECTION as constant - i_DIRECTION=0, # increase delay - o_Z=rx_ctl_in), - Instance("IDDRX1F", - i_D=rx_ctl_in, - i_SCLK=ClockSignal("eth_rx"), - i_RST=ResetSignal("eth_rx"), - o_Q0=self.rx_ctl_u) - ] - # Add a DELAYF and a IDDRX1F primitive on latching each of the 4 pins in rx_data: - rx_data_in = Signal(self.data_width//2) - for i in range(self.data_width//2): - module.submodules += [ - Instance("DELAYF", - p_DEL_MODE="SCLK_ALIGNED", - i_A=self._pins.rx_data[i], - i_LOADN=1, - i_MOVE=0, # set DIRECTION as constant - i_DIRECTION=0, # increase delay - o_Z=rx_data_in[i]), - Instance("IDDRX1F", - i_D=rx_data_in[i], - i_SCLK=ClockSignal("eth_rx"), - i_RST=ResetSignal("eth_rx"), - o_Q0=self.rx_data_u[i], - o_Q1=self.rx_data_u[i+4]) - ] + if clk_domain: + # If a clock domain name is given, use its clock signal for the DDR; + # This also assumes that the clock domain exists + if isinstance(clk_domain, str): + self.i_clk = ClockSignal(clk_domain) + self.clk_domain = clk_domain + else: + raise TypeError("Clock domain must be a string, not {!r}" + .format(clk_domain)) + else: + # Otherwise, create a signal ready for connections from the outside + self.i_clk = Signal() + warnings.warn("Since no clock domain is specified, " + "to use the DDR pins provided by this module, " + "you must connect a clock signal to `i_clk`.") + + self.rx_ctl_iddr = Pin(1, "i", xdr=2) + self.rx_data_iddr = Pin(self.data_width//2, "i", xdr=2) self.source = PHYEndpoint(data_width=self.data_width) def elaborate(self, platform): m = Module() - if self._pins is None: - raise NotImplementedError("Currently does not support simulation") - rx_ctl = Signal() rx_data = Signal(8) - m.d.eth_rx += [ - rx_ctl.eq(self.rx_ctl_u), - rx_data.eq(self.rx_data_u) + m.d.comb += [ + # DDR input clocks for rx_ctl and rx_data[3:0] + self.rx_ctl_iddr.i_clk.eq(self.i_clk), + self.rx_data_iddr.i_clk.eq(self.i_clk) + ] + + m.d[self.clk_domain] += [ + rx_ctl.eq(self.rx_ctl_iddr.i0), + rx_data.eq(Cat(self.rx_data_iddr.i0, + self.rx_data_iddr.i1)) ] rx_ctl_d = Signal() - m.d.eth_rx += [ + m.d[self.clk_domain] += [ rx_ctl_d.eq(rx_ctl), self.source.stb.eq(rx_ctl), self.source.payload.data.eq(rx_data) @@ -95,60 +106,64 @@ def elaborate(self, platform): class EthRGMIITX(Elaboratable): - def __init__(self, *, clk_domain=None, pins=None): - """A generic transmitter module for RGMII. - - Note that this module does not implement any DDR modules, but provides - :class:`nmigen.lib.io.Pins` objects representing the signals to be used as - the inputs of those DDR modules. Using these signals are recommended. - - This module also has `o_clk`, representing the clock to be used as - the clock of the DDRs. However, the user should consider whether - using `o_clk` on all the TX-related DDRs is appropriate, especially because - some platforms require a skew between the TX clock and the TX data pins. - """ - # Lattice ECP5: - # (with reference to: https://github.com/enjoy-digital/liteeth/blob/master/liteeth/phy/ecp5rgmii.py) - if self._device == "lattice_ecp5": - # Add a ODDRX1F and a DELAYF primitive on latching rx_ctl: - # * ODDRX1F: generic DDR output 2(IN):1(OUT) gearbox - # * DELAYF: dynamic output delay module, setting DDR SCLK input edge aligned to delayed data input - # (see Section 8 of FPGA-TN-02035-1.2, - # "ECP5 and ECP5-5G High-Speed I/O Interface") - tx_ctl_out = Signal() - module.submodules += [ - Instance("ODDRX1F", - i_D0=self.sink.stb, - i_D1=self.sink.stb, - i_SCLK=ClockSignal("eth_tx"), - i_RST=ResetSignal("eth_tx"), - o_Q=tx_ctl_out), - Instance("DELAYF", - p_DEL_MODE="SCLK_ALIGNED", - i_A=tx_ctl_out, - i_LOADN=1, - i_MOVE=0, # set DIRECTION as constant - i_DIRECTION=0, # increase delay - o_Z=self._pins.tx_ctl) - ] - # Add a ODDRX1F and a DELAYF primitive on latching each of the 4 pins in tx_data: - tx_data_out = Signal(self.data_width//2) - for i in range(self.data_width//2): - module.submodules += [ - Instance("ODDRX1F", - i_D0=self.sink.data[i], - i_D1=self.sink.data[4+i], - i_SCLK=ClockSignal("eth_tx"), - i_RST=ResetSignal("eth_tx"), - o_Q=tx_data_out[i]), - Instance("DELAYF", - p_DEL_MODE="SCLK_ALIGNED", - i_A=tx_data_out[i], - i_LOADN=1, - i_MOVE=0, # set DIRECTION as constant - i_DIRECTION=0, # increase delay - o_Z=self._pins.tx_data[i]) - ] + """A generic transmitter module for RGMII. + + Note that this module does not implement any DDR modules, but provides + :class:`nmigen.lib.io.Pins` objects representing the signals to be used as + the inputs of those DDR modules. Using these signals are recommended. + + This module also has `o_clk`, representing the clock to be used as + the clock of the DDRs. However, the user should consider whether + using `o_clk` on all the TX-related DDRs is appropriate, especially because + some platforms require a skew between the TX clock and the TX data pins. + + Parameters + ---------- + clk_domain : str or None + Clock domain to be used as the DDR clock. Optional and defaults to None, + meaning the user must connect the DDR clock signal to an external signal. + + Attributes + ---------- + data_width : int + Number of bits per Ethernet packet. Equals to 8. + sink : :class:`PHYEndpoint` + Representation of the status and data of the packet to transmit. See :class:`PHYEndPoint`. + o_clk : Signal() + Clock signal used as input for the DDR outputs. + If clk_domain has been specified, it is the clock signal of that domain. + Otherwise, it can be externally driven by other signals. + tx_clk_oddr : :class:`Pin` + DDR output representation for the TX CLK. See below for its attributes. + tx_ctl_oddr : :class:`Pin` + DDR output representation for the TX CTL. See below for its attributes. + tx_data_oddr : :class:`Pin` + DDR output representation for the TX DATA. See below for its attributes. + Its o0 represents the first 4 bits, and o1 represents the last 4 bits. + + Each of the DDR outputs above contains these attributes: + o_clk : Signal() + Clock signal for the DDR. Internally driven. + o0 : Signal + Value to output at the rising edge of o_clk. Internally driven. + o1 : Signal + Value to output at the falling edge of o_clk. Internally driven. + """ + def __init__(self, *, clk_domain=None): + self.data_width = 8 + if clk_domain: + # If a clock domain name is given, use its clock signal for the DDR + if isinstance(clk_domain, str): + self.o_clk = ClockSignal(clk_domain) + else: + raise TypeError("Clock domain must be a string, not {!r}" + .format(clk_domain)) + else: + # Otherwise, create a signal ready for connections from the outside + self.o_clk = Signal() + warnings.warn("Since no clock domain is specified, " + "to use the DDR pins provided by this module, " + "you must connect a clock signal to `o_clk`.") self.tx_clk_oddr = Pin(1, "o", xdr=2) self.tx_ctl_oddr = Pin(1, "o", xdr=2) @@ -159,9 +174,6 @@ def __init__(self, *, clk_domain=None, pins=None): def elaborate(self, platform): m = Module() - if self._pins is None: - raise NotImplementedError("Currently does not support simulation") - m.d.comb += [ # DDR output for tx_clk # (Note: often the device requires this output to be delayed @@ -187,48 +199,43 @@ def elaborate(self, platform): class EthRGMII(Elaboratable): - def __init__(self, *, data_width=8, pins=None, **kwargs): - self._pins = pins - - self.rx = EthRGMIIRX(data_width=data_width, - device=device, - pins=pins, **kwargs) - self.tx = EthRGMIITX(data_width=data_width, - device=device, - pins=pins, **kwargs) - self.sink = None - self.source = None - - - def _add_primitives(self, module): - """Add submodules as required by certain devices - """ - # Lattice ECP5: - # (with reference to: https://github.com/enjoy-digital/liteeth/blob/master/liteeth/phy/ecp5rgmii.py) - if self._device == "lattice_ecp5": - # Add a ODDRX1F and a DELAYF primitive on latching tx_clk: - tx_clk_out = Signal() - module.submodules += [ - Instance("ODDRX1F", - i_D0=1, - i_D1=0, - i_SCLK=ClockSignal("eth_tx"), - i_RST=ResetSignal("eth_tx"), - o_Q=tx_clk_out), - Instance("DELAYF", - p_DEL_MODE="SCLK_ALIGNED", - i_A=tx_clk_out, - i_LOADN=1, - i_MOVE=0, # set DIRECTION as constant - i_DIRECTION=0, # increase delay - o_Z=self._pins.tx_clk) - ] - + """A generic transmitter/receiver module for RGMII. + + Note that this module does not implement any DDR modules, but its TX and RX modules + provide :class:`nmigen.lib.io.Pins` objects representing the signals to be used as + the inputs or outputs of those DDR modules. Using these signals are recommended. + + Parameters + ---------- + rx_clk_domain : str or None + Clock domain to be used as the DDR clock for RX. Optional and defaults to None, + meaning the user must connect the DDR clock signal to an external signal. + tx_clk_domain : str or None + Clock domain to be used as the DDR clock for TX. Optional and defaults to None, + meaning the user must connect the DDR clock signal to an external signal. + + Attributes + ---------- + rx : EthRGMIIRX + RGMII receiver. See :class:`EthRGMIIRX`. + tx : EthRGMIITX + RGMII transmitter. See :class:`EthRGMIITX`. + source : :class:`PHYEndpoint` + Representation of the status and data of the packet received by the RX. See :class:`PHYEndPoint`. + sink : :class:`PHYEndpoint` + Representation of the status and data of the packet to transmit by the TX. See :class:`PHYEndPoint`. + + """ + def __init__(self, *args, rx_clk_domain=None, tx_clk_domain=None, **kwargs): + self.rx = EthRGMIIRX(*args, clk_domain=rx_clk_domain, **kwargs) + self.tx = EthRGMIITX(*args, clk_domain=tx_clk_domain, **kwargs) + self.source = self.rx.source + self.sink = self.tx.sink def elaborate(self, platform): m = Module() - - m.submodules.rx = rx = self.rx - m.submodules.tx = tx = self.tx + + m.submodules.rx = self.rx = self.rx + m.submodules.tx = self.tx = self.tx return m From 7857b81cf9898e0232832e0a409bb7f63e625dfa Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Fri, 31 Jan 2020 14:51:37 +0800 Subject: [PATCH 5/5] eth: fix styling --- nmigen_stdio/eth/endpoint.py | 74 ++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/nmigen_stdio/eth/endpoint.py b/nmigen_stdio/eth/endpoint.py index 78c9052..62f4e5c 100644 --- a/nmigen_stdio/eth/endpoint.py +++ b/nmigen_stdio/eth/endpoint.py @@ -7,46 +7,46 @@ class _EndpointBase(Record): - def init_record(self, payload_layout, param_layout=[]): - full_layout = [ - ("stb" , 1, Direction.FANOUT), # Data transfer is valid? - ("rdy" , 1, Direction.FANIN), # Transfer can start? - ("eop" , 1, Direction.FANOUT), # Last data received? - ("payload", payload_layout), # nested layout of the payload - ("param" , param_layout) # nested layout of the params, if any - ] - Record.__init__(self, full_layout) + def init_record(self, payload_layout, param_layout=[]): + full_layout = [ + ("stb" , 1, Direction.FANOUT), # Data transfer is valid? + ("rdy" , 1, Direction.FANIN), # Transfer can start? + ("eop" , 1, Direction.FANOUT), # Last data received? + ("payload", payload_layout), # nested layout of the payload + ("param" , param_layout) # nested layout of the params, if any + ] + Record.__init__(self, full_layout) class Endpoint(_EndpointBase): - def __init__(self, payload_layout, param_layout=[]): - self.init_record(payload_layout, param_layout) + def __init__(self, payload_layout, param_layout=[]): + self.init_record(payload_layout, param_layout) class PHYEndpoint(_EndpointBase): - """A Record representation of an endpoint accepting an Ethernet packet. - - Attributes - ---------- - stb : Signal() - Output to indicate whether or not the data transfer is valid. - rdy : Signal() - Input to indicate whether or not the endpoint is ready for RX/TX. - eop : Signal() - Output to indicate whether or not this is the last packet. - payload : :class:`Record` - A Record representation of the packet payload. See below for its attributes. - payload.data : Signal(data_width) - Packet data. - payload.last_be : Signal(data_width//2) - Last word byte enable. - payload.error : Signal(data_width//2): - Packet error. - """ - def __init__(self, *, data_width): - payload_layout = [ - ("data" , data_width, Direction.FANOUT), - ("last_be", data_width//8, Direction.FANOUT), - ("error" , data_width//8, Direction.FANOUT) - ] - self.init_record(payload_layout) + """A Record representation of an endpoint accepting an Ethernet packet. + + Attributes + ---------- + stb : Signal() + Output to indicate whether or not the data transfer is valid. + rdy : Signal() + Input to indicate whether or not the endpoint is ready for RX/TX. + eop : Signal() + Output to indicate whether or not this is the last packet. + payload : :class:`Record` + A Record representation of the packet payload. See below for its attributes. + payload.data : Signal(data_width) + Packet data. + payload.last_be : Signal(data_width//2) + Last word byte enable. + payload.error : Signal(data_width//2): + Packet error. + """ + def __init__(self, *, data_width): + payload_layout = [ + ("data" , data_width, Direction.FANOUT), + ("last_be", data_width//8, Direction.FANOUT), + ("error" , data_width//8, Direction.FANOUT) + ] + self.init_record(payload_layout)