Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add VLAN (802.1q) tag support for all datawidths of LiteethMAC #134

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions bench/sim_xgmii_vlan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from liteeth.phy.xgmii import LiteEthPHYXGMII
from liteeth.core import LiteEthVLANUDPIPCore
from liteeth.common import convert_ip
from litex.tools.litex_sim import *


class VLANSim(SimSoC):
def add_tx_test(self, cd, udp_port, dst_ip, xg_counter, dw=64, always_xmit=True):
send_pkt = Signal(reset=0)
if always_xmit:
send_pkt_counter_d = Signal()
cd += [
send_pkt_counter_d.eq(xg_counter[18]),
send_pkt.eq(send_pkt_counter_d ^ xg_counter[18])
]

bytes_per_word = dw // 8
sink_counter = Signal(16)
SINK_LENGTH = 8 * bytes_per_word # 8 words
shift = log2_int(bytes_per_word) # bits required to represent bytes per word
words_per_packet = SINK_LENGTH >> shift
# Note the clkmgt domain
cd += [
If(send_pkt,
sink_counter.eq(words_per_packet)),
If((sink_counter > 0) & (udp_port.sink.ready == 1),
sink_counter.eq(sink_counter - 1)
).Else(
udp_port.sink.valid.eq(0),
udp_port.sink.last.eq(0)
),
udp_port.sink.valid.eq(sink_counter > 0),
udp_port.sink.last.eq(sink_counter == 1),
If(sink_counter == 1,
udp_port.sink.last_be.eq(0x80)
).Else(
udp_port.sink.last_be.eq(0x0)
)
]

self.comb += [
# param
udp_port.sink.src_port.eq(3000),
udp_port.sink.dst_port.eq(7778),
udp_port.sink.ip_address.eq(convert_ip(dst_ip)),
udp_port.sink.length.eq(SINK_LENGTH),

# payload
udp_port.sink.data.eq(Cat(0xc0ffeec1ffee, sink_counter)),
udp_port.sink.error.eq(0)
]

def __init__(self, phy_model, host_ip="192.168.2.100", host_udp_port=2000, **soc_kwargs):
SimSoC.__init__(self,
cpu_type = None,
integrated_rom_size = 0x10000,
uart_name = "sim",
with_sdram = False,
with_ethernet = False,
with_etherbone = False,
etherbone_mac_address = 0x10e2d5000001,
etherbone_ip_address = "192.168.2.50",
sdram_module = "MT48LC16M16",
sdram_data_width = 8,
with_sdcard = False,
)

DW = 64 if phy_model == "xgmii" else 8
if DW == 64:
self.submodules.ethphy = LiteEthPHYXGMII(None, self.platform.request("xgmii_eth", 0), model=True)
else:
self.submodules.ethphy = LiteEthPHYGMII(None, self.platform.request("gmii_eth", 0), model=True)
self.submodules.udp_core = LiteEthVLANUDPIPCore(self.ethphy,
0x10e2d5000001,
convert_ip("192.168.2.50"),
self.sys_clk_freq,
with_ip_broadcast=False,
dw=DW)
udp_core = self.udp_core.add_vlan(vlan_ip="192.168.3.50", vlan_id=2001)
udp_port0 = udp_core.crossbar.get_port(3000, DW)
counter = Signal(28)
self.sync += counter.eq(counter+1)
self.add_tx_test(self.sync, udp_port0, "192.168.3.100", counter, dw=DW)

udp_core = self.udp_core.add_vlan(vlan_ip="192.168.4.50", vlan_id=2002)
udp_port1 = udp_core.crossbar.get_port(3000, DW)
self.add_tx_test(self.sync, udp_port1, "192.168.4.100", counter, dw=DW)


def main():
from litex.soc.integration.soc import LiteXSoCArgumentParser
parser = LiteXSoCArgumentParser(description="LiteX SoC Simulation utility")
parser.set_platform(SimPlatform)
sim_args(parser)
args = parser.parse_args()

