Skip to content

Commit

Permalink
Merge pull request #2095 from trabucayre/zynqmp_ethernet_sgmii
Browse files Browse the repository at this point in the history
soc/cores/cpu/zynqmp/core.py: added support for SGMII via PL with optional PTP support
  • Loading branch information
enjoy-digital authored Oct 9, 2024
2 parents 34b98ab + ddb8d16 commit 01c7a78
Showing 1 changed file with 180 additions and 15 deletions.
195 changes: 180 additions & 15 deletions litex/soc/cores/cpu/zynqmp/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

from migen import *

from litex.gen import *
from litex.gen import *

from litex.soc.cores.cpu import CPU
from litex.soc.interconnect import axi
from litex.soc.cores.cpu import CPU
from litex.soc.interconnect import axi
from litex.soc.interconnect.csr import *


# Zynq MP ------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -50,7 +51,7 @@ def __init__(self, platform, variant, *args, **kwargs):
self.periph_buses = [] # Peripheral buses (Connected to main SoC's bus).
self.memory_buses = [] # Memory buses (Connected directly to LiteDRAM).
self.axi_gp_masters = [None] * 3 # General Purpose AXI Masters.
self.gem_mac = [] # GEM MAC reserved ports.
self.gem_mac = {} # GEM MAC reserved ports.
self.i2c_use = [] # I2c reserved ports.
self.uart_use = [] # UART reserved ports.
self.can_use = [] # CAN reserved/used ports.
Expand Down Expand Up @@ -156,15 +157,39 @@ def add_axi_gp_master(self, n=0, data_width=32):

return axi_gpn

def add_ethernet(self, n=0, pads=None, if_type="gmii"):
"""
Enable GEMx peripheral.
==========
n: int
GEM id (0, 1, 2, 3)
pads:
Physicals pads.
clock_pads:
Physicals tx/rx clock pads (required for SGMII).
if_type: str
Physical ethernet interface (gmii, rgmii, sgmii).
reset: Signal
Reset signal between PS and converter (required for SGMII).
with_ptp: bool
Enable PTP support.
"""
def add_ethernet(self, n=0,
pads = None,
clock_pads = None,
if_type = "gmii",
reset = None,
with_ptp = False):
assert n < 3 and not n in self.gem_mac
assert pads is not None
assert if_type in ["gmii", "rgmii", "sgmii"]

# psu configuration
self.config[f"PSU__ENET{n}__PERIPHERAL__ENABLE"] = 1
self.config[f"PSU__ENET{n}__PERIPHERAL__IO"] = "EMIO"
self.config[f"PSU__ENET{n}__GRP_MDIO__ENABLE"] = 1
self.config[f"PSU__ENET{n}__GRP_MDIO__IO"] = "EMIO"
if with_ptp:
self.config[f"PSU__ENET{n}__PTP__ENABLE"] = 1

# psu GMII connection
gmii_rx_clk = Signal()
Expand Down Expand Up @@ -214,7 +239,7 @@ def add_ethernet(self, n=0, pads=None, if_type="gmii"):
i_T = mdio_t,
io_IO = pads.mdio
)
else:
elif if_type == "rgmii":
phys_mdio_i = Signal()
phys_mdio_o = Signal()
phys_mdio_t = Signal()
Expand Down Expand Up @@ -272,9 +297,137 @@ def add_ethernet(self, n=0, pads=None, if_type="gmii"):
o_duplex_status = Open(),
o_speed_mode = Open(2),
)
self.specials += Instance(f"gem{n}", **mac_params)
self.gem_mac[n] = "rgmii"
else:
pwrgood = Signal()
status = Signal(16)
reset_done = Signal(1)
pma_reset_out = Signal(1)
mmcm_locked_out = Signal(1)
gem_reset = Signal()
self.cd_sl_clk = ClockDomain("sl_clk")


sgmii_control = CSRStorage(fields=[
CSRField("reset", size=1, reset=0, values=[
("``0b0``", "Normal operations."),
("``0b1``", "Reset mode."),
]),
CSRField("tsu_inc_ctrl", size=2, reset=3, values=[
("``0b00``", "Timer register increments based on the gem_tsu_ms value."),
("``0b01``", "Timer register increments by an additional nanosecond."),
("``0b10``", "Timer register increments by one nanosecond fewer."),
("``0b11``", "Timer register increments as normal."),
])
])
setattr(self, f"sgmii_control{n}", sgmii_control)

