You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: website/blog/2025/05-05-arbiter-type-class/index.md
+22-15Lines changed: 22 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,7 +54,7 @@ for ((client, decoupled) <- arbiter.clients.zip(needToArbitrate)) {
54
54
```
55
55
56
56
This works, but its a bit clunky.
57
-
Considering the implementation of `PriorityArbiter`, is only 6 lines of Scala, it's not ideal that it takes another 4 lines to use it.
57
+
Considering the implementation of `PriorityArbiter` is only 6 lines of Scala, it's not ideal that it takes another 4 lines to use it.
58
58
59
59
## Generalizing the Arbiter
60
60
@@ -77,14 +77,16 @@ However, for our example above, this would be a bit difficult.
77
77
`Decoupled` is defined within Chisel itself--how can a user make Chisel's `Decoupled` inherit from `Arbitrable`?
78
78
79
79
Instead, we can try something different.
80
-
We could make the Arbiter generic to the type of the client and then use higher-order functions to extract the request and grant signals.
80
+
We could make the Arbiter generic to the type of the client and then require additional function arguments that tell how extract the request and grant signals from the particular client type we are using.
81
81
82
82
```scala
83
83
classGenericPriorityArbiter[A<:Data](
84
84
nClients: Int,
85
85
clientType: A
86
86
)(
87
+
/** Function that indicates how to connect request from type A */
87
88
requestFn: A=>Bool,
89
+
/** Function that indicates how to connect the grant from type A */
88
90
grantFn: (A, Bool) =>Unit) extendsModule {
89
91
valclients=IO(Vec(nClients, Flipped(clientType)))
90
92
@@ -98,7 +100,7 @@ class GenericPriorityArbiter[A <: Data](
98
100
```
99
101
100
102
You may notice this looks quite similar to the original `PriorityArbiter` in its implementation.
101
-
It uses two parameter lists in order to help the Scala type inferencer derive the types of the functions of the type of the client--we could do this with one parameter list but then it would require explicitly passing the type of the client.
103
+
> NOTE: It uses two parameter lists in order to help the Scala type inferencer derive the types of the functions of the type of the client--we could do this with one parameter list but then it would require explicitly passing the type of the client.
102
104
103
105
Now we can use it for both our `ArbiterClient` and `Decoupled` interfaces.
To clean this up even more, we can introduce a type class that captures the "arbitrable" pattern:
136
+
To clean this up even more, we can introduce a _type class_ that captures the "arbitrable" pattern:
135
137
136
138
```scala
137
139
traitArbitrable[A] {
@@ -141,7 +143,7 @@ trait Arbitrable[A] {
141
143
```
142
144
143
145
Effectively, we have taken the two arguments to the arbiter and turned them into methods on the type class.
144
-
This looks similar to the proposed object-oriented version of `Arbitrable` above, but note how it is parameterized by the type of the client and accepts the client as an argument.
146
+
This looks similar to the proposed object-oriented version of `Arbitrable` above, but note how it is parameterized by the type of the client and each function defined in the trait accepts the client as an argument.
145
147
146
148
We can then provide instances of this type class for specific types. For example, for `ArbiterClient` and `Decoupled`:
Then, we can refactor the arbiter to use the type class:
162
+
Then, we can refactor the arbiter to use the type class so that we're getting one 'package' of functions for type A, not having to pass them individually:
161
163
162
164
```scala
163
165
classGenericPriorityArbiter[A<:Data](nClients: Int, clientType: A, arbitrable: Arbitrable[A]) extendsModule {
@@ -184,35 +186,37 @@ val arbiter2 = Module(new GenericPriorityArbiter(4, Decoupled(UInt(8.W)), new De
184
186
arbiter2.clients :<>= clients2
185
187
```
186
188
187
-
At least we aren't repeating logic anymore, instead we get to just refer to the type class instance.
189
+
At least we aren't repeating logic anymore, instead we get to reuse the code for making the type class.
188
190
189
191
However, we can do even better.
190
192
191
193
## Implicit Type Class Instances
192
194
193
195
Scala has a powerful feature called **implicit resolution**.
194
-
This allows us to avoid passing around the type class instance explicitly.
195
-
Instead, we can define the type class instance as an implicit value and the compiler will automatically find it for us.
196
+
This allows us to avoid figuring out what type class we need to instantiate at every call site.
197
+
Instead, we can define a default function to use when a specific type class is needed, and the compiler will automatically find it for us. We do this by making the argument to the function implicit, then making sure the implicit value of the type class is in scope.
196
198
197
-
Let us rewrite our typeclass instances as implicit values:
199
+
Let us instantiate implicit functions to create our type class instances. This tells the compiler, "if you need a function to create an `Arbitrable[ArbiterClient]`, use this one."
198
200
199
201
```scala
202
+
// We could make a def, but since this function is the same every time, we just make this a `val`.
classGenericPriorityArbiter[A<:Data:Arbitrable](nClients: Int, clientType: A) extendsModule {
248
255
...
249
256
}
@@ -272,7 +279,7 @@ For more information, see [further reading](#further-reading) below.
272
279
## Conclusion
273
280
274
281
This example only scratches the surface of what type classes can do in Chisel and Scala.
275
-
Whenever you find yourself passing around the same bits of logic repeatedly, think about whether a type class could capture that pattern.
282
+
Whenever you find yourself passing functions around repeatedly, or are struggling with an inheritance pattern, think about whether a type class could capture that pattern.
0 commit comments