Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions core/src/main/scala-2/chisel3/choice/DynamicGroupIntf.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: Apache-2.0

package chisel3.choice

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context

private[chisel3] trait DynamicGroupFactoryIntf {
implicit def materializeDynamicGroupFactory[T <: DynamicGroup]: DynamicGroup.Factory[T] =
macro DynamicGroupMacros.materializeFactory[T]
}

private[chisel3] object DynamicGroupMacros {
def materializeFactory[T <: DynamicGroup: c.WeakTypeTag](c: Context): c.Tree = {
import c.universe._

val dynamicGroupTpe = weakTypeOf[DynamicGroup]
val caseTpe = weakTypeOf[Case]
val sourceInfoTpe = weakTypeOf[chisel3.experimental.SourceInfo]
val targetTpe = weakTypeOf[T]
val targetSym = targetTpe.typeSymbol

if (!targetSym.isClass || !targetSym.asClass.isTrait) {
c.abort(
c.enclosingPosition,
s"DynamicGroup can only be materialized for traits, got: ${targetTpe.typeSymbol.fullName}"
)
}
if (!(targetTpe <:< dynamicGroupTpe)) {
c.abort(c.enclosingPosition, s"${targetTpe.typeSymbol.fullName} must extend chisel3.choice.DynamicGroup")
}

val caseNames = targetTpe.decls.toList.collect {
case module: ModuleSymbol if module.typeSignature <:< caseTpe =>
module.name.decodedName.toString.trim
}.reverse

if (caseNames.isEmpty) {
c.abort(c.enclosingPosition, s"${targetSym.fullName} must declare at least one `object ... extends Case`")
}

val caseNameTrees = caseNames.map(name => Literal(Constant(name)))

q"""
new _root_.chisel3.choice.DynamicGroup.Factory[$targetTpe] {
override val caseNames: _root_.scala.Seq[String] = _root_.scala.Seq(..$caseNameTrees)
override def create()(implicit sourceInfo: $sourceInfoTpe): $targetTpe =
new $targetTpe {}
}
"""
}
}
5 changes: 5 additions & 0 deletions core/src/main/scala-3/chisel3/choice/DynamicGroupIntf.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: Apache-2.0

package chisel3.choice

private[chisel3] trait DynamicGroupFactoryIntf
84 changes: 84 additions & 0 deletions core/src/main/scala/chisel3/choice/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package chisel3

import chisel3.experimental.{BaseModule, SourceInfo}
import chisel3.util.simpleClassName
import chisel3.internal.Builder

/** This package contains Chisel language definitions for describing configuration options and their accepted values.
*/
Expand All @@ -30,6 +31,89 @@ package object choice {
final implicit def group: Group = this
}

/** Schema-style dynamic option group.
*
* This allows trait-based declarations of cases without requiring a singleton [[Group]] object.
*
* @example
* {{{
* trait PlatformType extends DynamicGroup {
* object FPGA extends Case
* object ASIC extends Case
* }
* }}}
*/
trait DynamicGroup {
private var initializedName: Option[String] = None
private var initializedCaseNames: Option[Seq[String]] = None
private var initializedSourceInfo: Option[SourceInfo] = None

private def groupName: String = initializedName.getOrElse(
throw new IllegalStateException("DynamicGroup was used before it was initialized")
)

private def caseNames: Seq[String] = initializedCaseNames.getOrElse(
throw new IllegalStateException("DynamicGroup was used before it was initialized")
)

private implicit def sourceInfo: SourceInfo = initializedSourceInfo.getOrElse(
throw new IllegalStateException("DynamicGroup was used before it was initialized")
)

private[chisel3] def initialize(name: String, caseNames: Seq[String], sourceInfo: SourceInfo): this.type = {
initializedName = Some(name)
initializedCaseNames = Some(caseNames.toVector)
initializedSourceInfo = Some(sourceInfo)
this
}

private lazy val cachedGroup = Builder.getOrCreateDynamicGroup(
groupName,
caseNames.toVector,
DynamicGroup.groupFactory(groupName)
)

final implicit def group: Group = cachedGroup
}
object DynamicGroup {
private[chisel3] def groupFactory(groupName: String)(implicit sourceInfo: SourceInfo): () => Group = () => {
object DynamicGroupSingleton extends Group()(sourceInfo) {
override private[chisel3] def name = groupName
}
DynamicGroupSingleton
}

/** Typeclass for constructing trait-based [[DynamicGroup]] instances.
*/
trait Factory[T <: DynamicGroup] {
def caseNames: Seq[String]
def create()(implicit sourceInfo: SourceInfo): T
}
object Factory extends DynamicGroupFactoryIntf

/** Create a trait-based [[DynamicGroup]] using an implicit factory.
*
* @tparam T The trait type that defines the case structure
* @param groupName The name of the group
* @param sourceInfo Source location information
* @return An instance of T that provides type-safe access to the cases
*
* @example
* {{{
* trait PlatformType extends DynamicGroup {
* object FPGA extends Case
* object ASIC extends Case
* }
* val platform = DynamicGroup[PlatformType]("Platform")
* }}}
*/
def apply[T <: DynamicGroup](groupName: String)(implicit factory: Factory[T], sourceInfo: SourceInfo): T = {
val instance = factory.create().initialize(groupName, factory.caseNames, sourceInfo)
instance.group
instance
}
}

