Skip to content
This repository has been archived by the owner on Aug 20, 2024. It is now read-only.

Multi protobuf module emission and consumption #2344

Merged
merged 25 commits into from
Sep 8, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
38a2786
Multiprotobuf algorithm now pulls the top module from the protobufs
jardhu Aug 16, 2021
4194f86
Remove double assignment compilation error
jardhu Aug 16, 2021
47c3a1b
Add compiler option to emit individual module protobufs
jardhu Aug 23, 2021
abae2b2
Scalafmt
jardhu Aug 23, 2021
7b1817a
Compiler options for proto buf emission
jardhu Aug 24, 2021
2362a75
Implement multi module combination when reading directory of protobufs
jardhu Aug 27, 2021
e2245c0
Use foreacher in protobuf emitter
jardhu Aug 31, 2021
3155a0a
Revert directoryToCircuit change
jardhu Aug 31, 2021
beeec3e
Low opt option now properly uses low opt emitter
jardhu Aug 31, 2021
691994a
emit-modules-protobuf for long -e option
jared-barocsi Sep 1, 2021
3c39745
Directory annotation now correctly throws exceptions
jardhu Sep 1, 2021
0b53842
Dedup collects extmodules if they don't have an accompanying implemen…
jardhu Sep 2, 2021
e161016
Absolute paths for exception references in scala doc
jardhu Sep 2, 2021
c6704d3
Compiler option tests
jardhu Sep 2, 2021
c99ed1e
Fix multiple inclusion of top module in combined circuit
jardhu Sep 2, 2021
f420097
Only one stage is sufficient for multiprotobuf test
jardhu Sep 2, 2021
f991a28
Multi-module circuit combination tests
jardhu Sep 3, 2021
e2189c4
Update src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala
jared-barocsi Sep 3, 2021
c26f7d8
Multi protobuf test checks output circuit against input for equality
jardhu Sep 3, 2021
118ff7d
Move collectInstantiatedModules into private util function
jardhu Sep 4, 2021
0885f70
Comment cleanup
jardhu Sep 7, 2021
4830c89
Rename file -> dir in scaladoc
jardhu Sep 7, 2021
5942386
Apply suggestions from code review
jared-barocsi Sep 8, 2021
fc136d4
Scalafmt
jardhu Sep 8, 2021
ed93404
Merge branch 'master' into multi-protobufs
jackkoenig Sep 8, 2021
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
39 changes: 38 additions & 1 deletion src/main/scala/firrtl/Emitter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ object EmitCircuitAnnotation extends HasShellOptions {
case "low-opt" =>
Seq(
RunFirrtlTransformAnnotation(new ProtoEmitter.OptLow),
EmitCircuitAnnotation(classOf[ProtoEmitter.Low])
EmitCircuitAnnotation(classOf[ProtoEmitter.OptLow])
)
case _ => throw new PhaseException(s"Unknown emitter '$a'! (Did you misspell it?)")
},
Expand Down Expand Up @@ -151,6 +151,43 @@ object EmitAllModulesAnnotation extends HasShellOptions {
helpText = "Run the specified module emitter (one file per module)",
shortOption = Some("e"),
helpValueName = Some("<chirrtl|high|middle|low|verilog|mverilog|sverilog>")
),
new ShellOption[String](
longOption = "emit-module-protobufs",
jared-barocsi marked this conversation as resolved.
Show resolved Hide resolved
toAnnotationSeq = (a: String) =>
a match {
case "chirrtl" =>
Seq(
RunFirrtlTransformAnnotation(new ProtoEmitter.Chirrtl),
EmitAllModulesAnnotation(classOf[ProtoEmitter.Chirrtl])
)
case "mhigh" =>
Seq(
RunFirrtlTransformAnnotation(new ProtoEmitter.MHigh),
EmitAllModulesAnnotation(classOf[ProtoEmitter.MHigh])
)
case "high" =>
Seq(
RunFirrtlTransformAnnotation(new ProtoEmitter.High),
EmitAllModulesAnnotation(classOf[ProtoEmitter.High])
)
case "middle" =>
Seq(
RunFirrtlTransformAnnotation(new ProtoEmitter.Middle),
EmitAllModulesAnnotation(classOf[ProtoEmitter.Middle])
)
case "low" =>
Seq(RunFirrtlTransformAnnotation(new ProtoEmitter.Low), EmitAllModulesAnnotation(classOf[ProtoEmitter.Low]))
case "low-opt" =>
Seq(
RunFirrtlTransformAnnotation(new ProtoEmitter.OptLow),
EmitAllModulesAnnotation(classOf[ProtoEmitter.OptLow])
)
case _ => throw new PhaseException(s"Unknown emitter '$a'! (Did you misspell it?)")
},
helpText = "Run the specified module emitter (one protobuf per module)",
shortOption = Some("p"),
helpValueName = Some("<chirrtl|mhigh|high|middle|low|low-opt>")
)
)

