Skip to content

Commit bdb8b54

Browse files
committed
applet.interface.ethernet.rgmii: new applet.
1 parent e6042e8 commit bdb8b54

8 files changed

Lines changed: 469 additions & 1 deletion

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
``ethernet-rgmii``
2+
==================
3+
4+
.. _applet.interface.ethernet.rgmii:
5+
6+
.. autoprogram:: glasgow.applet.interface.ethernet.rgmii:EthernetRGMIIApplet._get_argparser_for_sphinx("ethernet-rgmii")
7+
:prog: glasgow run ethernet-rgmii

docs/manual/src/applets/interface/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ I/O interfaces
2020
jtag_svf
2121
sbw_probe
2222
swd_probe
23+
ethernet_rgmii
24+
probe_rs

software/glasgow/applet/control/mdio/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ class ControlMDIOInterface:
9696
def __init__(self, logger: logging.Logger, assembly: AbstractAssembly, *,
9797
mdc: GlasgowPin, mdio: GlasgowPin):
9898
self._logger = logger
99-
self._level = logging.DEBUG if self._logger.name == __name__ else logging.TRACE
99+
if self._logger.name == __name__ or "interface.ethernet." in self._logger.name:
100+
self._level = logging.DEBUG
101+
else:
102+
self._level = logging.TRACE
100103

