Skip to content

Instantly share code, notes, and snippets.

@jackkoenig
Last active March 14, 2022 22:24
Show Gist options
  • Save jackkoenig/299c16ba1be0144340ca580a4d2336d0 to your computer and use it in GitHub Desktop.
Save jackkoenig/299c16ba1be0144340ca580a4d2336d0 to your computer and use it in GitHub Desktop.
Please see https://www.chisel-lang.org/chisel3/docs/cookbooks/cookbook.html for the most up-to-date cookbook.
layout title section
docs
General Cookbook
chisel3

General Cookbook

Please note that these examples make use of Chisel's scala-style printing.

Type Conversions

How do I create a UInt from an instance of a Bundle?

Call asUInt on the Bundle instance.

import chisel3._

class MyBundle extends Bundle {
  val foo = UInt(4.W)
  val bar = UInt(4.W)
}

class Foo extends RawModule {
  val bundle = Wire(new MyBundle)
  bundle.foo := 0xc.U
  bundle.bar := 0x3.U
  val uint = bundle.asUInt
  printf(p"$uint") // 195

  // Test
  assert(uint === 0xc3.U)
}

How do I create a Bundle from a UInt?

Use the asTypeOf method to reinterpret the UInt as the type of the Bundle.

import chisel3._

class MyBundle extends Bundle {
  val foo = UInt(4.W)
  val bar = UInt(4.W)
}

class Foo extends RawModule {
  val uint = 0xb4.U
  val bundle = uint.asTypeOf(new MyBundle)
  
  printf(p"$bundle") // Bundle(foo -> 11, bar -> 4)

  // Test
  assert(bundle.foo === 0xb.U)
  assert(bundle.bar === 0x4.U)
}

How can I tieoff a Bundle/Vec to 0?

You can use asTypeOf as above. If you don't want to worry about the type of the thing you are tying off, you can use chiselTypeOf:

import chisel3._
import chisel3.stage.ChiselStage

class MyBundle extends Bundle {
  val foo = UInt(4.W)
  val bar = Vec(4, UInt(1.W))
}

class Foo(typ: MyBundle) extends RawModule {
  val bundleA = IO(Output(typ))
  val bundleB = IO(Output(typ))
  
  // typ is already a Chisel Data Type, so can use it directly here, but you 
  // need to know that bundleA is of type typ
  bundleA := 0.U.asTypeOf(typ)
  
  // bundleB is a Hardware data IO(Output(...)) so need to call chiselTypeOf,
  // but this will work no matter the type of bundleB:
  bundleB := 0.U.asTypeOf(chiselTypeOf(bundleB)) 
}

ChiselStage.emitVerilog(new Foo(new MyBundle))

How do I create a Vec of Bools from a UInt?

Use VecInit given a Seq[Bool] generated using the asBools method.

import chisel3._

class Foo extends RawModule {
  val uint = 0xc.U
  val vec = VecInit(uint.asBools)

  printf(p"$vec") // Vec(0, 0, 1, 1)

  // Test
  assert(vec(0) === false.B)
  assert(vec(1) === false.B)
  assert(vec(2) === true.B)
  assert(vec(3) === true.B)
}

How do I create a UInt from a Vec of Bool?

Use the builtin function asUInt

import chisel3._

class Foo extends RawModule {
  val vec = VecInit(true.B, false.B, true.B, true.B)
  val uint = vec.asUInt

  printf(p"$uint") // 13

  // Test
  // (remember leftmost Bool in Vec is low order bit)
  assert(0xd.U === uint)

}

How do I connect a subset of Bundle fields?

See the DataView cookbook.

Vectors and Registers

Can I make a 2D or 3D Vector?

Yes. Using VecInit you can make Vectors that hold Vectors of Chisel types. Methods fill and tabulate make these multi-dimensional Vectors.

import chisel3._

class MyBundle extends Bundle {
  val foo = UInt(4.W)
  val bar = UInt(4.W)
}

class Foo extends Module {
  //2D Fill
  val twoDVec = VecInit.fill(2, 3)(5.U)
  //3D Fill
  val myBundle = Wire(new MyBundle)
  myBundle.foo := 0xc.U
  myBundle.bar := 0x3.U
  val threeDVec = VecInit.fill(1, 2, 3)(myBundle)
  assert(threeDVec(0)(0)(0).foo === 0xc.U && threeDVec(0)(0)(0).bar === 0x3.U)

