diff --git a/CHANGELOG.md b/CHANGELOG.md index cac4dfa..4a13770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,4 @@ * Add Lattice Semi SB_IO primitive * Add UART core * Add CRC core +* Add RGMII core diff --git a/clash-cores.cabal b/clash-cores.cabal index 163b805..97f93d8 100644 --- a/clash-cores.cabal +++ b/clash-cores.cabal @@ -108,6 +108,7 @@ common basic-config build-depends: base >= 4.10 && < 5, clash-prelude, + clash-protocols, constraints, containers >=0.5 && <0.8, ghc-typelits-extra >= 0.3.2, @@ -126,6 +127,7 @@ library Clash.Cores.Crc Clash.Cores.Crc.Internal Clash.Cores.Crc.Catalog + Clash.Cores.Ethernet.Rgmii Clash.Cores.LatticeSemi.ECP5.Blackboxes.IO Clash.Cores.LatticeSemi.ECP5.IO Clash.Cores.LatticeSemi.ICE40.Blackboxes.IO diff --git a/nix/nixpkgs.nix b/nix/nixpkgs.nix index 884d8a5..5620725 100644 --- a/nix/nixpkgs.nix +++ b/nix/nixpkgs.nix @@ -30,6 +30,10 @@ let self.callCabal2nix "doctest-parallel" sources.doctest-parallel {}; clash-prelude = self.callCabal2nix "clash-prelude" (sources.clash-compiler + "/clash-prelude") {}; + clash-protocols-base = + self.callCabal2nix "clash-protocols-base" (sources.clash-protocols + "/clash-protocols-base") {}; + clash-protocols = + self.callCabal2nix "clash-protocols" (sources.clash-protocols + "/clash-protocols") {}; clash-lib = self.callCabal2nix "clash-lib" (sources.clash-compiler + "/clash-lib") {}; clash-ghc = diff --git a/nix/sources.json b/nix/sources.json index 613a609..03eefe7 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -5,13 +5,25 @@ "homepage": "https://clash-lang.org/", "owner": "clash-lang", "repo": "clash-compiler", - "rev": "aba55fed9f45711c8336935721a43d243f7f78c1", - "sha256": "1hrzp8g189v46qfr9ds7w6w0yj5w8y4im1pa3lf5vjx3z64v26qv", + "rev": "f946617561565440d82f67747acb2486f6526a66", + "sha256": "0924xzzwzrpjb1yid9mvy2imxwrzyxfdmkd2l1wfrsdwgrc53dpg", "type": "tarball", - "url": "https://github.com/clash-lang/clash-compiler/archive/aba55fed9f45711c8336935721a43d243f7f78c1.tar.gz", + "url": "https://github.com/clash-lang/clash-compiler/archive/f946617561565440d82f67747acb2486f6526a66.tar.gz", "url_template": "https://github.com///archive/.tar.gz", "version": "1.8.1" }, + "clash-protocols": { + "branch": "packetstream", + "description": "a battery-included library for dataflow protocols", + "homepage": null, + "owner": "clash-lang", + "repo": "clash-protocols", + "rev": "b893b1b22e8157c1352295fa115e3f9c01fcaf5c", + "sha256": "0v21bzmg0p2fdzkvri9wvzhbg79zjxmp4zg53h3fb1rlcxnmxq9r", + "type": "tarball", + "url": "https://github.com/clash-lang/clash-protocols/archive/b893b1b22e8157c1352295fa115e3f9c01fcaf5c.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, "doctest-parallel": { "branch": "main", "description": "Test interactive Haskell examples", diff --git a/src/Clash/Cores/Ethernet/Rgmii.hs b/src/Clash/Cores/Ethernet/Rgmii.hs new file mode 100644 index 0000000..e3b9e45 --- /dev/null +++ b/src/Clash/Cores/Ethernet/Rgmii.hs @@ -0,0 +1,222 @@ +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} + +{- | +Module : Clash.Cores.Rgmii +Description : Functions and types to connect an RGMII PHY to a packet stream interface. + +To keep this module generic, users will have to provide their own "primitive" functions: + + 1. delay functions to set to the proper amount of delay (which can be different for RX and TX); + 2. iddr function to turn a single DDR (Double Data Rate) signal into 2 non-DDR signals; + 3. oddr function to turn two non-DDR signals into a single DDR signal. + +Note that Clash models a DDR signal as being twice as fast, thus both facilitating +and requiring type-level separation between the two "clock domains". +-} +module Clash.Cores.Ethernet.Rgmii ( + RgmiiRxChannel (..), + RgmiiTxChannel (..), + rgmiiReceiver, + rgmiiTransmitter, + unsafeRgmiiRxC, + rgmiiTxC, +) where + +import Clash.Prelude + +import Protocols +import Protocols.PacketStream + +import Data.Maybe (isJust) + +-- | RX channel from the RGMII PHY +data RgmiiRxChannel dom domDdr = RgmiiRxChannel + { rgmiiRxClk :: "rx_clk" ::: Clock dom + , rgmiiRxCtl :: "rx_ctl" ::: Signal domDdr Bit + , rgmiiRxData :: "rx_data" ::: Signal domDdr (BitVector 4) + } + +instance Protocol (RgmiiRxChannel dom domDdr) where + type Fwd (RgmiiRxChannel dom domDdr) = RgmiiRxChannel dom domDdr + type Bwd (RgmiiRxChannel dom domDdr) = Signal dom () + +-- | TX channel to the RGMII PHY +data RgmiiTxChannel domDdr = RgmiiTxChannel + { rgmiiTxClk :: "tx_clk" ::: Signal domDdr Bit + , rgmiiTxCtl :: "tx_ctl" ::: Signal domDdr Bit + , rgmiiTxData :: "tx_data" ::: Signal domDdr (BitVector 4) + } + +instance Protocol (RgmiiTxChannel domDdr) where + type Fwd (RgmiiTxChannel domDdr) = RgmiiTxChannel domDdr + type Bwd (RgmiiTxChannel domDdr) = Signal domDdr () + +-- | RGMII receiver. +rgmiiReceiver :: + forall dom domDdr. + (DomainPeriod dom ~ 2 * DomainPeriod domDdr) => + (KnownDomain dom) => + -- | RX channel from the RGMII PHY + RgmiiRxChannel dom domDdr -> + -- | RX delay function + (forall a. Signal domDdr a -> Signal domDdr a) -> + -- | iddr function + ( forall a. + (NFDataX a, BitPack a) => + Clock dom -> + Reset dom -> + Signal domDdr a -> + Signal dom (a, a) + ) -> + -- | (Error bit, Received data) + Signal dom (Bool, Maybe (BitVector 8)) +rgmiiReceiver RgmiiRxChannel{..} rxdelay iddr = bundle (ethRxErr, ethRxData) + where + ethRxCtl :: Signal dom (Bool, Bool) + ethRxCtl = iddr rgmiiRxClk resetGen (rxdelay (bitToBool <$> rgmiiRxCtl)) + + -- The RXCTL signal at the falling edge is the XOR of RXDV and RXERR + -- meaning that RXERR is the XOR of it and RXDV. + -- See RGMII interface documentation. + ethRxDv, ethRxErr :: Signal dom Bool + (ethRxDv, ethRxErr) = unbundle ((\(dv, err) -> (dv, dv `xor` err)) <$> ethRxCtl) + + -- LSB first! See RGMII interface documentation. + ethRxData1, ethRxData2 :: Signal dom (BitVector 4) + (ethRxData2, ethRxData1) = unbundle $ iddr rgmiiRxClk resetGen (rxdelay rgmiiRxData) + + ethRxData :: Signal dom (Maybe (BitVector 8)) + ethRxData = + (\(dv, dat) -> if dv then Just dat else Nothing) + <$> bundle (ethRxDv, liftA2 (++#) ethRxData1 ethRxData2) + +-- | RGMII transmitter. Does not consider transmission error. +rgmiiTransmitter :: + forall dom domDdr. + (DomainPeriod dom ~ 2 * DomainPeriod domDdr) => + Clock dom -> + Reset dom -> + -- | TX delay function + (forall a. Signal domDdr a -> Signal domDdr a) -> + -- | oddr function + ( forall a. + (NFDataX a, BitPack a) => + Clock dom -> + Reset dom -> + Signal dom a -> + Signal dom a -> + Signal domDdr a + ) -> + -- | Maybe the byte we have to send + Signal dom (Maybe (BitVector 8)) -> + -- | Error signal indicating whether the current packet is corrupt + Signal dom Bool -> + -- | TX channel to the RGMII PHY + RgmiiTxChannel domDdr +rgmiiTransmitter txClk rst txdelay oddr input err = channel + where + txEn, txErr :: Signal dom Bit + txEn = boolToBit . isJust <$> input + txErr = fmap boolToBit err + + ethTxData1, ethTxData2 :: Signal dom (BitVector 4) + (ethTxData1, ethTxData2) = unbundle $ + maybe + ( deepErrorX "rgmiiTransmitter: undefined Ethernet TX data 1" + , deepErrorX "rgmiiTransmitter: undefined Ethernet TX data 2" + ) + split + <$> input + + -- The TXCTL signal at the falling edge is the XOR of TXEN and TXERR + -- meaning that TXERR is the XOR of it and TXEN. + -- See RGMII interface documentation. + txCtl :: Signal domDdr Bit + txCtl = oddr txClk rst txEn (liftA2 xor txEn txErr) + + -- LSB first! See RGMII interface documentation. + txData :: Signal domDdr (BitVector 4) + txData = oddr txClk rst ethTxData2 ethTxData1 + + channel = + RgmiiTxChannel + { rgmiiTxClk = txdelay (oddr txClk rst (pure 1) (pure 0)) + , rgmiiTxCtl = txdelay txCtl + , rgmiiTxData = txdelay txData + } + +{- | +Circuit that adapts an `RgmiiRxChannel` to a `PacketStream`. Forwards data +from the RGMII receiver with one clock cycle latency so that we can properly +mark the last transfer of a packet: if we received valid data from the RGMII +receiver in the last clock cycle and the data in the current clock cycle is +invalid, we set `_last`. If the RGMII receiver gives an error, we set `_abort`. + +__UNSAFE__: ignores backpressure, because the RGMII PHY is unable to handle that. +-} +unsafeRgmiiRxC :: + forall dom domDdr. + (HiddenClockResetEnable dom) => + (DomainPeriod dom ~ 2 * DomainPeriod domDdr) => + -- | RX delay function + (forall a. Signal domDdr a -> Signal domDdr a) -> + -- | iddr function + ( forall a. + (NFDataX a, BitPack a) => + Clock dom -> + Reset dom -> + Signal domDdr a -> + Signal dom (a, a) + ) -> + Circuit (RgmiiRxChannel dom domDdr) (PacketStream dom 1 ()) +unsafeRgmiiRxC rxDelay iddr = fromSignals ckt + where + ckt (fwdIn, _) = (pure (), fwdOut) + where + (rxErr, rxData) = unbundle (rgmiiReceiver fwdIn rxDelay iddr) + lastRxErr = register False rxErr + lastRxData = register Nothing rxData + + fwdOut = go <$> bundle (rxData, lastRxData, lastRxErr) + + go (currData, lastData, lastErr) = + ( \byte -> + PacketStreamM2S + { _data = singleton byte + , _last = case currData of + Nothing -> Just 0 + Just _ -> Nothing + , _meta = () + , _abort = lastErr + } + ) + <$> lastData + +{- | +Circuit that adapts a `PacketStream` to an `RgmiiTxChannel`. +Never asserts backpressure. +-} +rgmiiTxC :: + forall dom domDdr. + (HiddenClockResetEnable dom) => + (DomainPeriod dom ~ 2 * DomainPeriod domDdr) => + -- | TX delay function + (forall a. Signal domDdr a -> Signal domDdr a) -> + -- | oddr function + ( forall a. + (NFDataX a, BitPack a) => + Clock dom -> + Reset dom -> + Signal dom a -> + Signal dom a -> + Signal domDdr a + ) -> + Circuit (PacketStream dom 1 ()) (RgmiiTxChannel domDdr) +rgmiiTxC txDelay oddr = fromSignals ckt + where + ckt (fwdIn, _) = (pure (PacketStreamS2M True), fwdOut) + where + input = fmap (head . _data) <$> fwdIn + err = maybe False _abort <$> fwdIn + fwdOut = rgmiiTransmitter hasClock hasReset txDelay oddr input err