Skip to content

Commit 3d1c614

Browse files
martijnbastiaanhiddemoll
authored andcommitted
Add auto center feature to elastic buffers
In #1135 we employ a "simple" trick to allow ring buffers to be aligned and UGNs to remain valid while clock control is still stabilizing the system. We do all this in software, which critically relies on the software being "quick enough". This is not always the case, as seen in #1168. Although we've more or less fixed the issue by making EB control much faster (#1178), this commit adds the option to entirely offload this responsibility to hardware. Closes #1179
1 parent 3f2ea18 commit 3d1c614

File tree

7 files changed

+857
-8
lines changed

7 files changed

+857
-8
lines changed

CLAUDE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ too much, use `OverloadedRecordDot`. Both of these should be enabled by default.
3737
a new function has "too many" arguments or results, consider grouping them into
3838
records.
3939

40+
## Executing commands
41+
When you execute a command you **must** wait for it to finish. Don't assume that no output
42+
for a while means that everything is okay.
43+
4044
# Rust
4145
Before building a Rust crate, you **must** ensure that `bittide-instances` has
4246
been built. You can do this by running:

bittide-instances/src/Bittide/Instances/Tests/ElasticBufferWb.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,5 @@ dut = withBittideByteOrder $ withClockResetEnable clockGen (resetGenN d2) enable
9797
, includeIlaWb = False
9898
}
9999

100-
type IMemWords = DivRU (64 * 1024) 4
100+
type IMemWords = DivRU (128 * 1024) 4
101101
type DMemWords = DivRU (32 * 1024) 4

bittide/bittide.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ library
210210
Bittide.Df
211211
Bittide.DoubleBufferedRam
212212
Bittide.ElasticBuffer
213+
Bittide.ElasticBuffer.AutoCenter
213214
Bittide.Ethernet.Mac
214215
Bittide.Jtag
215216
Bittide.MetaPeConfig

bittide/src/Bittide/ElasticBuffer.hs

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@
44

55
module Bittide.ElasticBuffer where
66

