summaryrefslogtreecommitdiff
path: root/core/src/main/scala/chisel3/Module.scala
diff options
context:
space:
mode:
authorJack Koenig2020-03-25 19:51:46 -0700
committerGitHub2020-03-25 19:51:46 -0700
commitdbb024a9adee6d82f37e357cf8b55456674ff65c (patch)
tree578858ab6d219ca6daf44cf87b73f75054989097 /core/src/main/scala/chisel3/Module.scala
parent6263fcc56b630b7181eb30680cadcdbb2bdf91dc (diff)
parentfbf5e6f1a0e8bf535d465b748ad554575fe62156 (diff)
Merge pull request #1384 from freechipsproject/no-more-compile-internal
No more compile internal
Diffstat (limited to 'core/src/main/scala/chisel3/Module.scala')
-rw-r--r--core/src/main/scala/chisel3/Module.scala405
1 files changed, 405 insertions, 0 deletions
diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala
new file mode 100644
index 00000000..f1c4e30a
--- /dev/null
+++ b/core/src/main/scala/chisel3/Module.scala
@@ -0,0 +1,405 @@
+// See LICENSE for license details.
+
+package chisel3
+
+import scala.collection.immutable.ListMap
+import scala.collection.mutable.{ArrayBuffer, HashMap}
+import scala.collection.JavaConversions._
+import scala.language.experimental.macros
+
+import java.util.IdentityHashMap
+
+import chisel3.internal._
+import chisel3.internal.Builder._
+import chisel3.internal.firrtl._
+import chisel3.internal.sourceinfo.{InstTransform, SourceInfo}
+import chisel3.experimental.BaseModule
+import _root_.firrtl.annotations.{ModuleName, ModuleTarget, IsModule}
+
+object Module extends SourceInfoDoc {
+ /** A wrapper method that all Module instantiations must be wrapped in
+ * (necessary to help Chisel track internal state).
+ *
+ * @param bc the Module being created
+ *
+ * @return the input module `m` with Chisel metadata properly set
+ */
+ def apply[T <: BaseModule](bc: => T): T = macro InstTransform.apply[T]
+
+ /** @group SourceInfoTransformMacro */
+ def do_apply[T <: BaseModule](bc: => T)
+ (implicit sourceInfo: SourceInfo,
+ compileOptions: CompileOptions): T = {
+ if (Builder.readyForModuleConstr) {
+ throwException("Error: Called Module() twice without instantiating a Module." +
+ sourceInfo.makeMessage(" See " + _))
+ }
+ Builder.readyForModuleConstr = true
+
+ val parent = Builder.currentModule
+ val whenDepth: Int = Builder.whenDepth
+
+ // Save then clear clock and reset to prevent leaking scope, must be set again in the Module
+ val (saveClock, saveReset) = (Builder.currentClock, Builder.currentReset)
+ Builder.currentClock = None
+ Builder.currentReset = None
+
+ // Execute the module, this has the following side effects:
+ // - set currentModule
+ // - unset readyForModuleConstr
+ // - reset whenDepth to 0
+ // - set currentClockAndReset
+ val module: T = bc // bc is actually evaluated here
+
+ if (Builder.whenDepth != 0) {
+ throwException("Internal Error! when() scope depth is != 0, this should have been caught!")
+ }
+ if (Builder.readyForModuleConstr) {
+ throwException("Error: attempted to instantiate a Module, but nothing happened. " +
+ "This is probably due to rewrapping a Module instance with Module()." +
+ sourceInfo.makeMessage(" See " + _))
+ }
+ Builder.currentModule = parent // Back to parent!
+ Builder.whenDepth = whenDepth
+ Builder.currentClock = saveClock // Back to clock and reset scope
+ Builder.currentReset = saveReset
+
+ val component = module.generateComponent()
+ Builder.components += component
+
+ // Handle connections at enclosing scope
+ if(!Builder.currentModule.isEmpty) {
+ pushCommand(DefInstance(sourceInfo, module, component.ports))
+ module.initializeInParent(compileOptions)
+ }
+ module
+ }
+
+ /** Returns the implicit Clock */
+ def clock: Clock = Builder.forcedClock
+ /** Returns the implicit Reset */
+ def reset: Reset = Builder.forcedReset
+ /** Returns the current Module */
+ def currentModule: Option[BaseModule] = Builder.currentModule
+}
+
+package experimental {
+
+ object IO {
+ /** Constructs a port for the current Module
+ *
+ * This must wrap the datatype used to set the io field of any Module.
+ * i.e. All concrete modules must have defined io in this form:
+ * [lazy] val io[: io type] = IO(...[: io type])
+ *
+ * Items in [] are optional.
+ *
+ * The granted iodef must be a chisel type and not be bound to hardware.
+ *
+ * Also registers a Data as a port, also performing bindings. Cannot be called once ports are
+ * requested (so that all calls to ports will return the same information).
+ * Internal API.
+ */
+ def apply[T<:Data](iodef: T): T = {
+ val module = Module.currentModule.get // Impossible to fail
+ require(!module.isClosed, "Can't add more ports after module close")
+ requireIsChiselType(iodef, "io type")
+
+ // Clone the IO so we preserve immutability of data types
+ val iodefClone = try {
+ iodef.cloneTypeFull
+ } catch {
+ // For now this is going to be just a deprecation so we don't suddenly break everyone's code
+ case e: AutoClonetypeException =>
+ Builder.deprecated(e.getMessage, Some(s"${iodef.getClass}"))
+ iodef
+ }
+ module.bindIoInPlace(iodefClone)
+ iodefClone
+ }
+ }
+}
+
+package internal {
+ import chisel3.experimental.BaseModule
+
+ object BaseModule {
+ private[chisel3] class ClonePorts (elts: Data*)(implicit compileOptions: CompileOptions) extends Record {
+ val elements = ListMap(elts.map(d => d.instanceName -> d.cloneTypeFull): _*)
+ def apply(field: String) = elements(field)
+ override def cloneType = (new ClonePorts(elts: _*)).asInstanceOf[this.type]
+ }
+
+ private[chisel3] def cloneIORecord(proto: BaseModule)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): ClonePorts = {
+ require(proto.isClosed, "Can't clone a module before module close")
+ val clonePorts = new ClonePorts(proto.getModulePorts: _*)
+ clonePorts.bind(WireBinding(Builder.forcedUserModule))
+ val cloneInstance = new DefInstance(sourceInfo, proto, proto._component.get.ports) {
+ override def name = clonePorts.getRef.name
+ }
+ pushCommand(cloneInstance)
+ if (!compileOptions.explicitInvalidate) {
+ pushCommand(DefInvalid(sourceInfo, clonePorts.ref))
+ }
+ if (proto.isInstanceOf[MultiIOModule]) {
+ clonePorts("clock") := Module.clock
+ clonePorts("reset") := Module.reset
+ }
+ clonePorts
+ }
+ }
+}
+
+package experimental {
+
+ /** Abstract base class for Modules, an instantiable organizational unit for RTL.
+ */
+ // TODO: seal this?
+ abstract class BaseModule extends HasId {
+ //
+ // Builder Internals - this tracks which Module RTL construction belongs to.
+ //
+ if (!Builder.readyForModuleConstr) {
+ throwException("Error: attempted to instantiate a Module without wrapping it in Module().")
+ }
+ readyForModuleConstr = false
+
+ Builder.currentModule = Some(this)
+ Builder.whenDepth = 0
+
+ //
+ // Module Construction Internals
+ //
+ protected var _closed = false
+
+ /** Internal check if a Module is closed */
+ private[chisel3] def isClosed = _closed
+
+ // Fresh Namespace because in Firrtl, Modules namespaces are disjoint with the global namespace
+ private[chisel3] val _namespace = Namespace.empty
+ private val _ids = ArrayBuffer[HasId]()
+ private[chisel3] def addId(d: HasId) {
+ if (Builder.aspectModule(this).isDefined) {
+ aspectModule(this).get.addId(d)
+ } else {
+ require(!_closed, "Can't write to module after module close")
+ _ids += d
+ }
+ }
+
+ protected def getIds = {
+ require(_closed, "Can't get ids before module close")
+ _ids.toSeq
+ }
+
+ private val _ports = new ArrayBuffer[Data]()
+
+ // getPorts unfortunately already used for tester compatibility
+ protected[chisel3] def getModulePorts = {
+ require(_closed, "Can't get ports before module close")
+ _ports.toSeq
+ }
+
+ // These methods allow checking some properties of ports before the module is closed,
+ // mainly for compatibility purposes.
+ protected def portsContains(elem: Data): Boolean = _ports contains elem
+
+ protected def portsSize: Int = _ports.size
+
+ /** Generates the FIRRTL Component (Module or Blackbox) of this Module.
+ * Also closes the module so no more construction can happen inside.
+ */
+ private[chisel3] def generateComponent(): Component
+
+ /** Sets up this module in the parent context
+ */
+ private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit
+
+ //
+ // Chisel Internals
+ //
+
+ /** The desired name of this module (which will be used in generated FIRRTL IR or Verilog).
+ *
+ * The name of a module approximates the behavior of the Java Reflection [[`getSimpleName` method
+ * https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getSimpleName--]] with some modifications:
+ *
+ * - Anonymous modules will get an `"_Anon"` tag
+ * - Modules defined in functions will use their class name and not a numeric name
+ *
+ * @note If you want a custom or parametric name, override this method.
+ */
+ def desiredName: String = {
+ /* The default module name is derived from the Java reflection derived class name. */
+ val baseName = this.getClass.getName
+
+ /* A sequence of string filters applied to the name */
+ val filters: Seq[String => String] = Seq(
+ ((a: String) => raw"\$$+anon".r.replaceAllIn(a, "_Anon")) // Merge the "$$anon" name with previous name
+ )
+
+ filters
+ .foldLeft(baseName){ case (str, filter) => filter(str) } // 1. Apply filters to baseName
+ .split("\\.|\\$") // 2. Split string at '.' or '$'
+ .filterNot(_.forall(_.isDigit)) // 3. Drop purely numeric names
+ .last // 4. Use the last name
+ }
+
+ /** Legalized name of this module. */
+ final lazy val name = try {
+ Builder.globalNamespace.name(desiredName)
+ } catch {
+ case e: NullPointerException => throwException(
+ s"Error: desiredName of ${this.getClass.getName} is null. Did you evaluate 'name' before all values needed by desiredName were available?", e) // scalastyle:ignore line.size.limit
+ case t: Throwable => throw t
+ }
+
+ /** Returns a FIRRTL ModuleName that references this object
+ *
+ * @note Should not be called until circuit elaboration is complete
+ */
+ final def toNamed: ModuleName = toTarget.toNamed
+
+ /** Returns a FIRRTL ModuleTarget that references this object
+ *
+ * @note Should not be called until circuit elaboration is complete
+ */
+ final def toTarget: ModuleTarget = ModuleTarget(this.circuitName, this.name)
+
+ /** Returns a FIRRTL ModuleTarget that references this object
+ *
+ * @note Should not be called until circuit elaboration is complete
+ */
+ final def toAbsoluteTarget: IsModule = {
+ _parent match {
+ case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, toTarget.module)
+ case None => toTarget
+ }
+ }
+
+ /**
+ * Internal API. Returns a list of this module's generated top-level ports as a map of a String
+ * (FIRRTL name) to the IO object. Only valid after the module is closed.
+ *
+ * Note: for BlackBoxes (but not ExtModules), this returns the contents of the top-level io
+ * object, consistent with what is emitted in FIRRTL.
+ *
+ * TODO: Use SeqMap/VectorMap when those data structures become available.
+ */
+ private[chisel3] def getChiselPorts: Seq[(String, Data)] = {
+ require(_closed, "Can't get ports before module close")
+ _component.get.ports.map { port =>
+ (port.id.getRef.asInstanceOf[ModuleIO].name, port.id)
+ }
+ }
+
+ /** Called at the Module.apply(...) level after this Module has finished elaborating.
+ * Returns a map of nodes -> names, for named nodes.
+ *
+ * Helper method.
+ */
+ protected def nameIds(rootClass: Class[_]): HashMap[HasId, String] = {
+ val names = new HashMap[HasId, String]()
+
+ def name(node: HasId, name: String) {
+ // First name takes priority, like suggestName
+ // TODO: DRYify with suggestName
+ if (!names.contains(node)) {
+ names.put(node, name)
+ }
+ }
+
+ /** Scala generates names like chisel3$util$Queue$$ram for private vals
+ * This extracts the part after $$ for names like this and leaves names
+ * without $$ unchanged
+ */
+ def cleanName(name: String): String = name.split("""\$\$""").lastOption.getOrElse(name)
+
+ for (m <- getPublicFields(rootClass)) {
+ Builder.nameRecursively(cleanName(m.getName), m.invoke(this), name)
+ }
+
+ names
+ }
+
+ /** Compatibility function. Allows Chisel2 code which had ports without the IO wrapper to
+ * compile under Bindings checks. Does nothing in non-compatibility mode.
+ *
+ * Should NOT be used elsewhere. This API will NOT last.
+ *
+ * TODO: remove this, perhaps by removing Bindings checks in compatibility mode.
+ */
+ def _compatAutoWrapPorts() {} // scalastyle:ignore method.name
+
+ /** Chisel2 code didn't require the IO(...) wrapper and would assign a Chisel type directly to
+ * io, then do operations on it. This binds a Chisel type in-place (mutably) as an IO.
+ */
+ protected def _bindIoInPlace(iodef: Data): Unit = { // scalastyle:ignore method.name
+ // Compatibility code: Chisel2 did not require explicit direction on nodes
+ // (unspecified treated as output, and flip on nothing was input).
+ // This sets assigns the explicit directions required by newer semantics on
+ // Bundles defined in compatibility mode.
+ // This recursively walks the tree, and assigns directions if no explicit
+ // direction given by upper-levels (override Input / Output) AND element is
+ // directly inside a compatibility Bundle determined by compile options.
+ def assignCompatDir(data: Data, insideCompat: Boolean): Unit = {
+ data match {
+ case data: Element if insideCompat => data._assignCompatibilityExplicitDirection
+ case data: Element => // Not inside a compatibility Bundle, nothing to be done
+ case data: Aggregate => data.specifiedDirection match {
+ // Recurse into children to ensure explicit direction set somewhere
+ case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => data match {
+ case record: Record =>
+ val compatRecord = !record.compileOptions.dontAssumeDirectionality
+ record.getElements.foreach(assignCompatDir(_, compatRecord))
+ case vec: Vec[_] =>
+ vec.getElements.foreach(assignCompatDir(_, insideCompat))
+ }
+ case SpecifiedDirection.Input | SpecifiedDirection.Output => // forced assign, nothing to do
+ }
+ }
+ }
+
+ assignCompatDir(iodef, false)
+
+ iodef.bind(PortBinding(this))
+ _ports += iodef
+ }
+
+ /** Private accessor for _bindIoInPlace */
+ private[chisel3] def bindIoInPlace(iodef: Data): Unit = _bindIoInPlace(iodef)
+
+ /**
+ * This must wrap the datatype used to set the io field of any Module.
+ * i.e. All concrete modules must have defined io in this form:
+ * [lazy] val io[: io type] = IO(...[: io type])
+ *
+ * Items in [] are optional.
+ *
+ * The granted iodef must be a chisel type and not be bound to hardware.
+ *
+ * Also registers a Data as a port, also performing bindings. Cannot be called once ports are
+ * requested (so that all calls to ports will return the same information).
+ * Internal API.
+ *
+ * TODO(twigg): Specifically walk the Data definition to call out which nodes
+ * are problematic.
+ */
+ protected def IO[T <: Data](iodef: T): T = chisel3.experimental.IO.apply(iodef) // scalastyle:ignore method.name
+
+ //
+ // Internal Functions
+ //
+
+ /** Keep component for signal names */
+ private[chisel3] var _component: Option[Component] = None
+
+ /** Signal name (for simulation). */
+ override def instanceName: String =
+ if (_parent == None) name else _component match {
+ case None => getRef.name
+ case Some(c) => getRef fullName c
+ }
+
+ }
+}