aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/firrtl/AddDescriptionNodes.scala
blob: de9ff523aadfb0e66f39ddc0e519a865426e936b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
// SPDX-License-Identifier: Apache-2.0

package firrtl

import firrtl.ir._
import firrtl.annotations._
import firrtl.Mappers._
import firrtl.options.Dependency

/**
  * A base trait for `Annotation`s that describe a `FirrtlNode`.
  * Usually, we would like to emit these descriptions in some way.
  */
sealed trait DescriptionAnnotation extends Annotation {
  def target:      Target
  def description: String
}

/**
  * A docstring description (a comment).
  * @param target the object being described
  * @param description the docstring describing the object
  */
case class DocStringAnnotation(target: Target, description: String) extends DescriptionAnnotation {
  def update(renames: RenameMap): Seq[DocStringAnnotation] = {
    renames.get(target) match {
      case None      => Seq(this)
      case Some(seq) => seq.map(n => this.copy(target = n))
    }
  }
  override private[firrtl] def dedup: Option[(Any, Annotation, ReferenceTarget)] = this match {
    case a @ DocStringAnnotation(refTarget: ReferenceTarget, _) =>
      Some(((refTarget.pathlessTarget, description), copy(target = refTarget.pathlessTarget), refTarget))
    case a @ DocStringAnnotation(pathTarget: InstanceTarget, _) =>
      Some(((pathTarget.pathlessTarget, description), copy(target = pathTarget.pathlessTarget), pathTarget.asReference))
    case _ => None
  }
}

/**
  * An Verilog-style attribute.
  * @param target the object being given an attribute
  * @param description the attribute
  */
case class AttributeAnnotation(target: Target, description: String) extends DescriptionAnnotation {
  def update(renames: RenameMap): Seq[AttributeAnnotation] = {
    renames.get(target) match {
      case None      => Seq(this)
      case Some(seq) => seq.map(n => this.copy(target = n))
    }
  }
  override private[firrtl] def dedup: Option[(Any, Annotation, ReferenceTarget)] = this match {
    case a @ AttributeAnnotation(refTarget: ReferenceTarget, _) =>
      Some(((refTarget.pathlessTarget, description), copy(target = refTarget.pathlessTarget), refTarget))
    case a @ AttributeAnnotation(pathTarget: InstanceTarget, _) =>
      Some(((pathTarget.pathlessTarget, description), copy(target = pathTarget.pathlessTarget), pathTarget.asReference))
    case _ => None
  }
}

/**
  * Base trait for an object that has associated descriptions
  */
private sealed trait HasDescription {
  def descriptions: Seq[Description]
}

/**
  * Base trait for a description that gives some information about a `FirrtlNode`.
  * Usually, we would like to emit these descriptions in some way.
  */
sealed trait Description extends FirrtlNode

/**
  * A docstring description (a comment)
  * @param string a comment
  */
case class DocString(string: StringLit) extends Description {
  def serialize: String = "@[" + string.serialize + "]"
}

/**
  * A Verilog-style attribute.
  * @param string the attribute
  */
case class Attribute(string: StringLit) extends Description {
  def serialize: String = "@[" + string.serialize + "]"
}

/**
  * A statement with descriptions
  * @param descriptions
  * @param stmt the encapsulated statement
  */
private case class DescribedStmt(descriptions: Seq[Description], stmt: Statement)
    extends Statement
    with HasDescription {
  override def serialize: String = s"${descriptions.map(_.serialize).mkString("\n")}\n${stmt.serialize}"
  def mapStmt(f:       Statement => Statement):   Statement = f(stmt)
  def mapExpr(f:       Expression => Expression): Statement = this.copy(stmt = stmt.mapExpr(f))
  def mapType(f:       Type => Type):             Statement = this.copy(stmt = stmt.mapType(f))
  def mapString(f:     String => String):         Statement = this.copy(stmt = stmt.mapString(f))
  def mapInfo(f:       Info => Info):             Statement = this.copy(stmt = stmt.mapInfo(f))
  def foreachStmt(f:   Statement => Unit):        Unit = f(stmt)
  def foreachExpr(f:   Expression => Unit):       Unit = stmt.foreachExpr(f)
  def foreachType(f:   Type => Unit):             Unit = stmt.foreachType(f)
  def foreachString(f: String => Unit):           Unit = stmt.foreachString(f)
  def foreachInfo(f:   Info => Unit):             Unit = stmt.foreachInfo(f)
}

