diff options
| author | Adam Izraelevitz | 2015-10-05 10:28:41 -0700 |
|---|---|---|
| committer | Adam Izraelevitz | 2015-10-05 10:28:41 -0700 |
| commit | 0a9dfbe9f58338fc8af11015f6e9227e0cb46ea4 (patch) | |
| tree | 6ecac3e05509202dc7ad94fb03f41300ebf89619 | |
| parent | 0a9cc3d72fa2c6f19385efa8350f232e2e9faf10 (diff) | |
| parent | edd894af01d004b417c11f264c5302896f321c7d (diff) | |
Merge pull request #44 from jackkoenig/master
Add Scala implementation to firrtl repo
| -rw-r--r-- | .gitignore | 15 | ||||
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | build.sbt | 14 | ||||
| -rw-r--r-- | project/sbt-antlr4.sbt | 3 | ||||
| -rw-r--r-- | src/main/antlr4/FIRRTL.g4 | 207 | ||||
| -rw-r--r-- | src/main/scala/Example.scala | 19 | ||||
| -rw-r--r-- | src/main/scala/firrtl/IR.scala | 110 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Parser.scala | 44 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Translator.scala | 115 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Utils.scala | 194 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Visitor.scala | 185 |
11 files changed, 908 insertions, 4 deletions
@@ -29,7 +29,14 @@ spec/spec.toc spec/spec.out spec/spec.synctex.gz notes/*.docx -build.sbt -target -project -src/main/scala + +# sbt specific +.cache +.history +.lib/ +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ @@ -18,3 +18,9 @@ `make build # Build firrtl` `make check # Run tests` `make # Build & test` + +#### Scala implementation +The Scala FIRRTL implementation relies upon sbt. +Example use: + `sbt -mem 2048 "run-main Example <input> <output>` +This command will read in <input> FIRRTL file, parse it, then output the AST to <output> diff --git a/build.sbt b/build.sbt new file mode 100644 index 00000000..0a083994 --- /dev/null +++ b/build.sbt @@ -0,0 +1,14 @@ +lazy val root = (project in file(".")). + settings( + name := "firrtl", + version := "1.0", + scalaVersion := "2.11.4" + ) + +antlr4Settings + +antlr4GenVisitor in Antlr4 := true // default = false + +antlr4GenListener in Antlr4 := false // default = true + +antlr4PackageName in Antlr4 := Option("firrtl.antlr") diff --git a/project/sbt-antlr4.sbt b/project/sbt-antlr4.sbt new file mode 100644 index 00000000..df6819fa --- /dev/null +++ b/project/sbt-antlr4.sbt @@ -0,0 +1,3 @@ +resolvers += "simplytyped" at "http://simplytyped.github.io/repo/releases" + +addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.7.7") diff --git a/src/main/antlr4/FIRRTL.g4 b/src/main/antlr4/FIRRTL.g4 new file mode 100644 index 00000000..08697bb1 --- /dev/null +++ b/src/main/antlr4/FIRRTL.g4 @@ -0,0 +1,207 @@ +// Jack Koenig +// UC Berkeley ASPIRE Lab +// July 9, 2015 + +grammar FIRRTL; + +/*------------------------------------------------------------------ + * PARSER RULES + *------------------------------------------------------------------*/ + +// TODO add [info] support (all over the place) +// TODO Fix connect +// TODO Add partial connect +// TODO Should FIRRTL keywords be legal IDs? + +// Does there have to be at least one module? +circuit + : 'circuit' id ':' '{' module* '}' + ; + +// TODO Add support for extmodule +module + : 'module' id ':' '{' port* blockStmt '}' + ; + +port + : portKind id ':' type + ; + +portKind + : 'input' + | 'output' + ; + +type + : 'UInt' '<' width '>' + | 'SInt' '<' width '>' + | 'Clock' + | '{' field* '}' // Bundle + | type '[' IntLit ']' // Vector + ; + +field + : orientation id ':' type + ; + +// FIXME This is what the spec says it should be +//orientation +// : 'default' +// | 'reverse' +// ; +orientation + : 'flip' + | // Nothing + ; + +width + : IntLit + | '?' + ; + +// Much faster than replacing blockStmt with stmt+ +blockStmt + : (stmt)* + ; + +stmt + : 'wire' id ':' type + | 'reg' id ':' type exp exp + | 'smem' id ':' type exp + | 'cmem' id ':' type exp + | 'inst' id (':' | 'of') id // FIXME which should it be? ':' or 'of' + | 'node' id '=' exp + | 'poison' id ':' type // Poison, FIXME + | dir 'accessor' id '=' exp '[' exp ']' exp? // FIXME what is this extra exp? + | exp ':=' exp // Connect + | 'onreset' exp ':=' exp + | exp '<>' exp // Bulk Connect + | exp '[' IntLit 'through' IntLit ']' ':=' exp // SubWordConnect + | 'when' exp ':' '{' blockStmt '}' ( 'else' ':' '{' blockStmt '}' )? + | 'assert' exp + | 'skip' + ; + +// Accessor Direction +dir + : 'infer' + | 'read' + | 'write' + | 'rdwr' + ; + +// TODO implement +// What is exp? +exp + : 'UInt' '<' width '>' '(' (IntLit) ')' // FIXME what does "ints" mean? + | 'SInt' '<' width '>' '(' (IntLit) ')' // FIXME same + | id // Ref + | exp '.' id // FIXME Does this work for no space? + | exp '[' IntLit ']' + | primop '(' exp* IntLit* ')' // FIXME Need a big check here + ; + +id + : Id + | keyword + ; + +// FIXME need to make sure this is exhaustive including all FIRRTL keywords that are legal IDs +keyword + : primop + | dir + | 'inst' + ; + +primop + : 'add' + | 'sub' + | 'addw' + | 'subw' + | 'mul' + | 'div' + | 'mod' + | 'quo' + | 'rem' + | 'lt' + | 'leq' + | 'gt' + | 'geq' + | 'eq' + | 'neq' + | 'mux' + | 'pad' + | 'asUInt' + | 'asSInt' + | 'shl' + | 'shr' + | 'dshl' + | 'dshr' + | 'cvt' + | 'neg' + | 'not' + | 'and' + | 'or' + | 'xor' + | 'andr' + | 'orr' + | 'xorr' + | 'cat' + | 'bit' + | 'bits' + ; + +/*------------------------------------------------------------------ + * LEXER RULES + *------------------------------------------------------------------*/ + +Id + : IdNondigit + ( IdNondigit + | Digit + )* + ; + +fragment +IdNondigit + : Nondigit + | [~!@#$%^*-+=?/] + ; + +// Should enforcing signed, non-neg, and positive ints be done in parser? +// => YES +IntLit + : '0' + | ( '+' | '-' )? [1-9] ( Digit )* + | '"' 'h' ( HexDigit )+ '"' + ; + +fragment +Nondigit + : [a-zA-Z_] + ; + +fragment +Digit + : [0-9] + ; + +fragment +HexDigit + : [a-zA-Z0-9] + ; + +Comment + : ';' ~[\r\n]* + -> skip + ; + +Whitespace + : [ \t,]+ + -> skip + ; + +Newline + : ( '\r'? '\n' )+ + -> skip + ; diff --git a/src/main/scala/Example.scala b/src/main/scala/Example.scala new file mode 100644 index 00000000..210a50fb --- /dev/null +++ b/src/main/scala/Example.scala @@ -0,0 +1,19 @@ +import java.io._ +import firrtl._ +import firrtl.Utils._ + +object Example +{ + // Example use of Scala FIRRTL parser and serialization + def main(args: Array[String]) + { + val inputFile = args(0) + + // Parse file + val ast = firrtl.Parser.parse(inputFile) + + val writer = new PrintWriter(new File(args(1))) + writer.write(ast.serialize) // serialize returns String + writer.close() + } +} diff --git a/src/main/scala/firrtl/IR.scala b/src/main/scala/firrtl/IR.scala new file mode 100644 index 00000000..c52c45a4 --- /dev/null +++ b/src/main/scala/firrtl/IR.scala @@ -0,0 +1,110 @@ +package firrtl + +import scala.collection.Seq + +// Should this be defined elsewhere? +case class FileInfo(file: String, line: Int, column: Int) { + override def toString(): String = s"$file@$line.$column" +} + +trait AST + +trait PrimOp extends AST +case object Add extends PrimOp +case object Sub extends PrimOp +case object Addw extends PrimOp +case object Subw extends PrimOp +case object Mul extends PrimOp +case object Div extends PrimOp +case object Mod extends PrimOp +case object Quo extends PrimOp +case object Rem extends PrimOp +case object Lt extends PrimOp +case object Leq extends PrimOp +case object Gt extends PrimOp +case object Geq extends PrimOp +case object Eq extends PrimOp +case object Neq extends PrimOp +case object Mux extends PrimOp +case object Pad extends PrimOp +case object AsUInt extends PrimOp +case object AsSInt extends PrimOp +case object Shl extends PrimOp +case object Shr extends PrimOp +case object Dshl extends PrimOp +case object Dshr extends PrimOp +case object Cvt extends PrimOp +case object Neg extends PrimOp +case object Not extends PrimOp +case object And extends PrimOp +case object Or extends PrimOp +case object Xor extends PrimOp +case object Andr extends PrimOp +case object Orr extends PrimOp +case object Xorr extends PrimOp +case object Cat extends PrimOp +case object Bit extends PrimOp +case object Bits extends PrimOp + +// TODO stanza ir has types on many of these, why? Is it the type of what we're referencing? +// Add types, default to UNKNOWN +// TODO add type +trait Exp extends AST +case class UIntValue(value: BigInt, width: BigInt) extends Exp +case class SIntValue(value: BigInt, width: BigInt) extends Exp +case class Ref(name: String, tpe: Type) extends Exp +case class Subfield(exp: Exp, name: String, tpe: Type) extends Exp +case class Subindex(exp: Exp, value: BigInt) extends Exp +case class DoPrimOp(op: PrimOp, args: Seq[Exp], consts: Seq[BigInt]) extends Exp + +trait AccessorDir extends AST +case object Infer extends AccessorDir +case object Read extends AccessorDir +case object Write extends AccessorDir +case object RdWr extends AccessorDir + +trait Stmt extends AST +case class DefWire(info: FileInfo, name: String, tpe: Type) extends Stmt +case class DefReg(info: FileInfo, name: String, tpe: Type, clock: Exp, reset: Exp) extends Stmt +case class DefMemory(info: FileInfo, name: String, seq: Boolean, tpe: Type, clock: Exp) extends Stmt +case class DefInst(info: FileInfo, name: String, module: Exp) extends Stmt +case class DefNode(info: FileInfo, name: String, value: Exp) extends Stmt +case class DefPoison(info: FileInfo, name: String, tpe: Type) extends Stmt +case class DefAccessor(info: FileInfo, name: String, dir: AccessorDir, source: Exp, index: Exp) extends Stmt +case class OnReset(info: FileInfo, lhs: Exp, rhs: Exp) extends Stmt +case class Connect(info: FileInfo, lhs: Exp, rhs: Exp) extends Stmt +case class BulkConnect(info: FileInfo, lhs: Exp, rhs: Exp) extends Stmt +case class When(info: FileInfo, pred: Exp, conseq: Stmt, alt: Stmt) extends Stmt +case class Assert(info: FileInfo, pred: Exp) extends Stmt +case class Block(stmts: Seq[Stmt]) extends Stmt +case object EmptyStmt extends Stmt + +trait Width extends AST +case class IntWidth(width: BigInt) extends Width +case object UnknownWidth extends Width + +trait FieldDir extends AST +case object Default extends FieldDir +case object Reverse extends FieldDir + +case class Field(name: String, dir: FieldDir, tpe: Type) extends AST + +trait Type extends AST +case class UIntType(width: Width) extends Type +case class SIntType(width: Width) extends Type +case object ClockType extends Type +case class BundleType(fields: Seq[Field]) extends Type +case class VectorType(tpe: Type, size: BigInt) extends Type +case object UnknownType extends Type + +trait PortDir extends AST +case object Input extends PortDir +case object Output extends PortDir + +case class Port(info: FileInfo, name: String, dir: PortDir, tpe: Type) extends AST + +case class Module(info: FileInfo, name: String, ports: Seq[Port], stmt: Stmt) extends AST + +case class Circuit(info: FileInfo, name: String, modules: Seq[Module]) extends AST + + diff --git a/src/main/scala/firrtl/Parser.scala b/src/main/scala/firrtl/Parser.scala new file mode 100644 index 00000000..35e41222 --- /dev/null +++ b/src/main/scala/firrtl/Parser.scala @@ -0,0 +1,44 @@ +package firrtl + +import org.antlr.v4.runtime._; +import org.antlr.v4.runtime.atn._; +import org.antlr.v4.runtime.tree._; +import java.io.FileInputStream +import scala.collection.JavaConverters._ +import scala.io.Source +import Utils._ +import antlr._ + +object Parser +{ + + /** Takes a firrtl filename, returns AST (root node is Circuit) + * + * Currently must be standard FIRRTL file + * Parser performs conversion to machine firrtl + */ + def parse(filename: String): Circuit = { + //val antlrStream = new ANTLRInputStream(input.reader) + val fixedInput = Translator.addBrackets(Source.fromFile(filename).getLines) + val antlrStream = new ANTLRInputStream(fixedInput.result) + val lexer = new FIRRTLLexer(antlrStream) + val tokens = new CommonTokenStream(lexer) + val parser = new FIRRTLParser(tokens) + + // FIXME Dangerous + parser.getInterpreter.setPredictionMode(PredictionMode.SLL) + + // Concrete Syntax Tree + val cst = parser.circuit + + val visitor = new Visitor(filename) + //val ast = visitor.visitCircuit(cst) match { + val ast = visitor.visit(cst) match { + case c: Circuit => c + case x => throw new ClassCastException("Error! AST not rooted with Circuit node!") + } + + ast + } + +} diff --git a/src/main/scala/firrtl/Translator.scala b/src/main/scala/firrtl/Translator.scala new file mode 100644 index 00000000..eba78b39 --- /dev/null +++ b/src/main/scala/firrtl/Translator.scala @@ -0,0 +1,115 @@ + +/* TODO + * - Add support for comments (that being said, current Scopers regex should ignore commented lines) + * - Add better error messages for illformed FIRRTL + * - Add support for files that do not have a circuit (like a module by itself in a file) + * - Improve performance? Replace regex? + * - Add proper commnad-line arguments? + * - Wrap in Reader subclass. This would have less memory footprint than creating a large string + */ + +package firrtl + +import scala.io.Source +import scala.collection.mutable.Stack +import scala.collection.mutable.StringBuilder +import java.io._ + + +object Translator +{ + + def addBrackets(inputIt: Iterator[String]): StringBuilder = { + def countSpaces(s: String): Int = s.prefixLength(_ == ' ') + + val Scopers = """\s*(circuit|module|when|else)(.*)""".r + + // Function start + val it = inputIt.zipWithIndex + var ret = new StringBuilder() + + if( !it.hasNext ) throw new Exception("Empty file!") + + // Find circuit before starting scope checks + var line = it.next + while ( it.hasNext && !line._1.contains("circuit") ) { + ret ++= line._1 + "\n" + line = it.next + } + ret ++= line._1 + " { \n" + if( !it.hasNext ) throw new Exception("No circuit in file!") + + + val scope = Stack[Int]() + scope.push(countSpaces(line._1)) + var newScope = true // indicates if increasing scope spacing is legal on next line + + while( it.hasNext ) { + it.next match { case (text, lineNum) => + val spaces = countSpaces(text) + + val l = if (text.length > spaces ) { // Check that line has text in it + if (newScope) { + scope.push(countSpaces(text)) + } else { + + // Check if change in current scope + if( spaces < scope.top ) { + while( spaces < scope.top ) { + // Close scopes (adding brackets as we go) + scope.pop() + ret.deleteCharAt(ret.lastIndexOf("\n")) // Put on previous line + ret ++= " }\n" + } + if( spaces != scope.top ) + throw new Exception("Spacing does not match scope on line : " + lineNum + " : " + scope.top) + } + else if( spaces > scope.top ) + throw new Exception("Invalid increase in scope on line " + lineNum) + } + // Now match on legal scope increasers + text match { + case Scopers(keyword, _* ) => { + newScope = true + text + " { " + } + case _ => { + newScope = false + text + } + } + } // if( text.length > spaces ) + else { + text // empty lines + } + + ret ++= l + "\n" + } // it.next match + } // while( it.hasNext ) + + // Print any closing braces + while( scope.top > 0 ) { + scope.pop() + ret.deleteCharAt(ret.lastIndexOf("\n")) // Put on previous line + ret ++= " }\n" + } + + ret + } + + def main(args: Array[String]) { + + try { + val translation = addBrackets(Source.fromFile(args(0)).getLines) + + val writer = new PrintWriter(new File(args(1))) + writer.write(translation.result) + writer.close() + } catch { + case e: Exception => { + throw new Exception("USAGE: Translator <input file> <output file>\n" + e) + } + } + } + +} diff --git a/src/main/scala/firrtl/Utils.scala b/src/main/scala/firrtl/Utils.scala new file mode 100644 index 00000000..916408bc --- /dev/null +++ b/src/main/scala/firrtl/Utils.scala @@ -0,0 +1,194 @@ +// Utility functions for FIRRTL IR + +/* TODO + * - Adopt style more similar to Chisel3 Emitter? + */ + +package firrtl + +import scala.collection.mutable.StringBuilder + +object Utils { + + implicit class BigIntUtils(bi: BigInt){ + def serialize(): String = + "\"h0" + bi.toString(16) + "\"" + } + + implicit class PrimOpUtils(op: PrimOp) { + def serialize(): String = { + op match { + case Add => "add" + case Sub => "sub" + case Addw => "addw" + case Subw => "subw" + case Mul => "mul" + case Div => "div" + case Mod => "mod" + case Quo => "quo" + case Rem => "rem" + case Lt => "lt" + case Leq => "leq" + case Gt => "gt" + case Geq => "geq" + case Eq => "eq" + case Neq => "neq" + case Mux => "mux" + case Pad => "pad" + case AsUInt => "asUInt" + case AsSInt => "asSInt" + case Shl => "shl" + case Shr => "shr" + case Dshl => "dshl" + case Dshr => "dshr" + case Cvt => "cvt" + case Neg => "neg" + case Not => "not" + case And => "and" + case Or => "or" + case Xor => "xor" + case Andr => "andr" + case Orr => "orr" + case Xorr => "xorr" + case Cat => "cat" + case Bit => "bit" + case Bits => "bits" + } + } + } + + implicit class ExpUtils(exp: Exp) { + def serialize(): String = + exp match { + case v: UIntValue => s"UInt<${v.width}>(${v.value.serialize})" + case v: SIntValue => s"SInt<${v.width}>(${v.value.serialize})" + case r: Ref => r.name + case s: Subfield => s"${s.exp.serialize}.${s.name}" + case s: Subindex => s"${s.exp.serialize}[${s.value}]" + case p: DoPrimOp => + s"${p.op.serialize}(" + (p.args.map(_.serialize) ++ p.consts.map(_.toString)).mkString(", ") + ")" + } + } + + // AccessorDir + implicit class AccessorDirUtils(dir: AccessorDir) { + def serialize(): String = + dir match { + case Infer => "infer" + case Read => "read" + case Write => "write" + case RdWr => "rdwr" + } + } + + + implicit class StmtUtils(stmt: Stmt) { + def serialize(): String = + stmt match { + case w: DefWire => s"wire ${w.name} : ${w.tpe.serialize}" + case r: DefReg => s"reg ${r.name} : ${r.tpe.serialize}, ${r.clock.serialize}, ${r.reset.serialize}" + case m: DefMemory => (if(m.seq) "smem" else "cmem") + + s" ${m.name} : ${m.tpe.serialize}, ${m.clock.serialize}" + case i: DefInst => s"inst ${i.name} of ${i.module.serialize}" + case n: DefNode => s"node ${n.name} = ${n.value.serialize}" + case p: DefPoison => s"poison ${p.name} : ${p.tpe.serialize}" + case a: DefAccessor => s"${a.dir.serialize} accessor ${a.name} = ${a.source.serialize}[${a.index.serialize}]" + case c: Connect => s"${c.lhs.serialize} := ${c.rhs.serialize}" + case o: OnReset => s"onreset ${o.lhs.serialize} := ${o.rhs.serialize}" + case b: BulkConnect => s"${b.lhs.serialize} <> ${b.rhs.serialize}" + case w: When => { + var str = new StringBuilder(s"when ${w.pred.serialize} : ") + withIndent { str ++= w.conseq.serialize } + if( w.alt != EmptyStmt ) { + str ++= newline + "else :" + withIndent { str ++= w.alt.serialize } + } + str.result + } + //case b: Block => b.stmts.map(newline ++ _.serialize).mkString + case b: Block => { + val s = new StringBuilder + b.stmts.foreach { s ++= newline ++ _.serialize } + s.result + } + case a: Assert => s"assert ${a.pred.serialize}" + case EmptyStmt => "skip" + } + } + + implicit class WidthUtils(w: Width) { + def serialize(): String = + w match { + case UnknownWidth => "?" + case w: IntWidth => w.width.toString + } + } + + implicit class FieldDirUtils(dir: FieldDir) { + def serialize(): String = + dir match { + case Reverse => "flip " + case Default => "" + } + } + + implicit class FieldUtils(field: Field) { + def serialize(): String = + s"${field.dir.serialize} ${field.name} : ${field.tpe.serialize}" + } + + implicit class TypeUtils(t: Type) { + def serialize(): String = { + val commas = ", " // for mkString in BundleType + t match { + case ClockType => "Clock" + case UnknownType => "UnknownType" + case t: UIntType => s"UInt<${t.width.serialize}>" + case t: SIntType => s"SInt<${t.width.serialize}>" + case t: BundleType => s"{ ${t.fields.map(_.serialize).mkString(commas)} }" + case t: VectorType => s"${t.tpe.serialize}[${t.size}]" + } + } + } + + implicit class PortDirUtils(p: PortDir) { + def serialize(): String = + p match { + case Input => "input" + case Output => "output" + } + } + + implicit class PortUtils(p: Port) { + def serialize(): String = + s"${p.dir.serialize} ${p.name} : ${p.tpe.serialize}" + } + + implicit class ModuleUtils(m: Module) { + def serialize(): String = { + var s = new StringBuilder(s"module ${m.name} : ") + withIndent { + s ++= m.ports.map(newline ++ _.serialize).mkString + s ++= newline ++ m.stmt.serialize + } + s.toString + } + } + + implicit class CircuitUtils(c: Circuit) { + def serialize(): String = { + var s = new StringBuilder(s"circuit ${c.name} : ") + //withIndent { c.modules.foreach(s ++= newline ++ newline ++ _.serialize) } + withIndent { s ++= newline ++ c.modules.map(_.serialize).mkString(newline + newline) } + s ++= newline ++ newline + s.toString + } + } + + private var indentLevel = 0 + private def newline = "\n" + (" " * indentLevel) + private def indent(): Unit = indentLevel += 1 + private def unindent() { require(indentLevel > 0); indentLevel -= 1 } + private def withIndent(f: => Unit) { indent(); f; unindent() } + +} diff --git a/src/main/scala/firrtl/Visitor.scala b/src/main/scala/firrtl/Visitor.scala new file mode 100644 index 00000000..276facce --- /dev/null +++ b/src/main/scala/firrtl/Visitor.scala @@ -0,0 +1,185 @@ +/* + * TODO FIXME + * - Support all integer types (not just "h...") + * - In ANTLR examples they use just visit, why am I having to use visitModule or other specific functions? + * - Make visit private? +*/ + +package firrtl + +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; +import org.antlr.v4.runtime.ParserRuleContext +import org.antlr.v4.runtime.tree.ErrorNode +import org.antlr.v4.runtime.tree.TerminalNode +import scala.collection.JavaConversions._ +import antlr._ + +class Visitor(val fullFilename: String) extends FIRRTLBaseVisitor[AST] +{ + // Strip file path + private val filename = fullFilename.drop(fullFilename.lastIndexOf("/")+1) + + // For some reason visitCircuit does not visit the right function + // FIXME for some reason this cannot be private, probably because it extends + // FIRRTLBaseVisitor which is in a subpackage? + def visit[AST](ctx: FIRRTLParser.CircuitContext): Circuit = visitCircuit(ctx) + + // These regex have to change if grammar changes + private def string2BigInt(s: String): BigInt = { + // private define legal patterns + val HexPattern = """\"*h([a-zA-Z0-9]+)\"*""".r + val DecPattern = """(\+|-)?([1-9]\d*)""".r + val ZeroPattern = "0".r + s match { + case ZeroPattern(_*) => BigInt(0) + case HexPattern(hexdigits) => BigInt(hexdigits, 16) + case DecPattern(sign, num) => BigInt(num) + case _ => throw new Exception("Invalid String for conversion to BigInt " + s) + } + } + private def getFileInfo(ctx: ParserRuleContext): FileInfo = + FileInfo(filename, ctx.getStart().getLine(), ctx.getStart().getCharPositionInLine()) + + private def visitCircuit[AST](ctx: FIRRTLParser.CircuitContext): Circuit = + Circuit(getFileInfo(ctx), ctx.id.getText, ctx.module.map(visitModule)) + + private def visitModule[AST](ctx: FIRRTLParser.ModuleContext): Module = + Module(getFileInfo(ctx), ctx.id.getText, ctx.port.map(visitPort), visitBlockStmt(ctx.blockStmt)) + + private def visitPort[AST](ctx: FIRRTLParser.PortContext): Port = + Port(getFileInfo(ctx), ctx.id.getText, visitPortKind(ctx.portKind), visitType(ctx.`type`)) + + private def visitPortKind[AST](ctx: FIRRTLParser.PortKindContext): PortDir = + ctx.getText match { + case "input" => Input + case "output" => Output + } + + // Match on a type instead of on strings? + private def visitType[AST](ctx: FIRRTLParser.TypeContext): Type = { + ctx.getChild(0).getText match { + case "UInt" => UIntType( visitWidth(ctx.width) ) + case "SInt" => SIntType( visitWidth(ctx.width) ) + case "Clock" => ClockType + case "{" => BundleType(ctx.field.map(visitField)) + case _ => new VectorType( visitType(ctx.`type`), string2BigInt(ctx.IntLit.getText) ) + } + } + + private def visitField[AST](ctx: FIRRTLParser.FieldContext): Field = + Field(ctx.id.getText, visitOrientation(ctx.orientation), visitType(ctx.`type`)) + + private def visitOrientation[AST](ctx: FIRRTLParser.OrientationContext): FieldDir = + ctx.getText match { + case "flip" => Reverse + case _ => Default + } + + private def visitWidth[AST](ctx: FIRRTLParser.WidthContext): Width = { + val text = ctx.getText + text match { + case "?" => UnknownWidth + case _ => IntWidth(string2BigInt(text)) + } + } + + private def visitBlockStmt[AST](ctx: FIRRTLParser.BlockStmtContext): Stmt = + Block(ctx.stmt.map(visitStmt)) + + private def visitStmt[AST](ctx: FIRRTLParser.StmtContext): Stmt = { + val info = getFileInfo(ctx) + + ctx.getChild(0).getText match { + case "wire" => DefWire(info, ctx.id(0).getText, visitType(ctx.`type`)) + case "reg" => DefReg(info, ctx.id(0).getText, visitType(ctx.`type`), + visitExp(ctx.exp(0)), visitExp(ctx.exp(1))) + case "smem" => DefMemory(info, ctx.id(0).getText, true, + visitType(ctx.`type`), visitExp(ctx.exp(0))) + case "cmem" => DefMemory(info, ctx.id(0).getText, false, + visitType(ctx.`type`), visitExp(ctx.exp(0))) + case "inst" => DefInst(info, ctx.id(0).getText, Ref(ctx.id(1).getText, UnknownType)) + case "node" => DefNode(info, ctx.id(0).getText, visitExp(ctx.exp(0))) + case "poison" => DefPoison(info, ctx.id(0).getText, visitType(ctx.`type`)) + case "onreset" => OnReset(info, visitExp(ctx.exp(0)), visitExp(ctx.exp(1))) + case "when" => { + val alt = if (ctx.blockStmt.length > 1) visitBlockStmt(ctx.blockStmt(1)) else EmptyStmt + When(info, visitExp(ctx.exp(0)), visitBlockStmt(ctx.blockStmt(0)), alt) + } + case "assert" => Assert(info, visitExp(ctx.exp(0))) + case "skip" => EmptyStmt + // If we don't match on the first child, try the next one + case _ => { + ctx.getChild(1).getText match { + case "accessor" => DefAccessor(info, ctx.id(0).getText, + visitDir(ctx.dir), visitExp(ctx.exp(0)), visitExp(ctx.exp(1))) + case ":=" => Connect(info, visitExp(ctx.exp(0)), visitExp(ctx.exp(1)) ) + case "<>" => BulkConnect(info, visitExp(ctx.exp(0)), visitExp(ctx.exp(1)) ) + } + } + } + } + + private def visitDir[AST](ctx: FIRRTLParser.DirContext): AccessorDir = + ctx.getText match { + case "infer" => Infer + case "read" => Read + case "write" => Write + case "rdwr" => RdWr // TODO remove exceptions for unmatched things + } + + private def visitExp[AST](ctx: FIRRTLParser.ExpContext): Exp = + if( ctx.getChildCount == 1 ) + Ref(ctx.getText, UnknownType) + else + ctx.getChild(0).getText match { + case "UInt" => UIntValue(string2BigInt(ctx.IntLit(0).getText), string2BigInt(ctx.width.getText)) + case "SInt" => SIntValue(string2BigInt(ctx.IntLit(0).getText), string2BigInt(ctx.width.getText)) + case _ => + ctx.getChild(1).getText match { + case "." => new Subfield(visitExp(ctx.exp(0)), ctx.id.getText, UnknownType) + case "[" => new Subindex(visitExp(ctx.exp(0)), string2BigInt(ctx.IntLit(0).getText)) + case "(" => + DoPrimOp(visitPrimop(ctx.primop), ctx.exp.map(visitExp), ctx.IntLit.map(x => string2BigInt(x.getText))) + } + } + + // TODO can I create this and have the opposite? create map and invert it? + private def visitPrimop[AST](ctx: FIRRTLParser.PrimopContext): PrimOp = + ctx.getText match { + case "add" => Add + case "sub" => Sub + case "addw" => Addw + case "subw" => Subw + case "mul" => Mul + case "div" => Div + case "mod" => Mod + case "quo" => Quo + case "rem" => Rem + case "lt" => Lt + case "leq" => Leq + case "gt" => Gt + case "geq" => Geq + case "eq" => Eq + case "neq" => Neq + case "mux" => Mux + case "pad" => Pad + case "asUInt" => AsUInt + case "asSInt" => AsSInt + case "shl" => Shl + case "shr" => Shr + case "dshl" => Dshl + case "dshr" => Dshr + case "cvt" => Cvt + case "neg" => Neg + case "not" => Not + case "and" => And + case "or" => Or + case "xor" => Xor + case "andr" => Andr + case "orr" => Orr + case "xorr" => Xorr + case "cat" => Cat + case "bit" => Bit + case "bits" => Bits + } +} |
