Skip to content
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

Move BackPressure and Simulate to Protocols.Internal.Classes #114

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

lmbollen
Copy link
Contributor

I think these also should have been moved in #74. This is preparation for template Haskell generators for tuple instances for Simulate (and friends?)

Copy link
Member

@DigitalBrains1 DigitalBrains1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these also should have been moved in #74.

I'm not very familiar with the code base yet, so I don't really have an opinion on this. In my contribution to that PR, I simply moved those things that needed to be moved. In general, when you're moving stuff around to avoid import loops, if you move too much you'll just have the exact same import loop but with a different module name, or it happens next time you want to do such a thing.

However, since this module just defines classes (well, it should, see further feedback), it seems fairly safe to put too many classes there. I don't readily see how you'd introduce an import loop for your TH that way.

This is preparation for template Haskell generators for tuple instances for Simulate (and friends?)

Why isn't it simply part of the PR that introduces those?

}
deriving (Show)

instance Default SimulationConfig where
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an instance, not a class.

directly, as it may be extend with other options in the future. Use 'def'
instead.
-}
data SimulationConfig = SimulationConfig
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm types are not classes either; I see I made the same transgression with Circuit. I suggest we move Circuit back to Protocols.Internal. It was in all likelihood a silly mistake I made.

input component is not sending any data. Note that, in the Df protocol,
protocols may send arbitrary acknowledgement signals when this happens.
-}
data StallAck
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again a type not a class.

@DigitalBrains1
Copy link
Member

DigitalBrains1 commented Sep 26, 2024

I feel PR #75 made a wrong choice. It put a function into Protocols.Internal.Classes. That doesn't really make sense from a naming standpoint. I also don't see a reason to do it even apart from naming.

I also noticed clash-protocols:Protocols.Wishbone imports clash-protocols-base:Protocols.Internal.Classes. I was like: Huh? No you can't? But apparently you can. Because PR #104 made this explicitly not exposed module an exposed module. I vehemently object to this. We don't want people to import those classes without their instances!

So I think we need about these changes to be applied as well:

Diff
diff --git a/clash-protocols-base/clash-protocols-base.cabal b/clash-protocols-base/clash-protocols-base.cabal
index a35f14e..8dd2756 100644
--- a/clash-protocols-base/clash-protocols-base.cabal
+++ b/clash-protocols-base/clash-protocols-base.cabal
@@ -113,7 +113,6 @@ library
   exposed-modules:
     Protocols.Cpp
     Protocols.Internal
-    Protocols.Internal.Classes
     Protocols.Internal.TaggedBundle
     Protocols.Internal.TaggedBundle.TH
     Protocols.Internal.TH
@@ -122,4 +121,7 @@ library
     Protocols.Plugin
     Protocols.Plugin.Internal
 
+  other-modules:
+    Protocols.Internal.Classes
+
   default-language: Haskell2010
diff --git a/clash-protocols-base/src/Protocols/Internal.hs b/clash-protocols-base/src/Protocols/Internal.hs
index d82318b..1905529 100644
--- a/clash-protocols-base/src/Protocols/Internal.hs
+++ b/clash-protocols-base/src/Protocols/Internal.hs
@@ -45,6 +45,119 @@ import GHC.Generics (Generic)
 >>> import Protocols
 -}
 
+{- | A /Circuit/, in its most general form, corresponds to a component with two
+pairs of an input and output. As a diagram:
+
+@
+            Circuit a b
+
+           +-----------+
+    Fwd a  |           |  Fwd b
+  +------->+           +-------->
+           |           |
+           |           |
+    Bwd a  |           |  Bwd b
+  <--------+           +<-------+
+           |           |
+           +-----------+
+@
+
+The first pair, @(Fwd a, Bwd a)@ can be thought of the data sent to and from
+the component on the left hand side of this circuit. For this pair, @Fwd a@
+is the data sent from the circuit on the left hand side (not pictured), while
+@Bwd a@ is the data sent to the left hand side from the current circuit.
+
+Similarly, the second pair, @(Fwd b, Bwd)@, can be thought of as the data
+sent to and from the right hand side of this circuit. In this case, @Fwd b@
+is the data sent from the current circuit to the one on the right hand side,
+while @Bwd b@ is the data received from the right hand side.
+
+In Haskell terms, we would say this is simply a function taking two inputs,
+@Fwd a@ and @Bwd b@, yielding a pair of outputs @Fwd b@ and @Bwd a@. This is
+in fact exactly its definition:
+
+@
+  newtype Circuit a b =
+    Circuit ( (Fwd a, Bwd b) -> (Bwd a, Fwd b) )
+@
+
+Note that the type parameters /a/ and /b/ don't directly correspond to the
+types of the inputs and outputs of this function. Instead, the type families
+@Fwd@ and @Bwd@ decide this. The type parameters can be thought of as
+deciders for what /protocol/ the left hand side and right hand side must
+speak.
+
+Let's make it a bit more concrete by building such a protocol. For this
+example, we'd like to build a protocol that sends data to a circuit, while
+allowing the circuit to signal whether it processed the sent data or not. Similarly,
+we'd like the sender to be able to indicate that it doesn't have any data to
+send. These kind of protocols fall under the umbrella of "dataflow" protocols,
+so lets call it /DataFlowSimple/ or /Df/ for short:
+
+@
+  data Df (dom :: Domain) (a :: Type)
+@
+
+We're only going to use it on the type level, so we won't need any
+constructors for this datatype. The first type parameter indicates the
+synthesis domain the protocol will use. This is the same /dom/ as used in
+/Signal dom a/. The second type indicates what data the protocol needs to
+send. Again, this is similar to the /a/ in /Signal dom a/.
+
+As said previously, we'd like the sender to either send /no data/ or
+/some data/. We can capture this in a data type very similar to /Maybe/:
+
+@
+  data Data a = NoData | Data a
+@
+
+On the way back, we'd like to either acknowledge or not acknowledge sent
+data. Similar to /Bool/ we define:
+
+@
+  newtype Ack = Ack Bool
+@
+
+With these three definitions we're ready to make an instance for /Fwd/ and
+/Bwd/:
+
+@
+instance Protocol (Df dom a) where
+  type Fwd (Df dom a) = Signal dom (Data a)
+  type Bwd (Df dom a) = Signal dom Ack
+@
+
+Having defined all this, we can take a look at /Circuit/ once more: now
+instantiated with our types. The following:
+
+@
+  f :: Circuit (Df dom a) (Df dom b)
+@
+
+..now corresponds to the following protocol:
+
+@
+                           +-----------+
+      Signal dom (Data a)  |           |  Signal dom (Data b)
+ +------------------------>+           +------------------------->
+                           |           |
+                           |           |
+      Signal dom Ack       |           |  Signal dom Ack
+ <-------------------------+           +<------------------------+
+                           |           |
+                           +-----------+
+@
+
+There's a number of advantages over manually writing out these function
+types:
+
+  1. It reduces syntactical noise in type signatures
+
+  2. It eliminates the need for manually routing acknowledgement lines
+-}
+newtype Circuit a b
+  = Circuit ((Fwd a, Bwd b) -> (Bwd a, Fwd b))
+
 -- | Protocol-agnostic acknowledgement
 newtype Ack = Ack Bool
   deriving (Generic, C.NFDataX, Show, C.Bundle, Eq, Ord)
@@ -759,3 +872,30 @@ tupCircuits :: Circuit a b -> Circuit c d -> Circuit (a, c) (b, d)
 tupCircuits (Circuit f) (Circuit g) = Circuit (reorder . (f *** g) . reorder)
  where
   reorder ~(~(a, b), ~(c, d)) = ((a, c), (b, d))
+
+{- | Force a /nack/ on the backward channel and /no data/ on the forward
+channel if reset is asserted.
+-}
+forceResetSanityGeneric ::
+  forall dom a fwd bwd.
+  ( C.KnownDomain dom
+  , C.HiddenReset dom
+  , IdleCircuit a
+  , Fwd a ~ C.Signal dom fwd
+  , Bwd a ~ C.Signal dom bwd
+  ) =>
+  Circuit a a
+forceResetSanityGeneric = Circuit go
+ where
+  go (fwd, bwd) =
+    C.unbundle $
+      C.mux
+        rstAsserted
+        (C.bundle (idleBwd $ Proxy @a, idleFwd $ Proxy @a))
+        (C.bundle (bwd, fwd))
+
+#if MIN_VERSION_clash_prelude(1,8,0)
+  rstAsserted = C.unsafeToActiveHigh C.hasReset
+#else
+  rstAsserted = C.unsafeToHighPolarity C.hasReset
+#endif
diff --git a/clash-protocols-base/src/Protocols/Internal/Classes.hs b/clash-protocols-base/src/Protocols/Internal/Classes.hs
index cba5a44..489a555 100644
--- a/clash-protocols-base/src/Protocols/Internal/Classes.hs
+++ b/clash-protocols-base/src/Protocols/Internal/Classes.hs
@@ -21,149 +21,9 @@ class Protocol a where
   -- existence of 'Bwd'.
   type Bwd (a :: Type)
 
