diff --git a/clash-cores/src/Clash/Cores/SPI.hs b/clash-cores/src/Clash/Cores/SPI.hs index cc2da9c3ac..bdf0cd6592 100644 --- a/clash-cores/src/Clash/Cores/SPI.hs +++ b/clash-cores/src/Clash/Cores/SPI.hs @@ -9,10 +9,16 @@ module Clash.Cores.SPI ( SPIMode(..) -- * SPI master + , SpiMasterIn(..) + , SpiMasterOut(..) , spiMaster + , spiMaster1 -- * SPI slave + , SpiSlaveIn(..) + , SpiSlaveOut(..) , SPISlaveConfig(..) , spiSlave + , spiSlave1 -- ** Vendor configured SPI slaves , spiSlaveLatticeSBIO , spiSlaveLatticeBB @@ -26,6 +32,53 @@ import Clash.Sized.Internal.BitVector import Clash.Cores.LatticeSemi.ICE40.IO import Clash.Cores.LatticeSemi.ECP5.IO +import Clash.Class.HasDomain + +-- | Input signals of an SPI subordinate. +data SpiSlaveIn (ds :: BiSignalDefault) (dom :: Domain) (misoLanes :: Nat) (mosiLanes :: Nat) = + SpiSlaveIn { + spiSlaveMosi :: "MOSI" ::: Signal dom (BitVector mosiLanes), + -- ^ Master-out, slave-in + spiSlaveMisoIn :: "MISO" ::: BiSignalIn ds dom misoLanes, + -- ^ Master-in, slave-out + spiSlaveSck :: "SCK" ::: Signal dom Bit, + -- ^ Serial Clock + spiSlaveSs :: "SS" ::: Signal dom Bit + -- ^ Slave select + } + +type instance TryDomain t (SpiSlaveIn ds dom misoLanes mosiLanes) = 'Found dom + +-- | Output signals of an SPI subordinate. +data SpiSlaveOut (ds :: BiSignalDefault) (dom :: Domain) (misoLanes :: Nat) (mosiLanes :: Nat) = + SpiSlaveOut { + spiSlaveMisoOut :: "MISO" ::: BiSignalOut ds dom misoLanes + -- ^ Master-in, slave-out + } + +type instance TryDomain t (SpiSlaveOut ds dom misoLanes mosiLanes) = 'Found dom + +-- | Output signals of an SPI master. +data SpiMasterOut (dom :: Domain) (misoLanes :: Nat) (mosiLanes :: Nat) = + SpiMasterOut { + spiMasterMosi :: "MOSI" ::: Signal dom (BitVector mosiLanes), + -- ^ Master-out, slave-in + spiMasterSck :: "SCK" ::: Signal dom Bit, + -- ^ Serial Clock + spiMasterSs :: "SS" ::: Signal dom Bit + -- ^ Slave select + } + +type instance TryDomain t (SpiMasterOut dom misoLanes mosiLanes) = 'Found dom + +-- | Input signals of an SPI master. +data SpiMasterIn (dom :: Domain) (misoLanes :: Nat) (mosiLanes :: Nat) = + SpiMasterIn { + spiMasterMiso :: "MISO" ::: Signal dom (BitVector misoLanes) + -- ^ Master-in, slave-out + } + +type instance TryDomain t (SpiMasterIn dom misoLanes mosiLanes) = 'Found dom -- | SPI mode -- @@ -83,7 +136,7 @@ sampleOnLeading _ = False sampleOnTrailing :: SPIMode -> Bool sampleOnTrailing = not . sampleOnLeading -data SPISlaveConfig ds dom +data SPISlaveConfig ds dom (misoW :: Nat) (mosiW :: Nat) = SPISlaveConfig { spiSlaveConfigMode :: SPIMode -- ^ SPI mode @@ -97,10 +150,10 @@ data SPISlaveConfig ds dom -- -- * Set to /False/ when core clock is twice as fast, or as fast, as the SCK , spiSlaveConfigBuffer - :: BiSignalIn ds dom 1 + :: BiSignalIn ds dom misoW -> Signal dom Bool - -> Signal dom Bit - -> BiSignalOut ds dom 1 + -> Signal dom (BitVector misoW) + -> BiSignalOut ds dom misoW -- ^ 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 @@ -108,19 +161,23 @@ data SPISlaveConfig ds dom -- | SPI capture and shift logic that is shared between slave and master spiCommon - :: forall n dom - . (HiddenClockResetEnable dom, KnownNat n, 1 <= n) + :: forall n dom inW outW + . ( HiddenClockResetEnable dom + , KnownNat inW + , KnownNat outW + , KnownNat n + , 1 <= n ) => SPIMode -> Signal dom Bool -- ^ Slave select - -> Signal dom Bit + -> Signal dom (BitVector inW) -- ^ 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)) + -> Signal dom (Vec outW (BitVector n)) + -> ( Signal dom (BitVector outW) -- Slave: MISO; Master: MOSI + , Signal dom Bool -- Acknowledge start of transfer + , Signal dom (Maybe (Vec inW (BitVector n))) ) spiCommon mode ssI msI sckI dinI = mooreB go cvt ( 0 :: Index n -- cntR @@ -134,13 +191,16 @@ spiCommon mode ssI msI sckI dinI = (ssI,msI,sckI,dinI) where cvt (_,_,_,dataInQ,dataOutQ,ackQ,doneQ) = - ( head dataOutQ + ( v2bv $ map head dataOutQ , ackQ , if doneQ - then Just (pack dataInQ) + then Just (map v2bv dataInQ) else Nothing ) + go :: (Index n, Bool, Bool, Vec inW (Vec n Bit), Vec outW (Vec n Bit), Bool, Bool) + -> (Bool, BitVector inW, Bool, Vec outW (BitVector n)) + -> (Index n, Bool, Bool, Vec inW (Vec n Bit), Vec outW (Vec n Bit), Bool, Bool) go (cntQ,cntOldQ,sckOldQ,dataInQ,dataOutQ,_,_) (ss,ms,sck,din) = (cntD,cntOldD,sck,dataInD,dataOutD,ackD,doneD) where @@ -149,16 +209,18 @@ spiCommon mode ssI msI sckI dinI = | sampleSck = if cntQ == maxBound then 0 else cntQ + 1 | otherwise = cntQ + dataInD :: Vec inW (Vec n Bit) dataInD | ss = unpack undefined# - | sampleSck = tail @(n-1) dataInQ :< ms + | sampleSck = zipWith (\d m -> tail @(n-1) d :< m) dataInQ (bv2v ms) | otherwise = dataInQ + dataOutD :: Vec outW (Vec n Bit) dataOutD - | ss || (sampleOnTrailing mode && sampleSck && cntQ == maxBound) = unpack din + | ss || (sampleOnTrailing mode && sampleSck && cntQ == maxBound) = fmap bv2v din | shiftSck = if sampleOnTrailing mode && cntQ == 0 then dataOutQ - else tail @(n-1) dataOutQ :< unpack undefined# + else map (\d -> tail @(n-1) d :< unpack undefined#) dataOutQ | otherwise = dataOutQ -- The counter is updated during the capture moment @@ -180,47 +242,74 @@ spiCommon mode ssI msI sckI dinI = -- | SPI slave configurable SPI mode and tri-state buffer spiSlave - :: forall n ds dom - . (HiddenClockResetEnable dom, KnownNat n, 1 <= n) - => SPISlaveConfig ds dom + :: forall n ds dom misoW mosiW + . ( HiddenClockResetEnable dom + , KnownNat n + , 1 <= n + , KnownNat misoW + , KnownNat mosiW ) + => SPISlaveConfig ds dom misoW mosiW -- ^ 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 + -> SpiSlaveIn ds dom misoW mosiW + -- ^ SPI interface + -> Signal dom (Vec misoW (BitVector n)) + -- ^ Data to send from slave to master. -- -- Input is latched the moment slave select goes low - -> ( BiSignalOut ds dom 1 + -> ( SpiSlaveOut ds dom misoW mosiW , Signal dom Bool - , Signal dom (Maybe (BitVector n))) + , Signal dom (Maybe (Vec mosiW (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 = + -- 2. the acknowledgement for the data sent from the master to the slave. + -- + -- 2. (Maybe) the word sent by the master +spiSlave (SPISlaveConfig mode latch buf) (SpiSlaveIn mosi bin sclk 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) + (miso, ack, dout) = spiCommon mode (bitToBool <$> ssL) mosiL (bitToBool <$> sclkL) din + bout = buf bin (not . bitToBool <$> ssL) miso + in (SpiSlaveOut bout, ack, dout) + +spiSlave1 + :: forall n ds dom + . ( HiddenClockResetEnable dom + , KnownNat n + , 1 <= n ) + => SPISlaveConfig ds dom 1 1 + -- ^ Configure SPI mode and tri-state buffer + -> SpiSlaveIn ds dom 1 1 + -- ^ SPI interface + -> Signal dom (BitVector n) + -- ^ Data to send from slave to master. + -- + -- Input is latched the moment slave select goes low + -> ( SpiSlaveOut ds dom 1 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. the acknowledgement for the data sent from the master to the slave. + -- + -- 2. (Maybe) the word sent by the master +spiSlave1 config spiIn dout = + let (spiOut, ack, din) = spiSlave config spiIn (singleton <$> dout) + in (spiOut, ack, fmap head <$> din) -- | 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 + :: forall n halfPeriod waitTime dom misoW mosiW . ( HiddenClockResetEnable dom + , KnownNat misoW + , KnownNat mosiW , KnownNat n , 1 <= n , 1 <= halfPeriod @@ -234,17 +323,14 @@ spiMaster -> SNat waitTime -- ^ (core clock) cycles between de-asserting slave-select and start of -- the SPI clock - -> Signal dom (Maybe (BitVector n)) + -> Signal dom (Maybe (Vec mosiW (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 + -> SpiMasterIn dom misoW mosiW + -> ( SpiMasterOut dom misoW mosiW , Signal dom Bool -- Busy , Signal dom Bool -- Acknowledge - , Signal dom (Maybe (BitVector n)) -- Data: Slave -> Master + , Signal dom (Maybe (Vec misoW (BitVector n))) -- Data: Slave -> Master ) -- ^ Parts of the tuple: -- @@ -254,28 +340,60 @@ spiMaster -- 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 = +spiMaster mode fN fW din (SpiMasterIn miso) = let (mosi, ack, dout) = spiCommon mode ssL misoL sclkL - (fromMaybe undefined# <$> din) + (fromMaybe (repeat 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) + in (SpiMasterOut mosi (boolToBit <$> sclk) (boolToBit <$> ss), busy, ack, dout) + +-- | SPI master with single-bit MISO and MOSI width. +spiMaster1 + :: 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 + -> SpiMasterIn dom 1 1 + -> ( SpiMasterOut dom 1 1 + , Signal dom Bool -- Busy + , Signal dom Bool -- Acknowledge + , Signal dom (Maybe (BitVector n)) -- Data: Slave -> Master + ) +spiMaster1 mode halfPeriod waitTime dout spiIn = + let (spiOut, busy, ack, din) = + spiMaster mode halfPeriod waitTime (fmap singleton <$> dout) spiIn + in (spiOut, busy, ack, fmap head <$> din) -- | Generate slave select and SCK spiGen - :: forall n halfPeriod waitTime dom + :: forall n halfPeriod waitTime dom outW . ( HiddenClockResetEnable dom , KnownNat n + , KnownNat outW , 1 <= n , 1 <= halfPeriod , 1 <= waitTime ) => SPIMode -> SNat halfPeriod -> SNat waitTime - -> Signal dom (Maybe (BitVector n)) + -> Signal dom (Maybe (Vec outW (BitVector n))) -> ( Signal dom Bool , Signal dom Bool , Signal dom Bool @@ -339,21 +457,12 @@ spiSlaveLatticeSBIO -- 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) + -> SpiSlaveIn 'Floating dom 1 1 -> Signal dom (BitVector n) -- ^ Data to send from slave to master -- -- Input is latched the moment slave select goes low - -> ( BiSignalOut 'Floating dom 1 + -> ( SpiSlaveOut 'Floating dom 1 1 , Signal dom Bool , Signal dom (Maybe (BitVector n))) -- ^ Parts of the tuple: @@ -362,11 +471,11 @@ spiSlaveLatticeSBIO -- -- 2. (Maybe) the word send by the master spiSlaveLatticeSBIO mode latchSPI = - spiSlave (SPISlaveConfig mode latchSPI sbioX) + spiSlave1 (SPISlaveConfig mode latchSPI sbioX) where sbioX bin en dout = bout where - (bout,_,_) = sbio 0b101001 bin (pure 0) dout (pure undefined) en + (bout,_,_) = sbio 0b101001 bin (pure 0) (head . bv2v <$> dout) (pure undefined) en -- | SPI slave configurable SPI mode, using the BB tri-state buffer @@ -385,21 +494,12 @@ spiSlaveLatticeBB -- 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) + -> SpiSlaveIn 'Floating dom 1 1 -> Signal dom (BitVector n) -- ^ Data to send from slave to master -- -- Input is latched the moment slave select goes low - -> ( BiSignalOut 'Floating dom 1 + -> ( SpiSlaveOut 'Floating dom 1 1 , Signal dom Bool , Signal dom (Maybe (BitVector n))) -- ^ Parts of the tuple: @@ -408,8 +508,8 @@ spiSlaveLatticeBB -- -- 2. (Maybe) the word send by the master spiSlaveLatticeBB mode latchSPI = - spiSlave (SPISlaveConfig mode latchSPI bbX) + spiSlave1 (SPISlaveConfig mode latchSPI bbX) where bbX bin en dout = bout where - (bout,_) = bidirectionalBuffer (toEnable en) bin dout + (bout,_) = bidirectionalBuffer (toEnable en) bin (head . bv2v <$> dout) diff --git a/clash-cores/test/Test/Cores/Internal/SampleSPI.hs b/clash-cores/test/Test/Cores/Internal/SampleSPI.hs index 2ba19ae1f6..082033c5a5 100644 --- a/clash-cores/test/Test/Cores/Internal/SampleSPI.hs +++ b/clash-cores/test/Test/Cores/Internal/SampleSPI.hs @@ -72,16 +72,16 @@ sampleCycling genM genS divHalf wait mVals sVals mode latch duration = samples = sampleN (getDuration duration) $ bundle (mOut, mAck, sOut, sAck) slaveIn = genS clk rst sVals sAck - (misoZ, sAck, sOut) = + (SpiSlaveOut misoZ, sAck, sOut) = withClockResetEnable clk rst enableGen - (spiSlaveLatticeSBIO mode latch sclk mosi miso ss slaveIn) + (spiSlaveLatticeSBIO mode latch (SpiSlaveIn mosi miso sclk ss) slaveIn) miso = veryUnsafeToBiSignalIn misoZ masterIn = genM clk rst mVals mAck bp - (sclk, mosi, ss, bp, mAck, mOut) = + (SpiMasterOut mosi sclk ss, bp, mAck, mOut) = withClockResetEnable clk rst enableGen - (spiMaster mode divHalf wait masterIn (readFromBiSignal miso)) + (spiMaster1 mode divHalf wait masterIn (SpiMasterIn $ readFromBiSignal miso)) clk = systemClockGen rst = systemResetGen diff --git a/clash-cores/test/Test/Cores/SPI/MultiSlave.hs b/clash-cores/test/Test/Cores/SPI/MultiSlave.hs index 08e05f4ba5..9b5276348f 100644 --- a/clash-cores/test/Test/Cores/SPI/MultiSlave.hs +++ b/clash-cores/test/Test/Cores/SPI/MultiSlave.hs @@ -23,18 +23,20 @@ slaveAddressRotate . (KnownDomain dom, KnownNat n, 1 <= n) => Clock dom -> Reset dom - -> (Signal dom Bool, Signal dom Bool) - -> Vec n (Signal dom Bool) -slaveAddressRotate clk rst = - E.mealyB clk rst enableGen - (\(cntQ, bQ) (ss, b) -> + -> (Signal dom Bit, Signal dom Bool) + -> Vec n (Signal dom Bit) +slaveAddressRotate clk rst (ss,bp) = + map (fmap boolToBit) + $ E.mealyB clk rst enableGen + (\(cntQ, bQ) (ss', b) -> let bF = bQ && not b cntD | bF = if cntQ == maxBound then 0 else cntQ + 1 | otherwise = cntQ - oH = (ss ||) <$> (unpack . complement $ bin2onehot cntQ) + oH = (ss' ||) <$> (unpack . complement $ bin2onehot cntQ) in ((cntD, b), oH)) (0 :: Index n, False) + (bitToBool <$> ss, bp) where bin2onehot = setBit 0 . fromEnum @@ -60,17 +62,17 @@ testMasterMultiSlave divHalf wait mVal sVal mode latch duration = ,(L.nub masterOutS,P.length masterOutS)) where slaveIn = pure sVal - (misoZ0, _, slaveOut0) = + (SpiSlaveOut misoZ0, _, slaveOut0) = withClockResetEnable clk rst enableGen - (spiSlaveLatticeSBIO mode latch sclk mosi miso ss0 slaveIn) + (spiSlaveLatticeSBIO mode latch (SpiSlaveIn mosi miso sclk ss0) slaveIn) - (misoZ1, _, slaveOut1) = + (SpiSlaveOut misoZ1, _, slaveOut1) = withClockResetEnable clk rst enableGen - (spiSlaveLatticeSBIO mode latch sclk mosi miso ss1 slaveIn) + (spiSlaveLatticeSBIO mode latch (SpiSlaveIn mosi miso sclk ss1) slaveIn) - (misoZ2, _, slaveOut2) = + (SpiSlaveOut misoZ2, _, slaveOut2) = withClockResetEnable clk rst enableGen - (spiSlaveLatticeSBIO mode latch sclk mosi miso ss2 slaveIn) + (spiSlaveLatticeSBIO mode latch (SpiSlaveIn mosi miso sclk ss2) slaveIn) miso = veryUnsafeToBiSignalIn (mergeBiSignalOuts (misoZ2 :> misoZ1 :> misoZ0 :> Nil)) @@ -79,9 +81,9 @@ testMasterMultiSlave divHalf wait mVal sVal mode latch duration = (ss2 `Cons` ss1 `Cons` ss0 `Cons` _) = slaveAddressRotate @3 clk rst (ss,bp) - (sclk, mosi, ss, bp, masterAck, masterOut) = + (SpiMasterOut mosi sclk ss, bp, masterAck, masterOut) = withClockResetEnable clk rst enableGen - (spiMaster mode divHalf wait masterIn (readFromBiSignal miso)) + (spiMaster1 mode divHalf wait masterIn (SpiMasterIn $ readFromBiSignal miso)) clk = systemClockGen rst = systemResetGen