101104
assembly.use_pulls({mdio: "low"})
102105
ports = assembly.add_port_group(mdc=mdc, mdio=mdio)
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# Ref: IEEE Std 802.3-2018
2+
# Accession: G00098
3+
4+
from typing import Iterable, AsyncIterator, BinaryIO
5+
import time
6+
import logging
7+
import asyncio
8+
import argparse
9+
10+
from amaranth import *
11+
from amaranth.lib import wiring, stream
12+
from amaranth.lib.wiring import In, Out
13+
from amaranth.lib.crc.catalog import CRC32_ETHERNET
14+
15+
from glasgow.support.logging import dump_hex
16+
from glasgow.support.os_network import OSNetworkInterface
17+
from glasgow.arch.ieee802_3 import *
18+
from glasgow.gateware import cobs, ethernet
19+
from glasgow.protocol import snoop
20+
from glasgow.abstract import AbstractAssembly, GlasgowPin
21+
from glasgow.applet import GlasgowAppletV2
22+
from glasgow.applet.control.mdio import ControlMDIOInterface
23+
24+
25+
__all__ = ["EthernetComponent", "AbstractEthernetApplet"]
26+
27+
28+
class EthernetComponent(wiring.Component):
29+
i_stream: In(stream.Signature(8))
30+
o_stream: Out(stream.Signature(8))
31+
32+
rx_bypass: In(1)
33+
tx_bypass: In(1)
34+
35+
def __init__(self, driver):
36+
self._driver = driver
37+
38+
super().__init__()
39+
40+
def elaborate(self, platform):
41+
m = Module()
42+
43+
m.submodules.tx_decoder = tx_decoder = cobs.Decoder()
44+
wiring.connect(m, tx_decoder.i, wiring.flipped(self.i_stream))
45+
46+
m.submodules.ctrl = ctrl = ethernet.Controller(self._driver)
47+
m.d.comb += ctrl.rx_bypass.eq(self.rx_bypass)
48+
m.d.comb += ctrl.tx_bypass.eq(self.tx_bypass)
49+
wiring.connect(m, ctrl.i, tx_decoder.o)
50+
51+
m.submodules.rx_encoder = rx_encoder = cobs.Encoder(fifo_depth=2048)
52+
wiring.connect(m, rx_encoder.i, ctrl.o)
53+
54+
wiring.connect(m, wiring.flipped(self.o_stream), rx_encoder.o)
55+
56+
return m
57+
58+
59+
class AbstractEthernetInterface:
60+
def __init__(self, logger: logging.Logger, assembly: AbstractAssembly, *,
61+
driver: ethernet.AbstractDriver):
62+
self._logger = logger
63+
self._level = logging.DEBUG if self._logger.name.startswith(__name__) else logging.TRACE
64+
65+
component = assembly.add_submodule(EthernetComponent(driver))
66+
self._pipe = assembly.add_inout_pipe(
67+
component.o_stream, component.i_stream,
68+
in_fifo_depth=0, out_buffer_size=512 * 128)
69+
self._rx_bypass = assembly.add_rw_register(component.rx_bypass)
70+
self._tx_bypass = assembly.add_rw_register(component.tx_bypass)
71+
72+
self._snoop: snoop.SnoopWriter = None
73+
74+
def _log(self, message: str, *args):
75+
self._logger.log(self._level, "Ethernet: " + message, *args)
76+
77+
@property
78+
def snoop_file(self) -> BinaryIO:
79+
if self._snoop is not None:
80+
return self._snoop.file
81+
82+
@snoop_file.setter
83+
def snoop_file(self, snoop_file):
84+
if snoop_file is not None:
85+
self._snoop = snoop.SnoopWriter(snoop_file,
86+
datalink_type=snoop.SnoopDatalinkType.Ethernet)
87+
else:
88+
self._snoop = None
89+
90+
def _snoop_packet(self, packet):
91+
if self._snoop is not None:
92+
self._snoop.write(snoop.SnoopPacket(packet, timestamp_ns=time.time_ns()))
93+
94+
async def send(self, packet: bytes | bytearray | memoryview) -> bool:
95+
cobs_packet = cobs.encode(packet) + b"\x00"
96+
if self._pipe.writable is None or len(cobs_packet) <= self._pipe.writable:
97+
self._log("tx data=<%s>", dump_hex(packet))
98+
self._snoop_packet(packet)
99+
await self._pipe.send(cobs_packet)
100+
await self._pipe.flush(_wait=False)
101+
return True
102+
else:
103+
self._logger.warning("tx drop")
104+
return False
105+
106+
async def recv(self) -> bytes:
107+
packet = cobs.decode((await self._pipe.recv_until(b"\x00"))[:-1])
108+
self._log("rx data=<%s> len=%d", dump_hex(packet), len(packet))
109+
self._snoop_packet(packet)
110+
return packet
111+
112+
async def iter_recv(self) -> AsyncIterator[bytes]:
113+
while True:
114+
yield await self.recv()
115+
116+
117+
class AbstractEthernetApplet(GlasgowAppletV2):
118+
logger = logging.getLogger(__name__)
119+
help = "send and receive Ethernet packets"
120+
description = """
121+
Communicate with an Ethernet network using a PHY connected via the $PHYIF$ interface.
122+
123+
The `bridge` operation is supported only on Linux. To create a suitable TAP interface, run:
124+
125+
::
126+
127+
sudo ip tuntap add glasgow0 mode tap user $USER
128+
sudo ip link set glasgow0 up
129+
"""
130+
required_revision = "C0"
131+
132+
@classmethod
133+
def add_setup_arguments(cls, parser):
134+
parser.add_argument("--snoop", dest="snoop_file", type=argparse.FileType("wb"),
135+
metavar="SNOOP-FILE", help="save packets to a file in RFC 1761 format")
136+
137+
async def setup(self, args):
138+
self.eth_iface.snoop_file = args.snoop_file
139+
140+
@classmethod
141+
def add_run_arguments(cls, parser):
142+
p_operation = parser.add_subparsers(dest="operation", metavar="OPERATION", required=True)
143+
144+
p_bridge = p_operation.add_parser(
145+
"bridge", help="bridge network to the host OS")
146+
p_bridge.add_argument(
147+
"interface", metavar="INTERFACE", nargs="?", type=str, default="glasgow0",
148+
help="forward packets to and from this TAP interface (default: %(default)s)")
149+
150+
p_loopback = p_operation.add_parser(
151+
"loopback", help="test connection to PHY using near-end loopback")
152+
p_loopback.add_argument(
153+
"--delay", "-d", metavar="DELAY", type=float, default=1.0,
154+
help="wait for DELAY seconds between sending packets (default: %(default)s)")
155+
156+
async def run(self, args):
157+
if args.operation == "bridge":
158+
os_iface = OSNetworkInterface(args.interface)
159+
160+
async def forward_rx():
161+
async for packet in self.eth_iface.iter_recv():
162+
if len(packet) >= 14: # must be at least ETH_HLEN, or we'll get EINVAL on Linux
163+
await os_iface.send([packet])
164+
165+
async def forward_tx():
166+
while True:
167+
for packet in await os_iface.recv():
168+
if not await self.eth_iface.send(packet):
169+
break
170+
171+
async with asyncio.TaskGroup() as group:
172+
group.create_task(forward_rx())
173+
group.create_task(forward_tx())
174+
175+
if args.operation == "loopback":
176+
# Enable near-end loopback.
177+
basic_control = REG_BASIC_CONTROL.from_int(
178+
await self.mdio_iface.c22_read(0, REG_BASIC_CONTROL_addr))
179+
basic_control.LOOPBACK = 1
180+
await self.mdio_iface.c22_write(0, REG_BASIC_CONTROL_addr, basic_control.to_int())
181+
182+
# Accept all packets, even those with CRC errors.
183+
await self.eth_iface._rx_bypass.set(True)
184+
185+
count_ok = 0
186+
count_bad = 0
187+
count_lost = 0
188+
try:
189+
packet_data = bytes(range(256))
190+
packet_fcs = CRC32_ETHERNET().compute(packet_data).to_bytes(4, "little")
191+
packet_full = packet_data + packet_fcs
192+
while True:
193+
await self.eth_iface.send(packet_data)
194+
try:
195+
async with asyncio.timeout(args.delay):
196+
packet_recv = await self.eth_iface.recv()
197+
if packet_recv == packet_full:
198+
self.logger.info("packet ok")
199+
count_ok += 1
200+
else:
201+
if len(packet_recv) < len(packet_full):
202+
self.logger.warning("packet bad (short)")
203+
elif len(packet_recv) > len(packet_full):
204+
self.logger.warning("packet bad (long)")
205+
elif packet_recv[:len(packet_data)] != packet_data:
206+
self.logger.warning("packet bad (data)")
207+
else:
208+
self.logger.warning("packet bad (crc)")
209+
count_bad += 1
210+
await asyncio.sleep(args.delay)
211+
except asyncio.TimeoutError:
212+
self.logger.warning("packet lost")
213+
count_lost += 1
214+
finally:
215+
count_all = count_ok + count_bad + count_lost
216+
if count_all:
217+
self.logger.info(f"statistics: "
218+
f"ok {count_ok}/{count_all} ({count_ok/count_all*100:.0f}%), "
219+
f"bad {count_bad}/{count_all} ({count_bad/count_all*100:.0f}%), "
220+
f"lost {count_lost}/{count_all} ({count_lost/count_all*100:.0f}%)")

0 commit comments

Comments
 (0)