Skip to content

Commit

Permalink
Implement CustomRadixTransform for wave viewers (chipsalliance#2434)
Browse files Browse the repository at this point in the history
1. Add CustomRadix{Def,Apply}Annotation to define and apply custom radix.
2. Add CustomRadixConfigFileAnnotation to output a JSON config file so
   users can generate scripts on their own.

Reviewed-by: Jiuyang Liu <[email protected]>
Co-authored-by: sinofp <[email protected]>
  • Loading branch information
sequencer and sinofp authored Dec 13, 2021
1 parent 4347f57 commit 7f88449
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 2 deletions.
5 changes: 3 additions & 2 deletions src/main/scala/firrtl/stage/FirrtlCli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package firrtl.stage

import firrtl.options.Shell
import firrtl.passes.CommonSubexpressionElimination
import firrtl.transforms.NoCircuitDedupAnnotation
import firrtl.transforms.{CustomRadixTransform, NoCircuitDedupAnnotation}

/** [[firrtl.options.Shell Shell]] mixin that provides command line options for FIRRTL. This does not include any
* [[firrtl.options.RegisteredLibrary RegisteredLibrary]] or [[firrtl.options.RegisteredTransform RegisteredTransform]]
Expand All @@ -28,7 +28,8 @@ trait FirrtlCli { this: Shell =>
OptimizeForFPGA,
CurrentFirrtlStateAnnotation,
CommonSubexpressionElimination,
AllowUnrecognizedAnnotations
AllowUnrecognizedAnnotations,
CustomRadixTransform
)
.map(_.addOptions(parser))

Expand Down
115 changes: 115 additions & 0 deletions src/main/scala/firrtl/transforms/CustomRadixTransform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: Apache-2.0

package firrtl.transforms

import firrtl.annotations.TargetToken.Instance
import firrtl.annotations.{Annotation, NoTargetAnnotation, ReferenceTarget, SingleTargetAnnotation}
import firrtl.options.{CustomFileEmission, Dependency, HasShellOptions, ShellOption}
import firrtl.stage.TransformManager.TransformDependency
import firrtl.stage.{Forms, RunFirrtlTransformAnnotation}
import firrtl.{AnnotationSeq, CircuitState, DependencyAPIMigration, Transform}

/** Contains a static map from signal value(BigInt) to signal name(String)
* This is useful for enumeration(finite state machine, bus transaction name, etc)
*
* @param name identifier for this alias
* @param filters a sequence of translation filter
* @param width width of this alias
*/
case class CustomRadixDefAnnotation(name: String, filters: Seq[(BigInt, String)], width: Int) extends NoTargetAnnotation

/** A annotation making a ReferenceTarget to be a specific [[CustomRadixDefAnnotation]].
*
* @param target the ReferenceTarget which the alias applied to
* @param name the identifier for the alias
*/
case class CustomRadixApplyAnnotation(target: ReferenceTarget, name: String)
extends SingleTargetAnnotation[ReferenceTarget] {
override def duplicate(n: ReferenceTarget): Annotation = this.copy(n)
}

/** Dumps a JSON config file for custom radix. Users can generate script using the emitted file.
*
* @param signals which alias contains which signals, the signals should be converted from ReferenceTarget to String
* @param filters sequence of [[CustomRadixDefAnnotation]], the name should match [[signals]].map(_._1)
*/
case class CustomRadixConfigFileAnnotation(
signals: Seq[(String, Seq[String])],
filters: Seq[CustomRadixDefAnnotation])
extends NoTargetAnnotation
with CustomFileEmission {
def waveViewer = "config"
def baseFileName(annotations: AnnotationSeq): String = "custom_radix"
def suffix: Option[String] = Some(".json")

def getBytes: Iterable[Byte] = {
import org.json4s.JsonDSL.WithBigDecimal._
import org.json4s.native.JsonMethods._
val aliasMap = signals.toMap
pretty(
render(
filters.map { a =>
val values = a.filters.map {
case (int, str) =>
("digit" -> int) ~
("alias" -> str)
}
a.name -> (
("width" -> a.width) ~
("values" -> values) ~
("signals" -> aliasMap(a.name))
)
}
)
)
}.getBytes
}

/** A Transform that generate scripts or config file for Custom Radix */
object CustomRadixTransform extends Transform with DependencyAPIMigration with HasShellOptions {

val options = Seq(
new ShellOption[String](
longOption = "wave-viewer-script",
toAnnotationSeq = str => {
RunFirrtlTransformAnnotation(Dependency(CustomRadixTransform)) +: str.toLowerCase
.split(',')
.map {
case "json" | "" => CustomRadixConfigFileAnnotation(Seq.empty, Seq.empty)
}
.toSeq
},
helpText = "<json>, you can combine them like 'json', pass empty string will generate json",
shortOption = None
)
)

// in case of any rename during transforms.
override def optionalPrerequisites: Seq[TransformDependency] = Forms.BackendEmitters
override def invalidates(a: Transform) = false

protected def execute(state: CircuitState): CircuitState = {
val annos = state.annotations
def toVerilogName(target: ReferenceTarget) =
(target.circuit +: target.tokens.collect { case Instance(i) => i } :+ target.ref).mkString(".")
// todo if scalaVersion >= 2.13: use groupMap
val filters = annos.collect { case a: CustomRadixDefAnnotation => a }

val signals = annos.collect {
case CustomRadixApplyAnnotation(target, name) =>
assert(filters.exists(_.name == name), s"$name not found in CustomRadixDefAnnotation.")
name -> target
}
.groupBy(_._1)
.mapValues(_.map(t => toVerilogName(t._2)))
.toSeq
.sortBy(_._1)

assert(filters.groupBy(_.name).forall(_._2.length == 1), "name collision in CustomRadixDefAnnotation detected.")

state.copy(annotations = annos.map {
case _: CustomRadixConfigFileAnnotation => CustomRadixConfigFileAnnotation(signals, filters)
case a => a
})
}
}
129 changes: 129 additions & 0 deletions src/test/scala/firrtlTests/transforms/CustomRadixTransformSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: Apache-2.0

package firrtlTests.transforms

import firrtl.annotations.ReferenceTarget
import firrtl.annotations.TargetToken.{Instance, OfModule}
import firrtl.testutils.FirrtlFlatSpec
import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlStage}
import firrtl.transforms.{CustomRadixApplyAnnotation, CustomRadixDefAnnotation}
import firrtl.util.BackendCompilationUtilities

