Skip to content

Commit a2102c6

Browse files
Merge pull request #199 from enjoy-digital/ptp
Add IGMP Multicast Joiner and PTP Slave Core (IEEE 1588v2, Layer 3).
2 parents e82cba3 + 726dc5c commit a2102c6

File tree

8 files changed

+5345
-2
lines changed

8 files changed

+5345
-2
lines changed

bench/arty_ptp.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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

Comments
 (0)