soc_kwargs = soc_core_argdict(args)

sys_clk_freq = int(1e6)
sim_config = SimConfig()
sim_config.add_clocker("sys_clk", freq_hz=sys_clk_freq)
sim_config.add_module("serial2console", "serial")
if args.ethernet_phy_model == "xgmii":
sim_config.add_module("xgmii_ethernet", "xgmii_eth", args={"interface": "tap0", "ip": "192.168.2.100"})
elif args.ethernet_phy_model == "gmii":
sim_config.add_module("gmii_ethernet", "gmii_eth", args={"interface": "tap0", "ip": "192.168.2.100"})

# SoC ------------------------------------------------------------------------------------------
soc = VLANSim(args.ethernet_phy_model, **soc_kwargs)

def pre_run_callback(vns):
if args.trace:
generate_gtkw_savefile(builder, vns, args.trace_fst)

# Build/Run ------------------------------------------------------------------------------------
builder = Builder(soc, **parser.builder_argdict)
builder.build(sim_config=sim_config,
interactive = not args.non_interactive,
pre_run_callback = pre_run_callback,
**parser.toolchain_argdict,
)


# Allows you to test on Debian like system:
# sudo ip link add link tap0 name tap0.2001 type vlan id 2001 && sudo ip addr add 192.168.3.100/24 dev tap0.2001 && sudo ip link set dev tap0.2001 up && sudo ip link add link tap0 name tap0.2002 type vlan id 2002 && sudo ip addr add 192.168.4.100/24 dev tap0.2002 && sudo ip link set dev tap0.2002 up && ip a && sudo tcpdump -i tap0

if __name__ == "__main__":
main()
24 changes: 22 additions & 2 deletions liteeth/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@
eth_preamble = 0xd555555555555555
buffer_depth = 2**log2_int(eth_mtu, need_pow2=False)

ethernet_type_ip = 0x800
ethernet_type_arp = 0x806
ethernet_type_ip = 0x0800
ethernet_type_arp = 0x0806
ethernet_8021q_tpid = 0x8100

# MAC Constants/Header -----------------------------------------------------------------------------

Expand All @@ -38,6 +39,14 @@
}
mac_header = Header(mac_header_fields, mac_header_length, swap_field_bytes=True)

vlan_mac_header_length = 4
vlan_mac_header_fields = {
"vid": HeaderField(0, 0, 16),
"ethernet_type": HeaderField(2, 0, 16)
}

vlan_mac_header = Header(vlan_mac_header_fields, vlan_mac_header_length, swap_field_bytes=True)

# ARP Constants/Header -----------------------------------------------------------------------------

arp_hwtype_ethernet = 0x0001
Expand All @@ -59,6 +68,7 @@
"target_ip": HeaderField(24, 0, 32)
}
arp_header = Header(arp_header_fields, arp_header_length, swap_field_bytes=True)
arp_vlan_min_length = eth_min_frame_length - eth_fcs_length - mac_header_length - vlan_mac_header_length

# Broadcast Constants ------------------------------------------------------------------------------

Expand Down Expand Up @@ -184,6 +194,16 @@ def eth_mac_description(dw):
]
return EndpointDescription(payload_layout)