/**
  * A module with descriptions
  * @param descriptions list of descriptions for the module
  * @param portDescriptions list of descriptions for the module's ports
  * @param mod the encapsulated module
  */
private case class DescribedMod(
  descriptions:     Seq[Description],
  portDescriptions: Map[String, Seq[Description]],
  mod:              DefModule)
    extends DefModule
    with HasDescription {
  val info = mod.info
  val name = mod.name
  val ports = mod.ports
  override def serialize: String = s"${descriptions.map(_.serialize).mkString("\n")}\n${mod.serialize}"
  def mapStmt(f:       Statement => Statement): DefModule = this.copy(mod = mod.mapStmt(f))
  def mapPort(f:       Port => Port):           DefModule = this.copy(mod = mod.mapPort(f))
  def mapString(f:     String => String):       DefModule = this.copy(mod = mod.mapString(f))
  def mapInfo(f:       Info => Info):           DefModule = this.copy(mod = mod.mapInfo(f))
  def foreachStmt(f:   Statement => Unit):      Unit = mod.foreachStmt(f)
  def foreachPort(f:   Port => Unit):           Unit = mod.foreachPort(f)
  def foreachString(f: String => Unit):         Unit = mod.foreachString(f)
  def foreachInfo(f:   Info => Unit):           Unit = mod.foreachInfo(f)
}

/** Wraps modules or statements with their respective described nodes. Descriptions come from [[DescriptionAnnotation]].
  * Describing a module or any of its ports will turn it into a `DescribedMod`. Describing a Statement will turn it into
  * a (private) `DescribedStmt`.
  *
  * @note should only be used by VerilogEmitter, described nodes will
  *       break other transforms.
  */
class AddDescriptionNodes extends Transform with DependencyAPIMigration {

  override def prerequisites = firrtl.stage.Forms.LowFormMinimumOptimized ++
    Seq(
      Dependency[firrtl.transforms.BlackBoxSourceHelper],
      Dependency[firrtl.transforms.FixAddingNegativeLiterals],
      Dependency[firrtl.transforms.ReplaceTruncatingArithmetic],
      Dependency[firrtl.transforms.InlineBitExtractionsTransform],
      Dependency[firrtl.transforms.PropagatePresetAnnotations],
      Dependency[firrtl.transforms.InlineAcrossCastsTransform],
      Dependency[firrtl.transforms.LegalizeClocksAndAsyncResetsTransform],
      Dependency[firrtl.transforms.FlattenRegUpdate],
      Dependency(passes.VerilogModulusCleanup),
      Dependency[firrtl.transforms.VerilogRename],
      Dependency(firrtl.passes.VerilogPrep)
    )

  override def optionalPrerequisites = firrtl.stage.Forms.LowFormOptimized

  override def optionalPrerequisiteOf = Seq.empty

  override def invalidates(a: Transform) = false

  def onStmt(compMap: Map[String, Seq[Description]])(stmt: Statement): Statement = {
    val s = stmt.map(onStmt(compMap))
    val sname = s match {
      case d: IsDeclaration => Some(d.name)
      case _ => None
    }
    val descs = sname.flatMap({
      case name =>
        compMap.get(name)
    })
    (descs, s) match {
      case (Some(d), DescribedStmt(prevDescs, ss)) => DescribedStmt(prevDescs ++ d, ss)
      case (Some(d), ss)                           => DescribedStmt(d, ss)
      case (None, _)                               => s
    }
  }

