diff options
| author | Jack | 2022-07-30 22:41:15 +0000 |
|---|---|---|
| committer | Jack | 2022-07-30 22:41:15 +0000 |
| commit | 4cd44fa4dab370fcc5c20bcacc1fa0ee02327252 (patch) | |
| tree | 05730be260feca0d2a870c4bb88325d36631a8fc /core | |
| parent | fe9635ef21bad233945617a24ab16cfa4055f2d1 (diff) | |
| parent | bced77045c8fc5db37e40b159c49220929e15d46 (diff) | |
Merge branch '3.5.x' into 3.5-release
Diffstat (limited to 'core')
21 files changed, 724 insertions, 194 deletions
diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index cc5b83d9..82c02cbc 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -226,8 +226,8 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) extend } // Since all children are the same, we can just use the sample_element rather than all children - // .get is safe because None means mixed directions, we only pass 1 so that's not possible - direction = ActualDirection.fromChildren(Set(sample_element.direction), resolvedDirection).get + direction = + ActualDirection.fromChildren(Set(sample_element.direction), resolvedDirection).getOrElse(ActualDirection.Empty) } // Note: the constructor takes a gen() function instead of a Seq to enforce @@ -321,6 +321,7 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) extend case ActualDirection.Bidirectional(ActualDirection.Default) | ActualDirection.Unspecified => SpecifiedDirection.Unspecified case ActualDirection.Bidirectional(ActualDirection.Flipped) => SpecifiedDirection.Flip + case ActualDirection.Empty => SpecifiedDirection.Unspecified } // TODO port technically isn't directly child of this data structure, but the result of some // muxes / demuxes. However, this does make access consistent with the top-level bindings. diff --git a/core/src/main/scala/chisel3/Annotation.scala b/core/src/main/scala/chisel3/Annotation.scala index e08557eb..c350fb30 100644 --- a/core/src/main/scala/chisel3/Annotation.scala +++ b/core/src/main/scala/chisel3/Annotation.scala @@ -20,6 +20,14 @@ trait ChiselAnnotation { def toFirrtl: Annotation } +/** Enhanced interface for Annotations in Chisel + * + * Defines a conversion to corresponding FIRRTL Annotation(s) + */ +trait ChiselMultiAnnotation { + def toFirrtl: Seq[Annotation] +} + /** Mixin for [[ChiselAnnotation]] that instantiates an associated FIRRTL Transform when this Annotation is present * during a run of * [[Driver$.execute(args:Array[String],dut:()=>chisel3\.RawModule)* Driver.execute]]. @@ -34,6 +42,9 @@ object annotate { def apply(anno: ChiselAnnotation): Unit = { Builder.annotations += anno } + def apply(annos: ChiselMultiAnnotation): Unit = { + Builder.newAnnotations += annos + } } /** Marks that a module to be ignored in Dedup Transform in Firrtl pass diff --git a/core/src/main/scala/chisel3/Bits.scala b/core/src/main/scala/chisel3/Bits.scala index a135a8e5..72094a65 100644 --- a/core/src/main/scala/chisel3/Bits.scala +++ b/core/src/main/scala/chisel3/Bits.scala @@ -8,7 +8,13 @@ import chisel3.experimental.{FixedPoint, Interval} import chisel3.internal._ import chisel3.internal.Builder.pushOp import chisel3.internal.firrtl._ -import chisel3.internal.sourceinfo.{SourceInfo, SourceInfoTransform, SourceInfoWhiteboxTransform, UIntTransform} +import chisel3.internal.sourceinfo.{ + IntLiteralApplyTransform, + SourceInfo, + SourceInfoTransform, + SourceInfoWhiteboxTransform, + UIntTransform +} import chisel3.internal.firrtl.PrimOp._ import _root_.firrtl.{ir => firrtlir} import _root_.firrtl.{constraint => firrtlconstraint} @@ -94,10 +100,10 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi * @param x an index * @return the specified bit */ - final def apply(x: BigInt): Bool = macro SourceInfoTransform.xArg + final def extract(x: BigInt): Bool = macro SourceInfoTransform.xArg /** @group SourceInfoTransformMacro */ - final def do_apply(x: BigInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { + final def do_extract(x: BigInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { if (x < 0) { Builder.error(s"Negative bit indices are illegal (got $x)") } @@ -121,26 +127,48 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi * * @param x an index * @return the specified bit - * @note convenience method allowing direct use of [[scala.Int]] without implicits */ - final def apply(x: Int): Bool = macro SourceInfoTransform.xArg + final def apply(x: BigInt): Bool = macro IntLiteralApplyTransform.safeApply + + /** @group SourceInfoTransformMacro */ + final def do_apply(x: BigInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + do_extract(x) + + /** Returns the specified bit on this $coll as a [[Bool]], statically addressed. + * + * @param x an index + * @return the specified bit + */ + final def apply(x: Int): Bool = macro IntLiteralApplyTransform.safeApply /** @group SourceInfoTransformMacro */ - final def do_apply(x: Int)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = apply(BigInt(x)) + final def do_apply(x: Int)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + do_extract(BigInt(x)) /** Returns the specified bit on this wire as a [[Bool]], dynamically addressed. * * @param x a hardware component whose value will be used for dynamic addressing * @return the specified bit */ - final def apply(x: UInt): Bool = macro SourceInfoTransform.xArg + final def extract(x: UInt): Bool = macro SourceInfoTransform.xArg /** @group SourceInfoTransformMacro */ - final def do_apply(x: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { + final def do_extract(x: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { val theBits = this >> x theBits(0) } + /** Returns the specified bit on this wire as a [[Bool]], dynamically addressed. + * + * @param x a hardware component whose value will be used for dynamic addressing + * @return the specified bit + */ + final def apply(x: UInt): Bool = macro SourceInfoTransform.xArg + + /** @group SourceInfoTransformMacro */ + final def do_apply(x: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + do_extract(x) + /** Returns a subset of bits on this $coll from `hi` to `lo` (inclusive), statically addressed. * * @example @@ -193,7 +221,7 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi /** @group SourceInfoTransformMacro */ final def do_apply(x: BigInt, y: BigInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = - apply(castToInt(x, "High index"), castToInt(y, "Low index")) + do_apply(castToInt(x, "High index"), castToInt(y, "Low index")) private[chisel3] def unop[T <: Data](sourceInfo: SourceInfo, dest: T, op: PrimOp): T = { requireIsHardware(this, "bits operated on") diff --git a/core/src/main/scala/chisel3/BlackBox.scala b/core/src/main/scala/chisel3/BlackBox.scala index f3fc2711..f618901f 100644 --- a/core/src/main/scala/chisel3/BlackBox.scala +++ b/core/src/main/scala/chisel3/BlackBox.scala @@ -71,11 +71,8 @@ package experimental { val names = nameIds(classOf[ExtModule]) - // Name ports based on reflection - for (port <- getModulePorts) { - require(names.contains(port), s"Unable to name port $port in $this") - port.setRef(ModuleIO(this, _namespace.name(names(port)))) - } + // Ports are named in the same way as regular Modules + namePorts(names) // All suggestions are in, force names to every node. // While BlackBoxes are not supposed to have an implementation, we still need to call diff --git a/core/src/main/scala/chisel3/CompileOptions.scala b/core/src/main/scala/chisel3/CompileOptions.scala index db773d6e..aca00d1f 100644 --- a/core/src/main/scala/chisel3/CompileOptions.scala +++ b/core/src/main/scala/chisel3/CompileOptions.scala @@ -6,22 +6,37 @@ import scala.language.experimental.macros import scala.reflect.macros.blackbox.Context trait CompileOptions { - // Should Record connections require a strict match of fields. - // If true and the same fields aren't present in both source and sink, a MissingFieldException, - // MissingLeftFieldException, or MissingRightFieldException will be thrown. + + /** Should Record connections require a strict match of fields. + * + * If true and the same fields aren't present in both source and sink, a MissingFieldException, + * MissingLeftFieldException, or MissingRightFieldException will be thrown. + */ val connectFieldsMustMatch: Boolean - // When creating an object that takes a type argument, the argument must be unbound (a pure type). + + /** When creating an object that takes a type argument, the argument must be unbound (a pure type). */ val declaredTypeMustBeUnbound: Boolean - // If a connection operator fails, don't try the connection with the operands (source and sink) reversed. + + /** If a connection operator fails, don't try the connection with the operands (source and sink) reversed. */ val dontTryConnectionsSwapped: Boolean - // If connection directionality is not explicit, do not use heuristics to attempt to determine it. + + /** If connection directionality is not explicit, do not use heuristics to attempt to determine it. */ val dontAssumeDirectionality: Boolean - // Check that referenced Data have actually been declared. + + /** Check that referenced Data have actually been declared. */ val checkSynthesizable: Boolean - // Require explicit assignment of DontCare to generate "x is invalid" + + /** Require explicit assignment of DontCare to generate "x is invalid" */ val explicitInvalidate: Boolean - // Should the reset type of Module be a Bool or a Reset + + /** Should the reset type of Module be a Bool or a Reset */ val inferModuleReset: Boolean + + /** If marked true, then any Module which consumes `inferModuleReset=false` must also mix in [[RequireSyncReset]] */ + def migrateInferModuleReset: Boolean = false + + /** Should connects emit as firrtl <= instead of <- */ + def emitStrictConnects: Boolean = true } object CompileOptions { @@ -36,6 +51,7 @@ object CompileOptions { } object ExplicitCompileOptions { + case class CompileOptionsClass( // Should Record connections require a strict match of fields. // If true and the same fields aren't present in both source and sink, a MissingFieldException, @@ -65,7 +81,30 @@ object ExplicitCompileOptions { checkSynthesizable = false, explicitInvalidate = false, inferModuleReset = false - ) + ) { + override def migrateInferModuleReset = false + override def emitStrictConnects = false + override def copy( + connectFieldsMustMatch: Boolean = false, + declaredTypeMustBeUnbound: Boolean = false, + dontTryConnectionsSwapped: Boolean = false, + dontAssumeDirectionality: Boolean = false, + checkSynthesizable: Boolean = false, + explicitInvalidate: Boolean = false, + inferModuleReset: Boolean = false + ) = new CompileOptionsClass( + connectFieldsMustMatch, + declaredTypeMustBeUnbound, + dontTryConnectionsSwapped, + dontAssumeDirectionality, + checkSynthesizable, + explicitInvalidate, + inferModuleReset + ) { + override def migrateInferModuleReset = false + override def emitStrictConnects = false + } + } // Collection of "strict" connection compile options, preferred for new code. implicit val Strict = new CompileOptionsClass( @@ -76,5 +115,30 @@ object ExplicitCompileOptions { checkSynthesizable = true, explicitInvalidate = true, inferModuleReset = true - ) + ) { + + override def migrateInferModuleReset = false + override def emitStrictConnects = true + + override def copy( + connectFieldsMustMatch: Boolean = true, + declaredTypeMustBeUnbound: Boolean = true, + dontTryConnectionsSwapped: Boolean = true, + dontAssumeDirectionality: Boolean = true, + checkSynthesizable: Boolean = true, + explicitInvalidate: Boolean = true, + inferModuleReset: Boolean = true + ) = new CompileOptionsClass( + connectFieldsMustMatch, + declaredTypeMustBeUnbound, + dontTryConnectionsSwapped, + dontAssumeDirectionality, + checkSynthesizable, + explicitInvalidate, + inferModuleReset + ) { + override def migrateInferModuleReset = false + override def emitStrictConnects = true + } + } } diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index f468335e..592ebe25 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -12,6 +12,7 @@ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.{DeprecatedSourceInfo, SourceInfo, SourceInfoTransform, UnlocatableSourceInfo} import scala.collection.immutable.LazyList // Needed for 2.12 alias +import scala.reflect.ClassTag import scala.util.Try /** User-specified directions. @@ -157,6 +158,31 @@ package experimental { target.direction } + private def hasBinding[B <: ConstrainedBinding: ClassTag](target: Data) = { + target.topBindingOpt match { + case Some(b: B) => true + case _ => false + } + } + + /** Check if a given `Data` is an IO port + * @param x the `Data` to check + * @return `true` if x is an IO port, `false` otherwise + */ + def isIO(x: Data): Boolean = hasBinding[PortBinding](x) + + /** Check if a given `Data` is a Wire + * @param x the `Data` to check + * @return `true` if x is a Wire, `false` otherwise + */ + def isWire(x: Data): Boolean = hasBinding[WireBinding](x) + + /** Check if a given `Data` is a Reg + * @param x the `Data` to check + * @return `true` if x is a Reg, `false` otherwise + */ + def isReg(x: Data): Boolean = hasBinding[RegBinding](x) + /** Check if two Chisel types are the same type. * Internally, this is dispatched to each Chisel type's * `typeEquivalent` function for each type to determine @@ -570,6 +596,7 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { private[chisel3] def badConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit = throwException(s"cannot connect ${this} and ${that}") + private[chisel3] def connect( that: Data )( @@ -583,6 +610,9 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { case _: ReadOnlyBinding => throwException(s"Cannot reassign to read-only $this") case _ => // fine } + } + if (connectCompileOptions.emitStrictConnects) { + try { MonoConnect.connect(sourceInfo, connectCompileOptions, this, that, Builder.referenceUserModule) } catch { @@ -610,6 +640,8 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { case (_: DontCareBinding, _) => throw BiConnect.DontCareCantBeSink case _ => // fine } + } + if (connectCompileOptions.emitStrictConnects) { try { BiConnect.connect(sourceInfo, connectCompileOptions, this, that, Builder.referenceUserModule) } catch { @@ -866,7 +898,7 @@ trait WireFactory { x.bind(WireBinding(Builder.forcedUserModule, Builder.currentWhen)) pushCommand(DefWire(sourceInfo, x)) - if (!compileOptions.explicitInvalidate) { + if (!compileOptions.explicitInvalidate || Builder.currentModule.get.isInstanceOf[ImplicitInvalidate]) { pushCommand(DefInvalid(sourceInfo, x.ref)) } diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 84139630..ba2d2e32 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -141,6 +141,15 @@ abstract class Module(implicit moduleCompileOptions: CompileOptions) extends Raw // Top module and compatibility mode use Bool for reset // Note that a Definition elaboration will lack a parent, but still not be a Top module val inferReset = (_parent.isDefined || Builder.inDefinition) && moduleCompileOptions.inferModuleReset + if (moduleCompileOptions.migrateInferModuleReset && !moduleCompileOptions.inferModuleReset) { + this match { + case _: RequireSyncReset => // Good! It's been migrated. + case _ => // Bad! It hasn't been migrated. + Builder.error( + s"$desiredName is not inferring its module reset, but has not been marked `RequireSyncReset`. Please extend this trait." + ) + } + } if (inferReset) Reset() else Bool() } @@ -266,7 +275,7 @@ package internal { private[chisel3] def setRefAndPortsRef(namespace: Namespace): Unit = { val record = _portsRecord // Use .forceName to re-use default name resolving behavior - record.forceName(None, default = this.desiredName, namespace) + record.forceName(default = this.desiredName, namespace) // Now take the Ref that forceName set and convert it to the correct Arg val instName = record.getRef match { case Ref(name) => name @@ -377,7 +386,7 @@ package internal { clonePorts.setAllParents(Some(cloneParent)) cloneParent._portsRecord = clonePorts // Normally handled during Module construction but ClonePorts really lives in its parent's parent - if (!compileOptions.explicitInvalidate) { + if (!compileOptions.explicitInvalidate || Builder.currentModule.get.isInstanceOf[ImplicitInvalidate]) { // FIXME This almost certainly doesn't work since clonePorts is not a real thing... pushCommand(DefInvalid(sourceInfo, clonePorts.ref)) } @@ -486,6 +495,27 @@ package experimental { */ private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit + private[chisel3] def namePorts(names: HashMap[HasId, String]): Unit = { + for (port <- getModulePorts) { + port._computeName(None).orElse(names.get(port)) match { + case Some(name) => + if (_namespace.contains(name)) { + Builder.error( + s"""Unable to name port $port to "$name" in $this,""" + + " name is already taken by another port!" + ) + } + port.setRef(ModuleIO(this, _namespace.name(name))) + case None => + Builder.error( + s"Unable to name port $port in $this, " + + "try making it a public field of the Module" + ) + port.setRef(ModuleIO(this, "<UNNAMED>")) + } + } + } + // // Chisel Internals // diff --git a/core/src/main/scala/chisel3/Printable.scala b/core/src/main/scala/chisel3/Printable.scala index a616f2b0..78655517 100644 --- a/core/src/main/scala/chisel3/Printable.scala +++ b/core/src/main/scala/chisel3/Printable.scala @@ -63,57 +63,76 @@ object Printable { */ def pack(fmt: String, data: Data*): Printable = { val args = data.toIterator - // Error handling def carrotAt(index: Int) = (" " * index) + "^" def errorMsg(index: Int) = s"""| fmt = "$fmt" | ${carrotAt(index)} | data = ${data.mkString(", ")}""".stripMargin - def getArg(i: Int): Data = { + + def checkArg(i: Int): Unit = { if (!args.hasNext) { val msg = "has no matching argument!\n" + errorMsg(i) // Exception wraps msg in s"Format Specifier '$msg'" throw new MissingFormatArgumentException(msg) } - args.next() + val _ = args.next() } + var iter = 0 + var curr_start = 0 + val buf = mutable.ListBuffer.empty[String] + while (iter < fmt.size) { + // Encountered % which is either + // 1. Describing a format specifier. + // 2. Literal Percent + // 3. Dangling percent - most likely due to a typo - intended literal percent or forgot the specifier. + // Try to give meaningful error reports + if (fmt(iter) == '%') { + if (iter != fmt.size - 1 && (fmt(iter + 1) != '%' && !fmt(iter + 1).isWhitespace)) { + checkArg(iter) + buf += fmt.substring(curr_start, iter) + curr_start = iter + iter += 1 + } - val pables = mutable.ListBuffer.empty[Printable] - var str = "" - var percent = false - for ((c, i) <- fmt.zipWithIndex) { - if (percent) { - val arg = c match { - case FirrtlFormat(x) => FirrtlFormat(x.toString, getArg(i)) - case 'n' => Name(getArg(i)) - case 'N' => FullName(getArg(i)) - case '%' => Percent - case x => - val msg = s"Illegal format specifier '$x'!\n" + errorMsg(i) - throw new UnknownFormatConversionException(msg) + // Last character is %. + else if (iter == fmt.size - 1) { + val msg = s"Trailing %\n" + errorMsg(fmt.size - 1) + throw new UnknownFormatConversionException(msg) + } + + // A lone % + else if (fmt(iter + 1).isWhitespace) { + val msg = s"Unescaped % - add % if literal or add proper specifier if not\n" + errorMsg(iter + 1) + throw new UnknownFormatConversionException(msg) + } + + // A literal percent - hence increment by 2. + else { + iter += 2 } - pables += PString(str.dropRight(1)) // remove format % - pables += arg - str = "" - percent = false - } else { - str += c - percent = c == '%' } - } - if (percent) { - val msg = s"Trailing %\n" + errorMsg(fmt.size - 1) - throw new UnknownFormatConversionException(msg) + + // Normal progression + else { + iter += 1 + } } require( !args.hasNext, s"Too many arguments! More format specifier(s) expected!\n" + errorMsg(fmt.size) ) + buf += fmt.substring(curr_start, iter) + + // The string received as an input to pack is already + // treated i.e. escape sequences are processed. + // Since StringContext API assumes the parts are un-treated + // treatEscapes is called within the implemented custom interpolators. + // The literal \ needs to be escaped before sending to the custom cf interpolator. - pables += PString(str) - Printables(pables) + val bufEscapeBackSlash = buf.map(_.replace("\\", "\\\\")) + StringContext(bufEscapeBackSlash.toSeq: _*).cf(data: _*) } } diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 12b6a76a..f2ce4c70 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -43,25 +43,20 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) extends val compileOptions = moduleCompileOptions - private[chisel3] def namePorts(names: HashMap[HasId, String]): Unit = { - for (port <- getModulePorts) { - port._computeName(None, None).orElse(names.get(port)) match { - case Some(name) => - if (_namespace.contains(name)) { - Builder.error( - s"""Unable to name port $port to "$name" in $this,""" + - " name is already taken by another port!" - ) - } - port.setRef(ModuleIO(this, _namespace.name(name))) - case None => - Builder.error( - s"Unable to name port $port in $this, " + - "try making it a public field of the Module" - ) - port.setRef(ModuleIO(this, "<UNNAMED>")) + // This could be factored into a common utility + private def canBeNamed(id: HasId): Boolean = id match { + case d: Data => + d.binding match { + case Some(_: ConstrainedBinding) => true + case _ => false } - } + case b: BaseModule => true + case m: MemBase[_] => true + // These names don't affect hardware + case _: VerificationStatement => false + // While the above should be comprehensive, since this is used in warning we want to be careful + // to never accidentally have a match error + case _ => false } private[chisel3] override def generateComponent(): Option[Component] = { @@ -74,8 +69,24 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) extends namePorts(names) // Then everything else gets named + val warnReflectiveNaming = Builder.warnReflectiveNaming for ((node, name) <- names) { - node.suggestName(name) + node match { + case d: HasId if warnReflectiveNaming && canBeNamed(d) => + val result = d._suggestNameCheck(name) + result match { + case None => // All good, no warning + case Some((oldName, oldPrefix)) => + val prevName = buildName(oldName, oldPrefix.reverse) + val newName = buildName(name, Nil) + val msg = s"[module ${this.name}] '$prevName' is renamed by reflection to '$newName'. " + + s"Chisel 3.6 removes reflective naming so the name will remain '$prevName'." + Builder.warningNoLoc(msg) + } + // Note that unnamable things end up here (eg. literals), this is supporting backwards + // compatibility + case _ => node.suggestName(name) + } } // All suggestions are in, force names to every node. @@ -83,26 +94,26 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) extends id match { case id: ModuleClone[_] => id.setRefAndPortsRef(_namespace) // special handling case id: InstanceClone[_] => id.setAsInstanceRef() - case id: BaseModule => id.forceName(None, default = id.desiredName, _namespace) - case id: MemBase[_] => id.forceName(None, default = "MEM", _namespace) - case id: stop.Stop => id.forceName(None, default = "stop", _namespace) - case id: assert.Assert => id.forceName(None, default = "assert", _namespace) - case id: assume.Assume => id.forceName(None, default = "assume", _namespace) - case id: cover.Cover => id.forceName(None, default = "cover", _namespace) - case id: printf.Printf => id.forceName(None, default = "printf", _namespace) + case id: BaseModule => id.forceName(default = id.desiredName, _namespace) + case id: MemBase[_] => id.forceName(default = "MEM", _namespace) + case id: stop.Stop => id.forceName(default = "stop", _namespace) + case id: assert.Assert => id.forceName(default = "assert", _namespace) + case id: assume.Assume => id.forceName(default = "assume", _namespace) + case id: cover.Cover => id.forceName(default = "cover", _namespace) + case id: printf.Printf => id.forceName(default = "printf", _namespace) case id: Data => if (id.isSynthesizable) { id.topBinding match { case OpBinding(_, _) => - id.forceName(Some(""), default = "T", _namespace) + id.forceName(default = "_T", _namespace) case MemoryPortBinding(_, _) => - id.forceName(None, default = "MPORT", _namespace) + id.forceName(default = "MPORT", _namespace) case PortBinding(_) => - id.forceName(None, default = "PORT", _namespace) + id.forceName(default = "PORT", _namespace) case RegBinding(_, _) => - id.forceName(None, default = "REG", _namespace) + id.forceName(default = "REG", _namespace) case WireBinding(_, _) => - id.forceName(Some(""), default = "WIRE", _namespace) + id.forceName(default = "_WIRE", _namespace) case _ => // don't name literals } } // else, don't name unbound types @@ -136,7 +147,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) extends // Generate IO invalidation commands to initialize outputs as unused, // unless the client wants explicit control over their generation. val invalidateCommands = { - if (!compileOptions.explicitInvalidate) { + if (!compileOptions.explicitInvalidate || this.isInstanceOf[ImplicitInvalidate]) { getModulePorts.map { port => DefInvalid(UnlocatableSourceInfo, port.ref) } } else { Seq() @@ -150,7 +161,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) extends private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = { implicit val sourceInfo = UnlocatableSourceInfo - if (!parentCompileOptions.explicitInvalidate) { + if (!parentCompileOptions.explicitInvalidate || Builder.currentModule.get.isInstanceOf[ImplicitInvalidate]) { for (port <- getModulePorts) { pushCommand(DefInvalid(sourceInfo, port.ref)) } @@ -166,6 +177,9 @@ trait RequireSyncReset extends Module { override private[chisel3] def mkReset: Bool = Bool() } +/** Mix with a [[RawModule]] to automatically connect DontCare to the module's ports, wires, and children instance IOs. */ +trait ImplicitInvalidate { self: RawModule => } + package object internal { import scala.annotation.implicitNotFound @@ -175,6 +189,41 @@ package object internal { /** Marker trait for modules that are not true modules */ private[chisel3] trait PseudoModule extends BaseModule + /* Check if a String name is a temporary name */ + def isTemp(name: String): Boolean = name.nonEmpty && name.head == '_' + + /** Creates a name String from a prefix and a seed + * @param prefix The prefix associated with the seed (must be in correct order, *not* reversed) + * @param seed The seed for computing the name (if available) + */ + def buildName(seed: String, prefix: Prefix): String = { + // Don't bother copying the String if there's no prefix + if (prefix.isEmpty) { + seed + } else { + // Using Java's String builder to micro-optimize appending a String excluding 1st character + // for temporaries + val builder = new java.lang.StringBuilder() + // Starting with _ is the indicator of a temporary + val temp = isTemp(seed) + // Make sure the final result is also a temporary if this is a temporary + if (temp) { + builder.append('_') + } + prefix.foreach { p => + builder.append(p) + builder.append('_') + } + if (temp) { + // We've moved the leading _ to the front, drop it here + builder.append(seed, 1, seed.length) + } else { + builder.append(seed) + } + builder.toString + } + } + // Private reflective version of "val io" to maintain Chisel.Module semantics without having // io as a virtual method. See https://github.com/freechipsproject/chisel3/pull/1550 for more // information about the removal of "val io" diff --git a/core/src/main/scala/chisel3/StrongEnum.scala b/core/src/main/scala/chisel3/StrongEnum.scala index 6f9cce55..cd6f11ee 100644 --- a/core/src/main/scala/chisel3/StrongEnum.scala +++ b/core/src/main/scala/chisel3/StrongEnum.scala @@ -71,7 +71,7 @@ object EnumAnnotations { } import EnumAnnotations._ -abstract class EnumType(private val factory: EnumFactory, selfAnnotating: Boolean = true) extends Element { +abstract class EnumType(private[chisel3] val factory: EnumFactory, selfAnnotating: Boolean = true) extends Element { // Use getSimpleName instead of enumTypeName because for debugging purposes // the fully qualified name isn't necessary (compared to for the diff --git a/core/src/main/scala/chisel3/experimental/dataview/package.scala b/core/src/main/scala/chisel3/experimental/dataview/package.scala index c583c516..71ae2d8f 100644 --- a/core/src/main/scala/chisel3/experimental/dataview/package.scala +++ b/core/src/main/scala/chisel3/experimental/dataview/package.scala @@ -33,7 +33,7 @@ package object dataview { // The names of views do not matter except for when a view is annotated. For Views that correspond // To a single Data, we just forward the name of the Target. For Views that correspond to more // than one Data, we return this assigned name but rename it in the Convert stage - result.forceName(None, "view", Builder.viewNamespace) + result.forceName("view", Builder.viewNamespace) result } } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala index 59b4c692..36bf6f87 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala @@ -11,6 +11,7 @@ import chisel3.internal.sourceinfo.{DefinitionTransform, DefinitionWrapTransform import chisel3.experimental.BaseModule import chisel3.internal.BaseModule.IsClone import firrtl.annotations.{IsModule, ModuleTarget} +import firrtl.annotations.{IsModule, ModuleTarget, NoTargetAnnotation} /** User-facing Definition type. * Represents a definition of an object of type [[A]] which are marked as @instantiable @@ -100,7 +101,10 @@ object Definition extends SourceInfoDoc { implicit sourceInfo: SourceInfo, compileOptions: CompileOptions ): Definition[T] = { - val dynamicContext = new DynamicContext(Nil, Builder.captureContext().throwOnFirstError) + val dynamicContext = { + val context = Builder.captureContext() + new DynamicContext(Nil, context.throwOnFirstError, context.warnReflectiveNaming) + } Builder.globalNamespace.copyTo(dynamicContext.globalNamespace) dynamicContext.inDefinition = true val (ir, module) = Builder.build(Module(proto), dynamicContext, false) @@ -112,3 +116,11 @@ object Definition extends SourceInfoDoc { } } + +/** Stores a [[Definition]] that is imported so that its Instances can be + * compiled separately. + */ +case class ImportDefinitionAnnotation[T <: BaseModule with IsInstantiable]( + definition: Definition[T], + overrideDefName: Option[String] = None) + extends NoTargetAnnotation diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala index cc926771..861023a1 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala @@ -2,13 +2,15 @@ package chisel3.experimental.hierarchy -import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.language.experimental.macros import chisel3._ import chisel3.internal.BaseModule.{InstantiableClone, IsClone, ModuleClone} +import chisel3.internal.Builder import chisel3.internal.sourceinfo.{InstanceTransform, SourceInfo} -import chisel3.experimental.BaseModule +import chisel3.experimental.{BaseModule, ExtModule} +import chisel3.internal.firrtl.{Component, DefBlackBox, DefModule, Port} import firrtl.annotations.IsModule +import chisel3.internal.throwException /** User-facing Instance type. * Represents a unique instance of type [[A]] which are marked as @instantiable @@ -107,9 +109,39 @@ object Instance extends SourceInfoDoc { implicit sourceInfo: SourceInfo, compileOptions: CompileOptions ): Instance[T] = { + // Check to see if the module is already defined internally or externally + val existingMod = Builder.components.map { + case c: DefModule if c.id == definition.proto => Some(c) + case c: DefBlackBox if c.name == definition.proto.name => Some(c) + case _ => None + }.flatten + + if (existingMod.isEmpty) { + // Add a Definition that will get emitted as an ExtModule so that FIRRTL + // does not complain about a missing element + val extModName = Builder.importDefinitionMap.getOrElse( + definition.proto.name, + throwException( + "Imported Definition information not found - possibly forgot to add ImportDefinition annotation?" + ) + ) + class EmptyExtModule extends ExtModule { + override def desiredName: String = extModName + override def generateComponent(): Option[Component] = { + require(!_closed, s"Can't generate $desiredName module more than once") + _closed = true + val firrtlPorts = definition.proto.getModulePorts.map { port => Port(port, port.specifiedDirection) } + val component = DefBlackBox(this, definition.proto.name, firrtlPorts, SpecifiedDirection.Unspecified, params) + Some(component) + } + } + Definition(new EmptyExtModule() {}) + } + val ports = experimental.CloneModuleAsRecord(definition.proto) val clone = ports._parent.get.asInstanceOf[ModuleClone[T]] clone._madeFromDefinition = true + new Instance(Clone(clone)) } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala index 46a38e7c..c83479b0 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -218,7 +218,7 @@ object Lookupable { result.bind(newBinding) result.setAllParents(Some(ViewParent)) - result.forceName(None, "view", Builder.viewNamespace) + result.forceName("view", Builder.viewNamespace) result } diff --git a/core/src/main/scala/chisel3/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala index 9b9c83f4..b1d9cae4 100644 --- a/core/src/main/scala/chisel3/experimental/package.scala +++ b/core/src/main/scala/chisel3/experimental/package.scala @@ -160,8 +160,42 @@ package object experimental { * } * }}} */ + @deprecated( + "@chiselName and NoChiselNamePrefix have been replaced by the compiler plugin and AffectsChiselPrefix. Use these instead", + "Chisel 3.5" + ) trait NoChiselNamePrefix + /** Generate prefixes from values of this type in the Chisel compiler plugin + * + * Users can mixin this trait to tell the Chisel compiler plugin to include the names of + * vals of this type when generating prefixes for naming `Data` and `Mem` instances. + * This is generally useful whenever creating a `class` that contains `Data`, `Mem`, + * or `Module` instances but does not itself extend `Data` or `Module`. + * + * @see See [[https://www.chisel-lang.org/chisel3/docs/explanations/naming.html the compiler plugin documentation]] for more information on this process. + * + * @example {{{ + * import chisel3._ + * import chisel3.experimental.AffectsChiselPrefix + * + * class MyModule extends Module { + * // Note: This contains a Data but is not a named component itself + * class NotAData extends AffectsChiselPrefix { + * val value = Wire(Bool()) + * } + * + * // Name with AffectsChiselPrefix: "nonData_value" + * // Name without AffectsChiselPrefix: "value" + * val nonData = new NotAData + * + * // Name with AffectsChiselPrefix: "nonData2_value" + * // Name without AffectsChiselPrefix: "value_1" + * val nonData2 = new NotAData + * } + */ + trait AffectsChiselPrefix + object BundleLiterals { implicit class AddBundleLiteralConstructor[T <: Record](x: T) { def Lit(elems: (T => (Data, Data))*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala index 2d6c9e4a..e8fb2361 100644 --- a/core/src/main/scala/chisel3/internal/BiConnect.scala +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -55,6 +55,24 @@ private[chisel3] object BiConnect { * 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. + * + * == Chisel Semantics and how they emit to firrtl == + * + * 1. Strict Bi-Connect (all fields as seen by firrtl must match exactly) + * `a <= b` + * + * 2. Strict Bi-Connect (implemented as being field-blasted because we know all firrtl fields would not match exactly) + * `a.foo <= b.foo, b.bar <= a.bar` + * + * 3. Not-Strict Bi-Connect (firrtl will allow fields to not match exactly) + * `a <- b` + * + * 4. Mixed Semantic Bi-Connect (some fields need to be handled differently) + * `a.foo <= b.foo` (case 2), `b.bar <- a.bar` (case 3) + * + * - The decision on 1 vs 2 is based on structural type -- if same type once emitted to firrtl, emit 1, otherwise emit 2 + * - 1/2 vs 3 is based on CompileOptions at connection point e.g. at `<>` , emit 3 if `emitStrictConnects = false` for either side + * - 4 is a special case of 2 turning into 3 for some subfields, when either side's subfield at `extends Bundle/Record` has `emitStrictConnects = false` */ def connect( sourceInfo: SourceInfo, @@ -140,8 +158,8 @@ private[chisel3] object BiConnect { // Handle Records defined in Chisel._ code by emitting a FIRRTL bulk // connect when possible and a partial connect otherwise case pair @ (left_r: Record, right_r: Record) => - val notStrict = - Seq(left_r.compileOptions, right_r.compileOptions).contains(ExplicitCompileOptions.NotStrict) + val emitStrictConnects: Boolean = + left_r.compileOptions.emitStrictConnects && right_r.compileOptions.emitStrictConnects // chisel3 <> is commutative but FIRRTL <- is not val flipConnection = @@ -161,40 +179,38 @@ private[chisel3] object BiConnect { ) ) { pushCommand(Connect(sourceInfo, leftReified.get.lref, rightReified.get.lref)) - } else if (notStrict) { - newLeft.bulkConnect(newRight)(sourceInfo, ExplicitCompileOptions.NotStrict) + } else if (!emitStrictConnects) { + newLeft.legacyConnect(newRight)(sourceInfo) } else { recordConnect(sourceInfo, connectCompileOptions, left_r, right_r, context_mod) } - // Handle Records connected to DontCare (change to NotStrict) + // Handle Records connected to DontCare 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") - } + if (!left_r.compileOptions.emitStrictConnects) { + left.legacyConnect(right)(sourceInfo) + } else { + // 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") - } + if (!right_r.compileOptions.emitStrictConnects) { + left.legacyConnect(right)(sourceInfo) + } else { + // 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 diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 4180f580..61f94f8f 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -6,7 +6,7 @@ import scala.util.DynamicVariable import scala.collection.mutable.ArrayBuffer import chisel3._ import chisel3.experimental._ -import chisel3.experimental.hierarchy.{Clone, Instance} +import chisel3.experimental.hierarchy.{Clone, ImportDefinitionAnnotation, Instance} import chisel3.internal.firrtl._ import chisel3.internal.naming._ import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget} @@ -137,6 +137,17 @@ private[chisel3] trait HasId extends InstanceId { this } + // Private internal version of suggestName that tells you if the name changed + // Returns Some(old name, old prefix) if name changed, None otherwise + private[chisel3] def _suggestNameCheck(seed: => String): Option[(String, Prefix)] = { + val oldSeed = this.seedOpt + val oldPrefix = this.naming_prefix + suggestName(seed) + if (oldSeed.nonEmpty && (oldSeed != this.seedOpt || oldPrefix != this.naming_prefix)) { + Some(oldSeed.get -> oldPrefix) + } else None + } + /** Takes the first seed suggested. Multiple calls to this function will be ignored. * If the final computed name conflicts with another name, it may get uniquified by appending * a digit at the end. @@ -166,37 +177,13 @@ private[chisel3] trait HasId extends InstanceId { } /** Computes the name of this HasId, if one exists - * @param defaultPrefix Optionally provide a default prefix for computing the name * @param defaultSeed Optionally provide default seed for computing the name * @return the name, if it can be computed */ - private[chisel3] def _computeName(defaultPrefix: Option[String], defaultSeed: Option[String]): Option[String] = { - - /** Computes a name of this signal, given the seed and prefix - * @param seed - * @param prefix - * @return - */ - def buildName(seed: String, prefix: Prefix): String = { - val builder = new StringBuilder() - prefix.foreach { p => - builder ++= p - builder += '_' - } - builder ++= seed - builder.toString - } - - if (hasSeed) { - Some(buildName(seedOpt.get, naming_prefix.reverse)) - } else { - defaultSeed.map { default => - defaultPrefix match { - case Some(p) => buildName(default, p :: naming_prefix.reverse) - case None => buildName(default, naming_prefix.reverse) - } - } - } + private[chisel3] def _computeName(defaultSeed: Option[String]): Option[String] = { + seedOpt + .orElse(defaultSeed) + .map(name => buildName(name, naming_prefix.reverse)) } /** This resolves the precedence of [[autoSeed]] and [[suggestName]] @@ -216,9 +203,9 @@ private[chisel3] trait HasId extends InstanceId { // 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(prefix: Option[String], default: => String, namespace: Namespace): Unit = + private[chisel3] def forceName(default: => String, namespace: Namespace): Unit = if (_ref.isEmpty) { - val candidate_name = _computeName(prefix, Some(default)).get + val candidate_name = _computeName(Some(default)).get val available_name = namespace.name(candidate_name) setRef(Ref(available_name)) // Clear naming prefix to free memory @@ -240,7 +227,18 @@ private[chisel3] trait HasId extends InstanceId { private def refName(c: Component): String = _ref match { case Some(arg) => arg.fullName(c) - case None => _computeName(None, None).get + case None => + // This is super hacky but this is just for a short term deprecation + // These accesses occur after Chisel elaboration so we cannot use the normal + // Builder.deprecated mechanism, we have to create our own one off ErrorLog and print the + // warning right away. + val errors = new ErrorLog + val logger = new _root_.logger.Logger(this.getClass.getName) + val msg = "Accessing the .instanceName or .toTarget of non-hardware Data is deprecated. " + + "This will become an error in Chisel 3.6." + errors.deprecated(msg, None) + errors.checkpoint(logger) + _computeName(None).get } // Helper for reifying views if they map to a single Target @@ -363,10 +361,50 @@ private[chisel3] class ChiselContext() { val viewNamespace = Namespace.empty } -private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq, val throwOnFirstError: Boolean) { +private[chisel3] class DynamicContext( + val annotationSeq: AnnotationSeq, + val throwOnFirstError: Boolean, + val warnReflectiveNaming: Boolean) { + val importDefinitionAnnos = annotationSeq.collect { case a: ImportDefinitionAnnotation[_] => a } + + // Map holding the actual names of extModules + // Pick the definition name by default in case not passed through annotation. + val importDefinitionMap = importDefinitionAnnos + .map(a => a.definition.proto.name -> a.overrideDefName.getOrElse(a.definition.proto.name)) + .toMap + + // Helper function which does 2 things + // 1. Ensure there are no repeated names for imported Definitions - both Proto Names as well as ExtMod Names + // 2. Return the distinct definition / extMod names + private def checkAndGeDistinctProtoExtModNames() = { + val importAllDefinitionProtoNames = importDefinitionAnnos.map { a => a.definition.proto.name } + val importDistinctDefinitionProtoNames = importDefinitionMap.keys.toSeq + val importAllDefinitionExtModNames = importDefinitionMap.toSeq.map(_._2) + val importDistinctDefinitionExtModNames = importAllDefinitionExtModNames.distinct + + if (importDistinctDefinitionProtoNames.length < importAllDefinitionProtoNames.length) { + val duplicates = importAllDefinitionProtoNames.diff(importDistinctDefinitionProtoNames).mkString(", ") + throwException(s"Expected distinct imported Definition names but found duplicates for: $duplicates") + } + if (importDistinctDefinitionExtModNames.length < importAllDefinitionExtModNames.length) { + val duplicates = importAllDefinitionExtModNames.diff(importDistinctDefinitionExtModNames).mkString(", ") + throwException(s"Expected distinct overrideDef names but found duplicates for: $duplicates") + } + (importAllDefinitionProtoNames ++ importAllDefinitionExtModNames).distinct + } + val globalNamespace = Namespace.empty + + // Ensure imported Definitions emit as ExtModules with the correct name so + // that instantiations will also use the correct name and prevent any name + // conflicts with Modules/Definitions in this elaboration + checkAndGeDistinctProtoExtModNames().foreach { + globalNamespace.name(_) + } + val components = ArrayBuffer[Component]() val annotations = ArrayBuffer[ChiselAnnotation]() + val newAnnotations = ArrayBuffer[ChiselMultiAnnotation]() var currentModule: Option[BaseModule] = None /** Contains a mapping from a elaborated module to their aspect @@ -432,8 +470,13 @@ private[chisel3] object Builder extends LazyLogging { def globalNamespace: Namespace = dynamicContext.globalNamespace def components: ArrayBuffer[Component] = dynamicContext.components def annotations: ArrayBuffer[ChiselAnnotation] = dynamicContext.annotations - def annotationSeq: AnnotationSeq = dynamicContext.annotationSeq - def namingStack: NamingStack = dynamicContext.namingStack + + // TODO : Unify this with annotations in the future - done this way for backward compatability + def newAnnotations: ArrayBuffer[ChiselMultiAnnotation] = dynamicContext.newAnnotations + + def annotationSeq: AnnotationSeq = dynamicContext.annotationSeq + def namingStack: NamingStack = dynamicContext.namingStack + def importDefinitionMap: Map[String, String] = dynamicContext.importDefinitionMap def unnamedViews: ArrayBuffer[Data] = dynamicContext.unnamedViews def viewNamespace: Namespace = chiselContext.get.viewNamespace @@ -480,7 +523,11 @@ private[chisel3] object Builder extends LazyLogging { } } buildAggName(d).map { name => - pushPrefix(name) + if (isTemp(name)) { + pushPrefix(name.tail) + } else { + pushPrefix(name) + } }.isDefined } @@ -621,6 +668,8 @@ private[chisel3] object Builder extends LazyLogging { throwException("Error: No implicit reset.") ) + def warnReflectiveNaming: Boolean = dynamicContext.warnReflectiveNaming + // 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 = { @@ -662,8 +711,9 @@ private[chisel3] object Builder extends LazyLogging { throwException(m) } } - def warning(m: => String): Unit = if (dynamicContextVar.value.isDefined) errors.warning(m) - def deprecated(m: => String, location: Option[String] = None): Unit = + def warning(m: => String): Unit = if (dynamicContextVar.value.isDefined) errors.warning(m) + def warningNoLoc(m: => String): Unit = if (dynamicContextVar.value.isDefined) errors.warningNoLoc(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. @@ -720,12 +770,12 @@ private[chisel3] object Builder extends LazyLogging { logger.info("Elaborating design...") val mod = f if (forceModName) { // This avoids definition name index skipping with D/I - mod.forceName(None, mod.name, globalNamespace) + mod.forceName(mod.name, globalNamespace) } errors.checkpoint(logger) logger.info("Done elaborating.") - (Circuit(components.last.name, components.toSeq, annotations.toSeq, makeViewRenameMap), mod) + (Circuit(components.last.name, components.toSeq, annotations.toSeq, makeViewRenameMap, newAnnotations.toSeq), mod) } } initializeSingletons() diff --git a/core/src/main/scala/chisel3/internal/Error.scala b/core/src/main/scala/chisel3/internal/Error.scala index 62086870..3b0846eb 100644 --- a/core/src/main/scala/chisel3/internal/Error.scala +++ b/core/src/main/scala/chisel3/internal/Error.scala @@ -186,6 +186,11 @@ private[chisel3] class ErrorLog { def warning(m: => String): Unit = errors += new Warning(m, getUserLineNumber) + /** Log a warning message without a source locator */ + def warningNoLoc(m: => String): Unit = { + errors += new Warning(m, None) + } + /** Emit an informational message */ @deprecated("This method will be removed in 3.5", "3.4") def info(m: String): Unit = diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala index 40056c89..31364804 100644 --- a/core/src/main/scala/chisel3/internal/MonoConnect.scala +++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala @@ -210,7 +210,9 @@ private[chisel3] object MonoConnect { } // Source is DontCare - it may be connected to anything. It generates a defInvalid for the sink. - case (sink, DontCare) => pushCommand(DefInvalid(sourceInfo, sink.lref)) + case (_sink: Element, DontCare) => + val sink = reify(_sink) // Handle views + pushCommand(DefInvalid(sourceInfo, sink.lref)) // DontCare as a sink is illegal. case (DontCare, _) => throw DontCareCantBeSink // Analog is illegal in mono connections. diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 1ee8842f..dc9ab027 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -94,7 +94,7 @@ object Arg { case Some(arg) => arg.name case None => id match { - case data: Data => data._computeName(None, Some("?")).get + case data: Data => data._computeName(Some("?")).get case _ => "?" } } @@ -861,7 +861,40 @@ case class DefBlackBox( params: Map[String, Param]) extends Component -case class Circuit(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation], renames: RenameMap) { - def firrtlAnnotations: Iterable[Annotation] = annotations.flatMap(_.toFirrtl.update(renames)) +case class Circuit( + name: String, + components: Seq[Component], + @deprecated("Do not use annotations val of Circuit directly - use firrtlAnnotations instead. Will be removed in a future release", + "Chisel 3.5") + annotations: Seq[ChiselAnnotation], + renames: RenameMap, + @deprecated("Do not use newAnnotations val of Circuit directly - use firrtlAnnotations instead. Will be removed in a future release", + "Chisel 3.5") + newAnnotations: Seq[ChiselMultiAnnotation]) { + + def this(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation], renames: RenameMap) = + this(name, components, annotations, renames, Seq.empty) + + def firrtlAnnotations: Iterable[Annotation] = + annotations.flatMap(_.toFirrtl.update(renames)) ++ newAnnotations.flatMap( + _.toFirrtl.flatMap(_.update(renames)) + ) + + def copy( + name: String = name, + components: Seq[Component] = components, + annotations: Seq[ChiselAnnotation] = annotations, + renames: RenameMap = renames + ) = Circuit(name, components, annotations, renames, newAnnotations) + +} +object Circuit + extends scala.runtime.AbstractFunction4[String, Seq[Component], Seq[ChiselAnnotation], RenameMap, Circuit] { + def unapply(c: Circuit): Option[(String, Seq[Component], Seq[ChiselAnnotation], RenameMap)] = { + Some((c.name, c.components, c.annotations, c.renames)) + } + + def apply(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation], renames: RenameMap): Circuit = + new Circuit(name, components, annotations, renames) } diff --git a/core/src/main/scala/chisel3/package.scala b/core/src/main/scala/chisel3/package.scala index bd088e21..87024683 100644 --- a/core/src/main/scala/chisel3/package.scala +++ b/core/src/main/scala/chisel3/package.scala @@ -1,10 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 import chisel3.internal.firrtl.BinaryPoint +import java.util.{MissingFormatArgumentException, UnknownFormatConversionException} +import scala.collection.mutable /** This package contains the main chisel3 API. */ package object chisel3 { + import internal.chiselRuntimeDeprecated + import internal.sourceinfo.DeprecatedSourceInfo import internal.firrtl.{Port, Width} import internal.Builder @@ -37,13 +41,11 @@ package object chisel3 { case bigint => Builder.error(s"Cannot convert $bigint to Bool, must be 0 or 1"); Bool.Lit(false) } - /** Int to UInt conversion, recommended style for constants. - */ - def U: UInt = UInt.Lit(bigint, Width()) + /** Int to UInt conversion, recommended style for constants. */ + def U: UInt = UInt.Lit(bigint, Width()) // scalastyle:ignore method.name - /** Int to SInt conversion, recommended style for constants. - */ - def S: SInt = SInt.Lit(bigint, Width()) + /** Int to SInt conversion, recommended style for constants. */ + def S: SInt = SInt.Lit(bigint, Width()) // scalastyle:ignore method.name /** Int to UInt conversion with specified width, recommended style for constants. */ @@ -210,29 +212,142 @@ package object chisel3 { implicit class PrintableHelper(val sc: StringContext) extends AnyVal { /** Custom string interpolator for generating Printables: p"..." - * Will call .toString on any non-Printable arguments (mimicking s"...") + * mimicks s"..." for non-Printable data) */ def p(args: Any*): Printable = { - sc.checkLengths(args) // Enforce sc.parts.size == pargs.size + 1 - val pargs: Seq[Option[Printable]] = args.map { - case p: Printable => Some(p) - case d: Data => Some(d.toPrintable) - case any => - for { - v <- Option(any) // Handle null inputs - str = v.toString - if !str.isEmpty // Handle empty Strings - } yield PString(str) + // P interpolator does not treat % differently - hence need to add % before sending to cf. + val t = sc.parts.map(_.replaceAll("%", "%%")) + StringContext(t: _*).cf(args: _*) + } + + /** Custom string interpolator for generating formatted Printables : cf"..." + * + * Enhanced version of scala's `f` interpolator. + * Each expression (argument) referenced within the string is + * converted to a particular Printable depending + * on the format specifier and type. + * + * ==== For Chisel types referenced within the String ==== + * + * - <code>%n</code> - Returns [[Name]] Printable. + * - <code>%N</code> - Returns [[FullName]] Printable. + * - <code>%b,%d,%x,%c</code> - Only applicable for types of [[Bits]] or dreived from it. - returns ([[Binary]],[[Decimal]], + * [[Hexadecimal]],[[Character]]) Printable respectively. + * - Default - If no specifier given call [[Data.toPrintable]] on the Chisel Type. + * + * ==== For [[Printable]] type: ==== + * No explicit format specifier supported - just return the Printable. + * + * ==== For regular scala types ==== + * Call String.format with the argument and specifier. + * Default is %s if no specifier is given. + * Wrap the result in [[PString]] Printable. + * + * ==== For the parts of the StringContext ==== + * Remove format specifiers and if literal percents (need to be escaped with %) + * are present convert them into [[Percent]] Printable. + * Rest of the string will be wrapped in [[PString]] Printable. + * + * @example + * {{{ + * + * val w1 = 20.U // Chisel UInt type (which extends Bits) + * val f1 = 30.2 // Scala float type. + * val pable = cf"w1 = $w1%x f1 = $f1%2.2f. This is 100%% clear" + * + * // pable is as follows + * // Printables(List(PString(w1 = ), Hexadecimal(UInt<5>(20)), PString( f1 = ), PString(30.20), PString(. This is 100), Percent, PString( clear))) + * }}} + * + * @throws UnknownFormatConversionException + * if literal percent not escaped with % or if the format specifier is not supported + * for the specific type + * + * @throws StringContext.InvalidEscapeException + * if a `parts` string contains a backslash (`\`) character + * that does not start a valid escape sequence. + * + * @throws IllegalArgumentException + * if the number of `parts` in the enclosing `StringContext` does not exceed + * the number of arguments `arg` by exactly 1. + */ + def cf(args: Any*): Printable = { + + // Handle literal % + // Takes the part string - + // - this is assumed to not have any format specifiers - already handled / removed before calling this function. + // Only thing present is literal % if any which should ideally be with %%. + // If not - then flag an error. + // Return seq of Printables (either PString or Percent or both - nothing else + def percentSplitter(s: String): Seq[Printable] = { + if (s.isEmpty) Seq(PString("")) + else { + val pieces = s.split("%%").toList.flatMap { p => + if (p.contains('%')) throw new UnknownFormatConversionException("Un-escaped % found") + // Wrap in PString and intersperse the escaped percentages + Seq(Percent, PString(p)) + } + if (pieces.isEmpty) Seq(Percent) + else pieces.tail // Don't forget to drop the extra percent we put at the beginning + } } + + def extractFormatSpecifier(part: String): (Option[String], String) = { + // Check if part starts with a format specifier (with % - disambiguate with literal % checking the next character if needed to be %) + // In the case of %f specifier there is a chance that we need more information - so capture till the 1st letter (a-zA-Z). + // Example cf"This is $val%2.2f here" - parts - Seq("This is ","%2.2f here") - the format specifier here is %2.2f. + val endFmtIdx = + if (part.length > 1 && part(0) == '%' && part(1) != '%') part.indexWhere(_.isLetter) + else -1 + val (fmt, rest) = part.splitAt(endFmtIdx + 1) + + val fmtOpt = if (fmt.nonEmpty) Some(fmt) else None + (fmtOpt, rest) + + } + + sc.checkLengths(args) // Enforce sc.parts.size == pargs.size + 1 val parts = sc.parts.map(StringContext.treatEscapes) - // Zip sc.parts and pargs together ito flat Seq - // eg. Seq(sc.parts(0), pargs(0), sc.parts(1), pargs(1), ...) - val seq = for { // append None because sc.parts.size == pargs.size + 1 - (literal, arg) <- parts.zip(pargs :+ None) - optPable <- Seq(Some(PString(literal)), arg) - pable <- optPable // Remove Option[_] - } yield pable - Printables(seq) + // The 1st part is assumed never to contain a format specifier. + // If the 1st part of a string is an argument - then the 1st part will be an empty String. + // So we need to parse parts following the 1st one to get the format specifiers if any + val partsAfterFirst = parts.tail + + // Align parts to their potential specifiers + val pables = partsAfterFirst.zip(args).flatMap { + case (part, arg) => { + val (fmt, modP) = extractFormatSpecifier(part) + val fmtArg: Printable = arg match { + case d: Data => { + fmt match { + case Some("%n") => Name(d) + case Some("%N") => FullName(d) + case Some(fForm) if d.isInstanceOf[Bits] => FirrtlFormat(fForm.substring(1, 2), d) + case Some(x) => { + val msg = s"Illegal format specifier '$x' for Chisel Data type!\n" + throw new UnknownFormatConversionException(msg) + } + case None => d.toPrintable + } + } + case p: Printable => { + fmt match { + case Some(x) => { + val msg = s"Illegal format specifier '$x' for Chisel Printable type!\n" + throw new UnknownFormatConversionException(msg) + } + case None => p + } + } + + // Generic case - use String.format (for example %d,%2.2f etc on regular Scala types) + case t => PString(fmt.getOrElse("%s").format(t)) + + } + Seq(fmtArg) ++ percentSplitter(modP) + } + } + Printables(percentSplitter(parts.head) ++ pables) } } |
