Skip to content

Clash gets stuck on this design #2882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
pieter-bos opened this issue Feb 10, 2025 · 3 comments
Open

Clash gets stuck on this design #2882

pieter-bos opened this issue Feb 10, 2025 · 3 comments

Comments

@pieter-bos
Copy link

pieter-bos commented Feb 10, 2025

The below self-contained example makes clash not terminate:

Unfold for original example
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module Top where

import Clash.Prelude
import Clash.Annotations.TH

class KnownNat (UsbSize a) => UsbSerialize a where
  type UsbSize a :: Nat
  usbDeserialize :: BitVector (UsbSize a) -> a

newtype Packed a = Packed { unPacked :: a }

instance BitPack a => UsbSerialize (Packed a) where
  type UsbSize (Packed a) = BitSize a
  usbDeserialize = Packed . unpack

data PID
  = Out
  | In
  deriving (Generic, NFDataX, BitPack)

instance UsbSerialize PID where
  type UsbSize PID = UsbSize (Packed PID)
  usbDeserialize = unPacked . usbDeserialize

baseCrc ::
  HiddenClockResetEnable dom =>
  Signal dom (Maybe Bit) ->
  Signal dom (Maybe Bit)
baseCrc = mealy (\() _ -> deepErrorX "Q") ()

bufferFirst ::
  HiddenClockResetEnable dom =>
  Signal dom (Maybe Bit) ->
  Signal dom (Maybe PID)
bufferFirst =
  mealy step 0
 where
  step buf (Just 0) = (0, Just $ usbDeserialize buf)
  step buf _ = (buf, Just (usbDeserialize (buf .<<+ 0)))

holdPID ::
  (KnownDomain dom, HiddenClockResetEnable dom) =>
  Signal dom (Maybe Bit) ->
  Signal dom (Maybe PID)
holdPID inp =
  bufferFirst $ baseCrc $ mealy (\() _ -> deepErrorX "QQ") () inp

usb ::
  forall dom .
  (KnownDomain dom, HiddenClockResetEnable dom) =>
  Signal dom Bool
usb = goodPid <$> holdPID (deepErrorX "QQ")
 where
  goodPid (Just In) = True
  goodPid _ = False

{-# OPAQUE usb #-}

topEntity ::
  "CLK" ::: Clock System ->
  "BTN" ::: Reset System ->
  "o" ::: Signal System Bool
topEntity clk rst =
  withClockResetEnable clk rst enableGen $ usb

$(makeTopEntity 'topEntity)
@kleinreact
Copy link
Member

A bit smaller reproducer:

{-# LANGUAGE UndecidableInstances #-}
module Top where

import Clash.Prelude

class KnownNat (UsbSize a) => UsbSerialize a where
  type UsbSize a :: Nat
  usbDeserialize :: BitVector (UsbSize a) -> a

newtype Packed a = Packed { unPacked :: a }

instance BitPack a => UsbSerialize (Packed a) where
  type UsbSize (Packed a) = BitSize a
  usbDeserialize = Packed . unpack

instance UsbSerialize Bit where
  type UsbSize Bit = UsbSize (Packed Bit)
  usbDeserialize = unPacked . usbDeserialize

topEntity :: HiddenClockResetEnable System => Signal System Bit
topEntity = mealy step 0 $ pure ()
 where
  step x _ = (x, usbDeserialize (x .<<+ 0))
--  {-# INLINE step #-}

Note that the commented INLINE at the end already suffices to fix the issue, which may be useful to figure out its origin.

@christiaanb
Copy link
Member

Change

newtype Packed a = Packed { unPacked :: a }

to

data Packed a = Packed { unPacked :: a }

This is a known bug in the Clash compiler: type families over newtypes break Clash. There's an attempt at a fix here #1064 but it needs to be rewritten and gotten into a working state.

@pieter-bos
Copy link
Author

For posterity: the motivation for this approach is to be able to use DerivingVia, so I can do something like:

data PID = In | Out | Setup
  deriving (Generic, BitPack)
  deriving UsbSerialize via (Packed PID)

Since DerivingVia instists that here PID must be Coercible to Packed PID, it must be a newtype for this approach to work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants