Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
35 changes: 35 additions & 0 deletions core/src/main/scala/chisel3/choice/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,41 @@ package object choice {
final implicit def group: Group = this
}

/** Dynamic option group that accepts a name and case names as runtime parameters.
* @example {{{ val platform = new DynamicGroup("Platform", Seq("FPGA", "ASIC")) }}}
*/
class DynamicGroup(val groupName: String, caseNames: Seq[String])(implicit _sourceInfo: SourceInfo) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love it due to the complexity, but this should probably implement the val naming with a suggestName method like other things in Chisel.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I want to disallow that level of dynamic behavior. The group name is essentially user/designer facing API even when they are generated dynamically.

import chisel3.internal.Builder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this is fine to include up top.


private[chisel3] def sourceInfo: SourceInfo = _sourceInfo
private[chisel3] def name: String = groupName

private val _group: Group = Builder.getOrCreateDynamicGroup(groupName, caseNames, () => {
object DynamicGroupSingleton extends Group()(_sourceInfo) {
override private[chisel3] def name = groupName
}
DynamicGroupSingleton
})

final implicit def group: Group = _group

private val _cases: Map[String, Case] = caseNames.map { caseName =>
caseName -> Builder.getOrCreateDynamicCase(_group, caseName, () => {
object DynamicCaseSingleton extends Case()(_group, _sourceInfo) {
override private[chisel3] def name = caseName
}
DynamicCaseSingleton
})
}.toMap

def cases: Map[String, Case] = _cases

def apply(caseName: String): Case = _cases.getOrElse(
caseName,
throw new NoSuchElementException(s"Case '$caseName' not found in group '$groupName'. Available cases: ${_cases.keys.mkString(", ")}")
)
}

/** An option case declaration.
*/
abstract class Case(implicit val group: Group, _sourceInfo: SourceInfo) {
Expand Down
30 changes: 30 additions & 0 deletions core/src/main/scala/chisel3/internal/Builder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,8 @@ private[chisel3] class DynamicContext(
val layers = mutable.LinkedHashSet[layer.Layer]()
val options = mutable.LinkedHashSet[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 @@ -614,6 +616,9 @@ private[chisel3] object Builder extends LazyLogging {
def options: mutable.LinkedHashSet[choice.Case] = dynamicContext.options
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

def annotationSeq: AnnotationSeq = dynamicContext.annotationSeq
Expand Down Expand Up @@ -864,6 +869,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
109 changes: 109 additions & 0 deletions src/test/scala-2/chiselTests/DynamicGroupSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: Apache-2.0

package chiselTests

import chisel3._
import chisel3.choice.{Case, DynamicGroup, Group, ModuleChoice}
import chisel3.testing.scalatest.FileCheck
import circt.stage.ChiselStage
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class DynamicGroupSpec extends AnyFlatSpec with Matchers with FileCheck {

class TargetIO(width: Int) extends Bundle {
val in = Flipped(UInt(width.W))
val out = UInt(width.W)
}

class FPGATarget extends FixedIORawModule[TargetIO](new TargetIO(8)) {
io.out := io.in
}

class ASICTarget extends FixedIOExtModule[TargetIO](new TargetIO(8))

class VerifTarget extends FixedIORawModule[TargetIO](new TargetIO(8)) {
io.out := io.in
}

it should "emit options and cases with DynamicGroup" in {
val platform = new DynamicGroup("Platform", Seq("FPGA", "ASIC"))

class ModuleWithDynamicChoice extends Module {
val inst = ModuleChoice(new VerifTarget)(Seq(
platform("FPGA") -> new FPGATarget,
platform("ASIC") -> new ASICTarget
))
val io = IO(inst.cloneType)
io <> inst
}

ChiselStage
.emitCHIRRTL(new ModuleWithDynamicChoice)
.fileCheck()(
"""|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 "allow same DynamicGroup name to be reused" in {
class ModuleWithReusedGroup extends Module {
val platform1 = new DynamicGroup("Platform", Seq("FPGA", "ASIC"))
val platform2 = new DynamicGroup("Platform", Seq("FPGA", "ASIC")) // Should share the same group

val inst1 = ModuleChoice(new VerifTarget)(Seq(platform1("FPGA") -> new FPGATarget))
val inst2 = ModuleChoice(new VerifTarget)(Seq(platform2("ASIC") -> new ASICTarget))
val io1 = IO(inst1.cloneType)
val io2 = IO(inst2.cloneType)
io1 <> inst1
io2 <> inst2
}

ChiselStage
.emitCHIRRTL(new ModuleWithReusedGroup)
.fileCheck()(
"""|CHECK: option Platform :
|CHECK-NEXT: FPGA
|CHECK-NEXT: ASIC
|CHECK-NOT: option Platform :
""".stripMargin
)

}

it should "reject DynamicGroup with same name but different cases" in {
class ModuleWithMismatchedCases extends Module {
val platform1 = new DynamicGroup("Platform", Seq("FPGA", "ASIC"))
val platform2 = new DynamicGroup("Platform", Seq("FPGA", "GPU"))
}

val exception = intercept[IllegalArgumentException] {
ChiselStage.emitCHIRRTL(new ModuleWithMismatchedCases)
}

exception.getMessage should include("DynamicGroup 'Platform' already exists with different case names")
exception.getMessage should include("FPGA")
exception.getMessage should include("ASIC")
exception.getMessage should include("GPU")
}

it should "reject DynamicGroup with same cases but different order" in {
class ModuleWithDifferentOrder extends Module {
val platform1 = new DynamicGroup("Platform", Seq("FPGA", "ASIC"))
val platform2 = new DynamicGroup("Platform", Seq("ASIC", "FPGA"))
}

val exception = intercept[IllegalArgumentException] {
ChiselStage.emitCHIRRTL(new ModuleWithDifferentOrder)
}

exception.getMessage should include("DynamicGroup 'Platform' already exists with different case names")
exception.getMessage should include("FPGA")
exception.getMessage should include("ASIC")
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package chiselTests.simulator

import chisel3._
import chisel3.choice.{Case, Group, ModuleChoice}
import chisel3.choice.{Case, Group, ModuleChoice, DynamicGroup}
import chisel3.simulator.{InstanceChoiceControl, Settings}
import chisel3.simulator.InstanceChoiceControl.SpecializationTime
import chisel3.simulator.scalatest.ChiselSim
Expand All @@ -16,10 +16,6 @@ object Platform extends Group {
object ASIC extends Case
}

object Opt extends Group {
object Fast extends Case
}

class TargetIO extends Bundle {
val out = Output(UInt(8.W))
}
Expand Down Expand Up @@ -47,9 +43,11 @@ class ModuleChoiceTestModule extends Module {

out1 := choiceOut1.out

// Use a dynamic group
val group = new DynamicGroup("Opt", Seq("Fast"))
val choiceOut2 = ModuleChoice(new Return0)(
Seq(
Opt.Fast -> new Return1
group("Fast") -> new Return1
)
)

Expand Down
Loading