def eth_mac_vlan_description(dw):
payload_layout = vlan_mac_header.get_layout() + [
("target_mac", 48),
("sender_mac", 48),
("data", dw),
("last_be", dw//8),
("error", dw//8)
]
return EndpointDescription(payload_layout)

# ARP
def eth_arp_description(dw):
param_layout = arp_header.get_layout()
Expand Down
58 changes: 58 additions & 0 deletions liteeth/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from liteeth.core.ip import LiteEthIP
from liteeth.core.udp import LiteEthUDP
from liteeth.core.icmp import LiteEthICMP
from liteeth.mac.common import LiteEthMACVLANCrossbar, LiteEthMACVLANPacketizer, LiteEthMACVLANDepacketizer

# IP Core ------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -61,6 +62,63 @@ def __init__(self, phy, mac_address, ip_address, clk_freq, dw=8,
dw = dw,
)

# VLAN UDP IP Core ---------------------------------------------------------------------------------

class LiteEthVLANUDPIPCore(Module, AutoCSR):
def __init__(self, phy, mac_address, ip_address, clk_freq,
with_icmp=True,
with_ip_broadcast = True,
dw=8):
self.mac_address = mac_address
self.with_icmp = with_icmp
self.with_ip_broadcast = with_ip_broadcast
self.clk_freq = clk_freq
self.dw = dw
ip_address = convert_ip(ip_address)
self.submodules.mac = LiteEthMAC(phy, dw, interface="crossbar", with_preamble_crc=True)

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,
with_broadcast=self.with_ip_broadcast,
dw=dw)

if with_icmp:
self.submodules.icmp = LiteEthICMP(self.ip, ip_address, dw=dw)

self.submodules.udp = LiteEthUDP(self.ip, ip_address, dw=dw)

vlan_mac_port = self.mac.crossbar.get_port(ethernet_8021q_tpid, dw=dw)

self.submodules.crossbar = LiteEthMACVLANCrossbar(dw)
self.submodules.packetizer = LiteEthMACVLANPacketizer(dw)
self.submodules.depacketizer = LiteEthMACVLANDepacketizer(dw)

self.comb += [
vlan_mac_port.sink.ethernet_type.eq(ethernet_8021q_tpid),
self.crossbar.master.source.connect(self.packetizer.sink),
self.packetizer.source.target_mac.eq(self.packetizer.sink.target_mac),
self.packetizer.source.sender_mac.eq(self.packetizer.sink.sender_mac),
self.packetizer.source.connect(vlan_mac_port.sink, omit={'ethernet_type'}),
vlan_mac_port.source.connect(self.depacketizer.sink),
self.depacketizer.source.connect(self.crossbar.master.sink),
]

def add_vlan(self, vlan_ip="192.168.3.50", vlan_id=2001):
vlan_ip_address = convert_ip(vlan_ip)
arp = LiteEthARP(self, self.mac_address, vlan_ip_address,
self.clk_freq, dw=self.dw, vlan_id=vlan_id)
setattr(self.submodules, f"vlan_{vlan_id}_arp", arp)
ip = LiteEthIP(self, self.mac_address, vlan_ip_address,
arp.table, dw=self.dw, with_broadcast=self.with_ip_broadcast, vlan_id=vlan_id)
setattr(self.submodules, f"vlan_{vlan_id}_ip", ip)
if self.with_icmp:
icmp = LiteEthICMP(ip, vlan_ip_address, dw=self.dw)
setattr(self.submodules, f"vlan_{vlan_id}_ip", icmp)

udp = LiteEthUDP(ip, vlan_ip_address, dw=self.dw)
setattr(self.submodules, f"vlan_{vlan_id}_udp", udp)
return udp

# UDP IP Core --------------------------------------------------------------------------------------

class LiteEthUDPIPCore(LiteEthIPCore):
Expand Down
31 changes: 22 additions & 9 deletions liteeth/core/arp.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ def __init__(self, dw=8):


class LiteEthARPTX(Module):
def __init__(self, mac_address, ip_address, dw=8):
def __init__(self, mac_address, ip_address, dw=8, vlan_id=False):
self.sink = sink = stream.Endpoint(_arp_table_layout)
self.source = source = stream.Endpoint(eth_mac_description(dw))

# # #

packet_length = max(arp_header.length, arp_min_length)
if vlan_id:
packet_length = max(arp_header.length, arp_vlan_min_length)
else:
packet_length = max(arp_header.length, arp_min_length)
packet_words = packet_length//(dw//8)
counter = Signal(max=packet_words, reset_less=True)

