Skip to content

Commit

Permalink
Change fifo implementation
Browse files Browse the repository at this point in the history
We now have a fifo implementation that exposes metadata called `fifoWithMeta`.
The previous implementation of `fifo` has been replaced with a new one.
The only difference is that the new implementation does not implement
bypassing when the fifo is empty.
  • Loading branch information
lmbollen committed May 2, 2023
1 parent f7ea048 commit 08a2f2b
Showing 1 changed file with 76 additions and 56 deletions.
132 changes: 76 additions & 56 deletions src/Protocols/Df.hs
Original file line number Diff line number Diff line change
Expand Up @@ -635,64 +635,84 @@ registerBwd
ra1 = if oAck then iDat else ra0
s = if Maybe.isNothing (dataToMaybe rb) || iAck then (NoData, ra1) else (ra1, rb)

-- | A fifo buffer with user-provided depth. Uses blockram to store data. Can
-- handle simultaneous write and read (full throughput rate).
-- | A generic First-In-First-Out (FIFO) circuit with a specified depth.
-- The circuit does not feature bypassing for an empty fifo.
-- When the reset is high or the enable is low, the output will be `Nothing` and the ready signal will be `False`.
fifo ::
forall dom a depth.
(C.HiddenClockResetEnable dom, C.KnownNat depth, C.NFDataX a, 1 C.<= depth) =>
forall dom a depth .
(C.HiddenClockResetEnable dom, 1 C.<= depth, C.NFDataX a) =>
-- | The depth of the FIFO, should be at least 1.
C.SNat depth ->
Circuit (Df dom a) (Df dom a)
fifo fifoDepth = Circuit $ C.hideReset circuitFunction where

-- implemented using a fixed-size array
-- write location and read location are both stored
-- to write, write to current location and move one to the right
-- to read, read from current location and move one to the right
-- loop around from the end to the beginning if necessary

circuitFunction reset (inpA, inpB) = (otpA, otpB) where
-- initialize bram
brRead = C.readNew
(C.blockRamU C.NoClearOnReset fifoDepth (C.errorX "No reset function"))
brReadAddr brWrite
-- run the state machine (a mealy machine)
(brReadAddr, brWrite, otpA, otpB)
= C.unbundle
$ C.mealy machineAsFunction s0
$ C.bundle (brRead, C.unsafeToHighPolarity reset, inpA, inpB)

-- when reset is on, set state to initial state and output blank outputs
machineAsFunction _ (_, True, _, _) = (s0, (0, Nothing, Ack False, NoData))
machineAsFunction (rAddr0,wAddr0,amtLeft0) (brRead0, False, pushData, Ack popped) =
let -- potentially push an item onto blockram
maybePush = if amtLeft0 > 0 then dataToMaybe pushData else Nothing
brWrite = (wAddr0,) <$> maybePush
-- adjust write address and amount left
-- (output state machine doesn't see amountLeft')
(wAddr1, amtLeft1)
| Just _ <- maybePush = (C.satSucc C.SatWrap wAddr0, amtLeft0 - 1)
| otherwise = (wAddr0, amtLeft0)
-- if we're about to push onto an empty queue, we can pop immediately instead
(brRead1, amtLeft2)
| Just push <- maybePush, amtLeft0 == maxBound = (push, amtLeft1)
| otherwise = (brRead0, amtLeft0)
-- adjust blockram read address and amount left
(rAddr1, amtLeft3)
| amtLeft2 < maxBound && popped = (C.satSucc C.SatWrap rAddr0, amtLeft1 + 1)
| otherwise = (rAddr0, amtLeft1)
brReadAddr = rAddr1
-- return our new state and outputs
otpAck = Maybe.isJust maybePush
otpDat = if amtLeft2 < maxBound then Data brRead1 else NoData
in ((rAddr1, wAddr1, amtLeft3), (brReadAddr, brWrite, Ack otpAck, otpDat))

