From 46c4841d68c22d36d2d64eff380f5fe126d7c572 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Tue, 27 Jan 2015 23:59:06 +0100 Subject: [PATCH] mac: import files from MiSoC --- liteethernet/mac/__init__.py | 111 ++++++++++++ liteethernet/mac/common.py | 14 ++ liteethernet/mac/core/last_be.py | 39 +++++ liteethernet/mac/core/preamble.py | 145 ++++++++++++++++ liteethernet/mac/frontend/sram.py | 251 +++++++++++++++++++++++++++ liteethernet/mac/phy/gmii.py | 76 ++++++++ liteethernet/mac/phy/loopback.py | 33 ++++ liteethernet/mac/phy/mii.py | 128 ++++++++++++++ liteethernet/mac/test/Makefile | 13 ++ liteethernet/mac/test/__init__.py | 0 liteethernet/mac/test/common.py | 120 +++++++++++++ liteethernet/mac/test/crc_tb.py | 83 +++++++++ liteethernet/mac/test/ethmac_tb.gtkw | 75 ++++++++ liteethernet/mac/test/ethmac_tb.py | 123 +++++++++++++ liteethernet/mac/test/preamble_tb.py | 82 +++++++++ 15 files changed, 1293 insertions(+) create mode 100644 liteethernet/mac/common.py create mode 100644 liteethernet/mac/core/last_be.py create mode 100644 liteethernet/mac/core/preamble.py create mode 100644 liteethernet/mac/frontend/sram.py create mode 100644 liteethernet/mac/phy/gmii.py create mode 100644 liteethernet/mac/phy/loopback.py create mode 100644 liteethernet/mac/phy/mii.py create mode 100644 liteethernet/mac/test/Makefile create mode 100644 liteethernet/mac/test/__init__.py create mode 100644 liteethernet/mac/test/common.py create mode 100644 liteethernet/mac/test/crc_tb.py create mode 100644 liteethernet/mac/test/ethmac_tb.gtkw create mode 100644 liteethernet/mac/test/ethmac_tb.py create mode 100644 liteethernet/mac/test/preamble_tb.py diff --git a/liteethernet/mac/__init__.py b/liteethernet/mac/__init__.py index e69de29b..a7ea0c2c 100644 --- a/liteethernet/mac/__init__.py +++ b/liteethernet/mac/__init__.py @@ -0,0 +1,111 @@ +# This file is Copyright (c) 2014 Florent Kermarrec +# License: BSD + +from migen.fhdl.std import * + +from migen.bus import wishbone +from migen.actorlib.fifo import AsyncFIFO +from migen.actorlib.structuring import Converter, Pipeline +from migen.bank.eventmanager import SharedIRQ +from migen.bank.description import * +from migen.fhdl.simplify import * + +from misoclib.ethmac.common import * +from misoclib.ethmac.preamble import PreambleInserter, PreambleChecker +from migen.actorlib.crc import CRC32Inserter, CRC32Checker +from misoclib.ethmac.last_be import TXLastBE, RXLastBE +from misoclib.ethmac.sram import SRAMWriter, SRAMReader + +class EthMAC(Module, AutoCSR): + def __init__(self, phy, interface="wishbone", with_hw_preamble_crc=True, endianness="be"): + # Preamble / CRC (optional) + if with_hw_preamble_crc: + self._hw_preamble_crc = CSRStatus(reset=1) + # Preamble insert/check + preamble_inserter = PreambleInserter(phy.dw) + preamble_checker = PreambleChecker(phy.dw) + self.submodules += RenameClockDomains(preamble_inserter, "eth_tx") + self.submodules += RenameClockDomains(preamble_checker, "eth_rx") + + # CRC insert/check + crc32_inserter = CRC32Inserter(eth_description(phy.dw)) + crc32_checker = CRC32Checker(eth_description(phy.dw)) + self.submodules += RenameClockDomains(crc32_inserter, "eth_tx") + self.submodules += RenameClockDomains(crc32_checker, "eth_rx") + + # Delimiters + tx_last_be = TXLastBE(phy.dw) + rx_last_be = RXLastBE(phy.dw) + self.submodules += RenameClockDomains(tx_last_be, "eth_tx") + self.submodules += RenameClockDomains(rx_last_be, "eth_rx") + + # Converters + reverse = endianness == "be" + tx_converter = Converter(eth_description(32), eth_description(phy.dw), reverse=reverse) + rx_converter = Converter(eth_description(phy.dw), eth_description(32), reverse=reverse) + self.submodules += RenameClockDomains(tx_converter, "eth_tx") + self.submodules += RenameClockDomains(rx_converter, "eth_rx") + + # Cross Domain Crossing + tx_cdc = AsyncFIFO(eth_description(32), 4) + rx_cdc = AsyncFIFO(eth_description(32), 4) + self.submodules += RenameClockDomains(tx_cdc, {"write": "sys", "read": "eth_tx"}) + self.submodules += RenameClockDomains(rx_cdc, {"write": "eth_rx", "read": "sys"}) + + # Graph + if with_hw_preamble_crc: + rx_pipeline = [phy, preamble_checker, crc32_checker, rx_last_be, rx_converter, rx_cdc] + tx_pipeline = [tx_cdc, tx_converter, tx_last_be, crc32_inserter, preamble_inserter, phy] + else: + rx_pipeline = [phy, rx_last_be, rx_converter, rx_cdc] + tx_pipeline = [tx_cdc, tx_converter, tx_last_be, phy] + self.submodules.rx_pipeline = Pipeline(*rx_pipeline) + self.submodules.tx_pipeline = Pipeline(*tx_pipeline) + + if interface == "wishbone": + nrxslots = 2 + ntxslots = 2 + + self.bus = wishbone.Interface() + + # SRAM Memories + sram_depth = buffer_depth//(32//8) + self.submodules.sram_writer = SRAMWriter(sram_depth, nrxslots) + self.submodules.sram_reader = SRAMReader(sram_depth, ntxslots) + self.submodules.ev = SharedIRQ(self.sram_writer.ev, self.sram_reader.ev) + + # Connect to pipelines + self.comb += [ + self.rx_pipeline.source.connect(self.sram_writer.sink), + self.sram_reader.source.connect(self.tx_pipeline.sink) + ] + + # Interface + wb_rx_sram_ifs = [wishbone.SRAM(self.sram_writer.mems[n], read_only=True) + for n in range(nrxslots)] + # TODO: FullMemoryWE should move to Mibuild + wb_tx_sram_ifs = [FullMemoryWE(wishbone.SRAM(self.sram_reader.mems[n], read_only=False)) + for n in range(ntxslots)] + wb_sram_ifs = wb_rx_sram_ifs + wb_tx_sram_ifs + + wb_slaves = [] + decoderoffset = log2_int(sram_depth) + decoderbits = log2_int(len(wb_sram_ifs)) + for n, wb_sram_if in enumerate(wb_sram_ifs): + def slave_filter(a, v=n): + return a[decoderoffset:decoderoffset+decoderbits] == v + wb_slaves.append((slave_filter, wb_sram_if.bus)) + self.submodules += wb_sram_if + wb_con = wishbone.Decoder(self.bus, wb_slaves, register=True) + self.submodules += wb_con + + elif interface == "lasmi": + raise NotImplementedError + + elif interface == "expose": + # expose pipelines endpoints + self.sink = tx_pipeline.sink + self.source = rx_pipeline.source + + else: + raise ValueError("EthMAC only supports Wishbone, LASMI or expose interfaces") diff --git a/liteethernet/mac/common.py b/liteethernet/mac/common.py new file mode 100644 index 00000000..177943ee --- /dev/null +++ b/liteethernet/mac/common.py @@ -0,0 +1,14 @@ +from migen.fhdl.std import * +from migen.flow.actor import EndpointDescription + +eth_mtu = 1532 +eth_preamble = 0xD555555555555555 +buffer_depth = 2**log2_int(eth_mtu, need_pow2=False) + +def eth_description(dw): + layout = [ + ("d", dw), + ("last_be", dw//8), + ("error", dw//8) + ] + return EndpointDescription(layout, packetized=True) diff --git a/liteethernet/mac/core/last_be.py b/liteethernet/mac/core/last_be.py new file mode 100644 index 00000000..da55faee --- /dev/null +++ b/liteethernet/mac/core/last_be.py @@ -0,0 +1,39 @@ +from migen.fhdl.std import * +from migen.genlib.record import * +from migen.flow.actor import Sink, Source + +from misoclib.ethmac.common import * + +class TXLastBE(Module): + def __init__(self, d_w): + self.sink = sink = Sink(eth_description(d_w)) + self.source = source = Source(eth_description(d_w)) + + ### + + ongoing = Signal() + self.sync += \ + If(self.sink.stb & self.sink.ack, + If(sink.sop, + ongoing.eq(1) + ).Elif(sink.last_be, + ongoing.eq(0) + ) + ) + self.comb += [ + Record.connect(self.sink, self.source), + self.source.eop.eq(self.sink.last_be), + self.source.stb.eq(self.sink.stb & (self.sink.sop | ongoing)) + ] + +class RXLastBE(Module): + def __init__(self, d_w): + self.sink = sink = Sink(eth_description(d_w)) + self.source = source = Source(eth_description(d_w)) + + ### + + self.comb += [ + Record.connect(self.sink, self.source), + self.source.last_be.eq(self.sink.eop) + ] diff --git a/liteethernet/mac/core/preamble.py b/liteethernet/mac/core/preamble.py new file mode 100644 index 00000000..fe2078fe --- /dev/null +++ b/liteethernet/mac/core/preamble.py @@ -0,0 +1,145 @@ +from migen.fhdl.std import * +from migen.genlib.fsm import FSM, NextState +from migen.genlib.misc import chooser +from migen.genlib.record import * +from migen.flow.actor import Sink, Source + +from misoclib.ethmac.common import * + +class PreambleInserter(Module): + def __init__(self, d_w): + self.sink = Sink(eth_description(d_w)) + self.source = Source(eth_description(d_w)) + + ### + + preamble = Signal(64, reset=eth_preamble) + cnt_max = (64//d_w)-1 + cnt = Signal(max=cnt_max+1) + clr_cnt = Signal() + inc_cnt = Signal() + + self.sync += \ + If(clr_cnt, + cnt.eq(0) + ).Elif(inc_cnt, + cnt.eq(cnt+1) + ) + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + fsm.act("IDLE", + self.sink.ack.eq(1), + clr_cnt.eq(1), + If(self.sink.stb & self.sink.sop, + self.sink.ack.eq(0), + NextState("INSERT"), + ) + ) + fsm.act("INSERT", + self.source.stb.eq(1), + self.source.sop.eq(cnt==0), + chooser(preamble, cnt, self.source.d), + If(cnt == cnt_max, + If(self.source.ack, NextState("COPY")) + ).Else( + inc_cnt.eq(self.source.ack) + ) + ) + fsm.act("COPY", + Record.connect(self.sink, self.source), + self.source.sop.eq(0), + + If(self.sink.stb & self.sink.eop & self.source.ack, + NextState("IDLE"), + ) + ) + +class PreambleChecker(Module): + def __init__(self, d_w): + self.sink = Sink(eth_description(d_w)) + self.source = Source(eth_description(d_w)) + + ### + + preamble = Signal(64, reset=eth_preamble) + cnt_max = (64//d_w) - 1 + cnt = Signal(max=cnt_max+1) + clr_cnt = Signal() + inc_cnt = Signal() + + self.sync += \ + If(clr_cnt, + cnt.eq(0) + ).Elif(inc_cnt, + cnt.eq(cnt+1) + ) + + discard = Signal() + clr_discard = Signal() + set_discard = Signal() + + self.sync += \ + If(clr_discard, + discard.eq(0) + ).Elif(set_discard, + discard.eq(1) + ) + + sop = Signal() + clr_sop = Signal() + set_sop = Signal() + self.sync += \ + If(clr_sop, + sop.eq(0) + ).Elif(set_sop, + sop.eq(1) + ) + + ref = Signal(d_w) + match = Signal() + self.comb += [ + chooser(preamble, cnt, ref), + match.eq(self.sink.d == ref) + ] + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + fsm.act("IDLE", + self.sink.ack.eq(1), + clr_cnt.eq(1), + clr_discard.eq(1), + If(self.sink.stb & self.sink.sop, + clr_cnt.eq(0), + inc_cnt.eq(1), + clr_discard.eq(0), + set_discard.eq(~match), + NextState("CHECK"), + ) + ) + fsm.act("CHECK", + self.sink.ack.eq(1), + If(self.sink.stb, + set_discard.eq(~match), + If(cnt == cnt_max, + If(discard | (~match), + NextState("IDLE") + ).Else( + set_sop.eq(1), + NextState("COPY") + ) + ).Else( + inc_cnt.eq(1) + ) + ) + ) + fsm.act("COPY", + Record.connect(self.sink, self.source), + self.source.sop.eq(sop), + clr_sop.eq(self.source.stb & self.source.ack), + + If(self.source.stb & self.source.eop & self.source.ack, + NextState("IDLE"), + ) + ) diff --git a/liteethernet/mac/frontend/sram.py b/liteethernet/mac/frontend/sram.py new file mode 100644 index 00000000..32c76f3c --- /dev/null +++ b/liteethernet/mac/frontend/sram.py @@ -0,0 +1,251 @@ +from migen.fhdl.std import * +from migen.genlib.fifo import SyncFIFO +from migen.genlib.fsm import FSM, NextState +from migen.genlib.misc import chooser +from migen.flow.actor import Sink, Source +from migen.bank.description import * +from migen.bank.eventmanager import * + +from misoclib.ethmac.common import * + +class SRAMWriter(Module, AutoCSR): + def __init__(self, depth, nslots=2): + self.sink = sink = Sink(eth_description(32)) + self.crc_error = Signal() + + slotbits = max(log2_int(nslots), 1) + lengthbits = log2_int(depth*4) # length in bytes + + self._slot = CSRStatus(slotbits) + self._length = CSRStatus(lengthbits) + + self.submodules.ev = EventManager() + self.ev.available = EventSourceLevel() + self.ev.finalize() + + ### + + # packet dropped if no slot available + sink.ack.reset = 1 + + # length computation + cnt = Signal(lengthbits) + clr_cnt = Signal() + inc_cnt = Signal() + inc_val = Signal(3) + self.comb += \ + If(sink.last_be[3], + inc_val.eq(1) + ).Elif(sink.last_be[2], + inc_val.eq(2) + ).Elif(sink.last_be[1], + inc_val.eq(3) + ).Else( + inc_val.eq(4) + ) + self.sync += \ + If(clr_cnt, + cnt.eq(0) + ).Elif(inc_cnt, + cnt.eq(cnt+inc_val) + ) + + # slot computation + slot = Signal(slotbits) + inc_slot = Signal() + self.sync += \ + If(inc_slot, + If(slot == nslots-1, + slot.eq(0), + ).Else( + slot.eq(slot+1) + ) + ) + ongoing = Signal() + discard = Signal() + + # status fifo + fifo = SyncFIFO([("slot", slotbits), ("length", lengthbits)], nslots) + self.submodules += fifo + + # fsm + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + fsm.act("IDLE", + inc_cnt.eq(sink.stb), + If(sink.stb & sink.sop, + ongoing.eq(1), + If(fifo.writable, + NextState("WRITE") + ) + ) + ) + fsm.act("WRITE", + inc_cnt.eq(sink.stb), + ongoing.eq(1), + If(sink.stb & sink.eop, + If((sink.error & sink.last_be) != 0, + NextState("DISCARD") + ).Else( + NextState("TERMINATE") + ) + ) + ) + fsm.act("DISCARD", + clr_cnt.eq(1), + NextState("IDLE") + ) + fsm.act("TERMINATE", + clr_cnt.eq(1), + inc_slot.eq(1), + fifo.we.eq(1), + fifo.din.slot.eq(slot), + fifo.din.length.eq(cnt), + NextState("IDLE") + ) + + self.comb += [ + fifo.re.eq(self.ev.available.clear), + self.ev.available.trigger.eq(fifo.readable), + self._slot.status.eq(fifo.dout.slot), + self._length.status.eq(fifo.dout.length), + ] + + # memory + mems = [None]*nslots + ports = [None]*nslots + for n in range(nslots): + mems[n] = Memory(32, depth) + ports[n] = mems[n].get_port(write_capable=True) + self.specials += ports[n] + self.mems = mems + + cases = {} + for n, port in enumerate(ports): + cases[n] = [ + ports[n].adr.eq(cnt[2:]), + ports[n].dat_w.eq(sink.d), + If(sink.stb & ongoing, + ports[n].we.eq(0xf) + ) + ] + self.comb += Case(slot, cases) + + +class SRAMReader(Module, AutoCSR): + def __init__(self, depth, nslots=2): + self.source = source = Source(eth_description(32)) + + slotbits = max(log2_int(nslots), 1) + lengthbits = log2_int(depth*4) # length in bytes + self.lengthbits = lengthbits + + self._start = CSR() + self._ready = CSRStatus() + self._slot = CSRStorage(slotbits) + self._length = CSRStorage(lengthbits) + + self.submodules.ev = EventManager() + self.ev.done = EventSourcePulse() + self.ev.finalize() + + ### + + # command fifo + fifo = SyncFIFO([("slot", slotbits), ("length", lengthbits)], nslots) + self.submodules += fifo + self.comb += [ + fifo.we.eq(self._start.re), + fifo.din.slot.eq(self._slot.storage), + fifo.din.length.eq(self._length.storage), + self._ready.status.eq(fifo.writable) + ] + + # length computation + cnt = Signal(lengthbits) + clr_cnt = Signal() + inc_cnt = Signal() + + self.sync += \ + If(clr_cnt, + cnt.eq(0) + ).Elif(inc_cnt, + cnt.eq(cnt+4) + ) + + # fsm + first = Signal() + last = Signal() + last_d = Signal() + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + fsm.act("IDLE", + clr_cnt.eq(1), + If(fifo.readable, + NextState("CHECK") + ) + ) + fsm.act("CHECK", + If(~last_d, + NextState("SEND"), + ).Else( + NextState("END"), + ) + ) + length_lsb = fifo.dout.length[0:2] + fsm.act("SEND", + source.stb.eq(1), + source.sop.eq(first), + source.eop.eq(last), + If(last, + If(length_lsb == 3, + source.last_be.eq(0b0010) + ).Elif(length_lsb == 2, + source.last_be.eq(0b0100) + ).Elif(length_lsb == 1, + source.last_be.eq(0b1000) + ).Else( + source.last_be.eq(0b0001) + ) + ), + If(source.ack, + inc_cnt.eq(~last), + NextState("CHECK") + ) + ) + fsm.act("END", + fifo.re.eq(1), + self.ev.done.trigger.eq(1), + NextState("IDLE") + ) + + # first/last computation + self.sync += [ + If(fsm.ongoing("IDLE"), + first.eq(1) + ).Elif(source.stb & source.ack, + first.eq(0) + ) + ] + self.comb += last.eq(cnt + 4 >= fifo.dout.length) + self.sync += last_d.eq(last) + + # memory + rd_slot = fifo.dout.slot + + mems = [None]*nslots + ports = [None]*nslots + for n in range(nslots): + mems[n] = Memory(32, depth) + ports[n] = mems[n].get_port() + self.specials += ports[n] + self.mems = mems + + cases = {} + for n, port in enumerate(ports): + self.comb += ports[n].adr.eq(cnt[2:]) + cases[n] = [source.d.eq(port.dat_r)] + self.comb += Case(rd_slot, cases) diff --git a/liteethernet/mac/phy/gmii.py b/liteethernet/mac/phy/gmii.py new file mode 100644 index 00000000..ebdd0235 --- /dev/null +++ b/liteethernet/mac/phy/gmii.py @@ -0,0 +1,76 @@ +from migen.fhdl.std import * +from migen.flow.actor import Sink, Source +from migen.bank.description import * +from migen.genlib.resetsync import AsyncResetSynchronizer + +from misoclib.ethmac.common import * + +class GMIIPHYTX(Module): + def __init__(self, pads): + self.sink = sink = Sink(eth_description(8)) + + ### + + self.sync += [ + pads.tx_er.eq(0), + pads.tx_en.eq(sink.stb), + pads.tx_data.eq(sink.d) + ] + self.comb += sink.ack.eq(1) + +class GMIIPHYRX(Module): + def __init__(self, pads): + self.source = source = Source(eth_description(8)) + + ### + + dv_d = Signal() + self.sync += dv_d.eq(pads.dv) + + sop = Signal() + eop = Signal() + self.comb += [ + sop.eq(pads.dv & ~dv_d), + eop.eq(~pads.dv & dv_d) + ] + self.sync += [ + source.stb.eq(pads.dv), + source.sop.eq(sop), + source.d.eq(pads.rx_data) + ] + self.comb += source.eop.eq(eop) + +# CRG is the only Xilinx specific module. +# TODO: use generic code or add support for others vendors +class GMIIPHYCRG(Module, AutoCSR): + def __init__(self, clock_pads, pads): + self._reset = CSRStorage() + + ### + + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + self.specials += [ + Instance("ODDR", + p_DDR_CLK_EDGE="SAME_EDGE", + i_C=ClockSignal("eth_tx"), i_CE=1, i_S=0, i_R=0, + i_D1=1, i_D2=0, o_Q=clock_pads.gtx, + ), + Instance("BUFG", i_I=clock_pads.rx, o_O=self.cd_eth_rx.clk), + ] + self.comb += self.cd_eth_tx.clk.eq(self.cd_eth_rx.clk) + + reset = self._reset.storage + self.comb += pads.rst_n.eq(~reset) + self.specials += [ + AsyncResetSynchronizer(self.cd_eth_tx, reset), + AsyncResetSynchronizer(self.cd_eth_rx, reset), + ] + +class GMIIPHY(Module, AutoCSR): + def __init__(self, clock_pads, pads): + self.dw = 8 + self.submodules.crg = GMIIPHYCRG(clock_pads, pads) + self.submodules.tx = RenameClockDomains(GMIIPHYTX(pads), "eth_tx") + self.submodules.rx = RenameClockDomains(GMIIPHYRX(pads), "eth_rx") + self.sink, self.source = self.tx.sink, self.rx.source diff --git a/liteethernet/mac/phy/loopback.py b/liteethernet/mac/phy/loopback.py new file mode 100644 index 00000000..50c9f995 --- /dev/null +++ b/liteethernet/mac/phy/loopback.py @@ -0,0 +1,33 @@ +from migen.fhdl.std import * +from migen.flow.actor import Sink, Source +from migen.bank.description import * +from migen.genlib.record import * + +from misoclib.ethmac.common import * + +class LoopbackPHYCRG(Module, AutoCSR): + def __init__(self): + self._reset = CSRStorage() + + ### + + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + self.comb += [ + self.cd_eth_rx.clk.eq(ClockSignal()), + self.cd_eth_tx.clk.eq(ClockSignal()) + ] + + reset = self._reset.storage + self.comb += [ + self.cd_eth_rx.rst.eq(reset), + self.cd_eth_tx.rst.eq(reset) + ] + +class LoopbackPHY(Module, AutoCSR): + def __init__(self): + self.dw = 8 + self.submodules.crg = LoopbackPHYCRG() + self.sink = sink = Sink(eth_description(8)) + self.source = source = Source(eth_description(8)) + self.comb += Record.connect(self.sink, self.source) diff --git a/liteethernet/mac/phy/mii.py b/liteethernet/mac/phy/mii.py new file mode 100644 index 00000000..17f2ba7b --- /dev/null +++ b/liteethernet/mac/phy/mii.py @@ -0,0 +1,128 @@ +from migen.fhdl.std import * +from migen.genlib.fsm import FSM, NextState +from migen.flow.actor import Sink, Source +from migen.bank.description import * +from migen.genlib.resetsync import AsyncResetSynchronizer + +from misoclib.ethmac.common import * + +class MIIPHYTX(Module): + def __init__(self, pads): + self.sink = sink = Sink(eth_description(8)) + + ### + + tx_en_r = Signal() + tx_data_r = Signal(4) + self.sync += [ + pads.tx_er.eq(0), + pads.tx_en.eq(tx_en_r), + pads.tx_data.eq(tx_data_r), + ] + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + fsm.act("IDLE", + sink.ack.eq(1), + If(sink.stb & sink.sop, + sink.ack.eq(0), + NextState("SEND_LO") + ) + ) + fsm.act("SEND_LO", + tx_data_r.eq(sink.d[0:4]), + tx_en_r.eq(1), + NextState("SEND_HI") + ) + fsm.act("SEND_HI", + tx_data_r.eq(sink.d[4:8]), + tx_en_r.eq(1), + sink.ack.eq(1), + If(sink.stb & sink.eop, + NextState("IDLE") + ).Else( + NextState("SEND_LO") + ) + ) + +class MIIPHYRX(Module): + def __init__(self, pads): + self.source = source = Source(eth_description(8)) + + ### + + sop = source.sop + set_sop = Signal() + clr_sop = Signal() + self.sync += \ + If(clr_sop, + sop.eq(0) + ).Elif(set_sop, + sop.eq(1) + ) + + lo = Signal(4) + hi = Signal(4) + load_nibble = Signal(2) + self.sync += \ + If(load_nibble[0], + lo.eq(pads.rx_data) + ).Elif(load_nibble[1], + hi.eq(pads.rx_data) + ) + self.comb += [ + source.d.eq(Cat(lo, hi)) + ] + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + fsm.act("IDLE", + set_sop.eq(1), + If(pads.dv, + load_nibble.eq(0b01), + NextState("LOAD_HI") + ) + ) + fsm.act("LOAD_LO", + source.stb.eq(1), + If(pads.dv, + clr_sop.eq(1), + load_nibble.eq(0b01), + NextState("LOAD_HI") + ).Else( + source.eop.eq(1), + NextState("IDLE") + ) + ) + fsm.act("LOAD_HI", + load_nibble.eq(0b10), + NextState("LOAD_LO") + ) + +class MIIPHYCRG(Module, AutoCSR): + def __init__(self, clock_pads, pads): + self._reset = CSRStorage() + + ### + + self.sync.base50 += clock_pads.phy.eq(~clock_pads.phy) + + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + self.comb += self.cd_eth_rx.clk.eq(clock_pads.rx) + self.comb += self.cd_eth_tx.clk.eq(clock_pads.tx) + + reset = self._reset.storage + self.comb += pads.rst_n.eq(~reset) + self.specials += [ + AsyncResetSynchronizer(self.cd_eth_tx, reset), + AsyncResetSynchronizer(self.cd_eth_rx, reset), + ] + +class MIIPHY(Module, AutoCSR): + def __init__(self, clock_pads, pads): + self.dw = 8 + self.submodules.crg = MIIPHYCRG(clock_pads, pads) + self.submodules.tx = RenameClockDomains(MIIPHYTX(pads), "eth_tx") + self.submodules.rx = RenameClockDomains(MIIPHYRX(pads), "eth_rx") + self.sink, self.source = self.tx.sink, self.rx.source diff --git a/liteethernet/mac/test/Makefile b/liteethernet/mac/test/Makefile new file mode 100644 index 00000000..55d28e10 --- /dev/null +++ b/liteethernet/mac/test/Makefile @@ -0,0 +1,13 @@ +MSCDIR = ../../../ +PYTHON = python3 + +CMD = PYTHONPATH=$(MSCDIR) $(PYTHON) + +crc_tb: + $(CMD) crc_tb.py + +preamble_tb: + $(CMD) preamble_tb.py + +ethmac_tb: + $(CMD) ethmac_tb.py diff --git a/liteethernet/mac/test/__init__.py b/liteethernet/mac/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/liteethernet/mac/test/common.py b/liteethernet/mac/test/common.py new file mode 100644 index 00000000..13123700 --- /dev/null +++ b/liteethernet/mac/test/common.py @@ -0,0 +1,120 @@ +import random, copy + +from migen.fhdl.std import * +from migen.flow.actor import Sink, Source +from migen.genlib.record import * + +from misoclib.ethmac.common import * + +def seed_to_data(seed, random=True): + if random: + return (seed * 0x31415979 + 1) & 0xffffffff + else: + return seed + +def check(p1, p2): + p1 = copy.deepcopy(p1) + p2 = copy.deepcopy(p2) + if isinstance(p1, int): + return 0, 1, int(p1 != p2) + else: + if len(p1) >= len(p2): + ref, res = p1, p2 + else: + ref, res = p2, p1 + shift = 0 + while((ref[0] != res[0]) and (len(res)>1)): + res.pop(0) + shift += 1 + length = min(len(ref), len(res)) + errors = 0 + for i in range(length): + if ref.pop(0) != res.pop(0): + errors += 1 + return shift, length, errors + +def randn(max_n): + return random.randint(0, max_n-1) + +class Packet(list): + def __init__(self, init=[]): + self.ongoing = False + self.done = False + for data in init: + self.append(data) + +class PacketStreamer(Module): + def __init__(self, description): + self.source = Source(description) + ### + self.packets = [] + self.packet = Packet() + self.packet.done = 1 + + def send(self, packet): + packet = copy.deepcopy(packet) + self.packets.append(packet) + + def do_simulation(self, selfp): + if len(self.packets) and self.packet.done: + self.packet = self.packets.pop(0) + if not self.packet.ongoing and not self.packet.done: + selfp.source.stb = 1 + selfp.source.sop = 1 + selfp.source.d = self.packet.pop(0) + self.packet.ongoing = True + elif selfp.source.stb == 1 and selfp.source.ack == 1: + selfp.source.sop = 0 + selfp.source.eop = (len(self.packet) == 1) + if len(self.packet) > 0: + selfp.source.stb = 1 + selfp.source.d = self.packet.pop(0) + else: + self.packet.done = 1 + selfp.source.stb = 0 + +class PacketLogger(Module): + def __init__(self, description): + self.sink = Sink(description) + ### + self.packet = Packet() + + def receive(self): + self.packet.done = 0 + while self.packet.done == 0: + yield + + def do_simulation(self, selfp): + selfp.sink.ack = 1 + if selfp.sink.stb == 1 and selfp.sink.sop == 1: + self.packet = Packet() + self.packet.append(selfp.sink.d) + elif selfp.sink.stb: + self.packet.append(selfp.sink.d) + if selfp.sink.stb == 1 and selfp.sink.eop == 1: + self.packet.done = True + +class AckRandomizer(Module): + def __init__(self, description, level=0): + self.level = level + + self.sink = Sink(description) + self.source = Source(description) + + self.run = Signal() + + self.comb += \ + If(self.run, + Record.connect(self.sink, self.source) + ).Else( + self.source.stb.eq(0), + self.sink.ack.eq(0), + ) + + def do_simulation(self, selfp): + n = randn(100) + if n < self.level: + selfp.run = 0 + else: + selfp.run = 1 + diff --git a/liteethernet/mac/test/crc_tb.py b/liteethernet/mac/test/crc_tb.py new file mode 100644 index 00000000..5d185ac1 --- /dev/null +++ b/liteethernet/mac/test/crc_tb.py @@ -0,0 +1,83 @@ +from migen.fhdl.std import * +from migen.actorlib.crc import * + +from misoclib.ethmac.common import * +from misoclib.ethmac.test.common import * + +payload = [ + 0x00, 0x0A, 0xE6, 0xF0, 0x05, 0xA3, 0x00, 0x12, + 0x34, 0x56, 0x78, 0x90, 0x08, 0x00, 0x45, 0x00, + 0x00, 0x30, 0xB3, 0xFE, 0x00, 0x00, 0x80, 0x11, + 0x72, 0xBA, 0x0A, 0x00, 0x00, 0x03, 0x0A, 0x00, + 0x00, 0x02, 0x04, 0x00, 0x04, 0x00, 0x00, 0x1C, + 0x89, 0x4D, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13 +] + +crc = [ + 0x7A, 0xD5, 0x6B, 0xB3 +] + +mux = { + "inserter": 0, + "checker": 1, + "both": 2 +} + +class TB(Module): + def __init__(self, random_level=50): + sm = self.submodules + sm.streamer = PacketStreamer(eth_description(8)) + sm.streamer_randomizer = AckRandomizer(eth_description(8), random_level) + sm.logger = PacketLogger(eth_description(8)) + sm.logger_randomizer = AckRandomizer(eth_description(8), random_level) + + self.comb += [ + self.streamer.source.connect(self.streamer_randomizer.sink), + self.logger_randomizer.source.connect(self.logger.sink) + ] + + sm.crc32_inserter = CRC32Inserter(eth_description(8)) + sm.crc32_checker = CRC32Checker(eth_description(8)) + + self.mux = Signal(2) + self.comb += [ + If(self.mux == mux["inserter"], + self.streamer_randomizer.source.connect(self.crc32_inserter.sink), + self.crc32_inserter.source.connect(self.logger_randomizer.sink) + ).Elif(self.mux == mux["checker"], + self.streamer_randomizer.source.connect(self.crc32_checker.sink), + self.crc32_checker.source.connect(self.logger_randomizer.sink) + ).Elif(self.mux == mux["both"], + self.streamer_randomizer.source.connect(self.crc32_inserter.sink), + self.crc32_inserter.source.connect(self.crc32_checker.sink), + self.crc32_checker.source.connect(self.logger_randomizer.sink) + ) + ] + + def gen_simulation(self, selfp): + selfp.mux = mux["inserter"] + print("streamer --> crc32_inserter --> logger:") + self.streamer.send(Packet(payload)) + yield from self.logger.receive() + s, l, e = check(payload+crc, self.logger.packet) + print("shift "+ str(s) + " / length " + str(l) + " / errors " + str(e)) + + selfp.mux = mux["checker"] + print("streamer --> crc32_checker --> logger:") + self.streamer.send(Packet(payload+crc)) + yield from self.logger.receive() + s, l, e = check(payload, self.logger.packet) + print("shift "+ str(s) + " / length " + str(l) + " / errors " + str(e)) + + selfp.mux = mux["both"] + print("streamer --> crc32_inserter --> crc32_checker --> logger:") + self.streamer.send(Packet(payload)) + yield from self.logger.receive() + s, l, e = check(payload, self.logger.packet) + print("shift "+ str(s) + " / length " + str(l) + " / errors " + str(e)) + +if __name__ == "__main__": + from migen.sim.generic import run_simulation + run_simulation(TB(), ncycles=1000, vcd_name="my.vcd") diff --git a/liteethernet/mac/test/ethmac_tb.gtkw b/liteethernet/mac/test/ethmac_tb.gtkw new file mode 100644 index 00000000..350521a2 --- /dev/null +++ b/liteethernet/mac/test/ethmac_tb.gtkw @@ -0,0 +1,75 @@ +[*] +[*] GTKWave Analyzer v3.3.46 (w)1999-2012 BSI +[*] Fri Oct 31 11:20:55 2014 +[*] +[dumpfile] "/home/florent/Dev/misoc/misoclib/ethmac/test/my.vcd" +[dumpfile_mtime] "Fri Oct 31 11:20:07 2014" +[dumpfile_size] 5152269 +[savefile] "/home/florent/Dev/misoc/misoclib/ethmac/test/ethmac_tb.gtkw" +[timestart] 0 +[size] 1548 849 +[pos] 101 171 +*-25.000000 34300000 -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 +[treeopen] top. +[sst_width] 379 +[signals_width] 464 +[sst_expanded] 1 +[sst_vpaned_height] 485 +@200 +-WISHBONE +@28 +top.dut.bus_ack +@22 +top.dut.bus_adr[29:0] +@28 +top.dut.bus_bte[1:0] +top.dut.bus_cti[2:0] +top.dut.bus_cyc +@22 +top.dut.bus_dat_r[31:0] +top.dut.bus_dat_w[31:0] +@28 +top.dut.bus_err +@22 +top.dut.bus_sel[3:0] +@28 +top.dut.bus_stb +top.dut.bus_we +@200 +-SRAM_READER --> PHYTx +@28 +top.dut.sram_reader_source_ack +top.dut.sram_reader_source_eop +@22 +top.dut.sram_reader_source_payload_d[31:0] +top.dut.sram_reader_source_payload_error[3:0] +top.dut.sram_reader_source_payload_last_be[3:0] +@28 +top.dut.sram_reader_source_sop +top.dut.sram_reader_source_stb +@200 +-LOOPBACK +@28 +top.dut.phy_source_ack +top.dut.phy_source_eop +@22 +top.dut.phy_source_payload_d[7:0] +@28 +top.dut.phy_source_payload_error +top.dut.phy_source_payload_last_be +top.dut.phy_source_sop +top.dut.phy_source_stb +@200 +-PHYRx --> SRAM_WRITER +@28 +top.dut.sram_writer_sink_ack +top.dut.sram_writer_sink_eop +@22 +top.dut.sram_writer_sink_payload_d[31:0] +top.dut.sram_writer_sink_payload_error[3:0] +top.dut.sram_writer_sink_payload_last_be[3:0] +@28 +top.dut.sram_writer_sink_sop +top.dut.sram_writer_sink_stb +[pattern_trace] 1 +[pattern_trace] 0 diff --git a/liteethernet/mac/test/ethmac_tb.py b/liteethernet/mac/test/ethmac_tb.py new file mode 100644 index 00000000..1e4dbf1f --- /dev/null +++ b/liteethernet/mac/test/ethmac_tb.py @@ -0,0 +1,123 @@ +from migen.fhdl.std import * +from migen.bus import wishbone +from migen.bus.transactions import * +from migen.sim.generic import run_simulation + +from misoclib.ethmac import EthMAC +from misoclib.ethmac.phy import loopback + +from misoclib.ethmac.test.common import * + +class WishboneMaster: + def __init__(self, obj): + self.obj = obj + self.dat = 0 + + def write(self, adr, dat): + self.obj.cyc = 1 + self.obj.stb = 1 + self.obj.adr = adr + self.obj.we = 1 + self.obj.sel = 0xF + self.obj.dat_w = dat + while self.obj.ack == 0: + yield + self.obj.cyc = 0 + self.obj.stb = 0 + yield + + def read(self, adr): + self.obj.cyc = 1 + self.obj.stb = 1 + self.obj.adr = adr + self.obj.we = 0 + self.obj.sel = 0xF + self.obj.dat_w = 0 + while self.obj.ack == 0: + yield + self.dat = self.obj.dat_r + self.obj.cyc = 0 + self.obj.stb = 0 + yield + +class SRAMReaderDriver: + def __init__(self, obj): + self.obj = obj + + def start(self, slot, length): + self.obj._slot.storage = slot + self.obj._length.storage = length + self.obj._start.re = 1 + yield + self.obj._start.re = 0 + yield + + def wait_done(self): + while self.obj.ev.done.pending == 0: + yield + + def clear_done(self): + self.obj.ev.done.clear = 1 + yield + self.obj.ev.done.clear = 0 + yield + +class TB(Module): + def __init__(self): + self.submodules.ethphy = loopback.LoopbackPHY() + self.submodules.ethmac = EthMAC(phy=self.ethphy, with_hw_preamble_crc=True) + + # use sys_clk for each clock_domain + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + self.comb += [ + self.cd_eth_rx.clk.eq(ClockSignal()), + self.cd_eth_rx.rst.eq(ResetSignal()), + self.cd_eth_tx.clk.eq(ClockSignal()), + self.cd_eth_tx.rst.eq(ResetSignal()), + ] + + def gen_simulation(self, selfp): + selfp.cd_eth_rx.rst = 1 + selfp.cd_eth_tx.rst = 1 + yield + selfp.cd_eth_rx.rst = 0 + selfp.cd_eth_tx.rst = 0 + + wishbone_master = WishboneMaster(selfp.ethmac.bus) + sram_reader_driver = SRAMReaderDriver(selfp.ethmac.sram_reader) + + sram_writer_slots_offset = [0x000, 0x200] + sram_reader_slots_offset = [0x400, 0x600] + + length = 1500+2 + + tx_payload = [seed_to_data(i, True) % 0xFF for i in range(length)] + [0, 0, 0, 0] + + errors = 0 + + for slot in range(2): + print("slot {}:".format(slot)) + # fill tx memory + for i in range(length//4+1): + dat = int.from_bytes(tx_payload[4*i:4*(i+1)], "big") + yield from wishbone_master.write(sram_reader_slots_offset[slot]+i, dat) + + # send tx payload & wait + yield from sram_reader_driver.start(slot, length) + yield from sram_reader_driver.wait_done() + yield from sram_reader_driver.clear_done() + + # get rx payload (loopback on PHY Model) + rx_payload = [] + for i in range(length//4+1): + yield from wishbone_master.read(sram_writer_slots_offset[slot]+i) + dat = wishbone_master.dat + rx_payload += list(dat.to_bytes(4, byteorder='big')) + + # check results + s, l, e = check(tx_payload[:length], rx_payload[:min(length, len(rx_payload))]) + print("shift "+ str(s) + " / length " + str(l) + " / errors " + str(e)) + +if __name__ == "__main__": + run_simulation(TB(), vcd_name="my.vcd") diff --git a/liteethernet/mac/test/preamble_tb.py b/liteethernet/mac/test/preamble_tb.py new file mode 100644 index 00000000..85bd7e64 --- /dev/null +++ b/liteethernet/mac/test/preamble_tb.py @@ -0,0 +1,82 @@ +from migen.fhdl.std import * + +from misoclib.ethmac.common import * +from misoclib.ethmac.preamble import * +from misoclib.ethmac.test.common import * + +preamble = [ + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xD5 +] + +payload = [ + 0x00, 0x0A, 0xE6, 0xF0, 0x05, 0xA3, 0x00, 0x12, + 0x34, 0x56, 0x78, 0x90, 0x08, 0x00, 0x45, 0x00, + 0x00, 0x30, 0xB3, 0xFE, 0x00, 0x00, 0x80, 0x11, + 0x72, 0xBA, 0x0A, 0x00, 0x00, 0x03, 0x0A, 0x00, + 0x00, 0x02, 0x04, 0x00, 0x04, 0x00, 0x00, 0x1C, + 0x89, 0x4D, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13 +] + +mux = { + "inserter": 0, + "checker": 1, + "both": 2 +} + +class TB(Module): + def __init__(self, random_level=50): + sm = self.submodules + sm.streamer = PacketStreamer(eth_description(8)) + sm.streamer_randomizer = AckRandomizer(eth_description(8), random_level) + sm.logger = PacketLogger(eth_description(8)) + sm.logger_randomizer = AckRandomizer(eth_description(8), random_level) + + self.comb += [ + self.streamer.source.connect(self.streamer_randomizer.sink), + self.logger_randomizer.source.connect(self.logger.sink) + ] + + sm.preamble_inserter = PreambleInserter(8) + sm.preamble_checker = PreambleChecker(8) + + self.mux = Signal(2) + self.comb += [ + If(self.mux == mux["inserter"], + self.streamer_randomizer.source.connect(self.preamble_inserter.sink), + self.preamble_inserter.source.connect(self.logger_randomizer.sink) + ).Elif(self.mux == mux["checker"], + self.streamer_randomizer.source.connect(self.preamble_checker.sink), + self.preamble_checker.source.connect(self.logger_randomizer.sink) + ).Elif(self.mux == mux["both"], + self.streamer_randomizer.source.connect(self.preamble_inserter.sink), + self.preamble_inserter.source.connect(self.preamble_checker.sink), + self.preamble_checker.source.connect(self.logger_randomizer.sink) + ) + ] + def gen_simulation(self, selfp): + selfp.mux = mux["inserter"] + print("streamer --> preamble_inserter --> logger:") + self.streamer.send(Packet(payload)) + yield from self.logger.receive() + s, l, e = check(preamble+payload, self.logger.packet) + print("shift "+ str(s) + " / length " + str(l) + " / errors " + str(e)) + + selfp.mux = mux["checker"] + print("streamer --> preamble_checker --> logger:") + self.streamer.send(Packet(preamble+payload)) + yield from self.logger.receive() + s, l, e = check(payload, self.logger.packet) + print("shift "+ str(s) + " / length " + str(l) + " / errors " + str(e)) + + selfp.mux = mux["both"] + print("streamer --> preamble_inserter --> preamble_checker --> logger:") + self.streamer.send(Packet(payload)) + yield from self.logger.receive() + s, l, e = check(payload, self.logger.packet) + print("shift "+ str(s) + " / length " + str(l) + " / errors " + str(e)) + +if __name__ == "__main__": + from migen.sim.generic import run_simulation + run_simulation(TB(), ncycles=1000, vcd_name="my.vcd") -- 2.30.2