-{- | A /Circuit/, in its most general form, corresponds to a component with two
-pairs of an input and output. As a diagram:
-
-@
-            Circuit a b
-
-           +-----------+
-    Fwd a  |           |  Fwd b
-  +------->+           +-------->
-           |           |
-           |           |
-    Bwd a  |           |  Bwd b
-  <--------+           +<-------+
-           |           |
-           +-----------+
-@
-
-The first pair, @(Fwd a, Bwd a)@ can be thought of the data sent to and from
-the component on the left hand side of this circuit. For this pair, @Fwd a@
-is the data sent from the circuit on the left hand side (not pictured), while
-@Bwd a@ is the data sent to the left hand side from the current circuit.
-
-Similarly, the second pair, @(Fwd b, Bwd)@, can be thought of as the data
-sent to and from the right hand side of this circuit. In this case, @Fwd b@
-is the data sent from the current circuit to the one on the right hand side,
-while @Bwd b@ is the data received from the right hand side.
-
-In Haskell terms, we would say this is simply a function taking two inputs,
-@Fwd a@ and @Bwd b@, yielding a pair of outputs @Fwd b@ and @Bwd a@. This is
-in fact exactly its definition:
-
-@
-  newtype Circuit a b =
-    Circuit ( (Fwd a, Bwd b) -> (Bwd a, Fwd b) )
-@
-
-Note that the type parameters /a/ and /b/ don't directly correspond to the
-types of the inputs and outputs of this function. Instead, the type families
-@Fwd@ and @Bwd@ decide this. The type parameters can be thought of as
-deciders for what /protocol/ the left hand side and right hand side must
-speak.
-
-Let's make it a bit more concrete by building such a protocol. For this
-example, we'd like to build a protocol that sends data to a circuit, while
-allowing the circuit to signal whether it processed the sent data or not. Similarly,
-we'd like the sender to be able to indicate that it doesn't have any data to
-send. These kind of protocols fall under the umbrella of "dataflow" protocols,
-so lets call it /DataFlowSimple/ or /Df/ for short:
-
-@
-  data Df (dom :: Domain) (a :: Type)
-@
-
-We're only going to use it on the type level, so we won't need any
-constructors for this datatype. The first type parameter indicates the
-synthesis domain the protocol will use. This is the same /dom/ as used in
-/Signal dom a/. The second type indicates what data the protocol needs to
-send. Again, this is similar to the /a/ in /Signal dom a/.
-
-As said previously, we'd like the sender to either send /no data/ or
-/some data/. We can capture this in a data type very similar to /Maybe/:
-
-@
-  data Data a = NoData | Data a
-@
-
-On the way back, we'd like to either acknowledge or not acknowledge sent
-data. Similar to /Bool/ we define:
-
-@
-  newtype Ack = Ack Bool
-@
-
-With these three definitions we're ready to make an instance for /Fwd/ and
-/Bwd/:
-
-@
-instance Protocol (Df dom a) where
-  type Fwd (Df dom a) = Signal dom (Data a)
-  type Bwd (Df dom a) = Signal dom Ack
-@
-
-Having defined all this, we can take a look at /Circuit/ once more: now
-instantiated with our types. The following:
-
-@
-  f :: Circuit (Df dom a) (Df dom b)
-@
-
-..now corresponds to the following protocol:
-
-@
-                           +-----------+
-      Signal dom (Data a)  |           |  Signal dom (Data b)
- +------------------------>+           +------------------------->
-                           |           |
-                           |           |
-      Signal dom Ack       |           |  Signal dom Ack
- <-------------------------+           +<------------------------+
-                           |           |
-                           +-----------+
-@
-
-There's a number of advantages over manually writing out these function
-types:
-
-  1. It reduces syntactical noise in type signatures
-
-  2. It eliminates the need for manually routing acknowledgement lines
--}
-newtype Circuit a b
-  = Circuit ((Fwd a, Bwd b) -> (Bwd a, Fwd b))
-
 {- | Idle state of a Circuit. Aims to provide no data for both the forward and
 backward direction. Transactions are not acknowledged.
 -}
 class (Protocol p) => IdleCircuit p where
   idleFwd :: Proxy p -> Fwd (p :: Type)
   idleBwd :: Proxy p -> Bwd (p :: Type)
