diff --git a/docs/src/explanations/properties.md b/docs/src/explanations/properties.md index 35ca1f8a515..01a6b72ae58 100644 --- a/docs/src/explanations/properties.md +++ b/docs/src/explanations/properties.md @@ -148,3 +148,254 @@ on integral `Property` typed values. | `+` | Perform addition as defined by FIRRTL spec section Integer Add Operation | | `*` | Perform multiplication as defined by FIRRTL spec section Integer Multiply Operation | | `>>` | Perform shift right as defined by FIRRTL spec section Integer Shift Right Operation | + +### Classes and Objects + +Classes and Objects are to `Property` types what modules and instances are to +hardware types. That is, they provide a means to declare hierarchies through +which `Property` typed values flow. `Class` declares a hierarchical container +with input and output `Property` ports, and a body that contains `Property` +connections and `Object`s. `Object`s represent the instantiation of a `Class`, +which requires any input `Property` ports to be assigned, and allows any output +`Property` ports to be read. + +This allows domain-specific data models to be built using the basic primitives +of an object-oriented programming language, and embedded directly in the +instance graph Chisel is constructing. Intuitively, inputs to a `Class` are like +constructor arguments, which must be supplied to create an `Object`. Similarly, +outputs from a `Class` are like fields, which may be accessed from an `Object`. +This separation allows `Class` declarations to abstract over any `Object`s +created in their body--from the outside, the inputs must be supplied and only +the outputs may be accessed. + +The graphs represented by `Class` declarations and `Object` instantiations +coexist within the hardware instance graph. `Object` instances can exist +within hardware modules, providing domain-specific information, but hardware +instances cannot exist within `Class` declarations. + +`Object`s can be referenced, and references to `Object`s are a special kind of +`Property[ClassType]` type. This allows the data model captured by `Class` +declarations and `Object` instances to form arbitrary graphs. + +To understand how `Object` graphs are represented, and can ultimately be +queried, consider how the hardware instance graph is elaborated. To build the +`Object` graph, we first pick an entrypoint module to start elaboration. The +elaboration process works according to the Verilog spec's definition of +elaboration--instances of modules and `Object`s are instantiated in-memory, +with connections to their inputs and outputs. Inputs are supplied, and outputs +may be read. After elaboration completes, the `Object` graph is exposed in terms +of the output ports, which may contain any `Property` types, including +references to `Object`s. + +To illustrate how these pieces come together, consider the following examples: + +```scala mdoc:silent +import chisel3.properties.Class +import chisel3.experimental.hierarchy.{instantiable, public, Definition, Instance} + +// An abstract description of a CSR, represented as a Class. +@instantiable +class CSRDescription extends Class { + // An output Property indicating the CSR name. + val identifier = IO(Output(Property[String]())) + // An output Property describing the CSR. + val description = IO(Output(Property[String]())) + // An output Property indicating the CSR width. + val width = IO(Output(Property[Int]())) + + // Input Properties to be passed to Objects representing instances of the Class. + @public val identifierIn = IO(Input(Property[String]())) + @public val descriptionIn = IO(Input(Property[String]())) + @public val widthIn = IO(Input(Property[Int]())) + + // Simply connect the inputs to the outputs to expose the values. + identifier := identifierIn + description := descriptionIn + width := widthIn +} +``` + +The `CSRDescription` is a `Class` that represents domain-specific information +about a CSR. This uses `@instantiable` and `@public` so the `Class` can work +with the `Definition` and `Instance` APIs. + +The readable fields we want to expose on `Object`s of the `CSRDescription` class +are a string identifier, a string description, and an integer bitwidth, so these +are output `Property` type ports on the `Class`. + +To capture concrete values at each `Object` instantiation, we have corresponding +input `Property` type ports, which are connected directly to the outputs. This +is how we would represent something like a Scala `case class` using `Class`. + +```scala mdoc:silent +// A hardware module representing a CSR and its description. +class CSRModule( + csrDescDef: Definition[CSRDescription], + width: Int, + identifierStr: String, + descriptionStr: String) + extends Module { + override def desiredName = identifierStr + + // Create a hardware port for the CSR value. + val value = IO(Output(UInt(width.W))) + + // Create a property port for a reference to the CSR description object. + val description = IO(Output(csrDescDef.getPropertyType)) + + // Instantiate a CSR description object, and connect its input properties. + val csrDescription = Instance(csrDescDef) + csrDescription.identifierIn := Property(identifierStr) + csrDescription.descriptionIn := Property(descriptionStr) + csrDescription.widthIn := Property(width) + + // Create a register for the hardware CSR. A real implementation would be more involved. + val csr = RegInit(0.U(width.W)) + + // Assign the CSR value to the hardware port. + value := csr + + // Assign a reference to the CSR description object to the property port. + description := csrDescription.getPropertyReference +} +``` + +The `CSRModule` is a `Module` that represents the (dummy) hardware for a CSR, as +well as a `CSRDescription`. Using a `Definition` of a `CSRDescription`, an +`Object` is created and its inputs supplied from the `CSRModule` constructor +arguments. Then, a reference to the `Object` is connected to the `CSRModule` +output, so the reference will be exposed to the outside. + +```scala mdoc:silent +// The entrypoint module. +class Top extends Module { + // Create a Definition for the CSRDescription Class. + val csrDescDef = Definition(new CSRDescription) + + // Get the CSRDescription ClassType. + val csrDescType = csrDescDef.getClassType + + // Create a property port to collect all the CSRDescription object references. + val descriptions = IO(Output(Property[Seq[csrDescType.Type]]())) + + // Instantiate a couple CSR modules. + val mcycle = Module(new CSRModule(csrDescDef, 64, "mcycle", "Machine cycle counter.")) + val minstret = Module(new CSRModule(csrDescDef, 64, "minstret", "Machine instructions-retired counter.")) + + // Assign references to the CSR description objects to the property port. + descriptions := Property(Seq(mcycle.description.as(csrDescType), minstret.description.as(csrDescType))) +} +``` + +The `Top` module represents the entrypoint. It creates the `Definition` of the +`CSRDescription`, and creates some `CSRModule`s. It then takes the description +references, collects them into a list, and outputs the list so it will be +exposed to the outside. + +While it is not required to use the `Definition` API to define a `Class`, this +is the "safe" API, with support in Chisel for working with `Definition`s and +`Instance`s of a `Class`. There is also an "unsafe" API. See `DynamicObject` for +more information. + +To illustrate what this example generates, here is a listing of the FIRRTL: + +``` +FIRRTL version 4.0.0 +circuit Top : + class CSRDescription : + output identifier : String + output description : String + output width : Integer + input identifierIn : String + input descriptionIn : String + input widthIn : Integer + + propassign identifier, identifierIn + propassign description, descriptionIn + propassign width, widthIn + + module mcycle : + input clock : Clock + input reset : Reset + output value : UInt<64> + output description : Inst + + object csrDescription of CSRDescription + propassign csrDescription.identifierIn, String("mcycle") + propassign csrDescription.descriptionIn, String("Machine cycle counter.") + propassign csrDescription.widthIn, Integer(64) + regreset csr : UInt<64>, clock, reset, UInt<64>(0h0) + connect value, csr + propassign description, csrDescription + + module minstret : + input clock : Clock + input reset : Reset + output value : UInt<64> + output description : Inst + + object csrDescription of CSRDescription + propassign csrDescription.identifierIn, String("minstret") + propassign csrDescription.descriptionIn, String("Machine instructions-retired counter.") + propassign csrDescription.widthIn, Integer(64) + regreset csr : UInt<64>, clock, reset, UInt<64>(0h0) + connect value, csr + propassign description, csrDescription + + public module Top : + input clock : Clock + input reset : UInt<1> + output descriptions : List> + + inst mcycle of mcycle + connect mcycle.clock, clock + connect mcycle.reset, reset + inst minstret of minstret + connect minstret.clock, clock + connect minstret.reset, reset + propassign descriptions, List>(mcycle.description, minstret.description) +``` + +To understand the `Object` graph that is constructed, we will consider an +entrypoint to elaboration, and then show a hypothetical JSON representation of +the `Object` graph. The details of how we go from IR to an `Object` graph are +outside the scope of this document, and implemented by related tools. + +If we elaborate `Top`, the `descriptions` output `Property` is our entrypoint to +the `Object` graph. Within it, there are two `Object`s, the `CSRDescription`s of +the `mcycle` and `minstret` modules: + +```json mdoc:silent +{ + "descriptions": [ + { + "identifier": "mcycle", + "description": "Machine cycle counter.", + "width": 64 + }, + { + "identifier": "minstret", + "description": "Machine instructions-retired counter.", + "width": 64 + } + ] +} +``` + +If instead, we elaborate one of the `CSRModule`s, for example, `minstret`, the +`description` output `Property` is our entrypoint to the `Object` graph, which +contains the single `CSRDescription` object: + +```json mdoc:silent +{ + "description": { + "identifier": "minstret", + "description": "Machine instructions-retired counter.", + "width": 64 + } +} +``` + +In this way, the output `Property` ports, `Object` references, and choice of +elaboration entrypoint allow us to view the `Object` graph representing the +domain-specific data model from different points in the hierarchy.