Skip to content
This repository has been archived by the owner on Sep 6, 2024. It is now read-only.

Commit

Permalink
Implement UDP protocol and write full ICMP, ARP and UDP demo
Browse files Browse the repository at this point in the history
  • Loading branch information
Akribes committed Jun 4, 2024
1 parent a1eeaef commit 7ca1b2b
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 77 deletions.
1 change: 1 addition & 0 deletions clash-eth.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ library
Clash.Cores.Ethernet.Mac.PaddingInserter
Clash.Cores.Ethernet.Mac.Preamble
Clash.Cores.Ethernet.Icmp
Clash.Cores.Ethernet.Udp
Clash.Lattice.ECP5.Colorlight.CRG
Clash.Lattice.ECP5.Colorlight.TopEntity
Clash.Lattice.ECP5.Prims
Expand Down
17 changes: 17 additions & 0 deletions python_tests/test_arp_udp_echo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import unittest
import os
import socket
import random

dst_ip = '192.168.1.123'

class TestArpUdpEcho(unittest.TestCase):
def testArpUdpEcho(self):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.settimeout(5)

for _ in range(50):
data = os.urandom(random.randint(0,1000))
port = random.randint(0, 65535)
s.sendto(data, (dst_ip, port))
self.assertEqual((data, (dst_ip, port)), s.recvfrom(1500))
66 changes: 0 additions & 66 deletions python_tests/test_ip_echo.py

This file was deleted.

88 changes: 84 additions & 4 deletions src/Clash/Cores/Ethernet/Examples/EchoStack.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,37 @@ Description : Simple Ethernet echo stack.
-}
module Clash.Cores.Ethernet.Examples.EchoStack
( ipEchoStackC
, fullStackC
, arpIcmpUdpStackC
) where

import Data.Bifunctor qualified as B

-- import prelude
import Clash.Prelude

-- import ethernet
import Clash.Cores.Ethernet.Examples.RxStacks ( ipRxStack )
import Clash.Cores.Ethernet.Examples.TxStacks ( ipTxStack )
import Clash.Cores.Ethernet.Mac.EthernetTypes ( MacAddress(..) )
import Clash.Cores.Ethernet.Arp
import Clash.Cores.Ethernet.Examples.RxStacks
import Clash.Cores.Ethernet.Examples.TxStacks
import Clash.Cores.Ethernet.IP.IPPacketizers
import Clash.Cores.Ethernet.Mac.EthernetTypes ( EthernetHeader(..), MacAddress(..) )

import Clash.Cores.Ethernet.IP.EthernetStream
import Clash.Cores.Ethernet.IP.IPv4Types

-- import protocols
import Protocols
import Protocols.Extra.PacketStream
import Protocols.Extra.PacketStream.PacketBuffer ( packetBufferC )
import Protocols.Extra.PacketStream.Routing

import Clash.Cores.Crc ( HardwareCrc )
import Clash.Cores.Crc.Catalog ( Crc32_ethernet )

import Clash.Cores.Ethernet.IP.IPv4Types
import Clash.Cores.Ethernet.Icmp ( icmpEchoResponderC )
import Clash.Cores.Ethernet.Udp


-- | Processes IP packets and echoes them back
ipEchoStackC
Expand Down Expand Up @@ -54,3 +66,71 @@ ipEchoStackC rxClk rxRst rxEn txClk txRst txEn mac ip = ckt
|> packetBufferC d10 d4
|> mapMeta swapIp
|> ipTxStack @4 txClk txRst txEn mac

-- | Full stack from ethernet to ethernet.
fullStackC
:: forall
(dom :: Domain)
(domEthRx :: Domain)
(domEthTx :: Domain)
. KnownDomain dom
=> KnownDomain domEthRx
=> KnownDomain domEthTx
=> HardwareCrc Crc32_ethernet 8 4
=> 1 <= DomainPeriod dom
=> DomainPeriod dom <= 5 * 10^11
=> KnownNat (DomainPeriod dom)
=> HiddenClockResetEnable dom
=> Clock domEthRx
-> Reset domEthRx
-> Enable domEthRx
-> Clock domEthTx
-> Reset domEthTx
-> Enable domEthTx
-> Signal dom MacAddress
-- ^ My mac address
-> Signal dom (IPv4Address, IPv4Address)
-- ^ Tuple of my IP and subnet mask
-> Circuit (PacketStream domEthRx 1 ()) (PacketStream domEthTx 1 ())
fullStackC rxClk rxRst rxEn txClk txRst txEn mac ip =
macRxStack @4 rxClk rxRst rxEn mac
|> arpIcmpUdpStackC mac ip (mapMeta $ B.second swapPorts)
|> macTxStack txClk txRst txEn
where
swapPorts hdr@UdpHeaderLite{..} = hdr
{ _udplSrcPort = _udplDstPort
, _udplDstPort = _udplSrcPort
}

-- | Wraps a circuit that handles UDP packets into a stack that handles IP, ICMP
-- and ARP.
arpIcmpUdpStackC
:: forall (dataWidth :: Nat) (dom :: Domain)
. HiddenClockResetEnable dom
=> KnownNat dataWidth
=> 1 <= dataWidth
=> 1 <= DomainPeriod dom
=> DomainPeriod dom <= 5 * 10^11
=> KnownNat (DomainPeriod dom)
=> Signal dom MacAddress
-- ^ My MAC Address
-> Signal dom (IPv4Address, IPv4Address)
-- ^ My IP address and the subnet
-> Circuit (PacketStream dom dataWidth (IPv4Address, UdpHeaderLite)) (PacketStream dom dataWidth (IPv4Address, UdpHeaderLite))
-- ^ UDP handler circuit
-> Circuit (PacketStream dom dataWidth EthernetHeader) (PacketStream dom dataWidth EthernetHeader)
arpIcmpUdpStackC macAddressS ipS udpCkt = circuit $ \ethIn -> do
[arpEthIn, ipEthIn] <- packetDispatcherC (routeBy _etherType $ 0x0806 :> 0x0800 :> Nil) -< ethIn
ipTx <- ipLitePacketizerC <| packetBufferC d10 d4 <| icmpUdpStack <| packetBufferC d10 d4 <| filterMetaS (isForMyIp <$> ipS) <| ipDepacketizerLiteC -< ipEthIn
(ipEthOut, arpLookup) <- toEthernetStreamC macAddressS -< ipTx
arpEthOut <- arpC d10 d5 macAddressS (fst <$> ipS) -< (arpEthIn, arpLookup)
packetArbiterC RoundRobin -< [arpEthOut, ipEthOut]

where
icmpUdpStack = circuit $ \ipIn -> do
[icmpIn, udpIn] <- packetDispatcherC (routeBy _ipv4lProtocol $ 0x0001 :> 0x0011 :> Nil) -< ipIn
icmpOut <- icmpEchoResponderC @dom @dataWidth (fst <$> ipS) -< icmpIn
udpInParsed <- udpDepacketizerC -< udpIn
udpOutParsed <- udpPacketizerC (fst <$> ipS) <| udpCkt -< udpInParsed
packetArbiterC RoundRobin -< [icmpOut, udpOutParsed]
isForMyIp (ip, subnet) (_ipv4lDestination -> to) = to == ip || to == ipv4Broadcast ip subnet
4 changes: 3 additions & 1 deletion src/Clash/Cores/Ethernet/IP/IPv4Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,15 @@ data IPv4Header = IPv4Header
data IPv4HeaderLite = IPv4HeaderLite
{ _ipv4lSource :: IPv4Address
, _ipv4lDestination :: IPv4Address
, _ipv4lProtocol :: Unsigned 8
, _ipv4lPayloadLength :: Unsigned 16
} deriving (Show, ShowX, Eq, Generic, BitPack, NFDataX, NFData)

toLite :: IPv4Header -> IPv4HeaderLite
toLite IPv4Header {..} = IPv4HeaderLite
{ _ipv4lSource = _ipv4Source
, _ipv4lDestination = _ipv4Destination
, _ipv4lProtocol = _ipv4Protocol
, _ipv4lPayloadLength = _ipv4Length - 20 -- We do not support IHLs other than 5
}