7+
import Clash.Prelude
8+
import Protocols
9+
710
import Bittide.ClockControl (RelDataCount, targetDataCount)
8-
import Bittide.Df
11+
import Bittide.Df (unsafeFromDf)
12+
import Bittide.ElasticBuffer.AutoCenter (autoCenter)
913
import Bittide.Extra.Maybe (orNothing)
1014
import Bittide.SharedTypes (BitboneMm)
1115
import Clash.Class.BitPackC (ByteOrder)
1216
import Clash.Cores.Xilinx.DcFifo
1317
import Clash.Cores.Xilinx.Xpm.Cdc.Extra (safeXpmCdcHandshake)
1418
import Clash.Cores.Xilinx.Xpm.Cdc.Pulse (xpmCdcPulse)
15-
import Clash.Prelude
16-
import GHC.Stack
17-
import Protocols (Ack (..), CSignal, Circuit (..), applyC)
19+
import Data.Maybe (isJust)
20+
import GHC.Stack (HasCallStack)
21+
import Protocols.Df (CollectMode (..), roundrobinCollect)
1822
import Protocols.Df.Extra (ackWhen, skid)
1923
import Protocols.MemoryMap (Access (..))
2024
import Protocols.MemoryMap.Registers.WishboneStandard (
@@ -24,6 +28,7 @@ import Protocols.MemoryMap.Registers.WishboneStandard (
2428
registerConfig,
2529
registerWbDfI,
2630
registerWbI,
31+
registerWbI_,
2732
)
2833

2934
import qualified Clash.Explicit.Prelude as E
@@ -251,6 +256,7 @@ The registers provided are:
251256
xilinxElasticBufferWb ::
252257
forall n readDom writeDom addrW a.
253258
( HasCallStack
259+
, HasSynchronousReset readDom
254260
, KnownDomain readDom
255261
, KnownDomain writeDom
256262
, NFDataX a
@@ -280,6 +286,11 @@ xilinxElasticBufferWb clkRead rstRead SNat clkWrite wdata =
280286
, wbDataCount
281287
, wbUnderflow
282288
, wbOverflow
289+
, wbAutoCenterReset
290+
, wbAutoCenterEnable
291+
, wbAutoCenterMargin
292+
, wbAutoCenterIsIdle
293+
, wbAutoCenterTotalAdjustments
283294
] <-
284295
deviceWb "ElasticBuffer" -< wb
285296

@@ -312,10 +323,70 @@ xilinxElasticBufferWb clkRead rstRead SNat clkWrite wdata =
312323
(ebAdjustmentDf1, Fwd ebReady) <- skid -< ebAdjustmentDf0
313324
ackWhen ebReady -< ebAdjustmentWaitDfActivity
314325

315-
let (dataCount, underflow, overflow0, readData, adjustmentAck) =
316-
xilinxElasticBuffer @n clkRead clkWrite ebAdjustmentSig wdata
326+
-- Auto-centering state machine
327+
(_autoCenterReset, Fwd autoCenterResetActivity) <-
328+
registerWbI
329+
(registerConfig "auto_center_reset_unchecked")
330+
{ access = WriteOnly
331+
, description =
332+
"Clear total adjustments. You must disable the state machine and wait for it to be idle before resetting it. After resetting, you must also wait for the state machine to become 'idle' again to make sure the registers are cleared."
333+
}
334+
()
335+
-< (wbAutoCenterReset, Fwd (pure Nothing))
336+
337+
(Fwd autoCenterEnable, _autoCenterEnableActivity) <-
338+
registerWbI
339+
(registerConfig "auto_center_enable")
340+
{ access = ReadWrite
341+
, description = "Enable auto-centering state machine"
342+
}
343+
False
344+
-< (wbAutoCenterEnable, Fwd (pure Nothing))
317345

318-
Fwd ebAdjustmentSig <- unsafeFromDf -< (ebAdjustmentDf1, Fwd adjustmentAck)
346+
(Fwd autoCenterMargin, _autoCenterMarginActivity) <-
347+
registerWbI
348+
(registerConfig "auto_center_margin")
349+
{ access = ReadWrite
350+
, description = "Margin for auto-centering"
351+
}
352+
(2 :: Unsigned 16)
353+
-< (wbAutoCenterMargin, Fwd (pure Nothing))
354+
355+
registerWbI_
356+
(registerConfig "auto_center_is_idle")
357+
{ access = ReadOnly
358+
, description = "Whether the auto-centering state machine is idle"
359+
}
360+
False
361+
-< (wbAutoCenterIsIdle, Fwd (Just <$> autoCenterIsIdle))
362+
363+
registerWbI_
364+
(registerConfig "auto_center_total_adjustments")
365+
{ access = ReadOnly
366+
, description = "Total adjustments applied by the auto-centering state machine"
367+
}
368+
(0 :: Signed 32)
369+
-< (wbAutoCenterTotalAdjustments, Fwd (Just <$> autoCenterTotalAdjustments))
370+
371+
let
372+
autoCenterReset = unsafeFromActiveHigh (isJust . busActivityWrite <$> autoCenterResetActivity)
373+
374+
(dataCount, underflow, overflow0, readData, adjustmentAck) =
375+
xilinxElasticBuffer @n clkRead clkWrite ebAdjustmentSig wdata
376+
377+
(autoCenterAdjustmentDf, Fwd autoCenterTotalAdjustments, Fwd autoCenterIsIdle) <-
378+
autoCenter
379+
(autoCenterReset `E.orReset` rstRead)
380+
(toEnable autoCenterEnable)
381+
autoCenterMargin
382+
dataCount
383+
-< ()
384+
385+
-- Multiplex manual and auto-center adjustments using round-robin collection
386+
ebAdjustmentDfMuxed <-
387+
roundrobinCollect @2 Parallel -< [ebAdjustmentDf1, autoCenterAdjustmentDf]
388+
389+
Fwd ebAdjustmentSig <- unsafeFromDf -< (ebAdjustmentDfMuxed, Fwd adjustmentAck)
319390

320391
-- Synchronize overflow pulse from write domain to read domain
321392
let overflow1 = xpmCdcPulse clkWrite clkRead overflow0
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
-- SPDX-FileCopyrightText: 2026 Google LLC
2+
--
3+
-- SPDX-License-Identifier: Apache-2.0
4+
5+
module Bittide.ElasticBuffer.AutoCenter where
6+
7+
import Clash.Prelude
8+
import Protocols
9+
10+
import Bittide.ClockControl (RelDataCount)
11+
12+
data State
13+
= InReset
14+
| Idle
15+
{ adjustments :: Signed 32
16+
-- ^ Cumulative adjustments that have been submitted to the elastic buffer
17+
}
18+
| Adjusting
19+
{ adjustments :: Signed 32
20+
-- ^ Cumulative adjustments that have been submitted to the elastic buffer
21+
, adjustment :: Signed 32
22+
-- ^ The adjustment that is currently being submitted to the elastic buffer
23+
}
24+
| Waiting
25+
{ adjustments :: Signed 32
26+
-- ^ Cumulative adjustments that have been submitted to the elastic buffer
27+
, wait :: Index 256
28+
-- ^ Number of cycles to wait before moving to Idle state. This should be long enough
29+
-- to ensure that the adjustment has taken an effect on the buffer's occupancy count.
30+
-- It is currently hardcoded to 256 cycles, which should be well above the time it
31+
-- takes for an adjustment to take effect. At the same time, it should be more than
32+
-- quick enough to not be too slow even for systems that are wildly out of balance.
33+
}
34+
deriving (Generic, NFDataX, Show)
35+
36+
{- | If 'True', there are no pending adjustments to the elastic buffer. This means that the
37+
accumulated adjustments have been applied (though not necessarily reflected in the occupancy
38+
count yet) and that they can be used for, e.g., UGN adjustments.
39+
-}
40+
type IsIdle = Bool
41+
42+
{- | Whether the auto-centering state machine is enabled. If 'False', the state machine will
43+
stay in the Idle state and will not submit any adjustments to the elastic buffer. Disabling
44+
the state machine while it is active is perfectly safe -- the state machine will handle
45+
its pending submissions and then move and stay in its 'Idle' state.
46+
-}
47+
type IsEnabled = Bool
48+
49+
{- | State machine that tries to keep the buffer centered around 0 by submitting adjustments
50+
to the elastic buffer. Note that adjustments to the elastic buffer are destructive, and
51+
should therefore only be used during bittide initialization.
52+
-}
53+
autoCenter ::
54+
(HiddenClock dom, KnownNat n, n <= 32) =>
55+
Reset dom ->
56+
Enable dom ->
57+
-- | Correction margin. If set to 1, the circuit will try to keep the buffer at
58+
-- [-1, 0, 1]. Note that if set to 0, the circuit will try to keep the buffer at exactly
59+
-- 0, which will cause it to submit many small adjustments, which may not be desirable.
60+
Signal dom (Unsigned 16) ->
61+
-- | The current number of items in the buffer. The circuit will try to keep this at 0
62+
-- (or within the margin if set).
63+
Signal dom (RelDataCount n) ->
64+
Circuit
65+
()
66+
( Df dom (Signed 32)
67+
, CSignal dom (Signed 32)
68+
, CSignal dom IsIdle
69+
)
70+
autoCenter reset enable margin relDataCount = Circuit go
71+
where
72+
go (_, (ack, _, _)) = ((), (adjustment, output, isIdle))
73+
where
74+
(adjustment, output, isIdle) =
75+
withEnable enableGen
76+
$ withReset reset
77+
$ mooreB
78+
goState
79+
goOutput
80+
InReset
81+
( fromEnable enable
82+
, numConvert <$> relDataCount
83+
, numConvert <$> margin
84+
, ack
85+
)
86+
87+
-- XXX: These functions are outside of the circuit definition to avoid shadowing :-/
88+
89+
goState :: State -> (IsEnabled, RelDataCount 32, Signed 32, Ack) -> State
90+
goState InReset _ = Idle 0
91+
goState s@Idle{} (False, _relDataCount, _margin, _ack) = s
92+
goState s@Idle{adjustments} (_isEnabled, relDataCount, margin, _ack)
93+
| negate margin <= relDataCount && relDataCount <= margin = s -- within margin
94+
| otherwise = Adjusting adjustments (negate relDataCount) -- outside margin
95+
goState s@Adjusting{adjustments, adjustment} (_isEnabled, _relDataCount, _margin, ~(Ack ack))
96+
| ack = Waiting{adjustments = adjustments + adjustment, wait = 0}
97+
| otherwise = s
98+
goState Waiting{adjustments, wait} (_isEnabled, _relDataCount, _margin, _ack)
99+
| wait == maxBound = Idle adjustments
100+
| otherwise = Waiting{adjustments, wait = wait + 1}
101+
102+
goOutput :: State -> (Maybe (Signed 32), Signed 32, IsIdle)
103+
goOutput s = (adjustment_, adjustments_, isIdle)
104+
where
105+
-- Note! The "idle" output reflects the state of submissions, not the state of the
106+
-- state machine. I.e., idle means that there are no pending adjustments to the elastic
107+
-- buffer.
108+
isIdle = case s of
109+
InReset -> False
110+
Idle{} -> True
111+
Adjusting{} -> False
112+
Waiting{} -> True
113+
114+
adjustments_ = case s of
115+
InReset -> 0
116+
Idle{adjustments} -> adjustments
117+
Adjusting{adjustments} -> adjustments
118+
Waiting{adjustments} -> adjustments
119+
120+
adjustment_ = case s of
121+
InReset -> Nothing
122+
Idle{} -> Nothing
123+
Adjusting{adjustment} -> Just adjustment
124+
Waiting{} -> Nothing

0 commit comments

Comments
 (0)