  //2D Tabulate
  val indexTiedVec = VecInit.tabulate(2, 2){ (x, y) => (x + y).U }
  assert(indexTiedVec(0)(0) === 0.U)
  assert(indexTiedVec(0)(1) === 1.U)
  assert(indexTiedVec(1)(0) === 1.U)
  assert(indexTiedVec(1)(1) === 2.U)
  //3D Tabulate
  val indexTiedVec3D = VecInit.tabulate(2, 3, 4){ (x, y, z) => (x + y * z).U }
  assert(indexTiedVec3D(0)(0)(0) === 0.U)
  assert(indexTiedVec3D(1)(1)(1) === 2.U)
  assert(indexTiedVec3D(1)(1)(2) === 3.U)
  assert(indexTiedVec3D(1)(1)(3) === 4.U)
  assert(indexTiedVec3D(1)(2)(3) === 7.U)
}

How do I create a Vector of Registers?

Rule! Use Reg of Vec not Vec of Reg!

You create a Reg of type Vec. Because Vecs are a type (like UInt, Bool) rather than a value, we must bind the Vec to some concrete value.

How do I create a Reg of type Vec?

For more information, the API Documentation for Vec provides more information.

import chisel3._

class Foo extends RawModule {
  val regOfVec = Reg(Vec(4, UInt(32.W))) // Register of 32-bit UInts
  regOfVec(0) := 123.U                   // Assignments to elements of the Vec
  regOfVec(1) := 456.U
  regOfVec(2) := 789.U
  regOfVec(3) := regOfVec(0)

  // Reg of Vec of 32-bit UInts initialized to zero
  //   Note that Seq.fill constructs 4 32-bit UInt literals with the value 0
  //   VecInit(...) then constructs a Wire of these literals
  //   The Reg is then initialized to the value of the Wire (which gives it the same type)
  val initRegOfVec = RegInit(VecInit(Seq.fill(4)(0.U(32.W))))
}

Bundles

How do I deal with aliased Bundle fields?

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

class AliasedBundle[T <: Data](gen: T) extends Bundle {
  val foo = gen
  val bar = gen
}
getVerilogString(new Top(new AliasedBundle(UInt(8.W))))
// chisel3.AliasedAggregateFieldException: AliasedBundle contains aliased fields named (bar,foo)
// 	at ... ()
// 	at repl.MdocSession$App9$Top$$anonfun$38$$anonfun$apply$23.apply(cookbook.md:223)
// 	at repl.MdocSession$App9$Top$$anonfun$38$$anonfun$apply$23.apply(cookbook.md:223)
// 	at chisel3.internal.prefix$.apply(prefix.scala:48)
// 	at repl.MdocSession$App9$Top$$anonfun$38.apply(cookbook.md:223)
// 	at repl.MdocSession$App9$Top$$anonfun$38.apply(cookbook.md)
// 	at chisel3.internal.plugin.package$.autoNameRecursively(package.scala:33)
// 	at repl.MdocSession$App9$Top.<init>(cookbook.md:223)
// 	at repl.MdocSession$App9$$anonfun$43$$anonfun$apply$25.apply(cookbook.md:242)
// 	at repl.MdocSession$App9$$anonfun$43$$anonfun$apply$25.apply(cookbook.md:242)
// 	at ... ()
// 	at ... (Stack trace trimmed to user code only. Rerun with --full-stacktrace to see the full stack trace)

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:

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.

getVerilogString(new Top(new AlsoAliasedBundle(UInt(8.W))))
// chisel3.AliasedAggregateFieldException: AlsoAliasedBundle contains aliased fields named (foo,gen)
// 	at ... ()
// 	at repl.MdocSession$App9$Top$$anonfun$38$$anonfun$apply$23.apply(cookbook.md:223)
// 	at repl.MdocSession$App9$Top$$anonfun$38$$anonfun$apply$23.apply(cookbook.md:223)
// 	at chisel3.internal.prefix$.apply(prefix.scala:48)
// 	at repl.MdocSession$App9$Top$$anonfun$38.apply(cookbook.md:223)
// 	at repl.MdocSession$App9$Top$$anonfun$38.apply(cookbook.md)
// 	at chisel3.internal.plugin.package$.autoNameRecursively(package.scala:33)
// 	at repl.MdocSession$App9$Top.<init>(cookbook.md:223)
// 	at repl.MdocSession$App9$$anonfun$45$$anonfun$apply$26.apply(cookbook.md:261)
// 	at repl.MdocSession$App9$$anonfun$45$$anonfun$apply$26.apply(cookbook.md:261)
// 	at ... ()
// 	at ... (Stack trace trimmed to user code only. Rerun with --full-stacktrace to see the full stack trace)

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):

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:

getVerilogString(new Top(new UsingAFunctionBundle(() => UInt(8.W))))

Warning: you must ensure that gen creates fresh objects rather than capturing an already constructed value:

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)
// chisel3.AutoClonetypeException: Automatically cloned UsingAFunctionBundle has field 'foo' aliased with base UsingAFunctionBundle. In the future, this will be solved automatically by the compiler plugin. For now, ensure Chisel types used in the Bundle definition are passed through constructor arguments, or wrapped in Input(...), Output(...), or Flipped(...) if appropriate. As a last resort, you can override cloneType manually.
// 	at ... ()
// 	at repl.MdocSession$App9$$anonfun$47$MisusedFunctionArguments$1$$anonfun$50$$anonfun$apply$31.apply(cookbook.md:292)
// 	at repl.MdocSession$App9$$anonfun$47$MisusedFunctionArguments$1$$anonfun$50$$anonfun$apply$31.apply(cookbook.md:292)
// 	at chisel3.internal.prefix$.apply(prefix.scala:48)
// 	at repl.MdocSession$App9$$anonfun$47$MisusedFunctionArguments$1$$anonfun$50.apply(cookbook.md:292)
// 	at repl.MdocSession$App9$$anonfun$47$MisusedFunctionArguments$1$$anonfun$50.apply(cookbook.md)
// 	at chisel3.internal.plugin.package$.autoNameRecursively(package.scala:33)
// 	at repl.MdocSession$App9$$anonfun$47$MisusedFunctionArguments$1.<init>(cookbook.md:292)
// 	at repl.MdocSession$App9$$anonfun$47$$anonfun$apply$33.apply(cookbook.md:294)
// 	at repl.MdocSession$App9$$anonfun$47$$anonfun$apply$33.apply(cookbook.md:294)
// 	at ... ()
// 	at ... (Stack trace trimmed to user code only. Rerun with --full-stacktrace to see the full stack trace)

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:

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:

getVerilogString(new Top(new UsingByNameParameters(UInt(8.W))))

Note that as this is just syntactic sugar over (1), the same warning applies.

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.

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

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. While we try to hide this implementation detail from the user, .cloneType is the mechanism by which Chisel creates fresh instances of Data objects:

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

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

The advised way is to use ChiselEnum to construct enumerated types representing the state of the FSM. State transitions are then handled with switch/is and when/.elsewhen/.otherwise.

import chisel3._
import chisel3.util.{switch, is}
import chisel3.experimental.ChiselEnum

object DetectTwoOnes {
  object State extends ChiselEnum {
    val sNone, sOne1, sTwo1s = Value
  }
}

/* This FSM detects two 1's one after the other */
class DetectTwoOnes extends Module {
  import DetectTwoOnes.State
  import DetectTwoOnes.State._

  val io = IO(new Bundle {
    val in = Input(Bool())
    val out = Output(Bool())
    val state = Output(State())
  })

  val state = RegInit(sNone)

  io.out := (state === sTwo1s)
  io.state := state

  switch (state) {
    is (sNone) {
      when (io.in) {
        state := sOne1
      }
    }
    is (sOne1) {
      when (io.in) {
        state := sTwo1s
      } .otherwise {
        state := sNone
      }
    }
    is (sTwo1s) {
      when (!io.in) {
        state := sNone
      }
    }
  }
}

Note: the is statement can take multiple conditions e.g. is (sTwo1s, sOne1) { ... }.

How do I unpack a value ("reverse concatenation") like in Verilog?

In Verilog, you can do something like the following which will unpack a the value z:

wire [1:0] a;
wire [3:0] b;
wire [2:0] c;
wire [8:0] z = [...];
assign {a,b,c} = z;

Unpacking often corresponds to reinterpreting an unstructured data type as a structured data type. Frequently, this structured type is used prolifically in the design, and has been declared as in the following example:

import chisel3._

class MyBundle extends Bundle {
  val a = UInt(2.W)
  val b = UInt(4.W)
  val c = UInt(3.W)
}

