diff --git a/hwtLib/peripheral/ethernet/_axis_eq.py b/hwtLib/peripheral/ethernet/_axis_eq.py new file mode 100644 index 00000000..d4e42487 --- /dev/null +++ b/hwtLib/peripheral/ethernet/_axis_eq.py @@ -0,0 +1,77 @@ +from future.backports.misc import ceil + +from hwt.code import If, Switch, log2ceil +from hwt.hdl.typeShortcuts import vec +from hwt.hdl.types.bits import Bits +from hwt.interfaces.std import Handshaked +from hwt.interfaces.utils import addClkRstn +from hwt.pyUtils.arrayQuery import iter_with_last +from hwt.synthesizer.param import Param +from hwt.synthesizer.unit import Unit +from hwtLib.amba.axis import AxiStream +from hwtLib.handshaked.streamNode import StreamNode + + +class AxiS_eq(Unit): + """ + Comparator of const size value provided as a AxiStream + """ + + def _config(self): + AxiStream._config(self) + self.VAL = Param(vec(0, 64)) + + def _declr(self): + addClkRstn(self) + with self._paramsShared(): + self.dataIn = AxiStream() + self.dataOut = Handshaked()._m() + self.dataOut.DATA_WIDTH = 1 + + def _impl(self): + V = self.VAL + VAL_W = self.VAL._dtype.bit_length() + D_W = self.DATA_WIDTH + if not V._is_full_valid(): + raise NotImplementedError() + din = self.dataIn + dout = self.dataOut + if VAL_W <= D_W: + # do comparison in single word + dout.data(din.data[VAL_W:]._eq(V)) + else: + # build fsm for comparing + word_cnt = ceil(VAL_W / D_W) + word_index = self._reg("word_index", Bits(log2ceil(word_cnt - 1)), + def_val=0) + # true if all previous words were matching + state = self._reg("state", def_val=1) + offset = 0 + word_cases = [] + for is_last_word, i in iter_with_last(range(word_cnt)): + val_low = offset + val_high = min(offset + D_W, VAL_W) + in_high = val_high - val_low + state_update = din.data[in_high:]._eq(V[val_high:val_low]) + if is_last_word: + dout.data(state & state_update) + else: + word_cases.append((i, state(state & state_update))) + + If(StreamNode([din], [dout]).ack(), + If(din.last, + word_index(0), + state(1), + ).Else( + word_index(word_index + 1), + Switch(word_index)\ + .addCases(word_cases) + ) + ) + + StreamNode([din], + [dout], + extraConds={dout: din.valid & din.last}, + skipWhen={dout: ~(din.valid & din.last)} + ).sync() + diff --git a/hwtLib/peripheral/ethernet/mac.py b/hwtLib/peripheral/ethernet/mac.py new file mode 100644 index 00000000..de7a6251 --- /dev/null +++ b/hwtLib/peripheral/ethernet/mac.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from hwt.code import connect, Or, If +from hwt.hdl.types.bits import Bits +from hwt.hdl.types.stream import HStream +from hwt.hdl.types.struct import HStruct +from hwt.interfaces.utils import addClkRstn, propagateClkRstn +from hwt.synthesizer.param import Param +from hwt.synthesizer.unit import Unit +from hwtLib.amba.axis import AxiStream +from hwtLib.amba.axis_comp.fifo_drop import AxiSFifoDrop +from hwtLib.amba.axis_comp.frame_deparser import AxiS_frameDeparser +from hwtLib.amba.axis_comp.frame_parser import AxiS_frameParser +from hwtLib.amba.axis_fullduplex import AxiStreamFullDuplex +from hwtLib.handshaked.streamNode import StreamNode +from hwtLib.logic.crc import Crc +from hwtLib.logic.crcPoly import CRC_32 +from hwtLib.peripheral.ethernet._axis_eq import AxiS_eq +from hwtLib.peripheral.ethernet.constants import ETH_BITRATE +from hwtLib.peripheral.ethernet.types import mac_t, parse_eth_addr +from hwtLib.peripheral.ethernet.vldsynced_data_err_last import VldSyncedDataErrLast +from hwtLib.amba.axis_comp.builder import AxiSBuilder + +CRC32_RESIDUE = 0x2144df1c + + +def vldSyncedReg(parent, intf): + reg = parent._reg(intf._name + "_reg", HStruct( + *((i._dtype, i._name) for i in intf._interfaces) + )) + for i in intf._interfaces: + getattr(reg, i._name)(i) + return reg + + +class EthernetMac(Unit): + """ + Media independent Ethernet MAC (Media Access Control) + Manages frame dropping, error handling and FCSs, + rest (preamble, SFD, IPG, CDCs, PHY signal protocol, ...) + is managed by adapter for specified PHY interface. + + :note: This component does not have any controll registers or statistics etc. + But the signals are accessible. Inherit from this class and + add control bus, statistics, address space of of your choice. + Same applies to a MAC address filter. + :note: This component Ehternet MAC implementation is efficient for + bandwidths where it is not required to send multiple packets in same clk tick. + (usually 10M - 10G but depends on frequency and data width) + """ + + def _config(self): + self.FREQ = Param(int(100e6)) + self.BITRATE = Param(ETH_BITRATE.M_100M) + self.DATA_WIDTH = Param(8) + self.DEFAULT_MAC_ADDR = Param("01:23:45:67:89:AB") + self.HAS_TX = Param(True) + self.HAS_RX = Param(True) + # number of fifo items (size[B] = *DATA_WIDTH/8) + self.RX_FIFO_DEPTH = Param(2048) + + def _declr(self): + addClkRstn(self) + self.USE_STRB = self.DATA_WIDTH > 8 + with self._paramsShared(): + if self.HAS_TX: + self.phy_tx = AxiStream()._m() + + if self.HAS_RX: + self.phy_rx = VldSyncedDataErrLast() + + self.eth = AxiStreamFullDuplex() + self.eth.USE_STRB = self.USE_STRB + self.eth.IS_BIGENDIAN = True + + def _rx_mac_filter(self): + def_mac = parse_eth_addr(self.DEFAULT_MAC_ADDR) + def_mac = int.from_bytes(def_mac, 'big') + mac_eq = AxiS_eq() + mac_eq._updateParamsFrom(self) + self.rx_mac_filter = mac_eq + return mac_eq.dataIn, mac_eq.dataOut + + def _rx_logic(self): + """ + Recieving of a frame takes at least these steps: + * parse dst mac + * check fcs + * cut off fcs + * store in output buffer + + The frame can be dropped if: + * there is an error durig recieving on PHY/adapter layer (err_rx_phy) + * or because of backpressure from eth.rx (err_rx_out_of_mem) + * or because of incorrect FCS (err_rx_bad_fcs) + * or because of MAC address filter (err_rx_not_my_mac) + """ + fcs_bad = self.err_rx_fcs_bad = self._sig("err_rx_fcs_bad") + fcs_good = self.err_rx_fcs_good = self._sig("err_rx_fcs_good") + out_of_mem = self.err_rx_out_of_mem = self._sig("err_rx_out_of_mem") + phy_err = self.err_rx_phy = self._sig("err_rx_phy") + not_my_mac = self.err_rx_not_my_mac = self._sig("err_rx_not_my_mac") + errors = [fcs_bad, out_of_mem, phy_err, not_my_mac] + + # dst mac is a stream because we want to resize it's channel + # according to input interface so we do not waste resoruces + # on buffers allong the way to output + + def propagate_config(u): + u.DATA_WIDTH = self.DATA_WIDTH + u.USE_STRB = self.USE_STRB + + dst_mac_parser = AxiS_frameParser(HStruct( + (HStream(mac_t, frame_len=(1, 1)), "dst"), + (HStream(Bits(8)), None), # ignore data at the end + )) + propagate_config(dst_mac_parser) + self.rx_mac_parser = dst_mac_parser + + fcs_cutter = AxiS_frameParser(HStruct( + (HStream(Bits(8)), "data"), + (Bits(32), None), # fcs to cut off + )) + propagate_config(fcs_cutter) + self.tx_fcs_cutter = fcs_cutter + + # rcr checker + crc = Crc() + crc.setConfig(CRC_32) + crc.MASK_GRANULARITY = 8 + crc.LATENCY = 0 + propagate_config(crc) + self.rx_crc = crc + + # reg added in order to see more glitches in sim more clearly + din = vldSyncedReg(self, self.phy_rx) + + in_sync = StreamNode( + [], + [dst_mac_parser.dataIn, + fcs_cutter.dataIn]) + in_sync.sync(din.vld) + for inp in (dst_mac_parser.dataIn, + fcs_cutter.dataIn, + crc.dataIn): + inp.data(din.data) + if self.USE_STRB: + inp.mask(din.mask) + inp.last(din.last) + crc.dataIn.vld(din.vld) + # drop fcs, was checked on original stream + + # output fifo for dropping of invalid frames + out_fifo = AxiSFifoDrop() + out_fifo.DEPTH = self.RX_FIFO_DEPTH + propagate_config(out_fifo) + self.rx_out_fifo = out_fifo + + out_fifo.dataIn(fcs_cutter.dataOut.data) + data = AxiSBuilder(self, out_fifo.dataOut).buff(4).end + + err_in_this_frame = self._reg("rx_err_in_this_frame", def_val=0) + fcs_good_in_this_frame = self._reg("rx_fcs_good_in_this_frame", def_val=0) + If(data.valid & data.last, + fcs_good_in_this_frame(0), + err_in_this_frame(0), + ).Else( + fcs_good_in_this_frame(fcs_good_in_this_frame | fcs_good), + err_in_this_frame(din.vld & Or(*errors)) + ) + out_fifo.dataIn_discard((din.vld & Or(*errors)) | err_in_this_frame) + + + # postpone procesing of last word until we know the result of fcs check + dout = self.eth.rx + connect(data, dout, exclude=[data.ready, data.valid]) + not_fcs_check_wait = ~data.last | fcs_good_in_this_frame | err_in_this_frame + StreamNode( + [data], [dout], + ).sync(not_fcs_check_wait) + + # errors and mac filter + mac_filter_in, is_my_mac = self._rx_mac_filter() + mac_filter_in(dst_mac_parser.dataOut.dst) + is_my_mac.rd(1) + not_my_mac(is_my_mac.vld & ~is_my_mac.data) + phy_err(din.err & din.vld) + fcs_good(din.vld & din.last & (crc.dataOut._eq(CRC32_RESIDUE))) + fcs_bad(din.vld & din.last & (crc.dataOut != CRC32_RESIDUE)) + out_of_mem(~fcs_cutter.dataIn.ready & din.vld) + propagateClkRstn(self) + + def _tx_logic(self): + """ + Compute and append FCS, underflow error checking + """ + # underflow = self.err_tx_underflow = self._sig("err_tx_underflow") + Eth_frame_t = HStruct( + (HStream(Bits(8)), "data"), + (Bits(32), "fcs"), + ) + + ff = AxiS_frameDeparser(Eth_frame_t) + crc = Crc() + crc.setConfig(CRC_32) + crc.MASK_GRANULARITY = 8 + crc.LATENCY = 0 + for c in (ff, crc): + c.DATA_WIDTH = self.DATA_WIDTH + c.USE_STRB = self.USE_STRB + self.tx_crc = crc + self.tx_frame_gen = ff + propagateClkRstn(self) + connect(self.eth.tx, + crc.dataIn, + ff.dataIn.data, + exclude=[crc.dataIn.vld, + crc.dataIn.rd, + *([self.eth.tx.strb, + crc.dataIn.mask, + ff.dataIn.data.strb, + ] if self.USE_STRB else []), + ff.dataIn.data.valid, + ff.dataIn.data.ready]) + StreamNode([self.eth.tx], + [crc.dataIn, ff.dataIn.data]).sync() + if self.USE_STRB: + ff.dataIn.data.strb(self.eth.tx.strb) + crc.dataIn.mask(self.eth.tx.strb) + fcs_tmp = self._reg("fcs_tmp", crc.dataOut._dtype) + fcs_vld = self._reg("fcs_vld", def_val=0) + # [TODO] check if this is required in order to save mux + If(crc.dataIn.vld & crc.dataIn.last, + fcs_tmp(crc.dataOut), + ff.dataIn.fcs.data(crc.dataOut), + fcs_vld(~ff.dataIn.fcs.rd) + ).Else( + ff.dataIn.fcs.data(fcs_tmp), + If(ff.dataIn.fcs.rd, + fcs_vld(0), + ) + ) + ff.dataIn.fcs.vld(fcs_vld | (crc.dataIn.vld & crc.dataIn.last)) + + self.phy_tx(ff.dataOut) + + def _impl(self): + if self.HAS_RX: + self._rx_logic() + if self.HAS_TX: + self._tx_logic() + + +if __name__ == "__main__": + from hwt.synthesizer.utils import toRtl + u = EthernetMac() + # u.DATA_WIDTH = 16 + u.HAS_TX = False + # u.HAS_RX = False + print(toRtl(u)) diff --git a/hwtLib/peripheral/ethernet/mac_rx_test.py b/hwtLib/peripheral/ethernet/mac_rx_test.py new file mode 100644 index 00000000..825f5655 --- /dev/null +++ b/hwtLib/peripheral/ethernet/mac_rx_test.py @@ -0,0 +1,60 @@ +from itertools import chain + +from hwt.pyUtils.arrayQuery import iter_with_last +from hwt.simulator.simTestCase import SingleUnitSimTestCase +from hwtLib.amba.axis import axis_recieve_bytes +from hwtLib.peripheral.ethernet.mac import EthernetMac +from hwtLib.peripheral.ethernet.mac_tx_test import REF_FRAME, REF_CRC +from hwtLib.peripheral.ethernet.types import format_eth_addr +from pycocotb.constants import CLK_PERIOD + + +class EthernetMacRx_8b_TC(SingleUnitSimTestCase): + DW = 8 + @classmethod + def setUpClass(cls): + super(SingleUnitSimTestCase, cls).setUpClass() + u = cls.getUnit() + cls.compileSim(u, build_dir="tmp/") + + @classmethod + def getUnit(cls): + u = cls.u = EthernetMac() + u.DEFAULT_MAC_ADDR = format_eth_addr(REF_FRAME[0:6]) + u.HAS_RX = True + u.HAS_TX = False + u.DATA_WIDTH = cls.DW + return u + + def test_nop(self): + u = self.u + self.randomize(u.eth.rx) + self.randomize(u.phy_rx) + self.runSim(CLK_PERIOD * 10) + self.assertEmpty(u.eth.rx._ag.data) + + def test_single(self): + u = self.u + u.phy_rx._ag.data.extend( + (d, 0, last) for last, d in iter_with_last(chain(REF_FRAME, REF_CRC)) + ) + self.runSim(CLK_PERIOD * (2 * len(u.phy_rx._ag.data) + 10)) + o, f = axis_recieve_bytes(u.eth.rx) + self.assertEqual(o, 0) + self.assertValSequenceEqual(f, REF_FRAME) + self.assertEmpty(u.eth.rx._ag.data) + + +EthernetMac_rx_TCs = [ + EthernetMacRx_8b_TC, +] + + +if __name__ == "__main__": + import unittest + suite = unittest.TestSuite() + # suite.addTest(EthernetMacRx_8b_TC('test_single')) + for tc in EthernetMac_rx_TCs: + suite.addTest(unittest.makeSuite(tc)) + runner = unittest.TextTestRunner(verbosity=3) + runner.run(suite) \ No newline at end of file diff --git a/hwtLib/peripheral/ethernet/mac_tx_test.py b/hwtLib/peripheral/ethernet/mac_tx_test.py new file mode 100644 index 00000000..9edc54e9 --- /dev/null +++ b/hwtLib/peripheral/ethernet/mac_tx_test.py @@ -0,0 +1,117 @@ +from binascii import crc32 + +from hwt.simulator.simTestCase import SingleUnitSimTestCase +from hwtLib.amba.axis import axis_send_bytes, axis_recieve_bytes +from hwtLib.peripheral.ethernet.mac import EthernetMac +from pyMathBitPrecise.bit_utils import byte_list_to_be_int +from pycocotb.constants import CLK_PERIOD + + +REF_FRAME = [ + 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] +REF_CRC = [0x7A, 0xD5, 0x6B, 0xB3] + + +class EthernetMacTx_8b_TC(SingleUnitSimTestCase): + DW = 8 + + @classmethod + def getUnit(cls): + u = cls.u = EthernetMac() + u.HAS_RX = False + u.DATA_WIDTH = cls.DW + return u + + def test_nop(self): + u = self.u + self.runSim(CLK_PERIOD * 10) + self.assertEmpty(u.phy_tx._ag.data) + + def pop_tx_frame(self): + return axis_recieve_bytes(self.u.phy_tx) + + def send_tx_frame(self, data): + axis_send_bytes(self.u.eth.tx, data) + + def test_single(self): + ref_data = REF_FRAME + crc_ref = REF_CRC + crc_ref = byte_list_to_be_int(crc_ref) + u = self.u + axis_send_bytes(u.eth.tx, ref_data) + self.runSim(CLK_PERIOD * (len(ref_data) * 2 + 10)) + f = self.pop_tx_frame() + self.assertEqual(f[0], 0) # frame offset + f = f[1] + + self.assertEqual(len(f), len(ref_data) + 4) + data = f[:-4] + self.assertValSequenceEqual(data, ref_data) + + crc = f[-4:] + crc = byte_list_to_be_int(crc) + py_crc = crc32(bytes(data)) + self.assertEqual( + crc, crc32(bytes(data)), + "0x{0:8x} 0x{1:8x}".format(crc_ref, py_crc)) + self.assertEqual( + crc, crc32(bytes(data)), + "0x{0:8x} 0x{1:8x}".format(crc, crc_ref)) + self.assertEmpty(u.phy_tx._ag.data) + + def test_frames_random_space(self, LENS=[64, 64, 65, 67]): + u = self.u + frames = [[x & 0xff for x in range(1, L + 1)] for L in LENS] + self.randomize(u.phy_tx) + for f in frames: + axis_send_bytes(u.eth.tx, f) + len_sum = sum(LENS) + self.runSim(CLK_PERIOD * (len_sum * 2 + 10)) + for f_ref in frames: + f = self.pop_tx_frame() + self.assertEqual(f[0], 0) # frame offset + f = f[1] + + self.assertEqual(len(f), len(f_ref) + 4) + data = f[:-4] + self.assertValSequenceEqual(data, f_ref) + + crc = f[-4:] + crc_ref = crc32(bytes(data)) + crc = byte_list_to_be_int(crc) + self.assertEqual( + crc, crc_ref, + "0x{0:8x} 0x{1:8x}".format(crc, crc_ref)) + self.assertEmpty(u.phy_tx._ag.data) + + +class EthernetMacTx_32b_TC(EthernetMacTx_8b_TC): + DW = 32 + + +class EthernetMacTx_64b_TC(EthernetMacTx_8b_TC): + DW = 64 + + +EthernetMac_tx_TCs = [ + EthernetMacTx_8b_TC, + EthernetMacTx_32b_TC, + EthernetMacTx_64b_TC, +] + + +if __name__ == "__main__": + import unittest + suite = unittest.TestSuite() + # suite.addTest(EthernetMacTx_32b_TC('test_single')) + for tc in EthernetMac_tx_TCs: + suite.addTest(unittest.makeSuite(tc)) + runner = unittest.TextTestRunner(verbosity=3) + runner.run(suite) diff --git a/hwtLib/tests/all.py b/hwtLib/tests/all.py index da6ad633..7c086142 100644 --- a/hwtLib/tests/all.py +++ b/hwtLib/tests/all.py @@ -30,6 +30,7 @@ from hwtLib.amba.axi_comp.to_axiLite_test import Axi_to_AxiLite_TC from hwtLib.amba.axi_test import AxiTC from hwtLib.amba.axis_comp.en_test import AxiS_en_TC +from hwtLib.amba.axis_comp.fifo_drop_test import AxiSFifoDropTC from hwtLib.amba.axis_comp.frameGen_test import AxisFrameGenTC from hwtLib.amba.axis_comp.frame_deparser.test import AxiS_frameDeparser_TC from hwtLib.amba.axis_comp.frame_join.test import AxiS_FrameJoin_TCs @@ -196,6 +197,8 @@ IpifEndpointDenseTC, IpifEndpointDenseStartTC, IpifEndpointArray from hwtLib.xilinx.ipif.interconnectMatrix_test import IpifInterconnectMatrixTC from hwtLib.xilinx.locallink.axis_conv_test import AxiS_localLinkConvTC +from hwtLib.peripheral.ethernet.mac_tx_test import EthernetMac_tx_TCs +from hwtLib.peripheral.ethernet.mac_rx_test import EthernetMac_rx_TCs # from hwt.simulator.simTestCase import SimTestCase @@ -331,6 +334,8 @@ def testSuiteFromTCs(*tcs): UartTxRxTC, SpiMasterTC, I2CMasterBitCntrlTC, + *EthernetMac_rx_TCs, + *EthernetMac_tx_TCs, MdioMasterTC, Hd44780Driver8bTC, CrcUtilsTC, @@ -371,6 +376,7 @@ def testSuiteFromTCs(*tcs): AxiSStoredBurstTC, AxiS_en_TC, AxiS_measuringFifoTC, + AxiSFifoDropTC, *AxiS_resizer_TCs, AxiS_frameDeparser_TC, AxiS_localLinkConvTC,