Skip to content
Draft
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
15 changes: 8 additions & 7 deletions core/src/main/scala/chisel3/internal/firrtl/Serializer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ private[chisel3] object Serializer {

/** Generate a legal FIRRTL name. */
private def legalize(name: String): String = name match {
// If the name starts with a digit, then escape it with backticks.
// If the name starts with a digit, escape it with backticks.
case _ if name.head.isDigit => s"`$name`"
case _ => name
// Otherwise, use the common keyword legalization
case _ => fir.Keywords.legalize(name)
}

/** create a new line with the appropriate indent */
Expand Down Expand Up @@ -115,7 +116,7 @@ private[chisel3] object Serializer {

private def serialize(arg: Arg, ctx: Component, info: SourceInfo)(implicit b: StringBuilder): Unit = arg match {
case Node(id) => serialize(getRef(id, info), ctx, info)
case Ref(name) => b ++= name
case Ref(name) => b ++= legalize(name)
// We don't need to legalize Slot names, firtool can parse subfields starting with digits
case Slot(imm, name) => serialize(imm, ctx, info); b += '.'; b ++= legalize(name)
case OpaqueSlot(imm) => serialize(imm, ctx, info)
Expand All @@ -126,11 +127,11 @@ private[chisel3] object Serializer {
case Index(imm, value) =>
serialize(imm, ctx, info); b += '['; serialize(value, ctx, info); b += ']'
case ModuleIO(mod, name) =>
if (mod eq ctx.id) { b ++= name }
else { b ++= getRef(mod, info).name; b += '.'; b ++= name }
if (mod eq ctx.id) { b ++= legalize(name) }
else { b ++= getRef(mod, info).name; b += '.'; b ++= legalize(name) }
case ModuleCloneIO(mod, name) =>
if (mod eq ctx.id) clonedModuleIOError(mod, name, info)
else { b ++= name }
else { b ++= legalize(name) }
case u @ ULit(n, w) =>
val width = w match {
case UnknownWidth => u.minWidth
Expand Down Expand Up @@ -518,7 +519,7 @@ private[chisel3] object Serializer {
b ++= "flip "
case _ => ()
}
b ++= legalize(getRef(elt, info).name); b ++= " : "
b ++= getRef(elt, info).name; b ++= " : "
serializeType(elt, childClearDir, info, checkProbe, true, typeAliases)
}
if (!t._isOpaqueType) {
Expand Down
188 changes: 188 additions & 0 deletions firrtl/src/main/scala/firrtl/ir/Keywords.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: Apache-2.0

package firrtl.ir

/** Object containing the set of FIRRTL keywords that need to be escaped when used as identifiers.
*
* This includes keywords from the FIRRTL specification as well as additional keywords recognized
* by the CIRCT FIRRTL parser (from FIRTokenKinds.def).
*/
object Keywords {

/** Set of FIRRTL keywords that need to be escaped when used as identifiers. */
val keywords: Set[String] = Set(
// Top-level keywords
"FIRRTL",
"version",
"circuit",
"public",
"module",
"extmodule",
"layer",
"formal",
"type",
// Port and parameter keywords
"input",
"output",
"parameter",
"defname",
// Layer keywords
"bind",
"inline",
"enablelayer",
"knownlayer",
"layerblock",
// Statement keywords
"node",
"wire",
"reg",
"regreset",
"inst",
"of",
"mem",
"skip",
// Memory keywords
"data-type",
"depth",
"read-latency",
"write-latency",
"read-under-write",
"reader",
"writer",
"readwriter",
"old",
"new",
"undefined",
// Connect-like keywords
"connect",
"invalidate",
"attach",
"define",
"propassign",
// Conditional keywords
"when",
"else",
"match",
// Command keywords
"stop",
"force",
"force_initial",
"release",
"release_initial",
"printf",
"fprintf",
"fflush",
"assert",
"assume",
"cover",
"intrinsic",
// Type keywords
"const",
"Clock",
"Reset",
"AsyncReset",
"UInt",
"SInt",
"Analog",
"Probe",
"RWProbe",
"flip",
"Unknown",
"Bool",
"Fixed",
"AnyRef",
"Path",
"Inst",
"Domain",
"Double",
"String",
"Integer",
"List",
// Expression keywords
"mux",
"read",
"probe",
"rwprobe",
// PrimOp keywords
"asUInt",
"asSInt",
"asClock",
"asAsyncReset",
"asReset",
"cvt",
"neg",
"not",
"andr",
"orr",
"xorr",
"add",
"sub",
"mul",
"div",
"rem",
"lt",
"leq",
"gt",
"geq",
"eq",
"neq",
"dshl",
"dshr",
"dshlw",
"and",
"or",
"xor",
"cat",
"pad",
"shl",
"shr",
"head",
"tail",
"bits",
"integer_add",
"integer_mul",
"integer_shr",
"integer_shl",
"list_concat",
"tagExtract",
// Additional CIRCT keywords not in spec (from FIRTokenKinds.def)
"case",
"class",
"cmem",
"contract",
"declgroup",
"domain_define",
"domains",
"extclass",
"false",
"group",
"infer",
"instchoice",
"intmodule",
"invalid",
"is",
"mport",
"object",
"option",
"rdwr",
"ref",
"requires",
"reset",
"simulation",
"smem",
"symbolic",
"true",
"with",
"write",
"unsafe_domain_cast"
)

/** Legalize a name by escaping it with backticks if it's a FIRRTL keyword.
*
* @param name the name to legalize
* @return the legalized name (with backticks if it's a keyword)
*/
def legalize(name: String): String = {
if (keywords.contains(name)) s"`$name`" else name
}
}
5 changes: 3 additions & 2 deletions firrtl/src/main/scala/firrtl/ir/Serializer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ object Serializer {

/** Generate a legal FIRRTL name. */
private def legalize(name: String): String = name match {
// If the name starts with a digit, then escape it with backticks.
// If the name starts with a digit, escape it with backticks.
case _ if name.head.isDigit => legalizedNames.getOrElseUpdate(name, s"`$name`")
case _ => name
// Otherwise, use the common keyword legalization
case _ => legalizedNames.getOrElseUpdate(name, Keywords.legalize(name))
}

private def s(str: StringLit)(implicit b: StringBuilder, indent: Int): Unit = b ++= str.serialize
Expand Down
34 changes: 34 additions & 0 deletions firrtl/src/test/scala/firrtlTests/SerializerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,40 @@ class SerializerSpec extends AnyFlatSpec with Matchers {
)
) should include("""assert(`42_clock`, `42_predicate`, `42_enable`, "message %d", `42_arg`) : `42_label`""")
info("fprintf okay!")
}

it should "escape FIRRTL keywords when used as identifiers" in {
info("type keywords okay!")
Seq("UInt", "Clock", "Reset").foreach { keyword =>
Serializer.serialize(Port(NoInfo, keyword, Input, UIntType(IntWidth(1)), Seq.empty)) should include(
s"input `$keyword`"
)
}

info("statement keywords okay!")
Seq("wire", "reg", "node", "skip", "when", "inst").foreach { keyword =>
Serializer.serialize(DefWire(NoInfo, keyword, UIntType(IntWidth(1)))) should include(s"wire `$keyword`")
}

info("primop keywords okay!")
Seq("add", "sub", "mul", "and", "or", "xor", "mux").foreach { keyword =>
Serializer.serialize(DefWire(NoInfo, keyword, UIntType(IntWidth(1)))) should include(s"wire `$keyword`")
}

info("module keywords okay!")
Seq("module", "circuit", "input", "output").foreach { keyword =>
Serializer.serialize(DefWire(NoInfo, keyword, UIntType(IntWidth(1)))) should include(s"wire `$keyword`")
}

info("connect keywords okay!")
Seq("connect", "invalidate", "attach").foreach { keyword =>
Serializer.serialize(DefWire(NoInfo, keyword, UIntType(IntWidth(1)))) should include(s"wire `$keyword`")
}

info("command keywords okay!")
Seq("printf", "assert", "assume", "cover").foreach { keyword =>
Serializer.serialize(DefWire(NoInfo, keyword, UIntType(IntWidth(1)))) should include(s"wire `$keyword`")
}
Serializer.serialize(
Fprint(
NoInfo,
Expand Down
2 changes: 1 addition & 1 deletion lit/tests/Converter/Circuit.sc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class FooBlackbox extends BlackBox {

// CHECK: public module FooModule :
// CHECK-NEXT: input clock : Clock
// CHECK-NEXT: input reset : UInt<1>
// CHECK-NEXT: input `reset` : UInt<1>
// CHECK-NEXT: output o : UInt<1>
class FooModule extends Module {
val o = IO(Output(Bool()))
Expand Down
2 changes: 1 addition & 1 deletion lit/tests/Converter/Debug.sc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Verf extends Module {
println(circt.stage.ChiselStage.emitCHIRRTL(new Verf))

// Following test ported from ProbeSpec.scala in chisel test suite
// CHECK-LABEL: circuit Probe :
// CHECK-LABEL: circuit `Probe` :
class Probe extends Module {
val x = IO(Input(Bool()))
val y = IO(Output(Bool()))
Expand Down
Loading