sgmii_status = CSRStatus(fields=[
CSRField("status", size=16, offset=0),
CSRField("pwrgood", size=1, offset=16),
CSRField("reset_done", size=1, offset=17),
CSRField("pma_reset_out", size=1, offset=18),
CSRField("mmcm_locked_out", size=1, offset=19),
])
setattr(self, f"sgmii_status{n}", sgmii_status)

if reset is not None:
self.comb += gem_reset.eq(ResetSignal("sys") | sgmii_control.fields.reset | reset)
else:
self.comb += gem_reset.eq(ResetSignal("sys") | sgmii_control.fields.reset)

self.comb += [
sgmii_status.fields.status.eq( status),
sgmii_status.fields.pwrgood.eq( pwrgood),
sgmii_status.fields.reset_done.eq( reset_done),
sgmii_status.fields.pma_reset_out.eq( pma_reset_out),
sgmii_status.fields.mmcm_locked_out.eq(mmcm_locked_out),
]

# FIXME: needs to add another PSU->FPGA Clock @50MHz
from migen.genlib.resetsync import AsyncResetSynchronizer
self.specials += [
Instance("BUFGCE_DIV",
p_BUFGCE_DIVIDE = 2,
i_CE = 1,
i_I = ClockSignal("sys"),
o_O = ClockSignal("sl_clk"),
),
AsyncResetSynchronizer(self.cd_sl_clk, ResetSignal("sys")),
]

mac_params = dict(
# Clk/Reset
i_independent_clock_bufg = ClockSignal("sl_clk"),
i_reset = gem_reset, # Asynchronous reset for entire core
o_userclk_out = Open(),
o_userclk2_out = Open(),
o_rxuserclk_out = Open(),
o_rxuserclk2_out = Open(),

# Transceiver Interface: Clk
i_gtrefclk_p = clock_pads.p,
i_gtrefclk_n = clock_pads.n,
o_gtrefclk_out = Open(),
o_resetdone = reset_done, # The GT transceiver has completed its reset cycle

# SGMII
o_txp = pads.txp, # Differential +ve of serial transmission from PMA to PMD.
o_txn = pads.txn, # Differential -ve of serial transmission from PMA to PMD.
i_rxp = pads.rxp, # Differential +ve for serial reception from PMD to PMA.
i_rxn = pads.rxn, # Differential -ve for serial reception from PMD to PMA.
o_pma_reset_out = pma_reset_out, # transceiver PMA reset signal
o_mmcm_locked_out = mmcm_locked_out, # MMCM Locked

# PS GEM: GMII
o_sgmii_clk_r = Open(),
o_sgmii_clk_f = Open(),
o_gmii_txclk = gmii_tx_clk,
o_gmii_rxclk = gmii_rx_clk,
i_gmii_txd = gmii_txd, # Transmit data from client MAC.
i_gmii_tx_en = gmii_tx_en, # Transmit control signal from client MAC.
i_gmii_tx_er = gmii_tx_er, # Transmit control signal from client MAC.
o_gmii_rxd = gmii_rxd, # Received Data to client MAC.
o_gmii_rx_dv = gmii_rx_dv, # Received control signal to client MAC.
o_gmii_rx_er = gmii_rx_er, # Received control signal to client MAC.
o_gmii_isolate = Open(), # Tristate control to electrically isolate GMII.

# PS GEM: MDIO
i_mdc = mdio_mdc, # Management Data Clock
i_mdio_i = mdio_o, # Management Data In
o_mdio_o = mdio_i, # Management Data Out
o_mdio_t = Open(), # Management Data Tristate

# Configuration
i_phyaddr = Constant(9, 5),
i_configuration_vector = Constant(0, 5), # Alternative to MDIO interface.
i_configuration_valid = Constant(0, 1), # Validation signal for Config vector
o_an_interrupt = Open(), # Interrupt to processor to signal that Auto-Negotiation has completed
i_an_adv_config_vector = Constant(55297, 16), # Alternate interface to program REG4 (AN ADV)
i_an_adv_config_val = Constant(0, 1), # Validation signal for AN ADV
i_an_restart_config = Constant(0, 1), # Alternate signal to modify AN restart bit in REG0
o_status_vector = status, # Core status.

o_gtpowergood = pwrgood,
i_signal_detect = Constant(1, 1), # Input from PMD to indicate presence of optical input.
)

