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

Commit

Permalink
UDP core topentity
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanG077 committed Aug 20, 2024
1 parent d6b5343 commit 868c010
Show file tree
Hide file tree
Showing 9 changed files with 573 additions and 137 deletions.
11 changes: 6 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ python_test: /run/netns/colorlight prog

HASKELL_SOURCES=$(shell find src -type f -iname '*.hs')

verilog=verilog/Clash.Lattice.ECP5.Colorlight.TopEntity.topEntity/topEntity.v
verilog=verilog/Clash.Lattice.ECP5.Colorlight.UdpCore.udpCore/udpCore.v
netlist=netlist/synth.json
pnr=netlist/pnr.cfg
bitstream=netlist/clash-eth.bit

${verilog}: ${HASKELL_SOURCES}
cabal run clash -- Clash.Lattice.ECP5.Colorlight.TopEntity --verilog -g -fclash-clear
cabal run clash -- Clash.Lattice.ECP5.Colorlight.UdpCore --verilog -g -fclash-clear

.PHONY: verilog
verilog: $(verilog)
Expand All @@ -59,10 +59,11 @@ ${netlist}: ${verilog}
mkdir -p netlist
yosys \
-m ${YOSYS_ECP5_INFER_OUTREG_LIB} \
-p "synth_ecp5 -no-rw-check -abc2 -top topEntity" \
-p "synth_ecp5 -no-rw-check -abc2 -top udpEcho" \
-p "ecp5_infer_bram_outreg" \
-p "write_json ${netlist}" \
verilog/Clash.Lattice.ECP5.Colorlight.TopEntity.topEntity/*.v
verilog/Clash.Lattice.ECP5.Colorlight.UdpCore.udpCore/*.v \
udpEcho.v

.PHONY: netlist
netlist: $(netlist)
Expand All @@ -73,7 +74,7 @@ ${pnr}: ${netlist} pinout.lpf
--textcfg ${pnr} --25k \
--speed 6 \
--package CABGA256 \
--randomize-seed --timing-allow-fail
--randomize-seed

.PHONY: pnr
pnr: $(pnr)
Expand Down
2 changes: 2 additions & 0 deletions clash-eth.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ library
Clash.Cores.Ethernet.Arp.ArpTypes
Clash.Cores.Ethernet.Examples.ArpStack
Clash.Cores.Ethernet.Examples.FullUdpStack
Clash.Cores.Ethernet.Examples.UdpCoreStack
Clash.Cores.Ethernet.Examples.RxStacks
Clash.Cores.Ethernet.Examples.TxStacks
Clash.Cores.Ethernet.IP.EthernetStream
Expand All @@ -118,6 +119,7 @@ library
Clash.Cores.Ethernet.Udp
Clash.Lattice.ECP5.Colorlight.CRG
Clash.Lattice.ECP5.Colorlight.TopEntity
Clash.Lattice.ECP5.Colorlight.UdpCore
Clash.Lattice.ECP5.Prims
Clash.Lattice.ECP5.RGMII
Protocols.Extra.PacketStream.AsyncFIFO
Expand Down
2 changes: 1 addition & 1 deletion src/Clash/Cores/Ethernet/Arp.hs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@ arpC maxAge maxWait tableDepth ourMacS ourIPv4S =
circuit $ \(ethStream, lookupIn) -> do
(entry, replyOut) <- arpReceiverC ourIPv4S -< ethStream
(lookupOut, requestOut) <- arpManagerC maxWait -< lookupIn
() <- arpTable tableDepth maxAge -< (lookupOut, entry)
() <- arpTable maxAge -< (lookupOut, entry)
arpPktOut <- Df.roundrobinCollect Df.Skip -< [replyOut, requestOut]
arpTransmitterC ourMacS ourIPv4S -< arpPktOut
163 changes: 33 additions & 130 deletions src/Clash/Cores/Ethernet/Arp/ArpTable.hs
Original file line number Diff line number Diff line change
@@ -1,152 +1,55 @@
{-|
Module : Clash.Cores.Arp.ArpTable
Description : Provides a highly configurable ARP table.
-}

{-# language FlexibleContexts #-}
{-# language RecordWildCards #-}

{-|
Module : Clash.Cores.Ethernet.Arp.ArpTable
Description : Provides an ARP table which is able to hold one ARP entry.
-}
module Clash.Cores.Ethernet.Arp.ArpTable
( arpTable
) where

import Clash.Prelude
import Clash.Signal.Extra

import Protocols
import Protocols.Df qualified as Df

import Clash.Cores.Ethernet.Arp.ArpTypes
import Clash.Cores.Ethernet.IP.IPv4Types

import Data.Maybe


data ArpTableState depth
= Active {
-- | Whether the output of the blockram contains valid data
_bramValid :: Bool
}
-- ^ The ARP table is handling insertion and lookup requests
| Invalidating {
-- | The timer of the entry at this address will be decremented
_writeAddr :: Unsigned depth
}
-- ^ The ARP table is decrementing the timers of all entries,
-- and therefore cannot currently accept any insertion or lookup requests.
deriving (Generic, Show, ShowX, NFDataX)

arpTableT
:: forall
(depth :: Nat)
(maxAgeSeconds :: Nat)
. KnownNat depth
=> KnownNat maxAgeSeconds
=> 1 <= maxAgeSeconds
=> 1 <= depth
=> depth <= 32
=> ArpTableState depth
-> ( Bool
, (ArpEntry, Index (maxAgeSeconds + 1))
, Maybe (ArpEntry, Unsigned depth)
, Maybe (IPv4Address, Unsigned depth)
, Bool
)
-> ( ArpTableState depth
, ( Ack
, Unsigned depth
, Maybe (Unsigned depth, (ArpEntry, Index (maxAgeSeconds + 1)))
, Maybe ArpResponse
)
)
-- If the reset is enabled, go back to the initial state
-- and don't acknowledge or send out data.
arpTableT _ (True, _, _, _, _) =
(Active False, (Ack False, 0, Nothing, Nothing))

arpTableT Active{..} (_, (arpEntry, secsLeft), insertionWithHash, lookupWithHash, secondPassed)
= (nextSt, (Ack True, readAddr, writeCmd, arpResponseOut))
where
writeCmd = (\(entry, hash) -> (hash, (entry, maxBound))) <$> insertionWithHash
validLookup = isJust lookupWithHash

arpResponseOut
| _bramValid && validLookup = Just (arpResponse (fst $ fromJustX lookupWithHash))
| otherwise = Nothing

-- It is possible that the IP stored in the entry is not the same as the lookup IP.
-- This happens due to hash collisions.
arpResponse lookupIP =
if secsLeft == 0 || lookupIP /= _arpIP arpEntry
then ArpEntryNotFound
else ArpEntryFound (_arpMac arpEntry)

(nextSt, readAddr)
| secondPassed = (Invalidating maxBound, maxBound)
| otherwise = (Active (validLookup && not _bramValid), maybe 0 snd lookupWithHash)

arpTableT Invalidating{..} (_, (arpEntry, secsLeft), _, _, _)
= (nextSt, (Ack False, readAddr, writeCmd, Nothing))
where
writeCmd = Just (_writeAddr, (arpEntry, satPred SatBound secsLeft))
(nextSt, readAddr)
| _writeAddr == 0 = (Active False, 0)
| otherwise = let addr = pred _writeAddr in (Invalidating addr, addr)

-- | ARP table that stores @2^depth@ entries in block ram. `maxAgeSeconds` is the number of seconds before the
-- entry will be removed from the table (lazily). The timeout is inaccurate for up to one second, because
-- the circuit uses a constant counter for efficiency. Every second, the ARP table is unable to handle insertion
-- and lookup requests for @2^depth@ clock cycles, because it needs to decrease the timers of the entries.
-- During this period, the component will assert backpressure. Note that this implies that the component will
-- not work correctly when the size of the ARP table is bigger than the clock frequency.
--
-- An entry may be evicted sooner than expected from the cache due to hash collisions; entries are addressed
-- by taking the last `depth` bits of their corresponding IPv4 address. By increasing the
-- number of entries in the table, the chance of IPv4 addresses colliding is lower.
-- | ARP table that stores one ARP entry in a register. `maxAgeSeconds` is the number of seconds before the
-- entry will be removed from the table (lazily). The timeout is inaccurate for up to one second less, because
-- the circuit uses a constant counter for efficiency reasons. For example, when `maxAgeSeconds` is set to 30,
-- an entry will expire in 29-30 seconds. The clock frequency must be at least 1 Hz for timeouts to work correctly.
arpTable
:: forall
(dom :: Domain)
(depth :: Nat)
(maxAgeSeconds :: Nat)
(dom :: Domain)
(maxAgeSeconds :: Nat)
. HiddenClockResetEnable dom
=> KnownDomain dom
=> 1 <= DomainPeriod dom
=> DomainPeriod dom <= 5 * 10^11
=> KnownNat (DomainPeriod dom)
=> 1 <= DomainPeriod dom
=> DomainPeriod dom <= 10^12
=> 1 <= maxAgeSeconds
=> 1 <= depth
=> depth <= 32
=> SNat depth
-- ^ Determines the number of entries in the ARP table, namely @2^depth@.
-> SNat maxAgeSeconds
-- ^ Entries are no longer valid after this number of seconds, starting at the time of insertion.
=> SNat maxAgeSeconds
-- ^ The ARP entry will expire after this many seconds
-> Circuit (ArpLookup dom, Df dom ArpEntry) ()
-- ^ First of LHS is a MAC lookup request for that IPv4 address.
-- Second of LHS is an insertion request.
arpTable SNat SNat = Circuit (hideReset ckt)
-- ^ First of LHS is a MAC address request for the given IPv4 address. Second of LHS is an insertion request
arpTable SNat = fromSignals ckt
where
ckt reset ((lookupReq, insertReq), ()) = ((arpResponse, outReady), ())
ckt ((lookupReq, insertReq), ()) = ((arpResponse, pure (Ack True)), ())
where
-- The underlying blockram.
tableEntry = blockRam1 NoClearOnReset (SNat @(2^depth)) (errorX "", 0) readAddr writeCmd

-- Hashes of the IPv4 addresses, used to address the blockram.
-- We simply take the last `depth` bits of the IPv4 address.
lookupWithHash :: Signal dom (Maybe (IPv4Address, Unsigned depth))
lookupWithHash = fmap (\ipAddr -> (ipAddr, resize $ bitCoerce ipAddr)) <$> lookupReq

insertionWithHash :: Signal dom (Maybe (ArpEntry, Unsigned depth))
insertionWithHash = fmap (\entry -> (entry, resize $ bitCoerce (_arpIP entry))) <$> fmap Df.dataToMaybe insertReq

readAddr :: Signal dom (Unsigned depth)
writeCmd :: Signal dom (Maybe (Unsigned depth, (ArpEntry, Index (maxAgeSeconds + 1))))
(outReady, readAddr, writeCmd, arpResponse) =
unbundle (mealy arpTableT (Active False) input)

input = bundle
( unsafeToActiveHigh reset
, tableEntry
, insertionWithHash
, lookupWithHash
, secondTimer
)
arpEntry :: Signal dom (ArpEntry, Index (maxAgeSeconds + 1))
arpEntry = register (errorX "empty initial content", 0) writeCommand

secondTimer = riseEvery (SNat @(10^12 `Div` DomainPeriod dom))
writeCommand = fmap go (bundle (insertReq, arpEntry, secondTimer))
where
go (req, (entry, secondsLeft), secondPassed) = case req of
Df.NoData -> (entry, if secondPassed then satPred SatBound secondsLeft else secondsLeft)
Df.Data reqEntry -> (reqEntry, maxBound)

arpResponse = fmap go (bundle (lookupReq, arpEntry))
where
go (ip, (entry, timeLeft)) = ip >>= \ipAddr ->
if timeLeft == 0 || _arpIP entry /= ipAddr
then Just ArpEntryNotFound
else Just (ArpEntryFound (_arpMac entry))
2 changes: 1 addition & 1 deletion src/Clash/Cores/Ethernet/Examples/FullUdpStack.hs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ arpIcmpUdpStackC
-> Signal dom (IPv4Address, IPv4Address)
-- ^ My IP address and the subnet
-> Circuit (PacketStream dom dataWidth (IPv4Address, UdpHeaderLite)) (PacketStream dom dataWidth (IPv4Address, UdpHeaderLite))
-- ^ UDP handler circuit
-- ^ Reversed 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
Expand Down
2 changes: 2 additions & 0 deletions src/Clash/Cores/Ethernet/Examples/TxStacks.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ macTxStack ethClk ethRst ethEn =
|> fcsInserterC
|> preambleInserterC
|> asyncFifoC d4 hasClock hasReset hasEnable ethClk ethRst ethEn
-- TODO: We can propbably easily remove some buffers here
|> exposeClockResetEnable (DfConv.registerBwd packetStreamProxyN packetStreamProxyN) ethClk ethRst ethEn
|> exposeClockResetEnable (DfConv.registerFwd packetStreamProxyN packetStreamProxyN) ethClk ethRst ethEn
|> exposeClockResetEnable downConverterC ethClk ethRst ethEn
|> exposeClockResetEnable (DfConv.registerBwd packetStreamProxy1 packetStreamProxy1) ethClk ethRst ethEn
|> exposeClockResetEnable (DfConv.registerFwd packetStreamProxy1 packetStreamProxy1) ethClk ethRst ethEn
|> exposeClockResetEnable interpacketGapInserterC ethClk ethRst ethEn d12
|> exposeClockResetEnable (DfConv.registerFwd packetStreamProxy1 packetStreamProxy1) ethClk ethRst ethEn
where
packetStreamProxyN = Proxy @(PacketStream domEth dataWidth ())
packetStreamProxy1 = Proxy @(PacketStream domEth 1 ())
Expand Down
Loading

0 comments on commit 868c010

Please sign in to comment.