Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Direct to firrtl #829

Merged
merged 13 commits into from
Jul 2, 2018
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion chiselFrontend/src/main/scala/chisel3/core/Printable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ case class PString(str: String) extends Printable {
(str replaceAll ("%", "%%"), List.empty)
}
/** Superclass for Firrtl format specifiers for Bits */
sealed abstract class FirrtlFormat(specifier: Char) extends Printable {
sealed abstract class FirrtlFormat(private[chisel3] val specifier: Char) extends Printable {
def bits: Bits
def unpack(ctx: Component): (String, Iterable[String]) = {
(s"%$specifier", List(bits.ref.fullName(ctx)))
Expand Down
19 changes: 12 additions & 7 deletions src/main/scala/chisel3/Driver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

package chisel3

import chisel3.internal.firrtl.Emitter
import chisel3.internal.firrtl.Converter
import chisel3.experimental.{RawModule, RunFirrtlTransform}

import java.io._
Expand Down Expand Up @@ -92,9 +92,11 @@ object Driver extends BackendCompilationUtilities {
*/
def elaborate[T <: RawModule](gen: () => T): Circuit = internal.Builder.build(Module(gen()))

def emit[T <: RawModule](gen: () => T): String = Emitter.emit(elaborate(gen))
def toFirrtl(ir: Circuit): firrtl.ir.Circuit = Converter.convert(ir)

def emit[T <: RawModule](ir: Circuit): String = Emitter.emit(ir)
def emit[T <: RawModule](gen: () => T): String = Driver.emit(elaborate(gen))

def emit[T <: RawModule](ir: Circuit): String = toFirrtl(ir).serialize

/** Elaborates the Module specified in the gen function into Verilog
*
Expand All @@ -111,7 +113,7 @@ object Driver extends BackendCompilationUtilities {
def dumpFirrtl(ir: Circuit, optName: Option[File]): File = {
val f = optName.getOrElse(new File(ir.name + ".fir"))
val w = new FileWriter(f)
w.write(Emitter.emit(ir))
w.write(Driver.emit(ir))
w.close()
f
}
Expand Down Expand Up @@ -145,15 +147,18 @@ object Driver extends BackendCompilationUtilities {
val firrtlOptions = optionsManager.firrtlOptions
val chiselOptions = optionsManager.chiselOptions

// use input because firrtl will be reading this
val firrtlString = Emitter.emit(circuit)
val firrtlCircuit = Converter.convert(circuit)

// Still emit to leave an artifact (and because this always has been the behavior)
val firrtlString = firrtlCircuit.serialize
val firrtlFileName = firrtlOptions.getInputFileName(optionsManager)
val firrtlFile = new File(firrtlFileName)

val w = new FileWriter(firrtlFile)
w.write(firrtlString)
w.close()

// Emit the annotations because it has always been the behavior
val annotationFile = new File(optionsManager.getBuildFileName("anno.json"))
val af = new FileWriter(annotationFile)
val firrtlAnnos = circuit.annotations.map(_.toFirrtl)
Expand All @@ -174,7 +179,7 @@ object Driver extends BackendCompilationUtilities {
}
/* This passes the firrtl source and annotations directly to firrtl */
optionsManager.firrtlOptions = optionsManager.firrtlOptions.copy(
firrtlSource = Some(firrtlString),
firrtlCircuit = Some(firrtlCircuit),
annotations = optionsManager.firrtlOptions.annotations ++ firrtlAnnos,
customTransforms = optionsManager.firrtlOptions.customTransforms ++ transforms.toList)

Expand Down
238 changes: 238 additions & 0 deletions src/main/scala/chisel3/internal/firrtl/Converter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// See LICENSE for license details.

package chisel3.internal.firrtl
import chisel3._
import chisel3.core.SpecifiedDirection
import chisel3.experimental._
import chisel3.internal.sourceinfo.{NoSourceInfo, SourceLine, SourceInfo}
import firrtl.{ir => fir}
import chisel3.internal.throwException

import scala.annotation.tailrec
import scala.collection.immutable.{Queue}

private[chisel3] object Converter {
// TODO modeled on unpack method on Printable, refactor?
def unpack(pable: Printable, ctx: Component): (String, Seq[Arg]) = pable match {
case Printables(pables) =>
val (fmts, args) = pables.map(p => unpack(p, ctx)).unzip
(fmts.mkString, args.flatten.toSeq)
case PString(str) => (str.replaceAll("%", "%%"), List.empty)
case format: FirrtlFormat =>
("%" + format.specifier, List(format.bits.ref))
case Name(data) => (data.ref.name, List.empty)
case FullName(data) => (data.ref.fullName(ctx), List.empty)
case Percent => ("%%", List.empty)
}

def convert(info: SourceInfo): fir.Info = info match {
case _: NoSourceInfo => fir.NoInfo
case SourceLine(fn, line, col) => fir.FileInfo(fir.StringLit(s"$fn $line:$col"))
}

def convert(op: PrimOp): fir.PrimOp = firrtl.PrimOps.fromString(op.name)

def convert(dir: MemPortDirection): firrtl.MPortDir = dir match {
case MemPortDirection.INFER => firrtl.MInfer
case MemPortDirection.READ => firrtl.MRead
case MemPortDirection.WRITE => firrtl.MWrite
case MemPortDirection.RDWR => firrtl.MReadWrite
}

// TODO memoize?
// TODO move into the Chisel IR?
def convert(arg: Arg, ctx: Component): fir.Expression = arg match {
case Node(id) => convert(id.getRef, ctx)
case Ref(name) => fir.Reference(name, fir.UnknownType)
case Slot(imm, name) => fir.SubField(convert(imm, ctx), name, fir.UnknownType)
case Index(imm, ILit(idx)) =>
fir.SubIndex(convert(imm, ctx), idx.toInt, fir.UnknownType)
case Index(imm, value) =>
fir.SubAccess(convert(imm, ctx), convert(value, ctx), fir.UnknownType)
case ModuleIO(mod, name) =>
if (mod eq ctx.id) fir.Reference(name, fir.UnknownType)
else fir.SubField(fir.Reference(mod.getRef.name, fir.UnknownType), name, fir.UnknownType)
case u @ ULit(n, UnknownWidth()) => fir.UIntLiteral(n, fir.IntWidth(u.minWidth))
case ULit(n, w) => fir.UIntLiteral(n, convert(w))
case slit @ SLit(n, w) => fir.SIntLiteral(n, convert(w))
val unsigned = if (n < 0) (BigInt(1) << slit.width.get) + n else n
val uint = convert(ULit(unsigned, slit.width), ctx)
fir.DoPrim(firrtl.PrimOps.AsSInt, Seq(uint), Seq.empty, fir.UnknownType)
// TODO Simplify
case fplit @ FPLit(n, w, bp) =>
val unsigned = if (n < 0) (BigInt(1) << fplit.width.get) + n else n
val uint = convert(ULit(unsigned, fplit.width), ctx)
val lit = bp.asInstanceOf[KnownBinaryPoint].value
fir.DoPrim(firrtl.PrimOps.AsFixedPoint, Seq(uint), Seq(lit), fir.UnknownType)
case lit: ILit => throwException(s"Internal Error! Unexpected ILit: $lit")
}
Copy link
Member

Choose a reason for hiding this comment

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

Stylistic nits. Per the style guide, you've got 120 characters to play with. For readability, you may consider putting some of these on one line. Additionally, I have a very hard time parsing dense match structures. You may consider doing an alignment on =>. Same for other large match blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I agree the density is pretty tough. I ended up deciding to have all of the cases be multi-line. The matches were so different in length that alignment on => wasn't working very well


// alt indicates to a WhenEnd whether we're closing an alt or just a regular when
// TODO we should probably have a different structure in the IR to close elses
private case class WhenFrame(when: fir.Conditionally, outer: Queue[fir.Statement], alt: Boolean)

// Whens markers are flat so scope must be inferred
// TODO refactor
def convert(cmds: Seq[Command], ctx: Component): fir.Statement = {
Copy link
Contributor

Choose a reason for hiding this comment

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

There's quite a bit of code here, can you write a summary of the algorithm in comments?
This mostly seems to be a straightforward 1:1 map of Chisel to FIRRTL types except with some funny stuff for whens.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah this definitely needs a summary, will add

@tailrec
def rec(acc: Queue[fir.Statement],
Copy link
Contributor

Choose a reason for hiding this comment

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

What does rec mean?

Copy link
Contributor

Choose a reason for hiding this comment

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

You've probably considered this but could you create a trait like HasToStatement that most of the Commands could extend. Then the convert of command to statement could handle most of the cases with

  case m: HasToStatement =>
     rec(acc :+ m.toStatement, scope)(cmds.tail)

It changes the encapsulation of information, since for example, DefWire has to know how to make a fir.DefWire but it sure cleans up the massive match. I can see it's a bit ugly though since toStatement would need access to the Converter object for info, clocks etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

rec is the name I give to inner recursive functions. It's arbitrary, I'm open to whatever. Scalaz uses go I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I have thought about it a bit but your point is well taken and I ought to share some of my thinking.

In prototyping this, I wanted to touch as little code as possible which is why the Converter is wholly self-contained, I am open to some amount of encapsulation though. toStatement would have to take the Component as an argument, but that's how def fullName currently works for converting Args to Expressions.

A big question related to performance I have that goes back to serialize in FIRRTL is whether large match statements or inheritance are better for performance. Serialization functions in this case are almost certainly megamorphic which is generally terrible for performance, but when we moved serialize from a match statement to a method on FIRRTLNode, it still got faster. Perhaps the dynamic method lookup in megamorphic state is still faster in practice than large match blocks. I'm really not sure.

scope: List[WhenFrame])
(cmds: Seq[Command]): Seq[fir.Statement] = {
if (cmds.isEmpty) {
assert(scope.isEmpty)
acc
} else cmds.head match {
case e: DefPrim[_] =>
val consts = e.args.collect { case ILit(i) => i }
val args = e.args.flatMap {
case _: ILit => None
case other => Some(convert(other, ctx))
}
val expr = e.op.name match {
case "mux" =>
assert(args.size == 3, s"Mux with unexpected args: $args")
fir.Mux(args(0), args(1), args(2), fir.UnknownType)
case _ =>
fir.DoPrim(convert(e.op), args, consts, fir.UnknownType)
}
val node = fir.DefNode(convert(e.sourceInfo), e.name, expr)
rec(acc :+ node, scope)(cmds.tail)
case e @ DefWire(info, id) =>
val wire = fir.DefWire(convert(info), e.name, extractType(id))
rec(acc :+ wire, scope)(cmds.tail)
case e @ DefReg(info, id, clock) =>
val reg = fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx),
firrtl.Utils.zero, convert(id.getRef, ctx))
rec(acc :+ reg, scope)(cmds.tail)
case e @ DefRegInit(info, id, clock, reset, init) =>
val reg = fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx),
convert(reset, ctx), convert(init, ctx))
rec(acc :+ reg, scope)(cmds.tail)
case e @ DefMemory(info, id, t, size) =>
val mem = firrtl.CDefMemory(convert(info), e.name, extractType(t), size, false)
rec(acc :+ mem, scope)(cmds.tail)
case e @ DefSeqMemory(info, id, t, size) =>
val mem = firrtl.CDefMemory(convert(info), e.name, extractType(t), size, true)
rec(acc :+ mem, scope)(cmds.tail)
case e: DefMemPort[_] =>
val port = firrtl.CDefMPort(convert(e.sourceInfo), e.name, fir.UnknownType,
e.source.fullName(ctx), Seq(convert(e.index, ctx), convert(e.clock, ctx)), convert(e.dir))
rec(acc :+ port, scope)(cmds.tail)
case Connect(info, loc, exp) =>
val con = fir.Connect(convert(info), convert(loc, ctx), convert(exp, ctx))
rec(acc :+ con, scope)(cmds.tail)
case BulkConnect(info, loc, exp) =>
val con = fir.PartialConnect(convert(info), convert(loc, ctx), convert(exp, ctx))
Copy link
Contributor

Choose a reason for hiding this comment

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

Are FIRRTL PartialConnect semantics equivalent to Chisel BulkConnect semantics? Or should this exception out because this should never be seen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FIRRTL PartialConnect semantics are closer to Chisel2 (and the current Chisel._ <>) semantics actually. It's still used by the compatibility wrapper.

rec(acc :+ con, scope)(cmds.tail)
case Attach(info, locs) =>
val att = fir.Attach(convert(info), locs.map(l => convert(l, ctx)))
rec(acc :+ att, scope)(cmds.tail)
case DefInvalid(info, arg) =>
val inv = fir.IsInvalid(convert(info), convert(arg, ctx))
rec(acc :+ inv, scope)(cmds.tail)
case e @ DefInstance(info, id, _) =>
val inst = fir.DefInstance(convert(info), e.name, id.name)
rec(acc :+ inst, scope)(cmds.tail)
case WhenBegin(info, pred) =>
val when = fir.Conditionally(convert(info), convert(pred, ctx),
fir.EmptyStmt, fir.EmptyStmt)
val frame = WhenFrame(when, acc, false)
rec(Queue.empty, frame +: scope)(cmds.tail)
case end @ WhenEnd(info, depth, _) =>
val frame = scope.head
val when = if (frame.alt) frame.when.copy(alt = fir.Block(acc))
else frame.when.copy(conseq = fir.Block(acc))
cmds.tail.headOption match {
case Some(AltBegin(_)) =>
assert(!frame.alt, "Internal Error! Unexpected when structure!")
rec(Queue.empty, frame.copy(when = when, alt = true) +: scope.tail)(cmds.drop(2))
case _ => // Not followed by otherwise
val cmdsx = if (depth > 0) WhenEnd(info, depth - 1, false) +: cmds.tail else cmds.tail
rec(frame.outer :+ when, scope.tail)(cmdsx)
}
case OtherwiseEnd(info, depth) =>
val frame = scope.head
val when = frame.when.copy(alt = fir.Block(acc))
val cmdsx = if (depth > 1) OtherwiseEnd(info, depth - 1) +: cmds.tail else cmds.tail
rec(scope.head.outer :+ when, scope.tail)(cmdsx)
case Stop(info, clock, ret) =>
val stop = fir.Stop(convert(info), ret, convert(clock, ctx), firrtl.Utils.one)
rec(acc :+ stop, scope)(cmds.tail)
case Printf(info, clock, pable) =>
val (fmt, args) = unpack(pable, ctx)
val p = fir.Print(convert(info), fir.StringLit(fmt),
args.map(a => convert(a, ctx)), convert(clock, ctx), firrtl.Utils.one)
rec(acc :+ p, scope)(cmds.tail)
}
}
fir.Block(rec(Queue.empty, List.empty)(cmds))
}

def convert(width: Width): fir.Width = width match {
case UnknownWidth() => fir.UnknownWidth
case KnownWidth(value) => fir.IntWidth(value)
}

def convert(bp: BinaryPoint): fir.Width = bp match {
case UnknownBinaryPoint => fir.UnknownWidth
case KnownBinaryPoint(value) => fir.IntWidth(value)
}
Copy link
Member

Choose a reason for hiding this comment

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

I was confused on the exact replication of convert(BinaryPoint) and convert(Width), but this is how it's done in chisel3.internal.firrtl (with associated replication there). That may benefit from a refactor, but that is clearly out of scope of this encapsulated PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah that was my thinking, a later refactor can clean a lot of stuff related to this and the Emitter


private def firrtlUserDirOf(d: Data): SpecifiedDirection = d match {
case d: Vec[_] =>
SpecifiedDirection.fromParent(d.specifiedDirection, firrtlUserDirOf(d.sample_element))
case d => d.specifiedDirection
}

def extractType(data: Data, clearDir: Boolean = false): fir.Type = data match {
case _: Clock => fir.ClockType
case d: UInt => fir.UIntType(convert(d.width))
case d: SInt => fir.SIntType(convert(d.width))
case d: FixedPoint => fir.FixedType(convert(d.width), convert(d.binaryPoint))
case d: Analog => fir.AnalogType(convert(d.width))
case d: Vec[_] => fir.VectorType(extractType(d.sample_element, clearDir), d.length)
case d: Record =>
val childClearDir = clearDir ||
d.specifiedDirection == SpecifiedDirection.Input || d.specifiedDirection == SpecifiedDirection.Output
def eltField(elt: Data): fir.Field = (childClearDir, firrtlUserDirOf(elt)) match {
case (true, _) => fir.Field(elt.getRef.name, fir.Default, extractType(elt, true))
case (false, SpecifiedDirection.Unspecified | SpecifiedDirection.Output) =>
fir.Field(elt.getRef.name, fir.Default, extractType(elt, false))
case (false, SpecifiedDirection.Flip | SpecifiedDirection.Input) =>
fir.Field(elt.getRef.name, fir.Flip, extractType(elt, false))
}
fir.BundleType(d.elements.toIndexedSeq.reverse.map { case (_, e) => eltField(e) })
}

def convert(name: String, param: Param): fir.Param = param match {
case IntParam(value) => fir.IntParam(name, value)
case DoubleParam(value) => fir.DoubleParam(name, value)
case StringParam(value) => fir.StringParam(name, fir.StringLit(value))
case RawParam(value) => fir.RawStringParam(name, value)
}
def convert(port: Port, topDir: SpecifiedDirection = SpecifiedDirection.Unspecified): fir.Port = {
val resolvedDir = SpecifiedDirection.fromParent(topDir, port.dir)
val dir = resolvedDir match {
case SpecifiedDirection.Unspecified | SpecifiedDirection.Output => fir.Output
case SpecifiedDirection.Flip | SpecifiedDirection.Input => fir.Input
}
val clearDir = resolvedDir match {
case SpecifiedDirection.Input | SpecifiedDirection.Output => true
case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => false
}
val tpe = extractType(port.id, clearDir)
fir.Port(fir.NoInfo, port.id.getRef.name, dir, tpe)
}

def convert(component: Component): fir.DefModule = component match {
case ctx @ DefModule(_, name, ports, cmds) =>
fir.Module(fir.NoInfo, name, ports.map(p => convert(p)), convert(cmds, ctx))
case ctx @ DefBlackBox(id, name, ports, topDir, params) =>
fir.ExtModule(fir.NoInfo, name, ports.map(p => convert(p, topDir)), id.desiredName,
params.map { case (name, p) => convert(name, p) }.toSeq)
}

def convert(circuit: Circuit): fir.Circuit =
fir.Circuit(fir.NoInfo, circuit.components.map(convert), circuit.name)
}

6 changes: 2 additions & 4 deletions src/test/scala/chiselTests/PrintableSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ import chisel3.testers.BasicTester

/* Printable Tests */
class PrintableSpec extends FlatSpec with Matchers {
private val PrintfRegex = """\s*printf\((.*)\).*""".r
// This regex is brittle, it relies on the first two arguments of the printf
// not containing quotes, problematic if Chisel were to emit UInt<1>("h01")
// instead of the current UInt<1>(1) for the enable signal
// This regex is brittle, it specifically finds the clock and enable signals followed by commas
private val PrintfRegex = """\s*printf\(\w+, [^,]+,(.*)\).*""".r
private val StringRegex = """([^"]*)"(.*?)"(.*)""".r
private case class Printf(str: String, args: Seq[String])
private def getPrintfs(firrtl: String): Seq[Printf] = {
Expand Down