Expand Down
31 changes: 31 additions & 0 deletions src/main/scala/firrtl/Utils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,37 @@ object Utils extends LazyLogging {
map.view.map({ case (k, vs) => k -> vs.toList }).toList
}

/** Combines several separate circuit modules (typically emitted by -e or -p compiler options) into a single circuit */
def combine(circuits: Seq[Circuit]): Circuit = {
def dedup(modules: Seq[DefModule]): Seq[Either[Module, DefModule]] = {
// Left means "lone module", Right means has ExtModules
val module: Option[Module] = {
val found: Seq[Module] = modules.collect { case m: Module => m }
assert(found.size <= 1)
jared-barocsi marked this conversation as resolved.
Show resolved Hide resolved
found.headOption
}
val extModules: Seq[ExtModule] = modules.collect { case e: ExtModule => e }.distinct

if (extModules.isEmpty) Seq(Left(module.get)) else (module ++: extModules).map(Right(_))
}
jared-barocsi marked this conversation as resolved.
Show resolved Hide resolved

// 1. Combine modules
val grouped: Seq[(String, Seq[DefModule])] = groupByIntoSeq(circuits.flatMap(_.modules))({
case mod: Module => mod.name
case ext: ExtModule => ext.defname
})
val deduped: Iterable[Either[Module, DefModule]] = grouped.flatMap { case (_, insts) => dedup(insts) }

// 2. Determine top
val top = {
val found = deduped.collect { case Left(m) => m }
assert(found.size == 1)
jared-barocsi marked this conversation as resolved.
Show resolved Hide resolved
found.head
}
val res = deduped.collect { case Right(m: Module) => m }
ir.Circuit(NoInfo, top +: res.toSeq, top.name)
}

