From 03e6c2ba149ac611d1e2329c4502fed0ccea48fe Mon Sep 17 00:00:00 2001 From: Adam Izraelevitz Date: Mon, 5 Oct 2020 12:29:40 -0700 Subject: Move more docs (#1601) * Fix broken mdoc * Added test-chisel-docs to ci workflow * Add requirement on build-treadle job * Added forgotton colon * Forgot cd into chisel3 dir * moved three docs into explanations * Updated reference Co-authored-by: Schuyler Eldridge --- docs/src/explanations/annotations.md | 141 ++++++++++++++++++++ docs/src/explanations/blackboxes.md | 147 ++++++++++++++++++++ docs/src/explanations/bundles-and-vecs.md | 215 ++++++++++++++++++++++++++++++ 3 files changed, 503 insertions(+) create mode 100644 docs/src/explanations/annotations.md create mode 100644 docs/src/explanations/blackboxes.md create mode 100644 docs/src/explanations/bundles-and-vecs.md (limited to 'docs/src/explanations') diff --git a/docs/src/explanations/annotations.md b/docs/src/explanations/annotations.md new file mode 100644 index 00000000..19d24605 --- /dev/null +++ b/docs/src/explanations/annotations.md @@ -0,0 +1,141 @@ +--- +layout: docs +title: "Annotations" +section: "chisel3" +--- + +`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. + +This article focuses on the approach to building a basic library that contains `Annotation`s and `Transform`s. + +### Imports +We need a few basic imports to reference the components we need. + +```scala mdoc:silent +import chisel3._ +import chisel3.experimental.{annotate, ChiselAnnotation, RunFirrtlTransform} +import chisel3.internal.InstanceId + +import firrtl._ +import firrtl.annotations.{Annotation, SingleTargetAnnotation} +import firrtl.annotations.{CircuitTarget, ModuleTarget, InstanceTarget, ReferenceTarget, Target} +``` + +### Define an `Annotation` and a `Transform` + +First, define an `Annotation` that contains a string associated with a `Target` thing in the Chisel circuit. +This `InfoAnnotation` extends [`SingleTargetAnnotation`](https://www.chisel-lang.org/api/firrtl/1.2.0/firrtl/annotations/SingleTargetAnnotation.html), an `Annotation` associated with *one* thing in a FIRRTL circuit: + +```scala mdoc:silent +/** An annotation that contains some string information */ +case class InfoAnnotation(target: Target, info: String) extends SingleTargetAnnotation[Target] { + def duplicate(newTarget: Target) = this.copy(target = newTarget) +} +``` + +Second, define a `Transform` that consumes this `InfoAnnotation`. +This `InfoTransform` simply reads all annotations, prints any `InfoAnnotation`s it finds, and removes them. + +```scala mdoc:invisible +object Issue1228 { + /* Workaround for https://github.com/freechipsproject/firrtl/pull/1228 */ + abstract class Transform extends firrtl.Transform { + override def name: String = this.getClass.getName + } +} +import Issue1228.Transform +``` + +```scala mdoc:silent +/** A transform that reads InfoAnnotations and prints information about them */ +class InfoTransform() extends Transform with DependencyAPIMigration { + + override def prerequisites = firrtl.stage.Forms.HighForm + + override def execute(state: CircuitState): CircuitState = { + println("Starting transform 'IdentityTransform'") + + val annotationsx = state.annotations.flatMap{ + case InfoAnnotation(a: CircuitTarget, info) => + println(s" - Circuit '${a.serialize}' annotated with '$info'") + None + case InfoAnnotation(a: ModuleTarget, info) => + println(s" - Module '${a.serialize}' annotated with '$info'") + None + case InfoAnnotation(a: InstanceTarget, info) => + println(s" - Instance '${a.serialize}' annotated with '$info'") + None + case InfoAnnotation(a: ReferenceTarget, info) => + println(s" - Component '${a.serialize} annotated with '$info''") + None + case a => + Some(a) + } + + state.copy(annotations = annotationsx) + } +} +``` + +> Note: `inputForm` and `outputForm` will be deprecated in favor of a new dependency API that allows transforms to specify their dependencies more specifically than with circuit forms. +> Full backwards compatibility for `inputForm` and `outputForm` will be maintained, however. + +### Create a Chisel API/Annotator + +Now, define a Chisel API to annotate Chisel things with this `InfoAnnotation`. +This is commonly referred to as an "annotator". + +Here, define an object, `InfoAnnotator` with a method `info` that generates `InfoAnnotation`s. +This uses the `chisel3.experimental.annotate` passed an anonymous `ChiselAnnotation` object. +The need for this `ChiselAnnotation` (which is different from an actual FIRRTL `Annotation`) is that no FIRRTL circuit exists at the time the `info` method is called. +This is delaying the generation of the `InfoAnnotation` until the full circuit is available. + +This annotator also mixes in the `RunFirrtlTransform` trait (abstract in the `transformClass` method) because this annotator, whenever used, should result in the FIRRTL compiler running the custom `InfoTransform`. + +```scala mdoc:silent +object InfoAnnotator { + def info(component: InstanceId, info: String): Unit = { + annotate(new ChiselAnnotation with RunFirrtlTransform { + def toFirrtl: Annotation = InfoAnnotation(component.toTarget, info) + def transformClass = classOf[InfoTransform] + }) + } +} +``` + +> Note: there are a number of different approaches to writing an annotator. +> You could use a trait that you mix into a `Module`, an object (like is done above), or any other software approach. +> The specific choice of how you implement this is up to you! + +### Using the Chisel API + +Now, we can use the method `InfoAnnotation.info` to create annotations that associate strings with specific things in a FIRRTL circuit. +Below is a Chisel `Module`, `ModC`, where both the actual module is annotated as well as an output. + +```scala mdoc:silent +class ModC(widthC: Int) extends Module { + val io = IO(new Bundle { + val in = Input(UInt(widthC.W)) + val out = Output(UInt(widthC.W)) + }) + io.out := io.in + + InfoAnnotator.info(this, s"ModC($widthC)") + + InfoAnnotator.info(io.out, s"ModC(ignore param)") +} +``` + +### Running the Compilation + +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 +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)))) +``` diff --git a/docs/src/explanations/blackboxes.md b/docs/src/explanations/blackboxes.md new file mode 100644 index 00000000..7064c7bb --- /dev/null +++ b/docs/src/explanations/blackboxes.md @@ -0,0 +1,147 @@ +--- +layout: docs +title: "Blackboxes" +section: "chisel3" +--- +Chisel *BlackBoxes* are used to instantiate externally defined modules. This construct is useful +for hardware constructs that cannot be described in Chisel and for connecting to FPGA or other IP not defined in Chisel. + +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. +`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_`). + +### Parameterization + +Verilog parameters can be passed as an argument to the BlackBox constructor. + +For example, consider instantiating a Xilinx differential clock buffer (IBUFDS) in a Chisel design: + +```scala mdoc:silent +import chisel3._ +import chisel3.util._ +import chisel3.experimental._ // To enable experimental features + +class IBUFDS extends BlackBox(Map("DIFF_TERM" -> "TRUE", + "IOSTANDARD" -> "DEFAULT")) { + val io = IO(new Bundle { + val O = Output(Clock()) + val I = Input(Clock()) + val IB = Input(Clock()) + }) +} + +class Top extends Module { + 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 +} +``` + +In the Chisel-generated Verilog code, `IBUFDS` will be instantiated as: + +```verilog +IBUFDS #(.DIFF_TERM("TRUE"), .IOSTANDARD("DEFAULT")) ibufds ( + .IB(ibufds_IB), + .I(ibufds_I), + .O(ibufds_O) +); +``` + +### 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 in1 = Input(UInt(64.W)) + val in2 = Input(UInt(64.W)) + val out = Output(UInt(64.W)) + }) +} +``` + +The implementation is described by the following verilog +```verilog +module BlackBoxRealAdd( + input [63:0] in1, + input [63:0] in2, + output reg [63:0] out +); + always @* begin + 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.md). 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 +class BlackBoxRealAdd extends BlackBox with HasBlackBoxResource { + 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") +} +``` +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 +``` + +### 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. +```scala mdoc:silent:reset +import chisel3._ +import chisel3.util.HasBlackBoxInline +class BlackBoxRealAdd extends BlackBox with HasBlackBoxInline { + val io = IO(new Bundle() { + val in1 = Input(UInt(64.W)) + val in2 = Input(UInt(64.W)) + val out = Output(UInt(64.W)) + }) + setInline("BlackBoxRealAdd.v", + """module BlackBoxRealAdd( + | input [15:0] in1, + | input [15:0] in2, + | output [15:0] out + |); + |always @* begin + | out <= $realtobits($bitstoreal(in1) + $bitstoreal(in2)); + |end + |endmodule + """.stripMargin) +} +``` +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 +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 +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. + +### The interpreter + +***Note that the FIRRTL Interpreter is deprecated. Please use Treadle, the new Chisel/FIRRTL simulator*** +The [firrtl interpreter](https://github.com/ucb-bar/firrtl-interpreter) uses a separate system that allows users to +construct scala implementations of the black boxes. The scala implementation code built into a BlackBoxFactory which is +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. diff --git a/docs/src/explanations/bundles-and-vecs.md b/docs/src/explanations/bundles-and-vecs.md new file mode 100644 index 00000000..4b1eb196 --- /dev/null +++ b/docs/src/explanations/bundles-and-vecs.md @@ -0,0 +1,215 @@ +--- +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 { + val sign = Bool() + val exponent = UInt(8.W) + val significand = UInt(23.W) +} + +class ModuleWithFloatWire extends RawModule { + val x = Wire(new MyFloat) + val xs = x.sign +} +``` + +> 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. + +```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. + +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. + val myVec = Wire(Vec(5, SInt(23.W))) + + // Connect to one element of vector. + val reg3 = myVec(3) +} +``` + +(Note that we specify the number followed by the type of the `Vec` elements. We also specifiy the width of the `SInt`) + +The set of primitive classes +(`SInt`, `UInt`, and `Bool`) plus the aggregate +classes (`Bundles` and `Vec`s) all inherit from a common +superclass, `Data`. Every object that ultimately inherits from +`Data` can be represented as a bit vector in a hardware design. + +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. + val myVec = Vec(5, SInt(23.W)) + val flag = Bool() + // Previously defined bundle. + val f = new MyFloat +} +``` + +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.md). + +### Flipping Bundles + +The `Flipped()` function recursively flips all elements in a Bundle/Record. This is very useful for building bidirectional interfaces that connect to each other (e.g. `Decoupled`). See below for an example. + +```scala mdoc:silent +class ABBundle extends Bundle { + val a = Input(Bool()) + val b = Output(Bool()) +} +class MyFlippedModule extends RawModule { + // Normal instantiation of the bundle + // 'a' is an Input and 'b' is an Output + val normalBundle = IO(new ABBundle) + normalBundle.b := normalBundle.a + + // Flipped recursively flips the direction of all Bundle fields + // Now 'a' is an Output and 'b' is an Input + val flippedBundle = IO(Flipped(new ABBundle)) + flippedBundle.a := flippedBundle.b +} +``` + +This generates the following Verilog: + +```scala mdoc +import chisel3.stage.ChiselStage + +println(ChiselStage.emitVerilog(new MyFlippedModule())) +``` + +### MixedVec + +(Chisel 3.2+) + +All elements of a `Vec` must be of the same type. If we want to create a Vec where the elements have different types, we +can use a MixedVec: + +```scala mdoc:silent +import chisel3.util.MixedVec +class ModuleMixedVec extends Module { + val io = IO(new Bundle { + val x = Input(UInt(3.W)) + val y = Input(UInt(10.W)) + val vec = Output(MixedVec(UInt(3.W), UInt(10.W))) + }) + io.vec(0) := io.x + io.vec(1) := io.y +} +``` + +We can also programmatically create the types in a MixedVec: + +```scala mdoc:silent +class ModuleProgrammaticMixedVec(x: Int, y: Int) extends Module { + val io = IO(new Bundle { + val vec = Input(MixedVec((x to y) map { i => UInt(i.W) })) + // ... + }) + // ...rest of the module goes here... +} +``` + +### A note on `cloneType` + +Since Chisel is built on top of Scala and the JVM, it needs to know how to construct copies of bundles for various +purposes (creating wires, IOs, etc). If you have a parametrized bundle and Chisel can't automatically figure out how to +clone your bundle, you will need to create a custom `cloneType` method in your bundle. Most of the time, this is as +simple as `override def cloneType = (new YourBundleHere(...)).asInstanceOf[this.type]`. + +Note that in the vast majority of cases, **this is not required** as Chisel can figure out how to clone most bundles +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) + val bar = UInt(b.W) + override def cloneType = (new ExampleBundle(a, b)).asInstanceOf[this.type] +} + +class ExampleBundleModule(btype: ExampleBundle) extends Module { + val io = IO(new Bundle { + val out = Output(UInt(32.W)) + val b = Input(chiselTypeOf(btype)) + }) + io.out := io.b.foo + io.b.bar +} + +class Top extends Module { + val io = IO(new Bundle { + val out = Output(UInt(32.W)) + val in = Input(UInt(17.W)) + }) + val x = Wire(new ExampleBundle(31, 17)) + x := DontCare + val m = Module(new ExampleBundleModule(x)) + m.io.b.foo := io.in + m.io.b.bar := io.in + io.out := m.io.out +} +``` + +Generally cloneType can be automatically defined if all arguments to the Bundle are vals e.g. + +```scala mdoc:silent +class MyCloneTypeBundle(val bitwidth: Int) extends Bundle { + val field = UInt(bitwidth.W) + // ... +} +``` + +The only caveat is if you are passing something of type Data as a "generator" parameter, in which case you should make +it a `private val`. + +For example, consider the following Bundle. Because its `gen` variable is not a `private val`, the user has to +explicitly define the `cloneType` method. + +```scala mdoc:silent +import chisel3.util.{Decoupled, Irrevocable} +class RegisterWriteIOExplicitCloneType[T <: Data](gen: T) extends Bundle { + val request = Flipped(Decoupled(gen)) + val response = Irrevocable(Bool()) + override def cloneType = new RegisterWriteIOExplicitCloneType(gen).asInstanceOf[this.type] +} +``` + +We can make this this infer cloneType by making `gen` private since it is a "type parameter": + +```scala mdoc:silent +class RegisterWriteIO[T <: Data](private val gen: T) extends Bundle { + val request = Flipped(Decoupled(gen)) + val response = Irrevocable(Bool()) +} +``` -- cgit v1.2.3