1 of 48

Chisel Breakdown 2

Jack Koenig, SiFive

2 of 48

Purpose of this Talk

  • Teach current and potential developers of the Chisel codebase (or related projects) about the implementation of Chisel
    • Users do not need to know most of this
  • Primary focus is on the “weird stuff”
  • I’m assuming moderate knowledge of using Chisel and Scala
  • I apologize in advance if this talk seems to jump a lot, there is a lot to cover and lots of things are related
  • Give an updated version of this talk (last updated April 2019)

2

3 of 48

What Is Chisel? (from many past talks)

  • Hardware Construction Language Embedded in Scala
  • NOT high-level synthesis (HLS)
  • Domain Specific Language where the domain is digital design
  • Register, Mux, Wire as objects in Scala, use modern programming language to productively put them together
    • Parameterized types
    • Object-oriented programming
    • Functional programming
    • Static Typing
  • Embedded in Scala, meta-programming language and the actual hardware construction language are the same
  • Intended as a platform upon which to build higher-level abstractions

3

4 of 48

What Is Chisel… Really?

  • A library of Scala objects and classes for representing hardware
    • Types: eg. UInt, SInt, Vec, Bundle, Clock, AsyncReset
    • Hardware: eg. Reg, Wire, IO, Mux, Mem
    • Structure: eg. Module, when
  • A hardware graph
    • Chisel IR (not to be confused with CHIRRTL or FIRRTL)
    • Chisel functions construct IR nodes and mutate global data structures
    • Chisel IR can be Emitted or Converted to CHIRRTL
  • A Scala compiler plugin (new in v3.4.0)
    • Naming
    • AutoCloneType2 (new in v3.4.3)
  • A lean frontend for FIRRTL
    • Some information is not available

4

5 of 48

Chisel To Verilog

5

6 of 48

Where are errors caught?

  • Scala compilation
    • Eg. Cannot call + on a Bundle
  • Chisel elaboration (Scala runtime)
    • Eg. Binding exceptions, bad connections
  • FIRRTL compilation
    • Eg. Initialization, invalid reset, combinational loop detection
  • Verilog compilation
    • Usually a bug in Chisel/FIRRTL
  • Simulation
    • Logical errors

6

7 of 48

Simple Elaboration Example

DefWire(_, UInt(8.W))

DefPrim(_, _wire_T, BitAndOp, Seq(foo, bar))

Connect(_, wire, _wire_T)

7

In ChiselIR, these are called Commands

Stored in the containing Module (called Component in ChiselIR)

val wire = Wire(UInt(8.W))�wire := foo & bar

8 of 48

Binding (Chisel’s type system)

  • The Scala Type of a Chisel Type and a Chisel Hardware Value are the same
  • Chisel elaboration differentiates via Binding

val template: UInt = UInt(8.W) // This is a Typeval wire: UInt = Wire(template) // This is a Value�template.getClass == wire.getClass // true�template === wire // Binding exception, template is not hardware

8

9 of 48

Chisel Project Structure

  • plugin
    • Scalac compiler plugin
  • macros
  • core
    • Bulk of Chisel implementation
  • chisel3 (src/main/scala)
    • Stage/Phase/main
    • util
  • test (src/test/scala)
    • This is where tests/examples go
  • docs
    • Documentation (using mdoc)

9

We use subprojects because a Scala macro cannot be used in the same compilation unit within which it was defined

10 of 48

macros/

  • Contains useful macros for Chisel
  • RuntimeDeprecated (@runtimeDeprecated(...))
  • RangeTransform (range”...”)
    • Interval Types
  • ChiselName (@chiselName)
    • Provide good names beyond reflective naming — replaced by plugin naming
  • SourceInfoTransform
    • Despite the name, does not provide source locators
    • Allows for chaining apply (bit extraction) after Chisel functions
    • More on this later

10

11 of 48

core/

  • Definitions of user-facing Chisel classes and objects
    • Eg. Module, Data, Wire, when, printf
  • CompileOptions (macro and materializer)
  • internal/
    • Builder
    • firrtl/{IR, Converter} — aka ChiselIR
    • SourceInfo (macro and materializer)
      • Creates source locators
      • Defined in core/ to prevent materialization of source locators in core/

11

12 of 48

