-
Notifications
You must be signed in to change notification settings - Fork 147
/
DcFifo.hs
267 lines (227 loc) · 8.67 KB
/
DcFifo.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
{-|
Copyright : (C) 2022, Google Inc,
License : BSD2 (see the file LICENSE)
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>
Support for the [FIFO Generator v13.2](https://docs.xilinx.com/v/u/en-US/pg057-fifo-generator).
It is necessary to read the product guide linked above in order to effectively
use the FIFO. The product guide also documents how to interface the FIFO
correctly. To aid comprehension, the signals in this module also mention the
name the product guide uses for that signal like @this@.
Note that the behavior of the FIFO in Haskell simulation does not correspond
exactly to the behavior in HDL simulation, and that neither behaviors correspond
exactly to hardware. All the different behaviors are functionally correct and
equivalent when the usage guidelines in the product guide are observed.
Furthermore, the FIFO is configured as follows:
* /Native/ interface type
* /Independent clocks block RAM/ implementation
* 2 synchronization stages
* /Standard FIFO/ read mode
* /No/ output registers
* Reset pin /enabled/, reset synchronization /disabled/, safety circuit
/disabled/
(Note: the GUI will still say /Asynchronous Reset/, grayed out. This might be
confusing: the resets are actually synchronous.)
* Full flag reset value: /@0 (False)@/
(Note: this is actually not configurable for synchronous resets, it is
always @False@.)
* No /Dout reset value/
* Optional overflow flag: 'dcOverflow'
* Optional underflow flag: 'dcUnderflow'
* No other configurable status flags (no almost full, almost empty, write
acknowledge, valid, or programmable full\/empty flags)
* Optional write data count: 'dcWriteDataCount' (only full width is supported)
* Optional read data count: 'dcReadDataCount' (only full width is supported)
(The order of these items corresponds to Vivado's /Customize IP/ GUI dialog in
Vivado 2022.1.)
-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
module Clash.Cores.Xilinx.DcFifo
( -- ** Reset sequencing
--
-- $resetSeq
-- * Instantiating IP
dcFifo
, FifoOut(..)
-- * Customizing IP
, DcConfig(..)
, defConfig
-- * Helper type aliases
, Full
, Empty
, DataCount
) where
import Clash.Explicit.Prelude
import Clash.Signal.Internal (Signal (..), ClockAB (..), clockTicks)
import Data.Maybe (isJust)
import qualified Data.Sequence as Seq
import Data.Sequence (Seq)
import Data.String.Interpolate (__i)
import GHC.Stack (HasCallStack)
import Clash.Annotations.Primitive (Primitive (InlineYamlPrimitive))
import Clash.Cores.Xilinx.DcFifo.Internal.BlackBoxes
import Clash.Cores.Xilinx.DcFifo.Internal.Instances ()
import Clash.Cores.Xilinx.DcFifo.Internal.Types
{- $setup
>>> import Clash.Explicit.Prelude
-}
{- $resetSeq
The FIFO only supports synchronous resets ('Clash.Signal.Synchronous'). If the
read or write domain has an asynchronous reset signal, simulating the FIFO or
generating HDL for it will throw an error.
The FIFO does not need to be reset; it is possible to use the FIFO with its
reset inputs permanently deasserted. However, resetting the FIFO will flush
it, so resets do have a use. It is necessary to generate a proper reset
sequence; see the product guide for details. Read and write side need to be
reset together, both for at least one clock cycle. During the time where
either or both of the resets are asserted, do not perform write or read
operations to avoid unexpected behavior.
-}
-- | Default config. Read\/write data counts are enabled but over-\/underflow
-- are not.
--
-- Examples using a type-level argument to specify the depth:
--
-- >>> fifo = dcFifo (defConfig @5)
-- >>> fifo = dcFifo @5 defConfig
-- >>> fifo = dcFifo (defConfig @5){dcReadDataCount=False}
-- >>> fifo = dcFifo @5 defConfig{dcReadDataCount=False}
defConfig :: KnownNat depth => DcConfig depth
defConfig = DcConfig
{ dcDepth = SNat
, dcReadDataCount = True
, dcWriteDataCount = True
, dcOverflow = False
, dcUnderflow = False
}
-- | Xilinx dual clock FIFO
--
-- For an explanation about the type-level parameter @depth@, see 'DcConfig'.
--
-- If 'dcReadDataCount', 'dcUnderflow', 'dcOverflow', or 'dcWriteDataCount' are
-- disabled, the relevant signals will return 'XException'.
dcFifo ::
forall depth a write read .
( KnownDomain write
, KnownDomain read
, NFDataX a
, KnownNat depth
-- Number of elements should be between [2**4, 2**17] ~ [16, 131072].
, 4 <= depth
, depth <= 17
, HasCallStack
) =>
DcConfig depth ->
-- | Write clock, @wr_clk@
Clock write ->
-- | Synchronous write-side reset, @wr_rst@
Reset write ->
-- | Read clock, @rd_clk@
Clock read ->
-- | Synchronous read-side reset, @rd_rst@
Reset read ->
-- | Write data, @din@ and @wr_en@
Signal write (Maybe a) ->
-- | Read enable @rd_en@
Signal read Bool ->
FifoOut read write depth a
dcFifo DcConfig{..} wClk wRst rClk rRst writeData rEnable =
case (resetKind @write, resetKind @read) of
(SSynchronous, SSynchronous) ->
let
(wFull, wOver, wCnt, rEmpty, rUnder, rCnt, rData) =
go (clockTicks wClk rClk) mempty rstSignalR rEnable rstSignalW writeData
in FifoOut
wFull
(if dcOverflow
then register wClk wRst enableGen False wOver
else errorX "Overflow disabled")
(if dcWriteDataCount then wCnt else errorX "Write data count disabled")
rEmpty
(if dcUnderflow
then register rClk rRst enableGen False rUnder
else errorX "Underflow disabled")
(if dcReadDataCount then rCnt else errorX "Read data count disabled")
(register rClk rRst enableGen (deepErrorX "No initial value") rData)
_ -> error $ show 'dcFifo <> " only supports synchronous resets"
where
rstSignalR = unsafeToActiveHigh rRst
rstSignalW = unsafeToActiveHigh wRst
fifoSize = natToNum @(2 ^ depth - 1) @Int
dataCount = fromIntegral . Seq.length
go ::
[ClockAB] ->
Seq a ->
Signal read Bool -> -- reset
Signal read Bool -> -- read enabled
Signal write Bool -> -- reset
Signal write (Maybe a) -> -- write data
( Signal write Full
, Signal write Bool
, Signal write (DataCount depth)
, Signal read Empty
, Signal read Bool
, Signal read (DataCount depth)
, Signal read a
)
go (ClockA:ticks) = goWrite ticks
go (ClockB:ticks) = goRead ticks
go (ClockAB:ticks) = go (ClockB:ClockA:ticks)
go [] = error "dcFifo.go: `ticks` should have been an infinite list"
goWrite ticks _q rstR rEna (True :- rstWNext) (_ :- wData) =
-- The register will discard the @wOver@ sample
(False :- preFull, undefined :- preOver, 0 :- preWCnt, fifoEmpty, under, rCnt, rData)
where
(preFull, preOver, preWCnt, fifoEmpty, under, rCnt, rData) =
go ticks mempty rstR rEna rstWNext wData
goWrite ticks q rstR rEna (_ :- rstW) (wDat :- wDats1) =
(full, over, wCnt, fifoEmpty, under, rCnt, rData)
where
(preFull, preOver, preWCnt, fifoEmpty, under, rCnt, rData) =
go ticks q' rstR rEna rstW wDats1
wCnt = dataCount q :- preWCnt
full = (Seq.length q == fifoSize) :- preFull
(q', over) =
if Seq.length q < fifoSize
then (case wDat of { Just x -> x Seq.<| q ; _ -> q }, False :- preOver)
else (q, isJust wDat :- preOver)
goRead ticks _q (True :- rstRNext) (_ :- rEnas1) rstW wData =
(full, over, wCnt, fifoEmpty, under, rCnt, rData)
where
-- The register will discard the sample
rData = undefined :- preRData
fifoEmpty = True :- preEmpty
rCnt = 0 :- preRCnt
-- The register will discard the sample
under = undefined :- preUnder
(full, over, wCnt, preEmpty, preUnder, preRCnt, preRData) =
go ticks mempty rstRNext rEnas1 rstW wData
goRead ticks q (_ :- rstRNext) (rEna :- rEnas1) rstW wData =
(full, over, wCnt, fifoEmpty, under, rCnt, rData)
where
rCnt = dataCount q :- preRCnt
fifoEmpty = (Seq.length q == 0) :- preEmpty
rData = nextData :- preRData
(full, over, wCnt, preEmpty, preUnder, preRCnt, preRData) =
go ticks q' rstRNext rEnas1 rstW wData
(q', nextData, under) =
if rEna
then
case Seq.viewr q of
Seq.EmptyR -> (q, deepErrorX "FIFO empty", True :- preUnder)
qData Seq.:> qDatum -> (qData, qDatum, False :- preUnder)
else (q, deepErrorX "Enable off", False :- preUnder)
-- See: https://github.com/clash-lang/clash-compiler/pull/2511
{-# CLASH_OPAQUE dcFifo #-}
{-# ANN dcFifo (
let primName = 'dcFifo
tfName = 'dcFifoBBF
in InlineYamlPrimitive [minBound..] [__i|
BlackBoxHaskell:
name: #{primName}
templateFunction: #{tfName}
workInfo: Always
|]) #-}