  def onModule(
    modMap:   Map[String, Seq[Description]],
    compMaps: Map[String, Map[String, Seq[Description]]]
  )(mod:      DefModule
  ): DefModule = {
    val compMap = compMaps.getOrElse(mod.name, Map())
    val newMod = mod.mapStmt(onStmt(compMap))
    val portDesc = mod.ports.collect {
      case p @ Port(_, name, _, _) if compMap.contains(name) =>
        name -> compMap(name)
    }.toMap

    val modDesc = modMap.get(newMod.name).getOrElse(Seq())

    if (portDesc.nonEmpty || modDesc.nonEmpty) {
      DescribedMod(modDesc, portDesc, newMod)
    } else {
      newMod
    }
  }

  /**
    * Merges descriptions of like types.
    *
    * Multiple DocStrings on the same object get merged together into one big multi-line comment.
    * Similarly, multiple attributes on the same object get merged into one attribute with attributes separated by
    * commas.
    * @param descs List of `Description`s that are modifying the same object
    * @return List of `Description`s with some descriptions merged
    */
  def mergeDescriptions(descs: Seq[Description]): Seq[Description] = {
    val (docs: Seq[DocString] @unchecked, nodocs) = descs.partition {
      case _: DocString => true
      case _ => false
    }
    val (attrs: Seq[Attribute] @unchecked, rest) = nodocs.partition {
      case _: Attribute => true
      case _ => false
    }

    val doc = if (docs.nonEmpty) {
      Seq(DocString(StringLit.unescape(docs.map(_.string.string).mkString("\n\n"))))
    } else {
      Seq()
    }
    val attr = if (attrs.nonEmpty) {
      Seq(Attribute(StringLit.unescape(attrs.map(_.string.string).mkString(", "))))
    } else {
      Seq()
    }

    rest ++ doc ++ attr
  }

  def collectMaps(
    annos: Seq[Annotation]
  ): (Map[String, Seq[Description]], Map[String, Map[String, Seq[Description]]]) = {
    val modList = annos.collect {
      case DocStringAnnotation(ModuleTarget(_, m), desc) => (m, DocString(StringLit.unescape(desc)))
      case AttributeAnnotation(ModuleTarget(_, m), desc) => (m, Attribute(StringLit.unescape(desc)))
    }

    // map field 1 (module name) -> field 2 (a list of Descriptions)
    val modMap = modList
      .groupBy(_._1)
      .mapValues(_.map(_._2))
      // and then merge like descriptions (e.g. multiple docstrings into one big docstring)
      .mapValues(mergeDescriptions)

    val compList = annos.collect {
      case DocStringAnnotation(ReferenceTarget(_, m, _, c, _), desc) =>
        (m, c, DocString(StringLit.unescape(desc)))
      case AttributeAnnotation(ReferenceTarget(_, m, _, c, _), desc) =>
        (m, c, Attribute(StringLit.unescape(desc)))
    }

    // map field 1 (name) -> a map that we build
    val compMap = compList
      .groupBy(_._1)
      .mapValues(
        // map field 2 (component name) -> field 3 (a list of Descriptions)
        _.groupBy(_._2)
          .mapValues(_.map(_._3))
          // and then merge like descriptions (e.g. multiple docstrings into one big docstring)
          .mapValues(mergeDescriptions)
          .toMap
      )

    (modMap.toMap, compMap.toMap)
  }

  def executeModule(module: DefModule, annos: Seq[Annotation]): DefModule = {
    val (modMap, compMap) = collectMaps(annos)

    onModule(modMap, compMap)(module)
  }

  override def execute(state: CircuitState): CircuitState = {
    val (modMap, compMap) = collectMaps(state.annotations)

    state.copy(circuit = state.circuit.mapModule(onModule(modMap, compMap)))
  }
}