chisel3 (src/main/scala/chisel3/)

  • stage/ChiselStage.scala (and friends)
    • This is where you invoke Chisel
    • Used for constructing custom compiler flows
  • compatibility.scala
    • import Chisel._
  • util
    • Decoupled, Queue, Arbiter, log2Ceil
  • aop
    • Aspect-oriented programming library
  • internal/firrtl/Emitter.scala

12

13 of 48

plugin/

  • Scalac compiler plugin — runs as part of Scala compilation
  • ChiselComponent
    • Provide good names beyond reflective naming
  • BundleComponent
    • Autoclonetype2
    • Someday hope to implement Bundle.elements (for performance and maybe better errors)

13

14 of 48

docs/ - Documentation That Works

  • docs/src
    • Run with: sbt docs/mdoc
    • Results in: docs/generated
  • Uses mdoc to make sure code examples are compiled and run
  • Can use result of running code examples in emitted mdoc
    • Eg. show Verilog emitted by example

14

15 of 48

docs/ - Documentation That Works

### Bundle Literals <a name="bundle-literals"></a>��Bundle literals can be constructed via an experimental import:��```scala mdoc�import chisel3._�import chisel3.experimental.BundleLiterals._��class MyBundle extends Bundle {� val a = UInt(8.W)� val b = Bool()�}��class Example extends RawModule {� val out = IO(Output(new MyBundle))� out := (new MyBundle).Lit(_.a -> 8.U, _.b -> true.B)�}�```��```scala mdoc:verilog�chisel3.stage.ChiselStage.emitVerilog(new Example)�```

15

16 of 48

ChiselStage (replaces Driver)

  • src/main/scala/chisel3/stage/ChiselStage.scala
  • User-facing API for invoking Chisel
  • Used by ChiselMain.main
    • Command-line interface
  • Lots of utility methods for generating Chirrtl, FIRRTL, Verilog
  • class ChiselStage vs. object ChiselStage
    • The class writes files and is used for custom flows
    • The object does not write files, used for little examples / test
    • This distinction isn’t very clear, object ChiselStage to be replaced

16

17 of 48

Builder

  • core/src/main/scala/chisel3/internal/Builder.scala
  • chisel3.internal.Builder
  • Internal “runtime”
  • Internal entry point into Chisel elaboration
  • Interface for calls into DynamicContext and ChiselContext
    • Contain global mutable state needed during elaboration
    • Wraps each in scala.util.DynamicVariable
      • Makes global state thread local
      • Allows multiple invocations of [single-threaded] Chisel elaboration
  • All calls mutating global state must go through the Builder
    • Eg. currentModule, pushOp, error

17

18 of 48

CompileOptions (Compatibility Mode)

  • Options to enable Chisel2-like semantics within Chisel3
  • Essentially a set of options that are implicitly created by importing chisel3._ or Chisel._
  • Implicitly passed to Chisel functions and alter behavior
    • chisel3 uses ExplicitCompileOptions.Strict
    • Compatibility mode uses ExplicitCompileOptions.NotStrict
  • It is possible (but ill-advised) to mix and match specific options
    • See src/test/scala/chiselTests/CompileOptionsTest.scala

18

19 of 48

Source Locators

Recall that SourceInfoTransform is defined in macros while SourceInfo is defined in core

From Bits.scala (core)

final def & (that: Bool): Bool = macro SourceInfoTransform.thatArg

def do_& (that: Bool)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool

All SourceInfoTransform does is replace invocations of & in user code with do_&

Why is this useful?

19

20 of 48

Source Locators

final def & (that: Bool): Bool = macro SourceInfoTransform.thatArg

def do_& (that: Bool)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool

Allows chaining apply (bit extract) after &

(myWire & 0xf0.U)(5)�myWire.&(0xf0.U)(5)

Without SourceInfoTransform, Scala would look for SourceInfo and CompileOptions in the second parameter list

20

21 of 48

Reflective Naming

  • Recall that the body of a Scala class is the primary constructor
  • By default, Chisel names via Java reflection which allows Chisel to inspect public fields of a given object

