Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RGMII core #7

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
* Add Lattice Semi SB_IO primitive
* Add UART core
* Add CRC core
* Add RGMII core
2 changes: 2 additions & 0 deletions clash-cores.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions nix/nixpkgs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
18 changes: 15 additions & 3 deletions nix/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/<owner>/<repo>/archive/<rev>.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/<owner>/<repo>/archive/<rev>.tar.gz"
},
"doctest-parallel": {
"branch": "main",
"description": "Test interactive Haskell examples",
Expand Down
222 changes: 222 additions & 0 deletions src/Clash/Cores/Ethernet/Rgmii.hs
Original file line number Diff line number Diff line change
@@ -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