From 0dfef393aeeb1098c568301a96f04f5f0caf6340 Mon Sep 17 00:00:00 2001 From: Peter Lebbing Date: Thu, 10 Oct 2024 12:44:49 +0200 Subject: [PATCH 1/2] New Cabal flag: clash-18 The new clash-18 flag makes it possible to exclude whole modules based on the targeted Clash version. If Cabal can access a more recent Clash version than 1.8, it will include those modules that need such a recent version. If it cannot find a recent enough Clash, it will subsequently fall back to also supporting Clash 1.8. In the latter case, it will exclude those modules that need a more recent version. The next commit will add such modules. Other minor improvements in this commit: - `cabal.project` allows listing multiple `subdir`s in one `source-repository-package` stanza, so use that functionality to simplify. - Move more common dependencies to the `common` section - Minor syntax improvements in `clash-cores.cabal` - `.tix` is added to `.gitignore` for files generated by Haskell Program Coverage (HPC). Co-authored-by: Jasper Vinkenvleugel --- .gitignore | 1 + cabal.project-master | 32 ++++++-------------------------- clash-cores.cabal | 26 ++++++++++++++++++-------- stack.yaml | 4 ++++ 4 files changed, 29 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 430c734..b87676d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ cabal.config *.bin *.log *.tar.gz +*.tix *~ *.DS_Store diff --git a/cabal.project-master b/cabal.project-master index 28c0e30..90b2b9a 100644 --- a/cabal.project-master +++ b/cabal.project-master @@ -3,32 +3,12 @@ source-repository-package type: git location: https://github.com/clash-lang/clash-compiler.git tag: master - subdir: clash-prelude - -source-repository-package - type: git - location: https://github.com/clash-lang/clash-compiler.git - tag: master - subdir: clash-lib - -source-repository-package - type: git - location: https://github.com/clash-lang/clash-compiler.git - tag: master - subdir: clash-ghc - -source-repository-package - type: git - location: https://github.com/clash-lang/clash-compiler.git - tag: master - subdir: clash-prelude-hedgehog - --- clash-testsuite -source-repository-package - type: git - location: https://github.com/clash-lang/clash-compiler.git - tag: master - subdir: tests + subdir: + clash-prelude + clash-lib + clash-ghc + clash-prelude-hedgehog + tests constraints: clash-prelude == 1.9.0, diff --git a/clash-cores.cabal b/clash-cores.cabal index 1eeffe3..2ea6468 100644 --- a/clash-cores.cabal +++ b/clash-cores.cabal @@ -42,6 +42,11 @@ flag nix default: False manual: True +flag clash-18 + description: + Disable cores that require a Clash version later than 1.8 + default: False + -- TODO: Remove this workaround. See: -- -- https://github.com/clash-lang/clash-compiler/pull/2665#issuecomment-1939044550 @@ -107,17 +112,26 @@ common basic-config build-depends: base >= 4.10 && < 5, - clash-prelude, constraints, containers >=0.5 && <0.8, ghc-typelits-extra >= 0.3.2, ghc-typelits-knownnat >= 0.6, ghc-typelits-natnormalise >= 0.6, + infinite-list ^>= 0.1, lens, QuickCheck, string-interpolate ^>= 0.3, template-haskell, + if flag(clash-18) + build-depends: + clash-lib >= 1.8.1, + clash-prelude >= 1.8.1, + else + build-depends: + clash-lib >= 1.9.0, + clash-prelude >= 1.9.0, + library import: basic-config hs-source-dirs: src @@ -172,9 +186,7 @@ library -fno-worker-wrapper build-depends: - clash-lib, unordered-containers, - infinite-list ^>= 0.1, ghc-prim >= 0.3.1.0 && < 1.0, lens, mtl >= 2.1.1 && < 2.4, @@ -184,7 +196,7 @@ library reducers >= 3.12.2 && < 4.0, text >= 1.2.2 && < 2.2, constraints >= 0.9 && < 1.0, - template-haskell >= 2.12.0.0 && < 2.22 + template-haskell >= 2.12.0.0 && < 2.22, test-suite unit-tests import: basic-config @@ -195,7 +207,7 @@ test-suite unit-tests if !flag(unit-tests) buildable: False - other-Modules: + other-modules: Test.Cores.Crc Test.Cores.Internal.SampleSPI Test.Cores.LineCoding8b10b @@ -209,7 +221,6 @@ test-suite unit-tests build-depends: clash-cores, - clash-lib, clash-prelude-hedgehog, deepseq, tasty >= 1.2 && < 1.6, @@ -218,7 +229,6 @@ test-suite unit-tests tasty-th, hedgehog, tasty-hedgehog >= 1.2.0, - infinite-list test-suite doctests type: exitcode-stdio-1.0 @@ -234,4 +244,4 @@ test-suite doctests base, clash-cores, doctest-parallel >= 0.2 && < 0.4, - filepath + filepath, diff --git a/stack.yaml b/stack.yaml index 93c932f..505afa9 100644 --- a/stack.yaml +++ b/stack.yaml @@ -7,6 +7,10 @@ packages: - . - hdl-tests +flags: + clash-cores: + clash-18: true + extra-deps: - # clash-testsuite git: https://github.com/clash-lang/clash-compiler.git From 275dca5973bcb203ae6b07b3060cf1428a7c13ac Mon Sep 17 00:00:00 2001 From: Jasper Vinkenvleugel Date: Wed, 17 Jul 2024 20:10:30 +0200 Subject: [PATCH 2/2] Add SGMII support Add support for connecting to Ethernet PHYs using SGMII References: - SGMII: https://archive.org/download/sgmii/SGMII.pdf - IEEE 802.3: https://standards.ieee.org/ieee/802.3/10422/ --- clash-cores.cabal | 21 ++ src/Clash/Cores/Sgmii.hs | 325 ++++++++++++++++++ src/Clash/Cores/Sgmii/AutoNeg.hs | 236 +++++++++++++ src/Clash/Cores/Sgmii/BitSlip.hs | 100 ++++++ src/Clash/Cores/Sgmii/Common.hs | 147 ++++++++ src/Clash/Cores/Sgmii/PcsReceive.hs | 299 ++++++++++++++++ src/Clash/Cores/Sgmii/PcsTransmit.hs | 50 +++ .../Cores/Sgmii/PcsTransmit/CodeGroup.hs | 117 +++++++ .../Cores/Sgmii/PcsTransmit/OrderedSet.hs | 229 ++++++++++++ src/Clash/Cores/Sgmii/RateAdapt.hs | 87 +++++ src/Clash/Cores/Sgmii/Sync.hs | 191 ++++++++++ test/Test/Cores/Sgmii/AutoNeg.hs | 161 +++++++++ test/Test/Cores/Sgmii/BitSlip.hs | 93 +++++ test/Test/Cores/Sgmii/RateAdapt.hs | 118 +++++++ test/Test/Cores/Sgmii/Sgmii.hs | 206 +++++++++++ test/Test/Cores/Sgmii/Sync.hs | 97 ++++++ test/unit-tests.hs | 25 +- 17 files changed, 2501 insertions(+), 1 deletion(-) create mode 100644 src/Clash/Cores/Sgmii.hs create mode 100644 src/Clash/Cores/Sgmii/AutoNeg.hs create mode 100644 src/Clash/Cores/Sgmii/BitSlip.hs create mode 100644 src/Clash/Cores/Sgmii/Common.hs create mode 100644 src/Clash/Cores/Sgmii/PcsReceive.hs create mode 100644 src/Clash/Cores/Sgmii/PcsTransmit.hs create mode 100644 src/Clash/Cores/Sgmii/PcsTransmit/CodeGroup.hs create mode 100644 src/Clash/Cores/Sgmii/PcsTransmit/OrderedSet.hs create mode 100644 src/Clash/Cores/Sgmii/RateAdapt.hs create mode 100644 src/Clash/Cores/Sgmii/Sync.hs create mode 100644 test/Test/Cores/Sgmii/AutoNeg.hs create mode 100644 test/Test/Cores/Sgmii/BitSlip.hs create mode 100644 test/Test/Cores/Sgmii/RateAdapt.hs create mode 100644 test/Test/Cores/Sgmii/Sgmii.hs create mode 100644 test/Test/Cores/Sgmii/Sync.hs diff --git a/clash-cores.cabal b/clash-cores.cabal index 2ea6468..fe26a02 100644 --- a/clash-cores.cabal +++ b/clash-cores.cabal @@ -178,6 +178,19 @@ library Clash.Cores.Xilinx.Xpm.Cdc.Single Clash.Cores.Xilinx.Xpm.Cdc.SyncRst + if !flag(clash-18) + exposed-modules: + Clash.Cores.Sgmii + Clash.Cores.Sgmii.AutoNeg + Clash.Cores.Sgmii.BitSlip + Clash.Cores.Sgmii.Common + Clash.Cores.Sgmii.PcsReceive + Clash.Cores.Sgmii.PcsTransmit + Clash.Cores.Sgmii.PcsTransmit.CodeGroup + Clash.Cores.Sgmii.PcsTransmit.OrderedSet + Clash.Cores.Sgmii.RateAdapt + Clash.Cores.Sgmii.Sync + other-modules: Data.Text.Extra @@ -219,6 +232,14 @@ test-suite unit-tests Test.Cores.Xilinx.DcFifo Test.Cores.Xilinx.DnaPortE2 + if !flag(clash-18) + other-modules: + Test.Cores.Sgmii.AutoNeg + Test.Cores.Sgmii.BitSlip + Test.Cores.Sgmii.RateAdapt + Test.Cores.Sgmii.Sgmii + Test.Cores.Sgmii.Sync + build-depends: clash-cores, clash-prelude-hedgehog, diff --git a/src/Clash/Cores/Sgmii.hs b/src/Clash/Cores/Sgmii.hs new file mode 100644 index 0000000..fb1a129 --- /dev/null +++ b/src/Clash/Cores/Sgmii.hs @@ -0,0 +1,325 @@ +{-# LANGUAGE CPP #-} + +{- | + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + Top-level SGMII module that combines all the blocks that are defined in the + sub-modules to one function that can be used in different projects. + + Example usage: + +@ +topEntity :: + Clock Dom0 -> + Clock Dom1 -> + Reset Dom0 -> + Reset Dom1 -> + Signal Dom1 Bool -> + Signal Dom1 Bool -> + Signal Dom1 (BitVector 8) -> + Signal Dom0 (BitVector 10) -> + ( Signal rxDom SgmiiStatus + , Signal rxDom Bool + , Signal rxDom Bool + , Signal rxDom (BitVector 8) + , Signal txDom (BitVector 10) + ) +topEntity = sgmii rxTxCdc +@ + Here, the type of @rxTxCdc@, which is the function that handles the + clock domain crossing between the transmit and receive domain between the + auto-negotiation block and transmission block, needs to be the following: + +@ +rxTxCdc :: + forall dom0 dom1. + (KnownDomain dom0, KnownDomain dom1) => + Clock rxDom -> + Clock txDom -> + Signal rxDom (Maybe Xmit) -> + Signal rxDom (Maybe ConfReg) -> + Signal rxDom (Maybe ConfReg) -> + ( Signal txDom (Maybe Xmit) + , Signal txDom (Maybe ConfReg) + , Signal txDom (Maybe ConfReg) + ) +@ + + For Xilinx boards, this could be implemented by using, for example, the + function 'Clash.Cores.Xilinx.Xpm.Cdc.Handshake.xpmCdcHandshake', but + vendor-neutral implementations could make use of other word-synchronizers. + + As the decoding of incoming 10-bit code groups is done on a best-effort + basis and they are always transmitted to @TXD@, this port should only be + read when @RX_DV@ is asserted as invalid data might be provided when it is + not. +-} +module Clash.Cores.Sgmii + ( sgmii + , sgmiiRA + , sgmiiRx + , sgmiiTx + ) +where + +import Clash.Cores.LineCoding8b10b +import Clash.Cores.Sgmii.AutoNeg +import Clash.Cores.Sgmii.BitSlip +import Clash.Cores.Sgmii.Common +import Clash.Cores.Sgmii.PcsReceive +import Clash.Cores.Sgmii.PcsTransmit +import Clash.Cores.Sgmii.RateAdapt +import Clash.Cores.Sgmii.Sync +import Clash.Prelude +import Data.Maybe (fromMaybe, isJust) + +-- | Receive side of the SGMII block, that combines all the functions that are +-- in the receive domain +sgmiiRx :: + (HiddenClockResetEnable dom) => + -- | Input code group + Signal dom CodeGroup -> + -- | Output tuple + ( Signal dom SgmiiStatus + , Signal dom Bool + , Signal dom Bool + , Signal dom (BitVector 8) + , Signal dom (Maybe Xmit) + , Signal dom (Maybe ConfReg) + , Signal dom (Maybe ConfReg) + , Signal dom CodeGroup + ) +sgmiiRx rxCg = + ( rxStatus + , regMaybe False rxDv + , regMaybe False rxEr + , regMaybe 0 ((fmap . fmap) fromSymbol rxDw) + , xmit + , txConfReg + , rxConfReg + , bsCg + ) + where + rxStatus = + SgmiiStatus + <$> bsStatus + <*> syncStatus + <*> (toLinkSpeed <$> regMaybe 0 rxConfReg) + <*> regMaybe Conf xmit + <*> regMaybe RudiInvalid rudi + + rxConfReg = toConfReg <$> regMaybe (RudiC 0) rudi + (xmit, txConfReg) = autoNeg syncStatus rudi + (rxDv, rxEr, rxDw, rudi) = pcsReceive cg rd dw rxEven syncStatus xmit + (cg, rd, dw, rxEven, syncStatus) = sync bsCg + (bsCg, bsStatus) = bitSlip rxCg syncStatus + +-- | Transmit side of the SGMII block, that combines all the functions that are +-- in the transmit domain +sgmiiTx :: + (HiddenClockResetEnable dom) => + -- | @TX_EN@ signal + Signal dom Bool -> + -- | @TX_ER@ signal + Signal dom Bool -> + -- | Input data word + Signal dom (BitVector 8) -> + -- | 'Xmit' signal + Signal dom (Maybe Xmit) -> + -- | Configuration register from MAC + Signal dom (Maybe ConfReg) -> + -- | Configuration register from PHY + Signal dom (Maybe ConfReg) -> + -- | Output code group + Signal dom CodeGroup +sgmiiTx txEn txEr txDw xmit txConfReg _ = + pcsTransmit txEn txEr txDw xmit txConfReg + +-- | Top-level SGMII function that takes as its second argument a function that +-- implements a clock domain crossing between the auto-negotiation and +-- transmission blocks. This block does not implement the serialization and +-- deserialization of the 10-bit code groups, but leaves this to be +-- implemented externally. These 10-bit code groups do not have to be word +-- aligned as this is implemented internally in the 'bitSlip' block. +-- +-- This function implements SGMII as described +-- [here](https://archive.org/download/sgmii/SGMII.pdf), without the optional +-- EEE (Energy-Efficient Ethernet) capability. +-- +-- This function reports its internal status by using 'SgmiiStatus', this +-- reports the synchronization status of the line in the first bit, the +-- configuration register in the following 16 bits, and the values of 'Rudi' +-- and 'Xmit' in the following 2-bit groups. +sgmii :: + forall rxDom txDom. + (KnownDomain rxDom, KnownDomain txDom) => + -- | Function used for the clock domain crossing between 'autoNeg' and + -- 'pcsTransmit', for the values of 'Xmit' and 'ConfReg' + ( Clock rxDom -> + Clock txDom -> + Signal rxDom (Maybe Xmit) -> + Signal rxDom (Maybe ConfReg) -> + Signal rxDom (Maybe ConfReg) -> + ( Signal txDom (Maybe Xmit) + , Signal txDom (Maybe ConfReg) + , Signal txDom (Maybe ConfReg) + ) + ) -> + -- | Clock of the receive domain, which is a 125 MHz clock that is derived + -- from the 625 MHz clock that is received from the PHY + Clock rxDom -> + -- | Clock of the transmit domain, which can be an internal clock of the FPGA + Clock txDom -> + -- | Reset of the receive domain + Reset rxDom -> + -- | Reset of the transmit domain + Reset txDom -> + -- | Input signal @TX_EN@, which enables data transmission when 'Xmit' from + -- 'autoNeg' is set to 'Data' + Signal txDom Bool -> + -- | Input signal @TX_ER@, which is used to propagate error values to the PHY + Signal txDom Bool -> + -- | Data octet @TXD@ to be transmitted to the PHY + Signal txDom (BitVector 8) -> + -- | Input code group from the PHY + Signal rxDom CodeGroup -> + -- | Tuple that contains the output signals from the SGMII block which are: + -- + -- - The current status of the receive block, + -- - The @RX_DV@ signal that indicates an incoming data packet, + -- - The @RX_ER@ signal that indicates a receive error, + -- - The @RXD@ signal which is the incoming data octet from the PHY, + -- - The word-aligned version of the received code group from the PHY, + -- - A 10-bit code group that can be serialized and transmitted to the PHY. + ( Signal rxDom SgmiiStatus + , Signal rxDom Bool + , Signal rxDom Bool + , Signal rxDom (BitVector 8) + , Signal rxDom CodeGroup + , Signal txDom CodeGroup + ) +sgmii rxTxCdc rxClk txClk rxRst txRst txEn txEr txDw rxCg = + (rxStatus, rxDv, rxEr, rxDw, bsCg, txCg) + where + txCg = sgmiiTx' txEn txEr txDw xmit2 txConfReg2 rxConfReg2 + where + sgmiiTx' = exposeClockResetEnable sgmiiTx txClk txRst enableGen + + (xmit2, txConfReg2, rxConfReg2) = + rxTxCdc rxClk txClk xmit1 txConfReg1 rxConfReg1 + + (rxStatus, rxDv, rxEr, rxDw, xmit1, txConfReg1, rxConfReg1, bsCg) = + sgmiiRx' rxCg + where + sgmiiRx' = exposeClockResetEnable sgmiiRx rxClk rxRst enableGen + +{-# CLASH_OPAQUE sgmii #-} + +-- | Receive side of the SGMII block, that combines all the functions that are +-- in the receive domain, rate-adapted +sgmiiRxRA :: + (HiddenClockResetEnable dom) => + -- | Input code group + Signal dom CodeGroup -> + -- | Output tuple + ( Signal dom SgmiiStatus + , Signal dom Bool + , Signal dom (Maybe (BitVector 8)) + , Signal dom (Maybe Xmit) + , Signal dom (Maybe ConfReg) + , Signal dom (Maybe ConfReg) + , Signal dom CodeGroup + ) +sgmiiRxRA rxCg = (rxStatus, rxEr, out, xmit, txConfReg, rxConfReg, bsCg) + where + out = rateAdaptRx linkSpeed $ orNothing <$> rxDv <*> rxDw + linkSpeed = toLinkSpeed <$> regMaybe 0 rxConfReg + (rxStatus, rxDv, rxEr, rxDw, xmit, txConfReg, rxConfReg, bsCg) = sgmiiRx rxCg + +-- | Transmit side of the SGMII block, that combines all the functions that are +-- in the transmit domain, rate-adapted +sgmiiTxRA :: + (HiddenClockResetEnable dom) => + -- | @TX_ER@ signal + Signal dom Bool -> + -- | Input data word + Signal dom (Maybe (BitVector 8)) -> + -- | 'Xmit' signal + Signal dom (Maybe Xmit) -> + -- | Configuration register from MAC + Signal dom (Maybe ConfReg) -> + -- | Configuration register from PHY + Signal dom (Maybe ConfReg) -> + -- | Ready signal and output code group + (Signal dom Bool, Signal dom CodeGroup) +sgmiiTxRA txEr txDw xmit txConfReg rxConfReg = (txReady, txCg) + where + linkSpeed = toLinkSpeed <$> regMaybe 0 rxConfReg + txCg = + sgmiiTx (isJust <$> out) txEr (fromMaybe 0 <$> out) xmit txConfReg rxConfReg + (txReady, out) = rateAdaptTx linkSpeed txDw + +-- | Rate-adapted version of 'sgmii' +sgmiiRA :: + forall rxDom txDom. + (KnownDomain rxDom, KnownDomain txDom) => + -- | Function used for the clock domain crossing between 'autoNeg' and + -- 'pcsTransmit', for the values of 'Xmit' and 'ConfReg' + ( Clock rxDom -> + Clock txDom -> + Signal rxDom (Maybe Xmit) -> + Signal rxDom (Maybe ConfReg) -> + Signal rxDom (Maybe ConfReg) -> + ( Signal txDom (Maybe Xmit) + , Signal txDom (Maybe ConfReg) + , Signal txDom (Maybe ConfReg) + ) + ) -> + -- | Clock of the receive domain, which is a 125 MHz clock that is derived + -- from the 625 MHz clock that is received from the PHY + Clock rxDom -> + -- | Clock of the transmit domain, which can be an internal clock of the FPGA + Clock txDom -> + -- | Reset of the receive domain + Reset rxDom -> + -- | Reset of the transmit domain + Reset txDom -> + -- | @TX_ER@ signal + Signal txDom Bool -> + -- | Data octet @TXD@ to be transmitted to the PHY + Signal txDom (Maybe (BitVector 8)) -> + -- | Input code group from the PHY + Signal rxDom CodeGroup -> + -- | Tuple that contains the output signals from the SGMII block which are: + -- + -- - The current status of the receive block, + -- - The @RX_ER@ signal that indicates a receive error, + -- - The incoming data octet from the PHY or 'Nothing' when no data word is + -- received, + -- - The word-aligned version of the received code group from the PHY, + -- - A 10-bit code group that can be serialized and transmitted to the PHY, + -- - A boolean that indicates if a new data word can be provided. + ( Signal rxDom SgmiiStatus + , Signal rxDom Bool + , Signal rxDom (Maybe (BitVector 8)) + , Signal rxDom CodeGroup + , Signal txDom CodeGroup + , Signal txDom Bool + ) +sgmiiRA rxTxCdc rxClk txClk rxRst txRst txEr txDw rxCg = + (rxStatus, rxEr, rxDw, bsCg, txCg, txReady) + where + (txReady, txCg) = sgmiiTx' txEr txDw xmit2 txConfReg2 rxConfReg2 + where + sgmiiTx' = exposeClockResetEnable sgmiiTxRA txClk txRst enableGen + + (xmit2, txConfReg2, rxConfReg2) = + rxTxCdc rxClk txClk xmit1 txConfReg1 rxConfReg1 + + (rxStatus, rxEr, rxDw, xmit1, txConfReg1, rxConfReg1, bsCg) = sgmiiRx' rxCg + where + sgmiiRx' = exposeClockResetEnable sgmiiRxRA rxClk rxRst enableGen + +{-# CLASH_OPAQUE sgmiiRA #-} diff --git a/src/Clash/Cores/Sgmii/AutoNeg.hs b/src/Clash/Cores/Sgmii/AutoNeg.hs new file mode 100644 index 0000000..ae07300 --- /dev/null +++ b/src/Clash/Cores/Sgmii/AutoNeg.hs @@ -0,0 +1,236 @@ +{-# LANGUAGE CPP #-} +{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-} + +{- | + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + Auto-negotiation process, as defined in IEEE 802.3 Figure 37-6 +-} +module Clash.Cores.Sgmii.AutoNeg + ( AutoNegState (..) + , Rudis + , Timeout + , autoNeg + , autoNegO + , autoNegT + ) +where + +import Clash.Cores.Sgmii.Common +import Clash.Prelude +import Data.Maybe (fromMaybe) +import Data.Proxy + +-- | List of values for 'Rudi' +type Rudis = Vec 3 Rudi + +-- | Type that specifies an 'Index' for the timeout of the link timer and the +-- timer used to qualify the 'Fail' status of 'Status' + +-- TODO: Replace this with @PeriodToCycles dom (Microseconds 1600)@, currently +-- this doesn't work because then I need to specify @1 <= DomainPeriod dom) +-- everywhere. +type Timeout dom = Index (DivRU (Microseconds 1600) (Max 1 (DomainPeriod dom))) + +-- | State type of 'autoNeg'. This contains all states as they are defined in +-- IEEE 802.3 Clause 37, with exception of the @AN_DISABLE_LINK_OK@ state as +-- SGMII always requires auto-negotiation to be available. +data AutoNegState dom + = AnEnable + {_rudis :: Maybe Rudis, _rxConfReg :: ConfReg, _failT :: Timeout dom} + | AnRestart + { _rudis :: Maybe Rudis + , _rxConfReg :: ConfReg + , _failT :: Timeout dom + , _linkT :: Timeout dom + } + | AbilityDetect + {_rudis :: Maybe Rudis, _rxConfReg :: ConfReg, _failT :: Timeout dom} + | AckDetect + { _rudis :: Maybe Rudis + , _rxConfReg :: ConfReg + , _failT :: Timeout dom + , _hist :: ConfReg + } + | CompleteAck + { _rudis :: Maybe Rudis + , _rxConfReg :: ConfReg + , _failT :: Timeout dom + , _linkT :: Timeout dom + } + | IdleDetect + { _rudis :: Maybe Rudis + , _rxConfReg :: ConfReg + , _failT :: Timeout dom + , _linkT :: Timeout dom + } + | LinkOk + {_rudis :: Maybe Rudis, _rxConfReg :: ConfReg, _failT :: Timeout dom} + deriving (Generic, NFDataX, Show) + +-- | Set the acknowledge bit of a 'ConfReg' to zero +noAckBit :: ConfReg -> ConfReg +noAckBit = replaceBit (14 :: Index 16) 0 + +-- | The duration of @linkT@ is 1.6 ms according to the SGMII reference, +-- which means that it has a frequency of 625 Hz. This is the same as 200000 +-- cycles of the 125 MHz clock: @1.6*10^-3 / (1 / (125*10^6))@. +-- +-- For simulation and testing, this is set to a more reasonable amount of 3 +-- to decrease the amount of test values that are needed to trigger a timeout. +timeout :: (KnownDomain dom) => Proxy dom -> Timeout dom +timeout Proxy = if clashSimulation then 3 else maxBound + +-- | Check if the the last three received values of @rxConfReg@ are the same +-- (with the exception for bit 14, the acknowledge bit, which is discarded). +-- If there has been 'Rudi' value of 'RudiI' in the same set of values, then +-- return 'False'. +abilityMatch :: Rudis -> Bool +abilityMatch rudis = all (match (head rudis)) rudis + where + match x y = + fromMaybe False (liftA2 (==) (toConfRegNoAck x) (toConfRegNoAck y)) + toConfRegNoAck = fmap noAckBit . toConfReg + +-- | Check if the last three values for 'ConfReg' are all the same, and also +-- check whether bit 14 (the acknowledge bit) has been asserted +ackMatch :: Rudis -> Bool +ackMatch rudis = + repeat (head rxConfRegs) == rxConfRegs && testBit (head rxConfRegs) 14 + where + rxConfRegs = map (fromMaybe 0 . toConfReg) rudis + +-- | Check if ability match and acknowledge match are set for the same value of +-- 'ConfReg' +consistencyMatch :: ConfReg -> Rudis -> Bool +consistencyMatch rxConfReg rudis = noAckBit rxConfReg == head rxConfRegs' + where + rxConfRegs' = map (noAckBit . fromMaybe 0 . toConfReg) rudis + +-- | Function that checks that the last three values of 'Rudi' have been 'RudiI' +idleMatch :: Rudis -> Bool +idleMatch = (==) (repeat RudiI) + +-- | State transition function for 'autoNeg' as defined in IEEE 802.3 Clause 37. +-- It takes the current 'Status' from 'Sgmii.sync' as well as the 'Rudi' +-- and 'ConfReg' signals from 'Sgmii.pcsReceive'. +autoNegT :: + forall dom. + (KnownDomain dom) => + -- | Current state + AutoNegState dom -> + -- | New input values + (Status, Maybe Rudi) -> + -- | New state + AutoNegState dom +autoNegT s (syncStatus, rudi) + | failT >= timeout (Proxy @dom) = + AnEnable (Just rudis) rxConfReg (timeout (Proxy @dom) - 1) + | rudi == Just RudiInvalid = AnEnable (Just rudis) rxConfReg failT + | otherwise = case s of + AnEnable{} -> AnRestart Nothing rxConfReg failT 0 + AnRestart{} + | linkT >= timeout (Proxy @dom) -> AbilityDetect Nothing rxConfReg failT + | otherwise -> AnRestart (Just rudis) rxConfReg failT linkT + AbilityDetect{} + | abilityMatch rudis && rxConfReg /= 0 -> + AckDetect Nothing rxConfReg failT rxConfReg + | otherwise -> AbilityDetect (Just rudis) rxConfReg failT + AckDetect{} + | ackMatch rudis && not (consistencyMatch (_rxConfReg s) rudis) -> + AnEnable Nothing rxConfReg failT + | abilityMatch rudis && rxConfReg == 0 -> + AnEnable Nothing rxConfReg failT + | ackMatch rudis && consistencyMatch (_rxConfReg s) rudis -> + CompleteAck Nothing rxConfReg failT 0 + | otherwise -> AckDetect (Just rudis) rxConfReg failT (_hist s) + CompleteAck{} + | abilityMatch rudis && rxConfReg == 0 -> + AnEnable Nothing rxConfReg failT + | linkT >= timeout (Proxy @dom) && not (abilityMatch rudis) -> + IdleDetect Nothing rxConfReg failT 0 + | linkT >= timeout (Proxy @dom) && rxConfReg /= 0 -> + IdleDetect Nothing rxConfReg failT 0 + | otherwise -> CompleteAck (Just rudis) rxConfReg failT linkT + IdleDetect{} + | abilityMatch rudis && rxConfReg == 0 -> + AnEnable Nothing rxConfReg failT + | linkT >= timeout (Proxy @dom) && idleMatch rudis -> + LinkOk Nothing rxConfReg failT + | otherwise -> IdleDetect (Just rudis) rxConfReg failT linkT + LinkOk{} + | abilityMatch rudis -> AnEnable Nothing rxConfReg failT + | otherwise -> LinkOk (Just rudis) rxConfReg failT + where + rudis = maybe rudis' (rudis' <<+) rudi + where + rudis' = fromMaybe (repeat RudiI) (_rudis s) + rxConfReg = fromMaybe (_rxConfReg s) (toConfReg =<< rudi) + failT = if syncStatus == Fail then _failT s + 1 else 0 + linkT = _linkT s + 1 + +-- | Output function for 'autoNeg' as defined in IEEE 802.3 Clause 37. Returns +-- the new value for 'Xmit' and 'ConfReg' for 'Sgmii.pcsTransmit'. +-- +-- __TODO__: The state diagram shows that in the state @ABILITY_DETECT@ the +-- acknowledge bit should be set to zero. However, if this is done, the PHY +-- does not always (~50% of the time) exit auto-negotiation mode, which means +-- that no data can be transmitted. This can be resolved by resetting the PCS +-- receive. The documentation for SGMII, specifically Table 1, shows that the +-- acknowledge bit of the configuration register is always asserted, that is +-- why here the decision has been made to remove this deassertion to zero in +-- @ABILITY_DETECT@. Now the PHY does correctly exit auto-negotiation mode, +-- and the configuration register will be transmitted correctly. However, due +-- to the lack of a description of specific changes in the documentation, it +-- is not clear whether this is indeed the correct solution, and it should be +-- investigated further. +autoNegO :: + forall dom. + (KnownDomain dom) => + -- | Current state + AutoNegState dom -> + -- | New outputs + (AutoNegState dom, Maybe Xmit, Maybe ConfReg) +autoNegO s = case s of + AnEnable{} -> (s, Just Conf, Just 0) + AnRestart{} -> (s, Nothing, Just 0) + -- According to IEEE 802.3 this should have the acknowledge bit deasserted, + -- but for SGMII the acknowledge bit is always asserted + AbilityDetect{} -> (s, Nothing, Just txConfReg) + AckDetect{} -> (s, Nothing, Just txConfReg) + CompleteAck{} -> (s, Nothing, Nothing) + IdleDetect{} -> (s, Just Idle, Nothing) + LinkOk{} -> (s, Just Data, Nothing) + where + txConfReg = 0b0100000000000001 + +-- | Function that implements the auto-negotiation block as defined in IEEE +-- 802.3 Clause 37, but modified to comply to the SGMII standard. This +-- modification is the decrease of 'Timeout' from 10 ms to 1.6 ms, and the +-- fact that SGMII always requires the acknowledge bit to be asserted. SGMII +-- also uses a different layout of the configuration register, but this does +-- not affect the state machine as the acknowledge bit is in the same +-- location. +-- +-- __N.B.__: This function does not implement the optional Next Page function. +autoNeg :: + forall dom. + (HiddenClockResetEnable dom) => + -- | Current 'Status' from 'Sgmii.sync' + Signal dom Status -> + -- | A new value of 'Rudi' from 'Sgmii.pcsReceive' + Signal dom (Maybe Rudi) -> + -- | Tuple containing the new value for 'Xmit' and a new 'ConfReg' + (Signal dom (Maybe Xmit), Signal dom (Maybe ConfReg)) +autoNeg syncStatus rudi = (xmit, txConfReg) + where + (_, xmit, txConfReg) = + mooreB + (autoNegT @dom) + (autoNegO @dom) + (AnEnable Nothing 0 0) + (syncStatus, rudi) + +{-# CLASH_OPAQUE autoNeg #-} diff --git a/src/Clash/Cores/Sgmii/BitSlip.hs b/src/Clash/Cores/Sgmii/BitSlip.hs new file mode 100644 index 0000000..1f22c24 --- /dev/null +++ b/src/Clash/Cores/Sgmii/BitSlip.hs @@ -0,0 +1,100 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE RecordWildCards #-} + +{- | + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + Bit slip function that word-aligns a stream of bits based on received + comma values +-} +module Clash.Cores.Sgmii.BitSlip + ( BitSlipState (..) + , bitSlip + , bitSlipO + , bitSlipT + ) +where + +import Clash.Cores.Sgmii.Common +import Clash.Prelude + +-- | State variable for 'bitSlip', with the two states as described in +-- 'bitSlipT'. Due to timing constraints, not all functions can be executed in +-- the same cycle, which is why intermediate values are saved in the record +-- for 'BSFail'. +data BitSlipState + = BSFail + { _rx :: (CodeGroup, CodeGroup) + , _commaLocs :: Vec 8 (Index 10) + , _hist :: Vec 10 CodeGroup + } + | BSOk {_rx :: (CodeGroup, CodeGroup), _commaLoc :: Index 10} + deriving (Generic, NFDataX, Show) + +-- | State transition function for 'bitSlip', where the initial state is the +-- training state, and after 8 consecutive commas have been detected at the +-- same index in the status register it moves into the 'BSOk' state where the +-- recovered index is used to shift the output 'BitVector' +bitSlipT :: + -- | Current state + BitSlipState -> + -- | New input values + (BitVector 10, Status) -> + -- | New state + BitSlipState +bitSlipT BSFail{..} (cg, _) + | Just i <- commaLoc, _commaLocs == repeat i = BSOk rx i + | otherwise = BSFail rx commaLocs hist + where + rx = (snd _rx, cg) + commaLocs = maybe _commaLocs (_commaLocs <<+) commaLoc + + hist = map pack b + where + b = take d10 (windows1d d10 (bitCoerce rx)) :: (Vec 10 (Vec 10 Bit)) + + commaLoc = elemIndex True $ map (`elem` commas) _hist +bitSlipT BSOk{..} (cg, syncStatus) + | syncStatus == Fail = BSFail rx (repeat _commaLoc) (repeat 0) + | otherwise = BSOk rx _commaLoc + where + rx = (snd _rx, cg) + +-- | Output function for 'bitSlip' that takes the calculated index value and +-- rotates the state vector to create the new output value, or outputs the +-- input directly when no such index value has been found yet. +bitSlipO :: + -- | Current state + BitSlipState -> + -- | New output value + (BitSlipState, BitVector 10, Status) +bitSlipO s = (s, resize (rotateR (pack (_rx s)) (10 - commaLoc)), bsStatus) + where + (commaLoc, bsStatus) = case s of + BSFail{} -> (fromEnum $ last (_commaLocs s), Fail) + BSOk{} -> (fromEnum $ _commaLoc s, Ok) + +-- | Function that takes a code word and returns the same code word, but if a +-- comma is detected the code words is shifted such that the comma is at the +-- beginning of the next code word to achieve word-alignment. +bitSlip :: + forall dom. + (HiddenClockResetEnable dom) => + -- | Input code group + Signal dom (BitVector 10) -> + -- | Current sync status from 'Sgmii.sync' + Signal dom Status -> + -- | Output code group + (Signal dom (BitVector 10), Signal dom Status) +bitSlip cg1 syncStatus = (register 0 cg2, register Fail bsStatus) + where + (_, cg2, bsStatus) = + mooreB + bitSlipT + bitSlipO + (BSFail (0, 0) (repeat 0) (repeat 0)) + (cg1, syncStatus) + +{-# CLASH_OPAQUE bitSlip #-} diff --git a/src/Clash/Cores/Sgmii/Common.hs b/src/Clash/Cores/Sgmii/Common.hs new file mode 100644 index 0000000..2d66748 --- /dev/null +++ b/src/Clash/Cores/Sgmii/Common.hs @@ -0,0 +1,147 @@ +{- | + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + Common functions, type definitions and hard-coded settings used in the + different modules that are defined for SGMII +-} +module Clash.Cores.Sgmii.Common where + +import Clash.Cores.LineCoding8b10b +import Clash.Prelude + +-- | Format of a single code group, 10-bit +type CodeGroup = BitVector 10 + +-- | Format of @rxConfReg@ and @txConfReg@, size of two data words +type ConfReg = BitVector 16 + +-- | Defines the type of the signal that indicates whether the current received +-- code group is at an even or odd index in the sequence +data Even = Even | Odd + deriving (Generic, NFDataX, Eq, Show) + +-- | Function that makes an Even RxEven Odd, and vice-versa +nextEven :: Even -> Even +nextEven Even = Odd +nextEven Odd = Even + +-- | Link speed that was communicated by the PHY +data LinkSpeed = Speed10 | Speed100 | Speed1000 + deriving (Generic, NFDataX, BitPack) + +-- | Get the current link speed from a 'ConfReg' +toLinkSpeed :: ConfReg -> LinkSpeed +toLinkSpeed confReg = + unpack $ pack (testBit confReg 11) ++# pack (testBit confReg 10) + +-- | Defines the possible different types of ordered sets that can be generated +-- by the 'Sgmii.PcsTransmit.orderedSet' process +data OrderedSet = OSetC | OSetI | OSetR | OSetS | OSetT | OSetV | OSetD + deriving (Generic, NFDataX, Eq, Show) + +-- | Defines the possible values for the RUDI output signal of the PCS Receive +-- block as defined in IEEE 802.3 Clause 36 +data Rudi = RudiC ConfReg | RudiI | RudiInvalid + deriving (Generic, NFDataX, Eq, Show) + +-- | Convert a 'Rudi' to a 'ConfReg' +toConfReg :: Rudi -> Maybe ConfReg +toConfReg (RudiC confReg) = Just confReg +toConfReg _ = Nothing + +-- | Record that holds the current status of the module, specifically the +-- 'Status' from 'Sgmii.sync', the 'ConfReg' that has been received by +-- 'Sgmii.pcsReceive', the 'Rudi' that is transmitted by 'Sgmii.pcsReceive' +-- and the 'Xmit' that is transmitted by 'Sgmii.autoNeg'. +data SgmiiStatus = SgmiiStatus + { _cBsStatus :: Status + , _cSyncStatus :: Status + , _cLinkSpeed :: LinkSpeed + , _cXmit :: Xmit + , _cRudi :: Rudi + } + +-- | Defines the type of the signal that indicates whether the transmission is +-- in sync ('Ok') or not ('Fail') +data Status = Fail | Ok + deriving (Generic, NFDataX, Eq, Show) + +-- | Signal that is received by the two PCS blocks from the auto-negotiation +-- block to indicate the current state of the auto-negotiation block +data Xmit = Conf | Data | Idle + deriving (Generic, NFDataX, BitPack, Eq, Show) + +-- | Return a 'Just' when the argument is 'True', else return a 'Nothing' +orNothing :: Bool -> a -> Maybe a +orNothing True a = Just a +orNothing False _ = Nothing + +-- | Code group that corresponds to K28.5 with negative disparity +cgK28_5N :: CodeGroup +cgK28_5N = 0b0101111100 + +-- | Code group that corresponds to K28.5 with positive disparity +cgK28_5P :: CodeGroup +cgK28_5P = 0b1010000011 + +-- | Vector containing the two alternative forms (with opposite running +-- disparity) of K28.5. This is the only relevant comma, as the other commas +-- are set as "reserved" in the list of control words. The order of the commas +-- in this is important, as the first comma returns the negative running +-- disparity when it is decoded and the second comma returns the positive +-- running disparity when it is decoded. This is used in 'Sync.LossOfSync' to +-- recover the correct running disparity from a received comma. +commas :: Vec 2 CodeGroup +commas = cgK28_5N :> cgK28_5P :> Nil + +-- | Data word corresponding to the decoded version of code group D00.0, used +-- for early-end detection +dwD00_0 :: Symbol8b10b +dwD00_0 = Dw 0b00000000 + +-- | Data word corresponding to the decoded version of code group D02.2, used +-- for alternating configuration transmission +dwD02_2 :: Symbol8b10b +dwD02_2 = Dw 0b01000010 + +-- | Data word corresponding to the decoded version of code group D05.6, used +-- for correcting idle transmission +dwD05_6 :: Symbol8b10b +dwD05_6 = Dw 0b11000101 + +-- | Data word corresponding to the decoded version of code group D16.2, used +-- for preserving idle transmission +dwD16_2 :: Symbol8b10b +dwD16_2 = Dw 0b01010000 + +-- | Data word corresponding to the decoded version of code group D21.5, used +-- for alternating configuration transmission +dwD21_5 :: Symbol8b10b +dwD21_5 = Dw 0b10110101 + +-- | Data word corresponding to the decoded version of code group K28.5, the +-- most commonly used comma value +cwK28_5 :: Symbol8b10b +cwK28_5 = Cw 0b10111100 + +-- | Data word corresponding to the decoded version of code group K23.7, used +-- for encapsulation of @Carrier_Extend@ (/R/) +cwR :: Symbol8b10b +cwR = Cw 0b11110111 + +-- | Data word corresponding to the decoded version of code group K27.7, used +-- for encapsulation of @Start_of_Packet@ (/S/) +cwS :: Symbol8b10b +cwS = Cw 0b11111011 + +-- | Data word corresponding to the decoded version of code group D29.7, used +-- for encapsulation of @End_of_Packet@ (/T/) +cwT :: Symbol8b10b +cwT = Cw 0b11111101 + +-- | Data word corresponding to the decoded version of code group K30.7, used +-- for encapsulation of @Error_Propagation@ (/V/) +cwV :: Symbol8b10b +cwV = Cw 0b11111110 diff --git a/src/Clash/Cores/Sgmii/PcsReceive.hs b/src/Clash/Cores/Sgmii/PcsReceive.hs new file mode 100644 index 0000000..8a78b83 --- /dev/null +++ b/src/Clash/Cores/Sgmii/PcsReceive.hs @@ -0,0 +1,299 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE RecordWildCards #-} + +{- | + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + PCS receive process, as defined in IEEE 802.3 Figure 36-7a and 36-7b +-} +module Clash.Cores.Sgmii.PcsReceive + ( PcsReceiveState (..) + , pcsReceive + , pcsReceiveO + , pcsReceiveT + ) +where + +import Clash.Cores.LineCoding8b10b +import Clash.Cores.Sgmii.Common +import Clash.Prelude + +-- | Defines all possible valid termination values +data CheckEnd = KDK | KDD | TRK | TRR | RRR | RRK | RRS + deriving (Eq) + +-- | State type of 'pcsReceive'. This contains all states as they are defined in +-- IEEE 802.3 Clause 36, with with exeception of the states @CARRIER_DETECT@, +-- @RECEIVE@ and @EPD2_CHECK_END@ as these do not act upon a received code +-- group. The transitions of these states are embedded in the states that +-- usually transition to either of these states. +data PcsReceiveState + = WaitForK {_rx :: Bool} + | RxK {_rx :: Bool} + | RxCB {_rx :: Bool} + | RxCC {_rx :: Bool, _hist :: Symbol8b10b} + | RxCD {_rx :: Bool, _rxConfReg :: ConfReg} + | RxInvalid {_rx :: Bool, _xmit :: Xmit} + | IdleD {_rx :: Bool} + | FalseCarrier {_rx :: Bool} + | StartOfPacket {_rx :: Bool} + | EarlyEnd {_rx :: Bool} + | TriRri {_rx :: Bool} + | TrrExtend {_rx :: Bool} + | PacketBurstRrs {_rx :: Bool} + | ExtendErr {_rx :: Bool} + | EarlyEndExt {_rx :: Bool} + | RxData {_rx :: Bool, _hist :: Symbol8b10b} + | RxDataError {_rx :: Bool, _hist :: Symbol8b10b} + | LinkFailed {_rx :: Bool, _xmit :: Xmit} + deriving (Generic, NFDataX, Show) + +-- | Calculate the number of bits that are different in two code groups. For +-- example: the code groups @0b0000@ and @0b0001@ have a difference of 1. +bitDiff :: + (KnownNat n) => + -- | First code group + BitVector n -> + -- | Second code group + BitVector n -> + -- | Bit difference + Index (n + 1) +bitDiff cg0 cg1 = foldl f 0 $ map bitCoerce $ bv2v $ xor cg0 cg1 + where + f a b = if b then a + 1 else a + +-- | Take the running disparity, the 'Even' signal and the current data word +-- and determine whether there is a 2-bit or more difference between the code +-- group and both alternative representations of the K28.5 control word or a +-- difference of between 2 and 9 bits between the code group and the expected +-- encoding of the K28.5 control word +carrierDetect :: + -- | Code group + CodeGroup -> + -- | Running disparity + Bool -> + -- | 'Even' signal + Even -> + -- | The carrier detection condition + Bool +carrierDetect cg rd rxEven + | rxEven /= Even = False + | bitDiff cgK28_5N cg >= 2 && bitDiff cgK28_5P cg >= 2 = True + | bitDiff cgK28_5 cg >= 2 && bitDiff cgK28_5 cg <= 9 = True + | otherwise = False + where + cgK28_5 = if rd then cgK28_5P else cgK28_5N + +-- | Take the running disparity, the current and next two input data words and +-- check whether they correspond to one of the specified end conditions +checkEnd :: + -- | Current and next 2 data words + Vec 3 Symbol8b10b -> + -- | End condition + Maybe CheckEnd +checkEnd dws + | dws == cwK28_5 :> dws !! (1 :: Index 3) :> cwK28_5 :> Nil = Just KDK + | dws == cwK28_5 :> dwD21_5 :> dwD00_0 :> Nil = Just KDD + | dws == cwK28_5 :> dwD02_2 :> dwD00_0 :> Nil = Just KDD + | dws == cwT :> cwR :> cwK28_5 :> Nil = Just TRK + | dws == cwT :> Nil ++ repeat cwR = Just TRR + | dws == repeat cwR = Just RRR + | dws == repeat cwR ++ cwK28_5 :> Nil = Just RRK + | dws == repeat cwR ++ cwS :> Nil = Just RRS + | otherwise = Nothing + +-- | Function that implements the transitions of the @EPD2_CHECK_END@ state +epd2CheckEnd :: Vec 3 Symbol8b10b -> Even -> Bool -> Maybe PcsReceiveState +epd2CheckEnd dws rxEven rx + | rxEnd == Just RRR = Just (TrrExtend rx) + | rxEnd == Just RRK && rxEven == Even = Just (TriRri rx) + | rxEnd == Just RRS = Just (PacketBurstRrs rx) + | otherwise = Nothing + where + rxEnd = checkEnd dws + +-- | Function that implements the transitions of the @RECEIVE@ state +receive :: Vec 3 Symbol8b10b -> Even -> Bool -> Maybe PcsReceiveState +receive dws rxEven rx + | rxEnd == Just KDK && rxEven == Even = Just (EarlyEnd rx) + | rxEnd == Just KDD && rxEven == Even = Just (EarlyEnd rx) + | rxEnd == Just TRK && rxEven == Even = Just (TriRri rx) + | rxEnd == Just TRR = Just (TrrExtend rx) + | rxEnd == Just RRR = Just (EarlyEnd rx) + | isDw (head dws) = Just (RxData rx (head dws)) + | otherwise = Nothing + where + rxEnd = checkEnd dws + +-- | State transition function for 'pcsReceive'. Takes the state as defined in +-- 'PcsReceiveState' and returns the next state as defined in Clause 36 of +-- IEEE 802.3. In contrast to the specification in Clause 36, here +-- 'Sgmii.syncT' is responsible for decoding the code groups instead of this +-- function, to not duplicate any work, but as this function does need to +-- determine the difference in bits the code group is set as an input value as +-- well. +-- +-- __N.B.__: This function does not implement the optional EEE +-- (Energy-Efficient Ethernet) capability. +pcsReceiveT :: + -- | Current state + PcsReceiveState -> + -- | Input values, where @Vec 3 CodeGroup@ contains the current and next two + -- | data words + (CodeGroup, Bool, Vec 3 Symbol8b10b, Even, Status, Xmit) -> + -- | New state + PcsReceiveState +pcsReceiveT WaitForK{..} (_, _, dws, rxEven, syncStatus, xmit) + | syncStatus == Fail = LinkFailed False xmit + | head dws == cwK28_5 && rxEven == Even = RxK False + | otherwise = WaitForK _rx +pcsReceiveT RxK{} (_, _, dws, _, syncStatus, xmit) + | syncStatus == Fail = LinkFailed False xmit + | head dws == dwD21_5 || head dws == dwD02_2 = RxCB False + | xmit == Data || isDw (head dws) = IdleD False + | otherwise = RxInvalid False xmit +pcsReceiveT RxCB{..} (_, _, dws, _, syncStatus, xmit) + | syncStatus == Fail = LinkFailed _rx xmit + | isDw (head dws) = RxCC _rx (head dws) + | otherwise = RxInvalid _rx xmit +pcsReceiveT RxCC{..} (_, _, dws, _, syncStatus, xmit) + | syncStatus == Fail = LinkFailed _rx xmit + | isDw (head dws) = RxCD _rx rxConfReg + | otherwise = RxInvalid _rx xmit + where + rxConfReg = pack $ map fromSymbol $ head dws :> _hist :> Nil +pcsReceiveT RxCD{..} (_, _, dws, rxEven, syncStatus, xmit) + | syncStatus == Fail = LinkFailed _rx xmit + | head dws == cwK28_5 && rxEven == Even = RxK _rx + | otherwise = RxInvalid _rx xmit +pcsReceiveT RxInvalid{..} (_, _, dws, rxEven, syncStatus, xmit) + | syncStatus == Fail = LinkFailed rx xmit + | rxEven == Odd = RxInvalid rx xmit + | head dws == cwK28_5 = RxK rx + | otherwise = WaitForK rx + where + rx = xmit == Data || _rx +pcsReceiveT IdleD{} (cg, rd, dws, rxEven, syncStatus, xmit) + | syncStatus == Fail = LinkFailed False xmit + | head dws /= cwK28_5 && xmit /= Data = RxInvalid False xmit + | carrierDetected && xmit == Data && head dws /= cwS = FalseCarrier False + | carrierDetected && xmit == Data && head dws == cwS = StartOfPacket False + | otherwise = RxK False + where + carrierDetected = carrierDetect cg rd rxEven +pcsReceiveT FalseCarrier{..} (_, _, dws, rxEven, syncStatus, xmit) + | syncStatus == Fail = LinkFailed True xmit + | head dws == cwK28_5 && rxEven == Even = RxK True + | otherwise = FalseCarrier _rx +pcsReceiveT EarlyEnd{..} (_, _, dws, _, syncStatus, xmit) + | syncStatus == Fail = LinkFailed _rx xmit + | head dws == dwD21_5 || head dws == dwD02_2 = RxCB False + | otherwise = IdleD _rx +pcsReceiveT TriRri{..} (_, _, dws, _, syncStatus, xmit) + | syncStatus == Fail = LinkFailed False xmit + | head dws == cwK28_5 = RxK False + | otherwise = TriRri _rx +pcsReceiveT PacketBurstRrs{..} (_, _, dws, _, syncStatus, xmit) + | syncStatus == Fail = LinkFailed _rx xmit + | head dws == cwS = StartOfPacket _rx + | otherwise = PacketBurstRrs _rx +pcsReceiveT ExtendErr{..} (_, _, dws, rxEven, syncStatus, xmit) + | syncStatus == Fail = LinkFailed _rx xmit + | head dws == cwS = StartOfPacket _rx + | head dws == cwK28_5 && rxEven == Even = RxK _rx + | Just x <- s, rxEven == Even = x + | otherwise = ExtendErr _rx + where + s = epd2CheckEnd dws rxEven _rx +pcsReceiveT LinkFailed{} (_, _, _, _, syncStatus, xmit) + | syncStatus == Fail = LinkFailed False xmit + | otherwise = WaitForK False +pcsReceiveT s (_, _, dws, rxEven, syncStatus, xmit) + | syncStatus == Fail = LinkFailed (_rx s) xmit + | Just x <- s1 = x + | otherwise = s2 + where + (s1, s2) = case s of + TrrExtend{} -> + (epd2CheckEnd dws rxEven (_rx s), ExtendErr (_rx s)) + EarlyEndExt{} -> + (epd2CheckEnd dws rxEven (_rx s), ExtendErr (_rx s)) + _ -> (receive dws rxEven (_rx s), RxDataError (_rx s) (head dws)) + +-- | Output function for 'pcsReceive', that sets the outputs as defined in IEEE +-- 802.3 Clause 36. +pcsReceiveO :: + -- | Current state + PcsReceiveState -> + -- | New output values + ( PcsReceiveState + , Maybe Bool + , Maybe Bool + , Maybe Symbol8b10b + , Maybe Rudi + ) +pcsReceiveO s = case s of + WaitForK{} -> (s, Just False, Just False, Nothing, Nothing) + RxK{} -> (s, Just False, Just False, Nothing, Nothing) + RxCB{} -> (s, Just False, Just False, Nothing, Nothing) + RxCD{} -> (s, Nothing, Nothing, Nothing, Just (RudiC (_rxConfReg s))) + RxInvalid{} -> + (s, Nothing, Nothing, Nothing, orNothing (_xmit s == Conf) RudiInvalid) + IdleD{} -> (s, Just False, Just False, Nothing, Just RudiI) + FalseCarrier{} -> (s, Nothing, Just True, Just (Cw 0b00001110), Nothing) + StartOfPacket{} -> (s, Just True, Just False, Just (Cw 0b01010101), Nothing) + EarlyEnd{} -> (s, Nothing, Just True, Nothing, Nothing) + TriRri{} -> (s, Just False, Just False, Nothing, Nothing) + TrrExtend{} -> (s, Just False, Just True, Just (Cw 0b00001111), Nothing) + PacketBurstRrs{} -> (s, Just False, Nothing, Just (Cw 0b00001111), Nothing) + ExtendErr{} -> (s, Just False, Nothing, Just (Cw 0b00011111), Nothing) + EarlyEndExt{} -> (s, Nothing, Just True, Nothing, Nothing) + RxData{} -> (s, Nothing, Just False, Just (_hist s), Nothing) + RxDataError{} -> (s, Nothing, Just True, Just (_hist s), Nothing) + LinkFailed{} -> + ( s + , orNothing (_rx s) False + , Just (_rx s) + , Nothing + , orNothing (_xmit s /= Data) RudiInvalid + ) + _ -> (s, Nothing, Nothing, Nothing, Nothing) + +-- | The 'pcsReceive' block. Takes a tuple with the new input code group, +-- running disparity and data word, 'Even', 'Status' and 'Xmit' signals +-- and runs the transition function 'pcsReceiveT'. The outputs are a set of +-- 'Maybe' values. +pcsReceive :: + (HiddenClockResetEnable dom) => + -- | Current code group from 'Sgmii.sync' + Signal dom CodeGroup -> + -- | Current running disparity from 'Sgmii.sync' + Signal dom Bool -> + -- | Input 'Symbol8b10b' from 'Sgmii.sync' + Signal dom (Vec 3 Symbol8b10b) -> + -- | The 'Even' value from 'Sgmii.sync' + Signal dom Even -> + -- | The current 'Status' from 'Sgmii.sync' + Signal dom Status -> + -- | The 'Xmit' signal from 'Sgmii.autoNeg' + Signal dom (Maybe Xmit) -> + -- | Tuple containing the output values + ( Signal dom (Maybe Bool) + , Signal dom (Maybe Bool) + , Signal dom (Maybe Symbol8b10b) + , Signal dom (Maybe Rudi) + ) +pcsReceive cg rd dw1 rxEven syncStatus xmit = (rxDv, rxEr, dw2, rudi) + where + (_, rxDv, rxEr, dw2, rudi) = + mooreB + pcsReceiveT + pcsReceiveO + (WaitForK False) + (cg, rd, dw1, rxEven, syncStatus, xmit') + + xmit' = regMaybe Idle xmit + +{-# CLASH_OPAQUE pcsReceive #-} diff --git a/src/Clash/Cores/Sgmii/PcsTransmit.hs b/src/Clash/Cores/Sgmii/PcsTransmit.hs new file mode 100644 index 0000000..2573075 --- /dev/null +++ b/src/Clash/Cores/Sgmii/PcsTransmit.hs @@ -0,0 +1,50 @@ +{-# LANGUAGE CPP #-} + +{- | + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + Top level module for the PCS transmit block, that combines the processes + that are defined in the two submodules @CodeGroup@ and @OrderedSet@. +-} +module Clash.Cores.Sgmii.PcsTransmit (pcsTransmit) where + +import Clash.Cores.Sgmii.Common +import Clash.Cores.Sgmii.PcsTransmit.CodeGroup +import Clash.Cores.Sgmii.PcsTransmit.OrderedSet +import Clash.Prelude + +-- | Takes the signals that are defined in IEEE 802.3 Clause 36 and runs them +-- through the state machines as defined for the PCS transmit block. These are +-- implemented in 'codeGroupT', 'codeGroupO' and 'orderedSetT'. +pcsTransmit :: + (HiddenClockResetEnable dom) => + -- | The @TX_EN@ signal + Signal dom Bool -> + -- | The @TX_ER@ signal + Signal dom Bool -> + -- | The new data word that needs to be transmitted + Signal dom (BitVector 8) -> + -- | The 'Xmit' signal from 'Sgmii.autoNeg' + Signal dom (Maybe Xmit) -> + -- | The 'ConfReg' from 'Sgmii.autoNeg' + Signal dom (Maybe ConfReg) -> + -- | The 8b/10b encoded output value + Signal dom CodeGroup +pcsTransmit txEn txEr dw xmit txConfReg = cg + where + (_, cg, txEven, cgSent) = + mooreB + codeGroupT + codeGroupO + (IdleDisparityOk False 0 0) + (txOSet, dw, txConfReg) + + (_, txOSet) = + mealyB + orderedSetT + (IdleS Idle False) + (txEn, txEr, dw, xmit, txEven, cgSent) + +{-# CLASH_OPAQUE pcsTransmit #-} diff --git a/src/Clash/Cores/Sgmii/PcsTransmit/CodeGroup.hs b/src/Clash/Cores/Sgmii/PcsTransmit/CodeGroup.hs new file mode 100644 index 0000000..b9327de --- /dev/null +++ b/src/Clash/Cores/Sgmii/PcsTransmit/CodeGroup.hs @@ -0,0 +1,117 @@ +{-# LANGUAGE CPP #-} + +{- | + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + Code group process of the PCS transmit block, as defined in IEEE 802.3 + Figure 36-6 +-} +module Clash.Cores.Sgmii.PcsTransmit.CodeGroup + ( CodeGroupState (..) + , codeGroupO + , codeGroupT + ) +where + +import Clash.Cores.LineCoding8b10b +import Clash.Cores.Sgmii.Common +import Clash.Prelude +import Data.Maybe (fromMaybe) + +-- | State type of the code group process as defined in IEEE 802.3 Clause 36, +-- with the exception of @GENERATE_CODE_GROUPS@ and @IDLE_DISPARITY_TEST@ as +-- these states does not act upon the 125 MHz @cg_timer@ timer +data CodeGroupState + = SpecialGo + { _rd :: Bool + , _cg :: CodeGroup + , _txConfReg :: ConfReg + , _txEven :: Even + , _txOSet :: OrderedSet + } + | DataGo + {_rd :: Bool, _cg :: CodeGroup, _txConfReg :: ConfReg, _txEven :: Even} + | IdleDisparityWrong {_rd :: Bool, _cg :: CodeGroup, _txConfReg :: ConfReg} + | IdleDisparityOk {_rd :: Bool, _cg :: CodeGroup, _txConfReg :: ConfReg} + | IdleIB {_rd :: Bool, _cg :: CodeGroup, _txConfReg :: ConfReg, _i :: Index 2} + | ConfCA {_rd :: Bool, _cg :: CodeGroup, _txConfReg :: ConfReg, _i :: Index 2} + | ConfCB {_rd :: Bool, _cg :: CodeGroup, _txConfReg :: ConfReg, _i :: Index 2} + | ConfCC {_rd :: Bool, _cg :: CodeGroup, _txConfReg :: ConfReg, _i :: Index 2} + | ConfCD {_rd :: Bool, _cg :: CodeGroup, _txConfReg :: ConfReg, _i :: Index 2} + deriving (Generic, NFDataX, Show) + +-- | State transitions from @GENERATE_CODE_GROUP@ from Figure 36-6, which need +-- to be set in all parent states of @GENERATE_CODE_GROUP@ as this state +-- itself is not implemented as it does not transmit a code group +generateCg :: + OrderedSet -> Bool -> CodeGroup -> ConfReg -> Even -> CodeGroupState +generateCg txOSet rd cg txConfReg txEven + | txOSet == OSetD = DataGo rd cg txConfReg txEven + | txOSet == OSetI && rd = IdleDisparityWrong rd cg txConfReg + | txOSet == OSetI && not rd = IdleDisparityOk rd cg txConfReg + | txOSet == OSetC = ConfCA rd cg txConfReg 0 + | otherwise = SpecialGo rd cg txConfReg txEven txOSet + +-- | State transition function for the states as defined in IEEE 802.3 Clause +-- 36, specifically Figure 36-6. This function receives an ordered set from +-- the ordered set state machine, a @TXD@ value from the outside world and +-- then sends out the correct code group based on the given ordered set. +codeGroupT :: + -- | State variable + CodeGroupState -> + -- | Input data word from the ordered set, new input value and the config + -- register + (OrderedSet, BitVector 8, Maybe ConfReg) -> + -- | The new state + CodeGroupState +codeGroupT s (txOSet, dw, txConfReg) = nextState + where + (dw', nextState) = case s of + SpecialGo{} -> + ( case _txOSet s of + OSetS -> cwS + OSetT -> cwT + OSetR -> cwR + _ -> cwV + , generateCg' (nextEven (_txEven s)) + ) + DataGo{} -> (Dw dw, generateCg' (nextEven (_txEven s))) + IdleDisparityWrong{} -> (cwK28_5, IdleIB rd cg txConfReg' 0) + IdleDisparityOk{} -> (cwK28_5, IdleIB rd cg txConfReg' 1) + IdleIB{} -> (if _i s == 0 then dwD05_6 else dwD16_2, generateCg' Odd) + ConfCA{} -> (cwK28_5, ConfCB rd cg txConfReg' (_i s)) + ConfCB{} -> + (if _i s == 0 then dwD21_5 else dwD02_2, ConfCC rd cg txConfReg' (_i s)) + ConfCC{} -> (Dw (resize txConfReg'), ConfCD rd cg txConfReg' (_i s)) + ConfCD{} -> + ( Dw (resize $ rotateR (_txConfReg s) 8) + , if _i s == 0 && txOSet == OSetC + then ConfCA rd cg txConfReg' 1 + else generateCg' Odd + ) + + generateCg' = generateCg txOSet rd cg txConfReg' + txConfReg' = fromMaybe (_txConfReg s) txConfReg + (rd, cg) = encode8b10b (_rd s) dw' + +{-# CLASH_OPAQUE codeGroupT #-} + +-- | Output transition function for the states as defined in IEEE 802.3 Clause +-- 36, specifically Figure 36-6. This function takes the state values that +-- have been determined in 'codeGroupT' and sets the correct outputs. +codeGroupO :: + -- | Current state + CodeGroupState -> + -- | New output values + (CodeGroupState, CodeGroup, Even, Bool) +codeGroupO s = case s of + SpecialGo{} -> (s, _cg s, nextEven (_txEven s), True) + DataGo{} -> (s, _cg s, nextEven (_txEven s), True) + IdleIB{} -> (s, _cg s, Odd, True) + ConfCB{} -> (s, _cg s, Odd, False) + ConfCD{} -> (s, _cg s, Odd, True) + _ -> (s, _cg s, Even, False) + +{-# CLASH_OPAQUE codeGroupO #-} diff --git a/src/Clash/Cores/Sgmii/PcsTransmit/OrderedSet.hs b/src/Clash/Cores/Sgmii/PcsTransmit/OrderedSet.hs new file mode 100644 index 0000000..0b5769f --- /dev/null +++ b/src/Clash/Cores/Sgmii/PcsTransmit/OrderedSet.hs @@ -0,0 +1,229 @@ +{-# LANGUAGE CPP #-} + +{- | + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + Ordered set process of the PCS transmit block, as defined in IEEE 802.3 + Figure 36-5 +-} +module Clash.Cores.Sgmii.PcsTransmit.OrderedSet + ( OrderedSetState (..) + , orderedSetT + ) +where + +import Clash.Cores.Sgmii.Common +import Clash.Prelude +import Data.Maybe (fromMaybe) + +-- | State type of 'orderedSetT' as defined in IEEE 802.3 Clause 36, with the +-- exeception of @TX_TEST_XMIT@, @TX_PACKET@ and @ALIGN_ERR_START@ as these +-- states do not transmit an ordered set to the code group process +data OrderedSetState + = Configuration {_xmit :: Xmit, _xmitChange :: Bool} + | IdleS {_xmit :: Xmit, _xmitChange :: Bool} + | XmitData {_xmit :: Xmit, _xmitChange :: Bool} + | StartOfPacket {_xmit :: Xmit, _xmitChange :: Bool} + | TxData {_xmit :: Xmit, _xmitChange :: Bool} + | EndOfPacketNoExt {_xmit :: Xmit, _xmitChange :: Bool} + | Epd2NoExt {_xmit :: Xmit, _xmitChange :: Bool} + | Epd3 {_xmit :: Xmit, _xmitChange :: Bool} + | EndOfPacketExt {_xmit :: Xmit, _xmitChange :: Bool} + | ExtendBy1 {_xmit :: Xmit, _xmitChange :: Bool} + | CarrierExtend {_xmit :: Xmit, _xmitChange :: Bool} + | StartError {_xmit :: Xmit, _xmitChange :: Bool} + | TxDataError {_xmit :: Xmit, _xmitChange :: Bool} + deriving (Generic, NFDataX, Show) + +-- | State transitions from @TX_TEST_XMIT@ from Figure 36-5, which need to be +-- set in all parent states of @TX_TEST_XMIT@ as this state itself is not +-- implemented as it does not transmit a code group +txTestXmit :: + Bool -> Bool -> Xmit -> Even -> Bool -> Bool -> Maybe OrderedSetState +txTestXmit txEn txEr xmit txEven tx xmitChange + | not (xmitChange && tx && txEven == Odd) = Nothing + | xmit == Conf = Just (Configuration xmit False) + | xmit == Idle = Just (IdleS xmit False) + | xmit == Data && txEn = Just (IdleS xmit False) + | xmit == Data && txEr = Just (IdleS xmit False) + | otherwise = Just (XmitData xmit False) + +-- | Void function that is used to check whether @/V/@ needs to be propagated +-- based on the values of the input pins +void :: OrderedSet -> Bool -> Bool -> BitVector 8 -> OrderedSet +void txOSet txEn txEr dw + | not txEn && txEr && dw /= 0b00001111 = OSetV + | txEn && txEr = OSetV + | otherwise = txOSet + +-- | Function to update the current values for 'Xmit' and @xmitChange@ +xmitUpdate :: OrderedSetState -> Maybe Xmit -> (Xmit, Bool) +xmitUpdate s xmit = (xmit', xmitChange) + where + xmit' = fromMaybe (_xmit s) xmit + xmitChange = (xmit' /= _xmit s) || _xmitChange s + +-- | State transition function for the states as defined in IEEE 802.3 Clause +-- 36, specifically Figure 36-5. This function receives the input values and +-- generates an ordered set to be transmitted by the code group process. +-- +-- __N.B.__: This function does not implement the optional EEE +-- (Energy-Efficient Ethernet) capability. +orderedSetT :: + -- | State variable + OrderedSetState -> + -- | The new input values, partly from the outside world, and partly from + -- 'Sgmii.autoNeg' and 'PcsTransmit.codeGroupT' + (Bool, Bool, BitVector 8, Maybe Xmit, Even, Bool) -> + -- | The new state and the new output values + (OrderedSetState, (OrderedSetState, OrderedSet)) +orderedSetT s@Configuration{} (txEn, txEr, _, xmit, txEven, tx) = + (nextState, out) + where + nextState = fromMaybe (Configuration xmit' xmitChange) s' + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + out = (s, OSetC) +orderedSetT s@IdleS{} (txEn, txEr, _, xmit, txEven, tx) = (nextState, out) + where + nextState + | Just x <- s' = x + | xmit' == Data && not txEn && not txEr && tx = XmitData xmit' xmitChange + | otherwise = IdleS xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + out = (s, OSetI) +orderedSetT s@XmitData{} (txEn, txEr, _, xmit, txEven, tx) = (nextState, out) + where + nextState + | Just x <- s' = x + | txEn && not txEr && tx = StartOfPacket xmit' xmitChange + | txEn && txEr && tx = StartError xmit' xmitChange + | otherwise = XmitData xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + out = (s, OSetI) +orderedSetT s@StartOfPacket{} (txEn, txEr, _, xmit, txEven, tx) = + (nextState, out) + where + nextState + | Just x <- s' = x + | txEn && tx = TxData xmit' xmitChange + | not txEn && not txEr && tx = EndOfPacketNoExt xmit' xmitChange + | not txEn && txEr && tx = EndOfPacketExt xmit' xmitChange + | otherwise = StartOfPacket xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + out = (s, OSetS) +orderedSetT s@TxData{} (txEn, txEr, dw, xmit, txEven, tx) = (nextState, out) + where + nextState + | Just x <- s' = x + | txEn && tx = TxData xmit' xmitChange + | not txEn && not txEr && tx = EndOfPacketNoExt xmit' xmitChange + | not txEn && txEr && tx = EndOfPacketExt xmit' xmitChange + | otherwise = TxData xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + txOSet = void OSetD txEn txEr dw + out = (s, txOSet) +orderedSetT s@EndOfPacketNoExt{} (txEn, txEr, _, xmit, txEven, tx) = + (nextState, out) + where + nextState + | Just x <- s' = x + | tx = Epd2NoExt xmit' xmitChange + | otherwise = EndOfPacketNoExt xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + out = (s, OSetT) +orderedSetT s@Epd2NoExt{} (txEn, txEr, _, xmit, txEven, tx) = (nextState, out) + where + nextState + | Just x <- s' = x + | txEven == Odd && tx = XmitData xmit' xmitChange + | txEven == Even && tx = Epd3 xmit' xmitChange + | otherwise = Epd2NoExt xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + out = (s, OSetR) +orderedSetT s@Epd3{} (txEn, txEr, _, xmit, txEven, tx) = (nextState, out) + where + nextState + | Just x <- s' = x + | tx = XmitData xmit' xmitChange + | otherwise = Epd3 xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + out = (s, OSetR) +orderedSetT s@EndOfPacketExt{} (txEn, txEr, dw, xmit, txEven, tx) = + (nextState, out) + where + nextState + | Just x <- s' = x + | not txEr && tx = ExtendBy1 xmit' xmitChange + | txEr && tx = CarrierExtend xmit' xmitChange + | otherwise = EndOfPacketExt xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + txOSet = void OSetT txEn txEr dw + out = (s, txOSet) +orderedSetT s@ExtendBy1{} (txEn, txEr, _, xmit, txEven, tx) = (nextState, out) + where + nextState + | Just x <- s' = x + | tx = Epd2NoExt xmit' xmitChange + | otherwise = ExtendBy1 xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + out = (s, OSetR) +orderedSetT s@CarrierExtend{} (txEn, txEr, dw, xmit, txEven, tx) = + (nextState, out) + where + nextState + | Just x <- s' = x + | not txEn && not txEr && tx = ExtendBy1 xmit' xmitChange + | txEn && txEr && tx = StartError xmit' xmitChange + | txEn && not txEr && tx = StartOfPacket xmit' xmitChange + | otherwise = CarrierExtend xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + txOSet = void OSetR txEn txEr dw + out = (s, txOSet) +orderedSetT s@StartError{} (txEn, txEr, _, xmit, txEven, tx) = (nextState, out) + where + nextState + | Just x <- s' = x + | tx = TxDataError xmit' xmitChange + | otherwise = StartError xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + out = (s, OSetS) +orderedSetT s@TxDataError{} (txEn, txEr, _, xmit, txEven, tx) = (nextState, out) + where + nextState + | Just x <- s' = x + | txEn && tx = TxData xmit' xmitChange + | not txEn && not txEr && tx = EndOfPacketNoExt xmit' xmitChange + | not txEn && txEr && tx = EndOfPacketExt xmit' xmitChange + | otherwise = TxDataError xmit' xmitChange + + (xmit', xmitChange) = xmitUpdate s xmit + s' = txTestXmit txEn txEr xmit' txEven tx xmitChange + out = (s, OSetV) + +{-# CLASH_OPAQUE orderedSetT #-} diff --git a/src/Clash/Cores/Sgmii/RateAdapt.hs b/src/Clash/Cores/Sgmii/RateAdapt.hs new file mode 100644 index 0000000..a7e2029 --- /dev/null +++ b/src/Clash/Cores/Sgmii/RateAdapt.hs @@ -0,0 +1,87 @@ +{-# LANGUAGE CPP #-} + +{- | + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + Functions for the rate adaptation blocks that are required for lower bit + rates than 1000 Mbps +-} +module Clash.Cores.Sgmii.RateAdapt + ( rateAdaptRx + , rateAdaptTx + ) +where + +import Clash.Cores.Sgmii.Common +import Clash.Prelude + +-- | State transition function for the receive rate adaption function +rateAdaptRxT :: + -- | Current state + Index 100 -> + -- | Input value + (LinkSpeed, Maybe a) -> + -- | New state and output value + (Index 100, Maybe a) +rateAdaptRxT n (_, Nothing) = (n, Nothing) +rateAdaptRxT n (linkSpeed, Just a) + | n == 0 = (n', Just a) + | otherwise = (n', Nothing) + where + n' = if n == repeatN then 0 else n + 1 + repeatN = case linkSpeed of + Speed1000 -> 0 + Speed100 -> 9 + Speed10 -> 99 + +-- | Rate adaption function that takes an input and only outputs this input once +-- per N cycles based on the current link speed +rateAdaptRx :: + (HiddenClockResetEnable dom) => + -- | Link speed reported by the PHY + Signal dom LinkSpeed -> + -- | Input value + Signal dom (Maybe a) -> + -- | Output value + Signal dom (Maybe a) +rateAdaptRx linkSpeed a = mealyB rateAdaptRxT 0 (linkSpeed, a) + +{-# CLASH_OPAQUE rateAdaptRx #-} + +-- | State transition function for the transmit rate adaption function +rateAdaptTxT :: + -- | Current state + Index 100 -> + -- | Input value + (LinkSpeed, Maybe a) -> + -- | New state and output value + (Index 100, (Bool, Maybe a)) +rateAdaptTxT n (linkSpeed, a) = (n', (ready, a)) + where + n' = if ready then 0 else n + 1 + ready = n == repeatN + repeatN = case linkSpeed of + Speed1000 -> 0 + Speed100 -> 9 + Speed10 -> 99 + +-- | Rate adaption function that passes an input to the output, and accepts new +-- inputs based on the link speed reported by the PHY. + +-- Remarks: +-- - The input is __only__ allowed to be changed the sample after the first +-- element in the output tuple, @READY@, has been asserted. +rateAdaptTx :: + (HiddenClockResetEnable dom) => + -- | Link speed reported by the PHY + Signal dom LinkSpeed -> + -- | Input value from the outside world + Signal dom (Maybe a) -> + -- | Output tuple containing the request for a new value ('Bool') and the + -- (possibly replicated) output value + (Signal dom Bool, Signal dom (Maybe a)) +rateAdaptTx linkSpeed bv = mealyB rateAdaptTxT 0 (linkSpeed, bv) + +{-# CLASH_OPAQUE rateAdaptTx #-} diff --git a/src/Clash/Cores/Sgmii/Sync.hs b/src/Clash/Cores/Sgmii/Sync.hs new file mode 100644 index 0000000..a1aad9d --- /dev/null +++ b/src/Clash/Cores/Sgmii/Sync.hs @@ -0,0 +1,191 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE ViewPatterns #-} + +{- | + Copyright : (C) 2024, QBayLogic B.V. + License : BSD2 (see the file LICENSE) + Maintainer : QBayLogic B.V. + + Synchronization process, as defined in IEEE 802.3 Figure 36-9 +-} +module Clash.Cores.Sgmii.Sync + ( OutputQueue + , SyncState (..) + , outputQueueO + , outputQueueT + , sync + , syncO + , syncT + ) +where + +import Clash.Cores.LineCoding8b10b +import Clash.Cores.Sgmii.Common +import Clash.Prelude +import Data.Maybe (isNothing) + +-- | State type of the output queue for 'sync' +type OutputQueue = Vec 3 (CodeGroup, Bool, Symbol8b10b, Even, Status) + +-- | State type of 'sync'. This contains all states as they are defined in IEEE +-- 802.3 Clause 36. +data SyncState + = LossOfSync + {_cg :: CodeGroup, _rd :: Bool, _dw :: Symbol8b10b, _rxEven :: Even} + | CommaDetect + {_cg :: CodeGroup, _rd :: Bool, _dw :: Symbol8b10b, _i :: Index 3} + | AcquireSync + { _cg :: CodeGroup + , _rd :: Bool + , _dw :: Symbol8b10b + , _rxEven :: Even + , _i :: Index 3 + } + | SyncAcquired + { _cg :: CodeGroup + , _rd :: Bool + , _dw :: Symbol8b10b + , _rxEven :: Even + , _i :: Index 3 + } + | SyncAcquiredA + { _cg :: CodeGroup + , _rd :: Bool + , _dw :: Symbol8b10b + , _rxEven :: Even + , _goodCgs :: Index 4 + , _i :: Index 3 + } + deriving (Generic, NFDataX, Show) + +-- | State transition function for 'sync'. Takes the state as defined in +-- 'SyncState', a the new incoming code group from the deserialization block +-- and returns the next state as defined in Clause 36 of IEEE 802.3. As is +-- described in the documentation for 'Sgmii.pcsReceive', this function also +-- does the decoding of 10-bit code groups (which is usually done by +-- 'Sgmii.pcsReceive') as it needs the information provided by the decode +-- function to determine whether a code group corresponds to a valid data +-- word. +syncT :: + -- | Current state + SyncState -> + -- | New input codegroup + CodeGroup -> + -- | New state and output tuple + SyncState +syncT s cg = case s of + LossOfSync{} + | isNothing comma -> LossOfSync cg rd dw rxEven + | otherwise -> CommaDetect cg rd dw 0 + CommaDetect{} + | not (isDw dw) -> LossOfSync cg rd dw Even + | _i s == 0 -> AcquireSync cg rd dw Even (_i s) + | otherwise -> SyncAcquired cg rd dw Even 0 + AcquireSync{} + | not (isValidSymbol dw) -> LossOfSync cg rd dw rxEven + | cg `elem` commas && rxEven == Even -> LossOfSync cg rd dw rxEven + | cg `elem` commas && rxEven == Odd -> CommaDetect cg rd dw 1 + | otherwise -> AcquireSync cg rd dw rxEven 0 + SyncAcquired{} + | _i s == maxBound && not (isValidSymbol dw) -> LossOfSync cg rd dw rxEven + | _i s == maxBound && cg `elem` commas && rxEven == Even -> + LossOfSync cg rd dw rxEven + | not (isValidSymbol dw) -> SyncAcquired cg rd dw rxEven (_i s + 1) + | cg `elem` commas && rxEven == Even -> + SyncAcquired cg rd dw rxEven (_i s + 1) + | _i s == 0 -> SyncAcquired cg rd dw rxEven 0 + | otherwise -> SyncAcquiredA cg rd dw rxEven goodCgs (_i s) + SyncAcquiredA{} + | _i s == maxBound && not (isValidSymbol dw) -> LossOfSync cg rd dw rxEven + | _i s == maxBound && cg `elem` commas && rxEven == Even -> + LossOfSync cg rd dw rxEven + | not (isValidSymbol dw) -> SyncAcquired cg rd dw rxEven (_i s + 1) + | cg `elem` commas && rxEven == Even -> + SyncAcquired cg rd dw rxEven (_i s + 1) + | _i s == 0 && goodCgs == maxBound -> SyncAcquired cg rd dw rxEven 0 + | goodCgs == maxBound -> SyncAcquired cg rd dw rxEven (_i s - 1) + | otherwise -> SyncAcquiredA cg rd dw rxEven goodCgs (_i s) + where + comma = elemIndex cg commas + rdNew = case s of + LossOfSync{} -> maybe (_rd s) bitCoerce comma + _ -> _rd s + (rd, dw) = decode8b10b rdNew cg + rxEven = nextEven (_rxEven s) + goodCgs = case s of + SyncAcquiredA{} -> _goodCgs s + 1 + _ -> 0 + +-- | Output function for 'sync'. Takes the state as defined in 'SyncState' and +-- returns a tuple containing the outputs as defined in Clause 36 of IEEE +-- 802.3 +syncO :: + -- | Current state + SyncState -> + -- | New state and output tuple + (SyncState, CodeGroup, Bool, Symbol8b10b, Even, Status) +syncO s = case s of + LossOfSync{} -> (s, _cg s, _rd s, _dw s, rxEven, Fail) + CommaDetect{} -> (s, _cg s, _rd s, _dw s, Even, Fail) + AcquireSync{} -> (s, _cg s, _rd s, _dw s, rxEven, Fail) + _ -> (s, _cg s, _rd s, _dw s, rxEven, Ok) + where + rxEven = nextEven (_rxEven s) + +-- | Transition function for the inputs of 'Sgmii.pcsReceive'. This is used to +-- keep a small list of "future" values for 'Symbol8b10b', such that these can +-- be used in 'Sgmii.checkEnd'. +outputQueueT :: + -- | Current state with three values for all inputs + OutputQueue -> + -- | New input values for the code group, running disparity, data word, 'Even' + -- signal and 'Status; + (CodeGroup, Bool, Symbol8b10b, Even, Status) -> + -- | New state + OutputQueue +outputQueueT s i = s <<+ i + +-- | Output function for the output queue, where the values are taken from the +-- current state +outputQueueO :: + -- Current state with three values for all inputs + OutputQueue -> + -- | New output with one value for everything except 'Symbol8b10b' for the + -- prescient 'Sgmii.checkEnd' function. + (CodeGroup, Bool, Vec 3 Symbol8b10b, Even, Status) +outputQueueO s = (cg, rd, dw, rxEven, syncStatus) + where + (head -> cg, head -> rd, dw, head -> rxEven, head -> syncStatus) = unzip5 s + +-- | Takes a code group and runs it through the state machine as defined in +-- IEEE 802.3 Clause 36 to check whether the signal is synchronized. If it is +-- not, output 'Status' @Fail@ and try to re-aquire synchronization, else +-- simply pass through the new running disparity and 'Symbol8b10b' from the +-- decoded code group as well as the 'Even' signal. The current code word is +-- also propagated as it is required by 'Sgmii.pcsReceive'. This function +-- contains a list of data words as these need to be used by the prescient +-- 'Sgmii.checkEnd' function. +sync :: + (HiddenClockResetEnable dom) => + -- | New code group from the PHY + Signal dom CodeGroup -> + -- | A tuple containing the input code group, running disparity, a new + -- 'Symbol8b10b', the new value for 'Even' and the current synchronization + -- status + ( Signal dom CodeGroup + , Signal dom Bool + , Signal dom (Vec 3 Symbol8b10b) + , Signal dom Even + , Signal dom Status + ) +sync rxCg = + mooreB + outputQueueT + outputQueueO + (repeat (0, False, Dw 0, Odd, Fail)) + (cg, rd, dw, rxEven, syncStatus) + where + (_, cg, rd, dw, rxEven, syncStatus) = + mooreB syncT syncO (LossOfSync 0 False (Dw 0) Even) rxCg + +{-# CLASH_OPAQUE sync #-} diff --git a/test/Test/Cores/Sgmii/AutoNeg.hs b/test/Test/Cores/Sgmii/AutoNeg.hs new file mode 100644 index 0000000..712ec4f --- /dev/null +++ b/test/Test/Cores/Sgmii/AutoNeg.hs @@ -0,0 +1,161 @@ +{-# LANGUAGE ViewPatterns #-} +{-# OPTIONS_GHC -Wno-orphans #-} +{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-} + +module Test.Cores.Sgmii.AutoNeg where + +import Clash.Cores.Sgmii.AutoNeg +import Clash.Cores.Sgmii.Common +import Clash.Hedgehog.Sized.BitVector +import qualified Clash.Prelude as C +import Data.List (find) +import Data.Maybe (isJust, isNothing) +import qualified Hedgehog as H +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range +import Test.Tasty +import Test.Tasty.Hedgehog +import Test.Tasty.TH +import Prelude + +-- | Generate a BitVector with its two most significant bits (15 and 14) set to +-- zero. Future improvement: also make bit 15 a random value. +genConfRegNoAck :: H.Gen ConfReg +genConfRegNoAck = (C.++#) (0b00 :: C.BitVector 2) <$> genDefinedBitVector + +-- | Generate a BitVector with its most significant bit (15) set to zero and the +-- acknowledge bit set to one. Future improvement: also make bit 15 a random +-- value. +genConfRegAck :: H.Gen ConfReg +genConfRegAck = (C.++#) (0b01 :: C.BitVector 2) <$> genDefinedBitVector + +-- | Generate a list of 'ConfReg's without bit 14 asserted +genConfRegsNoAck :: H.Range Int -> H.Gen [ConfReg] +genConfRegsNoAck range = do + confReg <- Gen.filter (/= 0) genConfRegNoAck + n <- Gen.int range + pure $ take n $ concat $ replicate n (replicate 3 confReg) + +-- | Generate a list of 'ConfReg's with bit 14 asserted where every value is +-- repeated 3 times in a row +genConfRegsAck :: H.Range Int -> H.Gen [ConfReg] +genConfRegsAck range = do + confReg <- Gen.filter (/= 0) genConfRegAck + n <- Gen.int range + pure $ take n $ concat $ replicate n (replicate 3 confReg) + +-- | Version of 'autoNeg' that does not return any actual values, but only the +-- entered state for debugging purposes. +autoNegSim :: + (C.HiddenClockResetEnable dom) => + C.Signal dom (Status, Maybe Rudi) -> + C.Signal dom (AutoNegState dom) +autoNegSim (C.unbundle -> i) = s + where + (s, _, _) = C.mooreB autoNegT autoNegO (AnEnable Nothing 0 0) i + +-- | Generate a list of values that do not contain the acknowledge bit, and +-- assert that the @ACKNOWLEDGE_DETECT@ state is entered but not the +-- @COMPLETE_ACKNOWLEDGE@ state +prop_autoNegNoAckComplete :: H.Property +prop_autoNegNoAckComplete = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 9 100)) + + inp <- H.forAll (genConfRegsNoAck (Range.singleton simDuration)) + let simOut = + C.sampleN simDuration (autoNegSim @C.System (C.fromList (map f inp))) + where + f a = (Ok, Just (RudiC a)) + + H.assert $ isNothing (find g simOut) + H.assert $ isJust (find h simOut) + where + g (CompleteAck{}) = True + g _ = False + + h (AckDetect{}) = True + h _ = False + +-- | Generate a list of values that do contain the acknowledge bit, and assert +-- that the @COMPLETE_ACKNOWLEDGE@ state is entered +prop_autoNegAckComplete :: H.Property +prop_autoNegAckComplete = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 12 100)) + + inp <- H.forAll (genConfRegsAck (Range.singleton simDuration)) + let simOut = + C.sampleN simDuration (autoNegSim @C.System (C.fromList (map f inp))) + where + f a = (Ok, Just (RudiC a)) + + H.assert $ isJust (find g simOut) + where + g (CompleteAck{}) = True + g _ = False + +-- | Assert that in a simulation, the number of times a given state that uses +-- the link timer as a transition predicate is entered is exactly equal to 3 +prop_autoNegLinkTimer :: H.Property +prop_autoNegLinkTimer = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 10 100)) + + inp <- H.forAll (genConfRegsNoAck (Range.singleton simDuration)) + let simOut = + C.sampleN simDuration (autoNegSim @C.System (C.fromList (map f inp))) + where + f a = (Ok, Just (RudiC a)) + + (length . filter g) simOut H.=== 3 + where + g (AnRestart{}) = True + g _ = False + +-- | Assert that if 'Status' is set to 'Fail', 'autoNeg' will never leave the +-- 'AnEnable' state (except at initialization, hence the first 10 outputs are +-- dropped from the comparision) +prop_autoNegFail :: H.Property +prop_autoNegFail = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 10 100)) + + inp <- H.forAll (genConfRegsAck (Range.singleton simDuration)) + let simOut = + C.sampleN simDuration (autoNegSim @C.System (C.fromList (map f inp))) + where + f a = (Fail, Just (RudiC a)) + + (length . filter g) (drop 10 simOut) H.=== simDuration - 10 + where + g (AnEnable{}) = True + g _ = False + +-- | Assert that if values with ack set and ack not set are inputted +-- interchangeably the system will never trigger 'acknowledgeMatch' and thus +-- not reach 'CompleteAck'. +prop_autoNegNoThreeInARow :: H.Property +prop_autoNegNoThreeInARow = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 1 100)) + + inp1 <- H.forAll (genConfRegsAck (Range.singleton simDuration)) + inp2 <- H.forAll (genConfRegsNoAck (Range.singleton simDuration)) + let inp = take simDuration $ concat $ zipWith (\a b -> [a, b]) inp1 inp2 + simOut = + C.sampleN simDuration (autoNegSim @C.System (C.fromList (map f inp))) + where + f a = (Ok, Just (RudiC a)) + + H.assert $ isNothing (find g simOut) + where + g (CompleteAck{}) = True + g _ = False + +C.createDomain C.vSystem{C.vName = "TimeoutDom", C.vPeriod = C.hzToPeriod 125e6} + +-- | Assert that for a domain frequency of 125 MHz the maximul value for timeout +-- is equal to the maxBound of an 'Index'. +prop_autoNegTimeoutLength :: H.Property +prop_autoNegTimeoutLength = H.withTests 1 $ H.property $ do + maxBound @(Clash.Cores.Sgmii.AutoNeg.Timeout TimeoutDom) + H.=== maxBound @(C.Index 200000) + +tests :: TestTree +tests = $(testGroupGenerator) diff --git a/test/Test/Cores/Sgmii/BitSlip.hs b/test/Test/Cores/Sgmii/BitSlip.hs new file mode 100644 index 0000000..484e24b --- /dev/null +++ b/test/Test/Cores/Sgmii/BitSlip.hs @@ -0,0 +1,93 @@ +module Test.Cores.Sgmii.BitSlip where + +import Clash.Cores.Sgmii.BitSlip +import Clash.Cores.Sgmii.Common +import Clash.Hedgehog.Sized.BitVector +import qualified Clash.Prelude as C +import Data.List (find) +import Data.Maybe (isJust, isNothing) +import qualified Hedgehog as H +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range +import Test.Cores.LineCoding8b10b +import Test.Tasty +import Test.Tasty.Hedgehog +import Test.Tasty.TH +import Prelude + +-- | Version of 'bitSlip' that also outputs the current state, used to check +-- if the correct state has been reached +bitSlipSim :: + forall dom. + (C.HiddenClockResetEnable dom) => + C.Signal dom (C.BitVector 10) -> + C.Signal dom (BitSlipState, C.BitVector 10, Status) +bitSlipSim cg = + C.bundle $ + C.mooreB + bitSlipT + bitSlipO + (BSFail (0, 0) (C.repeat 0) (C.repeat 0)) + (cg, pure Ok) + +-- | Check that if 'bitSlip' moves into 'BSOk', the index is non-zero as it +-- needs to be over code group boundaries due to 'checkBitSequence' +prop_bitSlipNoBSOk :: H.Property +prop_bitSlipNoBSOk = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 1 100)) + + inp <- + H.forAll + ( Gen.list + (Range.singleton simDuration) + (Gen.filter checkBitSequence genDefinedBitVector) + ) + let simOut = + map f $ + drop 1 $ + C.sampleN + (simDuration + 1) + (bitSlipSim @C.System (C.fromList (0 : inp))) + where + f (s, _, _) = s + + H.assert $ isNothing $ find g simOut + where + g (BSOk _ 0) = True + g _ = False + +-- | Check that with the comma at the third index, the output is equal to a +-- shifted version of the input, and the comma is actually at the third index +prop_bitSlipInOutCorrect :: H.Property +prop_bitSlipInOutCorrect = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 10 100)) + + inp1 <- + H.forAll + ( Gen.list + (Range.singleton simDuration) + ( Gen.filter + (\a -> isValidCodeGroup a && checkBitSequence a) + genDefinedBitVector + ) + ) + let inp2 = concatMap (\a -> [0b0101111100, a]) inp1 + + simOut = + drop 4 $ + C.sampleN + (length inp2 + 1) + (bitSlipSim @C.System (C.fromList (0 : inp2))) + + expected = take (length simOut) $ drop 1 inp2 + + map f simOut H.=== expected + H.assert $ isJust $ find g simOut + where + f (_, cg, _) = cg + + g (BSOk _ 0, _, _) = True + g _ = False + +tests :: TestTree +tests = $(testGroupGenerator) diff --git a/test/Test/Cores/Sgmii/RateAdapt.hs b/test/Test/Cores/Sgmii/RateAdapt.hs new file mode 100644 index 0000000..b89d252 --- /dev/null +++ b/test/Test/Cores/Sgmii/RateAdapt.hs @@ -0,0 +1,118 @@ +{-# LANGUAGE ViewPatterns #-} + +module Test.Cores.Sgmii.RateAdapt where + +import Clash.Cores.Sgmii.Common +import Clash.Cores.Sgmii.RateAdapt +import Clash.Hedgehog.Sized.BitVector +import qualified Clash.Prelude as C +import Data.List (uncons) +import Data.Maybe (catMaybes, fromJust, fromMaybe) +import qualified Hedgehog as H +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range +import Test.Tasty +import Test.Tasty.Hedgehog +import Test.Tasty.TH +import Prelude + +-- | Version of 'rateAdaptRx' that takes an input tuple instead of separate +-- variables +rateAdaptRxSim :: + (C.HiddenClockResetEnable dom) => + C.Signal dom (LinkSpeed, Maybe (C.BitVector 8)) -> + C.Signal dom (Maybe (C.BitVector 8)) +rateAdaptRxSim (C.unbundle -> (linkSpeed, rxDw)) = rateAdaptRx linkSpeed rxDw + +-- | Version of 'rateAdaptTx' that takes an input tuple instead of separate +-- variables +rateAdaptTxSim :: + (C.HiddenClockResetEnable dom) => + C.Signal dom (LinkSpeed, Maybe (C.BitVector 8)) -> + C.Signal dom (Bool, Maybe (C.BitVector 8)) +rateAdaptTxSim (C.unbundle -> (linkSpeed, txDw)) = + C.bundle $ rateAdaptTx linkSpeed txDw + +-- | Convert a speed to a symbol duplication factor +duplicationFactor :: LinkSpeed -> Int +duplicationFactor linkSpeed = case linkSpeed of + Speed10 -> 100 + Speed100 -> 10 + Speed1000 -> 1 + +-- | Function to take the n'th elements of a list +everyNth :: (Num a) => Int -> [a] -> [a] +everyNth n (drop (n - 1) -> l) + | null l = [] + | otherwise = head' 0 l : everyNth n (drop 1 l) + +-- | Function to safely take the first element of a list and replace it with a +-- default value if the list is empty +head' :: a -> [a] -> a +head' a l = fst $ fromMaybe (a, []) $ uncons l + +-- | Test whether the receive rate adaptation works as intended +rateAdaptRxTest :: LinkSpeed -> H.Property +rateAdaptRxTest linkSpeed = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 1 1000)) + + inp <- H.forAll (Gen.list (Range.singleton simDuration) genDefinedBitVector) + let simOut = + drop 1 $ + C.sampleN + (simDuration + 1) + ( rateAdaptRxSim @C.System + (C.fromList ((linkSpeed, Nothing) : map f inp)) + ) + where + f a = (linkSpeed, Just a) + + expected = + head' 0 inp : everyNth (duplicationFactor linkSpeed) (drop 1 inp) + + catMaybes simOut H.=== expected + +prop_rateAdaptRx10 :: H.Property +prop_rateAdaptRx10 = rateAdaptRxTest Speed10 + +prop_rateAdaptRx100 :: H.Property +prop_rateAdaptRx100 = rateAdaptRxTest Speed100 + +prop_rateAdaptRx1000 :: H.Property +prop_rateAdaptRx1000 = rateAdaptRxTest Speed1000 + +-- | Test whether the transmit rate adaptation works as intended +rateAdaptTxTest :: LinkSpeed -> H.Property +rateAdaptTxTest linkSpeed = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 1 100)) + + inp1 <- H.forAll (Gen.list (Range.singleton simDuration) genDefinedBitVector) + let inp2 = concatMap (replicate (duplicationFactor linkSpeed)) inp1 + + simOut = + map g $ + drop 1 $ + C.sampleN + (length inp2 + 1) + ( rateAdaptTxSim @C.System + (C.fromList ((linkSpeed, Nothing) : map f inp2)) + ) + where + f a = (linkSpeed, Just a) + g (_, a) = fromJust a + + expected = take (length simOut) inp2 + + simOut H.=== expected + +prop_rateAdaptTx10 :: H.Property +prop_rateAdaptTx10 = rateAdaptTxTest Speed10 + +prop_rateAdaptTx100 :: H.Property +prop_rateAdaptTx100 = rateAdaptTxTest Speed100 + +prop_rateAdaptTx1000 :: H.Property +prop_rateAdaptTx1000 = rateAdaptTxTest Speed1000 + +tests :: TestTree +tests = $(testGroupGenerator) diff --git a/test/Test/Cores/Sgmii/Sgmii.hs b/test/Test/Cores/Sgmii/Sgmii.hs new file mode 100644 index 0000000..02cf502 --- /dev/null +++ b/test/Test/Cores/Sgmii/Sgmii.hs @@ -0,0 +1,206 @@ +{-# LANGUAGE ViewPatterns #-} + +module Test.Cores.Sgmii.Sgmii where + +import Clash.Cores.Sgmii +import Clash.Cores.Sgmii.Common +import Clash.Hedgehog.Sized.BitVector +import qualified Clash.Prelude as C +import Data.Maybe (fromMaybe) +import Data.Tuple (swap) +import qualified Hedgehog as H +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range +import Test.Cores.Sgmii.AutoNeg +import Test.Tasty +import Test.Tasty.Hedgehog +import Test.Tasty.TH +import Prelude + +-- | Placeholder integration function for all different parts of SGMII +sgmiiSim :: + (C.HiddenClockResetEnable dom) => + C.Signal dom Bool -> + C.Signal dom Bool -> + C.Signal dom (C.BitVector 8) -> + C.Signal dom (C.BitVector 10) -> + ( C.Signal dom Bool + , C.Signal dom Bool + , C.Signal dom (C.BitVector 8) + , C.Signal dom (C.BitVector 10) + ) +sgmiiSim txEn txEr txDw rxCg = (rxDv, rxEr, rxDw, txCg) + where + txCg = sgmiiTx txEn txEr txDw xmit txConfReg (pure Nothing) + (_, rxDv, rxEr, rxDw, xmit, txConfReg, _, _) = sgmiiRx rxCg + +-- | Loopback function that combines two full SGMII systems, with one in +-- loopback configuration, to check whether the full system including +-- auto-negotiation works +loopbackSim :: + (C.HiddenClockResetEnable dom) => + C.Signal dom (Bool, Bool, C.BitVector 8) -> + C.Signal dom (C.BitVector 8) +loopbackSim (C.unbundle -> (txEn, txEr, txDw)) = rxDw + where + (_, _, rxDw, txCg) = sgmiiSim txEn txEr txDw rxCg + (dv, er, dw, rxCg) = sgmiiSim dv er dw txCg + +-- | Function that runs two versions of 'sgmii' at the same time +duplexTransmissionSim :: + (C.HiddenClockResetEnable dom) => + C.Signal dom (Bool, Bool, C.BitVector 8, C.BitVector 8) -> + C.Signal dom (C.BitVector 8, C.BitVector 8) +duplexTransmissionSim (C.unbundle -> (txEn, txEr, txDw1, txDw2)) = + C.bundle (rxDw1, rxDw2) + where + (_, _, rxDw1, cg1) = sgmiiSim txEn txEr txDw1 cg2 + (_, _, rxDw2, cg2) = sgmiiSim txEn txEr txDw2 cg1 + +-- | Function that is used to propagate the config register via 'pcsTransmit' +-- 'pcsReceive' +confRegSim :: + (C.HiddenClockResetEnable dom) => + C.Signal dom ConfReg -> + C.Signal dom ConfReg +confRegSim txConfReg = fromMaybe 0 <$> rxConfReg + where + (_, _, _, _, _, _, rxConfReg, _) = sgmiiRx txCg + txCg = + sgmiiTx + (pure False) + (pure False) + (pure 0) + (pure (Just Conf)) + (Just <$> txConfReg) + (pure Nothing) + +-- | Test that the completely integrated system works in a loopback mode, where +-- the output of the second 'sgmii' instance is connected to its own input. +-- The system starts in configuration mode, so the first 76 samples are used +-- to set it up fully, after which it is ready for data transmission. During +-- data transmission, the first several packages are replaced with control +-- packages, these are dropped from the output comparision. Then, the output +-- is shifted to deal with the delay that is introduced along the way due to +-- registers. +prop_loopbackTest :: H.Property +prop_loopbackTest = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 1 100)) + + inp <- H.forAll (Gen.list (Range.singleton simDuration) genDefinedBitVector) + let setupSamples = 77 + delaySamples = 20 + controlCount = 9 + + simOut = + drop (setupSamples + delaySamples + controlCount) $ + C.sampleN + (simDuration + setupSamples + delaySamples) + ( loopbackSim @C.System + ( C.fromList + ( replicate setupSamples (False, False, 0) + ++ map f inp + ++ replicate delaySamples (False, False, 0) + ) + ) + ) + where + f dw = (True, False, dw) + + expected = drop controlCount inp + + simOut H.=== expected + +-- | Similar to 'prop_loopbackTest', however this time there is no loopback but +-- different signals are sent from left to right and right to left +prop_duplexTransmission :: H.Property +prop_duplexTransmission = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 1 100)) + + inp1 <- H.forAll (Gen.list (Range.singleton simDuration) genDefinedBitVector) + inp2 <- H.forAll (Gen.list (Range.singleton simDuration) genDefinedBitVector) + let setupSamples = 77 + delaySamples = 10 + controlCount = 9 + + inp = zip inp1 inp2 + + simOut = + drop (setupSamples + delaySamples + controlCount) $ + C.sampleN + (simDuration + setupSamples + delaySamples) + ( duplexTransmissionSim @C.System + ( C.fromList + ( replicate setupSamples (False, False, 0, 0) + ++ map f inp + ++ replicate delaySamples (False, False, 0, 0) + ) + ) + ) + where + f (dw1, dw2) = (True, False, dw1, dw2) + + expected = map swap $ drop controlCount inp + + simOut H.=== expected + +-- | Similar to 'prop_duplexTransmission', however this time the system enters +-- carrier extend and starts retransmission +prop_duplexTransmissionCarrierExtend :: H.Property +prop_duplexTransmissionCarrierExtend = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 10 100)) + + inp1 <- H.forAll (Gen.list (Range.singleton simDuration) genDefinedBitVector) + inp2 <- H.forAll (Gen.list (Range.singleton simDuration) genDefinedBitVector) + inp3 <- H.forAll (Gen.list (Range.singleton simDuration) genDefinedBitVector) + inp4 <- H.forAll (Gen.list (Range.singleton simDuration) genDefinedBitVector) + let setupSamples = 77 + delaySamples = 10 + controlCount = 9 + extendCount = 2 + + inpA = zip inp1 inp2 + inpB = zip inp3 inp4 + + simOut = + drop (setupSamples + delaySamples + controlCount) $ + C.sampleN + (2 * simDuration + setupSamples + delaySamples + extendCount) + ( duplexTransmissionSim @C.System + ( C.fromList + ( replicate setupSamples (False, False, 0, 0) + ++ map f inpA + ++ [(False, True, 0, 0), (False, True, 0, 0)] + ++ map f inpB + ++ replicate delaySamples (False, False, 0, 0) + ) + ) + ) + where + f (dw1, dw2) = (True, False, dw1, dw2) + + expected1 = map swap $ drop controlCount inpA + simOut1 = take (simDuration - controlCount) simOut + + expected2 = map swap $ drop controlCount inpB + simOut2 = + take (simDuration - controlCount) $ + drop (simDuration + extendCount) simOut + + simOut1 H.=== expected1 + simOut2 H.=== expected2 + +-- | Assert that the configuration register will be propagated from the transmit +-- block to the receive block in exactly 25 cycles +prop_confRegPropagated :: H.Property +prop_confRegPropagated = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.singleton 25)) + + inp <- H.forAll genConfRegNoAck + let simOut = + drop 1 $ C.sampleN (simDuration + 1) (confRegSim @C.System (pure inp)) + + H.assert $ inp `elem` simOut + +tests :: TestTree +tests = $(testGroupGenerator) diff --git a/test/Test/Cores/Sgmii/Sync.hs b/test/Test/Cores/Sgmii/Sync.hs new file mode 100644 index 0000000..329b8d7 --- /dev/null +++ b/test/Test/Cores/Sgmii/Sync.hs @@ -0,0 +1,97 @@ +module Test.Cores.Sgmii.Sync where + +import Clash.Cores.LineCoding8b10b +import Clash.Cores.Sgmii.Common +import Clash.Cores.Sgmii.Sync +import Clash.Hedgehog.Sized.BitVector +import qualified Clash.Prelude as C +import Data.Function (on) +import Data.List (group, mapAccumL, maximumBy) +import qualified Hedgehog as H +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range +import Test.Cores.LineCoding8b10b +import Test.Tasty +import Test.Tasty.Hedgehog +import Test.Tasty.TH +import Prelude + +-- | Simulation function for 'sync' that provides a bundled output +syncSim :: + (C.HiddenClockResetEnable dom) => + C.Signal dom CodeGroup -> + C.Signal dom (CodeGroup, Bool, C.Vec 3 Symbol8b10b, Even, Status) +syncSim cg = C.bundle $ sync cg + +-- | Run the 'sync' function on a list of values that do not contain any comma +-- code groups and assert that the 'SyncStatus' will never go to 'Ok' +prop_syncNotOk :: H.Property +prop_syncNotOk = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 1 100)) + + inp <- + H.forAll + ( Gen.list + (Range.singleton simDuration) + (Gen.filter checkBitSequence genDefinedBitVector) + ) + let simOut = + map f $ + C.sampleN (simDuration + 1) (syncSim @C.System (C.fromList (0 : inp))) + where + f (_, _, _, _, syncStatus) = syncStatus + + H.assert (Ok `notElem` simOut) + +-- | Check that for any given input data word, this data word will always be +-- propagated to the output of the 'sync' block +prop_syncPropagateDw :: H.Property +prop_syncPropagateDw = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 1 100)) + + inp <- + H.forAll + ( Gen.list + (Range.singleton simDuration) + (Gen.filter checkBitSequence genDefinedBitVector) + ) + let delaySamples = 4 + + simOut = + map f $ + drop (1 + delaySamples) $ + C.sampleN + (simDuration + 1) + (syncSim @C.System (C.fromList (0 : inp))) + where + f (_, _, dw, _, _) = C.head dw + + expected = + take (simDuration - delaySamples) $ + snd (mapAccumL decode8b10b False inp) + + simOut H.=== expected + +-- | Assert that 'Even' is never two times 'Odd' in a row, and that 'Even' +-- is never the same more than two times in a row +prop_syncCheckEven :: H.Property +prop_syncCheckEven = H.property $ do + simDuration <- H.forAll (Gen.integral (Range.linear 1 100)) + + inp <- H.forAll (Gen.list (Range.singleton simDuration) genDefinedBitVector) + let delaySamples = 4 + + simOut = + map f $ + drop delaySamples $ + C.sampleN + (simDuration + delaySamples) + (syncSim @C.System (C.fromList (replicate delaySamples 0 ++ inp))) + where + f (_, _, _, rxEven, _) = rxEven + + H.assert $ [Odd, Odd] `notElem` group simOut + H.assert $ length (maximumBy (compare `on` length) (group simOut)) < 3 + +tests :: TestTree +tests = $(testGroupGenerator) diff --git a/test/unit-tests.hs b/test/unit-tests.hs index 09c1740..2ff9c4e 100644 --- a/test/unit-tests.hs +++ b/test/unit-tests.hs @@ -5,6 +5,9 @@ Test driver -} + +{-# LANGUAGE CPP #-} + module Main where import Prelude @@ -12,6 +15,13 @@ import Test.Tasty import qualified Test.Cores.Crc import qualified Test.Cores.LineCoding8b10b +#if MIN_VERSION_clash_prelude(1,9,0) +import qualified Test.Cores.Sgmii.AutoNeg +import qualified Test.Cores.Sgmii.BitSlip +import qualified Test.Cores.Sgmii.RateAdapt +import qualified Test.Cores.Sgmii.Sgmii +import qualified Test.Cores.Sgmii.Sync +#endif import qualified Test.Cores.SPI import qualified Test.Cores.SPI.MultiSlave import qualified Test.Cores.UART @@ -20,7 +30,7 @@ import qualified Test.Cores.Xilinx.DcFifo import qualified Test.Cores.Xilinx.DnaPortE2 tests :: TestTree -tests = testGroup "Unittests" +tests = testGroup "Unittests" $ [ Test.Cores.Crc.tests , Test.Cores.LineCoding8b10b.tests , Test.Cores.SPI.tests @@ -29,7 +39,20 @@ tests = testGroup "Unittests" , Test.Cores.Xilinx.BlockRam.tests , Test.Cores.Xilinx.DcFifo.tests , Test.Cores.Xilinx.DnaPortE2.tests + ] ++ tests_modern + +tests_modern :: [TestTree] +#if MIN_VERSION_clash_prelude(1,9,0) +tests_modern = + [ Test.Cores.Sgmii.AutoNeg.tests + , Test.Cores.Sgmii.BitSlip.tests + , Test.Cores.Sgmii.RateAdapt.tests + , Test.Cores.Sgmii.Sgmii.tests + , Test.Cores.Sgmii.Sync.tests ] +#else +tests_modern = [] +#endif main :: IO () main = defaultMain tests