|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +# |
| 4 | +# This file is part of LiteEth. |
| 5 | +# |
| 6 | +# Copyright (c) 2020-2026 Florent Kermarrec <florent@enjoy-digital.fr> |
| 7 | +# SPDX-License-Identifier: BSD-2-Clause |
| 8 | + |
| 9 | +# Build/Use: |
| 10 | +# ---------- |
| 11 | +# Build and load the PTP bench design: |
| 12 | +# ./bench/arty_ptp.py --build --load |
| 13 | +# |
| 14 | +# Configure ptp4l as a Master on the host (E2E, UDP/IPv4, software timestamping): |
| 15 | +# |
| 16 | +# Create a config file (e.g. ptp-master.cfg): |
| 17 | +# [global] |
| 18 | +# twoStepFlag 1 |
| 19 | +# time_stamping software |
| 20 | +# delay_mechanism E2E |
| 21 | +# network_transport UDPv4 |
| 22 | +# domainNumber 0 |
| 23 | +# logAnnounceInterval 1 |
| 24 | +# logSyncInterval 0 |
| 25 | +# logMinDelayReqInterval 0 |
| 26 | +# udp_ttl 1 |
| 27 | +# |
| 28 | +# [eth0] |
| 29 | +# masterOnly 1 |
| 30 | +# |
| 31 | +# Replace "eth0" with your network interface name (e.g. enp6s0, tap0). |
| 32 | +# |
| 33 | +# Run ptp4l: |
| 34 | +# sudo ptp4l -f ptp-master.cfg |
| 35 | +# |
| 36 | +# Monitor PTP state over Etherbone: |
| 37 | +# ./bench/test_ptp.py --count 100 |
| 38 | +# ./bench/test_ptp.py --count 100 --debug |
| 39 | +# ./bench/test_ptp.py --count 100 --plot |
| 40 | + |
| 41 | +import os |
| 42 | +import argparse |
| 43 | + |
| 44 | +from migen import * |
| 45 | + |
| 46 | +from litex.gen import * |
| 47 | + |
| 48 | +from litex_boards.platforms import digilent_arty |
| 49 | + |
| 50 | +from litex.soc.cores.clock import * |
| 51 | +from litex.soc.integration.soc_core import * |
| 52 | +from litex.soc.integration.builder import * |
| 53 | +from litex.soc.cores.led import LedChaser |
| 54 | + |
| 55 | +from liteeth.phy.mii import LiteEthPHYMII |
| 56 | +from liteeth.core.ptp import LiteEthPTP |
| 57 | + |
| 58 | +# CRG ---------------------------------------------------------------------------------------------- |
| 59 | + |
| 60 | +class _CRG(LiteXModule): |
| 61 | + def __init__(self, platform, sys_clk_freq): |
| 62 | + self.rst = Signal() |
| 63 | + self.cd_sys = ClockDomain() |
| 64 | + self.cd_sys_eth = ClockDomain() |
| 65 | + self.cd_eth = ClockDomain() |
| 66 | + |
| 67 | + # # # |
| 68 | + |
| 69 | + # Clk/Rst. |
| 70 | + clk100 = platform.request("clk100") |
| 71 | + rst_n = platform.request("cpu_reset_n") |
| 72 | + |
| 73 | + # PLL. |
| 74 | + self.pll = pll = S7PLL(speedgrade=-1) |
| 75 | + self.comb += pll.reset.eq(~rst_n | self.rst) |
| 76 | + pll.register_clkin(clk100, 100e6) |
| 77 | + pll.create_clkout(self.cd_sys, sys_clk_freq) |
| 78 | + pll.create_clkout(self.cd_sys_eth, sys_clk_freq) |
| 79 | + pll.create_clkout(self.cd_eth, 25e6) |
| 80 | + self.comb += platform.request("eth_ref_clk").eq(self.cd_eth.clk) |
| 81 | + platform.add_false_path_constraints(self.cd_sys.clk, pll.clkin) |
| 82 | + |
| 83 | +# PTP Bench SoC ------------------------------------------------------------------------------------ |
| 84 | + |
| 85 | +class PTPBenchSoC(SoCCore): |
| 86 | + def __init__(self, sys_clk_freq=int(100e6), p2p=False, ptp_debug=False): |
| 87 | + platform = digilent_arty.Platform() |
| 88 | + |
| 89 | + # SoCMini ---------------------------------------------------------------------------------- |
| 90 | + SoCMini.__init__(self, platform, clk_freq=sys_clk_freq, |
| 91 | + ident = "LiteEth PTP bench on Arty", |
| 92 | + ident_version = True, |
| 93 | + ) |
| 94 | + |
| 95 | + # CRG -------------------------------------------------------------------------------------- |
| 96 | + self.crg = _CRG(platform, sys_clk_freq) |
| 97 | + |
| 98 | + # Etherbone (with IGMP for PTP multicast) ------------------------------------------------- |
| 99 | + self.ethphy = LiteEthPHYMII( |
| 100 | + clock_pads = self.platform.request("eth_clocks"), |
| 101 | + pads = self.platform.request("eth"), |
| 102 | + with_hw_init_reset = False, |
| 103 | + ) |
| 104 | + # CDC between sys_eth and Ethernet PHY clocks. |
| 105 | + self.platform.add_false_path_constraints( |
| 106 | + self.crg.cd_sys_eth.clk, |
| 107 | + self.ethphy.crg.cd_eth_rx.clk, |
| 108 | + self.ethphy.crg.cd_eth_tx.clk, |
| 109 | + ) |
| 110 | + ptp_igmp_groups = [0xE0000181, 0xE0000182] # 224.0.1.129, 224.0.1.130. |
| 111 | + if p2p: |
| 112 | + ptp_igmp_groups.append(0xE000006B) # 224.0.0.107. |
| 113 | + self.add_etherbone(phy=self.ethphy, buffer_depth=255, |
| 114 | + with_igmp = True, |
| 115 | + igmp_groups = ptp_igmp_groups, |
| 116 | + igmp_interval = 2, |
| 117 | + ) |
| 118 | + |
| 119 | + # PTP -------------------------------------------------------------------------------------- |
| 120 | + udp = self.ethcore_etherbone.udp |
| 121 | + |
| 122 | + # PTP event / general ports (CDC from sys_eth to ethcore clock domain). |
| 123 | + self.ptp_event_port = udp.crossbar.get_port(319, dw=8, cd="sys_eth") |
| 124 | + self.ptp_general_port = udp.crossbar.get_port(320, dw=8, cd="sys_eth") |
| 125 | + |
| 126 | + # PTP core (runs in sys_eth domain). |
| 127 | + self.ptp = ClockDomainsRenamer("sys_eth")(LiteEthPTP( |
| 128 | + self.ptp_event_port, |
| 129 | + self.ptp_general_port, |
| 130 | + sys_clk_freq, |
| 131 | + monitor_debug = ptp_debug, |
| 132 | + )) |
| 133 | + |
| 134 | + # PTP configuration. |
| 135 | + self.comb += [ |
| 136 | + self.ptp.clock_id.eq((0x10e2d5000001 << 16) | 1), |
| 137 | + self.ptp.p2p_mode.eq(1 if p2p else 0), |
| 138 | + ] |
| 139 | + |
| 140 | + # Leds ------------------------------------------------------------------------------------- |
| 141 | + self.leds = LedChaser( |
| 142 | + pads = platform.request_all("user_led"), |
| 143 | + sys_clk_freq = sys_clk_freq, |
| 144 | + ) |
| 145 | + |
| 146 | +# Main --------------------------------------------------------------------------------------------- |
| 147 | + |
| 148 | +def main(): |
| 149 | + parser = argparse.ArgumentParser(description="LiteEth PTP Bench on Arty A7.") |
| 150 | + parser.add_argument("--build", action="store_true", help="Build bitstream.") |
| 151 | + parser.add_argument("--load", action="store_true", help="Load bitstream.") |
| 152 | + parser.add_argument("--p2p", action="store_true", help="Enable PTP P2P mode.") |
| 153 | + parser.add_argument("--ptp-debug", action="store_true", help="Enable PTP debug monitor CSRs.") |
| 154 | + args = parser.parse_args() |
| 155 | + |
| 156 | + soc = PTPBenchSoC(p2p=args.p2p, ptp_debug=args.ptp_debug) |
| 157 | + builder = Builder(soc, csr_csv="csr.csv") |
| 158 | + builder.build(run=args.build) |
| 159 | + |
| 160 | + if args.load: |
| 161 | + prog = soc.platform.create_programmer() |
| 162 | + prog.load_bitstream(os.path.join(builder.gateware_dir, soc.build_name + ".bit")) |
| 163 | + |
| 164 | +if __name__ == "__main__": |
| 165 | + main() |
0 commit comments