class CustomRadixTransformSpec extends FirrtlFlatSpec {
behavior.of("CustomRadix")

val testDir = os.Path(BackendCompilationUtilities.createTestDirectory("CustomRadix").getAbsolutePath)
val fir =
"""circuit M2 :
| module PT :
| input clock : Clock
| input reset : Reset
| output io : { flip i : UInt<7>, o : UInt<7>}
|
| io.o <= io.i
|
| module PT_1 :
| input clock : Clock
| input reset : Reset
| output io : { flip i : UInt<7>, o : UInt<7>}
|
| io.o <= io.i
|
| module M1 :
| input clock : Clock
| input reset : Reset
| output out : UInt<7>
|
| reg cnt : UInt<5>, clock with :
| reset => (reset, UInt<5>("h0"))
| node _cnt_T = add(cnt, UInt<1>("h1"))
| node _cnt_T_1 = tail(_cnt_T, 1)
| cnt <= _cnt_T_1
| inst pt of PT
| pt.clock <= clock
| pt.reset <= reset
| inst pt2 of PT_1
| pt2.clock <= clock
| pt2.reset <= reset
| pt2.io.i <= pt.io.o
| pt2.io.o is invalid
| pt.io.i <= UInt<1>("h0")
| node _T = eq(cnt, UInt<1>("h1"))
| when _T :
| pt.io.i <= UInt<1>("h1")
| else :
| node _T_1 = eq(cnt, UInt<2>("h2"))
| when _T_1 :
| pt.io.i <= UInt<2>("h2")
| else :
| node _T_2 = eq(cnt, UInt<2>("h3"))
| when _T_2 :
| pt.io.i <= UInt<7>("h64")
| else :
| node _T_3 = eq(cnt, UInt<3>("h4"))
| when _T_3 :
| pt.io.i <= UInt<7>("h65")
| out <= pt.io.o
|
| module PT_2 :
| input clock : Clock
| input reset : Reset
| output io : { flip i : UInt<7>, o : UInt<7>}
|
| io.o <= io.i
|
| module M2 :
| input clock : Clock
| input reset : UInt<1>
| output out : UInt<7>
|
| inst m1 of M1
| m1.clock <= clock
| m1.reset <= reset
| inst pt3 of PT_2
| pt3.clock <= clock
| pt3.reset <= reset
| pt3.io.i <= m1.out
| out <= pt3.io.o
|""".stripMargin

val annotations = Seq(
FirrtlCircuitAnnotation(firrtl.Parser.parse(fir)),
CustomRadixDefAnnotation("EnumExample", Seq(0, 1, 2, 100, 101).map(x => BigInt(x) -> s"e$x"), 7)
) ++ Seq(
("M1", Seq(), "in"),
("M2", Seq(Instance("pt3") -> OfModule("PT")), "io_o"),
("M2", Seq(Instance("m1") -> OfModule("M1"), Instance("pt2") -> OfModule("PT")), "io_i")
).map {
case (module, path, ref) =>
CustomRadixApplyAnnotation(ReferenceTarget("M2", module, path, ref, Seq()), "EnumExample")
}

it should "generate a JSON config file" in {
(new FirrtlStage).execute(Array("--wave-viewer-script", "", "--target-dir", testDir.toString), annotations)
val expected =
"""[{
| "EnumExample":{
| "width":7,
| "values":[{
| "digit":0,
| "alias":"e0"
| },{
| "digit":1,
| "alias":"e1"
| },{
| "digit":2,
| "alias":"e2"
| },{
| "digit":100,
| "alias":"e100"
| },{
| "digit":101,
| "alias":"e101"
| }],
| "signals":["M2.m1.pt.io_i","M2.m1.pt.io_o","M2.in"]
| }
|}]""".stripMargin
assert(expected == os.read(testDir / "custom_radix.json"))
}
}

0 comments on commit 7f88449

Please sign in to comment.