/** An option case declaration.
*/
abstract class Case(implicit val group: Group, _sourceInfo: SourceInfo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ object Definition extends SourceInfoDoc {
Builder.components ++= ir._circuit.components
Builder.annotations ++= ir._circuit.annotations
Builder.layers ++= dynamicContext.layers
Builder.options ++= dynamicContext.options
Builder.addOptions(dynamicContext.options.values)
dynamicContext.definitions.foreach(Builder.addDefinition)
module._circuit = Builder.currentModule
module.toDefinition
Expand Down
43 changes: 39 additions & 4 deletions core/src/main/scala/chisel3/internal/Builder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -533,8 +533,10 @@ private[chisel3] class DynamicContext(
val components = ArrayBuffer[Component]()
val annotations = ArrayBuffer[() => Seq[Annotation]]()
val layers = mutable.LinkedHashSet[layer.Layer]()
val options = mutable.LinkedHashSet[choice.Case]()
val options = mutable.LinkedHashMap[(choice.Group, String), choice.Case]()
val domains = mutable.LinkedHashSet[domain.Domain]()
val dynamicGroupsByName = mutable.HashMap[String, (choice.Group, Seq[String])]()
val dynamicCasesByGroupAndName = mutable.HashMap[(choice.Group, String), choice.Case]()
var currentModule: Option[BaseModule] = None

// Views that do not correspond to a single ReferenceTarget and thus require renaming
Expand Down Expand Up @@ -611,8 +613,16 @@ private[chisel3] object Builder extends LazyLogging {
def annotations: ArrayBuffer[() => Seq[Annotation]] = dynamicContext.annotations

def layers: mutable.LinkedHashSet[layer.Layer] = dynamicContext.layers
def options: mutable.LinkedHashSet[choice.Case] = dynamicContext.options
def domains: mutable.LinkedHashSet[domain.Domain] = dynamicContext.domains
def options: mutable.LinkedHashMap[(choice.Group, String), choice.Case] = dynamicContext.options
def addOption(option: choice.Case): Unit = {
dynamicContext.options.getOrElseUpdate((option.group, option.name), option)
}
def addOptions(options: Iterable[choice.Case]): Unit = options.foreach(addOption)
def domains: mutable.LinkedHashSet[domain.Domain] = dynamicContext.domains

def dynamicGroupsByName: mutable.HashMap[String, (choice.Group, Seq[String])] = dynamicContext.dynamicGroupsByName
def dynamicCasesByGroupAndName: mutable.HashMap[(choice.Group, String), choice.Case] =
dynamicContext.dynamicCasesByGroupAndName

def contextCache: BuilderContextCache = dynamicContext.contextCache

Expand Down Expand Up @@ -864,6 +874,31 @@ private[chisel3] object Builder extends LazyLogging {

def elaborationTrace: ElaborationTrace = dynamicContext.elaborationTrace

def getOrCreateDynamicGroup(name: String, caseNames: Seq[String], groupFactory: () => choice.Group): choice.Group = {
if (!inContext) return groupFactory()

dynamicGroupsByName.get(name) match {
case Some((existingGroup, existingCaseNames)) =>
if (existingCaseNames != caseNames) {
throw new IllegalArgumentException(
s"DynamicGroup '$name' already exists with different case names.\n" +
s" Existing: ${existingCaseNames.mkString(", ")}\n" +
s" New: ${caseNames.mkString(", ")}"
)
}
existingGroup
case None =>
val newGroup = groupFactory()
dynamicGroupsByName(name) = (newGroup, caseNames)
newGroup
}
}

def getOrCreateDynamicCase(group: choice.Group, name: String, caseFactory: () => choice.Case): choice.Case = {
if (!inContext) return caseFactory()
dynamicCasesByGroupAndName.getOrElseUpdate((group, name), caseFactory())
}

def forcedClock: Clock = currentClock.getOrElse(
// TODO add implicit clock change to Builder.exception
throwException("Error: No implicit clock.")
Expand Down Expand Up @@ -1109,7 +1144,7 @@ private[chisel3] object Builder extends LazyLogging {
Layer(l.sourceInfo, l.name, config, children.map(foldLayers).toSeq, l)
}

val optionDefs = groupByIntoSeq(options)(opt => opt.group).map { case (optGroup, cases) =>
val optionDefs = groupByIntoSeq(options.values)(opt => opt.group).map { case (optGroup, cases) =>
DefOption(
optGroup.sourceInfo,
optGroup.name,
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/chisel3/choice/ModuleChoice.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ object ModuleChoice extends ModuleChoice$Intf {
if (!instModule.io.typeEquivalent(instDefaultModule.io)) {
Builder.error("Error: choice module IO bundles are not type equivalent")
}
Builder.options += choice
Builder.addOption(choice)
(choice, instModule)
}

groupByIntoSeq(choiceModules.map(_._1))(opt => opt).foreach { case (_, group) =>
groupByIntoSeq(choiceModules.map(_._1))(opt => (opt.group, opt.name)).foreach { case (_, group) =>
if (group.size != 1) {
throw new IllegalArgumentException(s"Error: duplicate case '${group.head.name}'")
}
Expand Down
Loading
Loading