/
SPI.hs
415 lines (384 loc) · 12 KB
/
SPI.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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
{-|
Copyright : (C) 2019, Foamspace corp
2022, Google LLC
License : BSD2 (see the file LICENSE)
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>
SPI master and slave core
-}
module Clash.Cores.SPI
( SPIMode(..)
-- * SPI master
, spiMaster
-- * SPI slave
, SPISlaveConfig(..)
, spiSlave
-- ** Vendor configured SPI slaves
, spiSlaveLatticeSBIO
, spiSlaveLatticeBB
) where
import Data.Maybe (fromMaybe, isJust)
import Test.QuickCheck as QC
import Clash.Prelude
import Clash.Sized.Internal.BitVector
import Clash.Cores.LatticeSemi.ICE40.IO
import Clash.Cores.LatticeSemi.ECP5.IO
-- | SPI mode
--
-- * CPOL: Clock POLarity
-- * CPHA: Clock PHAse
data SPIMode
= SPIMode0
-- ^ CPOL = 0, CPHA = 0
--
-- Clock is idle low, so the leading edge is rising.
-- Phase is low, so data is sampled on the leading edge.
--
-- TL;DR Data is shifted on the rising edge of SCK.
| SPIMode1
-- ^ CPOL = 0, CPHA = 1
--
-- Clock is idle low, so the leading edge is rising.
-- Phase is high, so data is sampled on the trailing edge.
--
-- TL;DR Data is shifted on the falling edge of SCK.
| SPIMode2
-- ^ CPOL = 1, CPHA = 0
--
-- Clock is idle high, so the leading edge is falling.
-- Phase is low, so data is sampled on the leading edge.
--
-- TL;DR Data is shifted on the falling edge of SCK.
| SPIMode3
-- ^ CPOL = 1, CPHA = 1
--
-- Clock is idle high, so the leading edge is falling.
-- Phase is high, so data is sampled on the trailing edge.
--
-- TL;DR Data is shifted on the rising edge of SCK.
deriving (Eq, Show)
instance Arbitrary SPIMode where
arbitrary = QC.elements [SPIMode0, SPIMode1, SPIMode2, SPIMode3]
idleOnLow :: SPIMode -> Bool
idleOnLow SPIMode0 = True
idleOnLow SPIMode1 = True
idleOnLow _ = False
sampleOnRising :: SPIMode -> Bool
sampleOnRising SPIMode0 = True
sampleOnRising SPIMode3 = True
sampleOnRising _ = False
sampleOnLeading :: SPIMode -> Bool
sampleOnLeading SPIMode0 = True
sampleOnLeading SPIMode2 = True
sampleOnLeading _ = False
sampleOnTrailing :: SPIMode -> Bool
sampleOnTrailing = not . sampleOnLeading
data SPISlaveConfig ds dom
= SPISlaveConfig
{ spiSlaveConfigMode :: SPIMode
-- ^ SPI mode
, spiSlaveConfigLatch :: Bool
-- ^ Whether to latch the SPI pins
--
-- Recommended:
--
-- * Set to /True/ when core clock is /more/ than twice as fast as the SCK
-- Clock: 2*SCK < Core
--
-- * Set to /False/ when core clock is twice as fast, or as fast, as the SCK
, spiSlaveConfigBuffer
:: BiSignalIn ds dom 1
-> Signal dom Bool
-> Signal dom Bit
-> BiSignalOut ds dom 1
-- ^ Tri-state buffer: first argument is the inout pin, second
-- argument is the output enable, third argument is the value to
-- output when the enable is high
}
-- | SPI capture and shift logic that is shared between slave and master
spiCommon
:: forall n dom
. (HiddenClockResetEnable dom, KnownNat n, 1 <= n)
=> SPIMode
-> Signal dom Bool
-- ^ Slave select
-> Signal dom Bit
-- ^ Slave: MOSI; Master: MISO
-> Signal dom Bool
-- ^ SCK
-> Signal dom (BitVector n)
-> ( Signal dom Bit -- Slave: MISO; Master: MOSI
, Signal dom Bool -- Acknowledge start of transfer
, Signal dom (Maybe (BitVector n))
)
spiCommon mode ssI msI sckI dinI =
mooreB go cvt ( 0 :: Index n -- cntR
, False -- cntOldR
, undefined -- sckOldR
, deepErrorX "no initial dataInR"
, deepErrorX "no initial dataOutR"
, False -- ackR
, False -- doneR
)
(ssI,msI,sckI,dinI)
where
cvt (_,_,_,dataInQ,dataOutQ,ackQ,doneQ) =
( head dataOutQ
, ackQ
, if doneQ
then Just (pack dataInQ)
else Nothing
)
go (cntQ,cntOldQ,sckOldQ,dataInQ,dataOutQ,_,_) (ss,ms,sck,din) =
(cntD,cntOldD,sck,dataInD,dataOutD,ackD,doneD)
where
cntD
| ss = 0
| sampleSck = if cntQ == maxBound then 0 else cntQ + 1
| otherwise = cntQ
dataInD
| ss = unpack undefined#
| sampleSck = tail @(n-1) dataInQ :< ms
| otherwise = dataInQ
dataOutD
| ss || (sampleOnTrailing mode && sampleSck && cntQ == maxBound) = unpack din
| shiftSck = if sampleOnTrailing mode && cntQ == 0
then dataOutQ
else tail @(n-1) dataOutQ :< unpack undefined#
| otherwise = dataOutQ
-- The counter is updated during the capture moment
-- But we need to know during the shift moment whether the counter
-- overflowed to determine whether we need to load new data or shift
-- the existing data. That's why we capture it here.
cntOldD | not ss && shiftSck = cntQ == maxBound
| otherwise = cntOldQ
ackD = not ss && shiftSck && cntQ == 1
doneD = not ss && sampleSck && cntQ == maxBound
(sampleSck, shiftSck)
| sampleOnRising mode = (risingSck, fallingSck)
| otherwise = (fallingSck, risingSck)
where
risingSck = not sckOldQ && sck
fallingSck = sckOldQ && not sck
-- | SPI slave configurable SPI mode and tri-state buffer
spiSlave
:: forall n ds dom
. (HiddenClockResetEnable dom, KnownNat n, 1 <= n)
=> SPISlaveConfig ds dom
-- ^ Configure SPI mode and tri-state buffer
-> Signal dom Bool
-- ^ Serial Clock (SCLK)
-> Signal dom Bit
-- ^ Master Output Slave Input (MOSI)
-> BiSignalIn ds dom 1
-- ^ Master Input Slave Output (MISO)
--
-- Inout port connected to the tri-state buffer for the MISO
-> Signal dom Bool
-- ^ Slave select (SS)
-> Signal dom (BitVector n)
-- ^ Data to send from master to slave
--
-- Input is latched the moment slave select goes low
-> ( BiSignalOut ds dom 1
, Signal dom Bool
, Signal dom (Maybe (BitVector n)))
-- ^ Parts of the tuple:
--
-- 1. The "out" part of the inout port of the MISO; used only for simulation.
--
-- 2. (Maybe) the word send by the master
spiSlave (SPISlaveConfig mode latch buf) sclk mosi bin ss din =
let ssL = if latch then delay undefined ss else ss
mosiL = if latch then delay undefined mosi else mosi
sclkL = if latch then delay undefined sclk else sclk
(miso, ack, dout) = spiCommon mode ssL mosiL sclkL din
bout = buf bin (not <$> ssL) miso
in (bout, ack, dout)
-- | SPI master configurable in the SPI mode and clock divider
--
-- Adds latch to MISO line if the (half period) clock divider is
-- set to 2 or higher.
spiMaster
:: forall n halfPeriod waitTime dom
. ( HiddenClockResetEnable dom
, KnownNat n
, 1 <= n
, 1 <= halfPeriod
, 1 <= waitTime )
=> SPIMode
-- ^ SPI Mode
-> SNat halfPeriod
-- ^ Clock divider (half period)
--
-- If set to two or higher, the MISO line will be latched
-> SNat waitTime
-- ^ (core clock) cycles between de-asserting slave-select and start of
-- the SPI clock
-> Signal dom (Maybe (BitVector n))
-- ^ Data to send from master to slave, transmission starts when receiving
-- /Just/ a value
-> Signal dom Bit
-- ^ Master Input Slave Output (MISO)
-> ( Signal dom Bool -- SCK
, Signal dom Bit -- MOSI
, Signal dom Bool -- SS
, Signal dom Bool -- Busy
, Signal dom Bool -- Acknowledge
, Signal dom (Maybe (BitVector n)) -- Data: Slave -> Master
)
-- ^ Parts of the tuple:
--
-- 1. Serial Clock (SCLK)
-- 2. Master Output Slave Input (MOSI)
-- 3. Slave select (SS)
-- 4. Busy signal indicating that a transmission is in progress, new words on
-- the data line will be ignored when /True/
-- 5. (Maybe) the word send from the slave to the master
spiMaster mode fN fW din miso =
let (mosi, ack, dout) = spiCommon mode ssL misoL sclkL
(fromMaybe undefined# <$> din)
latch = snatToInteger fN /= 1
ssL = if latch then delay undefined ss else ss
misoL = if latch then delay undefined miso else miso
sclkL = if latch then delay undefined sclk else sclk
(ss, sclk, busy) = spiGen mode fN fW din
in (sclk, mosi, ss, busy, ack, dout)
-- | Generate slave select and SCK
spiGen
:: forall n halfPeriod waitTime dom
. ( HiddenClockResetEnable dom
, KnownNat n
, 1 <= n
, 1 <= halfPeriod
, 1 <= waitTime )
=> SPIMode
-> SNat halfPeriod
-> SNat waitTime
-> Signal dom (Maybe (BitVector n))
-> ( Signal dom Bool
, Signal dom Bool
, Signal dom Bool
)
spiGen mode SNat SNat =
unbundle . moore go cvt (0 :: Index (2*n),False,Idle @halfPeriod @waitTime)
where
cvt (_, sck, st) =
( st == Idle
, if idleOnLow mode then sck else not sck
, st /= Idle
)
go (cntQ,sckQ,stQ) din = (cntD,sckD,stD)
where
stD = case stQ of
Idle
| isJust din -> Wait maxBound
Wait 0 -> Transfer 0
Wait w -> Wait (w - 1)
Transfer n
| n /= maxBound -> Transfer (n+1)
| cntQ == maxBound -> Finish
| otherwise -> Transfer 0
Finish -> Idle
_ -> stQ
cntD = case stQ of
Transfer n
| n == maxBound -> if cntQ == maxBound then 0 else cntQ+1
| otherwise -> cntQ
_ -> 0
sckD = case stQ of
Transfer n | n == maxBound -> not sckQ
_ -> sckQ
data SPIMasterState halfPeriod waitTime
= Idle
| Wait (Index waitTime)
| Transfer (Index halfPeriod)
| Finish
deriving (Eq, Generic, NFDataX)
-- | SPI slave configurable SPI mode, using the SB_IO tri-state buffer
-- found on Lattice ICE40 Semiconductor FPGAs
spiSlaveLatticeSBIO
:: forall dom n
. (HiddenClockResetEnable dom, 1 <= n, KnownNat n)
=> SPIMode
-- ^ SPI Mode
-> Bool
-- ^ Whether to latch the SPI pins
--
-- Recommended:
--
-- * Set to /True/ when core clock is /more/ than twice as fast as the SCK
-- Clock: 2*SCK < Core
--
-- * Set to /False/ when core clock is twice as fast, or as fast, as the SCK
-> Signal dom Bool
-- ^ Serial Clock (SCLK)
-> Signal dom Bit
-- ^ Master Output Slave Input (MOSI)
-> BiSignalIn 'Floating dom 1
-- ^ Master Input Slave Output (MISO)
--
-- Inout port connected to the tri-state buffer for the MISO
-> Signal dom Bool
-- ^ Slave select (SS)
-> Signal dom (BitVector n)
-- ^ Data to send from slave to master
--
-- Input is latched the moment slave select goes low
-> ( BiSignalOut 'Floating dom 1
, Signal dom Bool
, Signal dom (Maybe (BitVector n)))
-- ^ Parts of the tuple:
--
-- 1. The "out" part of the inout port of the MISO; used only for simulation.
--
-- 2. (Maybe) the word send by the master
spiSlaveLatticeSBIO mode latchSPI =
spiSlave (SPISlaveConfig mode latchSPI sbioX)
where
sbioX bin en dout = bout
where
(bout,_,_) = sbio 0b101001 bin (pure 0) dout (pure undefined) en
-- | SPI slave configurable SPI mode, using the BB tri-state buffer
-- found on Lattice ECP5 Semiconductor FPGAs
spiSlaveLatticeBB
:: forall dom n
. (HiddenClockResetEnable dom, 1 <= n, KnownNat n)
=> SPIMode
-- ^ SPI Mode
-> Bool
-- ^ Whether to latch the SPI pins
--
-- Recommended:
--
-- * Set to /True/ when core clock is /more/ than twice as fast as the SCK
-- Clock: 2*SCK < Core
--
-- * Set to /False/ when core clock is twice as fast, or as fast, as the SCK
-> Signal dom Bool
-- ^ Serial Clock (SCLK)
-> Signal dom Bit
-- ^ Master Output Slave Input (MOSI)
-> BiSignalIn 'Floating dom 1
-- ^ Master Input Slave Output (MISO)
--
-- Inout port connected to the tri-state buffer for the MISO
-> Signal dom Bool
-- ^ Slave select (SS)
-> Signal dom (BitVector n)
-- ^ Data to send from slave to master
--
-- Input is latched the moment slave select goes low
-> ( BiSignalOut 'Floating dom 1
, Signal dom Bool
, Signal dom (Maybe (BitVector n)))
-- ^ Parts of the tuple:
--
-- 1. The "out" part of the inout port of the MISO; used only for simulation.
--
-- 2. (Maybe) the word send by the master
spiSlaveLatticeBB mode latchSPI =
spiSlave (SPISlaveConfig mode latchSPI bbX)
where
bbX bin en dout = bout
where
(bout,_) = bidirectionalBuffer (toEnable en) bin dout