diff --git a/core/src/main/scala/chisel3/choice/package.scala b/core/src/main/scala/chisel3/choice/package.scala index 72c7672c75d..e3a0c59dd27 100644 --- a/core/src/main/scala/chisel3/choice/package.scala +++ b/core/src/main/scala/chisel3/choice/package.scala @@ -3,8 +3,13 @@ package chisel3 import chisel3.experimental.{BaseModule, SourceInfo} +import chisel3.internal.Builder import chisel3.util.simpleClassName +import scala.collection.mutable +import scala.reflect.runtime.universe._ +import scala.reflect.runtime.{currentMirror => cm} + /** This package contains Chisel language definitions for describing configuration options and their accepted values. */ package object choice { @@ -13,7 +18,7 @@ package object choice { * * @example * {{{ - * import chisel3.option.{Group, Case} + * import chisel3.choice.{Group, Case} * object Platform extends Group { * object FPGA extends Case * object ASIC extends Case @@ -23,6 +28,22 @@ package object choice { abstract class Group(implicit _sourceInfo: SourceInfo) { self: Singleton => + private[choice] def registerCases(): Unit = { + // Grab a symbol for the derived class (a concrete Group) + val instanceMirror = cm.reflect(this) + val symbol = instanceMirror.symbol + + symbol.typeSignature.members.collect { + // Look only for inner objects in the Group. Note, this is not recursive. + case m: ModuleSymbol if m.isStatic => + val instance = cm.reflectModule(m.asModule).instance + // Confirms the instance is a subtype of Case + if (cm.classSymbol(instance.getClass).toType <:< typeOf[Case]) { + Builder.options += instance.asInstanceOf[Case] + } + } + } + private[chisel3] def sourceInfo: SourceInfo = _sourceInfo private[chisel3] def name: String = simpleClassName(this.getClass()) @@ -47,4 +68,14 @@ package object choice { */ def ->[T](module: => T): (Case, () => T) = (this, () => module) } + + /** Registers all options in a group with the Builder. + * This lets Chisel know that this layer should be emitted into FIRRTL text. + * + * This API can be used to guarantee that a design will always have certain + * group defined. This is analagous in spirit to [[layer.addLayer]]. + */ + def addGroup(group: Group): Unit = { + group.registerCases() + } } diff --git a/src/test/scala/chiselTests/ModuleChoiceSpec.scala b/src/test/scala/chiselTests/ModuleChoiceSpec.scala index 86da88ede1a..91c1b0863fb 100644 --- a/src/test/scala/chiselTests/ModuleChoiceSpec.scala +++ b/src/test/scala/chiselTests/ModuleChoiceSpec.scala @@ -3,7 +3,7 @@ package chiselTests import chisel3._ -import chisel3.choice.{Case, Group, ModuleChoice} +import chisel3.choice.{addGroup, Case, Group, ModuleChoice} import chiselTests.{ChiselFlatSpec, MatchesAndOmits, Utils} import _root_.circt.stage.ChiselStage @@ -23,7 +23,7 @@ class ASICTarget extends FixedIOExtModule[TargetIO](new TargetIO(8)) class VerifTarget extends FixedIORawModule[TargetIO](new TargetIO(8)) -class ModuleChoiceSpec extends ChiselFlatSpec with Utils with MatchesAndOmits { +class ModuleChoiceSpec extends ChiselFlatSpec with Utils with FileCheck { it should "emit options and cases" in { class ModuleWithChoice extends Module { val out = IO(UInt(8.W)) @@ -35,17 +35,14 @@ class ModuleChoiceSpec extends ChiselFlatSpec with Utils with MatchesAndOmits { out := inst.out } - val chirrtl = ChiselStage.emitCHIRRTL(new ModuleWithChoice, Array("--full-stacktrace")) - - info("CHIRRTL emission looks correct") - matchesAndOmits(chirrtl)( - "option Platform :", - "FPGA", - "ASIC", - "instchoice inst of VerifTarget, Platform :", - "FPGA => FPGATarget", - "ASIC => ASICTarget" - )() + generateFirrtlAndFileCheck(new ModuleWithChoice)( + """|CHECK: option Platform : + |CHECK-NEXT: FPGA + |CHECK-NEXT: ASIC + |CHECK: instchoice inst of VerifTarget, Platform : + |CHECK-NEXT: FPGA => FPGATarget + |CHECK-NEXT: ASIC => ASICTarget""".stripMargin + ) } it should "require that all cases are part of the same option" in { @@ -129,3 +126,20 @@ class ModuleChoiceSpec extends ChiselFlatSpec with Utils with MatchesAndOmits { } } +class AddGroupSpec extends ChiselFlatSpec with Utils with FileCheck { + it should "emit options for a registered group even if there are no consumers" in { + class ModuleWithoutChoice extends Module { + addGroup(Platform) + val out = IO(UInt(8.W)) + val in = IO(UInt(8.W)) + out := in + } + + generateFirrtlAndFileCheck(new ModuleWithoutChoice)( + """|CHECK: option Platform : + |CHECK-NEXT: ASIC @[ + |CHECK-NEXT: FPGA @[ + |""".stripMargin + ) + } +}