if with_ptp:
tsu_inc_ctrl = Signal(2)
tsu_timer_cnt = Signal(94)
self.cpu_params.update({
# TSU
f"o_emio_enet{n}_enet_tsu_timer_cnt" : tsu_timer_cnt,
f"i_emio_enet{n}_tsu_inc_ctrl" : tsu_inc_ctrl,
})
self.comb += [
tsu_inc_ctrl.eq(sgmii_control.fields.tsu_inc_ctrl),
self.pps[n].eq( tsu_timer_cnt[45])
]

self.specials += Instance(f"gem{n}", **mac_params)
self.gem_mac.append(n)
self.gem_mac[n] = "sgmii"

def add_i2c(self, n, pads):
assert n < 2 and not n in self.i2c_use
Expand Down Expand Up @@ -414,18 +567,30 @@ def do_finalize(self):

if len(self.gem_mac):
mac_tcl = []
for i in self.gem_mac:
mac_tcl.append(f"set gem{i} [create_ip -vendor xilinx.com -name gmii_to_rgmii -module_name gem{i}]")
for i, if_type in self.gem_mac.items():
ip_name = {"rgmii": "gmii_to_rgmii", "sgmii": "gig_ethernet_pcs_pma"}[if_type]
mac_tcl.append(f"set gem{i} [create_ip -vendor xilinx.com -name {ip_name} -module_name gem{i}]")
mac_tcl.append("set_property -dict [ list \\")
# FIXME: when more this sequence differs for the first and others
mac_tcl.append("CONFIG.{} {} \\".format("C_EXTERNAL_CLOCK", '{{false}}'))
mac_tcl.append("CONFIG.{} {} \\".format("C_USE_IDELAY_CTRL", '{{true}}'))
mac_tcl.append("CONFIG.{} {} \\".format("C_PHYADDR", '{{' + str(8 + i) + '}}'))
mac_tcl.append("CONFIG.{} {} \\".format("RGMII_TXC_SKEW", '{{' + str(0) + '}}'))
mac_tcl.append("CONFIG.{} {} \\".format("SupportLevel", '{{Include_Shared_Logic_in_Core}}'))
if if_type == "rgmii":
# FIXME: when more this sequence differs for the first and others
mac_tcl.append("CONFIG.{} {} \\".format("C_EXTERNAL_CLOCK", '{{false}}'))
mac_tcl.append("CONFIG.{} {} \\".format("C_USE_IDELAY_CTRL", '{{true}}'))
mac_tcl.append("CONFIG.{} {} \\".format("C_PHYADDR", '{{' + str(8 + i) + '}}'))
mac_tcl.append("CONFIG.{} {} \\".format("RGMII_TXC_SKEW", '{{' + str(0) + '}}'))
mac_tcl.append("CONFIG.{} {} \\".format("SupportLevel", '{{Include_Shared_Logic_in_Core}}'))
elif if_type == "sgmii":
mac_tcl.append("CONFIG.{} {} \\".format("DIFFCLK_BOARD_INTERFACE", '{{Custom}}'))
mac_tcl.append("CONFIG.{} {} \\".format("DrpClkRate", '{{50.0000}}'))
mac_tcl.append("CONFIG.{} {} \\".format("EMAC_IF_TEMAC", '{{GEM}}'))
mac_tcl.append("CONFIG.{} {} \\".format("GT_Location", '{{X1Y13}}'))
mac_tcl.append("CONFIG.{} {} \\".format("RefClkRate", '{{156.25}}'))
mac_tcl.append("CONFIG.{} {} \\".format("Standard", '{{SGMII}}'))
mac_tcl.append("CONFIG.{} {} \\".format("SupportLevel", '{{Include_Shared_Logic_in_Core}}'))

mac_tcl += [
f"] [get_ips gem{i}]",
f"generate_target all [get_ips gem{i}]",
f"synth_ip [get_ips gem{i}]"
]

self.platform.toolchain.pre_synthesis_commands += mac_tcl

0 comments on commit 01c7a78

Please sign in to comment.