From 7ce1085b68572be2dc0c43f93dbf54fed27e623a Mon Sep 17 00:00:00 2001 From: Leon Schuermann Date: Wed, 27 Jan 2021 17:43:23 +0100 Subject: [PATCH 1/3] liteeth MAC: implement RX hardware packet timestamping This implements optional packet timestamping based on a hardware timestamp source for incoming Ethernet packets, as required by applications such as IEEE 1588 (Precision Time Protocol). When a timestamp source is given as an argument, an additonal CSR is generated containing the packet timestamp. --- liteeth/mac/__init__.py | 11 +++++++++-- liteeth/mac/sram.py | 41 ++++++++++++++++++++++++++++++++++------- liteeth/mac/wishbone.py | 4 ++-- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/liteeth/mac/__init__.py b/liteeth/mac/__init__.py index 55356c9..9e44fb0 100644 --- a/liteeth/mac/__init__.py +++ b/liteeth/mac/__init__.py @@ -18,7 +18,8 @@ class LiteEthMAC(Module, AutoCSR): with_preamble_crc = True, nrxslots = 2, ntxslots = 2, - hw_mac = None): + hw_mac = None, + timestamp_source = None): assert interface in ["crossbar", "wishbone", "hybrid"] self.submodules.core = LiteEthMACCore(phy, dw, endianness, with_preamble_crc) self.csrs = [] @@ -37,7 +38,13 @@ class LiteEthMAC(Module, AutoCSR): self.rx_slots = CSRConstant(nrxslots) self.tx_slots = CSRConstant(ntxslots) self.slot_size = CSRConstant(2**bits_for(eth_mtu)) - self.submodules.interface = FullMemoryWE()(LiteEthMACWishboneInterface(32, nrxslots, ntxslots, endianness)) + self.submodules.interface = FullMemoryWE()(LiteEthMACWishboneInterface( + dw = 32, + nrxslots = nrxslots, + ntxslots = ntxslots, + endianness = endianness, + timestamp_source = timestamp_source, + )) self.ev, self.bus = self.interface.sram.ev, self.interface.bus self.csrs = self.interface.get_csrs() + self.core.get_csrs() if interface == "hybrid": diff --git a/liteeth/mac/sram.py b/liteeth/mac/sram.py index ac075b8..e4030e2 100644 --- a/liteeth/mac/sram.py +++ b/liteeth/mac/sram.py @@ -14,16 +14,22 @@ from litex.soc.interconnect.csr_eventmanager import * # MAC SRAM Writer ---------------------------------------------------------------------------------- class LiteEthMACSRAMWriter(Module, AutoCSR): - def __init__(self, dw, depth, nslots=2, endianness="big"): + def __init__(self, dw, depth, nslots=2, endianness="big", timestamp_source=None): self.sink = sink = stream.Endpoint(eth_phy_description(dw)) self.crc_error = Signal() slotbits = max(log2_int(nslots), 1) lengthbits = 32 + timestampbits = 0 if timestamp_source is None else value_bits_sign(timestamp_source)[0] self._slot = CSRStatus(slotbits) self._length = CSRStatus(lengthbits) + # If a timestamp source is passed in, timestamp all incoming + # packets and provide the value in a CSR + if timestamp_source is not None: + self._rx_timestamp = CSRStatus(timestampbits) + self.errors = CSRStatus(32) self.submodules.ev = EventManager() @@ -62,17 +68,29 @@ class LiteEthMACSRAMWriter(Module, AutoCSR): ongoing = Signal() # Status FIFO - fifo = stream.SyncFIFO([("slot", slotbits), ("length", lengthbits)], nslots) + fifo_layout = [("slot", slotbits), ("length", lengthbits)] + if timestamp_source is not None: + fifo_layout += [("rx_timestamp", timestampbits)] + + fifo = stream.SyncFIFO(fifo_layout, nslots) self.submodules += fifo + # If we timestamp incoming packets we're going to need another + # signal to hold the timestamp from the start of packet + # reception + if timestamp_source is not None: + rx_start_timestamp = Signal(timestampbits) + # FSM self.submodules.fsm = fsm = FSM(reset_state="IDLE") fsm.act("IDLE", If(sink.valid, If(fifo.sink.ready, - ongoing.eq(1), - NextValue(counter, counter + inc), - NextState("WRITE") + ongoing.eq(1), + *([] if timestamp_source is None else + [NextValue(rx_start_timestamp, timestamp_source)]), + NextValue(counter, counter + inc), + NextState("WRITE") ).Else( NextValue(self.errors.status, self.errors.status + 1), NextState("DISCARD_REMAINING") @@ -109,12 +127,14 @@ class LiteEthMACSRAMWriter(Module, AutoCSR): fifo.sink.slot.eq(slot), fifo.sink.length.eq(counter) ] + fsm.act("TERMINATE", NextValue(counter, 0), slot_ce.eq(1), fifo.sink.valid.eq(1), NextState("IDLE") ) + self.comb += [ fifo.source.ready.eq(self.ev.available.clear), self.ev.available.trigger.eq(fifo.source.valid), @@ -122,6 +142,13 @@ class LiteEthMACSRAMWriter(Module, AutoCSR): self._length.status.eq(fifo.source.length), ] + # RX timestamping to FIFO + if timestamp_source is not None: + self.comb += [ + fifo.sink.rx_timestamp.eq(rx_start_timestamp), + self._rx_timestamp.status.eq(fifo.source.rx_timestamp), + ] + # Memory mems = [None]*nslots ports = [None]*nslots @@ -242,8 +269,8 @@ class LiteEthMACSRAMReader(Module, AutoCSR): # MAC SRAM ----------------------------------------------------------------------------------------- class LiteEthMACSRAM(Module, AutoCSR): - def __init__(self, dw, depth, nrxslots, ntxslots, endianness): - self.submodules.writer = LiteEthMACSRAMWriter(dw, depth, nrxslots, endianness) + def __init__(self, dw, depth, nrxslots, ntxslots, endianness, timestamp_source=None): + self.submodules.writer = LiteEthMACSRAMWriter(dw, depth, nrxslots, endianness, timestamp_source) self.submodules.reader = LiteEthMACSRAMReader(dw, depth, ntxslots, endianness) self.submodules.ev = SharedIRQ(self.writer.ev, self.reader.ev) self.sink, self.source = self.writer.sink, self.reader.source diff --git a/liteeth/mac/wishbone.py b/liteeth/mac/wishbone.py index f112767..b044980 100644 --- a/liteeth/mac/wishbone.py +++ b/liteeth/mac/wishbone.py @@ -13,7 +13,7 @@ from litex.soc.interconnect import wishbone # MAC Wishbone Interface --------------------------------------------------------------------------- class LiteEthMACWishboneInterface(Module, AutoCSR): - def __init__(self, dw, nrxslots=2, ntxslots=2, endianness="big"): + def __init__(self, dw, nrxslots=2, ntxslots=2, endianness="big", timestamp_source=None): self.sink = stream.Endpoint(eth_phy_description(dw)) self.source = stream.Endpoint(eth_phy_description(dw)) self.bus = wishbone.Interface() @@ -22,7 +22,7 @@ class LiteEthMACWishboneInterface(Module, AutoCSR): # storage in SRAM sram_depth = eth_mtu//(dw//8) - self.submodules.sram = sram.LiteEthMACSRAM(dw, sram_depth, nrxslots, ntxslots, endianness) + self.submodules.sram = sram.LiteEthMACSRAM(dw, sram_depth, nrxslots, ntxslots, endianness, timestamp_source) self.comb += [ self.sink.connect(self.sram.sink), self.sram.source.connect(self.source) From e5f713f5a09896f33d4b8dae2e04314a5b552264 Mon Sep 17 00:00:00 2001 From: Leon Schuermann Date: Thu, 28 Jan 2021 11:43:28 +0100 Subject: [PATCH 2/3] liteeth MAC: add a TX return channel This changes the liteeth SRAM reader to utilize a feedback channel returning the slot of which a packet has been sent. The event source is changed from a pulse to a level-based trigger, such that it will continue asserted if a single packet has been acknowledged, but additional packets have been sent. This infrastructure allows to convey additional information about transmitted packets, such as timestamps or errors. --- liteeth/mac/sram.py | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/liteeth/mac/sram.py b/liteeth/mac/sram.py index e4030e2..3f4939a 100644 --- a/liteeth/mac/sram.py +++ b/liteeth/mac/sram.py @@ -179,27 +179,40 @@ class LiteEthMACSRAMReader(Module, AutoCSR): lengthbits = bits_for(depth*4) # length in bytes self.lengthbits = lengthbits + # MAC Reader command channel self._start = CSR() self._ready = CSRStatus() self._level = CSRStatus(log2_int(nslots) + 1) self._slot = CSRStorage(slotbits, reset_less=True) self._length = CSRStorage(lengthbits, reset_less=True) + # MAC Reader return channel + self._res_slot = CSRStatus(slotbits) self.submodules.ev = EventManager() - self.ev.done = EventSourcePulse() + self.ev.done = EventSourceLevel() self.ev.finalize() # # # # Command FIFO - fifo = stream.SyncFIFO([("slot", slotbits), ("length", lengthbits)], nslots) - self.submodules += fifo + cmd_fifo = stream.SyncFIFO([("slot", slotbits), ("length", lengthbits)], nslots) + self.submodules += cmd_fifo self.comb += [ - fifo.sink.valid.eq(self._start.re), - fifo.sink.slot.eq(self._slot.storage), - fifo.sink.length.eq(self._length.storage), - self._ready.status.eq(fifo.sink.ready), - self._level.status.eq(fifo.level) + cmd_fifo.sink.valid.eq(self._start.re), + cmd_fifo.sink.slot.eq(self._slot.storage), + cmd_fifo.sink.length.eq(self._length.storage), + self._ready.status.eq(cmd_fifo.sink.ready), + self._level.status.eq(cmd_fifo.level) + ] + + # Result FIFO (return channel) + res_fifo = stream.SyncFIFO([("slot", slotbits)], nslots) + + self.submodules += res_fifo + self.comb += [ + res_fifo.source.ready.eq(self.ev.done.clear), + self.ev.done.trigger.eq(res_fifo.source.valid), + self._res_slot.status.eq(res_fifo.source.slot), ] # Length computation @@ -210,12 +223,12 @@ class LiteEthMACSRAMReader(Module, AutoCSR): self.submodules.fsm = fsm = FSM(reset_state="IDLE") fsm.act("IDLE", NextValue(counter, 0), - If(fifo.source.valid, + If(cmd_fifo.source.valid, read_address.eq(0), NextState("SEND") ) ) - length_lsb = fifo.source.length[0:2] + length_lsb = cmd_fifo.source.length[0:2] if endianness == "big": self.comb += If(source.last, Case(length_lsb, { @@ -234,7 +247,7 @@ class LiteEthMACSRAMReader(Module, AutoCSR): })) fsm.act("SEND", source.valid.eq(1), - source.last.eq(counter >= (fifo.source.length - 4)), + source.last.eq(counter >= (cmd_fifo.source.length - 4)), read_address.eq(counter), If(source.ready, read_address.eq(counter + 4), @@ -245,13 +258,17 @@ class LiteEthMACSRAMReader(Module, AutoCSR): ) ) fsm.act("END", - fifo.source.ready.eq(1), - self.ev.done.trigger.eq(1), + res_fifo.sink.valid.eq(1), + cmd_fifo.source.ready.eq(1), NextState("IDLE") ) + self.comb += [ + res_fifo.sink.slot.eq(cmd_fifo.source.slot) + ] + # Memory - rd_slot = fifo.source.slot + rd_slot = cmd_fifo.source.slot mems = [None]*nslots ports = [None]*nslots for n in range(nslots): From 2b206b8f7fc618a4d8cff04723256db86fb1d958 Mon Sep 17 00:00:00 2001 From: Leon Schuermann Date: Thu, 28 Jan 2021 11:47:15 +0100 Subject: [PATCH 3/3] liteeth MAC: implement TX hardware packet timestamping This implements optional packet timestamping based on a hardware timestamp source for outgoing Ethernet packets, as required by applications such as IEEE 1588 (Precision Time Protocol). --- liteeth/mac/sram.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/liteeth/mac/sram.py b/liteeth/mac/sram.py index 3f4939a..5ffda47 100644 --- a/liteeth/mac/sram.py +++ b/liteeth/mac/sram.py @@ -172,13 +172,17 @@ class LiteEthMACSRAMWriter(Module, AutoCSR): # MAC SRAM Reader ---------------------------------------------------------------------------------- class LiteEthMACSRAMReader(Module, AutoCSR): - def __init__(self, dw, depth, nslots=2, endianness="big"): + def __init__(self, dw, depth, nslots=2, endianness="big", timestamp_source=None): self.source = source = stream.Endpoint(eth_phy_description(dw)) slotbits = max(log2_int(nslots), 1) lengthbits = bits_for(depth*4) # length in bytes self.lengthbits = lengthbits + # TX packet timestamping, for applications such as IEEE 1588 + # Precision Time Protocol + timestampbits = 0 if timestamp_source is None else value_bits_sign(timestamp_source)[0] + # MAC Reader command channel self._start = CSR() self._ready = CSRStatus() @@ -188,6 +192,9 @@ class LiteEthMACSRAMReader(Module, AutoCSR): # MAC Reader return channel self._res_slot = CSRStatus(slotbits) + if timestamp_source is not None: + self._res_tx_timestamp = CSRStatus(timestampbits) + self.submodules.ev = EventManager() self.ev.done = EventSourceLevel() self.ev.finalize() @@ -206,28 +213,41 @@ class LiteEthMACSRAMReader(Module, AutoCSR): ] # Result FIFO (return channel) - res_fifo = stream.SyncFIFO([("slot", slotbits)], nslots) + res_fifo_layout = [("slot", slotbits)] + if timestamp_source is not None: + res_fifo_layout += [("tx_timestamp", timestampbits)] + res_fifo = stream.SyncFIFO(res_fifo_layout, nslots) self.submodules += res_fifo + self.comb += [ res_fifo.source.ready.eq(self.ev.done.clear), self.ev.done.trigger.eq(res_fifo.source.valid), self._res_slot.status.eq(res_fifo.source.slot), ] + if timestamp_source is not None: + self.comb += self._res_tx_timestamp.status.eq(res_fifo.source.tx_timestamp), # Length computation read_address = Signal(lengthbits) counter = Signal(lengthbits) + # Store the timestamp of transmission start + if timestamp_source is not None: + tx_start_timestamp = Signal(timestampbits) + # FSM self.submodules.fsm = fsm = FSM(reset_state="IDLE") fsm.act("IDLE", NextValue(counter, 0), If(cmd_fifo.source.valid, read_address.eq(0), + *([] if timestamp_source is None + else [NextValue(tx_start_timestamp, timestamp_source)]), NextState("SEND") ) ) + length_lsb = cmd_fifo.source.length[0:2] if endianness == "big": self.comb += If(source.last, @@ -263,9 +283,9 @@ class LiteEthMACSRAMReader(Module, AutoCSR): NextState("IDLE") ) - self.comb += [ - res_fifo.sink.slot.eq(cmd_fifo.source.slot) - ] + self.comb += res_fifo.sink.slot.eq(cmd_fifo.source.slot) + if timestamp_source is not None: + self.comb += res_fifo.sink.tx_timestamp.eq(tx_start_timestamp) # Memory rd_slot = cmd_fifo.source.slot @@ -288,6 +308,6 @@ class LiteEthMACSRAMReader(Module, AutoCSR): class LiteEthMACSRAM(Module, AutoCSR): def __init__(self, dw, depth, nrxslots, ntxslots, endianness, timestamp_source=None): self.submodules.writer = LiteEthMACSRAMWriter(dw, depth, nrxslots, endianness, timestamp_source) - self.submodules.reader = LiteEthMACSRAMReader(dw, depth, ntxslots, endianness) + self.submodules.reader = LiteEthMACSRAMReader(dw, depth, ntxslots, endianness, timestamp_source) self.submodules.ev = SharedIRQ(self.writer.ev, self.reader.ev) self.sink, self.source = self.writer.sink, self.reader.source