-
-{- | Force a /nack/ on the backward channel and /no data/ on the forward
-channel if reset is asserted.
--}
-forceResetSanityGeneric ::
-  forall dom a fwd bwd.
-  ( KnownDomain dom
-  , HiddenReset dom
-  , IdleCircuit a
-  , Fwd a ~ Signal dom fwd
-  , Bwd a ~ Signal dom bwd
-  ) =>
-  Circuit a a
-forceResetSanityGeneric = Circuit go
- where
-  go (fwd, bwd) =
-    unbundle $
-      mux
-        rstAsserted
-        (bundle (idleBwd $ Proxy @a, idleFwd $ Proxy @a))
-        (bundle (bwd, fwd))
-
-#if MIN_VERSION_clash_prelude(1,8,0)
-  rstAsserted = unsafeToActiveHigh hasReset
-#else
-  rstAsserted = unsafeToHighPolarity hasReset
-#endif
diff --git a/clash-protocols/src/Protocols/Wishbone.hs b/clash-protocols/src/Protocols/Wishbone.hs
index ac22ed7..78307e8 100644
--- a/clash-protocols/src/Protocols/Wishbone.hs
+++ b/clash-protocols/src/Protocols/Wishbone.hs
@@ -15,7 +15,7 @@ import Prelude hiding (head, not, (&&))
 import Clash.Signal.Internal (Signal (..))
 import Control.DeepSeq (NFData)
 import Protocols
-import Protocols.Internal.Classes
+import Protocols.Internal
 
 import qualified Clash.Prelude as C
 

@DigitalBrains1
Copy link
Member

DigitalBrains1 commented Sep 26, 2024

I suspect there may be a mismatch between what different developers think the clash-protocols-base:Protocols.Internal.Classes module is for. I created this module for one purpose: to be able to import some classes in Protocols.Internal.TH. It is just a construction to avoid import loops. I wrote:

So I moved the classes Circuit, Protocol and the new IdleCircuit to a new module Protocols.Internal.Classes that is not exposed. This to prevent users accidentally importing just the class but not the instances. Instead, the API remains identical: [...]

Of course I made the rather dumb mistake of calling Circuit a class and indeed moving it there. Why?? I don't know...

Now I come back a few months later and the module is exported exposed and contains a function that has nothing to do with either classes or TH. I thought the module header communicated the purpose but maybe somebody knows how to improve the phrasing to prevent these things going forth? It now says:

{- |
These class definitions are needed to be able to write Template Haskell quotes
for instances. They are defined separately to avoid import loops.

This module is not exported; the classes and their (orphan) instances are
exported elsewhere.
-}

Which apparently did not achieve what I hoped it would and is currently a lie, as the module is exported exposed... :-(

[edit]
One thing that can be improved is that it should say exposed not exported like I'm again doing wrong now. That is, it should say the module is not exposed; it should keep saying the classes and instances are exported [elsewhere].
[/edit]

`clash-protocols-base` should only contain code and definitions related to the circuit plugin.
This includes the `Circuit` definition and `Protocol` typeclass. Furthermore we include instances
for types imported from underlying clash libraries such as tuples, `Vec` and `Signal`.
It not only contains classes, but also types
The separation between plugin related code and protocol related code
should be obvious from the module hierarchy
- `unitsTupleInstances`
- `taggedBundleTupleInstances`
- `protocolTupleInstances`
@lmbollen lmbollen changed the base branch from main to lucas/reorder-modules October 2, 2024 11:34
Base automatically changed from lucas/reorder-modules to main October 3, 2024 15:59
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

Successfully merging this pull request may close these issues.

3 participants