diff options
| author | Jack Koenig | 2020-03-22 18:13:58 -0700 |
|---|---|---|
| committer | Jack Koenig | 2020-03-25 19:17:15 -0700 |
| commit | fbf5e6f1a0e8bf535d465b748ad554575fe62156 (patch) | |
| tree | 578858ab6d219ca6daf44cf87b73f75054989097 /core/src/main/scala/chisel3/internal | |
| parent | b2e004fb615a3c931d910a338b9faa99c1c975d7 (diff) | |
Rename subprojects to more canonical names
* Rename coreMacros to macros
* Rename chiselFrontend to core
Also make each subproject publish with "chisel3-" as a prefix
Diffstat (limited to 'core/src/main/scala/chisel3/internal')
| -rw-r--r-- | core/src/main/scala/chisel3/internal/BiConnect.scala | 333 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Binding.scala | 114 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Builder.scala | 452 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Error.scala | 213 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/MonoConnect.scala | 264 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Namer.scala | 154 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/SourceInfo.scala | 61 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/firrtl/Converter.scala | 275 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/firrtl/IR.scala | 750 |
9 files changed, 2616 insertions, 0 deletions
diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala new file mode 100644 index 00000000..6b4c1070 --- /dev/null +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -0,0 +1,333 @@ +// See LICENSE for license details. + +package chisel3.internal + +import chisel3._ +import chisel3.experimental.{Analog, BaseModule, attach} +import chisel3.internal.Builder.pushCommand +import chisel3.internal.firrtl.{Connect, DefInvalid} +import scala.language.experimental.macros +import chisel3.internal.sourceinfo._ + +/** +* BiConnect.connect executes a bidirectional connection element-wise. +* +* Note that the arguments are left and right (not source and sink) so the +* intent is for the operation to be commutative. +* +* The connect operation will recurse down the left Data (with the right Data). +* An exception will be thrown if a movement through the left cannot be matched +* in the right (or if the right side has extra fields). +* +* See elemConnect for details on how the root connections are issued. +* +*/ + +private[chisel3] object BiConnect { + // scalastyle:off method.name public.methods.have.type + // These are all the possible exceptions that can be thrown. + // These are from element-level connection + def BothDriversException = + BiConnectException(": Both Left and Right are drivers") + def NeitherDriverException = + BiConnectException(": Neither Left nor Right is a driver") + def UnknownDriverException = + BiConnectException(": Locally unclear whether Left or Right (both internal)") + def UnknownRelationException = + BiConnectException(": Left or Right unavailable to current module.") + // These are when recursing down aggregate types + def MismatchedVecException = + BiConnectException(": Left and Right are different length Vecs.") + def MissingLeftFieldException(field: String) = + BiConnectException(s".$field: Left Record missing field ($field).") + def MissingRightFieldException(field: String) = + BiConnectException(s": Right Record missing field ($field).") + def MismatchedException(left: String, right: String) = + BiConnectException(s": Left ($left) and Right ($right) have different types.") + def AttachAlreadyBulkConnectedException(sourceInfo: SourceInfo) = + BiConnectException(sourceInfo.makeMessage(": Analog previously bulk connected at " + _)) + def DontCareCantBeSink = + BiConnectException(": DontCare cannot be a connection sink (LHS)") + // scalastyle:on method.name public.methods.have.type + + /** This function is what recursively tries to connect a left and right together + * + * There is some cleverness in the use of internal try-catch to catch exceptions + * during the recursive decent and then rethrow them with extra information added. + * This gives the user a 'path' to where in the connections things went wrong. + */ + def connect(sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Data, right: Data, context_mod: RawModule): Unit = { // scalastyle:ignore line.size.limit cyclomatic.complexity method.length + (left, right) match { + // Handle element case (root case) + case (left_a: Analog, right_a: Analog) => + try { + markAnalogConnected(sourceInfo, left_a, context_mod) + markAnalogConnected(sourceInfo, right_a, context_mod) + } catch { // convert attach exceptions to BiConnectExceptions + case attach.AttachException(message) => throw BiConnectException(message) + } + attach.impl(Seq(left_a, right_a), context_mod)(sourceInfo) + case (left_a: Analog, DontCare) => + try { + markAnalogConnected(sourceInfo, left_a, context_mod) + } catch { // convert attach exceptions to BiConnectExceptions + case attach.AttachException(message) => throw BiConnectException(message) + } + pushCommand(DefInvalid(sourceInfo, left_a.lref)) + case (DontCare, right_a: Analog) => connect(sourceInfo, connectCompileOptions, right, left, context_mod) + case (left_e: Element, right_e: Element) => { + elemConnect(sourceInfo, connectCompileOptions, left_e, right_e, context_mod) + // TODO(twigg): Verify the element-level classes are connectable + } + // Handle Vec case + case (left_v: Vec[Data@unchecked], right_v: Vec[Data@unchecked]) => { + if (left_v.length != right_v.length) { + throw MismatchedVecException + } + for (idx <- 0 until left_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, left_v(idx), right_v(idx), context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s"($idx)$message") + } + } + } + // Handle Vec connected to DontCare + case (left_v: Vec[Data@unchecked], DontCare) => { + for (idx <- 0 until left_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, left_v(idx), right, context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s"($idx)$message") + } + } + } + // Handle DontCare connected to Vec + case (DontCare, right_v: Vec[Data@unchecked]) => { + for (idx <- 0 until right_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, left, right_v(idx), context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s"($idx)$message") + } + } + } + // Handle Records defined in Chisel._ code (change to NotStrict) + case (left_r: Record, right_r: Record) => (left_r.compileOptions, right_r.compileOptions) match { + case (ExplicitCompileOptions.NotStrict, _) => + left_r.bulkConnect(right_r)(sourceInfo, ExplicitCompileOptions.NotStrict) + case (_, ExplicitCompileOptions.NotStrict) => + left_r.bulkConnect(right_r)(sourceInfo, ExplicitCompileOptions.NotStrict) + case _ => recordConnect(sourceInfo, connectCompileOptions, left_r, right_r, context_mod) + } + + // Handle Records connected to DontCare (change to NotStrict) + case (left_r: Record, DontCare) => + left_r.compileOptions match { + case ExplicitCompileOptions.NotStrict => + left.bulkConnect(right)(sourceInfo, ExplicitCompileOptions.NotStrict) + case _ => + // For each field in left, descend with right + for ((field, left_sub) <- left_r.elements) { + try { + connect(sourceInfo, connectCompileOptions, left_sub, right, context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s".$field$message") + } + } + } + case (DontCare, right_r: Record) => + right_r.compileOptions match { + case ExplicitCompileOptions.NotStrict => + left.bulkConnect(right)(sourceInfo, ExplicitCompileOptions.NotStrict) + case _ => + // For each field in left, descend with right + for ((field, right_sub) <- right_r.elements) { + try { + connect(sourceInfo, connectCompileOptions, left, right_sub, context_mod) + } catch { + case BiConnectException(message) => throw BiConnectException(s".$field$message") + } + } + } + + // Left and right are different subtypes of Data so fail + case (left, right) => throw MismatchedException(left.toString, right.toString) + } + } + + // Do connection of two Records + def recordConnect(sourceInfo: SourceInfo, + connectCompileOptions: CompileOptions, + left_r: Record, + right_r: Record, + context_mod: RawModule): Unit = { + // Verify right has no extra fields that left doesn't have + for((field, right_sub) <- right_r.elements) { + if(!left_r.elements.isDefinedAt(field)) { + if (connectCompileOptions.connectFieldsMustMatch) { + throw MissingLeftFieldException(field) + } + } + } + // For each field in left, descend with right + for((field, left_sub) <- left_r.elements) { + try { + right_r.elements.get(field) match { + case Some(right_sub) => connect(sourceInfo, connectCompileOptions, left_sub, right_sub, context_mod) + case None => { + if (connectCompileOptions.connectFieldsMustMatch) { + throw MissingRightFieldException(field) + } + } + } + } catch { + case BiConnectException(message) => throw BiConnectException(s".$field$message") + } + } + } + + + // These functions (finally) issue the connection operation + // Issue with right as sink, left as source + private def issueConnectL2R(left: Element, right: Element)(implicit sourceInfo: SourceInfo): Unit = { + // Source and sink are ambiguous in the case of a Bi/Bulk Connect (<>). + // If either is a DontCareBinding, just issue a DefInvalid for the other, + // otherwise, issue a Connect. + (left.topBinding, right.topBinding) match { + case (lb: DontCareBinding, _) => pushCommand(DefInvalid(sourceInfo, right.lref)) + case (_, rb: DontCareBinding) => pushCommand(DefInvalid(sourceInfo, left.lref)) + case (_, _) => pushCommand(Connect(sourceInfo, right.lref, left.ref)) + } + } + // Issue with left as sink, right as source + private def issueConnectR2L(left: Element, right: Element)(implicit sourceInfo: SourceInfo): Unit = { + // Source and sink are ambiguous in the case of a Bi/Bulk Connect (<>). + // If either is a DontCareBinding, just issue a DefInvalid for the other, + // otherwise, issue a Connect. + (left.topBinding, right.topBinding) match { + case (lb: DontCareBinding, _) => pushCommand(DefInvalid(sourceInfo, right.lref)) + case (_, rb: DontCareBinding) => pushCommand(DefInvalid(sourceInfo, left.lref)) + case (_, _) => pushCommand(Connect(sourceInfo, left.lref, right.ref)) + } + } + + // This function checks if element-level connection operation allowed. + // Then it either issues it or throws the appropriate exception. + def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Element, right: Element, context_mod: RawModule): Unit = { // scalastyle:ignore line.size.limit cyclomatic.complexity method.length + import BindingDirection.{Internal, Input, Output} // Using extensively so import these + // If left or right have no location, assume in context module + // This can occur if one of them is a literal, unbound will error previously + val left_mod: BaseModule = left.topBinding.location.getOrElse(context_mod) + val right_mod: BaseModule = right.topBinding.location.getOrElse(context_mod) + + val left_direction = BindingDirection.from(left.topBinding, left.direction) + val right_direction = BindingDirection.from(right.topBinding, right.direction) + + // CASE: Context is same module as left node and right node is in a child module + if( (left_mod == context_mod) && + (right_mod._parent.map(_ == context_mod).getOrElse(false)) ) { + // Thus, right node better be a port node and thus have a direction hint + ((left_direction, right_direction): @unchecked) match { + // CURRENT MOD CHILD MOD + case (Input, Input) => issueConnectL2R(left, right) + case (Internal, Input) => issueConnectL2R(left, right) + + case (Output, Output) => issueConnectR2L(left, right) + case (Internal, Output) => issueConnectR2L(left, right) + + case (Input, Output) => throw BothDriversException + case (Output, Input) => throw NeitherDriverException + case (_, Internal) => throw UnknownRelationException + } + } + + // CASE: Context is same module as right node and left node is in child module + else if( (right_mod == context_mod) && + (left_mod._parent.map(_ == context_mod).getOrElse(false)) ) { + // Thus, left node better be a port node and thus have a direction hint + ((left_direction, right_direction): @unchecked) match { + // CHILD MOD CURRENT MOD + case (Input, Input) => issueConnectR2L(left, right) + case (Input, Internal) => issueConnectR2L(left, right) + + case (Output, Output) => issueConnectL2R(left, right) + case (Output, Internal) => issueConnectL2R(left, right) + + case (Input, Output) => throw NeitherDriverException + case (Output, Input) => throw BothDriversException + case (Internal, _) => throw UnknownRelationException + } + } + + // CASE: Context is same module that both left node and right node are in + else if( (context_mod == left_mod) && (context_mod == right_mod) ) { + ((left_direction, right_direction): @unchecked) match { + // CURRENT MOD CURRENT MOD + case (Input, Output) => issueConnectL2R(left, right) + case (Input, Internal) => issueConnectL2R(left, right) + case (Internal, Output) => issueConnectL2R(left, right) + + case (Output, Input) => issueConnectR2L(left, right) + case (Output, Internal) => issueConnectR2L(left, right) + case (Internal, Input) => issueConnectR2L(left, right) + + case (Input, Input) => throw BothDriversException + case (Output, Output) => throw BothDriversException + case (Internal, Internal) => { + if (connectCompileOptions.dontAssumeDirectionality) { + throw UnknownDriverException + } else { + issueConnectR2L(left, right) + } + } + } + } + + // CASE: Context is the parent module of both the module containing left node + // and the module containing right node + // Note: This includes case when left and right in same module but in parent + else if( (left_mod._parent.map(_ == context_mod).getOrElse(false)) && + (right_mod._parent.map(_ == context_mod).getOrElse(false)) + ) { + // Thus both nodes must be ports and have a direction hint + ((left_direction, right_direction): @unchecked) match { + // CHILD MOD CHILD MOD + case (Input, Output) => issueConnectR2L(left, right) + case (Output, Input) => issueConnectL2R(left, right) + + case (Input, Input) => throw NeitherDriverException + case (Output, Output) => throw BothDriversException + case (_, Internal) => + if (connectCompileOptions.dontAssumeDirectionality) { + throw UnknownRelationException + } else { + issueConnectR2L(left, right) + } + case (Internal, _) => + if (connectCompileOptions.dontAssumeDirectionality) { + throw UnknownRelationException + } else { + issueConnectR2L(left, right) + } + } + } + + // Not quite sure where left and right are compared to current module + // so just error out + else throw UnknownRelationException + } + + // This function checks if analog element-level attaching is allowed, then marks the Analog as connected + def markAnalogConnected(implicit sourceInfo: SourceInfo, analog: Analog, contextModule: RawModule): Unit = { + analog.biConnectLocs.get(contextModule) match { + case Some(sl) => throw AttachAlreadyBulkConnectedException(sl) + case None => // Do nothing + } + // Mark bulk connected + analog.biConnectLocs(contextModule) = sourceInfo + } +} diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala new file mode 100644 index 00000000..07c44f9f --- /dev/null +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -0,0 +1,114 @@ +// See LICENSE for license details. + +package chisel3.internal + +import chisel3._ +import chisel3.experimental.BaseModule +import chisel3.internal.firrtl.LitArg + +/** Requires that a node is hardware ("bound") + */ +object requireIsHardware { + def apply(node: Data, msg: String = ""): Unit = { + node._parent match { // Compatibility layer hack + case Some(x: BaseModule) => x._compatAutoWrapPorts + case _ => + } + if (!node.isSynthesizable) { + val prefix = if (msg.nonEmpty) s"$msg " else "" + throw ExpectedHardwareException(s"$prefix'$node' must be hardware, " + + "not a bare Chisel type. Perhaps you forgot to wrap it in Wire(_) or IO(_)?") + } + } +} + +/** Requires that a node is a chisel type (not hardware, "unbound") + */ +object requireIsChiselType { + def apply(node: Data, msg: String = ""): Unit = if (node.isSynthesizable) { + val prefix = if (msg.nonEmpty) s"$msg " else "" + throw ExpectedChiselTypeException(s"$prefix'$node' must be a Chisel type, not hardware") + } +} + +// Element only direction used for the Binding system only. +private[chisel3] sealed abstract class BindingDirection +private[chisel3] object BindingDirection { + /** Internal type or wire + */ + case object Internal extends BindingDirection + /** Module port with output direction + */ + case object Output extends BindingDirection + /** Module port with input direction + */ + case object Input extends BindingDirection + + /** Determine the BindingDirection of an Element given its top binding and resolved direction. + */ + def from(binding: TopBinding, direction: ActualDirection): BindingDirection = { + binding match { + case PortBinding(_) => direction match { + case ActualDirection.Output => Output + case ActualDirection.Input => Input + case dir => throw new RuntimeException(s"Unexpected port element direction '$dir'") + } + case _ => Internal + } + } +} + +// Location refers to 'where' in the Module hierarchy this lives +sealed trait Binding { + def location: Option[BaseModule] +} +// Top-level binding representing hardware, not a pointer to another binding (like ChildBinding) +sealed trait TopBinding extends Binding + +// Constrained-ness refers to whether 'bound by Module boundaries' +// An unconstrained binding, like a literal, can be read by everyone +sealed trait UnconstrainedBinding extends TopBinding { + def location: Option[BaseModule] = None +} +// A constrained binding can only be read/written by specific modules +// Location will track where this Module is, and the bound object can be referenced in FIRRTL +sealed trait ConstrainedBinding extends TopBinding { + def enclosure: BaseModule + def location: Option[BaseModule] = { + // If an aspect is present, return the aspect module. Otherwise, return the enclosure module + // This allows aspect modules to pretend to be enclosed modules for connectivity checking, + // inside vs outside instance checking, etc. + Builder.aspectModule(enclosure) match { + case None => Some(enclosure) + case Some(aspect) => Some(aspect) + } + } +} + +// A binding representing a data that cannot be (re)assigned to. +sealed trait ReadOnlyBinding extends TopBinding + +// TODO(twigg): Ops between unenclosed nodes can also be unenclosed +// However, Chisel currently binds all op results to a module +case class OpBinding(enclosure: RawModule) extends ConstrainedBinding with ReadOnlyBinding +case class MemoryPortBinding(enclosure: RawModule) extends ConstrainedBinding +case class PortBinding(enclosure: BaseModule) extends ConstrainedBinding +case class RegBinding(enclosure: RawModule) extends ConstrainedBinding +case class WireBinding(enclosure: RawModule) extends ConstrainedBinding + +case class ChildBinding(parent: Data) extends Binding { + def location: Option[BaseModule] = parent.topBinding.location +} +/** Special binding for Vec.sample_element */ +case class SampleElementBinding[T <: Data](parent: Vec[T]) extends Binding { + def location = parent.topBinding.location +} +// A DontCare element has a specific Binding, somewhat like a literal. +// It is a source (RHS). It may only be connected/applied to sinks. +case class DontCareBinding() extends UnconstrainedBinding + +sealed trait LitBinding extends UnconstrainedBinding with ReadOnlyBinding +// Literal binding attached to a element that is not part of a Bundle. +case class ElementLitBinding(litArg: LitArg) extends LitBinding +// Literal binding attached to the root of a Bundle, containing literal values of its children. +case class BundleLitBinding(litMap: Map[Data, LitArg]) extends LitBinding diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala new file mode 100644 index 00000000..773a9ad1 --- /dev/null +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -0,0 +1,452 @@ +// See LICENSE for license details. + +package chisel3.internal + +import scala.util.DynamicVariable +import scala.collection.mutable.ArrayBuffer +import chisel3._ +import chisel3.experimental._ +import chisel3.internal.firrtl._ +import chisel3.internal.naming._ +import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget} + +import scala.collection.mutable + +private[chisel3] class Namespace(keywords: Set[String]) { + private val names = collection.mutable.HashMap[String, Long]() + for (keyword <- keywords) + names(keyword) = 1 + + private def rename(n: String): String = { + val index = names(n) + val tryName = s"${n}_${index}" + names(n) = index + 1 + if (this contains tryName) rename(n) else tryName + } + + private def sanitize(s: String, leadingDigitOk: Boolean = false): String = { + // TODO what character set does FIRRTL truly support? using ANSI C for now + def legalStart(c: Char) = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' + def legal(c: Char) = legalStart(c) || (c >= '0' && c <= '9') + val res = s filter legal + val headOk = (!res.isEmpty) && (leadingDigitOk || legalStart(res.head)) + if (headOk) res else s"_$res" + } + + def contains(elem: String): Boolean = names.contains(elem) + + // leadingDigitOk is for use in fields of Records + def name(elem: String, leadingDigitOk: Boolean = false): String = { + val sanitized = sanitize(elem, leadingDigitOk) + if (this contains sanitized) { + name(rename(sanitized)) + } else { + names(sanitized) = 1 + sanitized + } + } +} + +private[chisel3] object Namespace { + /** Constructs an empty Namespace */ + def empty: Namespace = new Namespace(Set.empty[String]) +} + +private[chisel3] class IdGen { + private var counter = -1L + def next: Long = { + counter += 1 + counter + } +} + +/** Public API to access Node/Signal names. + * currently, the node's name, the full path name, and references to its parent Module and component. + * These are only valid once the design has been elaborated, and should not be used during its construction. + */ +trait InstanceId { + def instanceName: String + def pathName: String + def parentPathName: String + def parentModName: String + /** Returns a FIRRTL Named that refers to this object in the elaborated hardware graph */ + def toNamed: Named + /** Returns a FIRRTL IsMember that refers to this object in the elaborated hardware graph */ + def toTarget: IsMember + /** Returns a FIRRTL IsMember that refers to the absolute path to this object in the elaborated hardware graph */ + def toAbsoluteTarget: IsMember +} + +private[chisel3] trait HasId extends InstanceId { + private[chisel3] def _onModuleClose: Unit = {} // scalastyle:ignore method.name + private[chisel3] val _parent: Option[BaseModule] = Builder.currentModule + _parent.foreach(_.addId(this)) + + private[chisel3] val _id: Long = Builder.idGen.next + + // TODO: remove this, but its removal seems to cause a nasty Scala compiler crash. + override def hashCode: Int = super.hashCode() + override def equals(that: Any): Boolean = super.equals(that) + + // Facilities for 'suggesting' a name to this. + // Post-name hooks called to carry the suggestion to other candidates as needed + private var suggested_name: Option[String] = None + private val postname_hooks = scala.collection.mutable.ListBuffer.empty[String=>Unit] + // Only takes the first suggestion! + def suggestName(name: =>String): this.type = { + if(suggested_name.isEmpty) suggested_name = Some(name) + for(hook <- postname_hooks) { hook(name) } + this + } + private[chisel3] def suggestedName: Option[String] = suggested_name + private[chisel3] def addPostnameHook(hook: String=>Unit): Unit = postname_hooks += hook + + // Uses a namespace to convert suggestion into a true name + // Will not do any naming if the reference already assigned. + // (e.g. tried to suggest a name to part of a Record) + private[chisel3] def forceName(default: =>String, namespace: Namespace): Unit = + if(_ref.isEmpty) { + val candidate_name = suggested_name.getOrElse(default) + val available_name = namespace.name(candidate_name) + setRef(Ref(available_name)) + } + + private var _ref: Option[Arg] = None + private[chisel3] def setRef(imm: Arg): Unit = _ref = Some(imm) + private[chisel3] def setRef(parent: HasId, name: String): Unit = setRef(Slot(Node(parent), name)) + private[chisel3] def setRef(parent: HasId, index: Int): Unit = setRef(Index(Node(parent), ILit(index))) + private[chisel3] def setRef(parent: HasId, index: UInt): Unit = setRef(Index(Node(parent), index.ref)) + private[chisel3] def getRef: Arg = _ref.get + private[chisel3] def getOptionRef: Option[Arg] = _ref + + // Implementation of public methods. + def instanceName: String = _parent match { + case Some(p) => p._component match { + case Some(c) => _ref match { + case Some(arg) => arg fullName c + case None => suggested_name.getOrElse("??") + } + case None => throwException("signalName/pathName should be called after circuit elaboration") + } + case None => throwException("this cannot happen") + } + def pathName: String = _parent match { + case None => instanceName + case Some(p) => s"${p.pathName}.$instanceName" + } + def parentPathName: String = _parent match { + case Some(p) => p.pathName + case None => throwException(s"$instanceName doesn't have a parent") + } + def parentModName: String = _parent match { + case Some(p) => p.name + case None => throwException(s"$instanceName doesn't have a parent") + } + // TODO Should this be public? + protected def circuitName: String = _parent match { + case None => instanceName + case Some(p) => p.circuitName + } + + private[chisel3] def getPublicFields(rootClass: Class[_]): Seq[java.lang.reflect.Method] = { + // Suggest names to nodes using runtime reflection + def getValNames(c: Class[_]): Set[String] = { + if (c == rootClass) { + Set() + } else { + getValNames(c.getSuperclass) ++ c.getDeclaredFields.map(_.getName) + } + } + val valNames = getValNames(this.getClass) + def isPublicVal(m: java.lang.reflect.Method) = + m.getParameterTypes.isEmpty && valNames.contains(m.getName) && !m.getDeclaringClass.isAssignableFrom(rootClass) + this.getClass.getMethods.sortWith(_.getName < _.getName).filter(isPublicVal(_)) + } +} +/** Holds the implementation of toNamed for Data and MemBase */ +private[chisel3] trait NamedComponent extends HasId { + /** Returns a FIRRTL ComponentName that references this object + * @note Should not be called until circuit elaboration is complete + */ + final def toNamed: ComponentName = + ComponentName(this.instanceName, ModuleName(this.parentModName, CircuitName(this.circuitName))) + + /** Returns a FIRRTL ReferenceTarget that references this object + * @note Should not be called until circuit elaboration is complete + */ + final def toTarget: ReferenceTarget = { + val name = this.instanceName + import _root_.firrtl.annotations.{Target, TargetToken} + Target.toTargetTokens(name).toList match { + case TargetToken.Ref(r) :: components => ReferenceTarget(this.circuitName, this.parentModName, Nil, r, components) + case other => + throw _root_.firrtl.annotations.Target.NamedException(s"Cannot convert $name into [[ReferenceTarget]]: $other") + } + } + + final def toAbsoluteTarget: ReferenceTarget = { + val localTarget = toTarget + _parent match { + case Some(parent) => parent.toAbsoluteTarget.ref(localTarget.ref).copy(component = localTarget.component) + case None => localTarget + } + } +} + +// Mutable global state for chisel that can appear outside a Builder context +private[chisel3] class ChiselContext() { + val idGen = new IdGen + + // Record the Bundle instance, class name, method name, and reverse stack trace position of open Bundles + val bundleStack: ArrayBuffer[(Bundle, String, String, Int)] = ArrayBuffer() +} + +private[chisel3] class DynamicContext() { + val globalNamespace = Namespace.empty + val components = ArrayBuffer[Component]() + val annotations = ArrayBuffer[ChiselAnnotation]() + var currentModule: Option[BaseModule] = None + + /** Contains a mapping from a elaborated module to their aspect + * Set by [[ModuleAspect]] + */ + val aspectModule: mutable.HashMap[BaseModule, BaseModule] = mutable.HashMap.empty[BaseModule, BaseModule] + + // Set by object Module.apply before calling class Module constructor + // Used to distinguish between no Module() wrapping, multiple wrappings, and rewrapping + var readyForModuleConstr: Boolean = false + var whenDepth: Int = 0 // Depth of when nesting + var currentClock: Option[Clock] = None + var currentReset: Option[Reset] = None + val errors = new ErrorLog + val namingStack = new NamingStack +} + +//scalastyle:off number.of.methods +private[chisel3] object Builder { + // All global mutable state must be referenced via dynamicContextVar!! + private val dynamicContextVar = new DynamicVariable[Option[DynamicContext]](None) + private def dynamicContext: DynamicContext = { + require(dynamicContextVar.value.isDefined, "must be inside Builder context") + dynamicContextVar.value.get + } + + private val chiselContext = new DynamicVariable[ChiselContext](new ChiselContext) + + // Initialize any singleton objects before user code inadvertently inherits them. + private def initializeSingletons(): Unit = { + // This used to contain: + // val dummy = core.DontCare + // but this would occasionally produce hangs due to static initialization deadlock + // when Builder initialization collided with chisel3.package initialization of the DontCare value. + // See: + // http://ternarysearch.blogspot.com/2013/07/static-initialization-deadlock.html + // https://bugs.openjdk.java.net/browse/JDK-8037567 + // https://stackoverflow.com/questions/28631656/runnable-thread-state-but-in-object-wait + } + + def namingStackOption: Option[NamingStack] = dynamicContextVar.value.map(_.namingStack) + + def idGen: IdGen = chiselContext.value.idGen + + def globalNamespace: Namespace = dynamicContext.globalNamespace + def components: ArrayBuffer[Component] = dynamicContext.components + def annotations: ArrayBuffer[ChiselAnnotation] = dynamicContext.annotations + def namingStack: NamingStack = dynamicContext.namingStack + + def currentModule: Option[BaseModule] = dynamicContextVar.value match { + case Some(dyanmicContext) => dynamicContext.currentModule + case _ => None + } + def currentModule_=(target: Option[BaseModule]): Unit = { + dynamicContext.currentModule = target + } + def aspectModule(module: BaseModule): Option[BaseModule] = dynamicContextVar.value match { + case Some(dynamicContext) => dynamicContext.aspectModule.get(module) + case _ => None + } + def addAspect(module: BaseModule, aspect: BaseModule): Unit = { + dynamicContext.aspectModule += ((module, aspect)) + } + def forcedModule: BaseModule = currentModule match { + case Some(module) => module + case None => throwException( + "Error: Not in a Module. Likely cause: Missed Module() wrap or bare chisel API call." + // A bare api call is, e.g. calling Wire() from the scala console). + ) + } + def referenceUserModule: RawModule = { + currentModule match { + case Some(module: RawModule) => + aspectModule(module) match { + case Some(aspect: RawModule) => aspect + case other => module + } + case _ => throwException( + "Error: Not in a RawModule. Likely cause: Missed Module() wrap, bare chisel API call, or attempting to construct hardware inside a BlackBox." // scalastyle:ignore line.size.limit + // A bare api call is, e.g. calling Wire() from the scala console). + ) + } + } + def forcedUserModule: RawModule = currentModule match { + case Some(module: RawModule) => module + case _ => throwException( + "Error: Not in a UserModule. Likely cause: Missed Module() wrap, bare chisel API call, or attempting to construct hardware inside a BlackBox." // scalastyle:ignore line.size.limit + // A bare api call is, e.g. calling Wire() from the scala console). + ) + } + def readyForModuleConstr: Boolean = dynamicContext.readyForModuleConstr + def readyForModuleConstr_=(target: Boolean): Unit = { + dynamicContext.readyForModuleConstr = target + } + def whenDepth: Int = dynamicContext.whenDepth + def whenDepth_=(target: Int): Unit = { + dynamicContext.whenDepth = target + } + def currentClock: Option[Clock] = dynamicContext.currentClock + def currentClock_=(newClock: Option[Clock]): Unit = { + dynamicContext.currentClock = newClock + } + + def currentReset: Option[Reset] = dynamicContext.currentReset + def currentReset_=(newReset: Option[Reset]): Unit = { + dynamicContext.currentReset = newReset + } + + def forcedClock: Clock = currentClock.getOrElse( + throwException("Error: No implicit clock.") + ) + def forcedReset: Reset = currentReset.getOrElse( + throwException("Error: No implicit reset.") + ) + + // TODO(twigg): Ideally, binding checks and new bindings would all occur here + // However, rest of frontend can't support this yet. + def pushCommand[T <: Command](c: T): T = { + forcedUserModule.addCommand(c) + c + } + def pushOp[T <: Data](cmd: DefPrim[T]): T = { + // Bind each element of the returned Data to being a Op + cmd.id.bind(OpBinding(forcedUserModule)) + pushCommand(cmd).id + } + + // Called when Bundle construction begins, used to record a stack of open Bundle constructors to + // record candidates for Bundle autoclonetype. This is a best-effort guess. + // Returns the current stack of open Bundles + // Note: elt will NOT have finished construction, its elements cannot be accessed + def updateBundleStack(elt: Bundle): Seq[Bundle] = { + val stackElts = Thread.currentThread().getStackTrace() + .reverse // so stack frame numbers are deterministic across calls + .dropRight(2) // discard Thread.getStackTrace and updateBundleStack + + // Determine where we are in the Bundle stack + val eltClassName = elt.getClass.getName + val eltStackPos = stackElts.map(_.getClassName).lastIndexOf(eltClassName) + + // Prune the existing Bundle stack of closed Bundles + // If we know where we are in the stack, discard frames above that + val stackEltsTop = if (eltStackPos >= 0) eltStackPos else stackElts.size + val pruneLength = chiselContext.value.bundleStack.reverse.prefixLength { case (_, cname, mname, pos) => + pos >= stackEltsTop || stackElts(pos).getClassName != cname || stackElts(pos).getMethodName != mname + } + chiselContext.value.bundleStack.trimEnd(pruneLength) + + // Return the stack state before adding the most recent bundle + val lastStack = chiselContext.value.bundleStack.map(_._1).toSeq + + // Append the current Bundle to the stack, if it's on the stack trace + if (eltStackPos >= 0) { + val stackElt = stackElts(eltStackPos) + chiselContext.value.bundleStack.append((elt, eltClassName, stackElt.getMethodName, eltStackPos)) + } + // Otherwise discard the stack frame, this shouldn't fail noisily + + lastStack + } + + /** Recursively suggests names to supported "container" classes + * Arbitrary nestings of supported classes are allowed so long as the + * innermost element is of type HasId + * (Note: Map is Iterable[Tuple2[_,_]] and thus excluded) + */ + def nameRecursively(prefix: String, nameMe: Any, namer: (HasId, String) => Unit): Unit = nameMe match { + case (id: HasId) => namer(id, prefix) + case Some(elt) => nameRecursively(prefix, elt, namer) + case (iter: Iterable[_]) if iter.hasDefiniteSize => + for ((elt, i) <- iter.zipWithIndex) { + nameRecursively(s"${prefix}_${i}", elt, namer) + } + case _ => // Do nothing + } + + def errors: ErrorLog = dynamicContext.errors + def error(m: => String): Unit = if (dynamicContextVar.value.isDefined) errors.error(m) + def warning(m: => String): Unit = if (dynamicContextVar.value.isDefined) errors.warning(m) + def deprecated(m: => String, location: Option[String] = None): Unit = + if (dynamicContextVar.value.isDefined) errors.deprecated(m, location) + + /** Record an exception as an error, and throw it. + * + * @param m exception message + */ + @throws(classOf[ChiselException]) + def exception(m: => String): Unit = { + error(m) + throwException(m) + } + + def build[T <: RawModule](f: => T): (Circuit, T) = { + chiselContext.withValue(new ChiselContext) { + dynamicContextVar.withValue(Some(new DynamicContext())) { + errors.info("Elaborating design...") + val mod = f + mod.forceName(mod.name, globalNamespace) + errors.checkpoint() + errors.info("Done elaborating.") + + (Circuit(components.last.name, components, annotations), mod) + } + } + } + initializeSingletons() +} + +/** Allows public access to the naming stack in Builder / DynamicContext, and handles invocations + * outside a Builder context. + * Necessary because naming macros expand in user code and don't have access into private[chisel3] + * objects. + */ +object DynamicNamingStack { + def pushContext(): NamingContextInterface = { + Builder.namingStackOption match { + case Some(namingStack) => namingStack.pushContext() + case None => DummyNamer + } + } + + def popReturnContext[T <: Any](prefixRef: T, until: NamingContextInterface): T = { + until match { + case DummyNamer => + require(Builder.namingStackOption.isEmpty, + "Builder context must remain stable throughout a chiselName-annotated function invocation") + case context: NamingContext => + require(Builder.namingStackOption.isDefined, + "Builder context must remain stable throughout a chiselName-annotated function invocation") + Builder.namingStackOption.get.popContext(prefixRef, context) + } + prefixRef + } + + def length() : Int = Builder.namingStackOption.get.length +} + +/** Casts BigInt to Int, issuing an error when the input isn't representable. */ +private[chisel3] object castToInt { + def apply(x: BigInt, msg: String): Int = { + val res = x.toInt + require(x == res, s"$msg $x is too large to be represented as Int") + res + } +} diff --git a/core/src/main/scala/chisel3/internal/Error.scala b/core/src/main/scala/chisel3/internal/Error.scala new file mode 100644 index 00000000..369da52e --- /dev/null +++ b/core/src/main/scala/chisel3/internal/Error.scala @@ -0,0 +1,213 @@ +// See LICENSE for license details. + +package chisel3.internal + +import scala.annotation.tailrec +import scala.collection.mutable.{ArrayBuffer, LinkedHashMap} + +class ChiselException(message: String, cause: Throwable = null) extends Exception(message, cause) { + + /** Package names whose stack trace elements should be trimmed when generating a trimmed stack trace */ + val blacklistPackages: Set[String] = Set("chisel3", "scala", "java", "sun", "sbt") + + /** The object name of Chisel's internal `Builder`. Everything stack trace element after this will be trimmed. */ + val builderName: String = chisel3.internal.Builder.getClass.getName + + /** Examine a [[Throwable]], to extract all its causes. Innermost cause is first. + * @param throwable an exception to examine + * @return a sequence of all the causes with innermost cause first + */ + @tailrec + private def getCauses(throwable: Throwable, acc: Seq[Throwable] = Seq.empty): Seq[Throwable] = + throwable.getCause() match { + case null => throwable +: acc + case a => getCauses(a, throwable +: acc) + } + + /** Returns true if an exception contains */ + private def containsBuilder(throwable: Throwable): Boolean = + throwable.getStackTrace().collectFirst { + case ste if ste.getClassName().startsWith(builderName) => throwable + }.isDefined + + /** Examine this [[ChiselException]] and it's causes for the first [[Throwable]] that contains a stack trace including + * a stack trace element whose declaring class is the [[builderName]]. If no such element exists, return this + * [[ChiselException]]. + */ + private lazy val likelyCause: Throwable = + getCauses(this).collectFirst{ case a if containsBuilder(a) => a }.getOrElse(this) + + /** For an exception, return a stack trace trimmed to user code only + * + * This does the following actions: + * + * 1. Trims the top of the stack trace while elements match [[blacklistPackages]] + * 2. Trims the bottom of the stack trace until an element matches [[builderName]] + * 3. Trims from the [[builderName]] all [[blacklistPackages]] + * + * @param throwable the exception whose stack trace should be trimmed + * @return an array of stack trace elements + */ + private def trimmedStackTrace(throwable: Throwable): Array[StackTraceElement] = { + def isBlacklisted(ste: StackTraceElement) = { + val packageName = ste.getClassName().takeWhile(_ != '.') + blacklistPackages.contains(packageName) + } + + val trimmedLeft = throwable.getStackTrace().view.dropWhile(isBlacklisted) + val trimmedReverse = trimmedLeft.reverse + .dropWhile(ste => !ste.getClassName.startsWith(builderName)) + .dropWhile(isBlacklisted) + trimmedReverse.reverse.toArray + } + + /** trims the top of the stack of elements belonging to [[blacklistPackages]] + * then trims the bottom elements until it reaches [[builderName]] + * then continues trimming elements belonging to [[blacklistPackages]] + */ + @deprecated("This method will be removed in 3.4", "3.3") + def trimmedStackTrace: Array[StackTraceElement] = trimmedStackTrace(this) + + def chiselStackTrace: String = { + val trimmed = trimmedStackTrace(likelyCause) + + val sw = new java.io.StringWriter + sw.write(likelyCause.toString + "\n") + sw.write("\t...\n") + trimmed.foreach(ste => sw.write(s"\tat $ste\n")) + sw.write("\t... (Stack trace trimmed to user code only, rerun with --full-stacktrace if you wish to see the full stack trace)\n") // scalastyle:ignore line.size.limit + sw.toString + } +} + +private[chisel3] object throwException { + def apply(s: String, t: Throwable = null): Nothing = + throw new ChiselException(s, t) +} + +/** Records and reports runtime errors and warnings. */ +private[chisel3] object ErrorLog { + val depTag = s"[${Console.BLUE}deprecated${Console.RESET}]" + val warnTag = s"[${Console.YELLOW}warn${Console.RESET}]" + val errTag = s"[${Console.RED}error${Console.RESET}]" +} + +private[chisel3] class ErrorLog { + /** Log an error message */ + def error(m: => String): Unit = + errors += new Error(m, getUserLineNumber) + + /** Log a warning message */ + def warning(m: => String): Unit = + errors += new Warning(m, getUserLineNumber) + + /** Emit an informational message */ + def info(m: String): Unit = + println(new Info("[%2.3f] %s".format(elapsedTime/1e3, m), None)) // scalastyle:ignore regex + + /** Log a deprecation warning message */ + def deprecated(m: => String, location: Option[String]): Unit = { + val sourceLoc = location match { + case Some(loc) => loc + case None => getUserLineNumber match { + case Some(elt: StackTraceElement) => s"${elt.getFileName}:${elt.getLineNumber}" + case None => "(unknown)" + } + } + + val thisEntry = (m, sourceLoc) + deprecations += ((thisEntry, deprecations.getOrElse(thisEntry, 0) + 1)) + } + + /** Throw an exception if any errors have yet occurred. */ + def checkpoint(): Unit = { + // scalastyle:off line.size.limit regex + deprecations.foreach { case ((message, sourceLoc), count) => + println(s"${ErrorLog.depTag} $sourceLoc ($count calls): $message") + } + errors foreach println + + if (!deprecations.isEmpty) { + println(s"${ErrorLog.warnTag} ${Console.YELLOW}There were ${deprecations.size} deprecated function(s) used." + + s" These may stop compiling in a future release - you are encouraged to fix these issues.${Console.RESET}") + println(s"${ErrorLog.warnTag} Line numbers for deprecations reported by Chisel may be inaccurate; enable scalac compiler deprecation warnings via either of the following methods:") + println(s"${ErrorLog.warnTag} In the sbt interactive console, enter:") + println(s"""${ErrorLog.warnTag} set scalacOptions in ThisBuild ++= Seq("-unchecked", "-deprecation")""") + println(s"${ErrorLog.warnTag} or, in your build.sbt, add the line:") + println(s"""${ErrorLog.warnTag} scalacOptions := Seq("-unchecked", "-deprecation")""") + } + + val allErrors = errors.filter(_.isFatal) + val allWarnings = errors.filter(!_.isFatal) + + if (!allWarnings.isEmpty && !allErrors.isEmpty) { + println(s"${ErrorLog.errTag} There were ${Console.RED}${allErrors.size} error(s)${Console.RESET} and ${Console.YELLOW}${allWarnings.size} warning(s)${Console.RESET} during hardware elaboration.") + } else if (!allWarnings.isEmpty) { + println(s"${ErrorLog.warnTag} There were ${Console.YELLOW}${allWarnings.size} warning(s)${Console.RESET} during hardware elaboration.") + } else if (!allErrors.isEmpty) { + println(s"${ErrorLog.errTag} There were ${Console.RED}${allErrors.size} error(s)${Console.RESET} during hardware elaboration.") + } + + if (!allErrors.isEmpty) { + throwException("Fatal errors during hardware elaboration") + } else { + // No fatal errors, clear accumulated warnings since they've been reported + errors.clear() + } + // scalastyle:on line.size.limit regex + } + + /** Returns the best guess at the first stack frame that belongs to user code. + */ + private def getUserLineNumber = { + def isChiselClassname(className: String): Boolean = { + // List of classpath prefixes that are Chisel internals and should be ignored when looking for user code + // utils are not part of internals and errors there can be reported + val chiselPrefixes = Set( + "java.", + "scala.", + "chisel3.internal.", + "chisel3.experimental.", + "chisel3.package$" // for some compatibility / deprecated types + ) + !chiselPrefixes.filter(className.startsWith(_)).isEmpty + } + + Thread.currentThread().getStackTrace.toList.dropWhile( + // Get rid of everything in Chisel core + ste => isChiselClassname(ste.getClassName) + ).headOption + } + + private val errors = ArrayBuffer[LogEntry]() + private val deprecations = LinkedHashMap[(String, String), Int]() + + private val startTime = System.currentTimeMillis + private def elapsedTime: Long = System.currentTimeMillis - startTime +} + +private abstract class LogEntry(msg: => String, line: Option[StackTraceElement]) { + def isFatal: Boolean = false + def format: String + + override def toString: String = line match { + case Some(l) => s"${format} ${l.getFileName}:${l.getLineNumber}: ${msg} in class ${l.getClassName}" + case None => s"${format} ${msg}" + } + + protected def tag(name: String, color: String): String = + s"[${color}${name}${Console.RESET}]" +} + +private class Error(msg: => String, line: Option[StackTraceElement]) extends LogEntry(msg, line) { + override def isFatal: Boolean = true + def format: String = tag("error", Console.RED) +} + +private class Warning(msg: => String, line: Option[StackTraceElement]) extends LogEntry(msg, line) { + def format: String = tag("warn", Console.YELLOW) +} + +private class Info(msg: => String, line: Option[StackTraceElement]) extends LogEntry(msg, line) { + def format: String = tag("info", Console.MAGENTA) +} diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala new file mode 100644 index 00000000..41402021 --- /dev/null +++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala @@ -0,0 +1,264 @@ +// See LICENSE for license details. + +package chisel3.internal + +import chisel3._ +import chisel3.experimental.{Analog, BaseModule, EnumType, FixedPoint, Interval, UnsafeEnum} +import chisel3.internal.Builder.pushCommand +import chisel3.internal.firrtl.{Connect, DefInvalid} +import scala.language.experimental.macros +import chisel3.internal.sourceinfo.SourceInfo + +/** +* MonoConnect.connect executes a mono-directional connection element-wise. +* +* Note that this isn't commutative. There is an explicit source and sink +* already determined before this function is called. +* +* The connect operation will recurse down the left Data (with the right Data). +* An exception will be thrown if a movement through the left cannot be matched +* in the right. The right side is allowed to have extra Record fields. +* Vecs must still be exactly the same size. +* +* See elemConnect for details on how the root connections are issued. +* +* Note that a valid sink must be writable so, one of these must hold: +* - Is an internal writable node (Reg or Wire) +* - Is an output of the current module +* - Is an input of a submodule of the current module +* +* Note that a valid source must be readable so, one of these must hold: +* - Is an internal readable node (Reg, Wire, Op) +* - Is a literal +* - Is a port of the current module or submodule of the current module +*/ + +private[chisel3] object MonoConnect { + // scalastyle:off method.name public.methods.have.type + // These are all the possible exceptions that can be thrown. + // These are from element-level connection + def UnreadableSourceException = + MonoConnectException(": Source is unreadable from current module.") + def UnwritableSinkException = + MonoConnectException(": Sink is unwriteable by current module.") + def UnknownRelationException = + MonoConnectException(": Sink or source unavailable to current module.") + // These are when recursing down aggregate types + def MismatchedVecException = + MonoConnectException(": Sink and Source are different length Vecs.") + def MissingFieldException(field: String) = + MonoConnectException(s": Source Record missing field ($field).") + def MismatchedException(sink: String, source: String) = + MonoConnectException(s": Sink ($sink) and Source ($source) have different types.") + def DontCareCantBeSink = + MonoConnectException(": DontCare cannot be a connection sink (LHS)") + def AnalogCantBeMonoSink = + MonoConnectException(": Analog cannot participate in a mono connection (sink - LHS)") + def AnalogCantBeMonoSource = + MonoConnectException(": Analog cannot participate in a mono connection (source - RHS)") + def AnalogMonoConnectionException = + MonoConnectException(": Analog cannot participate in a mono connection (source and sink)") + // scalastyle:on method.name public.methods.have.type + + /** This function is what recursively tries to connect a sink and source together + * + * There is some cleverness in the use of internal try-catch to catch exceptions + * during the recursive decent and then rethrow them with extra information added. + * This gives the user a 'path' to where in the connections things went wrong. + */ + def connect( //scalastyle:off cyclomatic.complexity method.length + sourceInfo: SourceInfo, + connectCompileOptions: CompileOptions, + sink: Data, + source: Data, + context_mod: RawModule): Unit = + (sink, source) match { + + // Handle legal element cases, note (Bool, Bool) is caught by the first two, as Bool is a UInt + case (sink_e: Bool, source_e: UInt) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: UInt, source_e: Bool) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: UInt, source_e: UInt) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: SInt, source_e: SInt) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: FixedPoint, source_e: FixedPoint) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: Interval, source_e: Interval) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: Clock, source_e: Clock) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: AsyncReset, source_e: AsyncReset) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: ResetType, source_e: Reset) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: EnumType, source_e: UnsafeEnum) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: EnumType, source_e: EnumType) if sink_e.typeEquivalent(source_e) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: UnsafeEnum, source_e: UInt) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + + // Handle Vec case + case (sink_v: Vec[Data @unchecked], source_v: Vec[Data @unchecked]) => + if(sink_v.length != source_v.length) { throw MismatchedVecException } + for(idx <- 0 until sink_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, sink_v(idx), source_v(idx), context_mod) + } catch { + case MonoConnectException(message) => throw MonoConnectException(s"($idx)$message") + } + } + // Handle Vec connected to DontCare. Apply the DontCare to individual elements. + case (sink_v: Vec[Data @unchecked], DontCare) => + for(idx <- 0 until sink_v.length) { + try { + implicit val compileOptions = connectCompileOptions + connect(sourceInfo, connectCompileOptions, sink_v(idx), source, context_mod) + } catch { + case MonoConnectException(message) => throw MonoConnectException(s"($idx)$message") + } + } + + // Handle Record case + case (sink_r: Record, source_r: Record) => + // For each field, descend with right + for((field, sink_sub) <- sink_r.elements) { + try { + source_r.elements.get(field) match { + case Some(source_sub) => connect(sourceInfo, connectCompileOptions, sink_sub, source_sub, context_mod) + case None => { + if (connectCompileOptions.connectFieldsMustMatch) { + throw MissingFieldException(field) + } + } + } + } catch { + case MonoConnectException(message) => throw MonoConnectException(s".$field$message") + } + } + // Handle Record connected to DontCare. Apply the DontCare to individual elements. + case (sink_r: Record, DontCare) => + // For each field, descend with right + for((field, sink_sub) <- sink_r.elements) { + try { + connect(sourceInfo, connectCompileOptions, sink_sub, source, context_mod) + } catch { + case MonoConnectException(message) => throw MonoConnectException(s".$field$message") + } + } + + // Source is DontCare - it may be connected to anything. It generates a defInvalid for the sink. + case (sink, DontCare) => pushCommand(DefInvalid(sourceInfo, sink.lref)) + // DontCare as a sink is illegal. + case (DontCare, _) => throw DontCareCantBeSink + // Analog is illegal in mono connections. + case (_: Analog, _:Analog) => throw AnalogMonoConnectionException + // Analog is illegal in mono connections. + case (_: Analog, _) => throw AnalogCantBeMonoSink + // Analog is illegal in mono connections. + case (_, _: Analog) => throw AnalogCantBeMonoSource + // Sink and source are different subtypes of data so fail + case (sink, source) => throw MismatchedException(sink.toString, source.toString) + } + + // This function (finally) issues the connection operation + private def issueConnect(sink: Element, source: Element)(implicit sourceInfo: SourceInfo): Unit = { + // If the source is a DontCare, generate a DefInvalid for the sink, + // otherwise, issue a Connect. + source.topBinding match { + case b: DontCareBinding => pushCommand(DefInvalid(sourceInfo, sink.lref)) + case _ => pushCommand(Connect(sourceInfo, sink.lref, source.ref)) + } + } + + // This function checks if element-level connection operation allowed. + // Then it either issues it or throws the appropriate exception. + def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, sink: Element, source: Element, context_mod: RawModule): Unit = { // scalastyle:ignore line.size.limit + import BindingDirection.{Internal, Input, Output} // Using extensively so import these + // If source has no location, assume in context module + // This can occur if is a literal, unbound will error previously + val sink_mod: BaseModule = sink.topBinding.location.getOrElse(throw UnwritableSinkException) + val source_mod: BaseModule = source.topBinding.location.getOrElse(context_mod) + + val sink_direction = BindingDirection.from(sink.topBinding, sink.direction) + val source_direction = BindingDirection.from(source.topBinding, source.direction) + + // CASE: Context is same module that both left node and right node are in + if( (context_mod == sink_mod) && (context_mod == source_mod) ) { + ((sink_direction, source_direction): @unchecked) match { + // SINK SOURCE + // CURRENT MOD CURRENT MOD + case (Output, _) => issueConnect(sink, source) + case (Internal, _) => issueConnect(sink, source) + case (Input, _) => throw UnwritableSinkException + } + } + + // CASE: Context is same module as sink node and right node is in a child module + else if( (sink_mod == context_mod) && + (source_mod._parent.map(_ == context_mod).getOrElse(false)) ) { + // Thus, right node better be a port node and thus have a direction + ((sink_direction, source_direction): @unchecked) match { + // SINK SOURCE + // CURRENT MOD CHILD MOD + case (Internal, Output) => issueConnect(sink, source) + case (Internal, Input) => issueConnect(sink, source) + case (Output, Output) => issueConnect(sink, source) + case (Output, Input) => issueConnect(sink, source) + case (_, Internal) => { + if (!(connectCompileOptions.dontAssumeDirectionality)) { + issueConnect(sink, source) + } else { + throw UnreadableSourceException + } + } + case (Input, Output) if (!(connectCompileOptions.dontTryConnectionsSwapped)) => issueConnect(source, sink) // scalastyle:ignore line.size.limit + case (Input, _) => throw UnwritableSinkException + } + } + + // CASE: Context is same module as source node and sink node is in child module + else if( (source_mod == context_mod) && + (sink_mod._parent.map(_ == context_mod).getOrElse(false)) ) { + // Thus, left node better be a port node and thus have a direction + ((sink_direction, source_direction): @unchecked) match { + // SINK SOURCE + // CHILD MOD CURRENT MOD + case (Input, _) => issueConnect(sink, source) + case (Output, _) => throw UnwritableSinkException + case (Internal, _) => throw UnwritableSinkException + } + } + + // CASE: Context is the parent module of both the module containing sink node + // and the module containing source node + // Note: This includes case when sink and source in same module but in parent + else if( (sink_mod._parent.map(_ == context_mod).getOrElse(false)) && + (source_mod._parent.map(_ == context_mod).getOrElse(false)) + ) { + // Thus both nodes must be ports and have a direction + ((sink_direction, source_direction): @unchecked) match { + // SINK SOURCE + // CHILD MOD CHILD MOD + case (Input, Input) => issueConnect(sink, source) + case (Input, Output) => issueConnect(sink, source) + case (Output, _) => throw UnwritableSinkException + case (_, Internal) => { + if (!(connectCompileOptions.dontAssumeDirectionality)) { + issueConnect(sink, source) + } else { + throw UnreadableSourceException + } + } + case (Internal, _) => throw UnwritableSinkException + } + } + + // Not quite sure where left and right are compared to current module + // so just error out + else throw UnknownRelationException + } +} diff --git a/core/src/main/scala/chisel3/internal/Namer.scala b/core/src/main/scala/chisel3/internal/Namer.scala new file mode 100644 index 00000000..999971a4 --- /dev/null +++ b/core/src/main/scala/chisel3/internal/Namer.scala @@ -0,0 +1,154 @@ +// See LICENSE for license details. + +// This file contains part of the implementation of the naming static annotation system. + +package chisel3.internal.naming +import chisel3.experimental.NoChiselNamePrefix + +import scala.collection.mutable.Stack +import scala.collection.mutable.ListBuffer + +import scala.collection.JavaConversions._ + +import java.util.IdentityHashMap + +/** Recursive Function Namer overview + * + * In every function, creates a NamingContext object, which associates all vals with a string name + * suffix, for example: + * val myValName = SomeStatement() + * produces the entry in items: + * {ref of SomeStatement(), "myValName"} + * + * This is achieved with a macro transforming: + * val myValName = SomeStatement() + * statements into a naming call: + * val myValName = context.name(SomeStatement(), "myValName") + * + * The context is created from a global dynamic context stack at the beginning of each function. + * At the end of each function call, the completed context is added to its parent context and + * associated with the return value (whose name at an enclosing function call will form the prefix + * for all named objects). + * + * When the naming context prefix is given, it will name all of its items with the prefix and the + * associated suffix name. Then, it will check its descendants for sub-contexts with references + * matching the item reference, and if there is a match, it will (recursively) give the + * sub-context a prefix of its current prefix plus the item reference suffix. + * + * Note that for Modules, the macro will insert a naming context prefix call with an empty prefix, + * starting the recursive naming process. + */ + +/** Base class for naming contexts, providing the basic API consisting of naming calls and + * ability to take descendant naming contexts. + */ +sealed trait NamingContextInterface { + /** Suggest a name (that will be propagated to FIRRTL) for an object, then returns the object + * itself (so this can be inserted transparently anywhere). + * Is a no-op (so safe) when applied on objects that aren't named, including non-Chisel data + * types. + */ + def name[T](obj: T, name: String): T + + /** Gives this context a naming prefix (which may be empty, "", for a top-level Module context) + * so that actual naming calls (HasId.suggestName) can happen. + * Recursively names descendants, for those whose return value have an associated name. + */ + def namePrefix(prefix: String) +} + +/** Dummy implementation to allow for naming annotations in a non-Builder context. + */ +object DummyNamer extends NamingContextInterface { + def name[T](obj: T, name: String): T = obj + + def namePrefix(prefix: String): Unit = { + } +} + +/** Actual namer functionality. + */ +class NamingContext extends NamingContextInterface { + val descendants = new IdentityHashMap[AnyRef, ListBuffer[NamingContext]]() + val anonymousDescendants = ListBuffer[NamingContext]() + val items = ListBuffer[(AnyRef, String)]() + var closed = false // a sanity check to ensure no more name() calls are done after name_prefix + + /** Adds a NamingContext object as a descendant - where its contained objects will have names + * prefixed with the name given to the reference object, if the reference object is named in the + * scope of this context. + */ + def addDescendant(ref: Any, descendant: NamingContext) { + ref match { + case ref: AnyRef => + descendants.getOrElseUpdate(ref, ListBuffer[NamingContext]()) += descendant + case _ => anonymousDescendants += descendant + } + } + + def name[T](obj: T, name: String): T = { + assert(!closed, "Can't name elements after name_prefix called") + obj match { + case _: NoChiselNamePrefix => // Don't name things with NoChiselNamePrefix + case ref: AnyRef => items += ((ref, name)) + case _ => + } + obj + } + + def namePrefix(prefix: String): Unit = { + closed = true + for ((ref, suffix) <- items) { + // First name the top-level object + chisel3.internal.Builder.nameRecursively(prefix + suffix, ref, (id, name) => id.suggestName(name)) + + // Then recurse into descendant contexts + if (descendants.containsKey(ref)) { + for (descendant <- descendants.get(ref)) { + descendant.namePrefix(prefix + suffix + "_") + } + descendants.remove(ref) + } + } + + for (descendant <- descendants.values().flatten) { + // Where we have a broken naming link, just ignore the missing parts + descendant.namePrefix(prefix) + } + for (descendant <- anonymousDescendants) { + descendant.namePrefix(prefix) + } + } +} + +/** Class for the (global) naming stack object, which provides a way to push and pop naming + * contexts as functions are called / finished. + */ +class NamingStack { + val namingStack = Stack[NamingContext]() + + /** Creates a new naming context, where all items in the context will have their names prefixed + * with some yet-to-be-determined prefix from object names in an enclosing scope. + */ + def pushContext(): NamingContext = { + val context = new NamingContext + namingStack.push(context) + context + } + + /** Called at the end of a function, popping the current naming context, adding it to the + * enclosing context's descendants, and passing through the prefix naming reference. + * Every instance of push_context() must have a matching pop_context(). + * + * Will assert out if the context being popped isn't the topmost on the stack. + */ + def popContext[T <: Any](prefixRef: T, until: NamingContext): Unit = { + assert(namingStack.top == until) + namingStack.pop() + if (!namingStack.isEmpty) { + namingStack.top.addDescendant(prefixRef, until) + } + } + + def length() : Int = namingStack.length +} diff --git a/core/src/main/scala/chisel3/internal/SourceInfo.scala b/core/src/main/scala/chisel3/internal/SourceInfo.scala new file mode 100644 index 00000000..f1130db4 --- /dev/null +++ b/core/src/main/scala/chisel3/internal/SourceInfo.scala @@ -0,0 +1,61 @@ +// See LICENSE for license details. + +// This file contains macros for adding source locators at the point of invocation. +// +// This is not part of coreMacros to disallow this macro from being implicitly invoked in Chisel +// frontend (and generating source locators in Chisel core), which is almost certainly a bug. +// +// Note: While these functions and definitions are not private (macros can't be +// private), these are NOT meant to be part of the public API (yet) and no +// forward compatibility guarantees are made. +// A future revision may stabilize the source locator API to allow library +// writers to append source locator information at the point of a library +// function invocation. + +package chisel3.internal.sourceinfo + +import scala.language.experimental.macros +import scala.reflect.macros.blackbox.Context + +/** Abstract base class for generalized source information. + */ +sealed trait SourceInfo { + /** A prettier toString + * + * Make a useful message if SourceInfo is available, nothing otherwise + */ + def makeMessage(f: String => String): String +} + +sealed trait NoSourceInfo extends SourceInfo { + def makeMessage(f: String => String): String = "" +} + +/** For when source info can't be generated because of a technical limitation, like for Reg because + * Scala macros don't support named or default arguments. + */ +case object UnlocatableSourceInfo extends NoSourceInfo + +/** For when source info isn't generated because the function is deprecated and we're lazy. + */ +case object DeprecatedSourceInfo extends NoSourceInfo + +/** For FIRRTL lines from a Scala source line. + */ +case class SourceLine(filename: String, line: Int, col: Int) extends SourceInfo { + def makeMessage(f: String => String): String = f(s"@[$filename $line:$col]") +} + +/** Provides a macro that returns the source information at the invocation point. + */ +object SourceInfoMacro { + def generate_source_info(c: Context): c.Tree = { + import c.universe._ + val p = c.enclosingPosition + q"_root_.chisel3.internal.sourceinfo.SourceLine(${p.source.file.name}, ${p.line}, ${p.column})" + } +} + +object SourceInfo { + implicit def materialize: SourceInfo = macro SourceInfoMacro.generate_source_info +} diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala new file mode 100644 index 00000000..5c1d6935 --- /dev/null +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -0,0 +1,275 @@ +// See LICENSE for license details. + +package chisel3.internal.firrtl +import chisel3._ +import chisel3.experimental._ +import chisel3.internal.sourceinfo.{NoSourceInfo, SourceLine, SourceInfo} +import firrtl.{ir => fir} +import chisel3.internal.{castToInt, throwException} + +import scala.annotation.tailrec +import scala.collection.immutable.Queue + +private[chisel3] object Converter { + // TODO modeled on unpack method on Printable, refactor? + def unpack(pable: Printable, ctx: Component): (String, Seq[Arg]) = pable match { + case Printables(pables) => + val (fmts, args) = pables.map(p => unpack(p, ctx)).unzip + (fmts.mkString, args.flatten.toSeq) + case PString(str) => (str.replaceAll("%", "%%"), List.empty) + case format: FirrtlFormat => + ("%" + format.specifier, List(format.bits.ref)) + case Name(data) => (data.ref.name, List.empty) + case FullName(data) => (data.ref.fullName(ctx), List.empty) + case Percent => ("%%", List.empty) + } + + def convert(info: SourceInfo): fir.Info = info match { + case _: NoSourceInfo => fir.NoInfo + case SourceLine(fn, line, col) => fir.FileInfo(fir.StringLit(s"$fn $line:$col")) + } + + def convert(op: PrimOp): fir.PrimOp = firrtl.PrimOps.fromString(op.name) + + def convert(dir: MemPortDirection): firrtl.MPortDir = dir match { + case MemPortDirection.INFER => firrtl.MInfer + case MemPortDirection.READ => firrtl.MRead + case MemPortDirection.WRITE => firrtl.MWrite + case MemPortDirection.RDWR => firrtl.MReadWrite + } + + // TODO + // * Memoize? + // * Move into the Chisel IR? + def convert(arg: Arg, ctx: Component): fir.Expression = arg match { // scalastyle:ignore cyclomatic.complexity + case Node(id) => + convert(id.getRef, ctx) + case Ref(name) => + fir.Reference(name, fir.UnknownType) + case Slot(imm, name) => + fir.SubField(convert(imm, ctx), name, fir.UnknownType) + case Index(imm, ILit(idx)) => + fir.SubIndex(convert(imm, ctx), castToInt(idx, "Index"), fir.UnknownType) + case Index(imm, value) => + fir.SubAccess(convert(imm, ctx), convert(value, ctx), fir.UnknownType) + case ModuleIO(mod, name) => + // scalastyle:off if.brace + if (mod eq ctx.id) fir.Reference(name, fir.UnknownType) + else fir.SubField(fir.Reference(mod.getRef.name, fir.UnknownType), name, fir.UnknownType) + // scalastyle:on if.brace + case u @ ULit(n, UnknownWidth()) => + fir.UIntLiteral(n, fir.IntWidth(u.minWidth)) + case ULit(n, w) => + fir.UIntLiteral(n, convert(w)) + case slit @ SLit(n, w) => fir.SIntLiteral(n, convert(w)) + val unsigned = if (n < 0) (BigInt(1) << slit.width.get) + n else n + val uint = convert(ULit(unsigned, slit.width), ctx) + fir.DoPrim(firrtl.PrimOps.AsSInt, Seq(uint), Seq.empty, fir.UnknownType) + // TODO Simplify + case fplit @ FPLit(n, w, bp) => + val unsigned = if (n < 0) (BigInt(1) << fplit.width.get) + n else n + val uint = convert(ULit(unsigned, fplit.width), ctx) + val lit = bp.asInstanceOf[KnownBinaryPoint].value + fir.DoPrim(firrtl.PrimOps.AsFixedPoint, Seq(uint), Seq(lit), fir.UnknownType) + case intervalLit @ IntervalLit(n, w, bp) => + val unsigned = if (n < 0) (BigInt(1) << intervalLit.width.get) + n else n + val uint = convert(ULit(unsigned, intervalLit.width), ctx) + val lit = bp.asInstanceOf[KnownBinaryPoint].value + fir.DoPrim(firrtl.PrimOps.AsInterval, Seq(uint), Seq(n, n, lit), fir.UnknownType) + case lit: ILit => + throwException(s"Internal Error! Unexpected ILit: $lit") + } + + /** Convert Commands that map 1:1 to Statements */ + def convertSimpleCommand(cmd: Command, ctx: Component): Option[fir.Statement] = cmd match { // scalastyle:ignore cyclomatic.complexity line.size.limit + case e: DefPrim[_] => + val consts = e.args.collect { case ILit(i) => i } + val args = e.args.flatMap { + case _: ILit => None + case other => Some(convert(other, ctx)) + } + val expr = e.op.name match { + case "mux" => + assert(args.size == 3, s"Mux with unexpected args: $args") + fir.Mux(args(0), args(1), args(2), fir.UnknownType) + case _ => + fir.DoPrim(convert(e.op), args, consts, fir.UnknownType) + } + Some(fir.DefNode(convert(e.sourceInfo), e.name, expr)) + case e @ DefWire(info, id) => + Some(fir.DefWire(convert(info), e.name, extractType(id))) + case e @ DefReg(info, id, clock) => + Some(fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx), + firrtl.Utils.zero, convert(id.getRef, ctx))) + case e @ DefRegInit(info, id, clock, reset, init) => + Some(fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx), + convert(reset, ctx), convert(init, ctx))) + case e @ DefMemory(info, id, t, size) => + Some(firrtl.CDefMemory(convert(info), e.name, extractType(t), size, false)) + case e @ DefSeqMemory(info, id, t, size, ruw) => + Some(firrtl.CDefMemory(convert(info), e.name, extractType(t), size, true, ruw)) + case e: DefMemPort[_] => + Some(firrtl.CDefMPort(convert(e.sourceInfo), e.name, fir.UnknownType, + e.source.fullName(ctx), Seq(convert(e.index, ctx), convert(e.clock, ctx)), convert(e.dir))) + case Connect(info, loc, exp) => + Some(fir.Connect(convert(info), convert(loc, ctx), convert(exp, ctx))) + case BulkConnect(info, loc, exp) => + Some(fir.PartialConnect(convert(info), convert(loc, ctx), convert(exp, ctx))) + case Attach(info, locs) => + Some(fir.Attach(convert(info), locs.map(l => convert(l, ctx)))) + case DefInvalid(info, arg) => + Some(fir.IsInvalid(convert(info), convert(arg, ctx))) + case e @ DefInstance(info, id, _) => + Some(fir.DefInstance(convert(info), e.name, id.name)) + case Stop(info, clock, ret) => + Some(fir.Stop(convert(info), ret, convert(clock, ctx), firrtl.Utils.one)) + case Printf(info, clock, pable) => + val (fmt, args) = unpack(pable, ctx) + Some(fir.Print(convert(info), fir.StringLit(fmt), + args.map(a => convert(a, ctx)), convert(clock, ctx), firrtl.Utils.one)) + case _ => None + } + + /** Internal datastructure to help translate Chisel's flat Command structure to FIRRTL's AST + * + * In particular, when scoping is translated from flat with begin end to a nested datastructure + * + * @param when Current when Statement, holds info, condition, and consequence as they are + * available + * @param outer Already converted Statements that precede the current when block in the scope in + * which the when is defined (ie. 1 level up from the scope inside the when) + * @param alt Indicates if currently processing commands in the alternate (else) of the when scope + */ + // TODO we should probably have a different structure in the IR to close elses + private case class WhenFrame(when: fir.Conditionally, outer: Queue[fir.Statement], alt: Boolean) + + /** Convert Chisel IR Commands into FIRRTL Statements + * + * @note ctx is needed because references to ports translate differently when referenced within + * the module in which they are defined vs. parent modules + * @param cmds Chisel IR Commands to convert + * @param ctx Component (Module) context within which we are translating + * @return FIRRTL Statement that is equivalent to the input cmds + */ + def convert(cmds: Seq[Command], ctx: Component): fir.Statement = { // scalastyle:ignore cyclomatic.complexity + @tailrec + // scalastyle:off if.brace + def rec(acc: Queue[fir.Statement], + scope: List[WhenFrame]) + (cmds: Seq[Command]): Seq[fir.Statement] = { + if (cmds.isEmpty) { + assert(scope.isEmpty) + acc + } else convertSimpleCommand(cmds.head, ctx) match { + // Most Commands map 1:1 + case Some(stmt) => + rec(acc :+ stmt, scope)(cmds.tail) + // When scoping logic does not map 1:1 and requires pushing/popping WhenFrames + // Please see WhenFrame for more details + case None => cmds.head match { + case WhenBegin(info, pred) => + val when = fir.Conditionally(convert(info), convert(pred, ctx), fir.EmptyStmt, fir.EmptyStmt) + val frame = WhenFrame(when, acc, false) + rec(Queue.empty, frame +: scope)(cmds.tail) + case WhenEnd(info, depth, _) => + val frame = scope.head + val when = if (frame.alt) frame.when.copy(alt = fir.Block(acc)) + else frame.when.copy(conseq = fir.Block(acc)) + // Check if this when has an else + cmds.tail.headOption match { + case Some(AltBegin(_)) => + assert(!frame.alt, "Internal Error! Unexpected when structure!") // Only 1 else per when + rec(Queue.empty, frame.copy(when = when, alt = true) +: scope.tail)(cmds.drop(2)) + case _ => // Not followed by otherwise + // If depth > 0 then we need to close multiple When scopes so we add a new WhenEnd + // If we're nested we need to add more WhenEnds to ensure each When scope gets + // properly closed + val cmdsx = if (depth > 0) WhenEnd(info, depth - 1, false) +: cmds.tail else cmds.tail + rec(frame.outer :+ when, scope.tail)(cmdsx) + } + case OtherwiseEnd(info, depth) => + val frame = scope.head + val when = frame.when.copy(alt = fir.Block(acc)) + // TODO For some reason depth == 1 indicates the last closing otherwise whereas + // depth == 0 indicates last closing when + val cmdsx = if (depth > 1) OtherwiseEnd(info, depth - 1) +: cmds.tail else cmds.tail + rec(scope.head.outer :+ when, scope.tail)(cmdsx) + } + } + } + // scalastyle:on if.brace + fir.Block(rec(Queue.empty, List.empty)(cmds)) + } + + def convert(width: Width): fir.Width = width match { + case UnknownWidth() => fir.UnknownWidth + case KnownWidth(value) => fir.IntWidth(value) + } + + def convert(bp: BinaryPoint): fir.Width = bp match { + case UnknownBinaryPoint => fir.UnknownWidth + case KnownBinaryPoint(value) => fir.IntWidth(value) + } + + private def firrtlUserDirOf(d: Data): SpecifiedDirection = d match { + case d: Vec[_] => + SpecifiedDirection.fromParent(d.specifiedDirection, firrtlUserDirOf(d.sample_element)) + case d => d.specifiedDirection + } + + def extractType(data: Data, clearDir: Boolean = false): fir.Type = data match { // scalastyle:ignore cyclomatic.complexity line.size.limit + case _: Clock => fir.ClockType + case _: AsyncReset => fir.AsyncResetType + case _: ResetType => fir.ResetType + case d: EnumType => fir.UIntType(convert(d.width)) + case d: UInt => fir.UIntType(convert(d.width)) + case d: SInt => fir.SIntType(convert(d.width)) + case d: FixedPoint => fir.FixedType(convert(d.width), convert(d.binaryPoint)) + case d: Interval => fir.IntervalType(d.range.lowerBound, d.range.upperBound, d.range.firrtlBinaryPoint) + case d: Analog => fir.AnalogType(convert(d.width)) + case d: Vec[_] => fir.VectorType(extractType(d.sample_element, clearDir), d.length) + case d: Record => + val childClearDir = clearDir || + d.specifiedDirection == SpecifiedDirection.Input || d.specifiedDirection == SpecifiedDirection.Output + def eltField(elt: Data): fir.Field = (childClearDir, firrtlUserDirOf(elt)) match { + case (true, _) => fir.Field(elt.getRef.name, fir.Default, extractType(elt, true)) + case (false, SpecifiedDirection.Unspecified | SpecifiedDirection.Output) => + fir.Field(elt.getRef.name, fir.Default, extractType(elt, false)) + case (false, SpecifiedDirection.Flip | SpecifiedDirection.Input) => + fir.Field(elt.getRef.name, fir.Flip, extractType(elt, false)) + } + fir.BundleType(d.elements.toIndexedSeq.reverse.map { case (_, e) => eltField(e) }) + } + + def convert(name: String, param: Param): fir.Param = param match { + case IntParam(value) => fir.IntParam(name, value) + case DoubleParam(value) => fir.DoubleParam(name, value) + case StringParam(value) => fir.StringParam(name, fir.StringLit(value)) + case RawParam(value) => fir.RawStringParam(name, value) + } + def convert(port: Port, topDir: SpecifiedDirection = SpecifiedDirection.Unspecified): fir.Port = { + val resolvedDir = SpecifiedDirection.fromParent(topDir, port.dir) + val dir = resolvedDir match { + case SpecifiedDirection.Unspecified | SpecifiedDirection.Output => fir.Output + case SpecifiedDirection.Flip | SpecifiedDirection.Input => fir.Input + } + val clearDir = resolvedDir match { + case SpecifiedDirection.Input | SpecifiedDirection.Output => true + case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => false + } + val tpe = extractType(port.id, clearDir) + fir.Port(fir.NoInfo, port.id.getRef.name, dir, tpe) + } + + def convert(component: Component): fir.DefModule = component match { + case ctx @ DefModule(_, name, ports, cmds) => + fir.Module(fir.NoInfo, name, ports.map(p => convert(p)), convert(cmds.toList, ctx)) + case ctx @ DefBlackBox(id, name, ports, topDir, params) => + fir.ExtModule(fir.NoInfo, name, ports.map(p => convert(p, topDir)), id.desiredName, + params.map { case (name, p) => convert(name, p) }.toSeq) + } + + def convert(circuit: Circuit): fir.Circuit = + fir.Circuit(fir.NoInfo, circuit.components.map(convert), circuit.name) +} + diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala new file mode 100644 index 00000000..d98bebcd --- /dev/null +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -0,0 +1,750 @@ +// See LICENSE for license details. + +package chisel3.internal.firrtl + +import firrtl.{ir => fir} + +import chisel3._ +import chisel3.internal._ +import chisel3.internal.sourceinfo.SourceInfo +import chisel3.experimental._ +import _root_.firrtl.{ir => firrtlir} +import _root_.firrtl.PrimOps + +import scala.collection.immutable.NumericRange +import scala.math.BigDecimal.RoundingMode + +// scalastyle:off number.of.types + +case class PrimOp(name: String) { + override def toString: String = name +} + +object PrimOp { + val AddOp = PrimOp("add") + val SubOp = PrimOp("sub") + val TailOp = PrimOp("tail") + val HeadOp = PrimOp("head") + val TimesOp = PrimOp("mul") + val DivideOp = PrimOp("div") + val RemOp = PrimOp("rem") + val ShiftLeftOp = PrimOp("shl") + val ShiftRightOp = PrimOp("shr") + val DynamicShiftLeftOp = PrimOp("dshl") + val DynamicShiftRightOp = PrimOp("dshr") + val BitAndOp = PrimOp("and") + val BitOrOp = PrimOp("or") + val BitXorOp = PrimOp("xor") + val BitNotOp = PrimOp("not") + val ConcatOp = PrimOp("cat") + val BitsExtractOp = PrimOp("bits") + val LessOp = PrimOp("lt") + val LessEqOp = PrimOp("leq") + val GreaterOp = PrimOp("gt") + val GreaterEqOp = PrimOp("geq") + val EqualOp = PrimOp("eq") + val PadOp = PrimOp("pad") + val NotEqualOp = PrimOp("neq") + val NegOp = PrimOp("neg") + val MultiplexOp = PrimOp("mux") + val AndReduceOp = PrimOp("andr") + val OrReduceOp = PrimOp("orr") + val XorReduceOp = PrimOp("xorr") + val ConvertOp = PrimOp("cvt") + val AsUIntOp = PrimOp("asUInt") + val AsSIntOp = PrimOp("asSInt") + val AsFixedPointOp = PrimOp("asFixedPoint") + val AsIntervalOp = PrimOp("asInterval") + val WrapOp = PrimOp("wrap") + val SqueezeOp = PrimOp("squz") + val ClipOp = PrimOp("clip") + val SetBinaryPoint = PrimOp("setp") + val IncreasePrecision = PrimOp("incp") + val DecreasePrecision = PrimOp("decp") + val AsClockOp = PrimOp("asClock") + val AsAsyncResetOp = PrimOp("asAsyncReset") +} + +abstract class Arg { + def fullName(ctx: Component): String = name + def name: String +} + +case class Node(id: HasId) extends Arg { + override def fullName(ctx: Component): String = id.getOptionRef match { + case Some(arg) => arg.fullName(ctx) + case None => id.suggestedName.getOrElse("??") + } + def name: String = id.getOptionRef match { + case Some(arg) => arg.name + case None => id.suggestedName.getOrElse("??") + } +} + +abstract class LitArg(val num: BigInt, widthArg: Width) extends Arg { + private[chisel3] def forcedWidth = widthArg.known + private[chisel3] def width: Width = if (forcedWidth) widthArg else Width(minWidth) + override def fullName(ctx: Component): String = name + // Ensure the node representing this LitArg has a ref to it and a literal binding. + def bindLitArg[T <: Element](elem: T): T = { + elem.bind(ElementLitBinding(this)) + elem.setRef(this) + elem + } + + protected def minWidth: Int + if (forcedWidth) { + require(widthArg.get >= minWidth, + s"The literal value ${num} was elaborated with a specified width of ${widthArg.get} bits, but at least ${minWidth} bits are required.") // scalastyle:ignore line.size.limit + } +} + +case class ILit(n: BigInt) extends Arg { + def name: String = n.toString +} + +case class ULit(n: BigInt, w: Width) extends LitArg(n, w) { + def name: String = "UInt" + width + "(\"h0" + num.toString(16) + "\")" + def minWidth: Int = 1 max n.bitLength + + require(n >= 0, s"UInt literal ${n} is negative") +} + +case class SLit(n: BigInt, w: Width) extends LitArg(n, w) { + def name: String = { + val unsigned = if (n < 0) (BigInt(1) << width.get) + n else n + s"asSInt(${ULit(unsigned, width).name})" + } + def minWidth: Int = 1 + n.bitLength +} + +case class FPLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends LitArg(n, w) { + def name: String = { + val unsigned = if (n < 0) (BigInt(1) << width.get) + n else n + s"asFixedPoint(${ULit(unsigned, width).name}, ${binaryPoint.asInstanceOf[KnownBinaryPoint].value})" + } + def minWidth: Int = 1 + n.bitLength +} + +case class IntervalLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends LitArg(n, w) { + def name: String = { + val unsigned = if (n < 0) (BigInt(1) << width.get) + n else n + s"asInterval(${ULit(unsigned, width).name}, ${n}, ${n}, ${binaryPoint.asInstanceOf[KnownBinaryPoint].value})" + } + val range: IntervalRange = { + new IntervalRange(IntervalRange.getBound(isClosed = true, BigDecimal(n)), + IntervalRange.getBound(isClosed = true, BigDecimal(n)), IntervalRange.getRangeWidth(binaryPoint)) + } + def minWidth: Int = 1 + n.bitLength +} + +case class Ref(name: String) extends Arg +case class ModuleIO(mod: BaseModule, name: String) extends Arg { + override def fullName(ctx: Component): String = + if (mod eq ctx.id) name else s"${mod.getRef.name}.$name" +} +case class Slot(imm: Node, name: String) extends Arg { + override def fullName(ctx: Component): String = + if (imm.fullName(ctx).isEmpty) name else s"${imm.fullName(ctx)}.${name}" +} +case class Index(imm: Arg, value: Arg) extends Arg { + def name: String = s"[$value]" + override def fullName(ctx: Component): String = s"${imm.fullName(ctx)}[${value.fullName(ctx)}]" +} + +object Width { + def apply(x: Int): Width = KnownWidth(x) + def apply(): Width = UnknownWidth() +} + +sealed abstract class Width { + type W = Int + def max(that: Width): Width = this.op(that, _ max _) + def + (that: Width): Width = this.op(that, _ + _) + def + (that: Int): Width = this.op(this, (a, b) => a + that) + def shiftRight(that: Int): Width = this.op(this, (a, b) => 0 max (a - that)) + def dynamicShiftLeft(that: Width): Width = + this.op(that, (a, b) => a + (1 << b) - 1) + + def known: Boolean + def get: W + protected def op(that: Width, f: (W, W) => W): Width +} + +sealed case class UnknownWidth() extends Width { + def known: Boolean = false + def get: Int = None.get + def op(that: Width, f: (W, W) => W): Width = this + override def toString: String = "" +} + +sealed case class KnownWidth(value: Int) extends Width { + require(value >= 0) + def known: Boolean = true + def get: Int = value + def op(that: Width, f: (W, W) => W): Width = that match { + case KnownWidth(x) => KnownWidth(f(value, x)) + case _ => that + } + override def toString: String = s"<${value.toString}>" +} + +object BinaryPoint { + def apply(x: Int): BinaryPoint = KnownBinaryPoint(x) + def apply(): BinaryPoint = UnknownBinaryPoint +} + +sealed abstract class BinaryPoint { + type W = Int + def max(that: BinaryPoint): BinaryPoint = this.op(that, _ max _) + def + (that: BinaryPoint): BinaryPoint = this.op(that, _ + _) + def + (that: Int): BinaryPoint = this.op(this, (a, b) => a + that) + def shiftRight(that: Int): BinaryPoint = this.op(this, (a, b) => 0 max (a - that)) + def dynamicShiftLeft(that: BinaryPoint): BinaryPoint = + this.op(that, (a, b) => a + (1 << b) - 1) + + def known: Boolean + def get: W + protected def op(that: BinaryPoint, f: (W, W) => W): BinaryPoint +} + +case object UnknownBinaryPoint extends BinaryPoint { + def known: Boolean = false + def get: Int = None.get + def op(that: BinaryPoint, f: (W, W) => W): BinaryPoint = this + override def toString: String = "" +} + +sealed case class KnownBinaryPoint(value: Int) extends BinaryPoint { + def known: Boolean = true + def get: Int = value + def op(that: BinaryPoint, f: (W, W) => W): BinaryPoint = that match { + case KnownBinaryPoint(x) => KnownBinaryPoint(f(value, x)) + case _ => that + } + override def toString: String = s"<<${value.toString}>>" +} + + +sealed abstract class MemPortDirection(name: String) { + override def toString: String = name +} +object MemPortDirection { + object READ extends MemPortDirection("read") + object WRITE extends MemPortDirection("write") + object RDWR extends MemPortDirection("rdwr") + object INFER extends MemPortDirection("infer") +} + +sealed trait RangeType { + def getWidth: Width + + def * (that: IntervalRange): IntervalRange + def +& (that: IntervalRange): IntervalRange + def -& (that: IntervalRange): IntervalRange + def << (that: Int): IntervalRange + def >> (that: Int): IntervalRange + def << (that: KnownWidth): IntervalRange + def >> (that: KnownWidth): IntervalRange + def merge(that: IntervalRange): IntervalRange +} + +object IntervalRange { + /** Creates an IntervalRange, this is used primarily by the range interpolator macro + * @param lower lower bound + * @param upper upper bound + * @param firrtlBinaryPoint binary point firrtl style + * @return + */ + def apply(lower: firrtlir.Bound, upper: firrtlir.Bound, firrtlBinaryPoint: firrtlir.Width): IntervalRange = { + new IntervalRange(lower, upper, firrtlBinaryPoint) + } + + def apply(lower: firrtlir.Bound, upper: firrtlir.Bound, binaryPoint: BinaryPoint): IntervalRange = { + new IntervalRange(lower, upper, IntervalRange.getBinaryPoint(binaryPoint)) + } + + def apply(lower: firrtlir.Bound, upper: firrtlir.Bound, binaryPoint: Int): IntervalRange = { + IntervalRange(lower, upper, BinaryPoint(binaryPoint)) + } + + /** Returns an IntervalRange appropriate for a signed value of the given width + * @param binaryPoint number of bits of mantissa + * @return + */ + def apply(binaryPoint: BinaryPoint): IntervalRange = { + IntervalRange(firrtlir.UnknownBound, firrtlir.UnknownBound, binaryPoint) + } + + /** Returns an IntervalRange appropriate for a signed value of the given width + * @param width number of bits to have in the interval + * @param binaryPoint number of bits of mantissa + * @return + */ + def apply(width: Width, binaryPoint: BinaryPoint = 0.BP): IntervalRange = { + val range = width match { + case KnownWidth(w) => + val nearestPowerOf2 = BigInt("1" + ("0" * (w - 1)), 2) + IntervalRange( + firrtlir.Closed(BigDecimal(-nearestPowerOf2)), firrtlir.Closed(BigDecimal(nearestPowerOf2 - 1)), binaryPoint + ) + case _ => + IntervalRange(firrtlir.UnknownBound, firrtlir.UnknownBound, binaryPoint) + } + range + } + + def unapply(arg: IntervalRange): Option[(firrtlir.Bound, firrtlir.Bound, BinaryPoint)] = { + return Some((arg.lower, arg.upper, arg.binaryPoint)) + } + + def getBound(isClosed: Boolean, value: String): firrtlir.Bound = { + if(value == "?") { + firrtlir.UnknownBound + } + else if(isClosed) { + firrtlir.Closed(BigDecimal(value)) + } + else { + firrtlir.Open(BigDecimal(value)) + } + } + + def getBound(isClosed: Boolean, value: BigDecimal): firrtlir.Bound = { + if(isClosed) { + firrtlir.Closed(value) + } + else { + firrtlir.Open(value) + } + } + + def getBound(isClosed: Boolean, value: Int): firrtlir.Bound = { + getBound(isClosed, (BigDecimal(value))) + } + + def getBinaryPoint(s: String): firrtlir.Width = { + firrtlir.UnknownWidth + } + + def getBinaryPoint(n: Int): firrtlir.Width = { + if(n < 0) { + firrtlir.UnknownWidth + } + else { + firrtlir.IntWidth(n) + } + } + def getBinaryPoint(n: BinaryPoint): firrtlir.Width = { + n match { + case UnknownBinaryPoint => firrtlir.UnknownWidth + case KnownBinaryPoint(w) => firrtlir.IntWidth(w) + } + } + + def getRangeWidth(w: Width): firrtlir.Width = { + if(w.known) { + firrtlir.IntWidth(w.get) + } + else { + firrtlir.UnknownWidth + } + } + def getRangeWidth(binaryPoint: BinaryPoint): firrtlir.Width = { + if(binaryPoint.known) { + firrtlir.IntWidth(binaryPoint.get) + } + else { + firrtlir.UnknownWidth + } + } + + //scalastyle:off method.name + def Unknown: IntervalRange = range"[?,?].?" +} + + +sealed class IntervalRange( + val lowerBound: firrtlir.Bound, + val upperBound: firrtlir.Bound, + private[chisel3] val firrtlBinaryPoint: firrtlir.Width) + extends firrtlir.IntervalType(lowerBound, upperBound, firrtlBinaryPoint) + with RangeType { + + (lowerBound, upperBound) match { + case (firrtlir.Open(begin), firrtlir.Open(end)) => + if(begin >= end) throw new ChiselException(s"Invalid range with ${serialize}") + binaryPoint match { + case KnownBinaryPoint(bp) => + if(begin >= end - (BigDecimal(1) / BigDecimal(BigInt(1) << bp))) { + throw new ChiselException(s"Invalid range with ${serialize}") + } + case _ => + } + case (firrtlir.Open(begin), firrtlir.Closed(end)) => + if(begin >= end) throw new ChiselException(s"Invalid range with ${serialize}") + case (firrtlir.Closed(begin), firrtlir.Open(end)) => + if(begin >= end) throw new ChiselException(s"Invalid range with ${serialize}") + case (firrtlir.Closed(begin), firrtlir.Closed(end)) => + if(begin > end) throw new ChiselException(s"Invalid range with ${serialize}") + case _ => + } + + //scalastyle:off cyclomatic.complexity + override def toString: String = { + val binaryPoint = firrtlBinaryPoint match { + case firrtlir.IntWidth(n) => s"$n" + case _ => "?" + } + val lowerBoundString = lowerBound match { + case firrtlir.Closed(l) => s"[$l" + case firrtlir.Open(l) => s"($l" + case firrtlir.UnknownBound => s"[?" + } + val upperBoundString = upperBound match { + case firrtlir.Closed(l) => s"$l]" + case firrtlir.Open(l) => s"$l)" + case firrtlir.UnknownBound => s"?]" + } + s"""range"$lowerBoundString,$upperBoundString.$binaryPoint"""" + } + + val increment: Option[BigDecimal] = firrtlBinaryPoint match { + case firrtlir.IntWidth(bp) => + Some(BigDecimal(math.pow(2, -bp.doubleValue))) + case _ => None + } + + /** If possible returns the lowest possible value for this Interval + * @return + */ + val getLowestPossibleValue: Option[BigDecimal] = { + increment match { + case Some(inc) => + lower match { + case firrtlir.Closed(n) => Some(n) + case firrtlir.Open(n) => Some(n + inc) + case _ => None + } + case _ => + None + } + } + + /** If possible returns the highest possible value for this Interval + * @return + */ + val getHighestPossibleValue: Option[BigDecimal] = { + increment match { + case Some(inc) => + upper match { + case firrtlir.Closed(n) => Some(n) + case firrtlir.Open(n) => Some(n - inc) + case _ => None + } + case _ => + None + } + } + + /** Return a Seq of the possible values for this range + * Mostly to be used for testing + * @return + */ + def getPossibleValues: NumericRange[BigDecimal] = { + (getLowestPossibleValue, getHighestPossibleValue, increment) match { + case (Some(low), Some(high), Some(inc)) => (low to high by inc) + case (_, _, None) => + throw new ChiselException(s"BinaryPoint unknown. Cannot get possible values from IntervalRange $toString") + case _ => + throw new ChiselException(s"Unknown Bound. Cannot get possible values from IntervalRange $toString") + + } + } + + override def getWidth: Width = { + width match { + case firrtlir.IntWidth(n) => KnownWidth(n.toInt) + case firrtlir.UnknownWidth => UnknownWidth() + } + } + + private def doFirrtlOp(op: firrtlir.PrimOp, that: IntervalRange): IntervalRange = { + PrimOps.set_primop_type( + firrtlir.DoPrim(op, + Seq(firrtlir.Reference("a", this), firrtlir.Reference("b", that)), Nil,firrtlir.UnknownType) + ).tpe match { + case i: firrtlir.IntervalType => IntervalRange(i.lower, i.upper, i.point) + case other => sys.error("BAD!") + } + } + + private def doFirrtlDynamicShift(that: UInt, isLeft: Boolean): IntervalRange = { + val uinttpe = that.widthOption match { + case None => firrtlir.UIntType(firrtlir.UnknownWidth) + case Some(w) => firrtlir.UIntType(firrtlir.IntWidth(w)) + } + val op = if(isLeft) PrimOps.Dshl else PrimOps.Dshr + PrimOps.set_primop_type( + firrtlir.DoPrim(op, + Seq(firrtlir.Reference("a", this), firrtlir.Reference("b", uinttpe)), Nil,firrtlir.UnknownType) + ).tpe match { + case i: firrtlir.IntervalType => IntervalRange(i.lower, i.upper, i.point) + case other => sys.error("BAD!") + } + } + + private def doFirrtlOp(op: firrtlir.PrimOp, that: Int): IntervalRange = { + PrimOps.set_primop_type( + firrtlir.DoPrim(op, + Seq(firrtlir.Reference("a", this)), Seq(BigInt(that)), firrtlir.UnknownType) + ).tpe match { + case i: firrtlir.IntervalType => IntervalRange(i.lower, i.upper, i.point) + case other => sys.error("BAD!") + } + } + + /** Multiply this by that, here we return a fully unknown range, + * firrtl's range inference can figure this out + * @param that + * @return + */ + override def *(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Mul, that) + } + + /** Add that to this, here we return a fully unknown range, + * firrtl's range inference can figure this out + * @param that + * @return + */ + override def +&(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Add, that) + } + + /** Subtract that from this, here we return a fully unknown range, + * firrtl's range inference can figure this out + * @param that + * @return + */ + override def -&(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Sub, that) + } + + private def adjustBoundValue(value: BigDecimal, binaryPointValue: Int): BigDecimal = { + if(binaryPointValue >= 0) { + val maskFactor = BigDecimal(1 << binaryPointValue) + val a = (value * maskFactor) + val b = a.setScale(0, RoundingMode.DOWN) + val c = b / maskFactor + c + } else { + value + } + } + + private def adjustBound(bound: firrtlir.Bound, binaryPoint: BinaryPoint): firrtlir.Bound = { + binaryPoint match { + case KnownBinaryPoint(binaryPointValue) => + bound match { + case firrtlir.Open(value) => firrtlir.Open(adjustBoundValue(value, binaryPointValue)) + case firrtlir.Closed(value) => firrtlir.Closed(adjustBoundValue(value, binaryPointValue)) + case _ => bound + } + case _ => firrtlir.UnknownBound + } + } + + /** Creates a new range with the increased precision + * + * @param newBinaryPoint + * @return + */ + def incPrecision(newBinaryPoint: BinaryPoint): IntervalRange = { + newBinaryPoint match { + case KnownBinaryPoint(that) => + doFirrtlOp(PrimOps.IncP, that) + case _ => + throwException(s"$this.incPrecision(newBinaryPoint = $newBinaryPoint) error, newBinaryPoint must be know") + } + } + + /** Creates a new range with the decreased precision + * + * @param newBinaryPoint + * @return + */ + def decPrecision(newBinaryPoint: BinaryPoint): IntervalRange = { + newBinaryPoint match { + case KnownBinaryPoint(that) => + doFirrtlOp(PrimOps.DecP, that) + case _ => + throwException(s"$this.decPrecision(newBinaryPoint = $newBinaryPoint) error, newBinaryPoint must be know") + } + } + + /** Creates a new range with the given binary point, adjusting precision + * on bounds as necessary + * + * @param newBinaryPoint + * @return + */ + def setPrecision(newBinaryPoint: BinaryPoint): IntervalRange = { + newBinaryPoint match { + case KnownBinaryPoint(that) => + doFirrtlOp(PrimOps.SetP, that) + case _ => + throwException(s"$this.setPrecision(newBinaryPoint = $newBinaryPoint) error, newBinaryPoint must be know") + } + } + + /** Shift this range left, i.e. shifts the min and max by the specified amount + * @param that + * @return + */ + override def <<(that: Int): IntervalRange = { + doFirrtlOp(PrimOps.Shl, that) + } + + /** Shift this range left, i.e. shifts the min and max by the known width + * @param that + * @return + */ + override def <<(that: KnownWidth): IntervalRange = { + <<(that.value) + } + + /** Shift this range left, i.e. shifts the min and max by value + * @param that + * @return + */ + def <<(that: UInt): IntervalRange = { + doFirrtlDynamicShift(that, isLeft = true) + } + + /** Shift this range right, i.e. shifts the min and max by the specified amount + * @param that + * @return + */ + override def >>(that: Int): IntervalRange = { + doFirrtlOp(PrimOps.Shr, that) + } + + /** Shift this range right, i.e. shifts the min and max by the known width + * @param that + * @return + */ + override def >>(that: KnownWidth): IntervalRange = { + >>(that.value) + } + + /** Shift this range right, i.e. shifts the min and max by value + * @param that + * @return + */ + def >>(that: UInt): IntervalRange = { + doFirrtlDynamicShift(that, isLeft = false) + } + + /** + * Squeeze returns the intersection of the ranges this interval and that Interval + * @param that + * @return + */ + def squeeze(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Squeeze, that) + } + + /** + * Wrap the value of this [[Interval]] into the range of a different Interval with a presumably smaller range. + * @param that + * @return + */ + def wrap(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Wrap, that) + } + + /** + * Clip the value of this [[Interval]] into the range of a different Interval with a presumably smaller range. + * @param that + * @return + */ + def clip(that: IntervalRange): IntervalRange = { + doFirrtlOp(PrimOps.Clip, that) + } + + /** merges the ranges of this and that, basically takes lowest low, highest high and biggest bp + * set unknown if any of this or that's value of above is unknown + * Like an union but will slurp up points in between the two ranges that were part of neither + * @param that + * @return + */ + override def merge(that: IntervalRange): IntervalRange = { + val lowest = (this.getLowestPossibleValue, that.getLowestPossibleValue) match { + case (Some(l1), Some(l2)) => + if(l1 < l2) { this.lower } else { that.lower } + case _ => + firrtlir.UnknownBound + } + val highest = (this.getHighestPossibleValue, that.getHighestPossibleValue) match { + case (Some(l1), Some(l2)) => + if(l1 >= l2) { this.lower } else { that.lower } + case _ => + firrtlir.UnknownBound + } + val newBinaryPoint = (this.firrtlBinaryPoint, that.firrtlBinaryPoint) match { + case (firrtlir.IntWidth(b1), firrtlir.IntWidth(b2)) => + if(b1 > b2) { firrtlir.IntWidth(b1)} else { firrtlir.IntWidth(b2) } + case _ => + firrtlir.UnknownWidth + } + IntervalRange(lowest, highest, newBinaryPoint) + } + + def binaryPoint: BinaryPoint = { + firrtlBinaryPoint match { + case firrtlir.IntWidth(n) => + assert(n < Int.MaxValue, s"binary point value $n is out of range") + KnownBinaryPoint(n.toInt) + case _ => UnknownBinaryPoint + } + } +} + +abstract class Command { + def sourceInfo: SourceInfo +} +abstract class Definition extends Command { + def id: HasId + def name: String = id.getRef.name +} +// scalastyle:off line.size.limit +case class DefPrim[T <: Data](sourceInfo: SourceInfo, id: T, op: PrimOp, args: Arg*) extends Definition +case class DefInvalid(sourceInfo: SourceInfo, arg: Arg) extends Command +case class DefWire(sourceInfo: SourceInfo, id: Data) extends Definition +case class DefReg(sourceInfo: SourceInfo, id: Data, clock: Arg) extends Definition +case class DefRegInit(sourceInfo: SourceInfo, id: Data, clock: Arg, reset: Arg, init: Arg) extends Definition +case class DefMemory(sourceInfo: SourceInfo, id: HasId, t: Data, size: BigInt) extends Definition +case class DefSeqMemory(sourceInfo: SourceInfo, id: HasId, t: Data, size: BigInt, readUnderWrite: fir.ReadUnderWrite.Value) extends Definition +case class DefMemPort[T <: Data](sourceInfo: SourceInfo, id: T, source: Node, dir: MemPortDirection, index: Arg, clock: Arg) extends Definition +case class DefInstance(sourceInfo: SourceInfo, id: BaseModule, ports: Seq[Port]) extends Definition +case class WhenBegin(sourceInfo: SourceInfo, pred: Arg) extends Command +case class WhenEnd(sourceInfo: SourceInfo, firrtlDepth: Int, hasAlt: Boolean = false) extends Command +case class AltBegin(sourceInfo: SourceInfo) extends Command +case class OtherwiseEnd(sourceInfo: SourceInfo, firrtlDepth: Int) extends Command +case class Connect(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command +case class BulkConnect(sourceInfo: SourceInfo, loc1: Node, loc2: Node) extends Command +case class Attach(sourceInfo: SourceInfo, locs: Seq[Node]) extends Command +case class ConnectInit(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command +case class Stop(sourceInfo: SourceInfo, clock: Arg, ret: Int) extends Command +case class Port(id: Data, dir: SpecifiedDirection) +case class Printf(sourceInfo: SourceInfo, clock: Arg, pable: Printable) extends Command +abstract class Component extends Arg { + def id: BaseModule + def name: String + def ports: Seq[Port] +} +case class DefModule(id: RawModule, name: String, ports: Seq[Port], commands: Seq[Command]) extends Component +case class DefBlackBox(id: BaseBlackBox, name: String, ports: Seq[Port], topDir: SpecifiedDirection, params: Map[String, Param]) extends Component + +case class Circuit(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation] = Seq.empty) |