-- initial state
-- (next read address in bram, next write address in bram, space left in bram)
-- Addresses only go from 0 to depth-1.
-- Space left goes from 0 to depth because the fifo could be empty
-- (space left = depth) or full (space left = 0).
s0 :: (C.Index depth, C.Index depth, C.Index (depth C.+ 1))
s0 = (0, 0, maxBound)
-- | Consumes `Df dom a`, produces `Df dom a` along with ready signal and data count.
Circuit (Df dom a) (Df dom a)
fifo depth = Circuit go
where
go (m2sL, s2mR) = (s2mL, m2sR)
where
(s2mL, (m2sR, _, _)) = toSignals (fifoWithMeta depth) (m2sL, (s2mR, CSignal (C.pure ()), CSignal (C.pure ())))

-- | State record for the FIFO circuit.
data FifoState depth = FifoState
{ readPointer :: C.Index depth
, dataCount :: C.Index (depth C.+ 1)
} deriving (Generic, C.NFDataX)

-- | A generic First-In-First-Out (FIFO) circuit with a specified depth that exposes meta information such as a ready signal and data count.
-- At least one cycle latency.
-- When the reset is high or the enable is low, the output will be `Nothing` and the ready signal will be `False`.
fifoWithMeta ::
forall dom a depth .
(C.HiddenClockResetEnable dom, 1 C.<= depth, C.NFDataX a) =>
-- | The depth of the FIFO, should be at least 1.
C.SNat depth ->
-- | Consumes `Df dom a`, produces `Df dom a` along with ready signal and data count.
Circuit (Df dom a) (Df dom a, CSignal dom C.Bool, CSignal dom (C.Index (depth C.+ 1)))
fifoWithMeta depth@C.SNat = Circuit circuitFunction
where
circuitFunction (fifoIn, (readyIn, _, _)) = (Ack <$> readyOut, (fifoOut, CSignal readyOut, CSignal dataCount))
where
circuitActive = C.unsafeToLowPolarity C.hasReset C..&&. C.fromEnable C.hasEnable
bramOut = C.readNew (C.blockRamU C.NoClearOnReset depth (C.errorX "No reset function")) readAddr writeOp
(readAddr, writeOp, fifoOut, readyOut, dataCount) =
C.mealyB go initialState (circuitActive, fifoIn, readyIn, bramOut)

-- Initial state of the FIFO
initialState = FifoState
{ readPointer = 0
, dataCount = 0
}
go ::
FifoState depth ->
(C.Bool, Data a, Ack, a) ->
( FifoState depth
, (C.Index depth, C.Maybe (C.Index depth, a), Data a, C.Bool, C.Index (depth C.+ 1)))
go state@FifoState{..} (False, _, _,_) = (state,(readPointer, Nothing, NoData, False, dataCount))
go FifoState{..} (True, dataToMaybe -> fifoIn, Ack readyIn, bramOut) = (nextState, output)
where
full = dataCount == maxBound
fifoEmpty = dataCount == 0
writePointer = C.satAdd C.SatWrap readPointer $ C.resize dataCount

readSuccess = not fifoEmpty && readyIn
writeSuccess = not full && Maybe.isJust fifoIn

readPointerNext = if readSuccess then C.satSucc C.SatWrap readPointer else readPointer
writeOpGo = if writeSuccess then (writePointer,) <$> fifoIn else Nothing
fifoOutGo = if fifoEmpty then NoData else Data bramOut

dataCountNext = dataCountDx dataCount
dataCountDx = case (writeSuccess, readSuccess) of
(True, False) -> C.satSucc C.SatError
(False, True) -> C.satPred C.SatError
_ -> id

nextState = FifoState
{ readPointer = readPointerNext
, dataCount = dataCountNext
}

output = (readPointerNext, writeOpGo, fifoOutGo, not full, dataCount)


--------------------------------- SIMULATE -------------------------------------

Expand Down

0 comments on commit 08a2f2b

Please sign in to comment.