From 38ce533a4ef2fb725b569e16af18f321a828e440 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Mon, 27 Dec 2021 01:24:32 +0100 Subject: [PATCH 01/27] test/model/phy.py: add dumping to .pcap file for analysis with wireshark --- test/model/phy.py | 30 +++++++++++++++++++++++++++++- test/test_icmp.py | 2 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/test/model/phy.py b/test/model/phy.py index c11904a9..90e1fd3e 100644 --- a/test/model/phy.py +++ b/test/model/phy.py @@ -4,6 +4,7 @@ # Copyright (c) 2015-2019 Florent Kermarrec # SPDX-License-Identifier: BSD-2-Clause +from struct import pack from litex.soc.interconnect.stream_sim import * from liteeth.common import * @@ -28,8 +29,11 @@ def __init__(self, dw): # PHY ---------------------------------------------------------------------------------------------- +LINKTYPE_ETHERNET = 1 +LINKTYPE_RAW = 101 + class PHY(Module): - def __init__(self, dw, debug=False): + def __init__(self, dw, debug=False, pcap_file=None): self.dw = dw self.debug = debug @@ -41,6 +45,13 @@ def __init__(self, dw, debug=False): self.mac_callback = None + self.cc = 0 + self.pcap_file = pcap_file + if pcap_file is not None: + file_header = pack('IHHiIII', 0xa1b2c3d4, 2, 4, 0, 0, 65535, LINKTYPE_ETHERNET) + with open(pcap_file, 'wb') as f: + f.write(file_header) + def set_mac_callback(self, callback): self.mac_callback = callback @@ -52,6 +63,14 @@ def send(self, datas): for d in datas: r += "{:02x}".format(d) print_phy(r) + + if self.pcap_file is not None: + ll = len(datas) - 8 + if ll > 0: + with open(self.pcap_file, 'ab') as f: + f.write(pack('IIII', self.cc, 0, ll, ll)) + f.write(bytes(datas)[8:]) + self.phy_source.send(packet) def receive(self): @@ -62,6 +81,14 @@ def receive(self): for d in self.phy_sink.packet: r += "{:02x}".format(d) print_phy(r) + + if self.pcap_file is not None: + ll = len(self.phy_sink.packet) - 8 + if ll > 0: + with open(self.pcap_file, 'ab') as f: + f.write(pack('IIII', self.cc, 0, ll, ll)) + f.write(bytes(self.phy_sink.packet)[8:]) + self.packet = self.phy_sink.packet @passive @@ -70,3 +97,4 @@ def generator(self): yield from self.receive() if self.mac_callback is not None: self.mac_callback(self.packet) + self.cc += 1 diff --git a/test/test_icmp.py b/test/test_icmp.py index 9bb3af18..cb3436db 100644 --- a/test/test_icmp.py +++ b/test/test_icmp.py @@ -28,7 +28,7 @@ class DUT(Module): def __init__(self): - self.submodules.phy_model = phy.PHY(8, debug=True) + self.submodules.phy_model = phy.PHY(8, debug=True, pcap_file='dump.pcap') self.submodules.mac_model = mac.MAC(self.phy_model, debug=True, loopback=False) self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address, debug=True) self.submodules.ip_model = ip.IP(self.mac_model, mac_address, ip_address, debug=True, loopback=False) From e8e0dbb6e658e6535afc1f8f14a73e7302654fa7 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Mon, 27 Dec 2021 23:39:35 +0100 Subject: [PATCH 02/27] icmp.py: take icmp type field into account * only send a ping reply to type 8 (ping request) packets * otherwise liteeth would send a ping reply to `destination unreachable` packets too, which is not wanted --- liteeth/common.py | 2 ++ liteeth/core/icmp.py | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/liteeth/common.py b/liteeth/common.py index bb5f5584..6a5b47d1 100644 --- a/liteeth/common.py +++ b/liteeth/common.py @@ -84,6 +84,8 @@ # ICMP Constants/Header ---------------------------------------------------------------------------- icmp_protocol = 0x01 +icmp_type_ping_reply = 0 +icmp_type_ping_request = 8 icmp_header_length = 8 icmp_header_fields = { "msgtype": HeaderField(0, 0, 8), diff --git a/liteeth/core/icmp.py b/liteeth/core/icmp.py index e1703dc8..ee1f7d49 100644 --- a/liteeth/core/icmp.py +++ b/liteeth/core/icmp.py @@ -86,7 +86,9 @@ def __init__(self, ip_address, dw=8): If(depacketizer.source.valid, NextState("DROP"), If(sink.protocol == icmp_protocol, - NextState("RECEIVE") + If(depacketizer.source.msgtype == icmp_type_ping_request, + NextState("RECEIVE") + ) ) ) ) @@ -137,7 +139,7 @@ def __init__(self, dw=8): self.comb += [ sink.connect(self.buffer.sink), self.buffer.source.connect(source, omit={"checksum"}), - self.source.msgtype.eq(0x0), + self.source.msgtype.eq(icmp_type_ping_reply), self.source.checksum.eq(self.buffer.source.checksum + 0x800 + (self.buffer.source.checksum >= 0xf800)) ] From 2a5dce394e70c75eec8540f2b0e71d0602f63607 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 28 Dec 2021 00:08:29 +0100 Subject: [PATCH 03/27] icmp testing: demonstrate that non-ping ICMP packets are ignored --- test/model/dumps.py | 12 +++++++++++- test/test_icmp.py | 14 +++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/test/model/dumps.py b/test/model/dumps.py index e9ea8f26..01952603 100644 --- a/test/model/dumps.py +++ b/test/model/dumps.py @@ -86,7 +86,7 @@ def verify_packet(packet, infos): ping_request = format_dump(""" 00 50 56 e0 14 49 00 0c 29 34 0b de 08 00 45 00 00 3c d7 43 00 00 80 01 2b 73 c0 a8 9e 8b ae 89 -2a 4d 08 00 2a 5c 02 00 21 00 61 62 63 64 65 66 +2a 4d 08 00 20 b4 02 00 21 00 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 61 62 63 64 65 66 67 68 69""") @@ -104,3 +104,13 @@ def verify_packet(packet, infos): 77 61 62 63 64 65 66 67 68 69""") ping_reply_infos = {} + +# ICMP: Destination unreachable (Port unreachable) +icmp_unreachable_reply = format_dump(""" +00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 c0 +00 3e 11 a7 00 00 40 01 6a 56 7f 00 00 01 7f 00 +00 01 03 03 d8 18 00 00 00 00 45 00 00 22 a0 52 +40 00 40 11 9c 76 7f 00 00 01 7f 00 00 01 de 05 +04 d2 00 0e fe 21 68 65 6c 6c 6f 0a +""") + diff --git a/test/test_icmp.py b/test/test_icmp.py index cb3436db..4035a579 100644 --- a/test/test_icmp.py +++ b/test/test_icmp.py @@ -37,8 +37,8 @@ def __init__(self): self.submodules.ip = LiteEthIPCore(self.phy_model, mac_address, ip_address, 100000) -def main_generator(dut): - packet = MACPacket(ping_request) +def send_overwrite_addresses(dump, dut): + packet = MACPacket(dump) packet.decode_remove_header() packet = IPPacket(packet) packet.decode() @@ -46,9 +46,17 @@ def main_generator(dut): packet.decode() dut.icmp_model.send(packet) - for i in range(256): + +def main_generator(dut): + # We expect no ping reply to this + send_overwrite_addresses(icmp_unreachable_reply, dut) + for i in range(512): yield + # We expect a ping reply to this + send_overwrite_addresses(ping_request, dut) + for i in range(512): + yield class TestICMP(unittest.TestCase): def test(self): From fece437bdac8779b9440c2e0fb62c7373d0486a5 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Sun, 3 Apr 2022 10:46:57 +0200 Subject: [PATCH 04/27] test: fix ICMP header fields, add missing clockdomains --- liteeth/common.py | 4 +++- test/test_icmp.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/liteeth/common.py b/liteeth/common.py index 2c4f88fe..3395d28c 100644 --- a/liteeth/common.py +++ b/liteeth/common.py @@ -96,7 +96,9 @@ "msgtype": HeaderField(0, 0, 8), "code": HeaderField(1, 0, 8), "checksum": HeaderField(2, 0, 16), - "quench": HeaderField(4, 0, 32) + "ident": HeaderField(4, 0, 16), + "sequence": HeaderField(6, 0, 16), + # "data": HeaderField(4, 0, 32) } icmp_header = Header(icmp_header_fields, icmp_header_length, swap_field_bytes=True) diff --git a/test/test_icmp.py b/test/test_icmp.py index 4035a579..9b57fb2e 100644 --- a/test/test_icmp.py +++ b/test/test_icmp.py @@ -28,6 +28,10 @@ class DUT(Module): def __init__(self): + self.clock_domains.cd_sys = ClockDomain() + self.clock_domains.eth_tx = ClockDomain() + self.clock_domains.eth_rx = ClockDomain() + self.submodules.phy_model = phy.PHY(8, debug=True, pcap_file='dump.pcap') self.submodules.mac_model = mac.MAC(self.phy_model, debug=True, loopback=False) self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address, debug=True) From c234798b5229b643a71d6eeaefeef9874ad48335 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Sun, 3 Apr 2022 15:26:30 +0200 Subject: [PATCH 05/27] test_icmp.py: fixed the testbench there is a liteeth problem with the ping replies --- test/model/icmp.py | 17 +++++++++++++--- test/model/ip.py | 11 +++++++++-- test/model/phy.py | 41 +++++++++++++++++++++++++++++---------- test/test_icmp.py | 48 +++++++++++++++++++++++++--------------------- 4 files changed, 80 insertions(+), 37 deletions(-) diff --git a/test/model/icmp.py b/test/model/icmp.py index faf5c90d..19235d95 100644 --- a/test/model/icmp.py +++ b/test/model/icmp.py @@ -32,6 +32,9 @@ def decode(self): setattr(self, k, get_field_data(v, header)) def encode(self): + ''' + insert header bytes in front of the payload bytes + ''' header = 0 for k, v in sorted(icmp_header.fields.items()): value = merge_bytes(split_bytes(getattr(self, k), @@ -41,6 +44,13 @@ def encode(self): for d in split_bytes(header, icmp_header.length): self.insert(0, d) + def insert_checksum(self): + self[2] = 0 + self[3] = 0 + c = ip.checksum(self) + self[2] = c & 0xff + self[3] = (c >> 8) & 0xff + def __repr__(self): r = "--------\n" for k in sorted(icmp_header.fields.keys()): @@ -63,21 +73,22 @@ def __init__(self, ip, ip_address, debug=False): self.ip.set_icmp_callback(self.callback) - def send(self, packet): + def send(self, packet, target_ip=0xFFFFFFFF): packet.encode() + packet.insert_checksum() if self.debug: print_icmp(">>>>>>>>") print_icmp(packet) ip_packet = ip.IPPacket(packet) ip_packet.version = 0x4 ip_packet.ihl = 0x5 - ip_packet.total_length = len(packet) + ip_packet.ihl + ip_packet.total_length = len(packet) + 20 ip_packet.identification = 0 ip_packet.flags = 0 ip_packet.fragment_offset = 0 ip_packet.ttl = 0x80 ip_packet.sender_ip = self.ip_address - ip_packet.target_ip = 0x12345678 # XXX + ip_packet.target_ip = target_ip ip_packet.checksum = 0 ip_packet.protocol = icmp_protocol self.ip.send(ip_packet) diff --git a/test/model/ip.py b/test/model/ip.py index ea4e2c67..5183bf00 100644 --- a/test/model/ip.py +++ b/test/model/ip.py @@ -24,10 +24,17 @@ def carry_around_add(a, b): def checksum(msg): + ''' + http://www.faqs.org/rfcs/rfc1071.html + ''' s = 0 - for i in range(0, len(msg), 2): - w = msg[i] + (msg[i+1] << 8) + for i in range(len(msg) // 2): + w = msg[2 * i] + (msg[2 * i + 1] << 8) s = carry_around_add(s, w) + # print(hex(w), hex(s)) + if len(msg) % 2: # Add the single remaining byte + s = carry_around_add(s, msg[-1]) + # print(hex(msg[-1]), hex(s)) return ~s & 0xffff diff --git a/test/model/phy.py b/test/model/phy.py index 90e1fd3e..b4312a77 100644 --- a/test/model/phy.py +++ b/test/model/phy.py @@ -15,6 +15,14 @@ def print_phy(s): print_with_prefix(s, "[PHY]") +def bytes_to_words(bs, width): + ws = [] + n_words = len(bs) // width + for i in range(n_words): + tmp = bs[i * width: (i + 1) * width] + ws.append(merge_bytes(tmp[::-1])) + return ws + # PHY Source --------------------------------------------------------------------------------------- class PHYSource(PacketStreamer): @@ -31,6 +39,7 @@ def __init__(self, dw): LINKTYPE_ETHERNET = 1 LINKTYPE_RAW = 101 +LINKTYPE_ETHERNET_MPACKET = 274 class PHY(Module): def __init__(self, dw, debug=False, pcap_file=None): @@ -48,7 +57,16 @@ def __init__(self, dw, debug=False, pcap_file=None): self.cc = 0 self.pcap_file = pcap_file if pcap_file is not None: - file_header = pack('IHHiIII', 0xa1b2c3d4, 2, 4, 0, 0, 65535, LINKTYPE_ETHERNET) + file_header = pack( + 'IHHiIII', + 0xa1b2c3d4, + 2, + 4, + 0, + 0, + 65535, + LINKTYPE_ETHERNET_MPACKET + ) with open(pcap_file, 'wb') as f: f.write(file_header) @@ -56,7 +74,6 @@ def set_mac_callback(self, callback): self.mac_callback = callback def send(self, datas): - packet = Packet(datas) if self.debug: r = ">>>>>>>>\n" r += "length " + str(len(datas)) + "\n" @@ -65,31 +82,35 @@ def send(self, datas): print_phy(r) if self.pcap_file is not None: - ll = len(datas) - 8 + ll = len(datas) # - 8 if ll > 0: with open(self.pcap_file, 'ab') as f: f.write(pack('IIII', self.cc, 0, ll, ll)) - f.write(bytes(datas)[8:]) + f.write(bytes(datas)) + packet = Packet(bytes_to_words(datas, self.dw // 8)) self.phy_source.send(packet) def receive(self): yield from self.phy_sink.receive() + p = self.phy_sink.packet # Each item is a word of width self.dw if self.debug: r = "<<<<<<<<\n" - r += "length " + str(len(self.phy_sink.packet)) + "\n" - for d in self.phy_sink.packet: - r += "{:02x}".format(d) + r += "length " + str(len(p)) + "\n" + for d in p: + r += f'{d:0{self.dw // 4}x} ' print_phy(r) + # Each item is a byte + self.packet = [b for w in p for b in split_bytes(w, self.dw // 8, "little")] + if self.pcap_file is not None: - ll = len(self.phy_sink.packet) - 8 + ll = len(self.packet) # - 8 if ll > 0: with open(self.pcap_file, 'ab') as f: f.write(pack('IIII', self.cc, 0, ll, ll)) - f.write(bytes(self.phy_sink.packet)[8:]) + f.write(bytes(self.packet)) - self.packet = self.phy_sink.packet @passive def generator(self): diff --git a/test/test_icmp.py b/test/test_icmp.py index 9b57fb2e..7dad3915 100644 --- a/test/test_icmp.py +++ b/test/test_icmp.py @@ -22,45 +22,49 @@ from litex.gen.sim import * -ip_address = 0x12345678 -mac_address = 0x12345678abcd +model_ip = convert_ip("192.168.10.1") +model_mac = 0x12345678abcd +dut_ip = convert_ip("192.168.10.50") +dut_mac = 0x12345678ffff class DUT(Module): - def __init__(self): + def __init__(self, dw=8): self.clock_domains.cd_sys = ClockDomain() self.clock_domains.eth_tx = ClockDomain() self.clock_domains.eth_rx = ClockDomain() + self.dw = dw - self.submodules.phy_model = phy.PHY(8, debug=True, pcap_file='dump.pcap') + self.submodules.phy_model = phy.PHY(self.dw, debug=True, pcap_file='dump.pcap') self.submodules.mac_model = mac.MAC(self.phy_model, debug=True, loopback=False) - self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address, debug=True) - self.submodules.ip_model = ip.IP(self.mac_model, mac_address, ip_address, debug=True, loopback=False) - self.submodules.icmp_model = icmp.ICMP(self.ip_model, ip_address, debug=True) + self.submodules.arp_model = arp.ARP(self.mac_model, model_mac, model_ip, debug=True) + self.submodules.ip_model = ip.IP(self.mac_model, model_mac, model_ip, debug=True, loopback=False) + self.submodules.icmp_model = icmp.ICMP(self.ip_model, model_ip, debug=True) - self.submodules.ip = LiteEthIPCore(self.phy_model, mac_address, ip_address, 100000) + self.submodules.ip = LiteEthIPCore(self.phy_model, dut_mac, dut_ip, 100000) -def send_overwrite_addresses(dump, dut): - packet = MACPacket(dump) - packet.decode_remove_header() - packet = IPPacket(packet) - packet.decode() - packet = ICMPPacket(packet) - packet.decode() - dut.icmp_model.send(packet) +def send_icmp(dut, msgtype=icmp_type_ping_request, code=0): + p = icmp.ICMPPacket(b"Hello World") + p.code = code + p.checksum = 0 + p.ident = 0x69b3 + p.msgtype = msgtype + p.sequence = 0x0 + dut.icmp_model.send(p, target_ip=dut_ip) def main_generator(dut): - # We expect no ping reply to this - send_overwrite_addresses(icmp_unreachable_reply, dut) + # We expect a ping reply to this (after ARP query) + send_icmp(dut) for i in range(512): yield - # We expect a ping reply to this - send_overwrite_addresses(ping_request, dut) - for i in range(512): - yield + # We expect no ping reply to this + # send_icmp(dut, 3, 3) + # for i in range(512): + # yield + class TestICMP(unittest.TestCase): def test(self): From 01dcdba1ee5166e3e1d88b2f3af154c5601248e6 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Sun, 3 Apr 2022 17:01:55 +0200 Subject: [PATCH 06/27] add ident and sequence parameters to ICMP stream also, add checks to ICMP testbench --- liteeth/common.py | 3 +-- liteeth/core/icmp.py | 6 ++++-- test/test_icmp.py | 44 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/liteeth/common.py b/liteeth/common.py index 3395d28c..e110aa7b 100644 --- a/liteeth/common.py +++ b/liteeth/common.py @@ -97,8 +97,7 @@ "code": HeaderField(1, 0, 8), "checksum": HeaderField(2, 0, 16), "ident": HeaderField(4, 0, 16), - "sequence": HeaderField(6, 0, 16), - # "data": HeaderField(4, 0, 32) + "sequence": HeaderField(6, 0, 16) } icmp_header = Header(icmp_header_fields, icmp_header_length, swap_field_bytes=True) diff --git a/liteeth/core/icmp.py b/liteeth/core/icmp.py index deb3c325..99ec5880 100644 --- a/liteeth/core/icmp.py +++ b/liteeth/core/icmp.py @@ -35,7 +35,8 @@ def __init__(self, ip_address, dw=8): "msgtype", "code", "checksum", - "quench", + "ident", + "sequence", "data", "last_be"}) @@ -98,7 +99,8 @@ def __init__(self, ip_address, dw=8): "msgtype", "code", "checksum", - "quench", + "ident", + "sequence", "data", "error", "last_be"}), diff --git a/test/test_icmp.py b/test/test_icmp.py index 7dad3915..5ec56a20 100644 --- a/test/test_icmp.py +++ b/test/test_icmp.py @@ -28,6 +28,18 @@ dut_ip = convert_ip("192.168.10.50") dut_mac = 0x12345678ffff +got_ping_reply = False + +class ICMP(icmp.ICMP): + def process(self, p): + global got_ping_reply + print("Received ping reply", p) + if p.code == 0 and p.ident == 0x69b3 and p.checksum == 0xaac0 and p.sequence == 1: + got_ping_reply = True + else: + raise RuntimeError("Invalid ping reply\nFAIL") + + class DUT(Module): def __init__(self, dw=8): self.clock_domains.cd_sys = ClockDomain() @@ -35,35 +47,47 @@ def __init__(self, dw=8): self.clock_domains.eth_rx = ClockDomain() self.dw = dw - self.submodules.phy_model = phy.PHY(self.dw, debug=True, pcap_file='dump.pcap') - self.submodules.mac_model = mac.MAC(self.phy_model, debug=True, loopback=False) + self.submodules.phy_model = phy.PHY(self.dw, debug=False, pcap_file='dump.pcap') + self.submodules.mac_model = mac.MAC(self.phy_model, debug=False, loopback=False) self.submodules.arp_model = arp.ARP(self.mac_model, model_mac, model_ip, debug=True) - self.submodules.ip_model = ip.IP(self.mac_model, model_mac, model_ip, debug=True, loopback=False) - self.submodules.icmp_model = icmp.ICMP(self.ip_model, model_ip, debug=True) + self.submodules.ip_model = ip.IP(self.mac_model, model_mac, model_ip, debug=False, loopback=False) + self.submodules.icmp_model = ICMP(self.ip_model, model_ip, debug=True) self.submodules.ip = LiteEthIPCore(self.phy_model, dut_mac, dut_ip, 100000) def send_icmp(dut, msgtype=icmp_type_ping_request, code=0): - p = icmp.ICMPPacket(b"Hello World") + p = icmp.ICMPPacket(b"Hello World 123456") p.code = code p.checksum = 0 - p.ident = 0x69b3 p.msgtype = msgtype - p.sequence = 0x0 + p.ident = 0x69b3 + p.sequence = 0x1 dut.icmp_model.send(p, target_ip=dut_ip) def main_generator(dut): + global got_ping_reply + # We expect a ping reply to this (after ARP query) send_icmp(dut) for i in range(512): yield + if got_ping_reply: + break + + if not got_ping_reply: + raise RuntimeError("Missing ping reply\nFAIL") # We expect no ping reply to this - # send_icmp(dut, 3, 3) - # for i in range(512): - # yield + got_ping_reply = False + send_icmp(dut, 3, 3) + for i in range(512): + if got_ping_reply: + raise RuntimeError("Inappropriate ping reply\nFAIL") + yield + + print("PASS") class TestICMP(unittest.TestCase): From b4519be77f9240a0c9c43e583087be54eaeb73fe Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Sun, 3 Apr 2022 23:43:02 +0200 Subject: [PATCH 07/27] PacketStreamer: WIP, make it drive last_be correctly also: it should be able to digest bytes and convert them to dw sized words on its own --- liteeth/mac/core.py | 3 +++ test/model/phy.py | 15 +++++++-------- test/test_icmp.py | 6 +++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/liteeth/mac/core.py b/liteeth/mac/core.py index 21d5858f..4b59900d 100644 --- a/liteeth/mac/core.py +++ b/liteeth/mac/core.py @@ -128,6 +128,9 @@ def do_finalize(self): if not getattr(phy, "integrated_ifg_inserter", False): tx_datapath.add_gap() tx_datapath.pipeline.append(phy) + print("tx_datapath.pipeline:") + for p in tx_datapath.pipeline: + print(p) self.submodules.tx_datapath = tx_datapath # RX Data-Path (PHY --> Core). diff --git a/test/model/phy.py b/test/model/phy.py index b4312a77..5130145c 100644 --- a/test/model/phy.py +++ b/test/model/phy.py @@ -74,22 +74,21 @@ def set_mac_callback(self, callback): self.mac_callback = callback def send(self, datas): + n_bytes = len(datas) if self.debug: r = ">>>>>>>>\n" - r += "length " + str(len(datas)) + "\n" + r += "length " + str(n_bytes) + "\n" for d in datas: r += "{:02x}".format(d) print_phy(r) - if self.pcap_file is not None: - ll = len(datas) # - 8 - if ll > 0: - with open(self.pcap_file, 'ab') as f: - f.write(pack('IIII', self.cc, 0, ll, ll)) - f.write(bytes(datas)) + if self.pcap_file is not None and n_bytes > 0: + with open(self.pcap_file, 'ab') as f: + f.write(pack('IIII', self.cc, 0, n_bytes, n_bytes)) + f.write(bytes(datas)) packet = Packet(bytes_to_words(datas, self.dw // 8)) - self.phy_source.send(packet) + self.phy_source.send(packet, n_bytes) def receive(self): yield from self.phy_sink.receive() diff --git a/test/test_icmp.py b/test/test_icmp.py index 5ec56a20..3797abc0 100644 --- a/test/test_icmp.py +++ b/test/test_icmp.py @@ -47,13 +47,13 @@ def __init__(self, dw=8): self.clock_domains.eth_rx = ClockDomain() self.dw = dw - self.submodules.phy_model = phy.PHY(self.dw, debug=False, pcap_file='dump.pcap') + self.submodules.phy_model = phy.PHY(self.dw, debug=True, pcap_file='dump.pcap') self.submodules.mac_model = mac.MAC(self.phy_model, debug=False, loopback=False) self.submodules.arp_model = arp.ARP(self.mac_model, model_mac, model_ip, debug=True) self.submodules.ip_model = ip.IP(self.mac_model, model_mac, model_ip, debug=False, loopback=False) self.submodules.icmp_model = ICMP(self.ip_model, model_ip, debug=True) - self.submodules.ip = LiteEthIPCore(self.phy_model, dut_mac, dut_ip, 100000) + self.submodules.ip = LiteEthIPCore(self.phy_model, dut_mac, dut_ip, 100000, dw=dw) def send_icmp(dut, msgtype=icmp_type_ping_request, code=0): @@ -92,7 +92,7 @@ def main_generator(dut): class TestICMP(unittest.TestCase): def test(self): - dut = DUT() + dut = DUT(32) generators = { "sys" : [main_generator(dut)], "eth_tx": [dut.phy_model.phy_sink.generator(), From b0953ea57df8a9803ed49d40a3438278e14a2346 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Sun, 3 Apr 2022 23:50:01 +0200 Subject: [PATCH 08/27] add .gtkw for ping debugging --- debug_ping.gtkw | 356 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 debug_ping.gtkw diff --git a/debug_ping.gtkw b/debug_ping.gtkw new file mode 100644 index 00000000..5f6ba741 --- /dev/null +++ b/debug_ping.gtkw @@ -0,0 +1,356 @@ +[dumpfile] "sim.vcd" +[timestart] 0 +[size] 2550 1387 +[pos] -1 -690 +*-9.000000 120 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 +[sst_width] 368 +[signals_width] 602 +[sst_expanded] 1 +[sst_vpaned_height] 424 +@28 +sys_clk +@c00200 +-mac_core_cdc +@28 +mac_core_cdc_sink_first +mac_core_cdc_sink_last +@22 +mac_core_cdc_sink_payload_data[31:0] +mac_core_cdc_sink_payload_error[3:0] +mac_core_cdc_sink_payload_last_be[3:0] +@28 +mac_core_cdc_sink_ready +mac_core_cdc_sink_valid +@200 +- +@28 +mac_core_cdc_source_first +mac_core_cdc_source_last +@22 +mac_core_cdc_source_payload_data[31:0] +mac_core_cdc_source_payload_error[3:0] +mac_core_cdc_source_payload_last_be[3:0] +@28 +mac_core_cdc_source_ready +mac_core_cdc_source_valid +@1401200 +-mac_core_cdc +@800200 +-mac_core_rx_cdc +@28 +mac_core_rx_cdc_sink_sink_first +mac_core_rx_cdc_sink_sink_last +@22 +mac_core_rx_cdc_sink_sink_payload_data[31:0] +mac_core_rx_cdc_sink_sink_payload_error[3:0] +mac_core_rx_cdc_sink_sink_payload_last_be[3:0] +@28 +mac_core_rx_cdc_sink_sink_ready +mac_core_rx_cdc_sink_sink_valid +@1000200 +-mac_core_rx_cdc +@c00200 +-last_be +@28 +mac_core_tx_last_be_sink_first +mac_core_tx_last_be_sink_last +@22 +mac_core_tx_last_be_sink_payload_data[31:0] +mac_core_tx_last_be_sink_payload_error[3:0] +mac_core_tx_last_be_sink_payload_last_be[3:0] +@28 +mac_core_tx_last_be_sink_ready +mac_core_tx_last_be_sink_valid +@200 +- +@28 +mac_core_tx_last_be_source_first +mac_core_tx_last_be_source_last +@22 +mac_core_tx_last_be_source_payload_data[31:0] +mac_core_tx_last_be_source_payload_error[3:0] +mac_core_tx_last_be_source_payload_last_be[3:0] +@28 +mac_core_tx_last_be_source_ready +mac_core_tx_last_be_source_valid +@1401200 +-last_be +@c00200 +-PADDING +@28 +mac_core_tx_padding_source_first +mac_core_tx_padding_source_last +@22 +mac_core_tx_padding_source_payload_data[31:0] +mac_core_tx_padding_source_payload_error[3:0] +mac_core_tx_padding_source_payload_last_be[3:0] +@28 +mac_core_tx_padding_source_ready +mac_core_tx_padding_source_valid +@1401200 +-PADDING +@c00200 +-CRC +@28 +mac_core_liteethmaccrc32inserter_source_first +mac_core_liteethmaccrc32inserter_source_last +mac_core_liteethmaccrc32inserter_source_ready +mac_core_liteethmaccrc32inserter_source_valid +@1401200 +-CRC +@c00200 +-preamble +@28 +mac_core_tx_preamble_source_first +mac_core_tx_preamble_source_last +@22 +mac_core_tx_preamble_source_payload_data[31:0] +mac_core_tx_preamble_source_payload_error[3:0] +mac_core_tx_preamble_source_payload_last_be[3:0] +@28 +mac_core_tx_preamble_source_ready +mac_core_tx_preamble_source_valid +@1401200 +-preamble +@c00200 +-ICMP_TX +@28 +icmp_tx_source_source_first +icmp_tx_source_source_last +@22 +icmp_tx_source_source_param_ip_address[31:0] +icmp_tx_source_source_param_length[15:0] +icmp_tx_source_source_param_protocol[7:0] +icmp_tx_source_source_payload_data[31:0] +icmp_tx_source_source_payload_error[3:0] +icmp_tx_source_source_payload_last_be[3:0] +@28 +icmp_tx_source_source_ready +icmp_tx_source_source_valid +@1401200 +-ICMP_TX +@c00200 +-mac_gap +@28 +mac_core_tx_gap_source_first +mac_core_tx_gap_source_last +@22 +mac_core_tx_gap_source_payload_data[31:0] +mac_core_tx_gap_source_payload_error[3:0] +mac_core_tx_gap_source_payload_last_be[3:0] +@28 +mac_core_tx_gap_source_ready +mac_core_tx_gap_source_valid +@1401200 +-mac_gap +@c00200 +-mac_source +@28 +mac_core_source_source_first +mac_core_source_source_last +@22 +mac_core_source_source_payload_data[31:0] +mac_core_source_source_payload_error[3:0] +mac_core_source_source_payload_last_be[3:0] +@28 +mac_core_source_source_ready +mac_core_source_source_valid +@1401200 +-mac_source +@c00200 +-arp_tx +@28 +arp_tx_source_source_first +arp_tx_source_source_last +@22 +arp_tx_source_source_payload_data[31:0] +arp_tx_source_source_payload_error[3:0] +arp_tx_source_source_payload_ethernet_type[15:0] +arp_tx_source_source_payload_last_be[3:0] +arp_tx_source_source_payload_sender_mac[47:0] +arp_tx_source_source_payload_target_mac[47:0] +@28 +arp_tx_source_source_ready +arp_tx_source_source_valid +@1401200 +-arp_tx +@c00200 +-arp_rx +@28 +arp_rx_sink_sink_first +arp_rx_sink_sink_last +@22 +arp_rx_sink_sink_payload_data[31:0] +arp_rx_sink_sink_payload_error[3:0] +arp_rx_sink_sink_payload_ethernet_type[15:0] +arp_rx_sink_sink_payload_last_be[3:0] +arp_rx_sink_sink_payload_sender_mac[47:0] +arp_rx_sink_sink_payload_target_mac[47:0] +@28 +arp_rx_sink_sink_ready +arp_rx_sink_sink_valid +@1401200 +-arp_rx +@c00200 +-IP +@28 +ip_tx_source_source_first +ip_tx_source_source_last +@22 +ip_tx_source_source_payload_data[31:0] +ip_tx_source_source_payload_error[3:0] +ip_tx_source_source_payload_ethernet_type[15:0] +ip_tx_source_source_payload_last_be[3:0] +ip_tx_source_source_payload_sender_mac[47:0] +ip_tx_source_source_payload_target_mac[47:0] +@28 +ip_tx_source_source_ready +ip_tx_source_source_valid +@200 +- +@28 +ip_rx_source_source_first +ip_rx_source_source_last +@22 +ip_rx_source_source_param_ip_address[31:0] +ip_rx_source_source_param_length[15:0] +ip_rx_source_source_param_protocol[7:0] +ip_rx_source_source_payload_data[31:0] +ip_rx_source_source_payload_error[3:0] +ip_rx_source_source_payload_last_be[3:0] +@28 +ip_rx_source_source_ready +ip_rx_source_source_valid +@1401200 +-IP +@c00200 +-IP_PACKETIZER +@28 +ip_tx_packetizer_source_first +ip_tx_packetizer_source_last +ip_tx_packetizer_source_last_a +ip_tx_packetizer_source_last_b +ip_tx_packetizer_source_last_s +@22 +ip_tx_packetizer_source_payload_data[31:0] +ip_tx_packetizer_source_payload_error[3:0] +ip_tx_packetizer_source_payload_ethernet_type[15:0] +ip_tx_packetizer_source_payload_last_be[3:0] +ip_tx_packetizer_source_payload_sender_mac[47:0] +ip_tx_packetizer_source_payload_target_mac[47:0] +@28 +ip_tx_packetizer_source_ready +ip_tx_packetizer_source_valid +@200 +- +@28 +ip_tx_packetizer_sink_last +@22 +ip_tx_packetizer_sink_payload_last_be[3:0] +@1401200 +-IP_PACKETIZER +@c00200 +-ARP_PACKETIZER +@28 +arp_tx_packetizer_source_first +arp_tx_packetizer_source_last +arp_tx_packetizer_source_last_a +arp_tx_packetizer_source_last_b +arp_tx_packetizer_source_last_s +@22 +arp_tx_packetizer_source_payload_data[31:0] +arp_tx_packetizer_source_payload_error[3:0] +arp_tx_packetizer_source_payload_ethernet_type[15:0] +arp_tx_packetizer_source_payload_last_be[3:0] +arp_tx_packetizer_source_payload_sender_mac[47:0] +arp_tx_packetizer_source_payload_target_mac[47:0] +@28 +arp_tx_packetizer_source_ready +arp_tx_packetizer_source_valid +@200 +- +@28 +arp_tx_packetizer_sink_last +@22 +arp_tx_packetizer_sink_payload_last_be[3:0] +@1401200 +-ARP_PACKETIZER +@c00200 +-ICMP_TX_PACKETIZER +@28 +icmp_tx_packetizer_source_first +icmp_tx_packetizer_source_last +icmp_tx_packetizer_source_last_a +icmp_tx_packetizer_source_last_b +icmp_tx_packetizer_source_last_s +@22 +icmp_tx_packetizer_source_param_ip_address[31:0] +icmp_tx_packetizer_source_param_length[15:0] +icmp_tx_packetizer_source_param_protocol[7:0] +icmp_tx_packetizer_source_payload_data[31:0] +icmp_tx_packetizer_source_payload_error[3:0] +icmp_tx_packetizer_source_payload_last_be[3:0] +@28 +icmp_tx_packetizer_source_ready +icmp_tx_packetizer_source_valid +@1401200 +-ICMP_TX_PACKETIZER +@c00200 +-echo_source +@28 +icmp_echo_sink_sink_first +icmp_echo_sink_sink_last +@22 +icmp_echo_sink_sink_param_checksum[15:0] +icmp_echo_sink_sink_param_code[7:0] +icmp_echo_sink_sink_param_ident[15:0] +icmp_echo_sink_sink_param_ip_address[31:0] +icmp_echo_sink_sink_param_length[15:0] +icmp_echo_sink_sink_param_msgtype[7:0] +icmp_echo_sink_sink_param_sequence[15:0] +icmp_echo_sink_sink_payload_data[31:0] +icmp_echo_sink_sink_payload_error[3:0] +icmp_echo_sink_sink_payload_last_be[3:0] +@28 +icmp_echo_sink_sink_ready +icmp_echo_sink_sink_valid +@200 +- +@28 +icmp_echo_source_source_first +icmp_echo_source_source_last +@22 +icmp_echo_source_source_param_checksum[15:0] +icmp_echo_source_source_param_code[7:0] +icmp_echo_source_source_param_ident[15:0] +icmp_echo_source_source_param_ip_address[31:0] +icmp_echo_source_source_param_length[15:0] +icmp_echo_source_source_param_msgtype[7:0] +icmp_echo_source_source_param_sequence[15:0] +icmp_echo_source_source_payload_data[31:0] +icmp_echo_source_source_payload_error[3:0] +icmp_echo_source_source_payload_last_be[3:0] +@28 +icmp_echo_source_source_ready +icmp_echo_source_source_valid +@1401200 +-echo_source +@800201 +-icmp_rx +@29 +icmp_rx_sink_sink_first +icmp_rx_sink_sink_last +@23 +icmp_rx_sink_sink_param_ip_address[31:0] +icmp_rx_sink_sink_param_length[15:0] +icmp_rx_sink_sink_param_protocol[7:0] +icmp_rx_sink_sink_payload_data[31:0] +icmp_rx_sink_sink_payload_error[3:0] +icmp_rx_sink_sink_payload_last_be[3:0] +@29 +icmp_rx_sink_sink_ready +icmp_rx_sink_sink_valid +@1000201 +-icmp_rx +[pattern_trace] 1 +[pattern_trace] 0 From 04a5565a872a3dd67e4189f4c4f1a80cbb2db6d7 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Mon, 4 Apr 2022 15:29:18 +0200 Subject: [PATCH 09/27] Let PacketStreamer / Logger handle the word <--> byte conversion test_icmp.py passes for DW=8 but fails for DW=32 --- test/model/phy.py | 25 +++++------------- test/test_icmp.py | 5 ++-- test/test_packet_streamer.py | 49 ++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 21 deletions(-) create mode 100644 test/test_packet_streamer.py diff --git a/test/model/phy.py b/test/model/phy.py index 5130145c..16dc10fe 100644 --- a/test/model/phy.py +++ b/test/model/phy.py @@ -14,26 +14,17 @@ def print_phy(s): print_with_prefix(s, "[PHY]") - -def bytes_to_words(bs, width): - ws = [] - n_words = len(bs) // width - for i in range(n_words): - tmp = bs[i * width: (i + 1) * width] - ws.append(merge_bytes(tmp[::-1])) - return ws - # PHY Source --------------------------------------------------------------------------------------- class PHYSource(PacketStreamer): def __init__(self, dw): - PacketStreamer.__init__(self, eth_phy_description(dw)) + PacketStreamer.__init__(self, eth_phy_description(dw), dw=dw) # PHY Sink ----------------------------------------------------------------------------------------- class PHYSink(PacketLogger): def __init__(self, dw): - PacketLogger.__init__(self, eth_phy_description(dw)) + PacketLogger.__init__(self, eth_phy_description(dw), dw=dw) # PHY ---------------------------------------------------------------------------------------------- @@ -79,7 +70,7 @@ def send(self, datas): r = ">>>>>>>>\n" r += "length " + str(n_bytes) + "\n" for d in datas: - r += "{:02x}".format(d) + r += f'{d:02x} ' print_phy(r) if self.pcap_file is not None and n_bytes > 0: @@ -87,22 +78,18 @@ def send(self, datas): f.write(pack('IIII', self.cc, 0, n_bytes, n_bytes)) f.write(bytes(datas)) - packet = Packet(bytes_to_words(datas, self.dw // 8)) - self.phy_source.send(packet, n_bytes) + self.phy_source.send(Packet(datas)) def receive(self): yield from self.phy_sink.receive() - p = self.phy_sink.packet # Each item is a word of width self.dw + self.packet = p = self.phy_sink.packet # Each item is a byte if self.debug: r = "<<<<<<<<\n" r += "length " + str(len(p)) + "\n" for d in p: - r += f'{d:0{self.dw // 4}x} ' + r += f'{d:02x} ' print_phy(r) - # Each item is a byte - self.packet = [b for w in p for b in split_bytes(w, self.dw // 8, "little")] - if self.pcap_file is not None: ll = len(self.packet) # - 8 if ll > 0: diff --git a/test/test_icmp.py b/test/test_icmp.py index 3797abc0..1bdd8677 100644 --- a/test/test_icmp.py +++ b/test/test_icmp.py @@ -30,6 +30,7 @@ got_ping_reply = False + class ICMP(icmp.ICMP): def process(self, p): global got_ping_reply @@ -49,9 +50,9 @@ def __init__(self, dw=8): self.submodules.phy_model = phy.PHY(self.dw, debug=True, pcap_file='dump.pcap') self.submodules.mac_model = mac.MAC(self.phy_model, debug=False, loopback=False) - self.submodules.arp_model = arp.ARP(self.mac_model, model_mac, model_ip, debug=True) + self.submodules.arp_model = arp.ARP(self.mac_model, model_mac, model_ip, debug=False) self.submodules.ip_model = ip.IP(self.mac_model, model_mac, model_ip, debug=False, loopback=False) - self.submodules.icmp_model = ICMP(self.ip_model, model_ip, debug=True) + self.submodules.icmp_model = ICMP(self.ip_model, model_ip, debug=False) self.submodules.ip = LiteEthIPCore(self.phy_model, dut_mac, dut_ip, 100000, dw=dw) diff --git a/test/test_packet_streamer.py b/test/test_packet_streamer.py new file mode 100644 index 00000000..9066a712 --- /dev/null +++ b/test/test_packet_streamer.py @@ -0,0 +1,49 @@ +''' +TODO add checking of output stream +''' +import unittest + +from migen import * +from litex.soc.interconnect.stream_sim import * +from liteeth.common import * + + +class DUT(Module): + def __init__(self, dw=8): + self.source = stream.Endpoint(eth_phy_description(dw)) + + ### + + self.dw = dw + self.submodules.phy_source = PacketStreamer( + eth_phy_description(dw), + last_be=True, + dw=dw + ) + self.comb += [ + self.phy_source.source.connect(self.source) + ] + + +def main_generator(dut): + print() + p = Packet(range(8)) + dut.phy_source.send(p) + # yield + # yield + # yield + yield (dut.source.ready.eq(1)) + for i in range(64): + yield + + +class TestPacketStreamer(unittest.TestCase): + def test(self): + dut = DUT(24) + generators = { + "sys": [ + main_generator(dut), + dut.phy_source.generator() + ], + } + run_simulation(dut, generators, vcd_name="sim.vcd") From 24d0b8313476d9137bea607a2dd489a843c72111 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Mon, 4 Apr 2022 16:42:06 +0200 Subject: [PATCH 10/27] test_icmp: working ping testbench for 8, 32 and 64 bit datapath --- test/model/phy.py | 2 +- test/test_icmp.py | 48 ++++++++++++++++++++++++++--------------------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/test/model/phy.py b/test/model/phy.py index 16dc10fe..06ebecc3 100644 --- a/test/model/phy.py +++ b/test/model/phy.py @@ -18,7 +18,7 @@ def print_phy(s): class PHYSource(PacketStreamer): def __init__(self, dw): - PacketStreamer.__init__(self, eth_phy_description(dw), dw=dw) + PacketStreamer.__init__(self, eth_phy_description(dw), dw=dw, last_be=True) # PHY Sink ----------------------------------------------------------------------------------------- diff --git a/test/test_icmp.py b/test/test_icmp.py index 1bdd8677..cde4acbf 100644 --- a/test/test_icmp.py +++ b/test/test_icmp.py @@ -8,7 +8,6 @@ from migen import * -from litex.soc.interconnect import wishbone from litex.soc.interconnect.stream_sim import * from liteeth.common import * @@ -35,10 +34,12 @@ class ICMP(icmp.ICMP): def process(self, p): global got_ping_reply print("Received ping reply", p) - if p.code == 0 and p.ident == 0x69b3 and p.checksum == 0xaac0 and p.sequence == 1: - got_ping_reply = True - else: - raise RuntimeError("Invalid ping reply\nFAIL") + tc = unittest.TestCase() + tc.assertEqual(p.code, 0) + tc.assertEqual(p.ident, 0x69b3) + tc.assertEqual(p.checksum, 0xaac0) + tc.assertEqual(p.sequence, 1) + got_ping_reply = True class DUT(Module): @@ -69,6 +70,7 @@ def send_icmp(dut, msgtype=icmp_type_ping_request, code=0): def main_generator(dut): global got_ping_reply + tc = unittest.TestCase() # We expect a ping reply to this (after ARP query) send_icmp(dut) @@ -76,24 +78,19 @@ def main_generator(dut): yield if got_ping_reply: break - - if not got_ping_reply: - raise RuntimeError("Missing ping reply\nFAIL") + tc.assertTrue(got_ping_reply, "Missing ping reply") # We expect no ping reply to this - got_ping_reply = False - send_icmp(dut, 3, 3) - for i in range(512): - if got_ping_reply: - raise RuntimeError("Inappropriate ping reply\nFAIL") - yield - - print("PASS") + # got_ping_reply = False + # send_icmp(dut, 3, 3) + # for i in range(256): + # tc.assertFalse(got_ping_reply, "Inappropriate ping reply") + # yield class TestICMP(unittest.TestCase): - def test(self): - dut = DUT(32) + def work(self, dw): + dut = DUT(dw) generators = { "sys" : [main_generator(dut)], "eth_tx": [dut.phy_model.phy_sink.generator(), @@ -101,6 +98,15 @@ def test(self): "eth_rx": dut.phy_model.phy_source.generator() } clocks = {"sys": 10, - "eth_rx": 10, - "eth_tx": 10} - run_simulation(dut, generators, clocks, vcd_name="sim.vcd") + "eth_rx": 9, + "eth_tx": 9} + run_simulation(dut, generators, clocks, vcd_name=f'test_icmp_{dw}.vcd') + + def test_8(self): + self.work(8) + + def test_32(self): + self.work(32) + + def test_64(self): + self.work(64) From 123a23d5135ee37e5a3d7b15f25f355829e8d069 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Mon, 4 Apr 2022 18:26:38 +0200 Subject: [PATCH 11/27] fix PacketStreamer bug * removed the state machine and replaced it by a sequential code * this fixes the mac_core testbench --- test/model/phy.py | 2 +- test/test_mac_core.py | 2 +- test/test_packet_streamer.py | 14 +++++--------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/test/model/phy.py b/test/model/phy.py index 06ebecc3..16dc10fe 100644 --- a/test/model/phy.py +++ b/test/model/phy.py @@ -18,7 +18,7 @@ def print_phy(s): class PHYSource(PacketStreamer): def __init__(self, dw): - PacketStreamer.__init__(self, eth_phy_description(dw), dw=dw, last_be=True) + PacketStreamer.__init__(self, eth_phy_description(dw), dw=dw) # PHY Sink ----------------------------------------------------------------------------------------- diff --git a/test/test_mac_core.py b/test/test_mac_core.py index 3a1a6480..f3ef0ed0 100644 --- a/test/test_mac_core.py +++ b/test/test_mac_core.py @@ -25,7 +25,7 @@ def __init__(self): self.submodules.mac_model = mac.MAC(self.phy_model, debug=False, loopback=True) self.submodules.core = LiteEthMACCore(phy=self.phy_model, dw=8, with_preamble_crc=True) - self.submodules.streamer = PacketStreamer(eth_phy_description(8), last_be=1) + self.submodules.streamer = PacketStreamer(eth_phy_description(8)) self.submodules.streamer_randomizer = Randomizer(eth_phy_description(8), level=50) self.submodules.logger_randomizer = Randomizer(eth_phy_description(8), level=50) diff --git a/test/test_packet_streamer.py b/test/test_packet_streamer.py index 9066a712..ccd1ac64 100644 --- a/test/test_packet_streamer.py +++ b/test/test_packet_streamer.py @@ -16,9 +16,7 @@ def __init__(self, dw=8): self.dw = dw self.submodules.phy_source = PacketStreamer( - eth_phy_description(dw), - last_be=True, - dw=dw + eth_phy_description(dw), dw=dw ) self.comb += [ self.phy_source.source.connect(self.source) @@ -27,19 +25,17 @@ def __init__(self, dw=8): def main_generator(dut): print() - p = Packet(range(8)) + p = Packet(range(10)) dut.phy_source.send(p) - # yield - # yield - # yield - yield (dut.source.ready.eq(1)) + # dut.phy_source.send(p) for i in range(64): + yield (dut.source.ready.eq(i % 2)) yield class TestPacketStreamer(unittest.TestCase): def test(self): - dut = DUT(24) + dut = DUT(64) generators = { "sys": [ main_generator(dut), From 11fbea778d600e2dce91b575e83894f698da16ad Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 5 Apr 2022 15:17:39 +0200 Subject: [PATCH 12/27] fix ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 797bc3ac..ac588316 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: # Install (n)Migen / LiteX / Cores - name: Install LiteX run: | - wget https://raw.githubusercontent.com/enjoy-digital/litex/master/litex_setup.py + wget https://github.com/yetifrisstlama/litex/raw/yfl/litex_setup.py python3 litex_setup.py init install --user # Install Project From c83c7dfb0271a6763fac7254192f45d39117b0d8 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Sun, 17 Apr 2022 20:40:16 +0200 Subject: [PATCH 13/27] test_etherbone.py: fix the test and make it self checking * only for 8 bit datapath for now --- liteeth/core/udp.py | 2 +- test/model/etherbone.py | 15 ++-- test/model/udp.py | 6 +- test/test_etherbone.py | 155 ++++++++++++++++++++++------------------ 4 files changed, 100 insertions(+), 78 deletions(-) diff --git a/liteeth/core/udp.py b/liteeth/core/udp.py index 253a1449..f74582c0 100644 --- a/liteeth/core/udp.py +++ b/liteeth/core/udp.py @@ -189,7 +189,7 @@ def __init__(self, ip_address, dw=8): ) ) fsm.act("RECEIVE", - depacketizer.source.connect(source, keep={"valid", "ready"}), + depacketizer.source.connect(source, keep={"valid", "ready", "last_be"}), source.last.eq(depacketizer.source.last | (count == (source.length - dw//8))), If(source.valid & source.ready, NextValue(count, count + dw//8), diff --git a/test/model/etherbone.py b/test/model/etherbone.py index 4fbb7fbf..34cfaa65 100644 --- a/test/model/etherbone.py +++ b/test/model/etherbone.py @@ -31,23 +31,26 @@ def __init__(self, udp, debug=False): udp.set_etherbone_callback(self.callback) - def send(self, packet): + def send(self, packet, target_ip=0x12345678): packet.encode() if self.debug: print_etherbone(">>>>>>>>") print_etherbone(packet) - udp_packet = udp.UDPPacket(packet) + udp_packet = udp.UDPPacket(packet.bytes) udp_packet.src_port = 0x1234 # FIXME udp_packet.dst_port = 0x1234 # FIXME - udp_packet.length = len(packet) + udp_packet.length = len(packet.bytes) + udp_header_length udp_packet.checksum = 0 - self.udp.send(udp_packet) + self.udp.send(udp_packet, target_ip) - def receive(self): + def receive(self, timeout=None): self.rx_packet = EtherbonePacket() - while not self.rx_packet.done: + i = 0 + while not self.rx_packet.done and ((timeout is None) or (timeout >= i)): + i += 1 yield + def callback(self, packet): packet = EtherbonePacket(packet) packet.decode() diff --git a/test/model/udp.py b/test/model/udp.py index 05aaabc5..ec163723 100644 --- a/test/model/udp.py +++ b/test/model/udp.py @@ -69,7 +69,7 @@ def __init__(self, ip, ip_address, debug=False, loopback=False): def set_etherbone_callback(self, callback): self.etherbone_callback = callback - def send(self, packet): + def send(self, packet, target_ip=0x12345678): packet.encode() if self.debug: print_udp(">>>>>>>>") @@ -77,13 +77,13 @@ def send(self, packet): ip_packet = ip.IPPacket(packet) ip_packet.version = 0x4 ip_packet.ihl = 0x5 - ip_packet.total_length = len(packet) + ip_packet.ihl + ip_packet.total_length = len(packet) + ip_packet.ihl * 4 ip_packet.identification = 0 ip_packet.flags = 0 ip_packet.fragment_offset = 0 ip_packet.ttl = 0x80 ip_packet.sender_ip = self.ip_address - ip_packet.target_ip = 0x12345678 # FIXME + ip_packet.target_ip = target_ip ip_packet.checksum = 0 ip_packet.protocol = udp_protocol self.ip.send(ip_packet) diff --git a/test/test_etherbone.py b/test/test_etherbone.py index e5cb2bcb..6260db08 100644 --- a/test/test_etherbone.py +++ b/test/test_etherbone.py @@ -22,87 +22,106 @@ ip_address = 0x12345678 mac_address = 0x12345678abcd +tc = unittest.TestCase() + class DUT(Module): def __init__(self): - self.submodules.phy_model = phy.PHY(8, debug=False) + self.submodules.phy_model = phy.PHY(8, debug=False, pcap_file='dump_wishbone.pcap') self.submodules.mac_model = mac.MAC(self.phy_model, debug=False, loopback=False) self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address, debug=False) self.submodules.ip_model = ip.IP(self.mac_model, mac_address, ip_address, debug=False, loopback=False) self.submodules.udp_model = udp.UDP(self.ip_model, ip_address, debug=False, loopback=False) - self.submodules.etherbone_model = etherbone.Etherbone(self.udp_model, debug=False) + self.submodules.etherbone_model = etherbone.Etherbone(self.udp_model, debug=True) - self.submodules.core = LiteEthUDPIPCore(self.phy_model, mac_address, ip_address, 100000) - self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234) + self.submodules.core = LiteEthUDPIPCore(self.phy_model, mac_address + 1, ip_address + 1, 100000) + self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234, buffer_depth=6) self.submodules.sram = wishbone.SRAM(1024) self.submodules.interconnect = wishbone.InterconnectPointToPoint(self.etherbone.wishbone.bus, self.sram.bus) +def test_probe(dut): + packet = etherbone.EtherbonePacket() + packet.pf = 1 + packet.encode() + packet.bytes += bytes([0x00, 0x00, 0x00, 0x00]) # Add payload padding + + dut.etherbone_model.send(packet, ip_address + 1) + yield from dut.etherbone_model.receive(400) + tc.assertEqual(dut.etherbone_model.rx_packet.pr, 1) + + +def test_writes(dut, writes_datas): + for i, wd in enumerate(writes_datas): + yield dut.sram.mem[i].eq(0) + + writes = etherbone.EtherboneWrites(base_addr=0x1000, datas=writes_datas) + record = etherbone.EtherboneRecord() + record.writes = writes + record.reads = None + record.bca = 0 + record.rca = 0 + record.rff = 0 + record.cyc = 0 + record.wca = 0 + record.wff = 0 + record.byte_enable = 0xf + record.wcount = len(writes_datas) + record.rcount = 0 + + packet = etherbone.EtherbonePacket() + packet.records = [record] + dut.etherbone_model.send(packet) + + for i in range(256): + yield + if (yield dut.sram.bus.cyc): + break + for i in range(32): + yield + + for i, wd in enumerate(writes_datas): + val = (yield dut.sram.mem[i]) + tc.assertEqual(val, wd) + + +def test_reads(dut, writes_datas): + reads_addrs = [] + for i, wd in enumerate(writes_datas): + yield dut.sram.mem[i].eq(wd) + reads_addrs.append(0x1000 + i * 4) + + reads = etherbone.EtherboneReads(base_ret_addr=0x1000, addrs=reads_addrs) + record = etherbone.EtherboneRecord() + record.writes = None + record.reads = reads + record.bca = 0 + record.rca = 0 + record.rff = 0 + record.cyc = 0 + record.wca = 0 + record.wff = 0 + record.byte_enable = 0xf + record.wcount = 0 + record.rcount = len(reads_addrs) + + packet = etherbone.EtherbonePacket() + packet.records = [record] + dut.etherbone_model.send(packet) + yield from dut.etherbone_model.receive(400) + + reads_datas = dut.etherbone_model.rx_packet.records.pop().writes.get_datas() + + # check results + tc.assertEqual(writes_datas, reads_datas) + + def main_generator(dut): - test_probe = True - test_writes = True - test_reads = True - - # test probe - if test_probe: - packet = etherbone.EtherbonePacket() - packet.pf = 1 - dut.etherbone_model.send(packet) - yield from dut.etherbone_model.receive() - print("probe: " + str(bool(dut.etherbone_model.rx_packet.pr))) - - for i in range(2): - # test writes - if test_writes: - writes_datas = [j for j in range(4)] - writes = etherbone.EtherboneWrites(base_addr=0x1000, datas=writes_datas) - record = etherbone.EtherboneRecord() - record.writes = writes - record.reads = None - record.bca = 0 - record.rca = 0 - record.rff = 0 - record.cyc = 0 - record.wca = 0 - record.wff = 0 - record.byte_enable = 0xf - record.wcount = len(writes_datas) - record.rcount = 0 - - packet = etherbone.EtherbonePacket() - packet.records = [record] - dut.etherbone_model.send(packet) - for i in range(256): - yield - - # test reads - if test_reads: - reads_addrs = [0x1000 + 4*j for j in range(4)] - reads = etherbone.EtherboneReads(base_ret_addr=0x1000, addrs=reads_addrs) - record = etherbone.EtherboneRecord() - record.writes = None - record.reads = reads - record.bca = 0 - record.rca = 0 - record.rff = 0 - record.cyc = 0 - record.wca = 0 - record.wff = 0 - record.byte_enable = 0xf - record.wcount = 0 - record.rcount = len(reads_addrs) - - packet = etherbone.EtherbonePacket() - packet.records = [record] - dut.etherbone_model.send(packet) - yield from dut.etherbone_model.receive() - loopback_writes_datas = [] - loopback_writes_datas = dut.etherbone_model.rx_packet.records.pop().writes.get_datas() - - # check resultss - s, l, e = check(writes_datas, loopback_writes_datas) - print("shift " + str(s) + " / length " + str(l) + " / errors " + str(e)) + writes_datas = [((0xA + j) << 28) + j for j in range(6)] + yield from test_probe(dut) + yield from test_writes(dut, writes_datas) + yield from test_reads(dut, writes_datas) class TestEtherbone(unittest.TestCase): @@ -117,4 +136,4 @@ def test_etherbone(self): clocks = {"sys": 10, "eth_rx": 10, "eth_tx": 10} - #run_simulation(dut, generators, clocks, vcd_name="sim.vcd") # FIXME: hanging + run_simulation(dut, generators, clocks, vcd_name="sim.vcd") From 4e2be2e68a6cf042c35593018eaa0568236cffea Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Sun, 17 Apr 2022 21:49:09 +0200 Subject: [PATCH 14/27] etherbone test with DW=32 succeeds --- test/test_etherbone.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_etherbone.py b/test/test_etherbone.py index 6260db08..b0fe6e07 100644 --- a/test/test_etherbone.py +++ b/test/test_etherbone.py @@ -21,21 +21,22 @@ ip_address = 0x12345678 mac_address = 0x12345678abcd +DW = 32 tc = unittest.TestCase() class DUT(Module): def __init__(self): - self.submodules.phy_model = phy.PHY(8, debug=False, pcap_file='dump_wishbone.pcap') + self.submodules.phy_model = phy.PHY(DW, debug=False, pcap_file='dump_wishbone.pcap') self.submodules.mac_model = mac.MAC(self.phy_model, debug=False, loopback=False) self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address, debug=False) self.submodules.ip_model = ip.IP(self.mac_model, mac_address, ip_address, debug=False, loopback=False) self.submodules.udp_model = udp.UDP(self.ip_model, ip_address, debug=False, loopback=False) self.submodules.etherbone_model = etherbone.Etherbone(self.udp_model, debug=True) - self.submodules.core = LiteEthUDPIPCore(self.phy_model, mac_address + 1, ip_address + 1, 100000) - self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234, buffer_depth=6) + self.submodules.core = LiteEthUDPIPCore(self.phy_model, mac_address + 1, ip_address + 1, 100000, dw=DW) + self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234, buffer_depth=8) self.submodules.sram = wishbone.SRAM(1024) self.submodules.interconnect = wishbone.InterconnectPointToPoint(self.etherbone.wishbone.bus, self.sram.bus) From 8c175c4a9bac1a65af3d4ec3a33e860c8b766e16 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Mon, 18 Apr 2022 00:22:50 +0200 Subject: [PATCH 15/27] test_etherbone.py: writes seem to work with DW=64 * but probes and reads not --- test/test_etherbone.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_etherbone.py b/test/test_etherbone.py index b0fe6e07..958a7389 100644 --- a/test/test_etherbone.py +++ b/test/test_etherbone.py @@ -21,7 +21,7 @@ ip_address = 0x12345678 mac_address = 0x12345678abcd -DW = 32 +DW = 64 tc = unittest.TestCase() @@ -36,7 +36,7 @@ def __init__(self): self.submodules.etherbone_model = etherbone.Etherbone(self.udp_model, debug=True) self.submodules.core = LiteEthUDPIPCore(self.phy_model, mac_address + 1, ip_address + 1, 100000, dw=DW) - self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234, buffer_depth=8) + self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234, buffer_depth=16) self.submodules.sram = wishbone.SRAM(1024) self.submodules.interconnect = wishbone.InterconnectPointToPoint(self.etherbone.wishbone.bus, self.sram.bus) @@ -46,7 +46,7 @@ def test_probe(dut): packet = etherbone.EtherbonePacket() packet.pf = 1 packet.encode() - packet.bytes += bytes([0x00, 0x00, 0x00, 0x00]) # Add payload padding + packet.bytes += bytes([0x00, 0x00, 0x00, 0x00]) # Add padding of 1 record dut.etherbone_model.send(packet, ip_address + 1) yield from dut.etherbone_model.receive(400) @@ -120,9 +120,9 @@ def test_reads(dut, writes_datas): def main_generator(dut): writes_datas = [((0xA + j) << 28) + j for j in range(6)] - yield from test_probe(dut) + # yield from test_probe(dut) yield from test_writes(dut, writes_datas) - yield from test_reads(dut, writes_datas) + # yield from test_reads(dut, writes_datas) class TestEtherbone(unittest.TestCase): From 8e55e4f2a88ba1f25b15868683b6bbd72f64453a Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 19 Apr 2022 00:57:43 +0200 Subject: [PATCH 16/27] packet.py: fix typo in Packetizer * in some cases, the delayed sink data was updated even though sink was not valid, leading to corrupted source data * this broke etherbone reads with DW=64 --- liteeth/packet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liteeth/packet.py b/liteeth/packet.py index 851550cc..17dbd0c6 100644 --- a/liteeth/packet.py +++ b/liteeth/packet.py @@ -97,7 +97,7 @@ def __init__(self, sink_description, source_description, header): ) if not aligned: header_offset_multiplier = 1 if header_words == 1 else 2 - self.sync += If(source.ready, sink_d.eq(sink)) + self.sync += If(source.valid & source.ready, sink_d.eq(sink)) fsm.act("UNALIGNED-DATA-COPY", source.valid.eq(sink.valid | sink_d.last), source_last_a.eq(sink.last | sink_d.last), From a2d04975fb6354b1fd7e83ba528571bae536c0fb Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 19 Apr 2022 01:02:48 +0200 Subject: [PATCH 17/27] test_etherbone.py: make all tests pass with DW=64 --- liteeth/frontend/etherbone.py | 2 +- test/test_etherbone.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/liteeth/frontend/etherbone.py b/liteeth/frontend/etherbone.py index 47568eb0..e31c9f85 100644 --- a/liteeth/frontend/etherbone.py +++ b/liteeth/frontend/etherbone.py @@ -140,7 +140,7 @@ def __init__(self): # # # self.submodules.fifo = fifo = PacketFIFO(eth_etherbone_packet_user_description(32), - payload_depth = 1, + payload_depth = 2, # Needed for DW=64 + 8 bytes padding in probe packet param_depth = 1, buffered = False ) diff --git a/test/test_etherbone.py b/test/test_etherbone.py index 958a7389..f79c243f 100644 --- a/test/test_etherbone.py +++ b/test/test_etherbone.py @@ -46,7 +46,10 @@ def test_probe(dut): packet = etherbone.EtherbonePacket() packet.pf = 1 packet.encode() - packet.bytes += bytes([0x00, 0x00, 0x00, 0x00]) # Add padding of 1 record + # TODO changing the number of padding bytes can break things with DW=64 + # this is because there is no proper last_be support in + # stream.StrideConverter, which is needed by LiteEthUDPCrossbar.get_port() + packet.bytes += bytes([0x00] * 8) # Add padding dut.etherbone_model.send(packet, ip_address + 1) yield from dut.etherbone_model.receive(400) @@ -120,9 +123,9 @@ def test_reads(dut, writes_datas): def main_generator(dut): writes_datas = [((0xA + j) << 28) + j for j in range(6)] - # yield from test_probe(dut) + yield from test_probe(dut) yield from test_writes(dut, writes_datas) - # yield from test_reads(dut, writes_datas) + yield from test_reads(dut, writes_datas) class TestEtherbone(unittest.TestCase): From d83430f7a1372c13ca575cd264f3774ce7c7897a Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 19 Apr 2022 01:50:33 +0200 Subject: [PATCH 18/27] test_etherbone: test 8, 32 and 64 bit DW in CI --- test/test_etherbone.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/test/test_etherbone.py b/test/test_etherbone.py index f79c243f..3d9d8857 100644 --- a/test/test_etherbone.py +++ b/test/test_etherbone.py @@ -5,6 +5,7 @@ # SPDX-License-Identifier: BSD-2-Clause import unittest +tc = unittest.TestCase() from migen import * @@ -14,28 +15,24 @@ from liteeth.common import * from liteeth.core import LiteEthUDPIPCore from liteeth.frontend.etherbone import LiteEthEtherbone - from test.model import phy, mac, arp, ip, udp, etherbone - from litex.gen.sim import * + ip_address = 0x12345678 mac_address = 0x12345678abcd -DW = 64 - -tc = unittest.TestCase() class DUT(Module): - def __init__(self): - self.submodules.phy_model = phy.PHY(DW, debug=False, pcap_file='dump_wishbone.pcap') + def __init__(self, dw): + self.submodules.phy_model = phy.PHY(dw, debug=False, pcap_file='dump_wishbone.pcap') self.submodules.mac_model = mac.MAC(self.phy_model, debug=False, loopback=False) self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address, debug=False) self.submodules.ip_model = ip.IP(self.mac_model, mac_address, ip_address, debug=False, loopback=False) self.submodules.udp_model = udp.UDP(self.ip_model, ip_address, debug=False, loopback=False) self.submodules.etherbone_model = etherbone.Etherbone(self.udp_model, debug=True) - self.submodules.core = LiteEthUDPIPCore(self.phy_model, mac_address + 1, ip_address + 1, 100000, dw=DW) + self.submodules.core = LiteEthUDPIPCore(self.phy_model, mac_address + 1, ip_address + 1, 100000, dw=dw) self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234, buffer_depth=16) self.submodules.sram = wishbone.SRAM(1024) @@ -129,8 +126,7 @@ def main_generator(dut): class TestEtherbone(unittest.TestCase): - def test_etherbone(self): - dut = DUT() + def do_test(self, dut): generators = { "sys" : [main_generator(dut)], "eth_tx": [dut.phy_model.phy_sink.generator(), @@ -141,3 +137,12 @@ def test_etherbone(self): "eth_rx": 10, "eth_tx": 10} run_simulation(dut, generators, clocks, vcd_name="sim.vcd") + + def test_etherbone_dw_8(self): + self.do_test(DUT(8)) + + def test_etherbone_dw_32(self): + self.do_test(DUT(32)) + + def test_etherbone_dw_64(self): + self.do_test(DUT(64)) From b4ebc3b755b000e4774545234f0602975de5244d Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 19 Apr 2022 10:53:17 +0200 Subject: [PATCH 19/27] test_etherbone.py: some polish * push IP address directly into ARP table * re-organize unit-test structure * remove some debug prints --- liteeth/core/arp.py | 22 ++--- liteeth/mac/core.py | 6 +- test/test_etherbone.py | 198 +++++++++++++++++++++-------------------- 3 files changed, 115 insertions(+), 111 deletions(-) diff --git a/liteeth/core/arp.py b/liteeth/core/arp.py index 18ccfbc2..a74d4222 100644 --- a/liteeth/core/arp.py +++ b/liteeth/core/arp.py @@ -202,9 +202,9 @@ def __init__(self, clk_freq, max_requests=8): # table in the future to improve performance when packets are # targeting multiple destinations. update = Signal() - cached_valid = Signal() - cached_ip_address = Signal(32, reset_less=True) - cached_mac_address = Signal(48, reset_less=True) + self.cached_valid = Signal() + self.cached_ip_address = Signal(32, reset_less=True) + self.cached_mac_address = Signal(48, reset_less=True) cached_timer = WaitTimer(clk_freq*10) self.submodules += cached_timer @@ -238,21 +238,21 @@ def __init__(self, clk_freq, max_requests=8): ) self.sync += \ If(update, - cached_valid.eq(1), - cached_ip_address.eq(sink.ip_address), - cached_mac_address.eq(sink.mac_address), + self.cached_valid.eq(1), + self.cached_ip_address.eq(sink.ip_address), + self.cached_mac_address.eq(sink.mac_address), ).Else( If(cached_timer.done, - cached_valid.eq(0) + self.cached_valid.eq(0) ) ) self.comb += cached_timer.wait.eq(~update) fsm.act("CHECK_TABLE", - If(cached_valid, - If(request_ip_address == cached_ip_address, + If(self.cached_valid, + If(request_ip_address == self.cached_ip_address, request_ip_address_reset.eq(1), NextState("PRESENT_RESPONSE"), - ).Elif(request.ip_address == cached_ip_address, + ).Elif(request.ip_address == self.cached_ip_address, request.ready.eq(request.valid), NextState("PRESENT_RESPONSE"), ).Else( @@ -282,7 +282,7 @@ def __init__(self, clk_freq, max_requests=8): request_counter_reset.eq(1), request_pending_clr.eq(1) ), - response.mac_address.eq(cached_mac_address) + response.mac_address.eq(self.cached_mac_address) ] fsm.act("PRESENT_RESPONSE", response.valid.eq(1), diff --git a/liteeth/mac/core.py b/liteeth/mac/core.py index 4b59900d..9c7c641b 100644 --- a/liteeth/mac/core.py +++ b/liteeth/mac/core.py @@ -128,9 +128,9 @@ def do_finalize(self): if not getattr(phy, "integrated_ifg_inserter", False): tx_datapath.add_gap() tx_datapath.pipeline.append(phy) - print("tx_datapath.pipeline:") - for p in tx_datapath.pipeline: - print(p) + # print("tx_datapath.pipeline:") + # for p in tx_datapath.pipeline: + # print(p) self.submodules.tx_datapath = tx_datapath # RX Data-Path (PHY --> Core). diff --git a/test/test_etherbone.py b/test/test_etherbone.py index 3d9d8857..64b55da3 100644 --- a/test/test_etherbone.py +++ b/test/test_etherbone.py @@ -5,7 +5,6 @@ # SPDX-License-Identifier: BSD-2-Clause import unittest -tc = unittest.TestCase() from migen import * @@ -25,110 +24,115 @@ class DUT(Module): def __init__(self, dw): - self.submodules.phy_model = phy.PHY(dw, debug=False, pcap_file='dump_wishbone.pcap') - self.submodules.mac_model = mac.MAC(self.phy_model, debug=False, loopback=False) - self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address, debug=False) - self.submodules.ip_model = ip.IP(self.mac_model, mac_address, ip_address, debug=False, loopback=False) - self.submodules.udp_model = udp.UDP(self.ip_model, ip_address, debug=False, loopback=False) - self.submodules.etherbone_model = etherbone.Etherbone(self.udp_model, debug=True) + self.submodules.phy_model = phy.PHY(dw) #, pcap_file='dump_wishbone.pcap') + self.submodules.mac_model = mac.MAC(self.phy_model) + self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address) + self.submodules.ip_model = ip.IP(self.mac_model, mac_address, ip_address) + self.submodules.udp_model = udp.UDP(self.ip_model, ip_address) + self.submodules.etherbone_model = etherbone.Etherbone(self.udp_model, debug=False) - self.submodules.core = LiteEthUDPIPCore(self.phy_model, mac_address + 1, ip_address + 1, 100000, dw=dw) - self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234, buffer_depth=16) + self.submodules.core = LiteEthUDPIPCore(self.phy_model, mac_address + 1, ip_address + 1, 100000, with_icmp=False, dw=dw) + self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234, buffer_depth=8) self.submodules.sram = wishbone.SRAM(1024) self.submodules.interconnect = wishbone.InterconnectPointToPoint(self.etherbone.wishbone.bus, self.sram.bus) -def test_probe(dut): - packet = etherbone.EtherbonePacket() - packet.pf = 1 - packet.encode() - # TODO changing the number of padding bytes can break things with DW=64 - # this is because there is no proper last_be support in - # stream.StrideConverter, which is needed by LiteEthUDPCrossbar.get_port() - packet.bytes += bytes([0x00] * 8) # Add padding - - dut.etherbone_model.send(packet, ip_address + 1) - yield from dut.etherbone_model.receive(400) - tc.assertEqual(dut.etherbone_model.rx_packet.pr, 1) - - -def test_writes(dut, writes_datas): - for i, wd in enumerate(writes_datas): - yield dut.sram.mem[i].eq(0) - - writes = etherbone.EtherboneWrites(base_addr=0x1000, datas=writes_datas) - record = etherbone.EtherboneRecord() - record.writes = writes - record.reads = None - record.bca = 0 - record.rca = 0 - record.rff = 0 - record.cyc = 0 - record.wca = 0 - record.wff = 0 - record.byte_enable = 0xf - record.wcount = len(writes_datas) - record.rcount = 0 - - packet = etherbone.EtherbonePacket() - packet.records = [record] - dut.etherbone_model.send(packet) - - for i in range(256): - yield - if (yield dut.sram.bus.cyc): - break - for i in range(32): - yield - - for i, wd in enumerate(writes_datas): - val = (yield dut.sram.mem[i]) - tc.assertEqual(val, wd) - - -def test_reads(dut, writes_datas): - reads_addrs = [] - for i, wd in enumerate(writes_datas): - yield dut.sram.mem[i].eq(wd) - reads_addrs.append(0x1000 + i * 4) - - reads = etherbone.EtherboneReads(base_ret_addr=0x1000, addrs=reads_addrs) - record = etherbone.EtherboneRecord() - record.writes = None - record.reads = reads - record.bca = 0 - record.rca = 0 - record.rff = 0 - record.cyc = 0 - record.wca = 0 - record.wff = 0 - record.byte_enable = 0xf - record.wcount = 0 - record.rcount = len(reads_addrs) - - packet = etherbone.EtherbonePacket() - packet.records = [record] - dut.etherbone_model.send(packet) - yield from dut.etherbone_model.receive(400) - - reads_datas = dut.etherbone_model.rx_packet.records.pop().writes.get_datas() - - # check results - tc.assertEqual(writes_datas, reads_datas) - - -def main_generator(dut): - writes_datas = [((0xA + j) << 28) + j for j in range(6)] - yield from test_probe(dut) - yield from test_writes(dut, writes_datas) - yield from test_reads(dut, writes_datas) - - class TestEtherbone(unittest.TestCase): + def do_probe(self, dut): + packet = etherbone.EtherbonePacket() + packet.pf = 1 + packet.encode() + # TODO changing the number of padding bytes can break things with DW=64 + # this is because there is no proper last_be support in + # stream.StrideConverter, which is needed by LiteEthUDPCrossbar.get_port() + packet.bytes += bytes([0x00] * 8) # Add padding + + dut.etherbone_model.send(packet, ip_address + 1) + yield from dut.etherbone_model.receive(400) + self.assertEqual(dut.etherbone_model.rx_packet.pr, 1) + + def do_writes(self, dut, writes_datas): + for i, wd in enumerate(writes_datas): + yield dut.sram.mem[i].eq(0) + + writes = etherbone.EtherboneWrites(base_addr=0x1000, datas=writes_datas) + record = etherbone.EtherboneRecord() + record.writes = writes + record.reads = None + record.bca = 0 + record.rca = 0 + record.rff = 0 + record.cyc = 0 + record.wca = 0 + record.wff = 0 + record.byte_enable = 0xf + record.wcount = len(writes_datas) + record.rcount = 0 + + packet = etherbone.EtherbonePacket() + packet.records = [record] + dut.etherbone_model.send(packet) + + for i in range(256): + yield + if (yield dut.sram.bus.cyc): + break + for i in range(32): + yield + + for i, wd in enumerate(writes_datas): + val = (yield dut.sram.mem[i]) + self.assertEqual(val, wd) + + def do_reads(self, dut, writes_datas): + reads_addrs = [] + for i, wd in enumerate(writes_datas): + yield dut.sram.mem[i].eq(wd) + reads_addrs.append(0x1000 + i * 4) + + reads = etherbone.EtherboneReads(base_ret_addr=0x1000, addrs=reads_addrs) + record = etherbone.EtherboneRecord() + record.writes = None + record.reads = reads + record.bca = 0 + record.rca = 0 + record.rff = 0 + record.cyc = 0 + record.wca = 0 + record.wff = 0 + record.byte_enable = 0xf + record.wcount = 0 + record.rcount = len(reads_addrs) + + packet = etherbone.EtherbonePacket() + packet.records = [record] + dut.etherbone_model.send(packet) + yield from dut.etherbone_model.receive(400) + + reads_datas = dut.etherbone_model.rx_packet.records.pop().writes.get_datas() + + # check results + self.assertEqual(writes_datas, reads_datas) + + def main_generator(self, dut): + writes_datas = [((0xA + j) << 28) + j for j in range(6)] + + # push IP address into ARP table to speed up sim. + yield dut.core.arp.table.cached_valid.eq(1) + yield dut.core.arp.table.cached_ip_address.eq(ip_address) + yield dut.core.arp.table.cached_mac_address.eq(mac_address) + + with self.subTest("do_probe"): + yield from self.do_probe(dut) + with self.subTest("do_writes"): + yield from self.do_writes(dut, writes_datas) + with self.subTest("do_reads"): + yield from self.do_reads(dut, writes_datas) + def do_test(self, dut): generators = { - "sys" : [main_generator(dut)], + "sys" : [self.main_generator(dut)], "eth_tx": [dut.phy_model.phy_sink.generator(), dut.phy_model.generator()], "eth_rx": dut.phy_model.phy_source.generator() @@ -136,7 +140,7 @@ def do_test(self, dut): clocks = {"sys": 10, "eth_rx": 10, "eth_tx": 10} - run_simulation(dut, generators, clocks, vcd_name="sim.vcd") + run_simulation(dut, generators, clocks) #, vcd_name="sim.vcd") def test_etherbone_dw_8(self): self.do_test(DUT(8)) From 57f357fff899b311581a29b925943da8673fdddf Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 19 Apr 2022 13:22:23 +0200 Subject: [PATCH 20/27] mode/phy.py: add stall detection * because some phy's don't have buffers and don't like to be stalled * add stall checks to test_icmp.py --- test/model/phy.py | 14 +++++++------- test/test_icmp.py | 24 ++++++++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/test/model/phy.py b/test/model/phy.py index 16dc10fe..e07349db 100644 --- a/test/model/phy.py +++ b/test/model/phy.py @@ -17,14 +17,14 @@ def print_phy(s): # PHY Source --------------------------------------------------------------------------------------- class PHYSource(PacketStreamer): - def __init__(self, dw): - PacketStreamer.__init__(self, eth_phy_description(dw), dw=dw) + def __init__(self, dw, assertStall): + PacketStreamer.__init__(self, eth_phy_description(dw), dw=dw, assertStall=assertStall) # PHY Sink ----------------------------------------------------------------------------------------- class PHYSink(PacketLogger): - def __init__(self, dw): - PacketLogger.__init__(self, eth_phy_description(dw), dw=dw) + def __init__(self, dw, assertStall): + PacketLogger.__init__(self, eth_phy_description(dw), dw=dw, assertStall=assertStall) # PHY ---------------------------------------------------------------------------------------------- @@ -33,12 +33,12 @@ def __init__(self, dw): LINKTYPE_ETHERNET_MPACKET = 274 class PHY(Module): - def __init__(self, dw, debug=False, pcap_file=None): + def __init__(self, dw, debug=False, pcap_file=None, assertStall=False): self.dw = dw self.debug = debug - self.submodules.phy_source = PHYSource(dw) - self.submodules.phy_sink = PHYSink(dw) + self.submodules.phy_source = PHYSource(dw, assertStall) + self.submodules.phy_sink = PHYSink(dw, assertStall) self.source = self.phy_source.source self.sink = self.phy_sink.sink diff --git a/test/test_icmp.py b/test/test_icmp.py index cde4acbf..051e133c 100644 --- a/test/test_icmp.py +++ b/test/test_icmp.py @@ -49,11 +49,11 @@ def __init__(self, dw=8): self.clock_domains.eth_rx = ClockDomain() self.dw = dw - self.submodules.phy_model = phy.PHY(self.dw, debug=True, pcap_file='dump.pcap') - self.submodules.mac_model = mac.MAC(self.phy_model, debug=False, loopback=False) - self.submodules.arp_model = arp.ARP(self.mac_model, model_mac, model_ip, debug=False) - self.submodules.ip_model = ip.IP(self.mac_model, model_mac, model_ip, debug=False, loopback=False) - self.submodules.icmp_model = ICMP(self.ip_model, model_ip, debug=False) + self.submodules.phy_model = phy.PHY(self.dw, pcap_file='dump_icmp.pcap', assertStall=True) + self.submodules.mac_model = mac.MAC(self.phy_model) + self.submodules.arp_model = arp.ARP(self.mac_model, model_mac, model_ip) + self.submodules.ip_model = ip.IP(self.mac_model, model_mac, model_ip) + self.submodules.icmp_model = ICMP(self.ip_model, model_ip, debug=True) self.submodules.ip = LiteEthIPCore(self.phy_model, dut_mac, dut_ip, 100000, dw=dw) @@ -72,6 +72,11 @@ def main_generator(dut): global got_ping_reply tc = unittest.TestCase() + # push IP address into ARP table to speed up sim. + yield dut.ip.arp.table.cached_valid.eq(1) + yield dut.ip.arp.table.cached_ip_address.eq(model_ip) + yield dut.ip.arp.table.cached_mac_address.eq(model_mac) + # We expect a ping reply to this (after ARP query) send_icmp(dut) for i in range(512): @@ -90,6 +95,8 @@ def main_generator(dut): class TestICMP(unittest.TestCase): def work(self, dw): + global got_ping_reply + got_ping_reply = False dut = DUT(dw) generators = { "sys" : [main_generator(dut)], @@ -97,9 +104,10 @@ def work(self, dw): dut.phy_model.generator()], "eth_rx": dut.phy_model.phy_source.generator() } - clocks = {"sys": 10, - "eth_rx": 9, - "eth_tx": 9} + # f_sys must be >= f_eth_* + clocks = {"sys": 9, + "eth_rx": 10, + "eth_tx": 10} run_simulation(dut, generators, clocks, vcd_name=f'test_icmp_{dw}.vcd') def test_8(self): From e84963865d818950f4ec4f3b2a2685452580b828 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 19 Apr 2022 13:45:42 +0200 Subject: [PATCH 21/27] test_etherbone: add stall-check * 64 bit datapath needs to stall the PHY by design * need some buffer somewhere to avoid that with real hardware --- test/test_etherbone.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_etherbone.py b/test/test_etherbone.py index 64b55da3..4f12b181 100644 --- a/test/test_etherbone.py +++ b/test/test_etherbone.py @@ -24,7 +24,8 @@ class DUT(Module): def __init__(self, dw): - self.submodules.phy_model = phy.PHY(dw) #, pcap_file='dump_wishbone.pcap') + assertStall = dw != 64 + self.submodules.phy_model = phy.PHY(dw, assertStall=assertStall, pcap_file='dump_wishbone.pcap') self.submodules.mac_model = mac.MAC(self.phy_model) self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address) self.submodules.ip_model = ip.IP(self.mac_model, mac_address, ip_address) @@ -140,7 +141,7 @@ def do_test(self, dut): clocks = {"sys": 10, "eth_rx": 10, "eth_tx": 10} - run_simulation(dut, generators, clocks) #, vcd_name="sim.vcd") + run_simulation(dut, generators, clocks, vcd_name=f'test_etherbone.vcd') def test_etherbone_dw_8(self): self.do_test(DUT(8)) From 09d59c049ac404fc227d516af182893fb8f3293e Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 19 Apr 2022 15:59:07 +0200 Subject: [PATCH 22/27] mac/core.py: add FIFO to TxDatapath * to prevent stalling the PHY when using etherbone with DW=64 --- liteeth/core/__init__.py | 8 +++--- liteeth/mac/__init__.py | 6 +++-- liteeth/mac/anti_underflow.py | 46 +++++++++++++++++++++++++++++++++++ liteeth/mac/core.py | 18 +++++++++++--- test/test_etherbone.py | 15 +++++++++--- 5 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 liteeth/mac/anti_underflow.py diff --git a/liteeth/core/__init__.py b/liteeth/core/__init__.py index 26fc9f1f..fc14d8e7 100644 --- a/liteeth/core/__init__.py +++ b/liteeth/core/__init__.py @@ -14,9 +14,9 @@ # IP Core ------------------------------------------------------------------------------------------ class LiteEthIPCore(Module, AutoCSR): - def __init__(self, phy, mac_address, ip_address, clk_freq, with_icmp=True, dw=8): + def __init__(self, phy, mac_address, ip_address, clk_freq, with_icmp=True, dw=8, anti_underflow=0): ip_address = convert_ip(ip_address) - self.submodules.mac = LiteEthMAC(phy, dw, interface="crossbar", with_preamble_crc=True) + self.submodules.mac = LiteEthMAC(phy, dw, interface="crossbar", with_preamble_crc=True, anti_underflow=anti_underflow) self.submodules.arp = LiteEthARP(self.mac, mac_address, ip_address, clk_freq, dw=dw) self.submodules.ip = LiteEthIP(self.mac, mac_address, ip_address, self.arp.table, dw=dw) if with_icmp: @@ -25,8 +25,8 @@ def __init__(self, phy, mac_address, ip_address, clk_freq, with_icmp=True, dw=8) # UDP IP Core -------------------------------------------------------------------------------------- class LiteEthUDPIPCore(LiteEthIPCore): - def __init__(self, phy, mac_address, ip_address, clk_freq, with_icmp=True, dw=8): + def __init__(self, phy, mac_address, ip_address, clk_freq, with_icmp=True, dw=8, anti_underflow=0): ip_address = convert_ip(ip_address) LiteEthIPCore.__init__(self, phy, mac_address, ip_address, clk_freq, dw=dw, - with_icmp=with_icmp) + with_icmp=with_icmp, anti_underflow=anti_underflow) self.submodules.udp = LiteEthUDP(self.ip, ip_address, dw=dw) diff --git a/liteeth/mac/__init__.py b/liteeth/mac/__init__.py index b660e799..dd63cc56 100644 --- a/liteeth/mac/__init__.py +++ b/liteeth/mac/__init__.py @@ -21,7 +21,8 @@ def __init__(self, phy, dw, hw_mac = None, timestamp = None, full_memory_we = False, - with_sys_datapath = False): + with_sys_datapath = False, + anti_underflow = 0): assert dw%8 == 0 assert interface in ["crossbar", "wishbone", "hybrid"] @@ -31,7 +32,8 @@ def __init__(self, phy, dw, phy = phy, dw = dw, with_sys_datapath = with_sys_datapath, - with_preamble_crc = with_preamble_crc + with_preamble_crc = with_preamble_crc, + anti_underflow = anti_underflow ) self.csrs = [] if interface == "crossbar": diff --git a/liteeth/mac/anti_underflow.py b/liteeth/mac/anti_underflow.py new file mode 100644 index 00000000..cb9a314b --- /dev/null +++ b/liteeth/mac/anti_underflow.py @@ -0,0 +1,46 @@ +# +# This file is part of LiteEth. +# +# SPDX-License-Identifier: BSD-2-Clause + +import math + +from liteeth.common import * + +# MAC Gap ------------------------------------------------------------------------------------------ + +class LiteEthAntiUnderflow(Module): + def __init__(self, dw, depth=32): + ''' + buffers a whole packet and releases it at once + workaround for driving PHYs with wide datapaths which expect a + continuous stream + ''' + self.sink = sink = stream.Endpoint(eth_phy_description(dw)) + self.source = source = stream.Endpoint(eth_phy_description(dw)) + + # # # + + self.submodules.fifo = fifo = stream.SyncFIFO( + eth_phy_description(dw), + depth, + buffered=True + ) + + self.comb += [ + sink.connect(fifo.sink), + fifo.source.connect(source, omit=['valid', 'ready']) + ] + + self.submodules.fsm = fsm = FSM(reset_state="STORE") + fsm.act("STORE", + If(sink.valid & sink.last | (fifo.level >= fifo.depth - 1), + NextState("FLUSH") + ) + ) + fsm.act("FLUSH", + fifo.source.connect(source, keep=['valid', 'ready']), + If(fifo.source.valid & fifo.source.last, + NextState("STORE") + ) + ) diff --git a/liteeth/mac/core.py b/liteeth/mac/core.py index 9c7c641b..42ed635c 100644 --- a/liteeth/mac/core.py +++ b/liteeth/mac/core.py @@ -9,6 +9,7 @@ from liteeth.common import * from liteeth.mac import gap, preamble, crc, padding, last_be +from liteeth.mac.anti_underflow import LiteEthAntiUnderflow from liteeth.phy.model import LiteEthPHYModel from migen.genlib.cdc import PulseSynchronizer @@ -21,7 +22,8 @@ class LiteEthMACCore(Module, AutoCSR): def __init__(self, phy, dw, with_sys_datapath = False, with_preamble_crc = True, - with_padding = True): + with_padding = True, + anti_underflow = 0): # Endpoints. self.sink = stream.Endpoint(eth_phy_description(dw)) @@ -100,6 +102,12 @@ def add_gap(self): self.submodules += tx_gap self.pipeline.append(tx_gap) + def add_anti_underflow(self, depth): + au = LiteEthAntiUnderflow(phy_dw, depth) + au = ClockDomainsRenamer("eth_tx")(au) + self.submodules += au + self.pipeline.append(au) + def do_finalize(self): self.submodules += stream.Pipeline(*self.pipeline) @@ -127,10 +135,12 @@ def do_finalize(self): # Gap insertion has to occurr in phy tx domain to ensure gap is correctly maintained if not getattr(phy, "integrated_ifg_inserter", False): tx_datapath.add_gap() + if anti_underflow > 0: + tx_datapath.add_anti_underflow(anti_underflow) tx_datapath.pipeline.append(phy) - # print("tx_datapath.pipeline:") - # for p in tx_datapath.pipeline: - # print(p) + print("tx_datapath.pipeline:") + for p in tx_datapath.pipeline: + print(p) self.submodules.tx_datapath = tx_datapath # RX Data-Path (PHY --> Core). diff --git a/test/test_etherbone.py b/test/test_etherbone.py index 4f12b181..31b2288a 100644 --- a/test/test_etherbone.py +++ b/test/test_etherbone.py @@ -24,15 +24,22 @@ class DUT(Module): def __init__(self, dw): - assertStall = dw != 64 - self.submodules.phy_model = phy.PHY(dw, assertStall=assertStall, pcap_file='dump_wishbone.pcap') + self.submodules.phy_model = phy.PHY(dw, assertStall=True, pcap_file='dump_wishbone.pcap') self.submodules.mac_model = mac.MAC(self.phy_model) self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address) self.submodules.ip_model = ip.IP(self.mac_model, mac_address, ip_address) self.submodules.udp_model = udp.UDP(self.ip_model, ip_address) self.submodules.etherbone_model = etherbone.Etherbone(self.udp_model, debug=False) - self.submodules.core = LiteEthUDPIPCore(self.phy_model, mac_address + 1, ip_address + 1, 100000, with_icmp=False, dw=dw) + self.submodules.core = LiteEthUDPIPCore( + self.phy_model, + mac_address + 1, + ip_address + 1, + 100000, + with_icmp=False, + dw=dw, + anti_underflow=4 + ) self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234, buffer_depth=8) self.submodules.sram = wishbone.SRAM(1024) @@ -117,7 +124,7 @@ def do_reads(self, dut, writes_datas): self.assertEqual(writes_datas, reads_datas) def main_generator(self, dut): - writes_datas = [((0xA + j) << 28) + j for j in range(6)] + writes_datas = [((0xA + j) << 24) + j for j in range(8)] # push IP address into ARP table to speed up sim. yield dut.core.arp.table.cached_valid.eq(1) From 677c6bf0212678ddd70ceca5ab62deeb6113cf5d Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Thu, 21 Apr 2022 11:51:18 +0200 Subject: [PATCH 23/27] test_etherbone: check for infinite send loop * due to StrideConverter bug --- test/test_etherbone.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/test_etherbone.py b/test/test_etherbone.py index 31b2288a..1becea41 100644 --- a/test/test_etherbone.py +++ b/test/test_etherbone.py @@ -54,7 +54,7 @@ def do_probe(self, dut): # TODO changing the number of padding bytes can break things with DW=64 # this is because there is no proper last_be support in # stream.StrideConverter, which is needed by LiteEthUDPCrossbar.get_port() - packet.bytes += bytes([0x00] * 8) # Add padding + packet.bytes += bytes([0x00] * 4) # Add padding dut.etherbone_model.send(packet, ip_address + 1) yield from dut.etherbone_model.receive(400) @@ -93,6 +93,9 @@ def do_writes(self, dut, writes_datas): val = (yield dut.sram.mem[i]) self.assertEqual(val, wd) + # Check for infinite packet send loop (crossbar bug) + self.assertEqual((yield dut.etherbone.record.receiver.fsm.state), 0) + def do_reads(self, dut, writes_datas): reads_addrs = [] for i, wd in enumerate(writes_datas): @@ -118,13 +121,16 @@ def do_reads(self, dut, writes_datas): dut.etherbone_model.send(packet) yield from dut.etherbone_model.receive(400) + # Check for infinite packet send loop (crossbar bug) + self.assertEqual((yield dut.etherbone.record.receiver.fsm.state), 0) + reads_datas = dut.etherbone_model.rx_packet.records.pop().writes.get_datas() # check results self.assertEqual(writes_datas, reads_datas) def main_generator(self, dut): - writes_datas = [((0xA + j) << 24) + j for j in range(8)] + writes_datas = [((0xA + j) << 24) + j for j in range(4)] # push IP address into ARP table to speed up sim. yield dut.core.arp.table.cached_valid.eq(1) @@ -134,8 +140,10 @@ def main_generator(self, dut): with self.subTest("do_probe"): yield from self.do_probe(dut) with self.subTest("do_writes"): + yield from self.do_writes(dut, [writes_datas[0]]) yield from self.do_writes(dut, writes_datas) with self.subTest("do_reads"): + yield from self.do_reads(dut, [writes_datas[0]]) yield from self.do_reads(dut, writes_datas) def do_test(self, dut): From a151d97f990fa0c722709c44b6443d8fbe652cb6 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 26 Apr 2022 13:52:06 +0200 Subject: [PATCH 24/27] test_etherbone.py: comments, remove antiUnderflow parameter --- test/test_etherbone.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/test_etherbone.py b/test/test_etherbone.py index 1becea41..47df48ae 100644 --- a/test/test_etherbone.py +++ b/test/test_etherbone.py @@ -24,7 +24,7 @@ class DUT(Module): def __init__(self, dw): - self.submodules.phy_model = phy.PHY(dw, assertStall=True, pcap_file='dump_wishbone.pcap') + self.submodules.phy_model = phy.PHY(dw, assertStall=dw < 64, pcap_file='dump_wishbone.pcap') self.submodules.mac_model = mac.MAC(self.phy_model) self.submodules.arp_model = arp.ARP(self.mac_model, mac_address, ip_address) self.submodules.ip_model = ip.IP(self.mac_model, mac_address, ip_address) @@ -37,8 +37,7 @@ def __init__(self, dw): ip_address + 1, 100000, with_icmp=False, - dw=dw, - anti_underflow=4 + dw=dw ) self.submodules.etherbone = LiteEthEtherbone(self.core.udp, 0x1234, buffer_depth=8) @@ -51,9 +50,6 @@ def do_probe(self, dut): packet = etherbone.EtherbonePacket() packet.pf = 1 packet.encode() - # TODO changing the number of padding bytes can break things with DW=64 - # this is because there is no proper last_be support in - # stream.StrideConverter, which is needed by LiteEthUDPCrossbar.get_port() packet.bytes += bytes([0x00] * 4) # Add padding dut.etherbone_model.send(packet, ip_address + 1) @@ -93,7 +89,7 @@ def do_writes(self, dut, writes_datas): val = (yield dut.sram.mem[i]) self.assertEqual(val, wd) - # Check for infinite packet send loop (crossbar bug) + # Check for infinite packet send loop (last_be bug in StrideConverter) self.assertEqual((yield dut.etherbone.record.receiver.fsm.state), 0) def do_reads(self, dut, writes_datas): From c4689fa1ae26434a70a74c39fbe0494046c64707 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 26 Apr 2022 14:21:07 +0200 Subject: [PATCH 25/27] clean-up for pull request --- .github/workflows/ci.yml | 2 +- debug_ping.gtkw | 356 ---------------------------------- liteeth/frontend/etherbone.py | 2 +- 3 files changed, 2 insertions(+), 358 deletions(-) delete mode 100644 debug_ping.gtkw diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac588316..797bc3ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: # Install (n)Migen / LiteX / Cores - name: Install LiteX run: | - wget https://github.com/yetifrisstlama/litex/raw/yfl/litex_setup.py + wget https://raw.githubusercontent.com/enjoy-digital/litex/master/litex_setup.py python3 litex_setup.py init install --user # Install Project diff --git a/debug_ping.gtkw b/debug_ping.gtkw deleted file mode 100644 index 5f6ba741..00000000 --- a/debug_ping.gtkw +++ /dev/null @@ -1,356 +0,0 @@ -[dumpfile] "sim.vcd" -[timestart] 0 -[size] 2550 1387 -[pos] -1 -690 -*-9.000000 120 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -[sst_width] 368 -[signals_width] 602 -[sst_expanded] 1 -[sst_vpaned_height] 424 -@28 -sys_clk -@c00200 --mac_core_cdc -@28 -mac_core_cdc_sink_first -mac_core_cdc_sink_last -@22 -mac_core_cdc_sink_payload_data[31:0] -mac_core_cdc_sink_payload_error[3:0] -mac_core_cdc_sink_payload_last_be[3:0] -@28 -mac_core_cdc_sink_ready -mac_core_cdc_sink_valid -@200 -- -@28 -mac_core_cdc_source_first -mac_core_cdc_source_last -@22 -mac_core_cdc_source_payload_data[31:0] -mac_core_cdc_source_payload_error[3:0] -mac_core_cdc_source_payload_last_be[3:0] -@28 -mac_core_cdc_source_ready -mac_core_cdc_source_valid -@1401200 --mac_core_cdc -@800200 --mac_core_rx_cdc -@28 -mac_core_rx_cdc_sink_sink_first -mac_core_rx_cdc_sink_sink_last -@22 -mac_core_rx_cdc_sink_sink_payload_data[31:0] -mac_core_rx_cdc_sink_sink_payload_error[3:0] -mac_core_rx_cdc_sink_sink_payload_last_be[3:0] -@28 -mac_core_rx_cdc_sink_sink_ready -mac_core_rx_cdc_sink_sink_valid -@1000200 --mac_core_rx_cdc -@c00200 --last_be -@28 -mac_core_tx_last_be_sink_first -mac_core_tx_last_be_sink_last -@22 -mac_core_tx_last_be_sink_payload_data[31:0] -mac_core_tx_last_be_sink_payload_error[3:0] -mac_core_tx_last_be_sink_payload_last_be[3:0] -@28 -mac_core_tx_last_be_sink_ready -mac_core_tx_last_be_sink_valid -@200 -- -@28 -mac_core_tx_last_be_source_first -mac_core_tx_last_be_source_last -@22 -mac_core_tx_last_be_source_payload_data[31:0] -mac_core_tx_last_be_source_payload_error[3:0] -mac_core_tx_last_be_source_payload_last_be[3:0] -@28 -mac_core_tx_last_be_source_ready -mac_core_tx_last_be_source_valid -@1401200 --last_be -@c00200 --PADDING -@28 -mac_core_tx_padding_source_first -mac_core_tx_padding_source_last -@22 -mac_core_tx_padding_source_payload_data[31:0] -mac_core_tx_padding_source_payload_error[3:0] -mac_core_tx_padding_source_payload_last_be[3:0] -@28 -mac_core_tx_padding_source_ready -mac_core_tx_padding_source_valid -@1401200 --PADDING -@c00200 --CRC -@28 -mac_core_liteethmaccrc32inserter_source_first -mac_core_liteethmaccrc32inserter_source_last -mac_core_liteethmaccrc32inserter_source_ready -mac_core_liteethmaccrc32inserter_source_valid -@1401200 --CRC -@c00200 --preamble -@28 -mac_core_tx_preamble_source_first -mac_core_tx_preamble_source_last -@22 -mac_core_tx_preamble_source_payload_data[31:0] -mac_core_tx_preamble_source_payload_error[3:0] -mac_core_tx_preamble_source_payload_last_be[3:0] -@28 -mac_core_tx_preamble_source_ready -mac_core_tx_preamble_source_valid -@1401200 --preamble -@c00200 --ICMP_TX -@28 -icmp_tx_source_source_first -icmp_tx_source_source_last -@22 -icmp_tx_source_source_param_ip_address[31:0] -icmp_tx_source_source_param_length[15:0] -icmp_tx_source_source_param_protocol[7:0] -icmp_tx_source_source_payload_data[31:0] -icmp_tx_source_source_payload_error[3:0] -icmp_tx_source_source_payload_last_be[3:0] -@28 -icmp_tx_source_source_ready -icmp_tx_source_source_valid -@1401200 --ICMP_TX -@c00200 --mac_gap -@28 -mac_core_tx_gap_source_first -mac_core_tx_gap_source_last -@22 -mac_core_tx_gap_source_payload_data[31:0] -mac_core_tx_gap_source_payload_error[3:0] -mac_core_tx_gap_source_payload_last_be[3:0] -@28 -mac_core_tx_gap_source_ready -mac_core_tx_gap_source_valid -@1401200 --mac_gap -@c00200 --mac_source -@28 -mac_core_source_source_first -mac_core_source_source_last -@22 -mac_core_source_source_payload_data[31:0] -mac_core_source_source_payload_error[3:0] -mac_core_source_source_payload_last_be[3:0] -@28 -mac_core_source_source_ready -mac_core_source_source_valid -@1401200 --mac_source -@c00200 --arp_tx -@28 -arp_tx_source_source_first -arp_tx_source_source_last -@22 -arp_tx_source_source_payload_data[31:0] -arp_tx_source_source_payload_error[3:0] -arp_tx_source_source_payload_ethernet_type[15:0] -arp_tx_source_source_payload_last_be[3:0] -arp_tx_source_source_payload_sender_mac[47:0] -arp_tx_source_source_payload_target_mac[47:0] -@28 -arp_tx_source_source_ready -arp_tx_source_source_valid -@1401200 --arp_tx -@c00200 --arp_rx -@28 -arp_rx_sink_sink_first -arp_rx_sink_sink_last -@22 -arp_rx_sink_sink_payload_data[31:0] -arp_rx_sink_sink_payload_error[3:0] -arp_rx_sink_sink_payload_ethernet_type[15:0] -arp_rx_sink_sink_payload_last_be[3:0] -arp_rx_sink_sink_payload_sender_mac[47:0] -arp_rx_sink_sink_payload_target_mac[47:0] -@28 -arp_rx_sink_sink_ready -arp_rx_sink_sink_valid -@1401200 --arp_rx -@c00200 --IP -@28 -ip_tx_source_source_first -ip_tx_source_source_last -@22 -ip_tx_source_source_payload_data[31:0] -ip_tx_source_source_payload_error[3:0] -ip_tx_source_source_payload_ethernet_type[15:0] -ip_tx_source_source_payload_last_be[3:0] -ip_tx_source_source_payload_sender_mac[47:0] -ip_tx_source_source_payload_target_mac[47:0] -@28 -ip_tx_source_source_ready -ip_tx_source_source_valid -@200 -- -@28 -ip_rx_source_source_first -ip_rx_source_source_last -@22 -ip_rx_source_source_param_ip_address[31:0] -ip_rx_source_source_param_length[15:0] -ip_rx_source_source_param_protocol[7:0] -ip_rx_source_source_payload_data[31:0] -ip_rx_source_source_payload_error[3:0] -ip_rx_source_source_payload_last_be[3:0] -@28 -ip_rx_source_source_ready -ip_rx_source_source_valid -@1401200 --IP -@c00200 --IP_PACKETIZER -@28 -ip_tx_packetizer_source_first -ip_tx_packetizer_source_last -ip_tx_packetizer_source_last_a -ip_tx_packetizer_source_last_b -ip_tx_packetizer_source_last_s -@22 -ip_tx_packetizer_source_payload_data[31:0] -ip_tx_packetizer_source_payload_error[3:0] -ip_tx_packetizer_source_payload_ethernet_type[15:0] -ip_tx_packetizer_source_payload_last_be[3:0] -ip_tx_packetizer_source_payload_sender_mac[47:0] -ip_tx_packetizer_source_payload_target_mac[47:0] -@28 -ip_tx_packetizer_source_ready -ip_tx_packetizer_source_valid -@200 -- -@28 -ip_tx_packetizer_sink_last -@22 -ip_tx_packetizer_sink_payload_last_be[3:0] -@1401200 --IP_PACKETIZER -@c00200 --ARP_PACKETIZER -@28 -arp_tx_packetizer_source_first -arp_tx_packetizer_source_last -arp_tx_packetizer_source_last_a -arp_tx_packetizer_source_last_b -arp_tx_packetizer_source_last_s -@22 -arp_tx_packetizer_source_payload_data[31:0] -arp_tx_packetizer_source_payload_error[3:0] -arp_tx_packetizer_source_payload_ethernet_type[15:0] -arp_tx_packetizer_source_payload_last_be[3:0] -arp_tx_packetizer_source_payload_sender_mac[47:0] -arp_tx_packetizer_source_payload_target_mac[47:0] -@28 -arp_tx_packetizer_source_ready -arp_tx_packetizer_source_valid -@200 -- -@28 -arp_tx_packetizer_sink_last -@22 -arp_tx_packetizer_sink_payload_last_be[3:0] -@1401200 --ARP_PACKETIZER -@c00200 --ICMP_TX_PACKETIZER -@28 -icmp_tx_packetizer_source_first -icmp_tx_packetizer_source_last -icmp_tx_packetizer_source_last_a -icmp_tx_packetizer_source_last_b -icmp_tx_packetizer_source_last_s -@22 -icmp_tx_packetizer_source_param_ip_address[31:0] -icmp_tx_packetizer_source_param_length[15:0] -icmp_tx_packetizer_source_param_protocol[7:0] -icmp_tx_packetizer_source_payload_data[31:0] -icmp_tx_packetizer_source_payload_error[3:0] -icmp_tx_packetizer_source_payload_last_be[3:0] -@28 -icmp_tx_packetizer_source_ready -icmp_tx_packetizer_source_valid -@1401200 --ICMP_TX_PACKETIZER -@c00200 --echo_source -@28 -icmp_echo_sink_sink_first -icmp_echo_sink_sink_last -@22 -icmp_echo_sink_sink_param_checksum[15:0] -icmp_echo_sink_sink_param_code[7:0] -icmp_echo_sink_sink_param_ident[15:0] -icmp_echo_sink_sink_param_ip_address[31:0] -icmp_echo_sink_sink_param_length[15:0] -icmp_echo_sink_sink_param_msgtype[7:0] -icmp_echo_sink_sink_param_sequence[15:0] -icmp_echo_sink_sink_payload_data[31:0] -icmp_echo_sink_sink_payload_error[3:0] -icmp_echo_sink_sink_payload_last_be[3:0] -@28 -icmp_echo_sink_sink_ready -icmp_echo_sink_sink_valid -@200 -- -@28 -icmp_echo_source_source_first -icmp_echo_source_source_last -@22 -icmp_echo_source_source_param_checksum[15:0] -icmp_echo_source_source_param_code[7:0] -icmp_echo_source_source_param_ident[15:0] -icmp_echo_source_source_param_ip_address[31:0] -icmp_echo_source_source_param_length[15:0] -icmp_echo_source_source_param_msgtype[7:0] -icmp_echo_source_source_param_sequence[15:0] -icmp_echo_source_source_payload_data[31:0] -icmp_echo_source_source_payload_error[3:0] -icmp_echo_source_source_payload_last_be[3:0] -@28 -icmp_echo_source_source_ready -icmp_echo_source_source_valid -@1401200 --echo_source -@800201 --icmp_rx -@29 -icmp_rx_sink_sink_first -icmp_rx_sink_sink_last -@23 -icmp_rx_sink_sink_param_ip_address[31:0] -icmp_rx_sink_sink_param_length[15:0] -icmp_rx_sink_sink_param_protocol[7:0] -icmp_rx_sink_sink_payload_data[31:0] -icmp_rx_sink_sink_payload_error[3:0] -icmp_rx_sink_sink_payload_last_be[3:0] -@29 -icmp_rx_sink_sink_ready -icmp_rx_sink_sink_valid -@1000201 --icmp_rx -[pattern_trace] 1 -[pattern_trace] 0 diff --git a/liteeth/frontend/etherbone.py b/liteeth/frontend/etherbone.py index 64ce60a8..29a888f7 100644 --- a/liteeth/frontend/etherbone.py +++ b/liteeth/frontend/etherbone.py @@ -140,7 +140,7 @@ def __init__(self): # # # self.submodules.fifo = fifo = PacketFIFO(eth_etherbone_packet_user_description(32), - payload_depth = 2, # Needed for DW=64 + 8 bytes padding in probe packet + payload_depth = 1, param_depth = 1, buffered = False ) From 250d9e04a792b3d55be8406bc9ec4fb93f9d7f63 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 26 Apr 2022 14:35:37 +0200 Subject: [PATCH 26/27] Revert "mac/core.py: add FIFO to TxDatapath" This reverts commit 09d59c049ac404fc227d516af182893fb8f3293e. To my surprise, stalling the XGMII PHY seems to work fine on hardware. The anti-underflow FIFO does not seem to be required. I haven't yet investigated why. --- liteeth/mac/__init__.py | 6 ++--- liteeth/mac/anti_underflow.py | 46 ----------------------------------- liteeth/mac/core.py | 18 +++----------- 3 files changed, 6 insertions(+), 64 deletions(-) delete mode 100644 liteeth/mac/anti_underflow.py diff --git a/liteeth/mac/__init__.py b/liteeth/mac/__init__.py index dd63cc56..b660e799 100644 --- a/liteeth/mac/__init__.py +++ b/liteeth/mac/__init__.py @@ -21,8 +21,7 @@ def __init__(self, phy, dw, hw_mac = None, timestamp = None, full_memory_we = False, - with_sys_datapath = False, - anti_underflow = 0): + with_sys_datapath = False): assert dw%8 == 0 assert interface in ["crossbar", "wishbone", "hybrid"] @@ -32,8 +31,7 @@ def __init__(self, phy, dw, phy = phy, dw = dw, with_sys_datapath = with_sys_datapath, - with_preamble_crc = with_preamble_crc, - anti_underflow = anti_underflow + with_preamble_crc = with_preamble_crc ) self.csrs = [] if interface == "crossbar": diff --git a/liteeth/mac/anti_underflow.py b/liteeth/mac/anti_underflow.py deleted file mode 100644 index cb9a314b..00000000 --- a/liteeth/mac/anti_underflow.py +++ /dev/null @@ -1,46 +0,0 @@ -# -# This file is part of LiteEth. -# -# SPDX-License-Identifier: BSD-2-Clause - -import math - -from liteeth.common import * - -# MAC Gap ------------------------------------------------------------------------------------------ - -class LiteEthAntiUnderflow(Module): - def __init__(self, dw, depth=32): - ''' - buffers a whole packet and releases it at once - workaround for driving PHYs with wide datapaths which expect a - continuous stream - ''' - self.sink = sink = stream.Endpoint(eth_phy_description(dw)) - self.source = source = stream.Endpoint(eth_phy_description(dw)) - - # # # - - self.submodules.fifo = fifo = stream.SyncFIFO( - eth_phy_description(dw), - depth, - buffered=True - ) - - self.comb += [ - sink.connect(fifo.sink), - fifo.source.connect(source, omit=['valid', 'ready']) - ] - - self.submodules.fsm = fsm = FSM(reset_state="STORE") - fsm.act("STORE", - If(sink.valid & sink.last | (fifo.level >= fifo.depth - 1), - NextState("FLUSH") - ) - ) - fsm.act("FLUSH", - fifo.source.connect(source, keep=['valid', 'ready']), - If(fifo.source.valid & fifo.source.last, - NextState("STORE") - ) - ) diff --git a/liteeth/mac/core.py b/liteeth/mac/core.py index 42ed635c..9c7c641b 100644 --- a/liteeth/mac/core.py +++ b/liteeth/mac/core.py @@ -9,7 +9,6 @@ from liteeth.common import * from liteeth.mac import gap, preamble, crc, padding, last_be -from liteeth.mac.anti_underflow import LiteEthAntiUnderflow from liteeth.phy.model import LiteEthPHYModel from migen.genlib.cdc import PulseSynchronizer @@ -22,8 +21,7 @@ class LiteEthMACCore(Module, AutoCSR): def __init__(self, phy, dw, with_sys_datapath = False, with_preamble_crc = True, - with_padding = True, - anti_underflow = 0): + with_padding = True): # Endpoints. self.sink = stream.Endpoint(eth_phy_description(dw)) @@ -102,12 +100,6 @@ def add_gap(self): self.submodules += tx_gap self.pipeline.append(tx_gap) - def add_anti_underflow(self, depth): - au = LiteEthAntiUnderflow(phy_dw, depth) - au = ClockDomainsRenamer("eth_tx")(au) - self.submodules += au - self.pipeline.append(au) - def do_finalize(self): self.submodules += stream.Pipeline(*self.pipeline) @@ -135,12 +127,10 @@ def do_finalize(self): # Gap insertion has to occurr in phy tx domain to ensure gap is correctly maintained if not getattr(phy, "integrated_ifg_inserter", False): tx_datapath.add_gap() - if anti_underflow > 0: - tx_datapath.add_anti_underflow(anti_underflow) tx_datapath.pipeline.append(phy) - print("tx_datapath.pipeline:") - for p in tx_datapath.pipeline: - print(p) + # print("tx_datapath.pipeline:") + # for p in tx_datapath.pipeline: + # print(p) self.submodules.tx_datapath = tx_datapath # RX Data-Path (PHY --> Core). From 00ed2c7dfdce174a6be899d3c8a5ff64c3cbf713 Mon Sep 17 00:00:00 2001 From: Michael Betz Date: Tue, 26 Apr 2022 14:40:44 +0200 Subject: [PATCH 27/27] LiteEthMacCore: remove debug print --- liteeth/mac/core.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/liteeth/mac/core.py b/liteeth/mac/core.py index 9c7c641b..21d5858f 100644 --- a/liteeth/mac/core.py +++ b/liteeth/mac/core.py @@ -128,9 +128,6 @@ def do_finalize(self): if not getattr(phy, "integrated_ifg_inserter", False): tx_datapath.add_gap() tx_datapath.pipeline.append(phy) - # print("tx_datapath.pipeline:") - # for p in tx_datapath.pipeline: - # print(p) self.submodules.tx_datapath = tx_datapath # RX Data-Path (PHY --> Core).