From e85d0e608ee0c15c5c5956a812e699325b81095d Mon Sep 17 00:00:00 2001 From: Mart Koster <30956441+Akribes@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:41:45 +0200 Subject: [PATCH] 76 ip loopback hardware test (#144) Write IP lookback hardware test --- clash-eth.cabal | 4 +- python_tests/test_eth_uart_echo.py | 19 ----- python_tests/test_ip_echo.py | 66 ++++++++++++++++ python_tests/test_uart_to_eth.py | 18 ----- python_tests/util.py | 11 +++ src/Clash/Cores/Ethernet/Examples/ArpStack.hs | 8 +- .../Cores/Ethernet/Examples/EchoStack.hs | 31 ++++---- src/Clash/Cores/Ethernet/Examples/RxStack.hs | 51 ------------- src/Clash/Cores/Ethernet/Examples/RxStacks.hs | 76 +++++++++++++++++++ .../Examples/{TxStack.hs => TxStacks.hs} | 47 ++++++++---- src/Clash/Cores/Ethernet/IP/IPPacketizers.hs | 44 +++++++---- src/Clash/Cores/Ethernet/IP/IPv4Types.hs | 38 +++++++++- .../Lattice/ECP5/Colorlight/TopEntity.hs | 11 ++- .../Extra/PacketStream/Converters.hs | 8 +- tests/Test/Cores/Ethernet/IP/IPPacketizers.hs | 33 +++++--- 15 files changed, 308 insertions(+), 157 deletions(-) delete mode 100644 python_tests/test_eth_uart_echo.py create mode 100644 python_tests/test_ip_echo.py delete mode 100644 python_tests/test_uart_to_eth.py delete mode 100644 src/Clash/Cores/Ethernet/Examples/RxStack.hs create mode 100644 src/Clash/Cores/Ethernet/Examples/RxStacks.hs rename src/Clash/Cores/Ethernet/Examples/{TxStack.hs => TxStacks.hs} (52%) diff --git a/clash-eth.cabal b/clash-eth.cabal index 72ba9807..3e078998 100644 --- a/clash-eth.cabal +++ b/clash-eth.cabal @@ -102,8 +102,8 @@ library Clash.Cores.Ethernet.Arp.ArpTypes Clash.Cores.Ethernet.Examples.ArpStack Clash.Cores.Ethernet.Examples.EchoStack - Clash.Cores.Ethernet.Examples.RxStack - Clash.Cores.Ethernet.Examples.TxStack + Clash.Cores.Ethernet.Examples.RxStacks + Clash.Cores.Ethernet.Examples.TxStacks Clash.Cores.Ethernet.IP.InternetChecksum Clash.Cores.Ethernet.IP.IPPacketizers Clash.Cores.Ethernet.IP.IPv4Types diff --git a/python_tests/test_eth_uart_echo.py b/python_tests/test_eth_uart_echo.py deleted file mode 100644 index 43f1966e..00000000 --- a/python_tests/test_eth_uart_echo.py +++ /dev/null @@ -1,19 +0,0 @@ -import unittest -import util - -input = "Hello, world!" - -class TestEthUartEcho(unittest.TestCase): - def testEthUartEcho(self): - eth_type = b'\xff\xff' - payload = input.encode('utf-8') - padding_needed = 46 - len(payload) - payload += b'\x00' * padding_needed - - with util.open_socket() as s, util.open_serial() as se: - mac = util.get_mac_addr(s, util.IFNAME) - frame = mac + mac + eth_type + payload - s.send(frame) - response = se.read(1500) - self.assertTrue(payload in response, - msg=f'\n\nExpected: response contains\n{payload}\n\nActual response: \n{response}') diff --git a/python_tests/test_ip_echo.py b/python_tests/test_ip_echo.py new file mode 100644 index 00000000..f54d9e01 --- /dev/null +++ b/python_tests/test_ip_echo.py @@ -0,0 +1,66 @@ +import unittest +import util +import os +import socket +import struct +import random + +IFNAME = os.environ['IFNAME'] +DEV = os.environ['DEV'] + +def open_socket(): + s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3)) + s.bind((IFNAME, 0)) + return s + +src_ip = '192.168.1.0' +dst_ip = '192.168.1.123' +src_mac = b"\x8c\x8c\xaa\xc8\x2b\xee" # hardcoded in echo stack +dst_mac = b"\x00\x00\x00\xff\xff\xff" + +class TestEthUartEcho(unittest.TestCase): + def _test(self, dst_ip): + """ + Sends five IP packets and tests if they are sent back + """ + with open_socket() as s: + s.settimeout(5) + + for _ in range(5): + data = os.urandom(random.randint(0,25)) + payload = struct.pack("!HHHH", 1337, 1337, len(data) + 8, 0) + data + ip_header = create_ip_header(src_ip, dst_ip, len(payload)) + packet = dst_mac + src_mac + b"\x08\x00" + ip_header + payload + + s.send(packet) + + response = s.recv(2**16 - 1) + total_length = struct.unpack_from("!H", response, 16)[0] + response = response[:total_length + 14] + + expected_ip_header = create_ip_header(dst_ip, src_ip, len(payload)) + expected = src_mac + dst_mac + b"\x08\x00" + expected_ip_header + payload + + self.assertEqual(expected, response) + + def testWithIp(self): + self._test(dst_ip) + + def testWithBroadcast(self): + self._test("192.168.1.255") + +def create_ip_header(src_ip, dst_ip, payload_length): + header = bytearray(struct.pack('!BBHHHBBH4s4s', + (4 << 4) + 5, # version, ihl + 0, + 20 + payload_length, # total length + 0, + 0, + 64, # TTL. Set to 64 so Wireshark thinks this is a totally normal UDP packet + 0, # Protocol. 0, because we can't set this in ipLitePacketizerC + 0, # checksum + socket.inet_aton(src_ip), + socket.inet_aton(dst_ip))) + checksum = util.internet_checksum(header) + header[10:12] = struct.pack("!H", checksum) + return bytes(header) diff --git a/python_tests/test_uart_to_eth.py b/python_tests/test_uart_to_eth.py deleted file mode 100644 index 46bb9c57..00000000 --- a/python_tests/test_uart_to_eth.py +++ /dev/null @@ -1,18 +0,0 @@ -import struct -import unittest -import util -import socket - -class TestUartToEth(unittest.TestCase): - def testNFrames(self): - """ - Sends a frame multiple times over UART, including preamble and a valid - frame check sequence and tests that the frames are returned over ethernet. - """ - N = 5 - frame = b"abcdefhijklmnopqrstuvwABCDEFHIJKLMNOPQRSTUVW" # 44 bytes long, should be padded to 46 bytes - with util.open_socket() as sock, util.open_serial() as ser: - ser.write(N * util.make_uart_packet(frame)) - for _ in range(N): - response = sock.recv(1500) - self.assertEqual(b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffabcdefhijklmnopqrstuvwABCDEFHIJKLMNOPQRSTUVW\x00\x00", response) diff --git a/python_tests/util.py b/python_tests/util.py index 3f0c6c56..5f7b6505 100644 --- a/python_tests/util.py +++ b/python_tests/util.py @@ -29,3 +29,14 @@ def make_uart_packet(data: bytes): if data == b'': return b'' return struct.pack("> 16) + (s & 0xffff) + s += s >> 16 + s = ~s & 0xffff + + return s diff --git a/src/Clash/Cores/Ethernet/Examples/ArpStack.hs b/src/Clash/Cores/Ethernet/Examples/ArpStack.hs index fcca6422..daa1ae27 100644 --- a/src/Clash/Cores/Ethernet/Examples/ArpStack.hs +++ b/src/Clash/Cores/Ethernet/Examples/ArpStack.hs @@ -15,8 +15,8 @@ import Clash.Cores.Crc import Clash.Cores.Crc.Catalog import Clash.Cores.Ethernet.Arp import Clash.Cores.Ethernet.Arp.ArpTypes -import Clash.Cores.Ethernet.Examples.RxStack -import Clash.Cores.Ethernet.Examples.TxStack +import Clash.Cores.Ethernet.Examples.RxStacks +import Clash.Cores.Ethernet.Examples.TxStacks import Clash.Cores.Ethernet.IP.IPv4Types import Clash.Cores.Ethernet.Mac.EthernetTypes @@ -56,9 +56,9 @@ arpStackC -> Circuit (PacketStream domEthRx 1 ()) (PacketStream domEthTx 1 ()) arpStackC rxClk rxRst rxEn txClk txRst txEn ourMacS ourIPv4S = circuit $ \stream -> do - ethStream <- rxStack @4 rxClk rxRst rxEn ourMacS -< stream + ethStream <- macRxStack @4 rxClk rxRst rxEn ourMacS -< stream [arpStream] <- packetDispatcherC (singleton $ \hdr -> _etherType hdr == arpEtherType) -< ethStream lookupIn <- constArpLookup -< () arpOtp <- arpC d10 d5 ourMacS ourIPv4S -< (arpStream, lookupIn) ethOtp <- packetArbiterC RoundRobin -< [arpOtp] - txStack txClk txRst txEn -< ethOtp + macTxStack txClk txRst txEn -< ethOtp diff --git a/src/Clash/Cores/Ethernet/Examples/EchoStack.hs b/src/Clash/Cores/Ethernet/Examples/EchoStack.hs index 2eae3f80..32298516 100644 --- a/src/Clash/Cores/Ethernet/Examples/EchoStack.hs +++ b/src/Clash/Cores/Ethernet/Examples/EchoStack.hs @@ -6,31 +6,29 @@ Module : Clash.Cores.Ethernet.Examples.EchoStack Description : Simple Ethernet echo stack. -} module Clash.Cores.Ethernet.Examples.EchoStack - ( echoStackC + ( ipEchoStackC ) where -- import prelude import Clash.Prelude -- import ethernet -import Clash.Cores.Ethernet.Examples.RxStack ( rxStack ) -import Clash.Cores.Ethernet.Examples.TxStack ( txStack ) -import Clash.Cores.Ethernet.Mac.EthernetTypes ( EthernetHeader(..), MacAddress(..) ) +import Clash.Cores.Ethernet.Examples.RxStacks ( ipRxStack ) +import Clash.Cores.Ethernet.Examples.TxStacks ( ipTxStack ) +import Clash.Cores.Ethernet.Mac.EthernetTypes ( MacAddress(..) ) -- import protocols -import Protocols ( Circuit, (|>) ) +import Protocols import Protocols.Extra.PacketStream import Protocols.Extra.PacketStream.PacketBuffer ( packetBufferC ) import Clash.Cores.Crc ( HardwareCrc ) import Clash.Cores.Crc.Catalog ( Crc32_ethernet ) +import Clash.Cores.Ethernet.IP.IPv4Types -myMac :: MacAddress -myMac = MacAddress $ 0x00 :> 0x00 :> 0x00 :> 0xff :> 0xff :> 0xff :> Nil - --- | Processes ethernet frames and echoes them back -echoStackC +-- | Processes IP packets and echoes them back +ipEchoStackC :: forall (dom :: Domain) (domEthRx :: Domain) @@ -46,12 +44,13 @@ echoStackC -> Clock domEthTx -> Reset domEthTx -> Enable domEthTx + -> Signal dom MacAddress + -> Signal dom (IPv4Address, IPv4Address) -> Circuit (PacketStream domEthRx 1 ()) (PacketStream domEthTx 1 ()) -echoStackC rxClk rxRst rxEn txClk txRst txEn = ckt +ipEchoStackC rxClk rxRst rxEn txClk txRst txEn mac ip = ckt where - swapMac hdr@EthernetHeader {..} = hdr { _macSrc = _macDst, _macDst = _macSrc} - ckt = rxStack @4 rxClk rxRst rxEn (pure myMac) + swapIp hdr@IPv4HeaderLite {..} = hdr { _ipv4lSource = _ipv4lDestination, _ipv4lDestination = _ipv4lSource} + ckt = ipRxStack @4 rxClk rxRst rxEn mac ip |> packetBufferC d10 d4 - |> mapMeta swapMac - |> txStack txClk txRst txEn - + |> mapMeta swapIp + |> ipTxStack @4 txClk txRst txEn mac diff --git a/src/Clash/Cores/Ethernet/Examples/RxStack.hs b/src/Clash/Cores/Ethernet/Examples/RxStack.hs deleted file mode 100644 index 0228caf9..00000000 --- a/src/Clash/Cores/Ethernet/Examples/RxStack.hs +++ /dev/null @@ -1,51 +0,0 @@ -{-# language FlexibleContexts #-} - -{-| -Module : Clash.Cores.Ethernet.Examples.RxStack -Description : Provides the entire receive stack as a circuit. --} -module Clash.Cores.Ethernet.Examples.RxStack - ( rxStack - ) where - -import Clash.Cores.Crc -import Clash.Cores.Crc.Catalog -import Clash.Prelude - -import Protocols -import Protocols.Extra.PacketStream -import Protocols.Extra.PacketStream.AsyncFIFO ( asyncFifoC ) -import Protocols.Extra.PacketStream.Converters ( upConverterC ) - -import Clash.Cores.Ethernet.Mac.EthernetTypes ( EthernetHeader, MacAddress ) -import Clash.Cores.Ethernet.Mac.FrameCheckSequence ( fcsValidatorC ) -import Clash.Cores.Ethernet.Mac.MacPacketizers ( macDepacketizerC ) -import Clash.Cores.Ethernet.Mac.Preamble ( preambleStripperC ) - - --- | Processes received ethernet frames -rxStack - :: forall - (dataWidth :: Nat) - (dom :: Domain) - (domEth :: Domain). - ( HiddenClockResetEnable dom - , KnownDomain domEth - , HardwareCrc Crc32_ethernet 8 dataWidth - ) - => 1 <= dataWidth - => KnownNat dataWidth - => Clock domEth - -> Reset domEth - -> Enable domEth - -> Signal dom MacAddress - -> Circuit (PacketStream domEth 1 ()) (PacketStream dom dataWidth EthernetHeader) -rxStack ethClk ethRst ethEn _macAddressS = stack - where - upConverterC' = exposeClockResetEnable upConverterC ethClk ethRst ethEn - stack = upConverterC' - |> asyncFifoC d4 ethClk ethRst ethEn hasClock hasReset hasEnable - |> preambleStripperC - |> fcsValidatorC - |> macDepacketizerC - -- TODO: filter on macAddressS or broadcoast diff --git a/src/Clash/Cores/Ethernet/Examples/RxStacks.hs b/src/Clash/Cores/Ethernet/Examples/RxStacks.hs new file mode 100644 index 00000000..7874f941 --- /dev/null +++ b/src/Clash/Cores/Ethernet/Examples/RxStacks.hs @@ -0,0 +1,76 @@ +{-# language FlexibleContexts #-} + +{-| +Module : Clash.Cores.Ethernet.Examples.RxStacks +Description : Provides the entire receive stack as a circuit. +-} +module Clash.Cores.Ethernet.Examples.RxStacks + ( macRxStack + , ipRxStack + ) where + +import Clash.Cores.Crc +import Clash.Cores.Crc.Catalog +import Clash.Prelude + +import Protocols +import Protocols.Extra.PacketStream +import Protocols.Extra.PacketStream.AsyncFIFO ( asyncFifoC ) +import Protocols.Extra.PacketStream.Converters ( upConverterC ) +import Protocols.Extra.PacketStream.Routing ( packetDispatcherC ) + +import Clash.Cores.Ethernet.IP.IPPacketizers +import Clash.Cores.Ethernet.IP.IPv4Types +import Clash.Cores.Ethernet.Mac.EthernetTypes +import Clash.Cores.Ethernet.Mac.FrameCheckSequence ( fcsValidatorC ) +import Clash.Cores.Ethernet.Mac.MacPacketizers ( macDepacketizerC ) +import Clash.Cores.Ethernet.Mac.Preamble ( preambleStripperC ) + +-- | Processes received ethernet frames +macRxStack + :: forall (dataWidth :: Nat) (dom :: Domain) (domEth :: Domain) + . ( HiddenClockResetEnable dom + , KnownDomain domEth + , HardwareCrc Crc32_ethernet 8 dataWidth + , KnownNat dataWidth + , 1 <= dataWidth + ) + => Clock domEth + -> Reset domEth + -> Enable domEth + -> Signal dom MacAddress + -> Circuit (PacketStream domEth 1 ()) (PacketStream dom dataWidth EthernetHeader) +macRxStack ethClk ethRst ethEn macAddressS = + upConverterC' + |> asyncFifoC' + |> preambleStripperC + |> fcsValidatorC + |> macDepacketizerC + |> filterMetaS (isForMyMac <$> macAddressS) + where + upConverterC' = exposeClockResetEnable upConverterC ethClk ethRst ethEn + asyncFifoC' = asyncFifoC d4 ethClk ethRst ethEn hasClock hasReset hasEnable + isForMyMac myMac (_macDst -> to) = to == myMac || to == broadcastMac + +-- | Processes received IP packets +ipRxStack + :: forall (dataWidth :: Nat) (dom :: Domain) (domEth :: Domain) + . ( HiddenClockResetEnable dom + , KnownDomain domEth + , HardwareCrc Crc32_ethernet 8 dataWidth + , KnownNat dataWidth + , 1 <= dataWidth + ) + => Clock domEth + -> Reset domEth + -> Enable domEth + -> Signal dom MacAddress + -> Signal dom (IPv4Address, IPv4Address) + -> Circuit (PacketStream domEth 1 ()) (PacketStream dom dataWidth IPv4HeaderLite) +ipRxStack ethClk ethRst ethEn macAddressS ipS = circuit $ \raw -> do + ethernetFrames <- macRxStack ethClk ethRst ethEn macAddressS -< raw + [ip] <- packetDispatcherC (isIpv4 :> Nil) -< ethernetFrames + ipDepacketizerLiteC |> filterMetaS (isForMyIp <$> ipS) -< ip + where + isIpv4 = (== 0x0800) . _etherType + isForMyIp (ip, subnet) (_ipv4lDestination -> to) = to == ip || to == ipv4Broadcast ip subnet diff --git a/src/Clash/Cores/Ethernet/Examples/TxStack.hs b/src/Clash/Cores/Ethernet/Examples/TxStacks.hs similarity index 52% rename from src/Clash/Cores/Ethernet/Examples/TxStack.hs rename to src/Clash/Cores/Ethernet/Examples/TxStacks.hs index 0c50303d..9818e32b 100644 --- a/src/Clash/Cores/Ethernet/Examples/TxStack.hs +++ b/src/Clash/Cores/Ethernet/Examples/TxStacks.hs @@ -1,11 +1,12 @@ {-# language FlexibleContexts #-} {-| -Module : Clash.Cores.Ethernet.Examples.TxStack +Module : Clash.Cores.Ethernet.Examples.TxStacks Description : Provides the entire transmit stack as a circuit. -} -module Clash.Cores.Ethernet.Examples.TxStack - ( txStack +module Clash.Cores.Ethernet.Examples.TxStacks + ( macTxStack + , ipTxStack ) where import Clash.Cores.Crc @@ -17,6 +18,8 @@ import Protocols.Extra.PacketStream ( PacketStream ) import Protocols.Extra.PacketStream.AsyncFIFO ( asyncFifoC ) import Protocols.Extra.PacketStream.Converters ( downConverterC ) +import Clash.Cores.Ethernet.IP.IPPacketizers +import Clash.Cores.Ethernet.IP.IPv4Types import Clash.Cores.Ethernet.Mac.EthernetTypes import Clash.Cores.Ethernet.Mac.FrameCheckSequence ( fcsInserterC ) import Clash.Cores.Ethernet.Mac.InterpacketGapInserter ( interpacketGapInserterC ) @@ -26,7 +29,7 @@ import Clash.Cores.Ethernet.Mac.Preamble ( preambleInserterC ) -- | Processes bytes to send over ethernet -txStack +macTxStack :: forall (dataWidth :: Nat) (dom :: Domain) (domEth :: Domain) . ( KnownNat dataWidth , 1 <= dataWidth @@ -38,12 +41,30 @@ txStack -> Reset domEth -> Enable domEth -> Circuit (PacketStream dom dataWidth EthernetHeader) (PacketStream domEth 1 ()) -txStack ethClk ethRst ethEn = ckt - where - ckt = macPacketizerC - |> paddingInserterC d60 - |> fcsInserterC - |> preambleInserterC - |> asyncFifoC d4 hasClock hasReset hasEnable ethClk ethRst ethEn - |> exposeClockResetEnable downConverterC ethClk ethRst ethEn - |> exposeClockResetEnable interpacketGapInserterC ethClk ethRst ethEn d12 +macTxStack ethClk ethRst ethEn = + macPacketizerC + |> paddingInserterC d60 + |> fcsInserterC + |> preambleInserterC + |> asyncFifoC d4 hasClock hasReset hasEnable ethClk ethRst ethEn + |> exposeClockResetEnable downConverterC ethClk ethRst ethEn + |> exposeClockResetEnable interpacketGapInserterC ethClk ethRst ethEn d12 + +-- | Sends IP packets to a known mac address +ipTxStack + :: forall (dataWidth :: Nat) (dom :: Domain) (domEth :: Domain) + . ( KnownNat dataWidth + , 1 <= dataWidth + , HiddenClockResetEnable dom + , KnownDomain domEth + , HardwareCrc Crc32_ethernet 8 dataWidth + ) + => Clock domEth + -> Reset domEth + -> Enable domEth + -> Signal dom MacAddress + -> Circuit (PacketStream dom dataWidth IPv4HeaderLite) (PacketStream domEth 1 ()) +ipTxStack ethClk ethRst ethEn macAddressS = + ipLitePacketizerC + |> toEthernetC macAddressS + |> macTxStack ethClk ethRst ethEn diff --git a/src/Clash/Cores/Ethernet/IP/IPPacketizers.hs b/src/Clash/Cores/Ethernet/IP/IPPacketizers.hs index 3b5ba89f..2526718c 100644 --- a/src/Clash/Cores/Ethernet/IP/IPPacketizers.hs +++ b/src/Clash/Cores/Ethernet/IP/IPPacketizers.hs @@ -100,17 +100,17 @@ ipDepacketizerC , 1 <= n ) => Circuit (PacketStream dom n EthernetHeader) (PacketStream dom n IPv4Header) -ipDepacketizerC = verifyChecksum |> depacketizerC const |> verifyLength +ipDepacketizerC = verifyChecksum |> depacketizerC const |> verifyIPHdr where - verifyLength = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, (go <$>) <$> fwdIn) + verifyIPHdr = Circuit $ \(fwdIn, bwdIn) -> (bwdIn, (go <$>) <$> fwdIn) go p = let header = _meta p abort = - _ipv4Version header /= 4 || _ipv4Ihl header /= 5 || + _ipv4Version header /= 4 || _ipv4FlagReserved header || - _ipv4FlagMF header || _ipv4FragmentOffset header /= 0 -- drop fragmented packets + _ipv4FlagMF header in p {_abort = _abort p || abort} -- | Version of `ipDepacketizerC` that only keeps some of the IPv4 header fields. @@ -124,7 +124,7 @@ ipDepacketizerLiteC ipDepacketizerLiteC = ipDepacketizerC |> toLiteC data VerifyChecksumS n - = DeCheck (BitVector 16) (Index (1 + 20 `Div` n)) (BitVector 8) + = DeCheck (BitVector 16) (Index (20 `DivRU` n)) (BitVector 8) -- ^ Checking. Contains accumulator, remaining bytes, and byte from previous -- packet in case of odd data widths | DeForward Bool @@ -141,24 +141,36 @@ verifyChecksum , 1 <= n ) => Circuit (PacketStream dom n EthernetHeader) (PacketStream dom n EthernetHeader) -verifyChecksum = case (sameNat (SNat @n) (SNat @(2 * (n `Div` 2) + n `Mod` 2)), compareSNat (SNat @(20 `Mod` n)) (SNat @n)) of - (Just Refl, SNatLE) -> Circuit $ mealyB go (DeCheck 0 0 undefined) +verifyChecksum = case (divProof, divModProof, modLeProof) of + (SNatLE, Just Refl, SNatLE) -> Circuit $ mealyB go (DeCheck 0 0 undefined) _ -> errorX "ipDepacketizerC: absurd in verifyChecksum: 2 * (n `Div` 2) + n `Mod` 2 not equal to n" where + divProof = compareSNat d1 (SNat @(20 `DivRU` n)) + divModProof = sameNat (SNat @n) (SNat @(2 * (n `Div` 2) + n `Mod` 2)) + modLeProof = compareSNat (SNat @(20 `Mod` n)) (SNat @n) + go :: ( 2 * (n `Div` 2) + n `Mod` 2 ~ n, 20 `Mod` n <= n) + => 1 <= 20 `DivRU` n => VerifyChecksumS n -> (Maybe (PacketStreamM2S n EthernetHeader), PacketStreamS2M) -> (VerifyChecksumS n, (PacketStreamS2M, Maybe (PacketStreamM2S n EthernetHeader))) - go (DeForward invalid) (mp, bwd) = (DeForward invalid, (bwd, (\p -> p {_abort = _abort p || invalid}) <$> mp)) + go (DeForward invalid) (fwd, bwd) = (nextSt, (bwd, (\p -> p {_abort = _abort p || invalid}) <$> fwd)) + where + nextSt = if isJust fwd && _ready bwd && isJust (_last $ fromJustX fwd) + then DeCheck 0 0 undefined + else DeForward invalid go s@(DeCheck {}) (Nothing, bwd) = (s, (bwd, Nothing)) go s@(DeCheck acc i byte) (Just fwdIn, PacketStreamS2M inReady) = (s', (PacketStreamS2M inReady, Just fwdOut)) where - containsData = i == maxBound + finalHeaderFragment = i == maxBound -- Set all data bytes to zero (dataLo, dataHi0) = splitAtI @(20 `Mod` n) @(n - 20 `Mod` n) $ _data fwdIn - dataHi1 = if containsData then repeat 0 else dataHi0 - header = dataLo ++ dataHi1 + dataHi1 = if finalHeaderFragment then repeat 0 else dataHi0 + -- If our datawidth is aligned we don't need to partial checksum of the data + header = case sameNat d0 (SNat @(20 `Mod` n)) of + Just Refl -> _data fwdIn + _ -> dataLo ++ dataHi1 -- We verify that 2 * n Div 2 + n Mod 2 = n, and distinguish between -- even and odd data widths. For odd data widths, we have to save the @@ -178,10 +190,12 @@ verifyChecksum = case (sameNat (SNat @n) (SNat @(2 * (n `Div` 2) + n `Mod` 2)), _ -> errorX "ipDepacketizerC: absurd in verifyChecksum: n `Mod` 2 not equal to 0 or 1" invalid = acc' /= 0xFFFF -- Note that we haven't taken the complement - i' = satSucc SatBound i s' | not inReady = s - | isJust (_last fwdIn) = DeForward invalid - | otherwise = DeCheck acc' i' byte' + | isJust (_last fwdIn) = DeCheck 0 0 undefined + | finalHeaderFragment = DeForward invalid + | otherwise = DeCheck acc' (succ i) byte' - fwdOut = if containsData then fwdIn { _abort = _abort fwdIn || invalid } else fwdIn + fwdOut = if finalHeaderFragment + then fwdIn { _abort = _abort fwdIn || invalid } + else fwdIn diff --git a/src/Clash/Cores/Ethernet/IP/IPv4Types.hs b/src/Clash/Cores/Ethernet/IP/IPv4Types.hs index 9087b5c4..49befb84 100644 --- a/src/Clash/Cores/Ethernet/IP/IPv4Types.hs +++ b/src/Clash/Cores/Ethernet/IP/IPv4Types.hs @@ -12,6 +12,7 @@ module Clash.Cores.Ethernet.IP.IPv4Types , toLiteC , fromLite , fromLiteC + , ipv4Broadcast ) where import Clash.Prelude @@ -27,6 +28,31 @@ import Data.Tuple newtype IPv4Address = IPv4Address (Vec 4 (BitVector 8)) deriving (Generic, Show, ShowX, NFDataX, NFData, Eq, BitPack) +bitCoerceMap2 + :: forall a b + . BitPack a + => BitPack b + => BitSize a ~ BitSize b + => (a -> a -> a) + -> b -> b -> b +bitCoerceMap2 f x y = bitCoerce $ f (bitCoerce x) (bitCoerce y) + +-- | `Bits` instance, borrowed from `BitVector`. +instance Bits IPv4Address where + (.&.) = bitCoerceMap2 @(BitVector 32) (.&.) + (.|.) = bitCoerceMap2 @(BitVector 32) (.|.) + xor = bitCoerceMap2 @(BitVector 32) xor + complement = bitCoerceMap @(BitVector 32) complement + shift a n = bitCoerceMap @(BitVector 32) (`shift` n) a + rotate a n = bitCoerceMap @(BitVector 32) (`rotate` n) a + bitSize = bitSize . bitCoerce @IPv4Address @(BitVector 32) + bitSizeMaybe = bitSizeMaybe . bitCoerce @IPv4Address @(BitVector 32) + isSigned = isSigned . bitCoerce @IPv4Address @(BitVector 32) + testBit = testBit . bitCoerce @IPv4Address @(BitVector 32) + bit = bitCoerce @(BitVector 32) . bit + popCount = popCount . bitCoerce @IPv4Address @(BitVector 32) + + -- | (Almost) full IPv4 header. Does not contain options field. data IPv4Header = IPv4Header { _ipv4Version :: BitVector 4 @@ -71,7 +97,7 @@ fromLite header = IPv4Header { _ipv4Version = 4 , _ipv4Ihl = ipv4Ihl , _ipv4Dscp = 0 , _ipv4Ecn = 0 - , _ipv4Length = _ipv4lPayloadLength header + zeroExtend (4 * ipv4Ihl) + , _ipv4Length = _ipv4lPayloadLength header + 20 , _ipv4Id = 0 , _ipv4FlagReserved = False , _ipv4FlagDF = False @@ -92,3 +118,13 @@ fromLiteC :: Circuit (PacketStream dom n IPv4HeaderLite) (PacketStream dom n IPv fromLiteC = Circuit (swap . unbundle . go . bundle) where go = fmap $ B.first $ fmap $ fmap fromLite + +-- | Computes the IPv4 broadcast address. +ipv4Broadcast + :: IPv4Address + -- ^ Host address + -> IPv4Address + -- ^ Subnet mask + -> IPv4Address + -- ^ Broadcast address +ipv4Broadcast address subnet = address .|. complement subnet diff --git a/src/Clash/Lattice/ECP5/Colorlight/TopEntity.hs b/src/Clash/Lattice/ECP5/Colorlight/TopEntity.hs index 419e7a7f..66e47646 100644 --- a/src/Clash/Lattice/ECP5/Colorlight/TopEntity.hs +++ b/src/Clash/Lattice/ECP5/Colorlight/TopEntity.hs @@ -27,6 +27,7 @@ import Clash.Lattice.ECP5.RGMII ( RGMIIRXChannel(..), RGMIITXChannel(..), rgmiiT import Protocols ( toSignals, (|>) ) +import Clash.Cores.Ethernet.Examples.EchoStack ( ipEchoStackC ) import Data.Proxy ( Proxy(Proxy) ) @@ -84,13 +85,15 @@ topEntity clk25 uartRxBit _dq_in _mdio_in eth0_rx _eth1_rx = ethTxEn = enableGen @DomEthTx -- Replace this with your FPGA's MAC address - ourMac = MacAddress (0x00 :> 0xE0 :> 0x6C :> 0x38 :> 0xCF :> 0xF0 :> Nil) - -- Hardcoded IPv4 - ourIPv4 = IPv4Address (192 :> 168 :> 1 :> 123 :> Nil) + ourMac = MacAddress (0x00 :> 0x00 :> 0x00 :> 0xff :> 0xff :> 0xff :> Nil) + -- Hardcoded IPv4 and subnet mash + ourIPv4 = ( IPv4Address (192 :> 168 :> 1 :> 123 :> Nil) + , IPv4Address (255 :> 255 :> 255 :> 0 :> Nil) + ) phyStack = exposeClockResetEnable (unsafeRgmiiRxC @DomEth0 @DomDDREth0 (delayg d80) iddrx1f) ethRxClk ethRxRst ethRxEn - |> exposeClockResetEnable (arpStackC ethRxClk ethRxRst ethRxEn ethTxClk ethTxRst ethTxEn (pure ourMac) (pure ourIPv4)) clk50 rst50 en50 + |> exposeClockResetEnable (ipEchoStackC ethRxClk ethRxRst ethRxEn ethTxClk ethTxRst ethTxEn (pure ourMac) (pure ourIPv4)) clk50 rst50 en50 |> exposeClockResetEnable (rgmiiTxC @DomEthTx @DomDDREth0 (delayg d0) oddrx1f) ethTxClk ethTxRst ethTxEn uartTxBit = uartRxBit diff --git a/src/Protocols/Extra/PacketStream/Converters.hs b/src/Protocols/Extra/PacketStream/Converters.hs index 69d90c6d..14b95929 100644 --- a/src/Protocols/Extra/PacketStream/Converters.hs +++ b/src/Protocols/Extra/PacketStream/Converters.hs @@ -27,6 +27,8 @@ data UpConverterState (dataWidth :: Nat) = -- ^ Where in the buffer we need to write the next element _ucFlush :: Bool, -- ^ If this is true the current state can presented as packetstream word + _ucFreshBuf :: Bool, + -- ^ If this is true we need to start a fresh buffer _ucAborted :: Bool, -- ^ Current packet is aborted _ucLastIdx :: Maybe (Index dataWidth) @@ -69,7 +71,8 @@ nextState st@(UpConverterState {..}) (Just PacketStreamM2S{..}) (PacketStreamS2M -- output fragment is accepted by the sink outReady = not _ucFlush || inReady bufFull = _ucIdx == maxBound - nextBuf = replace _ucIdx (head _data) _ucBuf + currBuf = if _ucFreshBuf then (repeat 0) else _ucBuf + nextBuf = replace _ucIdx (head _data) currBuf nextFlush = inLast || bufFull nextIdx = if nextFlush then 0 else _ucIdx + 1 @@ -78,6 +81,7 @@ nextState st@(UpConverterState {..}) (Just PacketStreamM2S{..}) (PacketStreamS2M { _ucBuf = nextBuf , _ucIdx = nextIdx , _ucFlush = nextFlush + , _ucFreshBuf = nextFlush , _ucAborted = nextAbort , _ucLastIdx = toMaybe inLast _ucIdx } @@ -99,7 +103,7 @@ upConverter -- Output packet stream to the sink upConverter = mealyB go s0 where - s0 = UpConverterState (repeat 0) 0 False False Nothing + s0 = UpConverterState (repeat undefined) 0 False True False Nothing go :: UpConverterState dataWidth -> (Maybe (PacketStreamM2S 1 ()), PacketStreamS2M) diff --git a/tests/Test/Cores/Ethernet/IP/IPPacketizers.hs b/tests/Test/Cores/Ethernet/IP/IPPacketizers.hs index 80edfcc7..e0d0027f 100644 --- a/tests/Test/Cores/Ethernet/IP/IPPacketizers.hs +++ b/tests/Test/Cores/Ethernet/IP/IPPacketizers.hs @@ -127,23 +127,29 @@ testIPDepacketizer _ = idWithModelSingleDomain genValidHeaders = concat <$> Gen.list (Range.linear 1 50) genValidHeaderPacket - model ps = - let - ps' = depacketizerModel const ps - aborts = (\h -> - pureInternetChecksum (C.bitCoerce h :: C.Vec 10 (C.BitVector 16)) /= 0 || - _ipv4Ihl h /= 5 || - _ipv4Version h /= 4 || - _ipv4FlagReserved h || - _ipv4FlagMF h || _ipv4FragmentOffset h /= 0 - ) . _meta <$> ps' - in - concat $ zipWith (\qs abort -> (\q -> q {_abort = _abort q || abort}) <$> qs) (chunkByPacket ps') (aborts ++ repeat False) + model fragments = concat $ zipWith setAbort packets aborts + where + setAbort packet abort = (\f -> f {_abort = _abort f || abort}) <$> packet + validateHeader hdr = + pureInternetChecksum (C.bitCoerce hdr :: C.Vec 10 (C.BitVector 16)) /= 0 || + _ipv4Ihl hdr /= 5 || + _ipv4Version hdr /= 4 || + _ipv4FlagReserved hdr || + _ipv4FlagMF hdr + packets = chunkByPacket $ depacketizerModel const fragments + aborts = validateHeader . _meta . head <$> packets + -- Odd data widths prop_ip_depacketizer_d1 :: Property prop_ip_depacketizer_d1 = testIPDepacketizer C.d1 +prop_ip_depacketizer_d3 :: Property +prop_ip_depacketizer_d3 = testIPDepacketizer C.d3 + +prop_ip_depacketizer_d5 :: Property +prop_ip_depacketizer_d5 = testIPDepacketizer C.d5 + prop_ip_depacketizer_d7 :: Property prop_ip_depacketizer_d7 = testIPDepacketizer C.d7 @@ -160,6 +166,9 @@ prop_ip_depacketizer_d23 = testIPDepacketizer C.d23 prop_ip_depacketizer_d2 :: Property prop_ip_depacketizer_d2 = testIPDepacketizer C.d2 +prop_ip_depacketizer_d4 :: Property +prop_ip_depacketizer_d4 = testIPDepacketizer C.d4 + prop_ip_depacketizer_d6 :: Property prop_ip_depacketizer_d6 = testIPDepacketizer C.d6