summaryrefslogtreecommitdiff
path: root/docs/src/explanations/chisel-enum.md
blob: 3f0f3b18594912a8a645dbf0fc7b9bbbd4bf9c61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
---
layout: docs
title:  "Enumerations"
section: "chisel3"
---

# ChiselEnum

The ChiselEnum type can be used to reduce the chance of error when encoding mux selectors, opcodes, and functional unit operations.
In contrast with `Chisel.util.Enum`, `ChiselEnum` are subclasses of `Data`, which means that they can be used to define fields in `Bundle`s, including in `IO`s.

## Functionality and Examples

```scala mdoc
// Imports used in the following examples
import chisel3._
import chisel3.util._
import chisel3.stage.ChiselStage
import chisel3.experimental.ChiselEnum
```

```scala mdoc:invisible
// Helper to print stdout from Chisel elab
// May be related to: https://github.com/scalameta/mdoc/issues/517
import java.io._
import _root_.logger.Logger
def grabLog[T](thunk: => T): (String, T) = {
  val baos = new ByteArrayOutputStream()
  val stream = new PrintStream(baos, true, "utf-8")
  val ret = Logger.makeScope(Nil) {
   Logger.setOutput(stream)
   thunk
  }
  (baos.toString, ret)
}
```

Below we see ChiselEnum being used as mux select for a RISC-V core. While wrapping the object in a package is not required, it is highly recommended as it allows for the type to be used in multiple files more easily.

```scala mdoc
// package CPUTypes {
object AluMux1Sel extends ChiselEnum {
  val selectRS1, selectPC = Value
}
// We can see the mapping by printing each Value
AluMux1Sel.all.foreach(println)
```

Here we see a mux using the AluMux1Sel to select between different inputs.

```scala mdoc
import AluMux1Sel._

class AluMux1Bundle extends Bundle {
  val aluMux1Sel = Input(AluMux1Sel())
  val rs1Out     = Input(Bits(32.W))
  val pcOut      = Input(Bits(32.W))
  val aluMux1Out = Output(Bits(32.W))
}

class AluMux1File extends Module {
  val io = IO(new AluMux1Bundle)

  // Default value for aluMux1Out
  io.aluMux1Out := 0.U

  switch (io.aluMux1Sel) {
    is (selectRS1) {
      io.aluMux1Out := io.rs1Out
    }
    is (selectPC) {
      io.aluMux1Out := io.pcOut
    }
  }
}
```
```scala mdoc:verilog
ChiselStage.emitVerilog(new AluMux1File)
```

ChiselEnum also allows for the user to directly set the Values by passing an `UInt` to `Value(...)`
as shown below. Note that the magnitude of each `Value` must be strictly greater than the one before
it.

```scala mdoc
object Opcode extends ChiselEnum {
    val load  = Value(0x03.U) // i "load"  -> 000_0011
    val imm   = Value(0x13.U) // i "imm"   -> 001_0011
    val auipc = Value(0x17.U) // u "auipc" -> 001_0111
    val store = Value(0x23.U) // s "store" -> 010_0011
    val reg   = Value(0x33.U) // r "reg"   -> 011_0011
    val lui   = Value(0x37.U) // u "lui"   -> 011_0111
    val br    = Value(0x63.U) // b "br"    -> 110_0011
    val jalr  = Value(0x67.U) // i "jalr"  -> 110_0111
    val jal   = Value(0x6F.U) // j "jal"   -> 110_1111
}
```

The user can 'jump' to a value and continue incrementing by passing a start point then using a regular Value definition.

```scala mdoc
object BranchFunct3 extends ChiselEnum {
    val beq, bne = Value
    val blt = Value(4.U)
    val bge, bltu, bgeu = Value
}
// We can see the mapping by printing each Value
BranchFunct3.all.foreach(println)
```

## Casting

You can cast an enum to a `UInt` using `.asUInt`:

```scala mdoc
class ToUInt extends RawModule {
  val in = IO(Input(Opcode()))
  val out = IO(Output(UInt()))
  out := in.asUInt
}
```

```scala mdoc:invisible
// Always need to run Chisel to see if there are elaboration errors
ChiselStage.emitVerilog(new ToUInt)
```

You can cast from a `UInt` to an enum by passing the `UInt` to the apply method of the `ChiselEnum` object:

```scala mdoc
class FromUInt extends Module {
  val in = IO(Input(UInt(7.W)))
  val out = IO(Output(Opcode()))
  out := Opcode(in)
}
```

However, if you cast from a `UInt` to an Enum type when there are undefined states in the Enum values
that the `UInt` could hit, you will see a warning like the following:

```scala mdoc:passthrough
val (log, _) = grabLog(ChiselStage.emitChirrtl(new FromUInt))
println(s"```$log```")
```
(Note that the name of the Enum is ugly as an artifact of our documentation generation flow, it will
be cleaner in normal use).

You can avoid this warning by using the `.safe` factory method which returns the cast Enum in addition
to a `Bool` indicating if the Enum is in a valid state:

```scala mdoc
class SafeFromUInt extends Module {
  val in = IO(Input(UInt(7.W)))
  val out = IO(Output(Opcode()))
  val (value, valid) = Opcode.safe(in)
  assert(valid, "Enum state must be valid, got %d!", in)
  out := value
}
```

Now there will be no warning:

```scala mdoc:passthrough
val (log2, _) = grabLog(ChiselStage.emitChirrtl(new SafeFromUInt))
println(s"```$log2```")
```

## Testing

The _Type_ of the enums values is `<ChiselEnum Object>.Type` which can be useful for passing the values
as parameters to a function (or any other time a type annotation is needed).
Calling `.litValue` on an enum value will return the integer value of that object as a
[`BigInt`](https://www.scala-lang.org/api/2.12.13/scala/math/BigInt.html).

```scala mdoc
def expectedSel(sel: AluMux1Sel.Type): Boolean = sel match {
  case AluMux1Sel.selectRS1 => (sel.litValue == 0)
  case AluMux1Sel.selectPC  => (sel.litValue == 1)
  case _                    => false
}
```

Some additional useful methods defined on the `ChiselEnum` object are:
* `.all`: returns the enum values within the enumeration
* `.getWidth`: returns the width of the hardware type

## Workarounds

As of Chisel v3.4.3 (1 July 2020), the width of the values is always inferred.
To work around this, you can add an extra `Value` that forces the width that is desired.
This is shown in the example below, where we add a field `ukn` to force the width to be 3 bits wide:

```scala mdoc
object StoreFunct3 extends ChiselEnum {
    val sb, sh, sw = Value
    val ukn = Value(7.U)
}
// We can see the mapping by printing each Value
StoreFunct3.all.foreach(println)
```

Signed values are not supported so if you want the value signed, you must cast the UInt with `.asSInt`.

## Additional Resources

The ChiselEnum type is much more powerful than stated above. It allows for Sequence, Vec, and Bundle assignments, as well as a `.next` operation to allow for stepping through sequential states and an `.isValid` for checking that a hardware value is a valid `Value`. The source code for the ChiselEnum can be found [here](https://github.com/chipsalliance/chisel3/blob/2a96767097264eade18ff26e1d8bce192383a190/core/src/main/scala/chisel3/StrongEnum.scala) in the class `EnumFactory`. Examples of the ChiselEnum operations can be found [here](https://github.com/chipsalliance/chisel3/blob/dd6871b8b3f2619178c2a333d9d6083805d99e16/src/test/scala/chiselTests/StrongEnum.scala).