object True {
private val _True = UIntLiteral(1, IntWidth(1))

Expand Down
50 changes: 45 additions & 5 deletions src/main/scala/firrtl/backends/proto/ProtoBufEmitter.scala
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
package firrtl.backends.proto

import firrtl.{AnnotationSeq, CircuitState, DependencyAPIMigration, Transform}
import firrtl.ir
import firrtl._
import firrtl.ir._
import firrtl.annotations.NoTargetAnnotation
import firrtl.options.CustomFileEmission
import firrtl.options.Viewer.view
import firrtl.proto.ToProto
import firrtl.stage.{FirrtlOptions, Forms}
import firrtl.stage.TransformManager.TransformDependency
import firrtl.traversals.Foreachers._
import java.io.{ByteArrayOutputStream, Writer}
import scala.collection.mutable.ArrayBuffer
import Utils.throwInternalError

/** This object defines Annotations that are used by Protocol Buffer emission.
*/
Expand Down Expand Up @@ -59,10 +62,47 @@ sealed abstract class ProtoBufEmitter(prereqs: Seq[TransformDependency])
override def optionalPrerequisiteOf = Seq.empty
override def invalidates(a: Transform) = false

override def execute(state: CircuitState) =
state.copy(annotations = state.annotations :+ Annotation.ProtoBufSerialization(state.circuit, Some(outputSuffix)))
private def emitAllModules(circuit: Circuit): Seq[Annotation.ProtoBufSerialization] = {
// For a given module, returns a Seq of all modules instantited inside of it
def collectInstantiatedModules(mod: Module, map: Map[String, DefModule]): Seq[DefModule] = {
// Use list instead of set to maintain order
val modules = ArrayBuffer.empty[DefModule]
def onStmt(stmt: Statement): Unit = stmt match {
case DefInstance(_, _, name, _) => modules += map(name)
case _: WDefInstanceConnector => throwInternalError(s"unrecognized statement: $stmt")
case other => other.foreach(onStmt)
}
onStmt(mod.body)
modules.distinct.toSeq
}
val modMap = circuit.modules.map(m => m.name -> m).toMap
// Turn each module into it's own circuit with it as the top and all instantied modules as ExtModules
circuit.modules.collect {
case m: Module =>
val instModules = collectInstantiatedModules(m, modMap)
val extModules = instModules.map {
case Module(info, name, ports, _) => ExtModule(info, name, ports, name, Seq.empty)
case ext: ExtModule => ext
}
val newCircuit = Circuit(m.info, extModules :+ m, m.name)
Annotation.ProtoBufSerialization(newCircuit, Some(outputSuffix))
}
}
jared-barocsi marked this conversation as resolved.
Show resolved Hide resolved

override def execute(state: CircuitState) = {
val newAnnos = state.annotations.flatMap {
case EmitCircuitAnnotation(a) if this.getClass == a =>
Seq(
Annotation.ProtoBufSerialization(state.circuit, Some(outputSuffix))
)
case EmitAllModulesAnnotation(a) if this.getClass == a =>
emitAllModules(state.circuit)
case _ => Seq()
}
state.copy(annotations = newAnnos ++ state.annotations)
}

override def emit(state: CircuitState, writer: Writer): Unit = {
def emit(state: CircuitState, writer: Writer): Unit = {
val ostream = new java.io.ByteArrayOutputStream
ToProto.writeToStream(ostream, state.circuit)
writer.write(ostream.toString())
Expand Down
18 changes: 18 additions & 0 deletions src/main/scala/firrtl/proto/FromProto.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import collection.JavaConverters._
import FirrtlProtos._
import com.google.protobuf.CodedInputStream
import Firrtl.Statement.{Formal, ReadUnderWrite}
import firrtl.ir.DefModule
import Utils.combine

object FromProto {

Expand All @@ -33,6 +35,22 @@ object FromProto {
proto.FromProto.convert(pb)
}

/** Deserialize all the ProtoBuf representations of [[ir.Circuit]] in @directory
*
* @param dir directory containing ProtoBuf representation(s)
* @return Deserialized FIRRTL Circuit
*/
def fromDirectory(dir: String): ir.Circuit = {
val d = new File(dir)
val fileList = if (d.exists && d.isDirectory) {
d.listFiles.filter(_.isFile).toList
} else {
List[File]()
}
jared-barocsi marked this conversation as resolved.
Show resolved Hide resolved

combine(fileList.map(f => fromInputStream(new FileInputStream(f))))
}

// Convert from ProtoBuf message repeated Statements to FIRRRTL Block
private def compressStmts(stmts: scala.collection.Seq[ir.Statement]): ir.Statement = stmts match {
case scala.collection.Seq() => ir.EmptyStmt
Expand Down
35 changes: 34 additions & 1 deletion src/main/scala/firrtl/stage/FirrtlAnnotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import firrtl._
import firrtl.ir.Circuit
import firrtl.annotations.{Annotation, NoTargetAnnotation}
import firrtl.options.{Dependency, HasShellOptions, OptionsException, ShellOption, Unserializable}
import java.io.FileNotFoundException
import java.io.{File, FileNotFoundException}
import java.nio.file.NoSuchFileException

import firrtl.stage.TransformManager.TransformDependency
Expand Down Expand Up @@ -64,6 +64,39 @@ object FirrtlFileAnnotation extends HasShellOptions {

}

/** Read a directory of FIRRTL files or ProtoBufs
* - set with `-I/--input-directory`
* @param file input filename
jared-barocsi marked this conversation as resolved.
Show resolved Hide resolved
*/
case class FirrtlDirectoryAnnotation(dir: String) extends NoTargetAnnotation with CircuitOption {

def toCircuit(info: Parser.InfoMode): FirrtlCircuitAnnotation = {
val circuit =
try {
proto.FromProto.fromDirectory(dir)
jared-barocsi marked this conversation as resolved.
Show resolved Hide resolved
} catch {
case a @ (_: FileNotFoundException | _: NoSuchFileException) =>
throw new OptionsException(s"Directory '$dir' not found! (Did you misspell it?)", a)
}
jared-barocsi marked this conversation as resolved.
Show resolved Hide resolved
FirrtlCircuitAnnotation(circuit)
}

}

object FirrtlDirectoryAnnotation extends HasShellOptions {

val options = Seq(
new ShellOption[String](
longOption = "input-directory",
toAnnotationSeq = a => Seq(FirrtlDirectoryAnnotation(a)),
helpText = "A directory of FIRRTL files",
Copy link
Contributor

Choose a reason for hiding this comment

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

can these be either .fir or .pb files? A mixture?

Copy link
Contributor

Choose a reason for hiding this comment

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

We should figure that out. I think to start we should make it be only .fir or only .pb files. Note that -i actually doesn't care about file extensions, it just tries to deserialize as protobuf and if it fails it deserializes as .fir but I think it's okay to require correct file extensions here.

shortOption = Some("I"),
helpValueName = Some("<directory>")
)
)

}

/** An explicit output file the emitter will write to
* - set with `-o/--output-file`
* @param file output filename
Expand Down
1 change: 1 addition & 0 deletions src/main/scala/firrtl/stage/FirrtlCli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ trait FirrtlCli { this: Shell =>
parser.note("FIRRTL Compiler Options")
Seq(
FirrtlFileAnnotation,
FirrtlDirectoryAnnotation,
OutputFileAnnotation,
InfoModeAnnotation,
FirrtlSourceAnnotation,
Expand Down
37 changes: 21 additions & 16 deletions src/main/scala/firrtl/stage/phases/Checks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,34 +31,39 @@ class Checks extends Phase {
* @throws firrtl.options.OptionsException if any checks fail
*/
def transform(annos: AnnotationSeq): AnnotationSeq = {
val inF, inS, eam, ec, outF, emitter, im, inC = collection.mutable.ListBuffer[Annotation]()
val inF, inS, inD, eam, ec, outF, emitter, im, inC = collection.mutable.ListBuffer[Annotation]()
annos.foreach(_ match {
case a: FirrtlFileAnnotation => a +=: inF
case a: FirrtlSourceAnnotation => a +=: inS
case a: EmitAllModulesAnnotation => a +=: eam
case a: EmitCircuitAnnotation => a +=: ec
case a: OutputFileAnnotation => a +=: outF
case a: InfoModeAnnotation => a +=: im
case a: FirrtlCircuitAnnotation => a +=: inC
case a: FirrtlFileAnnotation => a +=: inF
case a: FirrtlSourceAnnotation => a +=: inS
case a: FirrtlDirectoryAnnotation => a +=: inD
case a: EmitAllModulesAnnotation => a +=: eam
case a: EmitCircuitAnnotation => a +=: ec
case a: OutputFileAnnotation => a +=: outF
case a: InfoModeAnnotation => a +=: im
case a: FirrtlCircuitAnnotation => a +=: inC
case a @ RunFirrtlTransformAnnotation(_: firrtl.Emitter) => a +=: emitter
case _ =>
})

/* At this point, only a FIRRTL Circuit should exist */
if (inF.isEmpty && inS.isEmpty && inC.isEmpty) {
throw new OptionsException(s"""|Unable to determine FIRRTL source to read. None of the following were found:
| - an input file: -i, --input-file, FirrtlFileAnnotation
| - FIRRTL source: --firrtl-source, FirrtlSourceAnnotation
| - FIRRTL circuit: FirrtlCircuitAnnotation""".stripMargin)
if (inF.isEmpty && inS.isEmpty && inD.isEmpty && inC.isEmpty) {
throw new OptionsException(
s"""|Unable to determine FIRRTL source to read. None of the following were found:
| - an input file: -i, --input-file, FirrtlFileAnnotation
| - an input dir: -I, --input-directory, FirrtlDirectoryAnnotation
| - FIRRTL source: --firrtl-source, FirrtlSourceAnnotation
| - FIRRTL circuit: FirrtlCircuitAnnotation""".stripMargin
)
}

/* Only one FIRRTL input can exist */
if (inF.size + inS.size + inC.size > 1) {
throw new OptionsException(
s"""|Multiply defined input FIRRTL sources. More than one of the following was found:
| - an input file (${inF.size} times): -i, --input-file, FirrtlFileAnnotation
| - FIRRTL source (${inS.size} times): --firrtl-source, FirrtlSourceAnnotation
| - FIRRTL circuit (${inC.size} times): FirrtlCircuitAnnotation""".stripMargin
| - an input file (${inF.size} times): -i, --input-file, FirrtlFileAnnotation
| - an input dir (${inD.size} times): -I, --input-directory, FirrtlDirectoryAnnotation
| - FIRRTL source (${inS.size} times): --firrtl-source, FirrtlSourceAnnotation
| - FIRRTL circuit (${inC.size} times): FirrtlCircuitAnnotation""".stripMargin
)
}

Expand Down