class MyModule extends Module {� val io = IO(...) // nameableval wire = Wire(UInt(8.W)) // nameabledef func() = {� val reg = Reg(UInt(8.W) // NOT nameable by reflection� io.out := reg + wire� }�}

21

22 of 48

Chisel Naming Plugin (replaces @chiselName)

The compiler plugin inspects your Chisel source code, finding Data objects that are assigned to vals and inserting both naming and prefixing

class Example extends RawModule {� val a, b, c = IO(Input(UInt(8.W)))� val out = IO(Output(UInt()))� def func() = {� val x = a + b� val y = x - 3.U� y & 0xcf.U� }� val result = func() | 0x8.U� out := result�}

22

23 of 48

Chisel Naming Plugin

def func() = {� val x = a + b� val y = x - 3.U� y & 0xcf.U�}�val result = func() | 0x8.U�out := result

wire [7:0] result_x = a + b; // @[main.scala 11:15]�wire [7:0] result_y = result_x - 8'h3; // @[main.scala 12:15]�wire [7:0] _result_T = result_y & 8'hcf; // @[main.scala 13:7]�assign out = _result_T | 8'h8; // @[main.scala 15:23]

23

24 of 48

Chisel Naming Plugin

def func() = {� // val x = a + bval x = autoNameRecursively("x")(prefix("x")(a + b))� // val y = x - 3.Uval y = autoNameRecursively("y")(prefix("y")(x - 3.U))� y & 0xcf.U�}�// val result = func() | 0x8.Uval result = autoNameRecursively("result")(prefix("result")(func() | 0x8.U))�out := result // := also prefixes based on the LHS

24

25 of 48

CloneType - What & Why

Because Chisel Types are immutable objects, we need to create fresh instances of them in order to create hardware, add directions, etc.

val gen = new Bundle { val foo = Input(UInt(8.W)) } �val io = IO(gen) // We can use gen as a type templateval w = Wire(gen) // multiple times�w <> io

�We need the ability to create copies of any Data object

25

26 of 48

CloneType

  • Implementation detail for getting a copy of a Chisel Type
  • Sealed internal types have internal implementation (obviously)
  • User-defined types (Bundles and Records) need cloneType
    • Usually very formulaic, just want to pass the same arguments to the constructor

class MyBundle(w: Int) extends Bundle {� val field = UInt(w.W)�� override def cloneType: this.type =� new MyBundle(w).asInstanceOf[this.type]�}

26

27 of 48

Plugin AutoCloneType (aka autoclonetype2)

  • Replaces original version which was/is a beautiful reflection mess-terpiece
    • See extra slides for juicy details
  • Improvements in new version
    • Works for non-val parameters
    • Works for inner classes
    • Much faster
  • Introduced in Chisel v3.4.3 as opt-in, will be on by default in Chisel v3.5
    • Technically a backwards incompatible change if you rely on reflective autoclonetype and inherit from a Bundle that switches to using autoclonetype2
    • scalacOptions += "-P:chiselplugin:useBundlePlugin"

27

28 of 48

Plugin AutoCloneType (aka autoclonetype2)

class MyBundle(w: Int) extends Bundle {� val field = UInt(w.W)�� // Generated by the compiler pluginoverride protected def _cloneTypeImpl: Bundle = new MyBundle(w)� override protected def _usingPlugin: Boolean = true�}

abstract class Bundle {� override def cloneType: this.type = {� val clone = _cloneTypeImpl.asInstanceOf[this.type]� checkClone(clone)� clone� }�}

28

29 of 48

Aspects

  • Runs in AspectPhase after Chisel elaboration (but before FIRRTL)
  • Provides Selectors for traversing the elaborated Chisel objects (eg. Modules)
  • The sole result of executing an aspect are additional annotations
  • These annotations can be used by FIRRTL transforms to
    • Insert additional hardware
    • Generate verification collateral
    • Generate physical design collateral
  • Type-safe collateral generation framework for Chisel

29

30 of 48

Questions?

30

31 of 48

Extra / Old Slides

  • Note: Many of the following slides were copied without update from the previous version of this talk (given April 2019)

31

32 of 48

Types

32

33 of 48

Widths

  • Because Chisel is a lean frontend on Chisel, widths are often not known
  • “Why is .getWidth failing, it worked and all I changed was this one small thing!”

val x = io.in * io.in

println(x.getWidth) // 16

val y = WireInit(x * x)

println(y.getWidth) // 32

val z = Wire(UInt())

z := y * y

println(z.getWidth) // Exception

33

34 of 48

Why do we have to wrap Modules in Module(...)

  • We need to insert hooks before and after Module construction
  • For example, to set and unset the current module
    • The current module must be known so that we know within which module given hardware lives

object Foo {

def addOne(x: UInt): UInt = x + 1

}

34

35 of 48

DynamicContext

  • Container of Chisel elaboration global state
  • Examples:
    • Module namespace
    • Module definitions
    • Current module
    • Current implicit clock and reset
    • Errors (Recoverable ones that can be aggregated and reported later)

35

36 of 48

ChiselContext

  • Mutable global state that can appear outside of elaboration
  • Enables instantiating Chisel types (UInt, Bundle, etc.) outside of Chisel elaboration (eg. in chiseltest)
  • Examples:
    • Id generation
    • Prefix Stack

36

37 of 48

Record vs. Bundle

  • Bundle is like a struct
  • Record is the super class of Bundle for programmatic generation of elements
  • Requires the user to implement cloneType and elements

val elements: ListMap[String, Data]

Bundles use reflection to find and generate these elements

class MyBundle {

val foo = UInt(8.W)

}

37

38 of 48

IntelliJ/IDE Tips

  • Run sbt compile on the command-line before loading the project in or exporting project to IntelliJ
  • Historically, IntelliJ projects do not generically support code generation
  • Thus code generation in Chisel and FIRRTL SBT configuration does not work
  • If you modify any of the code generation (BuildInfo, ANTLR, ProtoBuf), rerun sbt compile on the command-line
  • Some day all of this will be fixed by the Scala Build Server Protocol
    • see Bloop

38

39 of 48

@chiselName

@chiselName is a macro that inspects the body of any class or object and finds Chisel Data elements that get assigned to a val which it then names

@chiselName

class MyModule extends Module {

val io = IO(...) // nameable

val wire = Wire(UInt(8.W)) // nameable

def func() = {

val reg = Reg(UInt(8.W) // nameable by @chiselName

io.out := reg + wire

}

}

39

40 of 48

Auto-CloneType

  • A beautiful reflection mess-terpiece
  • Automatically collects arguments to default constructor and constructs new instances with them
  • Arguments must be vals (fields of the class) so that reflection can find them
  • Suggests making arguments vals if they are not

class MyBundle(val width: Int) extends Bundle {

val field = UInt(width.W)

}

What about inner classes?

40

41 of 48

Auto-CloneType (Inner Classes)

  • Also known as Path-dependent Types
  • The type of the inner class is dependent on the instance of the outer class
  • The instance of the outer class is an implicit argument to the constructor
  • For normal inner classes, Scala reflection provides the outer object

class MyModule(width: Int) extends Module {

class MyBundle extends Bundle {

val field = UInt(width.W)

}

}

41

42 of 48

Auto-CloneType (Inner Classes)

  • Let’s expand that previous example

class MyModule(width: Int) extends Module { self =>

class MyBundle(outer: MyModule) extends Bundle {

val field = UInt(outer.width.W)

}

val io = IO(Output(new MyBundle(self)))

}

42

43 of 48

Auto-CloneType (Anonymous Inner Classes)

  • Unfortunately, for anonymous inner classes, Scala reflection does not know the outer object
  • Fortunately, Chisel knows the current Module, so for anonymous Bundles we can just guess that the outer object is the current Module

class MyModule(width: Int) extends Module {

val io = IO(new Bundle {

val field = Output(UInt(width.W))

})

}

43

44 of 48

Auto-CloneType (Nested Bundles)

  • What about outer Bundles?

class MyOuterBundle extends Bundle {

val foo = new Bundle {

val bar = UInt(8.W)

}

}

44

45 of 48

Auto-CloneType (Nested Bundles)

  • Unfortunately, since Bundles are not wrapped in some function call (like Module(...)), we don’t know the “current Bundle”
  • Instead, upon construction of Bundles we collect stack traces so that we can guess the “outer Bundle”
  • Yep, I told you it was a mess-terpiece

45

46 of 48

A word on package.scala

package object chisel3 { …

In Scala, all defs and vals must be defined in an object or class. This lets us define defs and vals that are included in import chisel3._

Why is this useful? For example, type aliasing:�� @deprecated("MultiIOModule is now just Module", "Chisel 3.5")� type MultiIOModule = chisel3.Module�We used to use this for all types, but type aliasing results�in bad API docs. Used extensively for import Chisel._

46

47 of 48

First, some FAQ

Q: Why multiple projects?�A: Scala macros cannot be used in the same project where they were defined

Q: Why {project}/src/main/scala?�A: This is where SBT expects source files, this comes from Maven

47

48 of 48

Chisel Simulation Flow

48