Expand All @@ -104,7 +106,7 @@ fromLite header = IPv4Header { _ipv4Version = 4
, _ipv4FlagMF = False
, _ipv4FragmentOffset = 0
, _ipv4Ttl = 64
, _ipv4Protocol = 0
, _ipv4Protocol = _ipv4lProtocol header
, _ipv4Checksum = 0
, _ipv4Source = _ipv4lSource header
, _ipv4Destination = _ipv4lDestination header
Expand Down
2 changes: 1 addition & 1 deletion src/Clash/Cores/Ethernet/Mac/EthernetTypes.hs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ toEthernetC macSrc = Circuit (swap . unbundle . helper macSrc . bundle)
}

hardCodedMac :: MacAddress
hardCodedMac = MacAddress (0x8C :> 0x8C :> 0xAA :> 0xC8 :> 0x2B :> 0xEE :> Nil)
hardCodedMac = MacAddress (0x00 :> 0xe0 :> 0x6c :> 0x38 :> 0xd0:> 0x2c :> Nil)

-- | Broadcast MAC address.
broadcastMac :: MacAddress
Expand Down
89 changes: 89 additions & 0 deletions src/Clash/Cores/Ethernet/Udp.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{-# language RecordWildCards #-}

{-|
Module : Clash.Cores.Ethernet.Udp
Description : Circuits and data types to handle the UDP protocol
-}
module Clash.Cores.Ethernet.Udp
( UdpHeaderLite(..)
, udpDepacketizerC
, udpPacketizerC
)
where

import Clash.Cores.Ethernet.IP.IPv4Types
import Clash.Prelude
import Protocols
import Protocols.Extra.PacketStream
import Protocols.Extra.PacketStream.Packetizers ( depacketizerC, packetizerC )

-- | The full UDP header
data UdpHeader = UdpHeader
{ _udpSrcPort :: Unsigned 16
-- ^ Source port
, _udpDstPort :: Unsigned 16
-- ^ Destination port
, _udpLength :: Unsigned 16
-- ^ length of header + payload
, _udpChecksum :: Unsigned 16
-- ^ UDP Checksum, We do not validate or generate it
} deriving (Generic, NFDataX, BitPack, Eq, Show, ShowX)

-- | UDP header
data UdpHeaderLite = UdpHeaderLite
{ _udplSrcPort :: Unsigned 16
-- ^ Source port
, _udplDstPort :: Unsigned 16
-- ^ Destination port
, _udplPayloadLength :: Unsigned 16
-- ^ Length of payload
} deriving (Generic, NFDataX, BitPack, Eq, Show, ShowX)

fromUdpLite :: UdpHeaderLite -> UdpHeader
fromUdpLite UdpHeaderLite{..} = UdpHeader
{ _udpSrcPort = _udplSrcPort
, _udpDstPort = _udplDstPort
, _udpLength = _udplPayloadLength + 8
, _udpChecksum = 0
}

toUdpLite :: UdpHeader -> UdpHeaderLite
toUdpLite UdpHeader{..} = UdpHeaderLite
{ _udplSrcPort = _udpSrcPort
, _udplDstPort = _udpDstPort
, _udplPayloadLength = _udpLength - 8
}

-- | Parses out the UDP header from an IP stream, but ignores the checksum.
-- The first element of the metadata is the source IP of incoming packets.
udpDepacketizerC
:: HiddenClockResetEnable dom
=> KnownNat n
=> 1 <= n
=> Circuit
(PacketStream dom n IPv4HeaderLite)
(PacketStream dom n (IPv4Address, UdpHeaderLite))
udpDepacketizerC = depacketizerC (\udph ipv4lh -> (_ipv4lSource ipv4lh, toUdpLite udph))

-- | Serializes the UDP packet to an IP stream. The first element of the metadata
-- is the destination IP for outgoing packets. No checksum is included in the UDP header.
udpPacketizerC
:: HiddenClockResetEnable dom
=> KnownNat n
=> 1 <= n
=> Signal dom IPv4Address
-- ^ Source IP address
-> Circuit
(PacketStream dom n (IPv4Address, UdpHeaderLite))
(PacketStream dom n IPv4HeaderLite)
udpPacketizerC myIp = mapMetaS (toIp <$> myIp) |> packetizerC fst snd
where
toIp srcIp (dstIp, udpLite) = (ipLite, udpHeader)
where
udpHeader = fromUdpLite udpLite
ipLite = IPv4HeaderLite
{ _ipv4lSource = srcIp
, _ipv4lDestination = dstIp
, _ipv4lProtocol = 0x0011
, _ipv4lPayloadLength = _udpLength udpHeader
}
5 changes: 2 additions & 3 deletions src/Clash/Lattice/ECP5/Colorlight/TopEntity.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import Clash.Prelude ( exposeClockResetEnable )

import Clash.Cores.Crc ( deriveHardwareCrc )
import Clash.Cores.Crc.Catalog ( Crc32_ethernet )
import Clash.Cores.Ethernet.Examples.ArpStack
import Clash.Cores.Ethernet.IP.IPv4Types ( IPv4Address(IPv4Address) )
import Clash.Cores.Ethernet.Mac.EthernetTypes ( MacAddress(MacAddress) )
import Clash.Lattice.ECP5.Colorlight.CRG
Expand All @@ -27,7 +26,7 @@ import Clash.Lattice.ECP5.RGMII ( RGMIIRXChannel(..), RGMIITXChannel(..), rgmiiT

import Protocols ( toSignals, (|>) )

import Clash.Cores.Ethernet.Examples.EchoStack ( ipEchoStackC )
import Clash.Cores.Ethernet.Examples.EchoStack ( fullStackC )
import Data.Proxy ( Proxy(Proxy) )


Expand Down Expand Up @@ -93,7 +92,7 @@ topEntity clk25 uartRxBit _dq_in _mdio_in eth0_rx _eth1_rx =

phyStack
= exposeClockResetEnable (unsafeRgmiiRxC @DomEth0 @DomDDREth0 (delayg d80) iddrx1f) ethRxClk ethRxRst ethRxEn
|> exposeClockResetEnable (ipEchoStackC ethRxClk ethRxRst ethRxEn ethTxClk ethTxRst ethTxEn (pure ourMac) (pure ourIPv4)) clk50 rst50 en50
|> exposeClockResetEnable (fullStackC 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
Expand Down
10 changes: 10 additions & 0 deletions src/Protocols/Extra/PacketStream/Routing.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Protocols.Extra.PacketStream.Routing
( packetArbiterC
, ArbiterMode(..)
, packetDispatcherC
, routeBy
) where

import Clash.Prelude
Expand Down Expand Up @@ -78,3 +79,12 @@ packetDispatcherC fs = Circuit (second unbundle . unbundle . fmap go . bundle .
Just i -> (bwds !! i, replace i (Just x) (repeat Nothing))
_ -> (PacketStreamS2M True, repeat Nothing)
go _ = (PacketStreamS2M False, repeat Nothing)

-- | Routing function for `packetDispatcherC` that matches against values with
-- an `Eq` instance. Useful to route according to a record field.
routeBy
:: Eq b
=> (a -> b)
-> Vec p b
-> Vec p (a -> Bool)
routeBy f = fmap $ \x -> (== x) . f
2 changes: 1 addition & 1 deletion tests/Test/Cores/Ethernet/Icmp.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ genIpAddr :: Gen IPv4Address
genIpAddr = IPv4Address <$> C.sequence (C.repeat @4 Gen.enumBounded)

genIPv4HeaderLite :: Gen IPv4HeaderLite
genIPv4HeaderLite = IPv4HeaderLite <$> genIpAddr <*> genIpAddr <*> pure 0
genIPv4HeaderLite = IPv4HeaderLite <$> genIpAddr <*> genIpAddr <*> Gen.enumBounded <*> pure 0

packetize
:: 1 C.<= dataWidth
Expand Down
2 changes: 1 addition & 1 deletion tests/Test/Cores/Ethernet/Mac/EthernetTypes.hs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ toEthernetTest C.SNat macSrc =
model src = map $ fmap (toEthernet src)

hardCodedMac :: MacAddress
hardCodedMac = MacAddress (0x8C C.:> 0x8C C.:> 0xAA C.:> 0xC8 C.:> 0x2B C.:> 0xEE C.:> C.Nil)
hardCodedMac = MacAddress (0x00 C.:> 0xe0 C.:> 0x6c C.:> 0x38 C.:> 0xd0 C.:> 0x2c C.:> C.Nil)

toEthernet :: MacAddress -> IPv4Address -> EthernetHeader
toEthernet src _ = EthernetHeader {
Expand Down

0 comments on commit 7ca1b2b

Please sign in to comment.