From 7976c0dd05e167f4a2348b9018b1a6e09ad54b82 Mon Sep 17 00:00:00 2001 From: Hidde Moll Date: Thu, 4 Jun 2026 10:39:29 +0200 Subject: [PATCH 1/5] Clarify output of `speedChangeToFincFdec` --- .../src/Bittide/Instances/Hitl/FincFdec.hs | 10 ++++++---- bittide/src/Bittide/ClockControl.hs | 17 ++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/bittide-instances/src/Bittide/Instances/Hitl/FincFdec.hs b/bittide-instances/src/Bittide/Instances/Hitl/FincFdec.hs index 9b440937d4..e45019efcf 100644 --- a/bittide-instances/src/Bittide/Instances/Hitl/FincFdec.hs +++ b/bittide-instances/src/Bittide/Instances/Hitl/FincFdec.hs @@ -15,6 +15,8 @@ import Clash.Prelude (withClockResetEnable) import Clash.Xilinx.ClockGen (clockWizardDifferential) import Bittide.ClockControl ( + FDEC, + FINC, SpeedChange (NoChange, SlowDown, SpeedUp), speedChangeToFincFdec, ) @@ -70,8 +72,8 @@ goFincFdecTests :: ::: ( Signal Basic200 TestState , -- Freq increase / freq decrease request to clock board "" - ::: ( "FINC" ::: Signal Basic200 Bool - , "FDEC" ::: Signal Basic200 Bool + ::: ( "FINC" ::: Signal Basic200 FINC + , "FDEC" ::: Signal Basic200 FDEC ) , -- SPI to clock board: "" ::: Signal Basic200 Spi.M2S @@ -177,8 +179,8 @@ fincFdecTests :: ) , -- Freq increase / freq decrease request to clock board "" - ::: ( "FINC" ::: Signal Basic200 Bool - , "FDEC" ::: Signal Basic200 Bool + ::: ( "FINC" ::: Signal Basic200 FINC + , "FDEC" ::: Signal Basic200 FDEC ) , -- SPI to clock board: "" ::: Signal Basic200 Spi.M2S diff --git a/bittide/src/Bittide/ClockControl.hs b/bittide/src/Bittide/ClockControl.hs index ef8b9b0ada..a3bafbc26f 100644 --- a/bittide/src/Bittide/ClockControl.hs +++ b/bittide/src/Bittide/ClockControl.hs @@ -69,6 +69,9 @@ data ToFincFdecState dom | Idle deriving (Generic, NFDataX) +type FINC = Bool +type FDEC = Bool + {- | Convert 'SpeedChange' to a pair of (FINC, FDEC). This is currently hardcoded to work on the Si5395 constraints: @@ -83,9 +86,9 @@ speedChangeToFincFdec :: Clock dom -> Reset dom -> Signal dom SpeedChange -> - Signal dom (Bool, Bool) + Signal dom (FINC, FDEC) speedChangeToFincFdec clk rst = - dflipflop clk . fmap conv . mealy clk rst enableGen go (Wait maxBound) + dflipflop clk . fmap speedChangeToPins . mealy clk rst enableGen go (Wait maxBound) where go :: ToFincFdecState dom -> SpeedChange -> (ToFincFdecState dom, SpeedChange) go (Wait n) _s @@ -97,14 +100,6 @@ speedChangeToFincFdec clk rst = go Idle NoChange = (Idle, NoChange) go Idle s = (Pulse maxBound s, NoChange) - -- FINC FDEC - conv NoChange = (False, False) - conv SpeedUp = (True, False) - conv SlowDown = (False, True) - -type FINC = Bool -type FDEC = Bool - speedChangeToPins :: SpeedChange -> (FINC, FDEC) speedChangeToPins = \case SpeedUp -> (True, False) @@ -163,7 +158,7 @@ speedChangeToStickyPins :: Enable dom -> SNat prd -> Signal dom (Maybe SpeedChange) -> - Signal dom (Bool, Bool) + Signal dom (FINC, FDEC) speedChangeToStickyPins clk rst ena SNat msc = speedChangeToPins <$> stickySC where sc = fromMaybe NoChange <$> msc From 0348cd34b74621d04f81c77cedc1fc8ad9d83eff Mon Sep 17 00:00:00 2001 From: Hidde Moll Date: Tue, 9 Jun 2026 10:57:55 +0200 Subject: [PATCH 2/5] Deduplicate `SpeedChange` to pins conversion functions --- .../Instances/Hitl/GenericDemo/BringUp.hs | 4 +- .../Instances/Hitl/GenericDemo/Core.hs | 6 +- bittide/src/Bittide/ClockControl.hs | 63 ------------------- .../src/Bittide/ClockControl/CallistoSw.hs | 13 ++-- 4 files changed, 13 insertions(+), 73 deletions(-) diff --git a/bittide-instances/src/Bittide/Instances/Hitl/GenericDemo/BringUp.hs b/bittide-instances/src/Bittide/Instances/Hitl/GenericDemo/BringUp.hs index a01d091e32..1cf03925d2 100644 --- a/bittide-instances/src/Bittide/Instances/Hitl/GenericDemo/BringUp.hs +++ b/bittide-instances/src/Bittide/Instances/Hitl/GenericDemo/BringUp.hs @@ -186,11 +186,9 @@ bringUp bufferDepth mkUserCore refClk refRst = frequencyAdjustments :: Signal Bittide (FINC, FDEC) frequencyAdjustments = delay bittideClk enableGen minBound - $ speedChangeToStickyPins + $ speedChangeToFincFdec bittideClk bittideRst - enableGen - (SNat @Si539xHoldTime) speedChanges idC -< (spi, sync, uartTx, Fwd frequencyAdjustments) diff --git a/bittide-instances/src/Bittide/Instances/Hitl/GenericDemo/Core.hs b/bittide-instances/src/Bittide/Instances/Hitl/GenericDemo/Core.hs index d1378e3fda..96dfffa618 100644 --- a/bittide-instances/src/Bittide/Instances/Hitl/GenericDemo/Core.hs +++ b/bittide-instances/src/Bittide/Instances/Hitl/GenericDemo/Core.hs @@ -57,7 +57,7 @@ import Clash.Prelude ( import Protocols import Bittide.CaptureUgn (captureUgns, sendUgn) -import Bittide.ClockControl (SpeedChange) +import Bittide.ClockControl (SpeedChange (NoChange)) import Bittide.ClockControl.CallistoSw (SwcccInternalBusses, callistoSwClockControlC) import Bittide.DoubleBufferedRam (wbStorage) import Bittide.ElasticBuffer (fromData, xilinxElasticBufferWb) @@ -244,7 +244,7 @@ core :: , "CC_SUITABLE" ::: CSignal Bittide (BitVector LinkCount) , "RXS" ::: Vec LinkCount (CSignal GthRx (BitVector 64)) ) - ( CSignal Bittide (Maybe SpeedChange) + ( CSignal Bittide SpeedChange , "TXS" ::: Vec LinkCount (CSignal Bittide (BitVector 64)) , Sync Bittide Basic125 , "UARTS" ::: Vec InternalCpuCount (Df Bittide (BitVector 8)) @@ -374,7 +374,7 @@ core bufferDepth mkUserCore (refClk, refRst) (bitClk, bitRst, bitEna) rxClocks r in if simulateCc then swCcOut0 - else pure Nothing + else pure NoChange else swCcOut0 -- Stop clock control diff --git a/bittide/src/Bittide/ClockControl.hs b/bittide/src/Bittide/ClockControl.hs index a3bafbc26f..133c16051c 100644 --- a/bittide/src/Bittide/ClockControl.hs +++ b/bittide/src/Bittide/ClockControl.hs @@ -8,13 +8,9 @@ module Bittide.ClockControl ( FDEC, RelDataCount, SpeedChange (..), - Si539xHoldTime, - Si539xMinUpdatePeriod, sign, speedChangeToFincFdec, speedChangeToPins, - speedChangeToStickyPins, - stickyBits, targetDataCount, ) where @@ -23,7 +19,6 @@ import Clash.Explicit.Prelude hiding (PeriodToCycles) import Bittide.Arithmetic.Time (PeriodToCycles) import Clash.Class.BitPackC (BitPackC) -import Data.Maybe (fromMaybe) import Protocols.MemoryMap.TypeDescription {- | The (virtual) type of the FIFO's data counter. Setting this to @@ -105,61 +100,3 @@ speedChangeToPins = \case SpeedUp -> (True, False) SlowDown -> (False, True) NoChange -> (False, False) - -{- | Holds any @a@ which has any bits set for @stickyCycles@ clock cycles. -On receiving a new @a@ with non-zero bits, it sets the new incoming value as it output -and holds it for @stickyCycles@ clock cycles. --} -stickyBits :: - forall dom stickyCycles a. - ( KnownDomain dom - , NFDataX a - , BitPack a - , 1 <= stickyCycles - ) => - Clock dom -> - Reset dom -> - Enable dom -> - SNat stickyCycles -> - Signal dom a -> - Signal dom a -stickyBits clk rst ena SNat = mealy clk rst ena go (0, unpack 0) - where - go :: (Index stickyCycles, a) -> a -> ((Index stickyCycles, a), a) - go (count, storedBits) incomingBits = ((nextCount, nextStored), storedBits) - where - newIncoming = pack incomingBits /= 0 - predCount = satPred SatZero count - holdingBits = count /= 0 - (nextStored, nextCount) - | newIncoming = (incomingBits, maxBound) - | holdingBits = (storedBits, predCount) - | otherwise = (unpack 0, predCount) - -{- | The minimum hold time for FINC/FDEC pulses for the Si539* boards Bittide works -with is specified as 100ns. An additional 50ns is included for margin of error. --} -type Si539xHoldTime = Nanoseconds 150 - --- | The minimum update period for FINC/FDEC pulses as specified in the Si539* manual is 1μs. -type Si539xMinUpdatePeriod = Microseconds 1 - -{- | Takes the clock modification from a Callisto clock control implementation and -converts it into clock control pin signals stickied for a specified hold time. --} -speedChangeToStickyPins :: - forall dom prd. - ( KnownDomain dom - , KnownNat prd - , 1 <= PeriodToCycles dom prd - ) => - Clock dom -> - Reset dom -> - Enable dom -> - SNat prd -> - Signal dom (Maybe SpeedChange) -> - Signal dom (FINC, FDEC) -speedChangeToStickyPins clk rst ena SNat msc = speedChangeToPins <$> stickySC - where - sc = fromMaybe NoChange <$> msc - stickySC = stickyBits clk rst ena (SNat @(PeriodToCycles dom prd)) sc diff --git a/bittide/src/Bittide/ClockControl/CallistoSw.hs b/bittide/src/Bittide/ClockControl/CallistoSw.hs index 53da93eeb6..22381e9d6a 100644 --- a/bittide/src/Bittide/ClockControl/CallistoSw.hs +++ b/bittide/src/Bittide/ClockControl/CallistoSw.hs @@ -9,12 +9,14 @@ module Bittide.ClockControl.CallistoSw ( import Clash.Prelude hiding (PeriodToCycles) +import Data.Maybe (fromMaybe) + import Clash.Class.BitPackC (ByteOrder) import Clash.Functor.Extra ((<<$>>)) import Protocols import VexRiscv -import Bittide.ClockControl (SpeedChange) +import Bittide.ClockControl (SpeedChange (NoChange)) import Bittide.ClockControl.Freeze (freeze) import Bittide.ClockControl.Registers (clockControlWb) import Bittide.Counter (domainDiffCountersWbC) @@ -84,7 +86,7 @@ callistoSwClockControlC :: ) ) ( Sync dom free - , CSignal dom (Maybe SpeedChange) + , CSignal dom SpeedChange , Vec otherWb (BitboneMm dom (SwcccRemBusWidth otherWb)) @@ -102,7 +104,10 @@ callistoSwClockControlC freeClk freeRst rxClocks rxResets dumpVcd peConfig = ) <- Vec.split -< allWishbone - speedChanges <- clockControlWb linkMask linksOk (unbundle diffCounters) -< clockControlBus + speedChange <- + applyC (fmap (fromMaybe NoChange)) id + <| clockControlWb linkMask linksOk (unbundle diffCounters) + -< clockControlBus clockControlBus <- arbiterMm -< [ccClockControlBusWide, muClockControlBusWide] -- We need to extend the width of both wishbone busses since we don't know which @@ -136,4 +141,4 @@ callistoSwClockControlC freeClk freeRst rxClocks rxResets dumpVcd peConfig = let diffCounters = fst <<$>> domainDiffs - idC -< (sync, speedChanges, wbRest) + idC -< (sync, speedChange, wbRest) From d2e4c1822823ee9059caf47928d31ee688ec2b4d Mon Sep 17 00:00:00 2001 From: Hidde Moll Date: Tue, 9 Jun 2026 10:49:12 +0200 Subject: [PATCH 3/5] Add functions to do FINC/FDEC through register operations --- .../src/manual_additions/si539x_spi.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs b/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs index 46e0404d70..9dfea2b13e 100644 --- a/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs +++ b/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs @@ -214,4 +214,34 @@ impl Si539xSpi { self.write(write_op); } } + + /// Do a frequency increment 'n' times. + /// + /// Waits for 1 us between SPI transactions to adhere to the maximum update rate of + /// 1 us of the Si5395. + pub fn finc(&self, timer: &Timer, n: u8) { + for _ in 0..n { + self.write(ConfigEntry { + page: 0x00, + address: 0x1D, + data: 0b1 << 0, + }); + timer.wait(Duration::from_micros(1)); + } + } + + /// Do a frequency decrement 'n' times. + /// + /// Waits for 1 us between SPI transactions to adhere to the maximum update rate of + /// 1 us of the Si5395. + pub fn fdec(&self, timer: &Timer, n: u8) { + for _ in 0..n { + self.write(ConfigEntry { + page: 0x00, + address: 0x1D, + data: 0b1 << 1, + }); + timer.wait(Duration::from_micros(1)); + } + } } From 426cb96520cc441e9fcfd1954906e3d4347cb6c1 Mon Sep 17 00:00:00 2001 From: Hidde Moll Date: Mon, 22 Jun 2026 11:19:47 +0200 Subject: [PATCH 4/5] Add function to write clock config with retry mechanism --- .../hitl-tests/clock-board/src/main.rs | 2 +- .../src/manual_additions/si539x_spi.rs | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/firmware-binaries/hitl-tests/clock-board/src/main.rs b/firmware-binaries/hitl-tests/clock-board/src/main.rs index 621ff99567..06b29bc424 100644 --- a/firmware-binaries/hitl-tests/clock-board/src/main.rs +++ b/firmware-binaries/hitl-tests/clock-board/src/main.rs @@ -145,7 +145,7 @@ fn main() -> ! { { uwriteln!(uart, "Test 2: Write clock configuration").unwrap(); if let Err(WriteError::NotConfirmed { entry, read_data }) = - si539x_spi.write_configuration(&timer, &CONFIG_200) + si539x_spi.write_configuration_with_retry(&timer, &CONFIG_200, 10) { all_passed = false; uwriteln!( diff --git a/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs b/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs index 9dfea2b13e..0b02dee92e 100644 --- a/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs +++ b/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs @@ -84,6 +84,34 @@ impl Si539xSpi { Ok(()) } + /// Write a clock board configuration using SPI, will retry upon failure. + /// + /// Writing a configuration can sometimes fail, usually because a written register was not + /// the same when not read back. This happens roughly once in 200 runs. + pub fn write_configuration_with_retry< + const PRE_LEN: usize, + const CFG_LEN: usize, + const PST_LEN: usize, + >( + &self, + timer: &Timer, + config: &Config, + max_retries: usize, + ) -> Result<(), WriteError> { + let mut retries = 0; + loop { + match self.write_configuration(timer, config) { + Ok(()) => return Ok(()), + Err(e) => { + retries += 1; + if retries >= max_retries { + return Err(e); + } + } + } + } + } + /// Verfiy that the config part of the configuration is as expected. pub fn verify_configuration< const PRE_LEN: usize, From 2bea23d0cf25d375dbba4fbb20ad20b32fec5ac0 Mon Sep 17 00:00:00 2001 From: Hidde Moll Date: Tue, 9 Jun 2026 16:45:30 +0200 Subject: [PATCH 5/5] Move `fincFdecTests` to software `fincFdecTests` uses a domain difference counter to check whether requested speed changes actually increment or decrement the frequency of the external clock. To achieve this the external clock needs to be roughly the same frequency as what the logic runs on. Previously both were 200 MHz, but our VexRiscv cannot run at that frequency. As a temporary solution we add a clock configuration for a clock of 125 MHz. Once we can run a CPU at 200 MHz we will remove the configuration for 125 MHz. --- bittide-instances/bittide-instances.cabal | 1 + .../data/constraints/fincFdecTests.xdc | 23 - .../Bittide/Instances/Hitl/Driver/FincFdec.hs | 94 +++ .../src/Bittide/Instances/Hitl/FincFdec.hs | 331 +++++----- .../src/Bittide/Instances/MemoryMaps.hs | 2 + .../Si5395J-125MHz-10ppb-Registers.csv | 622 ++++++++++++++++++ .../Si5395J-125MHz-10ppb.slabtimeproj | Bin 0 -> 18400 bytes firmware-binaries/Cargo.lock | 12 + firmware-binaries/Cargo.toml | 1 + .../hitl-tests/finc-fdec/Cargo.lock.license | 3 + .../hitl-tests/finc-fdec/Cargo.toml | 25 + .../hitl-tests/finc-fdec/build.rs | 10 + .../hitl-tests/finc-fdec/src/main.rs | 332 ++++++++++ 13 files changed, 1263 insertions(+), 193 deletions(-) delete mode 100644 bittide-instances/data/constraints/fincFdecTests.xdc create mode 100644 bittide-instances/src/Bittide/Instances/Hitl/Driver/FincFdec.hs create mode 100644 bittide/data/clock_configs/Si5395J-125MHz-10ppb-Registers.csv create mode 100644 bittide/data/clock_configs/Si5395J-125MHz-10ppb.slabtimeproj create mode 100644 firmware-binaries/hitl-tests/finc-fdec/Cargo.lock.license create mode 100644 firmware-binaries/hitl-tests/finc-fdec/Cargo.toml create mode 100644 firmware-binaries/hitl-tests/finc-fdec/build.rs create mode 100644 firmware-binaries/hitl-tests/finc-fdec/src/main.rs diff --git a/bittide-instances/bittide-instances.cabal b/bittide-instances/bittide-instances.cabal index 3ac6497152..1f43c49313 100644 --- a/bittide-instances/bittide-instances.cabal +++ b/bittide-instances/bittide-instances.cabal @@ -174,6 +174,7 @@ library Bittide.Instances.Hitl.Driver.ClockControl.Plot.Report Bittide.Instances.Hitl.Driver.ClockControl.Samples Bittide.Instances.Hitl.Driver.DnaOverSerial + Bittide.Instances.Hitl.Driver.FincFdec Bittide.Instances.Hitl.Driver.Si539xConfiguration Bittide.Instances.Hitl.Driver.VexRiscv Bittide.Instances.Hitl.Driver.VexRiscvTcp diff --git a/bittide-instances/data/constraints/fincFdecTests.xdc b/bittide-instances/data/constraints/fincFdecTests.xdc deleted file mode 100644 index e6a8d86f96..0000000000 --- a/bittide-instances/data/constraints/fincFdecTests.xdc +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Google LLC -# -# SPDX-License-Identifier: Apache-2.0 - -# SYSCLK_125 -set_property BOARD_PART_PIN sysclk_125_p [get_ports {CLK_125MHZ_p}] -set_property BOARD_PART_PIN sysclk_125_n [get_ports {CLK_125MHZ_n}] -# SMA_MGT_REFCLK -set_property BOARD_PART_PIN sma_mgt_refclk_p [get_ports {SMA_MGT_REFCLK_C_p}] -set_property BOARD_PART_PIN sma_mgt_refclk_n [get_ports {SMA_MGT_REFCLK_C_n}] - -# Vivado marks all clocks as related by default. Our external clocks are not -# though, which means that we need to explicitly mark them as unrelated (or -# "asynchronous"). -set_clock_groups \ - -asynchronous \ - -group [get_clocks -include_generated_clocks {CLK_125MHZ_p}] \ - -group [get_clocks -include_generated_clocks {SMA_MGT_REFCLK_C_p}] - -# GPIO_LED_0_LS -set_property BOARD_PART_PIN GPIO_LED_0_LS [get_ports {done}] -# GPIO_LED_1_LS -set_property BOARD_PART_PIN GPIO_LED_1_LS [get_ports {success}] diff --git a/bittide-instances/src/Bittide/Instances/Hitl/Driver/FincFdec.hs b/bittide-instances/src/Bittide/Instances/Hitl/Driver/FincFdec.hs new file mode 100644 index 0000000000..75eb95a157 --- /dev/null +++ b/bittide-instances/src/Bittide/Instances/Hitl/Driver/FincFdec.hs @@ -0,0 +1,94 @@ +-- SPDX-FileCopyrightText: 2025 Google LLC +-- +-- SPDX-License-Identifier: Apache-2.0 +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PackageImports #-} + +module Bittide.Instances.Hitl.Driver.FincFdec where + +import Clash.Prelude + +import Control.Monad (forM_) +import Project.Chan +import Project.FilePath +import Project.Handle (assertEither) + +import Vivado.Tcl (HwTarget) +import Vivado.VivadoM + +import Bittide.Hitl +import Bittide.Instances.Hitl.Utils.Driver (assertProbe) +import Bittide.Instances.Hitl.Utils.Gdb (initGdb) +import Bittide.Instances.Hitl.Utils.OpenOcd (parseTapInfo) +import Bittide.Instances.Hitl.Utils.Serial (initSerial) +import Bittide.Instances.Hitl.Utils.Usb (resetUsbDeviceByLocation) +import "bittide-extra" Control.Exception.Extra (brackets) + +import Control.Concurrent.Async (forConcurrently_, mapConcurrently_) +import Control.Concurrent.Async.Extra (zipWithConcurrently3_) +import Control.Monad.IO.Class +import System.Exit +import System.FilePath + +import qualified Bittide.Instances.Hitl.Utils.OpenOcd as Ocd +import qualified Data.List as L +import qualified Gdb +import qualified System.Timeout.Extra as T + +driver :: + String -> + [(HwTarget, DeviceInfo)] -> + VivadoM ExitCode +driver _name targets = do + liftIO + $ putStrLn + $ "Running driver function for targets " + <> show ((\(_, info) -> info.deviceId) <$> targets) + + projectDir <- liftIO $ findParentContaining "cabal.project" + let hitlDir = projectDir "_build" "hitl" + + forM_ targets (assertProbe "probe_test_start") + + -- Reset USB adapter, see documentation of "Bittide.Instances.Hitl.Utils.Usb" + liftIO $ mapM_ (\(_, d) -> resetUsbDeviceByLocation d.usbAdapterLocation) targets + + let + expectedJtagIds = [0x0514C001] + toInitArgs (_, deviceInfo) targetIndex = + Ocd.InitOpenOcdArgs{deviceInfo, expectedJtagIds, hitlDir, targetIndex} + initArgs = L.zipWith toInitArgs targets [0 ..] + optionalInitArgs = L.repeat def + openOcdStarts = liftIO <$> L.zipWith Ocd.initOpenOcd initArgs optionalInitArgs + + brackets openOcdStarts (liftIO . (.cleanup)) $ \initOcdsData -> do + let + allTapInfos = parseTapInfo expectedJtagIds <$> initOcdsData + + peTapInfos :: [Ocd.TapInfo] + peTapInfos + | all (== L.length expectedJtagIds) (L.length <$> allTapInfos) + , [pes] <- L.transpose allTapInfos = + pes + | otherwise = + error + $ "Unexpected number of OpenOCD taps initialized. Expected: " + <> show (L.length expectedJtagIds) + <> ", but got: " + <> show (L.length <$> allTapInfos) + + Gdb.withGdbs (L.length targets) $ \gdbs -> do + liftIO $ zipWithConcurrently3_ (initGdb hitlDir "finc-fdec") gdbs peTapInfos targets + liftIO $ mapConcurrently_ ((assertEither =<<) . Gdb.loadBinary) gdbs + + let serialStarts = liftIO <$> L.zipWith (initSerial hitlDir) targets [0 ..] + brackets serialStarts (liftIO . snd) $ \(L.map fst -> serials) -> do + liftIO $ mapConcurrently_ Gdb.continue gdbs + + liftIO + $ T.tryWithTimeout T.PrintActionTime "Waiting for test success" 60_000_000 + $ forConcurrently_ serials + $ \serial -> + waitForLine serial "All tests passed" + + pure ExitSuccess diff --git a/bittide-instances/src/Bittide/Instances/Hitl/FincFdec.hs b/bittide-instances/src/Bittide/Instances/Hitl/FincFdec.hs index e45019efcf..94e2f6ced8 100644 --- a/bittide-instances/src/Bittide/Instances/Hitl/FincFdec.hs +++ b/bittide-instances/src/Bittide/Instances/Hitl/FincFdec.hs @@ -8,207 +8,189 @@ FINC and FDEC pins. -} module Bittide.Instances.Hitl.FincFdec where +import Clash.Explicit.Prelude +import Clash.Prelude (HiddenClock, HiddenReset, withClock, withClockResetEnable, withReset) +import Protocols + import Clash.Annotations.TH (makeTopEntity) +import Clash.Class.BitPackC (ByteOrder) +import Clash.Cores.Uart (ValidBaud) import Clash.Cores.Xilinx (withXilinx) -import Clash.Explicit.Prelude -import Clash.Prelude (withClockResetEnable) import Clash.Xilinx.ClockGen (clockWizardDifferential) - -import Bittide.ClockControl ( - FDEC, - FINC, - SpeedChange (NoChange, SlowDown, SpeedUp), - speedChangeToFincFdec, +import Protocols.MemoryMap (Access (WriteOnly), MemoryMap, Mm, getMMAny) +import Protocols.MemoryMap.Registers.WishboneStandard ( + RegisterConfig (..), + deviceConfig, + deviceWbI, + registerConfig, + registerWbI, ) -import Bittide.ClockControl.Si539xSpi (ConfigState (Finished), si539xSpi) -import Bittide.Counter (domainDiffCounter) +import Protocols.Spi +import VexRiscv + +import Bittide.ClockControl (FDEC, FINC, SpeedChange (..), speedChangeToFincFdec) +import Bittide.ClockControl.Si539xSpi (si539xSpiWb) +import Bittide.Counter (domainDiffCountersWbC) import Bittide.Hitl ( + HitlTestCase (..), HitlTestGroup (..), - hitlVio, - testCasesFromEnum, + hitlVioBool, + paramForHwTargets, ) -import Bittide.Instances.Common (commonSpiConfig) import Bittide.Instances.Domains import Bittide.Instances.Hitl.Setup (allHwTargets) +import Bittide.ProcessingElement (PeConfig (..), processingElement) +import Bittide.SharedTypes (BitboneMm, withLittleEndian) +import Bittide.Wishbone (timeWb, uartDf, uartInterfaceWb) -import Data.Maybe (isJust) +import GHC.Stack (HasCallStack) import System.FilePath (()) +import qualified Bittide.Cpus.Riscv32imc as Riscv32imc +import qualified Bittide.Instances.Hitl.Driver.FincFdec as Driver +import qualified Clash.Class.Cdc as Cdc import qualified Clash.Cores.Xilinx.Gth as Gth import qualified Protocols.Spi as Spi -data TestState = Busy | Fail | Success -data Test - = -- | Keep pressing FDEC, see if counter falls below certain threshold - FDec - | -- | Keep pressing FINC, see if counter exceeds certain threshold - FInc - | -- | 'FDec' test followed by an 'FInc' one - FDecInc - | -- | 'FInc' test followed by an 'FDec' one - FIncDec - deriving (Enum, Generic, NFDataX, Bounded, BitPack, ShowX, Show) - -{- | Counter threshold after which a test is considered passed/failed. In theory -clocks can diverge at +-20 kHz (at 200 MHz), which gives the tests 500 ms to -adjust their clocks - which should be plenty. +type Baud = 921_600 + +baud :: SNat Baud +baud = SNat + +{- | Memory mapped component to control the FINC and FDEC pins. Note that you still need to +convert from 'SpeedChange' to the FINC/FDEC pins using 'speedChangeToFincFdec'. -} -threshold :: Signed 32 -threshold = 20_000 - -testStateToDoneSuccess :: TestState -> (Bool, Bool) -testStateToDoneSuccess = \case - Busy -> (False, False) - Fail -> (True, False) - Success -> (True, True) - -goFincFdecTests :: - Clock Basic200 -> - Reset Basic200 -> - Clock Ext200 -> - Signal Basic200 Test -> - Signal Basic200 Spi.S2M -> - "" - ::: ( Signal Basic200 TestState - , -- Freq increase / freq decrease request to clock board - "" - ::: ( "FINC" ::: Signal Basic200 FINC - , "FDEC" ::: Signal Basic200 FDEC - ) - , -- SPI to clock board: - "" ::: Signal Basic200 Spi.M2S - , -- Debug signals: - "" - ::: ( "SPI_BUSY" ::: Signal Basic200 Bool - , "SPI_STATE" ::: Signal Basic200 (BitVector 40) - , "SI_LOCKED" ::: Signal Basic200 Bool - , "COUNTER_ACTIVE" ::: Signal Basic200 Bool - , "COUNTER" ::: Signal Basic200 (Signed 32) - ) - ) -goFincFdecTests clk rst clkControlled testSelect spiS2M = - (testResult, fIncDec, spiM2S, debugSignals) +hardwareSpeedChange :: + forall aw dom. + ( HasCallStack + , HiddenClock dom + , HiddenReset dom + , KnownNat aw + , 1 <= aw + , ?byteOrder :: ByteOrder + ) => + Circuit (BitboneMm dom aw) (CSignal dom SpeedChange) +hardwareSpeedChange = circuit $ \(mm, wb) -> do + [speedChangeBus] <- deviceWbI (deviceConfig "HardwareSpeedChange") -< (mm, wb) + + (speedChange, _speedChangeActivity) <- + registerWbI speedChangeConfig NoChange -< (speedChangeBus, Fwd (pure Nothing)) + + idC -< speedChange + where + speedChangeConfig = (registerConfig "speed_change" ""){access = WriteOnly} + +fincFdecPe :: + forall free sky vendor. + ( HasCallStack + , KnownDomain free + , KnownDomain sky + , HasSynchronousReset free + , HasSynchronousReset sky + , 1 <= DomainPeriod free + , ValidBaud free Baud + , ?byteOrder :: ByteOrder + , Cdc.HiddenVendor vendor + , Cdc.ValidGray vendor 8 sky free + , Cdc.GrayConstraints vendor 8 sky free + ) => + Clock free -> + Reset free -> + Clock sky -> + Circuit + ( ToConstBwd Mm + , Jtag free + ) + ( "UART_TX" ::: CSignal free Bit + , Spi free + , CSignal free SpeedChange + ) +fincFdecPe freeClk freeRst skyClk = circuit $ \(mm, jtag) -> do + [timeBus, uartBus, siBus, dcBus, speedChangeBus] <- + withClockResetEnable freeClk freeRst enableGen + $ processingElement NoDumpVcd peConfig + -< (mm, jtag) + + Fwd _localCounter <- withClockResetEnable freeClk freeRst enableGen $ timeWb Nothing -< timeBus + (uartTx, _uartStatus) <- + withClockResetEnable freeClk freeRst enableGen + $ uartInterfaceWb d16 d16 + $ uartDf baud + -< (uartBus, Fwd (pure low)) + (Fwd spiDone, spiOut) <- + withClockResetEnable freeClk freeRst enableGen + $ si539xSpiWb (SNat @(Microseconds 10)) + -< siBus + + let skyRst = convertReset freeClk skyClk (unsafeFromActiveLow spiDone) + Fwd _domainDiff <- domainDiffCountersWbC (skyClk :> Nil) (skyRst :> Nil) freeClk freeRst -< dcBus + + speedChange <- withClock freeClk $ withReset freeRst $ hardwareSpeedChange -< speedChangeBus + + idC -< (uartTx, spiOut, speedChange) where - debugSignals = (spiBusy, pack <$> spiState, siClkLocked, counterActive, counter) - - (_, spiBusy, spiState@(fmap (== Finished) -> siClkLocked), spiM2S) = - withClockResetEnable clk rst enableGen - $ si539xSpi - commonSpiConfig - (SNat @(Microseconds 1)) - (pure Nothing) - spiS2M - - rstTest = unsafeFromActiveLow siClkLocked - rstControlled = convertReset clk clkControlled rst - - (counter, counterActive) = - unbundle - $ - -- Note that in a "real" Bittide system the clocks would be wired up the - -- other way around: the controlled domain would be the target domain. We - -- don't do that here because we know 'rstControlled' will come out of - -- reset much earlier than 'rstTest'. Doing it the "proper" way would - -- therefore introduce extra complexity, without adding to the test's - -- coverage. - (withXilinx domainDiffCounter) clkControlled rstControlled clk rstTest - - fIncDec = unbundle $ speedChangeToFincFdec clk rstTest fIncDecRequest - - (fIncDecRequest, testResult) = - unbundle - $ (!!) - <$> bundle (fDecResult :> fIncResult :> fDecIncResult :> fIncDecResult :> Nil) - <*> fmap fromEnum testSelect - - fDecResult = goFdec <$> counter - fIncResult = goFinc <$> counter - fDecIncResult = mealy clk rstTest enableGen goFdecFinc FDec counter - fIncDecResult = mealy clk rstTest enableGen goFincFdec FInc counter - - -- Keep pressing FDEC, expect counter to go below -@threshold@ - goFdec :: Signed 32 -> (SpeedChange, TestState) - goFdec n - | n > threshold = (NoChange, Fail) - | n < -threshold = (NoChange, Success) - | otherwise = (SlowDown, Busy) - - -- Keep pressing FINC, expect counter to go above @threshold@ - goFinc :: Signed 32 -> (SpeedChange, TestState) - goFinc n - | n > threshold = (NoChange, Success) - | n < -threshold = (NoChange, Fail) - | otherwise = (SpeedUp, Busy) - - -- Keep pressing FDEC, expect counter to go below -@threshold@, then keep pressing - -- FINC, expect counter to go above 0. - goFdecFinc :: Test -> Signed 32 -> (Test, (SpeedChange, TestState)) - goFdecFinc FDec n - | n > threshold = (FDec, (NoChange, Fail)) - | n < -threshold = (FInc, (NoChange, Busy)) - | otherwise = (FDec, (SlowDown, Busy)) - goFdecFinc FInc n - | n > 0 = (FInc, (NoChange, Success)) - | n < -(3 * threshold) = (FInc, (NoChange, Fail)) - | otherwise = (FInc, (SpeedUp, Busy)) - goFdecFinc s _ = (s, (NoChange, Fail)) -- Illegal state - - -- Keep pressing FINC, expect counter to go above @threshold@, then keep pressing - -- FDEC, expect counter to go below 0. - goFincFdec :: Test -> Signed 32 -> (Test, (SpeedChange, TestState)) - goFincFdec FInc n - | n > threshold = (FDec, (NoChange, Busy)) - | n < -threshold = (FInc, (NoChange, Fail)) - | otherwise = (FInc, (SpeedUp, Busy)) - goFincFdec FDec n - | n > (3 * threshold) = (FDec, (NoChange, Fail)) - | n < 0 = (FDec, (NoChange, Success)) - | otherwise = (FDec, (SlowDown, Busy)) - goFincFdec s _ = (s, (NoChange, Fail)) -- Illegal state + peConfig = + PeConfig + { cpu = Riscv32imc.vexRiscv0 + , depthI = SNat @(Div (8 * 1024) 4) + , depthD = SNat @(Div (8 * 1024) 4) + , initI = Nothing + , initD = Nothing + , iBusTimeout = d0 + , dBusTimeout = d0 + , includeIlaWb = False + } fincFdecTests :: -- Pins from internal oscillator: "CLK_125MHZ" ::: DiffClock Ext125 -> -- Pins from clock board: - "SMA_MGT_REFCLK_C" ::: DiffClock Ext200 -> - Signal Basic200 Spi.S2M -> + "SMA_MGT_REFCLK_C" ::: DiffClock Ext125 -> + "JTAG" ::: Signal Basic125 JtagIn -> + "USB_UART_TXD" ::: Signal Basic125 Bit -> + Signal Basic125 Spi.S2M -> "" - ::: ( "" - ::: ( "done" ::: Signal Basic200 Bool - , "success" ::: Signal Basic200 Bool - ) + ::: ( "JTAG" ::: Signal Basic125 JtagOut + , "USB_UART_RXD" ::: Signal Basic125 Bit + , -- SPI to clock board: + "" ::: Signal Basic125 Spi.M2S , -- Freq increase / freq decrease request to clock board "" - ::: ( "FINC" ::: Signal Basic200 FINC - , "FDEC" ::: Signal Basic200 FDEC + ::: ( "FINC" ::: Signal Basic125 FINC + , "FDEC" ::: Signal Basic125 FDEC ) - , -- SPI to clock board: - "" ::: Signal Basic200 Spi.M2S ) -fincFdecTests diffClk controlledDiffClock spiS2M = - ((testDone, testSuccess), fIncDec, spiM2S) +fincFdecTests freeClkDiff boardClkDiff jtagIn _uartRx spiS2M = + (jtagOut, uartTx, spiM2S, fincFdec) where - (_, odivClk) = Gth.ibufds_gte3 controlledDiffClock - clkControlled = Gth.bufgGt d0 odivClk noReset + freeClk :: Clock Basic125 + freeRst :: Reset Basic125 + (freeClk, freeRst) = clockWizardDifferential freeClkDiff noReset + + (_, odivClk) = Gth.ibufds_gte3 boardClkDiff - (clk, clkStableRst) = clockWizardDifferential diffClk noReset + boardClk :: Clock Basic125A + boardClk = Gth.bufgGt d0 odivClk noReset - started = isJust <$> testInput - testRst = orReset clkStableRst (unsafeFromActiveLow started) + testStart :: Signal Basic125 Bool + testStart = hitlVioBool freeClk testStart (pure True) - (testResult, fIncDec, spiM2S, _debugSignals) = - goFincFdecTests clk testRst clkControlled (fromJustX <$> testInput) spiS2M + testRst = unsafeFromActiveLow testStart `orReset` freeRst - (testDone, testSuccess) = unbundle $ testStateToDoneSuccess <$> testResult + ((_memoryMap, jtagOut), (uartTx, spiM2S, speedChange)) = + toSignals + (withXilinx $ withLittleEndian $ fincFdecPe freeClk testRst boardClk) + (((), jtagIn), ((), spiS2M, ())) - -- For debugging, add to separate VIO? - -- clkStable1 = unsafeToActiveLow clkStableRst - -- testRstBool = unsafeToActiveHigh testRst - -- (fInc, fDec) = fIncDec - -- (spiBusy, spiState, siClkLocked, counterActive, counter) = debugSignals + fincFdec = unbundle $ speedChangeToFincFdec freeClk testRst speedChange + +memoryMap :: MemoryMap +memoryMap = + getMMAny + $ withXilinx + $ withLittleEndian + $ fincFdecPe @Basic125 @Basic125A clockGen resetGen clockGen - testInput :: Signal Basic200 (Maybe Test) - testInput = hitlVio FDec clk testDone testSuccess {-# OPAQUE fincFdecTests #-} makeTopEntity 'fincFdecTests @@ -217,12 +199,21 @@ tests = HitlTestGroup { topEntity = 'fincFdecTests , targetXdcs = - [ "fincFdecTests.xdc" + [ "si539xConfigTest.xdc" + , "jtag" "config.xdc" + , "jtag" "pmod1.xdc" + , "uart" "pmod1.xdc" , "si539x" "fincfdec.xdc" , "si539x" "spi.xdc" ] , externalHdl = [] - , testCases = testCasesFromEnum @Test allHwTargets () - , mDriverProc = Nothing + , testCases = + [ HitlTestCase + { name = "FincFdecTests" + , parameters = paramForHwTargets allHwTargets () + , postProcData = () + } + ] + , mDriverProc = Just Driver.driver , mPostProc = Nothing } diff --git a/bittide-instances/src/Bittide/Instances/MemoryMaps.hs b/bittide-instances/src/Bittide/Instances/MemoryMaps.hs index 4889f9e996..4fb49c1bff 100644 --- a/bittide-instances/src/Bittide/Instances/MemoryMaps.hs +++ b/bittide-instances/src/Bittide/Instances/MemoryMaps.hs @@ -21,6 +21,7 @@ import Language.Haskell.TH (reportError, runIO) import System.Directory (createDirectoryIfMissing, removePathForcibly) import System.FilePath +import qualified Bittide.Instances.Hitl.FincFdec as FincFdec import qualified Bittide.Instances.Hitl.Si539xConfiguration as Si539xConfiguration import qualified Bittide.Instances.Hitl.SoftUgnDemo.MemoryMaps as SoftUgnDemo import qualified Bittide.Instances.Hitl.WireDemo.MemoryMaps as WireDemo @@ -53,6 +54,7 @@ $( do , ("DnaPortE2Test", DnaPortE2.dutMm) , ("Ethernet", vexRiscvEthernetMM) , ("ElasticBufferWbTest", ElasticBufferWb.dutMM) + , ("FincFdecTests", FincFdec.memoryMap) , ("Freeze", freezeMM) , ("NestedInterconnect", NestedInterconnect.nestedInterconnectMm) , ("ProcessingElement", vexRiscvUartHelloMM) diff --git a/bittide/data/clock_configs/Si5395J-125MHz-10ppb-Registers.csv b/bittide/data/clock_configs/Si5395J-125MHz-10ppb-Registers.csv new file mode 100644 index 0000000000..35a5e5aeff --- /dev/null +++ b/bittide/data/clock_configs/Si5395J-125MHz-10ppb-Registers.csv @@ -0,0 +1,622 @@ +# Si534x/7x/8x/9x Registers Script +# +# Part: Si5395 +# Project File: \\VBOXSVR\win_vm_shared\Si5395J-125MHz-10ppb.slabtimeproj +# Design ID: 125_10b +# Includes Pre/Post Download Control Register Writes: Yes +# Device Revision: A +# Creator: ClockBuilder Pro v4.11.0.1 [2023-09-14] +# Created On: 2026-06-09 16:17:14 GMT+02:00 +Address,Data +# +# Start configuration preamble +0x0B24,0xC0 +0x0B25,0x00 +0x0540,0x01 +# End configuration preamble +# +# Delay 300 msec +# Delay is worst case time for device to complete any calibration +# that is running due to device state change previous to this script +# being processed. +# +# Start configuration registers +0x0006,0x00 +0x0007,0x00 +0x0008,0x00 +0x000B,0x68 +0x0016,0x02 +0x0017,0xDC +0x0018,0xFF +0x0019,0xFF +0x001A,0xFF +0x0023,0xFF +0x0024,0x0F +0x0025,0x00 +0x0026,0x00 +0x0027,0x00 +0x0028,0x00 +0x002B,0x02 +0x002C,0x00 +0x002D,0x00 +0x002E,0x00 +0x002F,0x00 +0x0030,0x00 +0x0031,0x00 +0x0032,0x00 +0x0033,0x00 +0x0034,0x00 +0x0035,0x00 +0x0036,0x00 +0x0037,0x00 +0x0038,0x00 +0x0039,0x00 +0x003A,0x00 +0x003B,0x00 +0x003C,0x00 +0x003D,0x00 +0x003E,0x00 +0x003F,0x00 +0x0040,0x04 +0x0041,0x00 +0x0042,0x00 +0x0043,0x00 +0x0044,0x00 +0x0045,0x0C +0x0046,0x00 +0x0047,0x00 +0x0048,0x00 +0x0049,0x00 +0x004A,0x00 +0x004B,0x00 +0x004C,0x00 +0x004D,0x00 +0x004E,0x00 +0x004F,0x00 +0x0050,0x0F +0x0051,0x00 +0x0052,0x00 +0x0053,0x00 +0x0054,0x00 +0x0055,0x00 +0x0056,0x00 +0x0057,0x00 +0x0058,0x00 +0x0059,0x00 +0x005A,0x00 +0x005B,0x00 +0x005C,0x00 +0x005D,0x00 +0x005E,0x00 +0x005F,0x00 +0x0060,0x00 +0x0061,0x00 +0x0062,0x00 +0x0063,0x00 +0x0064,0x00 +0x0065,0x00 +0x0066,0x00 +0x0067,0x00 +0x0068,0x00 +0x0069,0x00 +0x0092,0x00 +0x0093,0x00 +0x0095,0x00 +0x0096,0x00 +0x0098,0x00 +0x009A,0x00 +0x009B,0x00 +0x009D,0x00 +0x009E,0x00 +0x00A0,0x00 +0x00A2,0x00 +0x00A9,0x00 +0x00AA,0x00 +0x00AB,0x00 +0x00AC,0x00 +0x00E5,0x01 +0x00EA,0x00 +0x00EB,0x00 +0x00EC,0x00 +0x00ED,0x00 +0x0102,0x01 +0x0103,0x02 +0x0104,0x09 +0x0105,0x3E +0x0106,0x18 +0x0108,0x01 +0x0109,0x09 +0x010A,0x3B +0x010B,0x28 +0x010D,0x01 +0x010E,0x09 +0x010F,0x3B +0x0110,0x28 +0x0112,0x01 +0x0113,0x09 +0x0114,0x3B +0x0115,0x28 +0x0117,0x01 +0x0118,0x09 +0x0119,0x3B +0x011A,0x28 +0x011C,0x01 +0x011D,0x09 +0x011E,0x3B +0x011F,0x28 +0x0121,0x01 +0x0122,0x09 +0x0123,0x3B +0x0124,0x28 +0x0126,0x01 +0x0127,0x09 +0x0128,0x3B +0x0129,0x28 +0x012B,0x01 +0x012C,0x09 +0x012D,0x3B +0x012E,0x28 +0x0130,0x01 +0x0131,0x09 +0x0132,0x3B +0x0133,0x28 +0x0135,0x02 +0x0136,0x09 +0x0137,0x3E +0x0138,0x19 +0x013A,0x02 +0x013B,0x09 +0x013C,0x3E +0x013D,0x19 +0x013F,0x00 +0x0140,0x00 +0x0141,0x40 +0x0142,0xFF +0x0206,0x00 +0x0208,0x00 +0x0209,0x00 +0x020A,0x00 +0x020B,0x00 +0x020C,0x00 +0x020D,0x00 +0x020E,0x00 +0x020F,0x00 +0x0210,0x00 +0x0211,0x00 +0x0212,0x00 +0x0213,0x00 +0x0214,0x00 +0x0215,0x00 +0x0216,0x00 +0x0217,0x00 +0x0218,0x00 +0x0219,0x00 +0x021A,0x00 +0x021B,0x00 +0x021C,0x00 +0x021D,0x00 +0x021E,0x00 +0x021F,0x00 +0x0220,0x00 +0x0221,0x00 +0x0222,0x00 +0x0223,0x00 +0x0224,0x00 +0x0225,0x00 +0x0226,0x00 +0x0227,0x00 +0x0228,0x00 +0x0229,0x00 +0x022A,0x00 +0x022B,0x00 +0x022C,0x00 +0x022D,0x00 +0x022E,0x00 +0x022F,0x00 +0x0231,0x0B +0x0232,0x0B +0x0233,0x0B +0x0234,0x0B +0x0235,0x00 +0x0236,0x00 +0x0237,0x00 +0x0238,0x80 +0x0239,0x89 +0x023A,0x00 +0x023B,0x00 +0x023C,0x00 +0x023D,0x00 +0x023E,0x80 +0x0247,0x04 +0x0248,0x00 +0x0249,0x00 +0x024A,0x00 +0x024B,0x00 +0x024C,0x00 +0x024D,0x00 +0x024E,0x00 +0x024F,0x00 +0x0250,0x00 +0x0251,0x00 +0x0252,0x00 +0x0253,0x00 +0x0254,0x00 +0x0255,0x00 +0x0256,0x00 +0x0257,0x00 +0x0258,0x00 +0x0259,0x00 +0x025A,0x00 +0x025B,0x00 +0x025C,0x00 +0x025D,0x00 +0x025E,0x00 +0x025F,0x00 +0x0260,0x00 +0x0261,0x00 +0x0262,0x00 +0x0263,0x00 +0x0264,0x00 +0x0265,0x4F +0x0266,0x00 +0x0267,0x00 +0x0268,0x04 +0x0269,0x00 +0x026A,0x00 +0x026B,0x31 +0x026C,0x32 +0x026D,0x35 +0x026E,0x5F +0x026F,0x31 +0x0270,0x30 +0x0271,0x62 +0x0272,0x00 +0x028A,0x00 +0x028B,0x00 +0x028C,0x00 +0x028D,0x00 +0x028E,0x00 +0x028F,0x00 +0x0290,0x00 +0x0291,0x00 +0x0292,0x3F +0x0293,0x2F +0x0294,0x80 +0x0296,0x00 +0x0297,0x00 +0x0299,0x00 +0x029D,0x00 +0x029E,0x00 +0x029F,0x00 +0x02A9,0x00 +0x02AA,0x00 +0x02AB,0x00 +0x02B7,0xFF +0x02BC,0x00 +0x0302,0x00 +0x0303,0x00 +0x0304,0x00 +0x0305,0x40 +0x0306,0x08 +0x0307,0x00 +0x0308,0x00 +0x0309,0x00 +0x030A,0x00 +0x030B,0xC8 +0x030C,0x00 +0x030D,0x00 +0x030E,0x00 +0x030F,0x00 +0x0310,0x40 +0x0311,0x08 +0x0312,0x00 +0x0313,0x00 +0x0314,0x00 +0x0315,0x00 +0x0316,0xC8 +0x0317,0x00 +0x0318,0x00 +0x0319,0x00 +0x031A,0x00 +0x031B,0x00 +0x031C,0x00 +0x031D,0x00 +0x031E,0x00 +0x031F,0x00 +0x0320,0x00 +0x0321,0x00 +0x0322,0x00 +0x0323,0x00 +0x0324,0x00 +0x0325,0x00 +0x0326,0x00 +0x0327,0x00 +0x0328,0x00 +0x0329,0x00 +0x032A,0x00 +0x032B,0x00 +0x032C,0x00 +0x032D,0x00 +0x032E,0x00 +0x032F,0x00 +0x0330,0x00 +0x0331,0x00 +0x0332,0x00 +0x0333,0x00 +0x0334,0x00 +0x0335,0x00 +0x0336,0x00 +0x0337,0x00 +0x0338,0x00 +0x0339,0x1C +0x033B,0x62 +0x033C,0x01 +0x033D,0x00 +0x033E,0x00 +0x033F,0x00 +0x0340,0x00 +0x0341,0x62 +0x0342,0x01 +0x0343,0x00 +0x0344,0x00 +0x0345,0x00 +0x0346,0x00 +0x0347,0x00 +0x0348,0x00 +0x0349,0x00 +0x034A,0x00 +0x034B,0x00 +0x034C,0x00 +0x034D,0x00 +0x034E,0x00 +0x034F,0x00 +0x0350,0x00 +0x0351,0x00 +0x0352,0x00 +0x0353,0x00 +0x0354,0x00 +0x0355,0x00 +0x0356,0x00 +0x0357,0x00 +0x0358,0x00 +0x0359,0x00 +0x035A,0x00 +0x035B,0x00 +0x035C,0x00 +0x035D,0x00 +0x035E,0x00 +0x035F,0x00 +0x0360,0x00 +0x0361,0x00 +0x0362,0x00 +0x0487,0x00 +0x0508,0x00 +0x0509,0x00 +0x050A,0x00 +0x050B,0x00 +0x050C,0x00 +0x050D,0x00 +0x050E,0x00 +0x050F,0x00 +0x0510,0x00 +0x0511,0x00 +0x0512,0x00 +0x0513,0x00 +0x0515,0x00 +0x0516,0x00 +0x0517,0x00 +0x0518,0x00 +0x0519,0x00 +0x051A,0x00 +0x051B,0x00 +0x051C,0x00 +0x051D,0x00 +0x051E,0x00 +0x051F,0x00 +0x0521,0x2B +0x052A,0x01 +0x052B,0x01 +0x052C,0x0F +0x052D,0x03 +0x052E,0x00 +0x052F,0x00 +0x0531,0x00 +0x0532,0x00 +0x0533,0x04 +0x0534,0x00 +0x0535,0x01 +0x0536,0x04 +0x0537,0x00 +0x0538,0x00 +0x0539,0x00 +0x053D,0x0A +0x053E,0x06 +0x0588,0x00 +0x0589,0x0C +0x058A,0x00 +0x058B,0x00 +0x058C,0x00 +0x058D,0x00 +0x059B,0x18 +0x059C,0x0C +0x059D,0x00 +0x059E,0x00 +0x059F,0x00 +0x05A0,0x00 +0x05A1,0x00 +0x05A2,0x00 +0x05A4,0x20 +0x05A5,0x00 +0x05A6,0x00 +0x05AC,0x00 +0x05AD,0x00 +0x05AE,0x00 +0x05B1,0x00 +0x05B2,0x00 +0x0802,0x35 +0x0803,0x05 +0x0804,0x01 +0x0805,0x00 +0x0806,0x00 +0x0807,0x00 +0x0808,0x00 +0x0809,0x00 +0x080A,0x00 +0x080B,0x00 +0x080C,0x00 +0x080D,0x00 +0x080E,0x00 +0x080F,0x00 +0x0810,0x00 +0x0811,0x00 +0x0812,0x00 +0x0813,0x00 +0x0814,0x00 +0x0815,0x00 +0x0816,0x00 +0x0817,0x00 +0x0818,0x00 +0x0819,0x00 +0x081A,0x00 +0x081B,0x00 +0x081C,0x00 +0x081D,0x00 +0x081E,0x00 +0x081F,0x00 +0x0820,0x00 +0x0821,0x00 +0x0822,0x00 +0x0823,0x00 +0x0824,0x00 +0x0825,0x00 +0x0826,0x00 +0x0827,0x00 +0x0828,0x00 +0x0829,0x00 +0x082A,0x00 +0x082B,0x00 +0x082C,0x00 +0x082D,0x00 +0x082E,0x00 +0x082F,0x00 +0x0830,0x00 +0x0831,0x00 +0x0832,0x00 +0x0833,0x00 +0x0834,0x00 +0x0835,0x00 +0x0836,0x00 +0x0837,0x00 +0x0838,0x00 +0x0839,0x00 +0x083A,0x00 +0x083B,0x00 +0x083C,0x00 +0x083D,0x00 +0x083E,0x00 +0x083F,0x00 +0x0840,0x00 +0x0841,0x00 +0x0842,0x00 +0x0843,0x00 +0x0844,0x00 +0x0845,0x00 +0x0846,0x00 +0x0847,0x00 +0x0848,0x00 +0x0849,0x00 +0x084A,0x00 +0x084B,0x00 +0x084C,0x00 +0x084D,0x00 +0x084E,0x00 +0x084F,0x00 +0x0850,0x00 +0x0851,0x00 +0x0852,0x00 +0x0853,0x00 +0x0854,0x00 +0x0855,0x00 +0x0856,0x00 +0x0857,0x00 +0x0858,0x00 +0x0859,0x00 +0x085A,0x00 +0x085B,0x00 +0x085C,0x00 +0x085D,0x00 +0x085E,0x00 +0x085F,0x00 +0x0860,0x00 +0x0861,0x00 +0x090E,0x02 +0x0943,0x01 +0x0949,0x00 +0x094A,0x00 +0x094E,0x49 +0x094F,0xF2 +0x095E,0x00 +0x0A02,0x00 +0x0A03,0x03 +0x0A04,0x00 +0x0A05,0x03 +0x0A14,0x00 +0x0A1A,0x00 +0x0A20,0x00 +0x0A26,0x00 +0x0A2C,0x00 +0x0A38,0x00 +0x0A39,0x00 +0x0A3A,0x00 +0x0A3C,0x00 +0x0A3D,0x00 +0x0A3E,0x00 +0x0A40,0x00 +0x0A41,0x00 +0x0A42,0x00 +0x0A44,0x00 +0x0A45,0x00 +0x0A46,0x00 +0x0A48,0x00 +0x0A49,0x00 +0x0A4A,0x00 +0x0A4C,0x00 +0x0A4D,0x00 +0x0A4E,0x00 +0x0A4F,0x00 +0x0A50,0x00 +0x0A51,0x00 +0x0A52,0x00 +0x0A53,0x00 +0x0A54,0x00 +0x0A55,0x00 +0x0A56,0x00 +0x0A57,0x00 +0x0A58,0x00 +0x0A59,0x00 +0x0A5A,0x00 +0x0A5B,0x00 +0x0A5C,0x00 +0x0A5D,0x00 +0x0A5E,0x00 +0x0A5F,0x00 +0x0B44,0x0F +0x0B46,0x00 +0x0B47,0x0F +0x0B48,0x0F +0x0B4A,0x1C +0x0B57,0x0E +0x0B58,0x01 +0x0C02,0x03 +0x0C03,0x00 +0x0C07,0x00 +0x0C08,0x00 +# End configuration registers +# +# Start configuration postamble +0x0514,0x01 +0x001C,0x01 +0x0540,0x00 +0x0B24,0xC3 +0x0B25,0x02 +# End configuration postamble diff --git a/bittide/data/clock_configs/Si5395J-125MHz-10ppb.slabtimeproj b/bittide/data/clock_configs/Si5395J-125MHz-10ppb.slabtimeproj new file mode 100644 index 0000000000000000000000000000000000000000..9dd563e1ec3ac92ccd233fddf21fd81b9eafd32a GIT binary patch literal 18400 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 = load_clock_config_csv!( + "../../../bittide/data/clock_configs/Si5395J-125MHz-10ppb-Registers.csv" +); + +const MAX_CONFIG_RETRIES: usize = 10; + +const THRESHOLD: i32 = 20_000; + +#[derive(Copy, Clone)] +enum Direction { + Finc, + Fdec, +} + +impl uDisplay for Direction { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: uWrite + ?Sized, + { + match self { + Direction::Fdec => uwrite!(f, "Fdec"), + Direction::Finc => uwrite!(f, "Finc"), + } + } +} + +type TestResult = Result<(), Direction>; + +fn read_counter(dc: &DomainDiffCounters) -> i32 { + dc.counters(0).unwrap().into_inner() +} + +fn counter_test( + dc: &DomainDiffCounters, + mut apply_speed_change: SpeedChangeFn, + (mut pass_test, pass): (Pass, i32), + (mut fail_test, fail): (Fail, i32), + direction: Direction, +) -> TestResult +where + Pass: FnMut(&i32, &i32) -> bool, + Fail: FnMut(&i32, &i32) -> bool, + SpeedChangeFn: FnMut(), +{ + loop { + apply_speed_change(); + let count = read_counter(dc); + if pass_test(&count, &pass) { + return Ok(()); + } + if fail_test(&count, &fail) { + return Err(direction); + } + } +} + +fn noop() {} + +// FINC/FDEC test patterns, split into hardware and software variants. +// Hardware: set the SpeedChange register once, the hardware will continuously pulse +// the FINC/FDEC pins. +// Software: send one SPI request per loop iteration. + +fn do_hw_fdec(sc: &HardwareSpeedChange, dc: &DomainDiffCounters) -> TestResult { + sc.set_speed_change(SpeedChange::SlowDown); + let result = counter_test( + dc, + noop, + (i32::lt, -THRESHOLD), + (i32::gt, THRESHOLD), + Direction::Fdec, + ); + sc.set_speed_change(SpeedChange::NoChange); + result +} + +fn do_hw_finc(sc: &HardwareSpeedChange, dc: &DomainDiffCounters) -> TestResult { + sc.set_speed_change(SpeedChange::SpeedUp); + let result = counter_test( + dc, + noop, + (i32::gt, THRESHOLD), + (i32::lt, -THRESHOLD), + Direction::Finc, + ); + sc.set_speed_change(SpeedChange::NoChange); + result +} + +fn do_hw_fdec_inc(sc: &HardwareSpeedChange, dc: &DomainDiffCounters) -> TestResult { + sc.set_speed_change(SpeedChange::SlowDown); + counter_test( + dc, + noop, + (i32::lt, -THRESHOLD), + (i32::gt, THRESHOLD), + Direction::Fdec, + )?; + sc.set_speed_change(SpeedChange::SpeedUp); + let result = counter_test( + dc, + noop, + (i32::gt, 0), + (i32::lt, -(3 * THRESHOLD)), + Direction::Finc, + ); + sc.set_speed_change(SpeedChange::NoChange); + result +} + +fn do_hw_finc_dec(sc: &HardwareSpeedChange, dc: &DomainDiffCounters) -> TestResult { + sc.set_speed_change(SpeedChange::SpeedUp); + counter_test( + dc, + noop, + (i32::gt, THRESHOLD), + (i32::lt, -THRESHOLD), + Direction::Finc, + )?; + sc.set_speed_change(SpeedChange::SlowDown); + let result = counter_test( + dc, + noop, + (i32::lt, 0), + (i32::gt, (3 * THRESHOLD)), + Direction::Fdec, + ); + sc.set_speed_change(SpeedChange::NoChange); + result +} + +fn do_sw_fdec(si539x_spi: &Si539xSpi, timer: &Timer, dc: &DomainDiffCounters) -> TestResult { + counter_test( + dc, + || si539x_spi.fdec(timer, 1), + (i32::lt, -THRESHOLD), + (i32::gt, THRESHOLD), + Direction::Fdec, + ) +} + +fn do_sw_finc(si539x_spi: &Si539xSpi, timer: &Timer, dc: &DomainDiffCounters) -> TestResult { + counter_test( + dc, + || si539x_spi.finc(timer, 1), + (i32::gt, THRESHOLD), + (i32::lt, -THRESHOLD), + Direction::Finc, + ) +} + +fn do_sw_fdec_inc(si539x_spi: &Si539xSpi, timer: &Timer, dc: &DomainDiffCounters) -> TestResult { + counter_test( + dc, + || si539x_spi.fdec(timer, 1), + (i32::lt, -THRESHOLD), + (i32::gt, THRESHOLD), + Direction::Fdec, + )?; + + counter_test( + dc, + || si539x_spi.finc(timer, 1), + (i32::gt, 0), + (i32::lt, -(3 * THRESHOLD)), + Direction::Finc, + ) +} + +fn do_sw_finc_dec(si539x_spi: &Si539xSpi, timer: &Timer, dc: &DomainDiffCounters) -> TestResult { + counter_test( + dc, + || si539x_spi.finc(timer, 1), + (i32::gt, THRESHOLD), + (i32::lt, -THRESHOLD), + Direction::Finc, + )?; + + counter_test( + dc, + || si539x_spi.fdec(timer, 1), + (i32::lt, 0), + (i32::gt, (3 * THRESHOLD)), + Direction::Fdec, + ) +} + +/// Run a single test with the full per-test sequence: +/// +/// 1. Program clock board +/// 2. Enable domain diff counter +/// 3. Execute test body (FINC/FDEC until threshold) +/// 5. Disable domain diff counter +/// 4. Print result +/// +/// Returns whether the test passed. +fn run_test( + name: &str, + si539x_spi: &Si539xSpi, + timer: &Timer, + uart: &mut Uart, + dc: &DomainDiffCounters, + test_body: Test, +) -> bool +where + Test: FnOnce(&DomainDiffCounters) -> TestResult, +{ + if let Err(WriteError::NotConfirmed { entry, read_data }) = + si539x_spi.write_configuration_with_retry(timer, &CONFIG_125, MAX_CONFIG_RETRIES) + { + uwriteln!( + uart, + "FAIL: {} (clock board at 0x{:02X}{:02X}: wrote 0x{:02X}, read 0x{:02X})", + name, + entry.page, + entry.address, + entry.data, + read_data, + ) + .unwrap(); + return false; + } + + dc.set_enable(0, true); + loop { + if dc.counters_active(0) == Some(true) { + break; + } + } + + let test_result = test_body(dc); + dc.set_enable(0, false); + + match test_result { + Ok(()) => uwriteln!(uart, "PASS: {}", name).unwrap(), + Err(dir) => uwriteln!(uart, "FAIL: {} on part {}", name, dir).unwrap(), + } + + test_result.is_ok() +} + +macro_rules! run_tests { + ( + spi: $spi:ident, + timer: $timer:ident, + uart: $uart:ident, + dc: $dc:ident, + tests: [ + $([name: $name:literal, test: $test:expr]),+ + $(,)? + ], + ) => { + $( + run_test( + $name, + &$spi, + &$timer, + &mut $uart, + &$dc, + $test, + ) + )&&+ + }; +} + +#[cfg_attr(not(test), entry)] +fn main() -> ! { + let si539x_spi = INSTANCES.si539x_spi; + let timer = INSTANCES.timer; + let mut uart = INSTANCES.uart; + let dc = INSTANCES.domain_diff_counters; + let sc = INSTANCES.hardware_speed_change; + + let all_passed = run_tests!( + spi: si539x_spi, + timer: timer, + uart: uart, + dc: dc, + tests: [ + [name: "Hardware FDec", test: |dc| do_hw_fdec(&sc, dc)], + [name: "Hardware FInc", test: |dc| do_hw_finc(&sc, dc)], + [name: "Hardware FDecInc", test: |dc| do_hw_fdec_inc(&sc, dc)], + [name: "Hardware FIncDec", test: |dc| do_hw_finc_dec(&sc, dc)], + [name: "Software FDec", test: |dc| do_sw_fdec(&si539x_spi, &timer, dc)], + [name: "Software FInc", test: |dc| do_sw_finc(&si539x_spi, &timer, dc)], + [name: "Software FDecInc", test: |dc| do_sw_fdec_inc(&si539x_spi, &timer, dc)], + [name: "Software FIncDec", test: |dc| do_sw_finc_dec(&si539x_spi, &timer, dc)], + ], + ); + + if all_passed { + uwriteln!(uart, "All tests passed").unwrap(); + } else { + uwriteln!(uart, "Some tests failed").unwrap(); + } + + loop { + continue; + } +} + +#[panic_handler] +fn panic_handler(_info: &PanicInfo) -> ! { + loop { + continue; + } +}