Expand Down Expand Up @@ -294,16 +297,26 @@ def __init__(self, clk_freq, max_requests=8):
# ARP ----------------------------------------------------------------------------------------------

class LiteEthARP(Module):
def __init__(self, mac, mac_address, ip_address, clk_freq, dw=8):
self.submodules.tx = tx = LiteEthARPTX(mac_address, ip_address, dw)
def __init__(self, mac, mac_address, ip_address, clk_freq, dw=8, vlan_id=False):
self.submodules.tx = tx = LiteEthARPTX(mac_address, ip_address, dw, vlan_id=vlan_id)
self.submodules.rx = rx = LiteEthARPRX(mac_address, ip_address, dw)
self.submodules.table = table = LiteEthARPTable(clk_freq)
self.comb += [
rx.source.connect(table.sink),
table.source.connect(tx.sink)
]
mac_port = mac.crossbar.get_port(ethernet_type_arp, dw=dw)
self.comb += [
tx.source.connect(mac_port.sink),
mac_port.source.connect(rx.sink)
]
if vlan_id:
assert(type(vlan_id) is int)
mac_port = mac.crossbar.get_port((vlan_id << 16) | ethernet_type_arp, dw=dw)
self.comb += [
mac_port.sink.vid.eq(vlan_id),
tx.source.connect(mac_port.sink),
mac_port.source.connect(rx.sink, omit={'dei', 'pcp', 'vid'})
]

else:
mac_port = mac.crossbar.get_port(ethernet_type_arp, dw=dw)
self.comb += [
tx.source.connect(mac_port.sink),
mac_port.source.connect(rx.sink)
]
23 changes: 17 additions & 6 deletions liteeth/core/ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,25 @@ def __init__(self, mac_address, ip_address, with_broadcast=True, dw=8):
# IP -----------------------------------------------------------------------------------------------

class LiteEthIP(Module):
def __init__(self, mac, mac_address, ip_address, arp_table, with_broadcast=True, dw=8):
def __init__(self, mac, mac_address, ip_address, arp_table, with_broadcast=True, dw=8, vlan_id=False):
self.submodules.tx = tx = LiteEthIPTX(mac_address, ip_address, arp_table, dw=dw)
self.submodules.rx = rx = LiteEthIPRX(mac_address, ip_address, with_broadcast, dw=dw)
mac_port = mac.crossbar.get_port(ethernet_type_ip, dw)
self.comb += [
tx.source.connect(mac_port.sink),
mac_port.source.connect(rx.sink)
]

if vlan_id:
assert(type(vlan_id) is int)
mac_port = mac.crossbar.get_port((vlan_id << 16) | ethernet_type_ip, dw=dw)
self.comb += [
mac_port.sink.vid.eq(vlan_id),
tx.source.connect(mac_port.sink),
mac_port.source.connect(rx.sink, omit={'dei', 'pcp', 'vid'})
]
else:
mac_port = mac.crossbar.get_port(ethernet_type_ip, dw)
self.comb += [
tx.source.connect(mac_port.sink),
mac_port.source.connect(rx.sink)
]

self.submodules.crossbar = crossbar = LiteEthIPV4Crossbar(dw)
self.comb += [
crossbar.master.source.connect(tx.sink),
Expand Down
6 changes: 5 additions & 1 deletion liteeth/crossbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def do_finalize(self):
# RX dispatch
sources = [port.source for port in self.users.values()]
self.submodules.dispatcher = Dispatcher(self.master.sink, sources, one_hot=True)
dispatch_sig = getattr(self.master.sink, self.dispatch_param)
if type(self.dispatch_param) is list:
params = [getattr(self.master.sink, param) for param in self.dispatch_param]
dispatch_sig = Cat(*params)
else:
dispatch_sig = getattr(self.master.sink, self.dispatch_param)
for i, (k, v) in enumerate(self.users.items()):
self.comb += If(dispatch_sig == k, self.dispatcher.sel.eq(2**i))
Loading