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

[docs] Add Cookbook section on aliased Bundle fields #2444

Merged
merged 1 commit into from
Mar 15, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 142 additions & 1 deletion docs/src/cookbooks/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Please note that these examples make use of [Chisel's scala-style printing](../e
* [Can I make a 2D or 3D Vector?](#can-i-make-a-2D-or-3D-Vector)
* [How do I create a Vector of Registers?](#how-do-i-create-a-vector-of-registers)
* [How do I create a Reg of type Vec?](#how-do-i-create-a-reg-of-type-vec)
* Bundles
* [How do I deal with aliased Bundle fields?](#aliased-bundle-fields)
* [How do I create a finite state machine?](#how-do-i-create-a-finite-state-machine-fsm)
* [How do I unpack a value ("reverse concatenation") like in Verilog?](#how-do-i-unpack-a-value-reverse-concatenation-like-in-verilog)
* [How do I do subword assignment (assign to some bits in a UInt)?](#how-do-i-do-subword-assignment-assign-to-some-bits-in-a-uint)
Expand All @@ -31,7 +33,7 @@ Please note that these examples make use of [Chisel's scala-style printing](../e
* [How can I dynamically set/parametrize the name of a module?](#how-can-i-dynamically-setparametrize-the-name-of-a-module)
* Directionality
* [How do I strip directions from a bidirectional Bundle (or other Data)?](#how-do-i-strip-directions-from-a-bidirectional-bundle-or-other-data)

## Type Conversions

### How do I create a UInt from an instance of a Bundle?
Expand Down Expand Up @@ -231,6 +233,145 @@ class Foo extends RawModule {
}
```

## Bundles

### <a name="aliased-bundle-fields"></a> How do I deal with aliased Bundle fields?

```scala mdoc:invisible:reset
import chisel3._

class Top[T <: Data](gen: T) extends Module {
val in = IO(Input(gen))
val out = IO(Output(gen))
out := in
}
```

Following the `gen` pattern when creating Bundles can result in some opaque error messages:

```scala mdoc
class AliasedBundle[T <: Data](gen: T) extends Bundle {
val foo = gen
val bar = gen
}
```

```scala mdoc:crash
getVerilogString(new Top(new AliasedBundle(UInt(8.W))))
```

This error is saying that fields `foo` and `bar` of `AliasedBundle` are the
exact same object in memory.
This is a problem for Chisel because we need to be able to distinguish uses of
`foo` and `bar` but cannot when they are referentially the same.

Note that the following example looks different but will give you exactly the same issue:

```scala mdoc
class AlsoAliasedBundle[T <: Data](val gen: T) extends Bundle {
// ^ This val makes `gen` a field, just like `foo`
val foo = gen
}
```

By making `gen` a `val`, it becomes a public field of the `class`, just like `foo`.

```scala mdoc:crash
getVerilogString(new Top(new AlsoAliasedBundle(UInt(8.W))))
```

There are several ways to solve this issue with their own advantages and disadvantages.

#### 1. 0-arity function parameters

Instead of passing an object as a parameter, you can pass a 0-arity function (a function with no arguments):

```scala mdoc
class UsingAFunctionBundle[T <: Data](gen: () => T) extends Bundle {
val foo = gen()
val bar = gen()
}
```

Note that the type of `gen` is now `() => T`.
Because it is now a function and not a subtype of `Data`, you can safely make `gen` a `val` without
it becoming a hardware field of the `Bundle`.

Note that this also means you must pass `gen` as a function, for example:

```scala mdoc:silent
getVerilogString(new Top(new UsingAFunctionBundle(() => UInt(8.W))))
```

<a name="aliased-warning"></a> **Warning**: you must ensure that `gen` creates fresh objects rather than capturing an already constructed value:

```scala mdoc:crash
class MisusedFunctionArguments extends Module {
// This usage is correct
val in = IO(Input(new UsingAFunctionBundle(() => UInt(8.W))))

// This usage is incorrect
val fizz = UInt(8.W)
val out = IO(Output(new UsingAFunctionBundle(() => fizz)))
}
getVerilogString(new MisusedFunctionArguments)
```
In the above example, value `fizz` and fields `foo` and `bar` of `out` are all the same object in memory.


#### 2. By-name function parameters

Functionally the same as (1) but with more subtle syntax, you can use [Scala by-name function parameters](https://docs.scala-lang.org/tour/by-name-parameters.html):

```scala mdoc
class UsingByNameParameters[T <: Data](gen: => T) extends Bundle {
val foo = gen
val bar = gen
}
```

With this usage, you do not include `() =>` when passing the argument:

```scala mdoc:silent
getVerilogString(new Top(new UsingByNameParameters(UInt(8.W))))
```

Note that as this is just syntactic sugar over (1), the [same warning applies](#aliased-warning).

#### 3. Directioned Bundle fields

You can alternatively wrap the fields with `Output(...)`, which creates fresh instances of the passed argument.
Chisel treats `Output` as the "default direction" so if all fields are outputs, the `Bundle` is functionally equivalent to a `Bundle` with no directioned fields.

```scala mdoc
class DirectionedBundle[T <: Data](gen: T) extends Bundle {
val foo = Output(gen)
val bar = Output(gen)
}
```

```scala mdoc:invisible
getVerilogString(new Top(new DirectionedBundle(UInt(8.W))))
```

This approach is admittedly a little ugly and may mislead others reading the code because it implies that this Bundle is intended to be used as an `Output`.

#### 4. Call `.cloneType` directly

You can also just call `.cloneType` on your `gen` argument directly.
Copy link
Contributor

Choose a reason for hiding this comment

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

One thing I have always been wondering about: Why don't we allow calling chiselTypeOf in this context?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In Chisel2, the distinction between types and values was super blurry so users often ended up just peppering .cloneType everywhere to make sure things worked correctly. The idea in Chisel3 was to have a stronger distinction here--binding operators only take types, and chiselTypeOf only converts from hardware values back to types.

That being said, my use of .cloneType here is back to the Chisel2 style and is admittedly just avoiding this issue.

While we try to hide this implementation detail from the user, `.cloneType` is the mechanism by which Chisel creates fresh instances of `Data` objects:

```scala mdoc
class UsingCloneTypeBundle[T <: Data](gen: T) extends Bundle {
val foo = gen.cloneType
val bar = gen.cloneType
}
```

```scala mdoc:invisible
getVerilogString(new Top(new UsingCloneTypeBundle(UInt(8.W))))
```

### How do I create a finite state machine (FSM)?

The advised way is to use [`ChiselEnum`](https://www.chisel-lang.org/api/latest/chisel3/experimental/index.html#ChiselEnum=chisel3.experimental.EnumFactory) to construct enumerated types representing the state of the FSM.
Expand Down