module CLaSH.Prelude.Synchronizer
  ( 
    dualFlipFlopSynchronizer
    
  , asyncFIFOSynchronizer
  )
where
import Data.Bits                   (complement, shiftR, xor)
import GHC.TypeLits                (type (+))
import CLaSH.Class.BitPack         (boolToBV)
import CLaSH.Prelude.BitIndex      (slice)
import CLaSH.Prelude.Mealy         (mealyB')
import CLaSH.Prelude.RAM           (asyncRam')
import CLaSH.Promoted.Nat          (SNat, powSNat, subSNat)
import CLaSH.Promoted.Nat.Literals (d0, d1, d2)
import CLaSH.Signal                ((.&&.), not1)
import CLaSH.Signal.Explicit       (Signal', SClock, register',
                                    unsafeSynchronizer)
import CLaSH.Sized.BitVector       (BitVector, (++#))
dualFlipFlopSynchronizer :: SClock clk1    
                                           
                         -> SClock clk2    
                                           
                         -> a              
                                           
                         -> Signal' clk1 a 
                         -> Signal' clk2 a 
dualFlipFlopSynchronizer clk1 clk2 i = register' clk2 i
                                     . register' clk2 i
                                     . unsafeSynchronizer clk1 clk2
fifoMem :: _
        => SClock wclk
        -> SClock rclk
        -> SNat addrSize
        -> Signal' wclk (BitVector addrSize)
        -> Signal' rclk (BitVector addrSize)
        -> Signal' wclk Bool
        -> Signal' wclk Bool
        -> Signal' wclk a
        -> Signal' rclk a
fifoMem wclk rclk addrSize waddr raddr winc wfull wdata =
  asyncRam' wclk rclk
            (d2 `powSNat` addrSize)
            waddr raddr
            (winc .&&. not1 wfull)
            wdata
ptrCompareT :: _
            => SNat addrSize
            -> (BitVector (addrSize + 1) -> BitVector (addrSize + 1) -> Bool)
            -> (BitVector (addrSize + 1), BitVector (addrSize + 1), Bool)
            -> (BitVector (addrSize + 1), Bool)
            -> ((BitVector (addrSize + 1), BitVector (addrSize + 1), Bool)
               ,(Bool, BitVector addrSize, BitVector (addrSize + 1)))
ptrCompareT addrSize flagGen (bin,ptr,flag) (s_ptr,inc) = ((bin',ptr',flag')
                                                          ,(flag,addr,ptr))
  where
    
    bin' = bin + boolToBV (inc && not flag)
    ptr' = (bin' `shiftR` 1) `xor` bin'
    addr = slice (addrSize `subSNat` d1) d0 bin
    flag' = flagGen ptr' s_ptr
isFull :: _
       => SNat addrSize
       -> BitVector (addrSize + 1)
       -> BitVector (addrSize + 1)
       -> Bool
isFull addrSize ptr s_ptr =
  ptr == (complement (slice addrSize (addrSize `subSNat` d1) s_ptr) ++#
         slice (addrSize `subSNat` d2) d0 s_ptr)
asyncFIFOSynchronizer :: _
                      => SNat addrSize     
                                           
                                           
                      -> SClock wclk       
                                           
                      -> SClock rclk       
                                           
                      -> Signal' wclk a    
                      -> Signal' wclk Bool 
                      -> Signal' rclk Bool 
                      -> (Signal' rclk a, Signal' rclk Bool, Signal' wclk Bool)
                      
asyncFIFOSynchronizer addrSize wclk rclk wdata winc rinc = (rdata,rempty,wfull)
  where
    s_rptr = dualFlipFlopSynchronizer rclk wclk 0 rptr
    s_wptr = dualFlipFlopSynchronizer wclk rclk 0 wptr
    rdata = fifoMem wclk rclk addrSize waddr raddr winc wfull wdata
    (rempty,raddr,rptr) = mealyB' rclk (ptrCompareT addrSize (==)) (0,0,True)
                                  (s_wptr,rinc)
    (wfull,waddr,wptr)  = mealyB' wclk (ptrCompareT addrSize (isFull addrSize))
                                  (0,0,False) (s_rptr,winc)