From 3f3118c344fa4b5b1f6825340180565645928796 Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Mon, 29 Sep 2025 08:36:23 +0200 Subject: [PATCH 01/11] add convenience function to return all enum values together with their names --- core/src/main/scala/chisel3/ChiselEnum.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/chisel3/ChiselEnum.scala b/core/src/main/scala/chisel3/ChiselEnum.scala index f2cbc684072..757c80aec9a 100644 --- a/core/src/main/scala/chisel3/ChiselEnum.scala +++ b/core/src/main/scala/chisel3/ChiselEnum.scala @@ -229,10 +229,15 @@ abstract class ChiselEnum extends ChiselEnumIntf { def getWidth: Int = width.get + /** All Enum values */ def all: Seq[Type] = enumInstances - /* Accessor for Seq of names in enumRecords */ + + /** All Enum names */ def allNames: Seq[String] = enumNames + /** All Enum values with their names */ + def allWithNames: Seq[(Type, String)] = all.zip(allNames) + private[chisel3] def nameOfValue(id: BigInt): Option[String] = { enumRecords.find(_.inst.litValue == id).map(_.name) } From 833a2cf2fab23ccf45a9b2c5ab945ea2cc0b8a72 Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Mon, 29 Sep 2025 08:40:29 +0200 Subject: [PATCH 02/11] Define contains method on ChiselEnum to figure out if enum value is one of the values that contains a specific substring --- core/src/main/scala/chisel3/ChiselEnum.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/main/scala/chisel3/ChiselEnum.scala b/core/src/main/scala/chisel3/ChiselEnum.scala index 757c80aec9a..ab3863ed303 100644 --- a/core/src/main/scala/chisel3/ChiselEnum.scala +++ b/core/src/main/scala/chisel3/ChiselEnum.scala @@ -118,6 +118,13 @@ abstract class EnumType(private[chisel3] val factory: ChiselEnum) extends Elemen implicit sourceInfo: SourceInfo ): Bool = isOneOf(u1 +: u2.toSeq) + /** Creates circuitry that outputs True iff the Enum is equal to one of the values that has `s` in its name + * + * @param s the substring to search for in the Enum value's name + */ + def contains(s: String)(implicit sourceInfo: SourceInfo): Bool = + isOneOf(factory.allWithNames.filter(m => m._2 contains s).map(m => m._1)) + def next(implicit sourceInfo: SourceInfo): this.type = { if (litOption.isDefined) { val index = factory.all.indexOf(this) From 4e90d392ce7237db027c004378da091081e22d95 Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Mon, 29 Sep 2025 08:43:32 +0200 Subject: [PATCH 03/11] Allow enum value to be specified as string. Useful if values specified in external file instead of hardcoded inside chisel source code --- core/src/main/scala/chisel3/ChiselEnum.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/scala/chisel3/ChiselEnum.scala b/core/src/main/scala/chisel3/ChiselEnum.scala index ab3863ed303..f72e9317ae0 100644 --- a/core/src/main/scala/chisel3/ChiselEnum.scala +++ b/core/src/main/scala/chisel3/ChiselEnum.scala @@ -282,6 +282,11 @@ abstract class ChiselEnum extends ChiselEnumIntf { def apply(): Type = new Type + /** Return the Enum value of which the name exactly matches the specified String. If @name does not match a valid Enum, fail */ + def apply(name: String): Type = + allWithNames.collectFirst { case (enumValue, enumName) if enumName == name => enumValue } + .getOrElse(throwException(s"Enum value $name is not defined")) + private def castImpl( n: UInt, warn: Boolean From 8fb908fdb2664405a631d0cd9184918e060cbaae Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Mon, 29 Sep 2025 11:06:08 +0200 Subject: [PATCH 04/11] change toString method on ChiselEnum to generate a list of all values with their names --- core/src/main/scala/chisel3/ChiselEnum.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/scala/chisel3/ChiselEnum.scala b/core/src/main/scala/chisel3/ChiselEnum.scala index f72e9317ae0..fdf447b463c 100644 --- a/core/src/main/scala/chisel3/ChiselEnum.scala +++ b/core/src/main/scala/chisel3/ChiselEnum.scala @@ -245,6 +245,9 @@ abstract class ChiselEnum extends ChiselEnumIntf { /** All Enum values with their names */ def allWithNames: Seq[(Type, String)] = all.zip(allNames) + /** All Enum values with their names, printed one per line. Compatible with gtkwave filter file */ + override def toString(): String = allWithNames.map(e => s"${e._1.litValue} ${e._2}").mkString("", "\n", "\n") + private[chisel3] def nameOfValue(id: BigInt): Option[String] = { enumRecords.find(_.inst.litValue == id).map(_.name) } From 962a5e08f15d0ef32c3678b612a3cdbebb3a2fa3 Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Mon, 29 Sep 2025 14:35:46 +0200 Subject: [PATCH 05/11] add scala tests for ChiselEnum string methods --- src/test/scala-2/chiselTests/ChiselEnum.scala | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/test/scala-2/chiselTests/ChiselEnum.scala b/src/test/scala-2/chiselTests/ChiselEnum.scala index b9d416f7564..cc3fb9cd5ea 100644 --- a/src/test/scala-2/chiselTests/ChiselEnum.scala +++ b/src/test/scala-2/chiselTests/ChiselEnum.scala @@ -68,6 +68,16 @@ class CastFromLit(in: UInt) extends Module { io.valid := io.out.isValid } +class CastFromStringLit(in: String) extends Module { + val io = IO(new Bundle { + val out = Output(EnumExample()) + val valid = Output(Bool()) + }) + + io.out := EnumExample(in) + io.valid := io.out.isValid +} + class CastFromNonLit extends Module { val io = IO(new Bundle { val in = Input(UInt(EnumExample.getWidth.W)) @@ -204,6 +214,11 @@ class CastFromLitTester extends Module { assert(mod.io.out === enumVal) assert(mod.io.valid === true.B) } + for ((enumVal, enumName) <- EnumExample.allWithNames) { + val mod = Module(new CastFromStringLit(enumName)) + assert(mod.io.out === enumVal) + assert(mod.io.valid === true.B) + } stop() } @@ -254,6 +269,10 @@ class CastToInvalidEnumTester extends Module { Module(new CastFromLit(invalid_value)) } +class CastStringToInvalidEnumTester extends Module { + Module(new CastFromStringLit("nonExistingEnumValue")) +} + class EnumOpsTester extends Module { for { x <- EnumExample.all @@ -375,6 +394,22 @@ class IsOneOfTester extends Module { assert(!e0.isOneOf(e1)) assert(!e2.isOneOf(e101)) + // contains-a-string method + assert(e100 contains "10") + assert(e101 contains "10") + assert(!(e0 contains "e1")) + assert(!(e0 contains "noMatchAnywhere")) + + // every enum value contains the empty string + assert(EnumExample.all.map(_ contains "").reduce(_ && _)) + // every enum value contains its own full name + assert(EnumExample.allWithNames.map(m => m._1 contains m._2).reduce(_ && _)) + + // check enum value specified as string + assert(OtherEnum.otherEnum == OtherEnum("otherEnum")) + assert(OtherEnum.otherEnum === OtherEnum("otherEnum")) + assert(EnumExample.allWithNames.map(m => EnumExample(m._2) === m._1).reduce(_ && _)) + stop() } @@ -467,7 +502,7 @@ class ChiselEnumSpec extends AnyFlatSpec with Matchers with LogUtils with Chisel verilog should include("assign out3 = 8'h81;") } - it should "cast literal UInts to enums correctly" in { + it should "cast literal UInts and Strings to enums correctly" in { simulate(new CastFromLitTester)(RunUntilFinished(3)) } @@ -483,6 +518,9 @@ class ChiselEnumSpec extends AnyFlatSpec with Matchers with LogUtils with Chisel intercept[ChiselException] { ChiselStage.emitCHIRRTL(new CastToInvalidEnumTester) } + intercept[ChiselException] { + ChiselStage.emitCHIRRTL(new CastStringToInvalidEnumTester) + } } it should "only allow non-literal casts to enums if the width is smaller than or equal to the enum width" in { From 2757afc7cca263f535648897310b3d7d5081d679 Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Mon, 29 Sep 2025 16:21:28 +0200 Subject: [PATCH 06/11] fix failing test. Need to allow isOneOf on an empty Seq --- core/src/main/scala/chisel3/ChiselEnum.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/chisel3/ChiselEnum.scala b/core/src/main/scala/chisel3/ChiselEnum.scala index fdf447b463c..64d6684fa4e 100644 --- a/core/src/main/scala/chisel3/ChiselEnum.scala +++ b/core/src/main/scala/chisel3/ChiselEnum.scala @@ -102,7 +102,10 @@ abstract class EnumType(private[chisel3] val factory: ChiselEnum) extends Elemen * @return a hardware [[Bool]] that indicates if this value matches any of the given values */ final def isOneOf(s: Seq[EnumType])(implicit sourceInfo: SourceInfo): Bool = { - VecInit(s.map(this === _)).asUInt.orR + s.length match { + case 0 => false.B + case _ => VecInit(s.map(this === _)).asUInt.orR + } } /** Test if this enumeration is equal to any of the values given as arguments From 847a0864f5158429f87264a8c7202eb60c982cf3 Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Wed, 1 Oct 2025 08:54:20 +0200 Subject: [PATCH 07/11] better method name: change ChiselEnum.contains(String) to ChiselEnum.nameContains(String) --- core/src/main/scala/chisel3/ChiselEnum.scala | 2 +- src/test/scala-2/chiselTests/ChiselEnum.scala | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/chisel3/ChiselEnum.scala b/core/src/main/scala/chisel3/ChiselEnum.scala index 64d6684fa4e..e081133e817 100644 --- a/core/src/main/scala/chisel3/ChiselEnum.scala +++ b/core/src/main/scala/chisel3/ChiselEnum.scala @@ -125,7 +125,7 @@ abstract class EnumType(private[chisel3] val factory: ChiselEnum) extends Elemen * * @param s the substring to search for in the Enum value's name */ - def contains(s: String)(implicit sourceInfo: SourceInfo): Bool = + def nameContains(s: String)(implicit sourceInfo: SourceInfo): Bool = isOneOf(factory.allWithNames.filter(m => m._2 contains s).map(m => m._1)) def next(implicit sourceInfo: SourceInfo): this.type = { diff --git a/src/test/scala-2/chiselTests/ChiselEnum.scala b/src/test/scala-2/chiselTests/ChiselEnum.scala index cc3fb9cd5ea..ce815c7dc38 100644 --- a/src/test/scala-2/chiselTests/ChiselEnum.scala +++ b/src/test/scala-2/chiselTests/ChiselEnum.scala @@ -394,16 +394,16 @@ class IsOneOfTester extends Module { assert(!e0.isOneOf(e1)) assert(!e2.isOneOf(e101)) - // contains-a-string method - assert(e100 contains "10") - assert(e101 contains "10") - assert(!(e0 contains "e1")) - assert(!(e0 contains "noMatchAnywhere")) + // enum-name-contains-a-string method + assert(e100 nameContains "10") + assert(e101 nameContains "10") + assert(!(e0 nameContains "e1")) + assert(!(e0 nameContains "noMatchAnywhere")) // every enum value contains the empty string - assert(EnumExample.all.map(_ contains "").reduce(_ && _)) + assert(EnumExample.all.map(_ nameContains "").reduce(_ && _)) // every enum value contains its own full name - assert(EnumExample.allWithNames.map(m => m._1 contains m._2).reduce(_ && _)) + assert(EnumExample.allWithNames.map(m => m._1 nameContains m._2).reduce(_ && _)) // check enum value specified as string assert(OtherEnum.otherEnum == OtherEnum("otherEnum")) From 398d9e96500f1a08f47ad626b493eeadbe93f8bd Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Wed, 1 Oct 2025 11:53:01 +0200 Subject: [PATCH 08/11] I really like the infix notation for nameContains. Scalafmt allows it for contains, so let's tolerate nameContains too. I don't want scalafmt to make the test code harder-to-read --- .scalafmt.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/.scalafmt.conf b/.scalafmt.conf index 75a46ff6a15..303917acd55 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -30,3 +30,4 @@ newlines.sometimesBeforeColonInMethodReturnType = true optIn.annotationNewlines = true rewrite.rules = [SortImports, PreferCurlyFors, AvoidInfix] +rewrite.avoidInfix.excludeFilters."+" = ["nameContains"] From e82231970854cda8ab85782963ee50618cd186fc Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Wed, 1 Oct 2025 10:49:14 +0200 Subject: [PATCH 09/11] change ChiselEnum's toString method to be more consistent with normal Scala toStrings --- core/src/main/scala/chisel3/ChiselEnum.scala | 8 ++++++-- src/test/scala-2/chiselTests/ChiselEnum.scala | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/chisel3/ChiselEnum.scala b/core/src/main/scala/chisel3/ChiselEnum.scala index e081133e817..a3f78f7711f 100644 --- a/core/src/main/scala/chisel3/ChiselEnum.scala +++ b/core/src/main/scala/chisel3/ChiselEnum.scala @@ -248,8 +248,12 @@ abstract class ChiselEnum extends ChiselEnumIntf { /** All Enum values with their names */ def allWithNames: Seq[(Type, String)] = all.zip(allNames) - /** All Enum values with their names, printed one per line. Compatible with gtkwave filter file */ - override def toString(): String = allWithNames.map(e => s"${e._1.litValue} ${e._2}").mkString("", "\n", "\n") + /** Print Enum type name, followed by with all Enum values with their names to a string like this: + * `Opcodes(add=0, sub=1, mul=2, div=3)` + */ + override def toString: String = + getClass.getSimpleName.init + + allWithNames.map(e => s"${e._2}=${e._1.litValue}").mkString("(", ", ", ")") private[chisel3] def nameOfValue(id: BigInt): Option[String] = { enumRecords.find(_.inst.litValue == id).map(_.name) diff --git a/src/test/scala-2/chiselTests/ChiselEnum.scala b/src/test/scala-2/chiselTests/ChiselEnum.scala index ce815c7dc38..2c55eaa46e4 100644 --- a/src/test/scala-2/chiselTests/ChiselEnum.scala +++ b/src/test/scala-2/chiselTests/ChiselEnum.scala @@ -575,6 +575,10 @@ class ChiselEnumSpec extends AnyFlatSpec with Matchers with LogUtils with Chisel "object UnnamedEnum extends ChiselEnum { Value }" shouldNot compile } + it should "dump enum name=value pairs as string" in { + s"$EnumExample" should be("EnumExample(e0=0, e1=1, e2=2, e100=100, e101=101)") + } + "ChiselEnum FSM" should "work" in { simulate(new ChiselEnumFSMTester)(RunUntilFinished(11)) } From 4b830f9c0c987f838eb4fbe0bfeff772b6fbf8c1 Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Wed, 1 Oct 2025 11:10:06 +0200 Subject: [PATCH 10/11] asTable function to print map of value->name as string that can be parsed by external programs --- core/src/main/scala/chisel3/ChiselEnum.scala | 7 +++++++ src/test/scala-2/chiselTests/ChiselEnum.scala | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/core/src/main/scala/chisel3/ChiselEnum.scala b/core/src/main/scala/chisel3/ChiselEnum.scala index a3f78f7711f..fef2cce2d2c 100644 --- a/core/src/main/scala/chisel3/ChiselEnum.scala +++ b/core/src/main/scala/chisel3/ChiselEnum.scala @@ -255,6 +255,13 @@ abstract class ChiselEnum extends ChiselEnumIntf { getClass.getSimpleName.init + allWithNames.map(e => s"${e._2}=${e._1.litValue}").mkString("(", ", ", ")") + /** Return all Enum (value, name) combinations in a string, one per line. + * Intended for parsing by external tools, e.g. sim environment, or as gtkwave text filter + */ + def asTable: String = + "# " + getClass.getSimpleName.init + "\n" + // comment line, ignored by gtkwave + allWithNames.map(e => s"${e._1.litValue} ${e._2}").mkString("", "\n", "\n") + private[chisel3] def nameOfValue(id: BigInt): Option[String] = { enumRecords.find(_.inst.litValue == id).map(_.name) } diff --git a/src/test/scala-2/chiselTests/ChiselEnum.scala b/src/test/scala-2/chiselTests/ChiselEnum.scala index 2c55eaa46e4..3f39bfb9b9f 100644 --- a/src/test/scala-2/chiselTests/ChiselEnum.scala +++ b/src/test/scala-2/chiselTests/ChiselEnum.scala @@ -579,6 +579,16 @@ class ChiselEnumSpec extends AnyFlatSpec with Matchers with LogUtils with Chisel s"$EnumExample" should be("EnumExample(e0=0, e1=1, e2=2, e100=100, e101=101)") } + it should "dump enum mappings as machine-readable table, usable by gtkwave as plaintext filter" in { + EnumExample.asTable should be("""# EnumExample + |0 e0 + |1 e1 + |2 e2 + |100 e100 + |101 e101 + |""".stripMargin) + } + "ChiselEnum FSM" should "work" in { simulate(new ChiselEnumFSMTester)(RunUntilFinished(11)) } From 9b7bd93e4bede1f0f65071600a72341f7d0f3316 Mon Sep 17 00:00:00 2001 From: Maarten Boersma Date: Tue, 7 Oct 2025 08:41:44 +0200 Subject: [PATCH 11/11] Revert .scalafmt.conf exception for named function as infix operator and change test code accordingly --- .scalafmt.conf | 1 - src/test/scala-2/chiselTests/ChiselEnum.scala | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 303917acd55..75a46ff6a15 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -30,4 +30,3 @@ newlines.sometimesBeforeColonInMethodReturnType = true optIn.annotationNewlines = true rewrite.rules = [SortImports, PreferCurlyFors, AvoidInfix] -rewrite.avoidInfix.excludeFilters."+" = ["nameContains"] diff --git a/src/test/scala-2/chiselTests/ChiselEnum.scala b/src/test/scala-2/chiselTests/ChiselEnum.scala index 3f39bfb9b9f..e5d00595520 100644 --- a/src/test/scala-2/chiselTests/ChiselEnum.scala +++ b/src/test/scala-2/chiselTests/ChiselEnum.scala @@ -395,15 +395,15 @@ class IsOneOfTester extends Module { assert(!e2.isOneOf(e101)) // enum-name-contains-a-string method - assert(e100 nameContains "10") - assert(e101 nameContains "10") - assert(!(e0 nameContains "e1")) - assert(!(e0 nameContains "noMatchAnywhere")) + assert(e100.nameContains("10")) + assert(e101.nameContains("10")) + assert(!(e0.nameContains("e1"))) + assert(!(e0.nameContains("noMatchAnywhere"))) // every enum value contains the empty string - assert(EnumExample.all.map(_ nameContains "").reduce(_ && _)) + assert(EnumExample.all.map(_.nameContains("")).reduce(_ && _)) // every enum value contains its own full name - assert(EnumExample.allWithNames.map(m => m._1 nameContains m._2).reduce(_ && _)) + assert(EnumExample.allWithNames.map(m => m._1.nameContains(m._2)).reduce(_ && _)) // check enum value specified as string assert(OtherEnum.otherEnum == OtherEnum("otherEnum"))