diff options
| author | Jack Koenig | 2021-09-17 21:01:26 -0700 |
|---|---|---|
| committer | Jack Koenig | 2021-09-17 21:01:26 -0700 |
| commit | 5c8c19345e6711279594cf1f9ddab33623c8eba7 (patch) | |
| tree | d9d6ced3934aa4a8be3dec19ddcefe50a7a93d5a /docs/src/explanations | |
| parent | e63b9667d89768e0ec6dc8a9153335cb48a213a7 (diff) | |
| parent | 958904cb2f2f65d02b2ab3ec6d9ec2e06d04e482 (diff) | |
Merge branch 'master' into 3.5-release
Diffstat (limited to 'docs/src/explanations')
24 files changed, 2705 insertions, 44 deletions
diff --git a/docs/src/explanations/annotations.md b/docs/src/explanations/annotations.md index 19d24605..510ebca5 100644 --- a/docs/src/explanations/annotations.md +++ b/docs/src/explanations/annotations.md @@ -4,6 +4,8 @@ title: "Annotations" section: "chisel3" --- +# Annotations + `Annotation`s are metadata containers associated with zero or more "things" in a FIRRTL circuit. Commonly, `Annotation`s are used to communicate information from Chisel to a specific, custom FIRRTL `Transform`. In this way `Annotation`s can be viewed as the "arguments" that a specific `Transform` consumes. @@ -132,10 +134,8 @@ class ModC(widthC: Int) extends Module { Compiling this circuit to Verilog will then result in the `InfoTransform` running and the added `println`s showing information about the components annotated. -```scala mdoc +```scala mdoc:compile-only import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation} -// This currently doesn't work because of mdoc limitations. However, it will work -// in your normal Scala code. -//(new ChiselStage).execute(Array.empty, Seq(ChiselGeneratorAnnotation(() => new ModC(4)))) +(new ChiselStage).execute(Array.empty, Seq(ChiselGeneratorAnnotation(() => new ModC(4)))) ``` diff --git a/docs/src/explanations/blackboxes.md b/docs/src/explanations/blackboxes.md index a8d5fe03..4ecd1ea0 100644 --- a/docs/src/explanations/blackboxes.md +++ b/docs/src/explanations/blackboxes.md @@ -9,7 +9,7 @@ for hardware constructs that cannot be described in Chisel and for connecting to Modules defined as a `BlackBox` will be instantiated in the generated Verilog, but no code will be generated to define the behavior of module. -Unlike Module, `BlackBox` has no implicit clock and reset. +Unlike Module, `BlackBox` has no implicit clock and reset. `BlackBox`'s clock and reset ports must be explicitly declared and connected to input signals. Ports declared in the IO Bundle will be generated with the requested name (ie. no preceding `io_`). @@ -34,7 +34,7 @@ class IBUFDS extends BlackBox(Map("DIFF_TERM" -> "TRUE", } class Top extends Module { - val io = IO(new Bundle{}) + val io = IO(new Bundle {}) val ibufds = Module(new IBUFDS) // connecting one of IBUFDS's input clock ports to Top's clock signal ibufds.io.I := clock @@ -52,12 +52,14 @@ IBUFDS #(.DIFF_TERM("TRUE"), .IOSTANDARD("DEFAULT")) ibufds ( ``` ### Providing Implementations for Blackboxes + Chisel provides the following ways of delivering the code underlying the blackbox. Consider the following blackbox that adds two real numbers together. The numbers are represented in chisel3 as 64-bit unsigned integers. + ```scala mdoc:silent:reset import chisel3._ class BlackBoxRealAdd extends BlackBox { - val io = IO(new Bundle() { + val io = IO(new Bundle { val in1 = Input(UInt(64.W)) val in2 = Input(UInt(64.W)) val out = Output(UInt(64.W)) @@ -66,6 +68,7 @@ class BlackBoxRealAdd extends BlackBox { ``` The implementation is described by the following verilog + ```verilog module BlackBoxRealAdd( input [63:0] in1, @@ -73,39 +76,43 @@ module BlackBoxRealAdd( output reg [63:0] out ); always @* begin - out <= $realtobits($bitstoreal(in1) + $bitstoreal(in2)); + out <= $realtobits($bitstoreal(in1) + $bitstoreal(in2)); end endmodule ``` ### Blackboxes with Verilog in a Resource File -In order to deliver the verilog snippet above to the backend simulator, chisel3 provides the following tools based on the chisel/firrtl [annotation system](../explanations/annotations). Add the trait ```HasBlackBoxResource``` to the declaration, and then call a function in the body to say where the system can find the verilog. The Module now looks like -```mdoc scala:silent:reset + +In order to deliver the verilog snippet above to the backend simulator, chisel3 provides the following tools based on the chisel/firrtl [annotation system](../explanations/annotations). Add the trait `HasBlackBoxResource` to the declaration, and then call a function in the body to say where the system can find the verilog. The Module now looks like + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.util.HasBlackBoxResource + class BlackBoxRealAdd extends BlackBox with HasBlackBoxResource { - val io = IO(new Bundle() { + val io = IO(new Bundle { val in1 = Input(UInt(64.W)) val in2 = Input(UInt(64.W)) val out = Output(UInt(64.W)) }) - setResource("/real_math.v") + addResource("/real_math.v") } ``` -The verilog snippet above gets put into a resource file names ```real_math.v```. What is a resource file? It comes from + +The verilog snippet above gets put into a resource file names `real_math.v`. What is a resource file? It comes from a java convention of keeping files in a project that are automatically included in library distributions. In a typical chisel3 project, see [chisel-template](https://github.com/ucb-bar/chisel-template), this would be a directory in the - source hierarchy -``` -src/main/resources/real_math.v -``` + source hierarchy: `src/main/resources/real_math.v`. ### Blackboxes with In-line Verilog -It is also possible to place this verilog directly in the scala source. Instead of ```HasBlackBoxResource``` use - ```HasBlackBoxInline``` and instead of ```setResource``` use ```setInline```. The code will look like this. +It is also possible to place this verilog directly in the scala source. Instead of `HasBlackBoxResource` use + `HasBlackBoxInline` and instead of `setResource` use `setInline`. The code will look like this. + ```scala mdoc:silent:reset import chisel3._ import chisel3.util.HasBlackBoxInline class BlackBoxRealAdd extends BlackBox with HasBlackBoxInline { - val io = IO(new Bundle() { + val io = IO(new Bundle { val in1 = Input(UInt(64.W)) val in2 = Input(UInt(64.W)) val out = Output(UInt(64.W)) @@ -123,15 +130,16 @@ class BlackBoxRealAdd extends BlackBox with HasBlackBoxInline { """.stripMargin) } ``` -This technique will copy the inline verilog into the target directory under the name ```BlackBoxRealAdd.v``` + +This technique will copy the inline verilog into the target directory under the name `BlackBoxRealAdd.v` ### Under the Hood This mechanism of delivering verilog content to the testing backends is implemented via chisel/firrtl annotations. The -two methods, inline and resource, are two kinds of annotations that are created via the ```setInline``` and -```setResource``` methods calls. Those annotations are passed through to the chisel-testers which in turn passes them +two methods, inline and resource, are two kinds of annotations that are created via the `setInline` and +`setResource` methods calls. Those annotations are passed through to the chisel-testers which in turn passes them on to firrtl. The default firrtl verilog compilers have a pass that detects the annotations and moves the files or inline test into the build directory. For each unique file added, the transform adds a line to a file -black_box_verilog_files.f, this file is added to the command line constructed for verilator or vcs to inform them where +`black_box_verilog_files.f`, this file is added to the command line constructed for verilator or vcs to inform them where to look. The [dsptools project](https://github.com/ucb-bar/dsptools) is a good example of using this feature to build a real number simulation tester based on black boxes. @@ -144,4 +152,4 @@ construct scala implementations of the black boxes. The scala implementation co passed down to the interpreter by the execution harness. The interpreter is a scala simulation tester. Once again the dsptools project uses this mechanism and is a good place to look at it. > It is planned that the BlackBoxFactory will be replaced by integration with the annotation based blackbox methods ->stuff soon. +> stuff soon. diff --git a/docs/src/explanations/bundles-and-vecs.md b/docs/src/explanations/bundles-and-vecs.md index dcac31cd..78626c42 100644 --- a/docs/src/explanations/bundles-and-vecs.md +++ b/docs/src/explanations/bundles-and-vecs.md @@ -3,14 +3,12 @@ layout: docs title: "Bundles and Vecs" section: "chisel3" --- -``` - -``` `Bundle` and `Vec` are classes that allow the user to expand the set of Chisel datatypes with aggregates of other types. Bundles group together several named fields of potentially different types into a coherent unit, much like a `struct` in C. Users define their own bundles by defining a class as a subclass of `Bundle`. + ```scala mdoc:silent import chisel3._ class MyFloat extends Bundle { @@ -25,24 +23,12 @@ class ModuleWithFloatWire extends RawModule { } ``` -> Currently, there is no way to create a bundle literal like ```8.U``` for ```UInt```s. Therefore, in order to create ->literals for bundles, we must declare a [[wire|Combinational-Circuits#wires]] of that bundle type, and then assign ->values to it. We are working on a way to declare bundle literals without requiring the creation of a Wire node and ->assigning to it. +You can create literal Bundles using the experimental [Bundle Literals](../appendix/experimental-features#bundle-literals) feature. -```scala mdoc:silent -class ModuleWithFloatConstant extends RawModule { - // Floating point constant. - val floatConst = Wire(new MyFloat) - floatConst.sign := true.B - floatConst.exponent := 10.U - floatConst.significand := 128.U -} -``` - -A Scala convention is to capitalize the name of new classes and we suggest you follow that convention in Chisel too. +Scala convention is to name classes using UpperCamelCase, and we suggest you follow that convention in your Chisel code. Vecs create an indexable vector of elements, and are constructed as follows: + ```scala mdoc:silent class ModuleWithVec extends RawModule { // Vector of 5 23-bit signed integers. @@ -63,6 +49,7 @@ superclass, `Data`. Every object that ultimately inherits from Bundles and Vecs can be arbitrarily nested to build complex data structures: + ```scala mdoc:silent class BigBundle extends Bundle { // Vector of 5 23-bit signed integers. @@ -77,7 +64,7 @@ Note that the builtin Chisel primitive and aggregate classes do not require the `new` when creating an instance, whereas new user datatypes will. A Scala `apply` constructor can be defined so that a user datatype also does not require `new`, as described in -[Function Constructor](../wiki-deprecated/functional-module-creation). +[Function Constructor](../explanations/functional-module-creation). ### Flipping Bundles @@ -152,6 +139,7 @@ Note that in the vast majority of cases, **this is not required** as Chisel can automatically. Here is an example of a parametrized bundle (`ExampleBundle`) that features a custom `cloneType`. + ```scala mdoc:silent class ExampleBundle(a: Int, b: Int) extends Bundle { val foo = UInt(a.W) diff --git a/docs/src/explanations/chisel-enum.md b/docs/src/explanations/chisel-enum.md new file mode 100644 index 00000000..a390aea4 --- /dev/null +++ b/docs/src/explanations/chisel-enum.md @@ -0,0 +1,228 @@ +--- +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"```\n$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"```\n$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 +} +``` + +The enum value type also defines some convenience methods for working with `ChiselEnum` values. For example, continuing with the RISC-V opcode +example, one could easily create hardware signal that is only asserted on LOAD/STORE operations (when the enum value is equal to `Opcode.load` +or `Opcode.store`) using the `.isOneOf` method: + +```scala mdoc +class LoadStoreExample extends Module { + val io = IO(new Bundle { + val opcode = Input(Opcode()) + val load_or_store = Output(Bool()) + }) + io.load_or_store := io.opcode.isOneOf(Opcode.load, Opcode.store) +} +``` + +```scala mdoc:invisible +// Always need to run Chisel to see if there are elaboration errors +ChiselStage.emitVerilog(new LoadStoreExample) +``` + +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). diff --git a/docs/src/explanations/combinational-circuits.md b/docs/src/explanations/combinational-circuits.md new file mode 100644 index 00000000..b9e5b8c6 --- /dev/null +++ b/docs/src/explanations/combinational-circuits.md @@ -0,0 +1,84 @@ +--- +layout: docs +title: "Combinational Circuits" +section: "chisel3" +--- + +# Combinational Circuits + +A circuit is represented as a graph of nodes in Chisel. Each node is +a hardware operator that has zero or more inputs and that drives one +output. A literal, introduced above, is a degenerate kind of node +that has no inputs and drives a constant value on its output. One way +to create and wire together nodes is using textual expressions. For +example, we can express a simple combinational logic circuit +using the following expression: + +```scala +(a & b) | (~c & d) +``` + +The syntax should look familiar, with `&` and `|` +representing bitwise-AND and -OR respectively, and `~` +representing bitwise-NOT. The names `a` through `d` +represent named wires of some (unspecified) width. + +Any simple expression can be converted directly into a circuit tree, +with named wires at the leaves and operators forming the internal +nodes. The final circuit output of the expression is taken from the +operator at the root of the tree, in this example, the bitwise-OR. + +Simple expressions can build circuits in the shape of trees, but to +construct circuits in the shape of arbitrary directed acyclic graphs +(DAGs), we need to describe fan-out. In Chisel, we do this by naming +a wire that holds a subexpression that we can then reference multiple +times in subsequent expressions. We name a wire in Chisel by +declaring a variable. For example, consider the select expression, +which is used twice in the following multiplexer description: +```scala +val sel = a | b +val out = (sel & in1) | (~sel & in0) +``` + +The keyword `val` is part of Scala, and is used to name variables +that have values that won't change. It is used here to name the +Chisel wire, `sel`, holding the output of the first bitwise-OR +operator so that the output can be used multiple times in the second +expression. + +### Wires + +Chisel also supports wires as hardware nodes to which one can assign values or connect other nodes. + +```scala +val myNode = Wire(UInt(8.W)) +when (isReady) { + myNode := 255.U +} .otherwise { + myNode := 0.U +} +``` + +```scala +val myNode = Wire(UInt(8.W)) +when (input > 128.U) { + myNode := 255.U +} .elsewhen (input > 64.U) { + myNode := 1.U +} .otherwise { + myNode := 0.U +} +``` + +Note that the last connection to a Wire takes effect. For example, the following two Chisel circuits are equivalent: + +```scala +val myNode = Wire(UInt(8.W)) +myNode := 10.U +myNode := 0.U +``` + +```scala +val myNode = Wire(UInt(8.W)) +myNode := 0.U +``` diff --git a/docs/src/explanations/data-types.md b/docs/src/explanations/data-types.md new file mode 100644 index 00000000..6ac6077b --- /dev/null +++ b/docs/src/explanations/data-types.md @@ -0,0 +1,136 @@ +--- +layout: docs +title: "Data Types" +section: "chisel3" +--- + +# Chisel Data Types + +Chisel datatypes are used to specify the type of values held in state +elements or flowing on wires. While hardware designs ultimately +operate on vectors of binary digits, other more abstract +representations for values allow clearer specifications and help the +tools generate more optimal circuits. In Chisel, a raw collection of +bits is represented by the ```Bits``` type. Signed and unsigned integers +are considered subsets of fixed-point numbers and are represented by +types ```SInt``` and ```UInt``` respectively. Signed fixed-point +numbers, including integers, are represented using two's-complement +format. Boolean values are represented as type ```Bool```. Note +that these types are distinct from Scala's builtin types such as +```Int``` or ```Boolean```. + +> There is a new experimental type **Interval** which gives the developer more control of the type by allowing the definition of an IntervalRange. See: [Interval Type](../appendix/experimental-features#interval-type) + +Additionally, Chisel defines `Bundles` for making +collections of values with named fields (similar to ```structs``` in +other languages), and ```Vecs``` for indexable collections of +values. + +Bundles and Vecs will be covered later. + +Constant or literal values are expressed using Scala integers or +strings passed to constructors for the types: +```scala +1.U // decimal 1-bit lit from Scala Int. +"ha".U // hexadecimal 4-bit lit from string. +"o12".U // octal 4-bit lit from string. +"b1010".U // binary 4-bit lit from string. + +5.S // signed decimal 4-bit lit from Scala Int. +-8.S // negative decimal 4-bit lit from Scala Int. +5.U // unsigned decimal 3-bit lit from Scala Int. + +8.U(4.W) // 4-bit unsigned decimal, value 8. +-152.S(32.W) // 32-bit signed decimal, value -152. + +true.B // Bool lits from Scala lits. +false.B +``` +Underscores can be used as separators in long string literals to aid +readability, but are ignored when creating the value, e.g.: +```scala +"h_dead_beef".U // 32-bit lit of type UInt +``` + +By default, the Chisel compiler will size each constant to the minimum +number of bits required to hold the constant, including a sign bit for +signed types. Bit widths can also be specified explicitly on +literals, as shown below. Note that (`.W` is used to cast a Scala Int +to a Chisel width) +```scala +"ha".asUInt(8.W) // hexadecimal 8-bit lit of type UInt +"o12".asUInt(6.W) // octal 6-bit lit of type UInt +"b1010".asUInt(12.W) // binary 12-bit lit of type UInt + +5.asSInt(7.W) // signed decimal 7-bit lit of type SInt +5.asUInt(8.W) // unsigned decimal 8-bit lit of type UInt +``` + +For literals of type ```UInt```, the value is +zero-extended to the desired bit width. For literals of type +```SInt```, the value is sign-extended to fill the desired bit width. +If the given bit width is too small to hold the argument value, then a +Chisel error is generated. + +>We are working on a more concise literal syntax for Chisel using +symbolic prefix operators, but are stymied by the limitations of Scala +operator overloading and have not yet settled on a syntax that is +actually more readable than constructors taking strings. + +>We have also considered allowing Scala literals to be automatically +converted to Chisel types, but this can cause type ambiguity and +requires an additional import. + +>The SInt and UInt types will also later support an optional exponent +field to allow Chisel to automatically produce optimized fixed-point +arithmetic circuits. + +## Casting + +We can also cast types in Chisel: + +```scala +val sint = 3.S(4.W) // 4-bit SInt + +val uint = sint.asUInt // cast SInt to UInt +uint.asSInt // cast UInt to SInt +``` + +**NOTE**: `asUInt`/`asSInt` with an explicit width can **not** be used to cast (convert) between Chisel datatypes. +No width parameter is accepted, as Chisel will automatically pad or truncate as required when the objects are connected. + +We can also perform casts on clocks, though you should be careful about this, since clocking (especially in ASIC) requires special attention: + +```scala +val bool: Bool = false.B // always-low wire +val clock = bool.asClock // always-low clock + +clock.asUInt // convert clock to UInt (width 1) +clock.asUInt.asBool // convert clock to Bool (Chisel 3.2+) +clock.asUInt.toBool // convert clock to Bool (Chisel 3.0 and 3.1 only) +``` + +## Analog/BlackBox type + +(Experimental, Chisel 3.1+) + +Chisel supports an `Analog` type (equivalent to Verilog `inout`) that can be used to support arbitrary nets in Chisel. This includes analog wires, tri-state/bi-directional wires, and power nets (with appropriate annotations). + +`Analog` is an undirectioned type, and so it is possible to connect multiple `Analog` nets together using the `attach` operator. It is possible to connect an `Analog` **once** using `<>` but illegal to do it more than once. + +```scala +val a = IO(Analog(1.W)) +val b = IO(Analog(1.W)) +val c = IO(Analog(1.W)) + +// Legal +attach(a, b) +attach(a, c) + +// Legal +a <> b + +// Illegal - connects 'a' multiple times +a <> b +a <> c +``` diff --git a/docs/src/explanations/dataview.md b/docs/src/explanations/dataview.md new file mode 100644 index 00000000..2f229bfc --- /dev/null +++ b/docs/src/explanations/dataview.md @@ -0,0 +1,520 @@ +--- +layout: docs +title: "DataView" +section: "chisel3" +--- + +# DataView + +_New in Chisel 3.5_ + +```scala mdoc:invisible +import chisel3._ +import chisel3.stage.ChiselStage.emitVerilog +``` + +## Introduction + +DataView is a mechanism for "viewing" Scala objects as a subtype of `chisel3.Data`. +Often, this is useful for viewing one subtype of `chisel3.Data`, as another. +One can think about a `DataView` as a mapping from a _Target_ type `T` to a _View_ type `V`. +This is similar to a cast (eg. `.asTypeOf`) with a few differences: +1. Views are _connectable_—connections to the view will occur on the target +2. Whereas casts are _structural_ (a reinterpretation of the underlying bits), a DataView is a customizable mapping +3. Views can be _partial_—not every field in the target must be included in the mapping + +## A Motivating Example (AXI4) + +[AXI4](https://en.wikipedia.org/wiki/Advanced_eXtensible_Interface) is a common interface in digital +design. +A typical Verilog peripheral using AXI4 will define a write channel as something like: +```verilog +module my_module( + // Write Channel + input AXI_AWVALID, + output AXI_AWREADY, + input [3:0] AXI_AWID, + input [19:0] AXI_AWADDR, + input [1:0] AXI_AWLEN, + input [1:0] AXI_AWSIZE, + // ... +); +``` + +This would correspond to the following Chisel Bundle: + +```scala mdoc +class VerilogAXIBundle(val addrWidth: Int) extends Bundle { + val AWVALID = Output(Bool()) + val AWREADY = Input(Bool()) + val AWID = Output(UInt(4.W)) + val AWADDR = Output(UInt(addrWidth.W)) + val AWLEN = Output(UInt(2.W)) + val AWSIZE = Output(UInt(2.W)) + // The rest of AW and other AXI channels here +} + +// Instantiated as +class my_module extends RawModule { + val AXI = IO(new VerilogAXIBundle(20)) +} +``` + +Expressing something that matches a standard Verilog interface is important when instantiating Verilog +modules in a Chisel design as `BlackBoxes`. +Generally though, Chisel developers prefer to use composition via utilities like `Decoupled` rather +than a flat handling of `ready` and `valid` as in the above. +A more "Chisel-y" implementation of this interface might look like: + +```scala mdoc +// Note that both the AW and AR channels look similar and could use the same Bundle definition +class AXIAddressChannel(val addrWidth: Int) extends Bundle { + val id = UInt(4.W) + val addr = UInt(addrWidth.W) + val len = UInt(2.W) + val size = UInt(2.W) + // ... +} +import chisel3.util.Decoupled +// We can compose the various AXI channels together +class AXIBundle(val addrWidth: Int) extends Bundle { + val aw = Decoupled(new AXIAddressChannel(addrWidth)) + // val ar = new AXIAddressChannel + // ... Other channels here ... +} +// Instantiated as +class MyModule extends RawModule { + val axi = IO(new AXIBundle(20)) +} +``` + +Of course, this would result in very different looking Verilog: + +```scala mdoc:verilog +emitVerilog(new MyModule { + override def desiredName = "MyModule" + axi := DontCare // Just to generate Verilog in this stub +}) +``` + +So how can we use our more structured types while maintaining expected Verilog interfaces? +Meet DataView: + +```scala mdoc +import chisel3.experimental.dataview._ + +// We recommend putting DataViews in a companion object of one of the involved types +object AXIBundle { + // Don't be afraid of the use of implicits, we will discuss this pattern in more detail later + implicit val axiView = DataView[VerilogAXIBundle, AXIBundle]( + // The first argument is a function constructing an object of View type (AXIBundle) + // from an object of the Target type (VerilogAXIBundle) + vab => new AXIBundle(vab.addrWidth), + // The remaining arguments are a mapping of the corresponding fields of the two types + _.AWVALID -> _.aw.valid, + _.AWREADY -> _.aw.ready, + _.AWID -> _.aw.bits.id, + _.AWADDR -> _.aw.bits.addr, + _.AWLEN -> _.aw.bits.len, + _.AWSIZE -> _.aw.bits.size, + // ... + ) +} +``` + +This `DataView` is a mapping between our flat, Verilog-style AXI Bundle to our more compositional, +Chisel-style AXI Bundle. +It allows us to define our ports to match the expected Verilog interface, while manipulating it as if +it were the more structured type: + +```scala mdoc +class AXIStub extends RawModule { + val AXI = IO(new VerilogAXIBundle(20)) + val view = AXI.viewAs[AXIBundle] + + // We can now manipulate `AXI` via `view` + view.aw.bits := 0.U.asTypeOf(new AXIAddressChannel(20)) // zero everything out by default + view.aw.valid := true.B + when (view.aw.ready) { + view.aw.bits.id := 5.U + view.aw.bits.addr := 1234.U + // We can still manipulate AXI as well + AXI.AWLEN := 1.U + } +} +``` + +This will generate Verilog that matches the standard naming convention: + +```scala mdoc:verilog +emitVerilog(new AXIStub) +``` + +Note that if both the _Target_ and the _View_ types are subtypes of `Data` (as they are in this example), +the `DataView` is _invertible_. +This means that we can easily create a `DataView[AXIBundle, VerilogAXIBundle]` from our existing +`DataView[VerilogAXIBundle, AXIBundle]`, all we need to do is provide a function to construct +a `VerilogAXIBundle` from an instance of an `AXIBundle`: + +```scala mdoc:silent +// Note that typically you should define these together (eg. inside object AXIBundle) +implicit val axiView2 = AXIBundle.axiView.invert(ab => new VerilogAXIBundle(ab.addrWidth)) +``` + +The following example shows this and illustrates another use case of `DataView`—connecting unrelated +types: + +```scala mdoc +class ConnectionExample extends RawModule { + val in = IO(new AXIBundle(20)) + val out = IO(Flipped(new VerilogAXIBundle(20))) + out.viewAs[AXIBundle] <> in +} +``` + +This results in the corresponding fields being connected in the emitted Verilog: + +```scala mdoc:verilog +emitVerilog(new ConnectionExample) +``` + +## Other Use Cases + +While the ability to map between `Bundle` types as in the AXI4 example is pretty compelling, +DataView has many other applications. +Importantly, because the _Target_ of the `DataView` need not be a `Data`, it provides a way to use +`non-Data` objects with APIs that require `Data`. + +### Tuples + +Perhaps the most helpful use of `DataView` for a non-`Data` type is viewing Scala tuples as `Bundles`. +For example, in Chisel prior to the introduction of `DataView`, one might try to `Mux` tuples and +see an error like the following: + +<!-- Todo will need to ensure built-in code for Tuples is suppressed once added to stdlib --> + +```scala mdoc:fail +class TupleExample extends RawModule { + val a, b, c, d = IO(Input(UInt(8.W))) + val cond = IO(Input(Bool())) + val x, y = IO(Output(UInt(8.W))) + (x, y) := Mux(cond, (a, b), (c, d)) +} +``` + +The issue, is that Chisel primitives like `Mux` and `:=` only operate on subtypes of `Data` and +Tuples (as members of the Scala standard library), are not subclasses of `Data`. +`DataView` provides a mechanism to _view_ a `Tuple` as if it were a `Data`: + +<!-- TODO replace this with stdlib import --> + +```scala mdoc:invisible +// ProductDataProduct +implicit val productDataProduct: DataProduct[Product] = new DataProduct[Product] { + def dataIterator(a: Product, path: String): Iterator[(Data, String)] = { + a.productIterator.zipWithIndex.collect { case (d: Data, i) => d -> s"$path._$i" } + } +} +``` + +```scala mdoc +// We need a type to represent the Tuple +class HWTuple2[A <: Data, B <: Data](val _1: A, val _2: B) extends Bundle + +// Provide DataView between Tuple and HWTuple +implicit def view[A <: Data, B <: Data]: DataView[(A, B), HWTuple2[A, B]] = + DataView(tup => new HWTuple2(tup._1.cloneType, tup._2.cloneType), + _._1 -> _._1, _._2 -> _._2) +``` + +Now, we can use `.viewAs` to view Tuples as if they were subtypes of `Data`: + +```scala mdoc +class TupleVerboseExample extends RawModule { + val a, b, c, d = IO(Input(UInt(8.W))) + val cond = IO(Input(Bool())) + val x, y = IO(Output(UInt(8.W))) + (x, y).viewAs[HWTuple2[UInt, UInt]] := Mux(cond, (a, b).viewAs[HWTuple2[UInt, UInt]], (c, d).viewAs[HWTuple2[UInt, UInt]]) +} +``` + +This is much more verbose than the original idea of just using the Tuples directly as if they were `Data`. +We can make this better by providing an implicit conversion that views a `Tuple` as a `HWTuple2`: + +```scala mdoc +implicit def tuple2hwtuple[A <: Data, B <: Data](tup: (A, B)): HWTuple2[A, B] = + tup.viewAs[HWTuple2[A, B]] +``` + +Now, the original code just works! + +```scala mdoc +class TupleExample extends RawModule { + val a, b, c, d = IO(Input(UInt(8.W))) + val cond = IO(Input(Bool())) + val x, y = IO(Output(UInt(8.W))) + (x, y) := Mux(cond, (a, b), (c, d)) +} +``` + +```scala mdoc:invisible +// Always emit Verilog to make sure it actually works +emitVerilog(new TupleExample) +``` + +Note that this example ignored `DataProduct` which is another required piece (see [the documentation +about it below](#dataproduct)). + +All of this is slated to be included the Chisel standard library. + +## Totality and PartialDataView + +A `DataView` is _total_ if all fields of the _Target_ type and all fields of the _View_ type are +included in the mapping. +Chisel will error if a field is accidentally left out from a `DataView`. +For example: + +```scala mdoc +class BundleA extends Bundle { + val foo = UInt(8.W) + val bar = UInt(8.W) +} +class BundleB extends Bundle { + val fizz = UInt(8.W) +} +``` + +```scala mdoc:crash +{ // Using an extra scope here to avoid a bug in mdoc (documentation generation) +// We forgot BundleA.foo in the mapping! +implicit val myView = DataView[BundleA, BundleB](_ => new BundleB, _.bar -> _.fizz) +class BadMapping extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out := in.viewAs[BundleB] +} +// We must run Chisel to see the error +emitVerilog(new BadMapping) +} +``` + +As that error suggests, if we *want* the view to be non-total, we can use a `PartialDataView`: + +```scala mdoc +// A PartialDataView does not have to be total for the Target +implicit val myView = PartialDataView[BundleA, BundleB](_ => new BundleB, _.bar -> _.fizz) +class PartialDataViewModule extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out := in.viewAs[BundleB] +} +``` + +```scala mdoc:verilog +emitVerilog(new PartialDataViewModule) +``` + +While `PartialDataViews` need not be total for the _Target_, both `PartialDataViews` and `DataViews` +must always be total for the _View_. +This has the consequence that `PartialDataViews` are **not** invertible in the same way as `DataViews`. + +For example: + +```scala mdoc:crash +{ // Using an extra scope here to avoid a bug in mdoc (documentation generation) +implicit val myView2 = myView.invert(_ => new BundleA) +class PartialDataViewModule2 extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + // Using the inverted version of the mapping + out.viewAs[BundleA] := in +} +// We must run Chisel to see the error +emitVerilog(new PartialDataViewModule2) +} +``` + +As noted, the mapping must **always** be total for the `View`. + +## Advanced Details + +`DataView` takes advantage of features of Scala that may be new to many users of Chisel—in particular +[Type Classes](#type-classes). + +### Type Classes + +[Type classes](https://en.wikipedia.org/wiki/Type_class) are powerful language feature for writing +polymorphic code. +They are a common feature in "modern programming languages" like +Scala, +Swift (see [protocols](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html)), +and Rust (see [traits](https://doc.rust-lang.org/book/ch10-02-traits.html)). +Type classes may appear similar to inheritance in object-oriented programming but there are some +important differences: + +1. You can provide a type class for a type you don't own (eg. one defined in a 3rd party library, + the Scala standard library, or Chisel itself) +2. You can write a single type class for many types that do not have a sub-typing relationship +3. You can provide multiple different type classes for the same type + +For `DataView`, (1) is crucial because we want to be able to implement `DataViews` of built-in Scala +types like tuples and `Seqs`. Furthermore, `DataView` has two type parameters (the _Target_ and the +_View_ types) so inheritance does not really make sense—which type would `extend` `DataView`? + +In Scala 2, type classes are not a built-in language feature, but rather are implemented using implicits. +There are great resources out there for interested readers: +* [Basic Tutorial](https://scalac.io/blog/typeclasses-in-scala/) +* [Fantastic Explanation on StackOverflow](https://stackoverflow.com/a/5598107/2483329) + +Note that Scala 3 has added built-in syntax for type classes that does not apply to Chisel 3 which +currently only supports Scala 2. + +### Implicit Resolution + +Given that `DataView` is implemented using implicits, it is important to understand implicit +resolution. +Whenever the compiler sees an implicit argument is required, it first looks in _current scope_ +before looking in the _implicit scope_. + +1. Current scope + * Values defined in the current scope + * Explicit imports + * Wildcard imports +2. Implicit scope + * Companion object of a type + * Implicit scope of an argument's type + * Implicit scope of type parameters + +If at either stage, multiple implicits are found, then the static overloading rule is used to resolve +it. +Put simply, if one implicit applies to a more-specific type than the other, the more-specific one +will be selected. +If multiple implicits apply within a given stage, then the compiler throws an ambiguous implicit +resolution error. + + +This section draws heavily from [[1]](https://stackoverflow.com/a/5598107/2483329) and +[[2]](https://stackoverflow.com/a/5598107/2483329). +In particular, see [1] for examples. + +#### Implicit Resolution Example + +To help clarify a bit, let us consider how implicit resolution works for `DataView`. +Consider the definition of `viewAs`: + +```scala +def viewAs[V <: Data](implicit dataView: DataView[T, V]): V +``` + +Armed with the knowledge from the previous section, we know that whenever we call `.viewAs`, the +Scala compiler will first look for a `DataView[T, V]` in the current scope (defined in, or imported), +then it will look in the companion objects of `DataView`, `T`, and `V`. +This enables a fairly powerful pattern, namely that default or typical implementations of a `DataView` +should be defined in the companion object for one of the two types. +We can think about `DataViews` defined in this way as "low priority defaults". +They can then be overruled by a specific import if a given user ever wants different behavior. +For example: + +Given the following types: + +```scala mdoc +class Foo extends Bundle { + val a = UInt(8.W) + val b = UInt(8.W) +} +class Bar extends Bundle { + val c = UInt(8.W) + val d = UInt(8.W) +} +object Foo { + implicit val f2b = DataView[Foo, Bar](_ => new Bar, _.a -> _.c, _.b -> _.d) + implicit val b2f = f2b.invert(_ => new Foo) +} +``` + +This provides an implementation of `DataView` in the _implicit scope_ as a "default" mapping between +`Foo` and `Bar` (and it doesn't even require an import!): + +```scala mdoc +class FooToBar extends Module { + val foo = IO(Input(new Foo)) + val bar = IO(Output(new Bar)) + bar := foo.viewAs[Bar] +} +``` + +```scala mdoc:verilog +emitVerilog(new FooToBar) +``` + +However, it's possible that some user of `Foo` and `Bar` wants different behavior, +perhaps they would prefer more of "swizzling" behavior rather than a direct mapping: + +```scala mdoc +object Swizzle { + implicit val swizzle = DataView[Foo, Bar](_ => new Bar, _.a -> _.d, _.b -> _.c) +} +// Current scope always wins over implicit scope +import Swizzle._ +class FooToBarSwizzled extends Module { + val foo = IO(Input(new Foo)) + val bar = IO(Output(new Bar)) + bar := foo.viewAs[Bar] +} +``` + +```scala mdoc:verilog +emitVerilog(new FooToBarSwizzled) +``` + +### DataProduct + +`DataProduct` is a type class used by `DataView` to validate the correctness of a user-provided mapping. +In order for a type to be "viewable" (ie. the `Target` type of a `DataView`), it must have an +implementation of `DataProduct`. + +For example, say we have some non-Bundle type: +```scala mdoc +// Loosely based on chisel3.util.Counter +class MyCounter(val width: Int) { + /** Indicates if the Counter is incrementing this cycle */ + val active = WireDefault(false.B) + val value = RegInit(0.U(width.W)) + def inc(): Unit = { + active := true.B + value := value + 1.U + } + def reset(): Unit = { + value := 0.U + } +} +``` + +Say we want to view `MyCounter` as a `Valid[UInt]`: + +```scala mdoc:fail +import chisel3.util.Valid +implicit val counterView = DataView[MyCounter, Valid[UInt]](c => Valid(UInt(c.width.W)), _.value -> _.bits, _.active -> _.valid) +``` + +As you can see, this fails Scala compliation. +We need to provide an implementation of `DataProduct[MyCounter]` which provides Chisel a way to access +the objects of type `Data` within `MyCounter`: + +```scala mdoc:silent +import chisel3.util.Valid +implicit val counterProduct = new DataProduct[MyCounter] { + // The String part of the tuple is a String path to the object to help in debugging + def dataIterator(a: MyCounter, path: String): Iterator[(Data, String)] = + List(a.value -> s"$path.value", a.active -> s"$path.active").iterator +} +// Now this works +implicit val counterView = DataView[MyCounter, Valid[UInt]](c => Valid(UInt(c.width.W)), _.value -> _.bits, _.active -> _.valid) +``` + +Why is this useful? +This is how Chisel is able to check for totality as [described above](#totality-and-partialdataview). +In addition to checking if a user has left a field out of the mapping, it also allows Chisel to check +if the user has included a `Data` in the mapping that isn't actually a part of the _target_ nor the +_view_. + diff --git a/docs/src/explanations/explanations.md b/docs/src/explanations/explanations.md new file mode 100644 index 00000000..01894ad7 --- /dev/null +++ b/docs/src/explanations/explanations.md @@ -0,0 +1,39 @@ +--- +layout: docs +title: "Explanations" +section: "chisel3" +--- + +# Explanations + +Explanation documentation gives background and context. +They can also explain why things are so - design decisions, +historical reasons, technical constraints. + +If you are just getting started with Chisel, we suggest you +read these documents in the following order: + +* [Motivation](motivation) +* [Supported Hardware](supported-hardware) +* [Data Types](data-types) +* [Bundles and Vecs](bundles-and-vecs) +* [Combinational Circuits](combinational-circuits) +* [Operators](operators) +* [Width Inference](width-inference) +* [Functional Abstraction](functional-abstraction) +* [Ports](ports) +* [Modules](modules) +* [Sequential Circuits](sequential-circuits) +* [Memories](memories) +* [Interfaces and Connections](interfaces-and-connections) +* [Black Boxes](blackboxes) +* [Enumerations](enumerations) +* [Functional Module Creation](functional-module-creation) +* [Muxes and Input Selection](muxes-and-input-selection) +* [Multiple Clock Domains](multi-clock) +* [Reset](reset) +* [Polymorphism and Paramterization](polymorphism-and-parameterization) +* [Printing in Chisel](printing) +* [Naming](naming) +* [Unconnected Wires](unconnected-wires) +* [Annotations](annotations) diff --git a/docs/src/explanations/functional-abstraction.md b/docs/src/explanations/functional-abstraction.md new file mode 100644 index 00000000..4e3900b6 --- /dev/null +++ b/docs/src/explanations/functional-abstraction.md @@ -0,0 +1,34 @@ +--- +layout: docs +title: "Functional Abstraction" +section: "chisel3" +--- + +# Functional Abstraction + +We can define functions to factor out a repeated piece of logic that +we later reuse multiple times in a design. For example, we can wrap +up our earlier example of a simple combinational logic block as +follows: + +```scala mdoc:invisible +import chisel3._ +``` + +```scala mdoc:silent +def clb(a: UInt, b: UInt, c: UInt, d: UInt): UInt = + (a & b) | (~c & d) +``` + +where ```clb``` is the function which takes ```a```, ```b```, +```c```, ```d``` as arguments and returns a wire to the output of a +boolean circuit. The ```def``` keyword is part of Scala and +introduces a function definition, with each argument followed by a colon then its type, +and the function return type given after the colon following the +argument list. The equals (```=})```sign separates the function argument list from the function +definition. + +We can then use the block in another circuit as follows: +```scala mdoc:silent +val out = clb(a,b,c,d) +``` diff --git a/docs/src/explanations/functional-module-creation.md b/docs/src/explanations/functional-module-creation.md new file mode 100644 index 00000000..407edb1d --- /dev/null +++ b/docs/src/explanations/functional-module-creation.md @@ -0,0 +1,120 @@ +--- +layout: docs +title: "Functional Module Creation" +section: "chisel3" +--- + +# Functional Module Creation + +Objects in Scala have a pre-existing creation function (method) called `apply`. +When an object is used as value in an expression (which basically means that the constructor was called), this method determines the returned value. +When dealing with hardware modules, one would expect the module output to be representative of the hardware module's functionality. +Therefore, we would sometimes like the module output to be the value returned when using the object as a value in an expression. +Since hardware modules are represented as Scala objects, this can be done by defining the object's `apply` method to return the module's output. +This can be referred to as creating a functional interface for module construction. +If we apply this on the standard mux2 example, we would to return the mux2 output ports when we used mux2 in an expression. +Implementing this requires building a constructor that takes multiplexer inputs as parameters and returns the multiplexer output: + +```scala mdoc:silent +import chisel3._ + +class Mux2 extends Module { + val io = IO(new Bundle { + val sel = Input(Bool()) + val in0 = Input(UInt()) + val in1 = Input(UInt()) + val out = Output(UInt()) + }) + io.out := Mux(io.sel, io.in0, io.in1) +} + +object Mux2 { + def apply(sel: UInt, in0: UInt, in1: UInt) = { + val m = Module(new Mux2) + m.io.in0 := in0 + m.io.in1 := in1 + m.io.sel := sel + m.io.out + } +} +``` + +As we can see in the code example, we defined the `apply` method to take the Mux2 inputs as the method parameters, and return the Mux2 output as the function's return value. +By defining modules in this way, it is easier to later implement larger and more complex version of this regular module. +For example, we previously implemented Mux4 like this: + +```scala mdoc:silent +class Mux4 extends Module { + val io = IO(new Bundle { + val in0 = Input(UInt(1.W)) + val in1 = Input(UInt(1.W)) + val in2 = Input(UInt(1.W)) + val in3 = Input(UInt(1.W)) + val sel = Input(UInt(2.W)) + val out = Output(UInt(1.W)) + }) + val m0 = Module(new Mux2) + m0.io.sel := io.sel(0) + m0.io.in0 := io.in0 + m0.io.in1 := io.in1 + + val m1 = Module(new Mux2) + m1.io.sel := io.sel(0) + m1.io.in0 := io.in2 + m1.io.in1 := io.in3 + + val m3 = Module(new Mux2) + m3.io.sel := io.sel(1) + m3.io.in0 := m0.io.out + m3.io.in1 := m1.io.out + + io.out := m3.io.out +} +``` + +However, by using the creation function we redefined for Mux2, we can now use the Mux2 outputs as values of the modules themselves +when writing the Mux4 output expression: + +```scala mdoc:invisible:reset +// We need to re-do this to allow us to `reset` +// and then re-define Mux4 +import chisel3._ + +class Mux2 extends Module { + val io = IO(new Bundle { + val sel = Input(Bool()) + val in0 = Input(UInt()) + val in1 = Input(UInt()) + val out = Output(UInt()) + }) + io.out := Mux(io.sel, io.in0, io.in1) +} + +object Mux2 { + def apply(sel: UInt, in0: UInt, in1: UInt) = { + val m = Module(new Mux2) + m.io.in0 := in0 + m.io.in1 := in1 + m.io.sel := sel + m.io.out + } +} +``` + +```scala mdoc:silent +class Mux4 extends Module { + val io = IO(new Bundle { + val in0 = Input(UInt(1.W)) + val in1 = Input(UInt(1.W)) + val in2 = Input(UInt(1.W)) + val in3 = Input(UInt(1.W)) + val sel = Input(UInt(2.W)) + val out = Output(UInt(1.W)) + }) + io.out := Mux2(io.sel(1), + Mux2(io.sel(0), io.in0, io.in1), + Mux2(io.sel(0), io.in2, io.in3)) +} +``` + +This allows us to write more intuitively readable hardware connection descriptions, which are similar to software expression evaluation. diff --git a/docs/src/explanations/interfaces-and-connections.md b/docs/src/explanations/interfaces-and-connections.md new file mode 100644 index 00000000..9f48b642 --- /dev/null +++ b/docs/src/explanations/interfaces-and-connections.md @@ -0,0 +1,149 @@ +--- +layout: docs +title: "Interfaces and Connections" +section: "chisel3" +--- + +# Interfaces & Bulk Connections + +For more sophisticated modules it is often useful to define and instantiate interface classes while defining the IO for a module. First and foremost, interface classes promote reuse allowing users to capture once and for all common interfaces in a useful form. + +Secondly, interfaces allow users to dramatically reduce wiring by supporting bulk connections between producer and consumer modules. Finally, users can make changes in large interfaces in one place reducing the number of updates required when adding or removing pieces of the interface. + +Note that Chisel has some built-in standard interface which should be used whenever possible for interoperability (e.g. Decoupled). + +## Ports: Subclasses & Nesting + +As we saw earlier, users can define their own interfaces by defining a class that subclasses Bundle. For example, a user could define a simple link for hand-shaking data as follows: + +```scala mdoc:invisible +import chisel3._ +``` + +```scala mdoc:silent +class SimpleLink extends Bundle { + val data = Output(UInt(16.W)) + val valid = Output(Bool()) +} +``` + +We can then extend SimpleLink by adding parity bits using bundle inheritance: +```scala mdoc:silent +class PLink extends SimpleLink { + val parity = Output(UInt(5.W)) +} +``` +In general, users can organize their interfaces into hierarchies using inheritance. + +From there we can define a filter interface by nesting two PLinks into a new FilterIO bundle: +```scala mdoc:silent +class FilterIO extends Bundle { + val x = Flipped(new PLink) + val y = new PLink +} +``` +where flip recursively changes the direction of a bundle, changing input to output and output to input. + +We can now define a filter by defining a filter class extending module: +```scala mdoc:silent +class Filter extends Module { + val io = IO(new FilterIO) + // ... +} +``` +where the io field contains FilterIO. + +## Bundle Vectors + +Beyond single elements, vectors of elements form richer hierarchical interfaces. For example, in order to create a crossbar with a vector of inputs, producing a vector of outputs, and selected by a UInt input, we utilize the Vec constructor: +```scala mdoc:silent +import chisel3.util.log2Ceil +class CrossbarIo(n: Int) extends Bundle { + val in = Vec(n, Flipped(new PLink)) + val sel = Input(UInt(log2Ceil(n).W)) + val out = Vec(n, new PLink) +} +``` +where Vec takes a size as the first argument and a block returning a port as the second argument. + +## Bulk Connections + +We can now compose two filters into a filter block as follows: +```scala mdoc:silent +class Block extends Module { + val io = IO(new FilterIO) + val f1 = Module(new Filter) + val f2 = Module(new Filter) + f1.io.x <> io.x + f1.io.y <> f2.io.x + f2.io.y <> io.y +} +``` +where <> bulk connects interfaces of opposite gender between sibling modules or interfaces of the same gender between parent/child modules. + +Bulk connections connect leaf ports of the same name to each other. If the names do not match or are missing, Chisel does not generate a connection. + +Caution: bulk connections should only be used with **directioned elements** (like IOs), and is not magical (e.g. connecting two wires isn't supported since Chisel can't necessarily figure out the directions automatically [chisel3#603](https://github.com/freechipsproject/chisel3/issues/603)). + +## The standard ready-valid interface (ReadyValidIO / Decoupled) + +Chisel provides a standard interface for [ready-valid interfaces](http://inst.eecs.berkeley.edu/~cs150/Documents/Interfaces.pdf). +A ready-valid interface consists of a `ready` signal, a `valid` signal, and some data stored in `bits`. +The `ready` bit indicates that a consumer is *ready* to consume data. +The `valid` bit indicates that a producer has *valid* data on `bits`. +When both `ready` and `valid` are asserted, a data transfer from the producer to the consumer takes place. +A convenience method `fire` is provided that is asserted if both `ready` and `valid` are asserted. + +Usually, we use the utility function [`Decoupled()`](https://chisel.eecs.berkeley.edu/api/latest/chisel3/util/Decoupled$.html) to turn any type into a ready-valid interface rather than directly using [ReadyValidIO](http://chisel.eecs.berkeley.edu/api/latest/chisel3/util/ReadyValidIO.html). + +* `Decoupled(...)` creates a producer / output ready-valid interface (i.e. bits is an output). +* `Flipped(Decoupled(...))` creates a consumer / input ready-valid interface (i.e. bits is an input). + +Take a look at the following example Chisel code to better understand exactly what is generated: + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.util.Decoupled + +/** + * Using Decoupled(...) creates a producer interface. + * i.e. it has bits as an output. + * This produces the following ports: + * input io_readyValid_ready, + * output io_readyValid_valid, + * output [31:0] io_readyValid_bits + */ +class ProducingData extends Module { + val io = IO(new Bundle { + val readyValid = Decoupled(UInt(32.W)) + }) + // do something with io.readyValid.ready + io.readyValid.valid := true.B + io.readyValid.bits := 5.U +} + +/** + * Using Flipped(Decoupled(...)) creates a consumer interface. + * i.e. it has bits as an input. + * This produces the following ports: + * output io_readyValid_ready, + * input io_readyValid_valid, + * input [31:0] io_readyValid_bits + */ +class ConsumingData extends Module { + val io = IO(new Bundle { + val readyValid = Flipped(Decoupled(UInt(32.W))) + }) + io.readyValid.ready := false.B + // do something with io.readyValid.valid + // do something with io.readyValid.bits +} +``` + +`DecoupledIO` is a ready-valid interface with the *convention* that there are no guarantees placed on deasserting `ready` or `valid` or on the stability of `bits`. +That means `ready` and `valid` can also be deasserted without a data transfer. + +`IrrevocableIO` is a ready-valid interface with the *convention* that the value of `bits` will not change while `valid` is asserted and `ready` is deasserted. +Also the consumer shall keep `ready` asserted after a cycle where `ready` was high and `valid` was low. +Note that the *irrevocable* constraint *is only a convention* and cannot be enforced by the interface. +Chisel does not automatically generate checkers or assertions to enforce the *irrevocable* convention. diff --git a/docs/src/explanations/memories.md b/docs/src/explanations/memories.md new file mode 100644 index 00000000..10759f25 --- /dev/null +++ b/docs/src/explanations/memories.md @@ -0,0 +1,184 @@ +--- +layout: docs +title: "Memories" +section: "chisel3" +--- + +# Memories + +Chisel provides facilities for creating both read only and read/write memories. + +## ROM + +Users can define read-only memories by constructing a `Vec` with `VecInit`. +`VecInit` can accept either a variable-argument number of `Data` literals or a `Seq[Data]` literals that initialize the ROM. + +For example, users can create a small ROM initialized to 1, 2, 4, 8 and loop through all values using a counter as an address generator as follows: + +```scala mdoc:compile-only +import chisel3._ +import chisel3.util.Counter +val m = VecInit(1.U, 2.U, 4.U, 8.U) +val c = Counter(m.length) +c.inc() +val r = m(c.value) +``` + +We can create an *n* value sine lookup table generator using a ROM initialized as follows: + +```scala mdoc:compile-only +import chisel3._ + +val Pi = math.Pi +def sinTable(amp: Double, n: Int) = { + val times = + (0 until n).map(i => (i*2*Pi)/(n.toDouble-1) - Pi) + val inits = + times.map(t => Math.round(amp * math.sin(t)).asSInt(32.W)) + VecInit(inits) +} +``` + +where `amp` is used to scale the fixpoint values stored in the ROM. + +## Read-Write Memories + +Memories are given special treatment in Chisel since hardware implementations of memory vary greatly. For example, FPGA memories are instantiated quite differently from ASIC memories. Chisel defines a memory abstraction that can map to either simple Verilog behavioural descriptions or to instances of memory modules that are available from external memory generators provided by foundry or IP vendors. + + +### `SyncReadMem`: sequential/synchronous-read, sequential/synchronous-write + +Chisel has a construct called `SyncReadMem` for sequential/synchronous-read, sequential/synchronous-write memories. These `SyncReadMem`s will likely be synthesized to technology SRAMs (as opposed to register banks). + +If the same memory address is both written and sequentially read on the same clock edge, or if a sequential read enable is cleared, then the read data is undefined. + +Values on the read data port are not guaranteed to be held until the next read cycle. If that is the desired behavior, external logic to hold the last read value must be added. + +#### Read port/write port + +Ports into `SyncReadMem`s are created by applying a `UInt` index. A 1024-entry SRAM with one write port and one read port might be expressed as follows: + +```scala mdoc:silent +import chisel3._ +class ReadWriteSmem extends Module { + val width: Int = 32 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(width.W)) + val dataOut = Output(UInt(width.W)) + }) + + val mem = SyncReadMem(1024, UInt(width.W)) + // Create one write port and one read port + mem.write(io.addr, io.dataIn) + io.dataOut := mem.read(io.addr, io.enable) +} +``` + +Below is an example waveform of the one write port/one read port `SyncReadMem` with [masks](#masks). Note that the signal names will differ from the exact wire names generated for the `SyncReadMem`. With masking, it is also possible that multiple RTL arrays will be generated with the behavior below. + + + + +#### Single-ported + +Single-ported SRAMs can be inferred when the read and write conditions are mutually exclusive in the same `when` chain: + +```scala mdoc:silent +import chisel3._ +class RWSmem extends Module { + val width: Int = 32 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(width.W)) + val dataOut = Output(UInt(width.W)) + }) + + val mem = SyncReadMem(1024, UInt(width.W)) + io.dataOut := DontCare + when(io.enable) { + val rdwrPort = mem(io.addr) + when (io.write) { rdwrPort := io.dataIn } + .otherwise { io.dataOut := rdwrPort } + } +} +``` + +(The `DontCare` is there to make Chisel's [unconnected wire detection](unconnected-wires) aware that reading while writing is undefined.) + +Here is an example single read/write port waveform, with [masks](#masks) (again, generated signal names and number of arrays may differ): + + + +### `Mem`: combinational/asynchronous-read, sequential/synchronous-write + +Chisel supports random-access memories via the `Mem` construct. Writes to `Mem`s are combinational/asynchronous-read, sequential/synchronous-write. These `Mem`s will likely be synthesized to register banks, since most SRAMs in modern technologies (FPGA, ASIC) tend to no longer support combinational (asynchronous) reads. + +Creating asynchronous-read versions of the examples above simply involves replacing `SyncReadMem` with `Mem`. + +### Masks + +Chisel memories also support write masks for subword writes. Chisel will infer masks if the data type of the memory is a vector. To infer a mask, specify the `mask` argument of the `write` function which creates write ports. A given masked length is written if the corresponding mask bit is set. For example, in the example below, if the 0th bit of mask is true, it will write the lower byte of the data at corresponding address. + +```scala mdoc:silent +import chisel3._ +class MaskedReadWriteSmem extends Module { + val width: Int = 8 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val mask = Input(Vec(4, Bool())) + val dataIn = Input(Vec(4, UInt(width.W))) + val dataOut = Output(Vec(4, UInt(width.W))) + }) + + // Create a 32-bit wide memory that is byte-masked + val mem = SyncReadMem(1024, Vec(4, UInt(width.W))) + // Write with mask + mem.write(io.addr, io.dataIn, io.mask) + io.dataOut := mem.read(io.addr, io.enable) +} +``` + +Here is an example of masks with readwrite ports: + +```scala mdoc:silent +import chisel3._ +class MaskedRWSmem extends Module { + val width: Int = 32 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val mask = Input(Vec(2, Bool())) + val addr = Input(UInt(10.W)) + val dataIn = Input(Vec(2, UInt(width.W))) + val dataOut = Output(Vec(2, UInt(width.W))) + }) + + val mem = SyncReadMem(1024, Vec(2, UInt(width.W))) + io.dataOut := DontCare + when(io.enable) { + val rdwrPort = mem(io.addr) + when (io.write) { + when(io.mask(0)) { + rdwrPort(0) := io.dataIn(0) + } + when(io.mask(1)) { + rdwrPort(1) := io.dataIn(1) + } + }.otherwise { io.dataOut := rdwrPort } + } +} +``` + +### Memory Initialization + +Chisel memories can be initialized from an external `binary` or `hex` file emitting proper Verilog for synthesis or simulation. There are multiple modes of initialization. + +For more information, check the experimental docs on [Loading Memories](../appendix/experimental-features#loading-memories) feature. + diff --git a/docs/src/explanations/modules.md b/docs/src/explanations/modules.md new file mode 100644 index 00000000..f82a14d6 --- /dev/null +++ b/docs/src/explanations/modules.md @@ -0,0 +1,138 @@ +--- +layout: docs +title: "Modules" +section: "chisel3" +--- + +# Modules + +Chisel *modules* are very similar to Verilog *modules* in +defining a hierarchical structure in the generated circuit. + +The hierarchical module namespace is accessible in downstream tools +to aid in debugging and physical layout. A user-defined module is +defined as a *class* which: + + - inherits from `Module`, + - contains at least one interface wrapped in a Module's `IO()` method (traditionally stored in a port field named ```io```), and + - wires together subcircuits in its constructor. + +As an example, consider defining your own two-input multiplexer as a +module: +```scala mdoc:silent +import chisel3._ +class Mux2IO extends Bundle { + val sel = Input(UInt(1.W)) + val in0 = Input(UInt(1.W)) + val in1 = Input(UInt(1.W)) + val out = Output(UInt(1.W)) +} + +class Mux2 extends Module { + val io = IO(new Mux2IO) + io.out := (io.sel & io.in1) | (~io.sel & io.in0) +} +``` + +The wiring interface to a module is a collection of ports in the +form of a ```Bundle```. The interface to the module is defined +through a field named ```io```. For ```Mux2```, ```io``` is +defined as a bundle with four fields, one for each multiplexer port. + +The ```:=``` assignment operator, used here in the body of the +definition, is a special operator in Chisel that wires the input of +left-hand side to the output of the right-hand side. + +### Module Hierarchy + +We can now construct circuit hierarchies, where we build larger modules out +of smaller sub-modules. For example, we can build a 4-input +multiplexer module in terms of the ```Mux2``` module by wiring +together three 2-input multiplexers: + +```scala mdoc:silent +class Mux4IO extends Bundle { + val in0 = Input(UInt(1.W)) + val in1 = Input(UInt(1.W)) + val in2 = Input(UInt(1.W)) + val in3 = Input(UInt(1.W)) + val sel = Input(UInt(2.W)) + val out = Output(UInt(1.W)) +} +class Mux4 extends Module { + val io = IO(new Mux4IO) + + val m0 = Module(new Mux2) + m0.io.sel := io.sel(0) + m0.io.in0 := io.in0 + m0.io.in1 := io.in1 + + val m1 = Module(new Mux2) + m1.io.sel := io.sel(0) + m1.io.in0 := io.in2 + m1.io.in1 := io.in3 + + val m3 = Module(new Mux2) + m3.io.sel := io.sel(1) + m3.io.in0 := m0.io.out + m3.io.in1 := m1.io.out + + io.out := m3.io.out +} +``` + +We again define the module interface as ```io``` and wire up the +inputs and outputs. In this case, we create three ```Mux2``` +children modules, using the ```Module``` constructor function and +the Scala ```new``` keyword to create a +new object. We then wire them up to one another and to the ports of +the ```Mux4``` interface. + +Note: Chisel `Module`s have an implicit clock (called `clock`) and +an implicit reset (called `reset`). To create modules without implicit +clock and reset, Chisel provides `RawModule`. + +> Historical Note: Prior to Chisel 3.5, Modules were restricted to only +having a single user-defined port named `io`. There was also a type called +`MultiIOModule` that provided implicit clock and reset while allowing the +user to define as many ports as they want. This is now the functionality +of `Module`. + +### `RawModule` + +A `RawModule` is a module that **does not provide an implicit clock and reset.** +This can be useful when interfacing a Chisel module with a design that expects +a specific naming convention for clock or reset. + +Then we can use it in place of *Module* usage : +```scala mdoc:silent +import chisel3.{RawModule, withClockAndReset} + +class Foo extends Module { + val io = IO(new Bundle{ + val a = Input(Bool()) + val b = Output(Bool()) + }) + io.b := !io.a +} + +class FooWrapper extends RawModule { + val a_i = IO(Input(Bool())) + val b_o = IO(Output(Bool())) + val clk = IO(Input(Clock())) + val rstn = IO(Input(Bool())) + + val foo = withClockAndReset(clk, !rstn){ Module(new Foo) } + + foo.io.a := a_i + b_o := foo.io.b +} +``` + +In the example above, the `RawModule` is used to change the reset polarity +of module `SlaveSpi`. Indeed, the reset is active high by default in Chisel +modules, then using `withClockAndReset(clock, !rstn)` we can use an active low +reset in entire design. + +The clock is just wired as it, but if needed, `RawModule` can be used in +conjunction with `BlackBox` to connect a differential clock input for example. diff --git a/docs/src/explanations/motivation.md b/docs/src/explanations/motivation.md new file mode 100644 index 00000000..afe02a7b --- /dev/null +++ b/docs/src/explanations/motivation.md @@ -0,0 +1,44 @@ +--- +layout: docs +title: "Motivation" +section: "chisel3" +--- + +# Motivation -- "Why Chisel?" + +We were motivated to develop a new hardware language by years of +struggle with existing hardware description languages in our research +projects and hardware design courses. _Verilog_ and _VHDL_ were developed +as hardware _simulation_ languages, and only later did they become +a basis for hardware _synthesis_. Much of the semantics of these +languages are not appropriate for hardware synthesis and, in fact, +many constructs are simply not synthesizable. Other constructs are +non-intuitive in how they map to hardware implementations, or their +use can accidentally lead to highly inefficient hardware structures. +While it is possible to use a subset of these languages and still get +acceptable results, they nonetheless present a cluttered and confusing +specification model, particularly in an instructional setting. + +However, our strongest motivation for developing a new hardware +language is our desire to change the way that electronic system design +takes place. We believe that it is important to not only teach +students how to design circuits, but also to teach them how to design +*circuit generators* ---programs that automatically generate +designs from a high-level set of design parameters and constraints. +Through circuit generators, we hope to leverage the hard work of +design experts and raise the level of design abstraction for everyone. +To express flexible and scalable circuit construction, circuit +generators must employ sophisticated programming techniques to make +decisions concerning how to best customize their output circuits +according to high-level parameter values and constraints. While +Verilog and VHDL include some primitive constructs for programmatic +circuit generation, they lack the powerful facilities present in +modern programming languages, such as object-oriented programming, +type inference, support for functional programming, and reflection. + +Instead of building a new hardware design language from scratch, we +chose to embed hardware construction primitives within an existing +language. We picked Scala not only because it includes the +programming features we feel are important for building circuit +generators, but because it was specifically developed as a base for +domain-specific languages. diff --git a/docs/src/explanations/muxes-and-input-selection.md b/docs/src/explanations/muxes-and-input-selection.md new file mode 100644 index 00000000..ae087e83 --- /dev/null +++ b/docs/src/explanations/muxes-and-input-selection.md @@ -0,0 +1,62 @@ +--- +layout: docs +title: "Muxes and Input Selection" +section: "chisel3" +--- + +# Muxes and Input Selection + +Selecting inputs is very useful in hardware description, and therefore Chisel provides several built-in generic input-selection implementations. + +### Mux +The first one is `Mux`. This is a 2-input selector. Unlike the `Mux2` example which was presented previously, the built-in `Mux` allows +the inputs (`in0` and `in1`) to be any datatype as long as they are the same subclass of `Data`. + +By using the functional module creation feature presented in the previous section, we can create multi-input selector in a simple way: + +```scala +Mux(c1, a, Mux(c2, b, Mux(..., default))) +``` + +### MuxCase + +The nested `Mux` is not necessary since Chisel also provides the built-in `MuxCase`, which implements that exact feature. +`MuxCase` is an n-way `Mux`, which can be used as follows: + +```scala +MuxCase(default, Array(c1 -> a, c2 -> b, ...)) +``` + +Where each selection dependency is represented as a tuple in a Scala +array [ condition -> selected_input_port ]. + +### MuxLookup +Chisel also provides `MuxLookup` which is an n-way indexed multiplexer: + +```scala +MuxLookup(idx, default, + Array(0.U -> a, 1.U -> b, ...)) +``` + +This is the same as a `MuxCase`, where the conditions are all index based selection: + +```scala +MuxCase(default, + Array((idx === 0.U) -> a, + (idx === 1.U) -> b, ...)) +``` + +Note that the conditions/cases/selectors (eg. c1, c2) must be in parentheses. + +### Mux1H +Another ```Mux``` utility is the one-hot mux, ```Mux1H```. It takes a sequence of selectors and values and returns the value associated with the one selector that is set. If zero or multiple selectors are set the behavior is undefined. For example: + +```scala + val hotValue = chisel3.util.Mux1H(Seq( + io.selector(0) -> 2.U, + io.selector(1) -> 4.U, + io.selector(2) -> 8.U, + io.selector(4) -> 11.U, + )) +``` +```Mux1H``` whenever possible generates *Firrtl* that is readily optimizable as low depth and/or tree. This optimization is not possible when the values are of type ```FixedPoint``` or an aggregate type that contains ```FixedPoint```s and results instead as a simple ```Mux``` tree. This behavior could be sub-optimal. As ```FixedPoint``` is still *experimental* this behavior may change in the future. diff --git a/docs/src/explanations/operators.md b/docs/src/explanations/operators.md new file mode 100644 index 00000000..231a53fa --- /dev/null +++ b/docs/src/explanations/operators.md @@ -0,0 +1,65 @@ +--- +layout: docs +title: "Operators" +section: "chisel3" +--- + +# Chisel Operators + +Chisel defines a set of hardware operators: + +| Operation | Explanation | +| --------- | --------- | +| **Bitwise operators** | **Valid on:** SInt, UInt, Bool | +| `val invertedX = ~x` | Bitwise NOT | +| `val hiBits = x & "h_ffff_0000".U` | Bitwise AND | +| `val flagsOut = flagsIn \| overflow` | Bitwise OR | +| `val flagsOut = flagsIn ^ toggle` | Bitwise XOR | +| **Bitwise reductions.** | **Valid on:** SInt and UInt. Returns Bool. | +| `val allSet = x.andR` | AND reduction | +| `val anySet = x.orR` | OR reduction | +| `val parity = x.xorR` | XOR reduction | +| **Equality comparison.** | **Valid on:** SInt, UInt, and Bool. Returns Bool. | +| `val equ = x === y` | Equality | +| `val neq = x =/= y` | Inequality | +| **Shifts** | **Valid on:** SInt and UInt | +| `val twoToTheX = 1.S << x` | Logical shift left | +| `val hiBits = x >> 16.U` | Right shift (logical on UInt and arithmetic on SInt). | +| **Bitfield manipulation** | **Valid on:** SInt, UInt, and Bool. | +| `val xLSB = x(0)` | Extract single bit, LSB has index 0. | +| `val xTopNibble = x(15, 12)` | Extract bit field from end to start bit position. | +| `val usDebt = Fill(3, "hA".U)` | Replicate a bit string multiple times. | +| `val float = Cat(sign, exponent, mantissa)` | Concatenates bit fields, with first argument on left. | +| **Logical Operations** | **Valid on:** Bool +| `val sleep = !busy` | Logical NOT | +| `val hit = tagMatch && valid` | Logical AND | +| `val stall = src1busy || src2busy` | Logical OR | +| `val out = Mux(sel, inTrue, inFalse)` | Two-input mux where sel is a Bool | +| **Arithmetic operations** | **Valid on Nums:** SInt and UInt. | +| `val sum = a + b` *or* `val sum = a +% b` | Addition (without width expansion) | +| `val sum = a +& b` | Addition (with width expansion) | +| `val diff = a - b` *or* `val diff = a -% b` | Subtraction (without width expansion) | +| `val diff = a -& b` | Subtraction (with width expansion) | +| `val prod = a * b` | Multiplication | +| `val div = a / b` | Division | +| `val mod = a % b` | Modulus | +| **Arithmetic comparisons** | **Valid on Nums:** SInt and UInt. Returns Bool. | +| `val gt = a > b` | Greater than | +| `val gte = a >= b` | Greater than or equal | +| `val lt = a < b` | Less than | +| `val lte = a <= b` | Less than or equal | + +>Our choice of operator names was constrained by the Scala language. +We have to use triple equals```===``` for equality and ```=/=``` +for inequality to allow the +native Scala equals operator to remain usable. + +The Chisel operator precedence is not directly defined as part of the Chisel language. +Practically, it is determined by the evaluation order of the circuit, +which natuarally follows the [Scala operator precedence](https://docs.scala-lang.org/tour/operators.html). +If in doubt of operator precedence, use parentheses. + +> The Chisel/Scala operator precedence is similar but +not identical to precedence in Java or C. Verilog has the same operator precedence as C, but VHDL +does not. Verilog has precedence ordering for logic operations, but in VHDL +those operators have the same precedence and are evaluated from left to right. diff --git a/docs/src/explanations/polymorphism-and-parameterization.md b/docs/src/explanations/polymorphism-and-parameterization.md new file mode 100644 index 00000000..b388ee5b --- /dev/null +++ b/docs/src/explanations/polymorphism-and-parameterization.md @@ -0,0 +1,240 @@ +--- +layout: docs +title: "Polymorphism and Parameterization" +section: "chisel3" +--- + +# Polymorphism and Parameterization + +_This section is advanced and can be skipped at first reading._ + +Scala is a strongly typed language and uses parameterized types to specify generic functions and classes. +In this section, we show how Chisel users can define their own reusable functions and classes using parameterized classes. + +## Parameterized Functions + +Earlier we defined `Mux2` on `Bool`, but now we show how we can define a generic multiplexer function. +We define this function as taking a boolean condition and con and alt arguments (corresponding to then and else expressions) of type `T`: + +```scala +def Mux[T <: Bits](c: Bool, con: T, alt: T): T = { ... } +``` + +where `T` is required to be a subclass of `Bits`. +Scala ensures that in each usage of `Mux`, it can find a common superclass of the actual con and alt argument types, +otherwise it causes a Scala compilation type error. +For example, + +```scala +Mux(c, UInt(10), UInt(11)) +``` + +yields a `UInt` wire because the `con` and `alt` arguments are each of type `UInt`. + +<!--- +Jack: I cannot seem to get this to actually work + Scala does not like the * in FIR since it could be from UInt or SInt + +We now present a more advanced example of parameterized functions for defining an inner product FIR digital filter generically over Chisel `Num`s. + +The inner product FIR filter can be mathematically defined as: +\begin{equation} +y[t] = \sum_j w_j * x_j[t-j] +\end{equation} + + +where `x` is the input and `w` is a vector of weights. +In Chisel this can be defined as: + + +```scala +def delays[T <: Data](x: T, n: Int): List[T] = + if (n <= 1) List(x) else x :: delays(RegNext(x), n - 1) + +def FIR[T <: Data with Num[T]](ws: Seq[T], x: T): T = + ws zip delays(x, ws.length) map { case (a, b) => a * b } reduce (_ + _) +``` + +where +`delays` creates a list of incrementally increasing delays of its input and +`reduce` constructs a reduction circuit given a binary combiner function `f`. +In this case, `reduce` creates a summation circuit. +Finally, the `FIR` function is constrained to work on inputs of type `Num` where Chisel multiplication and addition are defined. +---> + +## Parameterized Classes + +Like parameterized functions, we can also parameterize classes to make them more reusable. +For instance, we can generalize the Filter class to use any kind of link. +We do so by parameterizing the `FilterIO` class and defining the constructor to take a single argument `gen` of type `T` as below. +```scala mdoc:invisible +import chisel3._ +``` +```scala mdoc:silent +class FilterIO[T <: Data](gen: T) extends Bundle { + val x = Input(gen) + val y = Output(gen) +} +``` + +We can now define `Filter` by defining a module class that also takes a link type constructor argument and passes it through to the `FilterIO` interface constructor: + +```scala mdoc:silent +class Filter[T <: Data](gen: T) extends Module { + val io = IO(new FilterIO(gen)) + // ... +} +``` + +We can now define a `PLink`-based `Filter` as follows: +```scala mdoc:invisible +class SimpleLink extends Bundle { + val data = Output(UInt(16.W)) + val valid = Output(Bool()) +} +class PLink extends SimpleLink { + val parity = Output(UInt(5.W)) +} +``` +```scala mdoc:compile-only +val f = Module(new Filter(new PLink)) +``` + +A generic FIFO could be defined as follows: + +```scala mdoc:silent +import chisel3.util.log2Up + +class DataBundle extends Bundle { + val a = UInt(32.W) + val b = UInt(32.W) +} + +class Fifo[T <: Data](gen: T, n: Int) extends Module { + val io = IO(new Bundle { + val enqVal = Input(Bool()) + val enqRdy = Output(Bool()) + val deqVal = Output(Bool()) + val deqRdy = Input(Bool()) + val enqDat = Input(gen) + val deqDat = Output(gen) + }) + val enqPtr = RegInit(0.U((log2Up(n)).W)) + val deqPtr = RegInit(0.U((log2Up(n)).W)) + val isFull = RegInit(false.B) + val doEnq = io.enqRdy && io.enqVal + val doDeq = io.deqRdy && io.deqVal + val isEmpty = !isFull && (enqPtr === deqPtr) + val deqPtrInc = deqPtr + 1.U + val enqPtrInc = enqPtr + 1.U + val isFullNext = Mux(doEnq && ~doDeq && (enqPtrInc === deqPtr), + true.B, Mux(doDeq && isFull, false.B, + isFull)) + enqPtr := Mux(doEnq, enqPtrInc, enqPtr) + deqPtr := Mux(doDeq, deqPtrInc, deqPtr) + isFull := isFullNext + val ram = Mem(n, gen) + when (doEnq) { + ram(enqPtr) := io.enqDat + } + io.enqRdy := !isFull + io.deqVal := !isEmpty + ram(deqPtr) <> io.deqDat +} +``` + +An Fifo with 8 elements of type DataBundle could then be instantiated as: + +```scala mdoc:compile-only +val fifo = Module(new Fifo(new DataBundle, 8)) +``` + +It is also possible to define a generic decoupled (ready/valid) interface: +```scala mdoc:invisible:reset +import chisel3._ +class DataBundle extends Bundle { + val a = UInt(32.W) + val b = UInt(32.W) +} +``` + +```scala mdoc:silent +class DecoupledIO[T <: Data](data: T) extends Bundle { + val ready = Input(Bool()) + val valid = Output(Bool()) + val bits = Output(data) +} +``` + +This template can then be used to add a handshaking protocol to any +set of signals: + +```scala mdoc:silent +class DecoupledDemo extends DecoupledIO(new DataBundle) +``` + +The FIFO interface can be now be simplified as follows: + +```scala mdoc:silent +class Fifo[T <: Data](data: T, n: Int) extends Module { + val io = IO(new Bundle { + val enq = Flipped(new DecoupledIO(data)) + val deq = new DecoupledIO(data) + }) + // ... +} +``` + +## Parametrization based on Modules + +You can also parametrize modules based on other modules rather than just types. The following is an example of a module parametrized by other modules as opposed to e.g. types. + +```scala mdoc:silent +import chisel3.RawModule +import chisel3.experimental.BaseModule +import chisel3.stage.ChiselStage + +// Provides a more specific interface since generic Module +// provides no compile-time information on generic module's IOs. +trait MyAdder { + def in1: UInt + def in2: UInt + def out: UInt +} + +class Mod1 extends RawModule with MyAdder { + val in1 = IO(Input(UInt(8.W))) + val in2 = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := in1 + in2 +} + +class Mod2 extends RawModule with MyAdder { + val in1 = IO(Input(UInt(8.W))) + val in2 = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := in1 - in2 +} + +class X[T <: BaseModule with MyAdder](genT: => T) extends Module { + val io = IO(new Bundle { + val in1 = Input(UInt(8.W)) + val in2 = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + }) + val subMod = Module(genT) + io.out := subMod.out + subMod.in1 := io.in1 + subMod.in2 := io.in2 +} + +println(ChiselStage.emitVerilog(new X(new Mod1))) +println(ChiselStage.emitVerilog(new X(new Mod2))) +``` + +Output: + +```scala mdoc:verilog +ChiselStage.emitVerilog(new X(new Mod1)) +ChiselStage.emitVerilog(new X(new Mod2)) +``` diff --git a/docs/src/explanations/ports.md b/docs/src/explanations/ports.md new file mode 100644 index 00000000..ce38cf22 --- /dev/null +++ b/docs/src/explanations/ports.md @@ -0,0 +1,67 @@ +--- +layout: docs +title: "Ports" +section: "chisel3" +--- + +# Ports + +Ports are used as interfaces to hardware components. A port is simply +any `Data` object that has directions assigned to its members. + +Chisel provides port constructors to allow a direction to be added +(input or output) to an object at construction time. Primitive port +constructors wrap the type of the port in `Input` or `Output`. + +An example port declaration is as follows: +```scala mdoc:invisible +import chisel3._ +``` +```scala mdoc +class Decoupled extends Bundle { + val ready = Output(Bool()) + val data = Input(UInt(32.W)) + val valid = Input(Bool()) +} +``` + +After defining ```Decoupled```, it becomes a new type that can be +used as needed for module interfaces or for named collections of +wires. + +By folding directions into the object declarations, Chisel is able to +provide powerful wiring constructs described later. + +## Inspecting Module ports + +(Chisel 3.2+) + +Chisel 3.2 introduced `DataMirror.modulePorts` which can be used to inspect the IOs of any Chisel module (this includes modules in both `import chisel3._` and `import Chisel._`, as well as BlackBoxes from each package). +Here is an example of how to use this API: + +```scala mdoc +import chisel3.experimental.DataMirror +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} + +class Adder extends Module { + val a = IO(Input(UInt(8.W))) + val b = IO(Input(UInt(8.W))) + val c = IO(Output(UInt(8.W))) + c := a +& b +} + +class Test extends Module { + val adder = Module(new Adder) + // for debug only + adder.a := DontCare + adder.b := DontCare + + // Inspect ports of adder + // See the result below. + DataMirror.modulePorts(adder).foreach { case (name, port) => { + println(s"Found port $name: $port") + }} +} + +(new ChiselStage).execute(Array.empty, Seq(ChiselGeneratorAnnotation(() => new Test))) +```
\ No newline at end of file diff --git a/docs/src/explanations/printing.md b/docs/src/explanations/printing.md new file mode 100644 index 00000000..abd1a427 --- /dev/null +++ b/docs/src/explanations/printing.md @@ -0,0 +1,137 @@ +--- +layout: docs +title: "Printing" +section: "chisel3" +--- + +# Printing in Chisel + +Chisel provides the `printf` function for debugging purposes. It comes in two flavors: + +* [Scala-style](#scala-style) +* [C-style](#c-style) + +### Scala-style + +Chisel also supports printf in a style similar to [Scala's String Interpolation](http://docs.scala-lang.org/overviews/core/string-interpolation.html). Chisel provides a custom string interpolator `p` which can be used as follows: + +```scala mdoc:invisible +import chisel3._ +``` +```scala mdoc:compile-only +val myUInt = 33.U +printf(p"myUInt = $myUInt") // myUInt = 33 +``` + +Note that when concatenating `p"..."` strings, you need to start with a `p"..."` string: + +```scala mdoc:compile-only +// Does not interpolate the second string +val myUInt = 33.U +printf("my normal string" + p"myUInt = $myUInt") +``` + +#### Simple formatting + +Other formats are available as follows: + +```scala mdoc:compile-only +val myUInt = 33.U +// Hexadecimal +printf(p"myUInt = 0x${Hexadecimal(myUInt)}") // myUInt = 0x21 +// Binary +printf(p"myUInt = ${Binary(myUInt)}") // myUInt = 100001 +// Character +printf(p"myUInt = ${Character(myUInt)}") // myUInt = ! +``` + +We recognize that the format specifiers are verbose, so we are working on a more concise syntax. + +#### Aggregate data-types + +Chisel provides default custom "pretty-printing" for Vecs and Bundles. The default printing of a Vec is similar to printing a Seq or List in Scala while printing a Bundle is similar to printing a Scala Map. + +```scala mdoc:compile-only +val myVec = VecInit(5.U, 10.U, 13.U) +printf(p"myVec = $myVec") // myVec = Vec(5, 10, 13) + +val myBundle = Wire(new Bundle { + val foo = UInt() + val bar = UInt() +}) +myBundle.foo := 3.U +myBundle.bar := 11.U +printf(p"myBundle = $myBundle") // myBundle = Bundle(a -> 3, b -> 11) +``` + +#### Custom Printing + +Chisel also provides the ability to specify _custom_ printing for user-defined Bundles. + +```scala mdoc:compile-only +class Message extends Bundle { + val valid = Bool() + val addr = UInt(32.W) + val length = UInt(4.W) + val data = UInt(64.W) + override def toPrintable: Printable = { + val char = Mux(valid, 'v'.U, '-'.U) + p"Message:\n" + + p" valid : ${Character(char)}\n" + + p" addr : 0x${Hexadecimal(addr)}\n" + + p" length : $length\n" + + p" data : 0x${Hexadecimal(data)}\n" + } +} + +val myMessage = Wire(new Message) +myMessage.valid := true.B +myMessage.addr := "h1234".U +myMessage.length := 10.U +myMessage.data := "hdeadbeef".U + +printf(p"$myMessage") +``` + +Which prints the following: + +``` +Message: + valid : v + addr : 0x00001234 + length : 10 + data : 0x00000000deadbeef +``` + +Notice the use of `+` between `p` interpolated "strings". The results of `p` interpolation can be concatenated by using the `+` operator. For more information, please see the documentation + +### C-Style + +Chisel provides `printf` in a similar style to its C namesake. It accepts a double-quoted format string and a variable number of arguments which will then be printed on rising clock edges. Chisel supports the following format specifiers: + +| Format Specifier | Meaning | +| :-----: | :-----: | +| `%d` | decimal number | +| `%x` | hexadecimal number | +| `%b` | binary number | +| `%c` | 8-bit ASCII character | +| `%%` | literal percent | + +It also supports a small set of escape characters: + +| Escape Character | Meaning | +| :-----: | :-----: | +| `\n` | newline | +| `\t` | tab | +| `\"` | literal double quote | +| `\'` | literal single quote | +| `\\` | literal backslash | + +Note that single quotes do not require escaping, but are legal to escape. + +Thus printf can be used in a way very similar to how it is used in C: + +```scala mdoc:compile-only +val myUInt = 32.U +printf("myUInt = %d", myUInt) // myUInt = 32 +``` diff --git a/docs/src/explanations/reset.md b/docs/src/explanations/reset.md new file mode 100644 index 00000000..a99a39e3 --- /dev/null +++ b/docs/src/explanations/reset.md @@ -0,0 +1,179 @@ +--- +layout: docs +title: "Reset" +section: "chisel3" +--- + +# Reset + +```scala mdoc:invisible +import chisel3._ + +class Submodule extends Module +``` + +As of Chisel 3.2.0, Chisel 3 supports both synchronous and asynchronous reset, +meaning that it can natively emit both synchronous and asynchronously reset registers. + +The type of register that is emitted is based on the type of the reset signal associated +with the register. + +There are three types of reset that implement a common trait `Reset`: +* `Bool` - constructed with `Bool()`. Also known as "synchronous reset". +* `AsyncReset` - constructed with `AsyncReset()`. Also known as "asynchronous reset". +* `Reset` - constructed with `Reset()`. Also known as "abstract reset". + +For implementation reasons, the concrete Scala type is `ResetType`. Stylistically we avoid `ResetType`, instead using the common trait `Reset`. + +Registers with reset signals of type `Bool` are emitted as synchronous reset flops. +Registers with reset signals of type `AsyncReset` are emitted as asynchronouly reset flops. +Registers with reset signals of type `Reset` will have their reset type _inferred_ during FIRRTL compilation. + +### Reset Inference + +FIRRTL will infer a concrete type for any signals of type abstract `Reset`. +The rules are as follows: +1. An abstract `Reset` with only signals of type `AsyncReset`, abstract `Reset`, and `DontCare` +in both its fan-in and fan-out will infer to be of type `AsyncReset` +2. An abstract `Reset` with signals of both types `Bool` and `AsyncReset` in its fan-in and fan-out +is an error. +3. Otherwise, an abstract `Reset` will infer to type `Bool`. + +You can think about (3) as the mirror of (1) replacing `AsyncReset` with `Bool` with the additional +rule that abstract `Reset`s with neither `AsyncReset` nor `Bool` in their fan-in and fan-out will +default to type `Bool`. +This "default" case is uncommon and implies that reset signal is ultimately driven by a `DontCare`. + +### Implicit Reset + +A `Module`'s `reset` is of type abstract `Reset`. +Prior to Chisel 3.2.0, the type of this field was `Bool`. +For backwards compatability, if the top-level module has an implicit reset, its type will default to `Bool`. + +#### Setting Implicit Reset Type + +_New in Chisel 3.3.0_ + +If you would like to set the reset type from within a Module (including the top-level `Module`), +rather than relying on _Reset Inference_, you can mixin one of the following traits: +* `RequireSyncReset` - sets the type of `reset` to `Bool` +* `RequireAsyncReset` - sets the type of `reset` to `AsyncReset` + +For example: + +```scala mdoc:silent +class MyAlwaysSyncResetModule extends Module with RequireSyncReset { + val mySyncResetReg = RegInit(false.B) // reset is of type Bool +} +``` + +```scala mdoc:silent +class MyAlwaysAsyncResetModule extends Module with RequireAsyncReset { + val myAsyncResetReg = RegInit(false.B) // reset is of type AsyncReset +} +``` + +**Note:** This sets the concrete type, but the Scala type will remain `Reset`, so casting may still be necessary. +This comes up most often when using a reset of type `Bool` in logic. + + +### Reset-Agnostic Code + +The purpose of abstract `Reset` is to make it possible to design hardware that is agnostic to the +reset discipline used. +This enables code reuse for utilities and designs where the reset discipline does not matter to +the functionality of the block. + +Consider the two example modules below which are agnostic to the type of reset used within them: + +```scala mdoc:silent +class ResetAgnosticModule extends Module { + val io = IO(new Bundle { + val out = UInt(4.W) + }) + val resetAgnosticReg = RegInit(0.U(4.W)) + resetAgnosticReg := resetAgnosticReg + 1.U + io.out := resetAgnosticReg +} + +class ResetAgnosticRawModule extends RawModule { + val clk = IO(Input(Clock())) + val rst = IO(Input(Reset())) + val out = IO(Output(UInt(8.W))) + + val resetAgnosticReg = withClockAndReset(clk, rst)(RegInit(0.U(8.W))) + resetAgnosticReg := resetAgnosticReg + 1.U + out := resetAgnosticReg +} +``` + +These modules can be used in both synchronous and asynchronous reset domains. +Their reset types will be inferred based on the context within which they are used. + +### Forcing Reset Type + +You can set the type of a Module's implicit reset as described [above](#setting-implicit-reset-type). + +You can also cast to force the concrete type of reset. +* `.asBool` will reinterpret a `Reset` as `Bool` +* `.asAsyncReset` will reinterpret a `Reset` as `AsyncReset`. + +You can then use `withReset` to use a cast reset as the implicit reset. +See ["Multiple Clock Domains"](../explanations/multi-clock) for more information about `withReset`. + + +The following will make `myReg` as well as both `resetAgnosticReg`s synchronously reset: + +```scala mdoc:silent +class ForcedSyncReset extends Module { + // withReset's argument becomes the implicit reset in its scope + withReset (reset.asBool) { + val myReg = RegInit(0.U) + val myModule = Module(new ResetAgnosticModule) + + // RawModules do not have implicit resets so withReset has no effect + val myRawModule = Module(new ResetAgnosticRawModule) + // We must drive the reset port manually + myRawModule.rst := Module.reset // Module.reset grabs the current implicit reset + } +} +``` + +The following will make `myReg` as well as both `resetAgnosticReg`s asynchronously reset: + +```scala mdoc:silent +class ForcedAysncReset extends Module { + // withReset's argument becomes the implicit reset in its scope + withReset (reset.asAsyncReset){ + val myReg = RegInit(0.U) + val myModule = Module(new ResetAgnosticModule) // myModule.reset is connected implicitly + + // RawModules do not have implicit resets so withReset has no effect + val myRawModule = Module(new ResetAgnosticRawModule) + // We must drive the reset port manually + myRawModule.rst := Module.reset // Module.reset grabs the current implicit reset + } +} +``` + +**Note:** such casts (`asBool` and `asAsyncReset`) are not checked by FIRRTL. +In doing such a cast, you as the designer are effectively telling the compiler +that you know what you are doing and to force the type as cast. + +### Last-Connect Semantics + +It is **not** legal to override the reset type using last-connect semantics +unless you are overriding a `DontCare`: + +```scala mdoc:silent +class MyModule extends Module { + val resetBool = Wire(Reset()) + resetBool := DontCare + resetBool := false.B // this is fine + withReset(resetBool) { + val mySubmodule = Module(new Submodule()) + } + resetBool := true.B // this is fine + resetBool := false.B.asAsyncReset // this will error in FIRRTL +} +``` diff --git a/docs/src/explanations/sequential-circuits.md b/docs/src/explanations/sequential-circuits.md new file mode 100644 index 00000000..36bbb1aa --- /dev/null +++ b/docs/src/explanations/sequential-circuits.md @@ -0,0 +1,50 @@ +--- +layout: docs +title: "Sequential Circuits" +section: "chisel3" +--- + +# Sequential Circuits + +```scala mdoc:invisible +import chisel3._ +val in = Bool() +``` +The simplest form of state element supported by Chisel is a positive edge-triggered register, which can be instantiated as: +```scala mdoc:compile-only +val reg = RegNext(in) +``` +This circuit has an output that is a copy of the input signal `in` delayed by one clock cycle. Note that we do not have to specify the type of Reg as it will be automatically inferred from its input when instantiated in this way. In the current version of Chisel, clock and reset are global signals that are implicitly included where needed. + +Note that registers which do not specify an initial value will not change value upon toggling the reset signal. + +Using registers, we can quickly define a number of useful circuit constructs. For example, a rising-edge detector that takes a boolean signal in and outputs true when the current value is true and the previous value is false is given by: + +```scala mdoc:silent +def risingedge(x: Bool) = x && !RegNext(x) +``` +Counters are an important sequential circuit. To construct an up-counter that counts up to a maximum value, max, then wraps around back to zero (i.e., modulo max+1), we write: +```scala mdoc:silent +def counter(max: UInt) = { + val x = RegInit(0.asUInt(max.getWidth.W)) + x := Mux(x === max, 0.U, x + 1.U) + x +} +``` +The counter register is created in the counter function with a reset value of 0 (with width large enough to hold max), to which the register will be initialized when the global reset for the circuit is asserted. The := assignment to x in counter wires an update combinational circuit which increments the counter value unless it hits the max at which point it wraps back to zero. Note that when x appears on the right-hand side of an assignment, its output is referenced, whereas when on the left-hand side, its input is referenced. +Counters can be used to build a number of useful sequential circuits. For example, we can build a pulse generator by outputting true when a counter reaches zero: +```scala mdoc:silent +// Produce pulse every n cycles. +def pulse(n: UInt) = counter(n - 1.U) === 0.U +``` +A square-wave generator can then be toggled by the pulse train, toggling between true and false on each pulse: +```scala mdoc:silent +// Flip internal state when input true. +def toggle(p: Bool) = { + val x = RegInit(false.B) + x := Mux(p, !x, x) + x +} +// Square wave of a given period. +def squareWave(period: UInt) = toggle(pulse(period >> 1)) +``` diff --git a/docs/src/explanations/supported-hardware.md b/docs/src/explanations/supported-hardware.md new file mode 100644 index 00000000..fe3909e7 --- /dev/null +++ b/docs/src/explanations/supported-hardware.md @@ -0,0 +1,11 @@ +--- +layout: docs +title: "Supported Hardware" +section: "chisel3" +--- + +# Supported Hardware + +While Chisel focuses on binary logic, Chisel can support analog and tri-state wires with the `Analog` type - see [Datatypes in Chisel](data-types). + +We focus on binary logic designs as they constitute the vast majority of designs in practice. Tri-state logic are poorly supported standard industry flows and require special/controlled hard macros in order to be done. diff --git a/docs/src/explanations/unconnected-wires.md b/docs/src/explanations/unconnected-wires.md new file mode 100644 index 00000000..48012d12 --- /dev/null +++ b/docs/src/explanations/unconnected-wires.md @@ -0,0 +1,138 @@ +--- +layout: docs +title: "Unconnected Wires" +section: "chisel3" +--- + +# Unconnected Wires + +The Invalidate API [(#645)](https://github.com/freechipsproject/chisel3/pull/645) adds support to Chisel +for reporting unconnected wires as errors. + +Prior to this pull request, Chisel automatically generated a firrtl `is invalid` for `Module IO()`, and each `Wire()` definition. +This made it difficult to detect cases where output signals were never driven. +Chisel now supports a `DontCare` element, which may be connected to an output signal, indicating that that signal is intentionally not driven. +Unless a signal is driven by hardware or connected to a `DontCare`, Firrtl will complain with a "not fully initialized" error. + +### API + +Output signals may be connected to DontCare, generating a `is invalid` when the corresponding firrtl is emitted. + +```scala mdoc:invisible +import chisel3._ +``` +```scala mdoc:silent + +class Out extends Bundle { + val debug = Bool() + val debugOption = Bool() +} +val io = new Bundle { val out = new Out } +``` + +```scala mdoc:compile-only +io.out.debug := true.B +io.out.debugOption := DontCare +``` + +This indicates that the signal `io.out.debugOption` is intentionally not driven and firrtl should not issue a "not fully initialized" +error for this signal. + +This can be applied to aggregates as well as individual signals: +```scala mdoc:invisible +import chisel3._ +``` +```scala mdoc:silent +import chisel3._ +class ModWithVec extends Module { + // ... + val nElements = 5 + val io = IO(new Bundle { + val outs = Output(Vec(nElements, Bool())) + }) + io.outs <> DontCare + // ... +} + +class TrivialInterface extends Bundle { + val in = Input(Bool()) + val out = Output(Bool()) +} + +class ModWithTrivalInterface extends Module { + // ... + val io = IO(new TrivialInterface) + io <> DontCare + // ... +} +``` + +This feature is controlled by `CompileOptions.explicitInvalidate` and is set to `false` in `NotStrict` (Chisel2 compatibility mode), +and `true` in `Strict` mode. + +You can selectively enable this for Chisel2 compatibility mode by providing your own explicit `compileOptions`, +either for a group of Modules (via inheritance): +```scala mdoc:silent +abstract class ExplicitInvalidateModule extends Module()(chisel3.ExplicitCompileOptions.NotStrict.copy(explicitInvalidate = true)) +``` +or on a per-Module basis: +```scala mdoc:silent +class MyModule extends Module { + override val compileOptions = chisel3.ExplicitCompileOptions.NotStrict.copy(explicitInvalidate = true) + val io = IO(new Bundle { /* ... */ } ) + // ... +} +``` + +Or conversely, disable this stricter checking (which is now the default in pure chisel3): +```scala mdoc:silent +abstract class ImplicitInvalidateModule extends Module()(chisel3.ExplicitCompileOptions.Strict.copy(explicitInvalidate = false)) +``` +or on a per-Module basis: +```scala mdoc:invisible:reset +import chisel3._ +``` +```scala mdoc:silent +class MyModule extends Module { + override val compileOptions = chisel3.ExplicitCompileOptions.Strict.copy(explicitInvalidate = false) + val io = IO(new Bundle { /* ... */ } ) + // ... +} +``` + +Please see the corresponding [API tests](https://github.com/freechipsproject/chisel3/blob/master/src/test/scala/chiselTests/InvalidateAPISpec.scala) +for examples. + +### Determining the unconnected element + +I have an interface with 42 wires. +Which one of them is unconnected? + +The firrtl error message should contain something like: +```bash +firrtl.passes.CheckInitialization$RefNotInitializedException: @[:@6.4] : [module Router] Reference io is not fully initialized. + @[Decoupled.scala 38:19:@48.12] : node _GEN_23 = mux(and(UInt<1>("h1"), eq(UInt<2>("h3"), _T_84)), _GEN_2, VOID) @[Decoupled.scala 38:19:@48.12] + @[Router.scala 78:30:@44.10] : node _GEN_36 = mux(_GEN_0.ready, _GEN_23, VOID) @[Router.scala 78:30:@44.10] + @[Router.scala 75:26:@39.8] : node _GEN_54 = mux(io.in.valid, _GEN_36, VOID) @[Router.scala 75:26:@39.8] + @[Router.scala 70:50:@27.6] : node _GEN_76 = mux(io.load_routing_table_request.valid, VOID, _GEN_54) @[Router.scala 70:50:@27.6] + @[Router.scala 65:85:@19.4] : node _GEN_102 = mux(_T_62, VOID, _GEN_76) @[Router.scala 65:85:@19.4] + : io.outs[3].bits.body <= _GEN_102 +``` +The first line is the initial error report. +Successive lines, indented and beginning with source line information indicate connections involving the problematic signal. +Unfortunately, if these are `when` conditions involving muxes, they may be difficult to decipher. +The last line of the group, indented and beginning with a `:` should indicate the uninitialized signal component. +This example (from the [Router tutorial](https://github.com/ucb-bar/chisel-tutorial/blob/release/src/main/scala/examples/Router.scala)) +was produced when the output queue bits were not initialized. +The old code was: +```scala + io.outs.foreach { out => out.noenq() } +``` +which initialized the queue's `valid` bit, but did not initialize the actual output values. +The fix was: +```scala + io.outs.foreach { out => + out.bits := 0.U.asTypeOf(out.bits) + out.noenq() + } +``` diff --git a/docs/src/explanations/width-inference.md b/docs/src/explanations/width-inference.md new file mode 100644 index 00000000..66d9736d --- /dev/null +++ b/docs/src/explanations/width-inference.md @@ -0,0 +1,40 @@ +--- +layout: docs +title: "Width Inference" +section: "chisel3" +--- + +# Width Inference + +Chisel provides bit width inference to reduce design effort. Users are encouraged to manually specify widths of ports and registers to prevent any surprises, but otherwise unspecified widths will be inferred by the Firrtl compiler. + +For all circuit components declared with unspecified widths, the FIRRTL compiler will infer the minimum possible width that maintains the legality of all its incoming connections. Implicit here is that inference is done in a right to left fashion in the sense of an assignment statement in chisel, i.e. from the left hand side from the right hand side. If a component has no incoming connections, and the width is unspecified, then an error is thrown to indicate that the width could not be inferred. + +For module input ports with unspecified widths, the inferred width is the minimum possible width that maintains the legality of all incoming connections to all instantiations of the module. +The width of a ground-typed multiplexor expression is the maximum of its two corresponding input widths. For multiplexing aggregate-typed expressions, the resulting widths of each leaf subelement is the maximum of its corresponding two input leaf subelement widths. +The width of a conditionally valid expression is the width of its input expression. For the full formal description see the [Firrtl Spec](https://github.com/freechipsproject/firrtl/blob/master/spec/spec.pdf). + +Hardware operators have output widths as defined by the following set of rules: + +| operation | bit width | +| --------- | --------- | +| `z = x + y` *or* `z = x +% y` | `w(z) = max(w(x), w(y))` | +| `z = x +& y` | `w(z) = max(w(x), w(y)) + 1` | +| `z = x - y` *or* `z = x -% y` | `w(z) = max(w(x), w(y))` | +| `z = x -& y` | `w(z) = max(w(x), w(y)) + 1` | +| `z = x & y` | `w(z) = max(w(x), w(y))` | +| `z = Mux(c, x, y)` | `w(z) = max(w(x), w(y))` | +| `z = w * y` | `w(z) = w(x) + w(y)` | +| `z = x << n` | `w(z) = w(x) + maxNum(n)` | +| `z = x >> n` | `w(z) = w(x) - minNum(n)` | +| `z = Cat(x, y)` | `w(z) = w(x) + w(y)` | +| `z = Fill(n, x)` | `w(z) = w(x) * maxNum(n)` | + +>where for instance `w(z)` is the bit width of wire `z`, and the `&` +rule applies to all bitwise logical operations. + +Given a path of connections that begins with an unspecified width element (most commonly a top-level input), then the compiler will throw an exception indicating a certain width was uninferrable. + +A common "gotcha" comes from truncating addition and subtraction with the operators `+` and `-`. Users who want the result to maintain the full, expanded precision of the addition or subtraction should use the expanding operators `+&` and `-&`. + +> The default truncating operation comes from Chisel's history as a microprocessor design language. |