The easiest way to accomplish this in Chisel would be:

class Foo extends RawModule {
  val z = Wire(UInt(9.W))
  z := DontCare // This is a dummy connection
  val unpacked = z.asTypeOf(new MyBundle)
  printf("%d", unpacked.a)
  printf("%d", unpacked.b)
  printf("%d", unpacked.c)
}

If you really need to do this for a one-off case (Think thrice! It is likely you can better structure the code using bundles), then rocket-chip has a Split utility which can accomplish this.

How do I do subword assignment (assign to some bits in a UInt)?

You may try to do something like the following where you want to assign only some bits of a Chisel type. Below, the left-hand side connection to io.out(0) is not allowed.

import chisel3._
import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation}

class Foo extends Module {
  val io = IO(new Bundle {
    val bit = Input(Bool())
    val out = Output(UInt(10.W))
  })
  io.out(0) := io.bit
}

If you try to compile this, you will get an error.

(new ChiselStage).execute(Array("-X", "verilog"), Seq(new ChiselGeneratorAnnotation(() => new Foo)))
// chisel3.internal.ChiselException: Cannot reassign to read-only Foo.?: OpResult[Bool]
// 	at ... ()
// 	at repl.MdocSession$App16$Foo.<init>(cookbook.md:450)
// 	at repl.MdocSession$App16$$anonfun$81$$anonfun$apply$47.apply(cookbook.md:458)
// 	at repl.MdocSession$App16$$anonfun$81$$anonfun$apply$47.apply(cookbook.md:458)
// 	at ... ()
// 	at ... (Stack trace trimmed to user code only. Rerun with --full-stacktrace to see the full stack trace)

Chisel3 does not support subword assignment. The reason for this is that subword assignment generally hints at a better abstraction with an aggregate/structured types, i.e., a Bundle or a Vec.

If you must express it this way, one approach is to blast your UInt to a Vec of Bool and back:

import chisel3._

class Foo extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(10.W))
    val bit = Input(Bool())
    val out = Output(UInt(10.W))
  })
  val bools = VecInit(io.in.asBools)
  bools(0) := io.bit
  io.out := bools.asUInt
}

How do I create an optional I/O?

The following example is a module which includes the optional port out2 only if the given parameter is true.

import chisel3._

class ModuleWithOptionalIOs(flag: Boolean) extends Module {
  val io = IO(new Bundle {
    val in = Input(UInt(12.W))
    val out = Output(UInt(12.W))
    val out2 = if (flag) Some(Output(UInt(12.W))) else None
  })

  io.out := io.in
  if (flag) {
    io.out2.get := io.in
  }
}

The following is an example where an entire IO is optional:

import chisel3._

class ModuleWithOptionalIO(flag: Boolean) extends Module {
  val in = if (flag) Some(IO(Input(Bool()))) else None
  val out = IO(Output(Bool()))

  out := in.getOrElse(false.B)
}

How do I minimize the number of bits used in an output vector?

Use inferred width and a Seq instead of a Vec:

Consider:

import chisel3._

// Count the number of set bits up to and including each bit position
class CountBits(width: Int) extends Module {
  val bits = IO(Input(UInt(width.W)))
  val countSequence = Seq.tabulate(width)(i => IO(Output(UInt())))
  val countVector = IO(Output(Vec(width, UInt())))
  countSequence.zipWithIndex.foreach { case (port, i) =>
    port := util.PopCount(bits(i, 0))
  }
  countVector := countSequence
}

Unlike Vecs which represent a singular Chisel type and must have the same width for every element, Seq is a purely Scala construct, so their elements are independent from the perspective of Chisel and can have different widths.

module CountBits(
  input        clock,
  input        reset,
  input  [3:0] bits,
  output       countSequence_0,
  output [1:0] countSequence_1,
  output [1:0] countSequence_2,
  output [2:0] countSequence_3,
  output [2:0] countVector_0,
  output [2:0] countVector_1,
  output [2:0] countVector_2,
  output [2:0] countVector_3
);

Predictable Naming

How do I get Chisel to name signals properly in blocks like when/withClockAndReset?

Use the compiler plugin, and check out the Naming Cookbook if that still does not do what you want.

How do I get Chisel to name the results of vector reads properly?

Currently, name information is lost when using dynamic indexing. For example:

class Foo extends Module {
  val io = IO(new Bundle {
    val in = Input(Vec(4, Bool()))
    val idx = Input(UInt(2.W))
    val en = Input(Bool())
    val out = Output(Bool())
  })

  val x = io.in(io.idx)
  val y = x && io.en
  io.out := y
}

The above code loses the x name, instead using _GEN_3 (the other _GEN_* signals are expected).

module Foo(
  input        clock,
  input        reset,
  input        io_in_0,
  input        io_in_1,
  input        io_in_2,
  input        io_in_3,
  input  [1:0] io_idx,
  input        io_en,
  output       io_out
);
  wire  _GEN_1; // @[main.scala 15:13]
  wire  _GEN_2; // @[main.scala 15:13]
  wire  _GEN_3; // @[main.scala 15:13]
  assign _GEN_1 = 2'h1 == io_idx ? io_in_1 : io_in_0; // @[main.scala 15:13]
  assign _GEN_2 = 2'h2 == io_idx ? io_in_2 : _GEN_1; // @[main.scala 15:13]
  assign _GEN_3 = 2'h3 == io_idx ? io_in_3 : _GEN_2; // @[main.scala 15:13]
  assign io_out = _GEN_3 & io_en; // @[main.scala 16:10]
endmodule

This can be worked around by creating a wire and connecting the dynamic index to the wire:

val x = WireInit(io.in(io.idx))

Which produces:

module Foo(
  input        clock,
  input        reset,
  input        io_in_0,
  input        io_in_1,
  input        io_in_2,
  input        io_in_3,
  input  [1:0] io_idx,
  input        io_en,
  output       io_out
);
  wire  _GEN_1;
  wire  _GEN_2;
  wire  x;
  assign _GEN_1 = 2'h1 == io_idx ? io_in_1 : io_in_0;
  assign _GEN_2 = 2'h2 == io_idx ? io_in_2 : _GEN_1;
  assign x = 2'h3 == io_idx ? io_in_3 : _GEN_2;
  assign io_out = x & io_en; // @[main.scala 16:10]
endmodule

How can I dynamically set/parametrize the name of a module?

You can override the desiredName function. This works with normal Chisel modules and BlackBoxes. Example:

import chisel3._

class Coffee extends BlackBox {
    val io = IO(new Bundle {
        val I = Input(UInt(32.W))
        val O = Output(UInt(32.W))
    })
    override def desiredName = "Tea"
}

class Salt extends Module {
    val io = IO(new Bundle {})
    val drink = Module(new Coffee)
    override def desiredName = "SodiumMonochloride"
}

Elaborating the Chisel module Salt yields our "desired names" for Salt and Coffee in the output Verilog:

import chisel3.stage.ChiselStage

ChiselStage.emitVerilog(new Salt)
module SodiumMonochloride(
  input   clock,
  input   reset
);
  wire [31:0] drink_I; // @[cookbook.md 591:23]
  wire [31:0] drink_O; // @[cookbook.md 591:23]
  Tea drink ( // @[cookbook.md 591:23]
    .I(drink_I),
    .O(drink_O)
  );
  assign drink_I = 32'h0;
endmodule

Directionality

How do I strip directions from a bidirectional Bundle (or other Data)?

Given a bidirectional port like a Decoupled, you will get an error if you try to connect it directly to a register:

import chisel3.util.Decoupled
class BadRegConnect extends Module {
  val io = IO(new Bundle {
    val enq = Decoupled(UInt(8.W))
  })
  
  val monitor = Reg(chiselTypeOf(io.enq))
  monitor := io.enq
}
ChiselStage.emitVerilog(new BadRegConnect)
// firrtl.passes.CheckHighFormLike$RegWithFlipException:  @[cookbook.md 622:20]: [module BadRegConnect] Register monitor cannot be a bundle type with flips.

While there is no construct to "strip direction" in Chisel3, wrapping a type in Output(...) (the default direction in Chisel3) will set all of the individual elements to output direction. This will have the desired result when used to construct a Register:

import chisel3.util.Decoupled
class CoercedRegConnect extends Module {
  val io = IO(new Bundle {
    val enq = Flipped(Decoupled(UInt(8.W)))
  })
  
  // Make a Reg which contains all of the bundle's signals, regardless of their directionality
  val monitor = Reg(Output(chiselTypeOf(io.enq)))
  // Even though io.enq is bidirectional, := will drive all fields of monitor with the fields of io.enq
  monitor := io.enq
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment