diff options
| author | Jack Koenig | 2021-09-17 21:01:26 -0700 |
|---|---|---|
| committer | Jack Koenig | 2021-09-17 21:01:26 -0700 |
| commit | 5c8c19345e6711279594cf1f9ddab33623c8eba7 (patch) | |
| tree | d9d6ced3934aa4a8be3dec19ddcefe50a7a93d5a /src/main | |
| parent | e63b9667d89768e0ec6dc8a9153335cb48a213a7 (diff) | |
| parent | 958904cb2f2f65d02b2ab3ec6d9ec2e06d04e482 (diff) | |
Merge branch 'master' into 3.5-release
Diffstat (limited to 'src/main')
30 files changed, 1139 insertions, 678 deletions
diff --git a/src/main/resources/chisel3/top.cpp b/src/main/resources/chisel3/top.cpp index 4e9c1433..a3475e2f 100644 --- a/src/main/resources/chisel3/top.cpp +++ b/src/main/resources/chisel3/top.cpp @@ -47,8 +47,15 @@ int main(int argc, char** argv) { cout << "Starting simulation!\n"; while (!Verilated::gotFinish() && main_time < timeout) { - if (main_time > 10) { - top->reset = 0; // Deassert reset + // Deassert reset on timestep 10. This needs to occur before the clock + // asserts on timestep 11 because there is a single call to top->eval() in + // this loop. Verilator evaluates sequential logic (always blocks) before + // combinational logic during top->eval(). Staggering the reset update is + // necessary to produce the same simulation behavior independent of whether + // or not the generated Verilog puts synchronous reset logic inside or + // outside its associated always block. + if (main_time == 10) { + top->reset = 0; } if ((main_time % 10) == 1) { top->clock = 1; // Toggle clock @@ -92,4 +99,3 @@ int main(int argc, char** argv) { if (tfp) tfp->close(); #endif } - diff --git a/src/main/scala/chisel3/Driver.scala b/src/main/scala/chisel3/Driver.scala index 998f5ca0..fb564446 100644 --- a/src/main/scala/chisel3/Driver.scala +++ b/src/main/scala/chisel3/Driver.scala @@ -84,203 +84,3 @@ case class ChiselExecutionSuccess( */ @deprecated("This will be removed in Chisel 3.5", "Chisel 3.4") case class ChiselExecutionFailure(message: String) extends ChiselExecutionResult - -@deprecated("Please switch to chisel3.stage.ChiselStage", "3.2.4") -object Driver extends BackendCompilationUtilities { - - /** - * Elaborate the Module specified in the gen function into a Chisel IR Circuit. - * - * @param gen A function that creates a Module hierarchy. - * @return The resulting Chisel IR in the form of a Circuit. (TODO: Should be FIRRTL IR) - */ - @deprecated("Use ChiselStage.elaborate or use a ChiselStage class. This will be removed in 3.4.", "3.2.4") - def elaborate[T <: RawModule](gen: () => T): Circuit = internal.Builder.build(Module(gen()))._1 - - /** - * Convert the given Chisel IR Circuit to a FIRRTL Circuit. - * - * @param ir Chisel IR Circuit, generated e.g. by elaborate(). - */ - @deprecated("Use ChiselStage.convert or use a ChiselStage class. This will be removed in 3.4.", "3.2.4") - def toFirrtl(ir: Circuit): firrtl.ir.Circuit = Converter.convert(ir) - - /** - * Emit the Module specified in the gen function directly as a FIRRTL string without - * invoking FIRRTL. - * - * @param gen A function that creates a Module hierarchy. - */ - @deprecated("Use (new chisel3.stage.ChiselStage).emitChirrtl. This will be removed in 3.4.", "3.2.2") - def emit[T <: RawModule](gen: () => T): String = Driver.emit(elaborate(gen)) - - /** - * Emit the given Chisel IR Circuit as a FIRRTL string, without invoking FIRRTL. - * - * @param ir Chisel IR Circuit, generated e.g. by elaborate(). - */ - @deprecated("Use (new chisel3.stage.ChiselStage).emitChirrtl", "3.2.2") - def emit[T <: RawModule](ir: Circuit): String = Emitter.emit(ir) - - /** - * Elaborate the Module specified in the gen function into Verilog. - * - * @param gen A function that creates a Module hierarchy. - * @return A String containing the design in Verilog. - */ - @deprecated("Use (new chisel3.stage.ChiselStage).emitVerilog. This will be removed in 3.4.", "3.2.2") - def emitVerilog[T <: RawModule](gen: => T): String = { - execute(Array[String](), { () => gen }) match { - case ChiselExecutionSuccess(_, _, Some(firrtl.FirrtlExecutionSuccess(_, verilog))) => verilog - case _ => sys.error("Cannot get Verilog!") - } - } - - /** - * Dump the elaborated Chisel IR Circuit as a FIRRTL String, without invoking FIRRTL. - * - * If no File is given as input, it will dump to a default filename based on the name of the - * top Module. - * - * @param c Elaborated Chisel Circuit. - * @param optName File to dump to. If unspecified, defaults to "<topmodule>.fir". - * @return The File the circuit was dumped to. - */ - @deprecated("Migrate to chisel3.stage.ChiselStage. This will be removed in 3.4.", "3.2.4") - def dumpFirrtl(ir: Circuit, optName: Option[File]): File = { - val f = optName.getOrElse(new File(ir.name + ".fir")) - val w = new FileWriter(f) - w.write(Driver.emit(ir)) - w.close() - f - } - - /** - * Emit the annotations of a circuit - * - * @param ir The circuit containing annotations to be emitted - * @param optName An optional filename (will use s"\${ir.name}.json" otherwise) - */ - @deprecated("Migrate to chisel3.stage.ChiselStage. This will be removed in 3.4.", "3.2.4") - def dumpAnnotations(ir: Circuit, optName: Option[File]): File = { - val f = optName.getOrElse(new File(ir.name + ".anno.json")) - val w = new FileWriter(f) - w.write(JsonProtocol.serialize(ir.annotations.map(_.toFirrtl))) - w.close() - f - } - - /** - * Dump the elaborated Circuit to ProtoBuf. - * - * If no File is given as input, it will dump to a default filename based on the name of the - * top Module. - * - * @param c Elaborated Chisel Circuit. - * @param optFile Optional File to dump to. If unspecified, defaults to "<topmodule>.pb". - * @return The File the circuit was dumped to. - */ - @deprecated("Migrate to chisel3.stage.ChiselStage. This will be removed in 3.4.", "3.2.4") - def dumpProto(c: Circuit, optFile: Option[File]): File = { - val f = optFile.getOrElse(new File(c.name + ".pb")) - val ostream = new java.io.FileOutputStream(f) - // Lazily convert modules to make intermediate objects garbage collectable - val modules = c.components.map(m => () => Converter.convert(m)) - firrtl.proto.ToProto.writeToStreamFast(ostream, ir.NoInfo, modules, c.name) - f - } - - private var target_dir: Option[String] = None - @deprecated("Use chisel3.stage.ChiselStage with '--target-directory'. This will be removed in 3.4.", "3.2.2") - def parseArgs(args: Array[String]): Unit = { - for (i <- 0 until args.size) { - if (args(i) == "--targetDir") { - target_dir = Some(args(i + 1)) - } - } - } - - @deprecated("This has no effect on Chisel3 Driver! This will be removed in 3.4.", "3.2.2") - def targetDir(): String = { target_dir getOrElse new File(".").getCanonicalPath } - - /** - * Run the chisel3 compiler and possibly the firrtl compiler with options specified - * - * @param optionsManager The options specified - * @param dut The device under test - * @return An execution result with useful stuff, or failure with message - */ - @deprecated("Use chisel3.stage.ChiselStage.execute. This will be removed in 3.4.", "3.2.2") - def execute( - optionsManager: ExecutionOptionsManager with HasChiselExecutionOptions with HasFirrtlOptions, - dut: () => RawModule): ChiselExecutionResult = { - - val annos: AnnotationSeq = - Seq(DriverCompatibility.OptionsManagerAnnotation(optionsManager), ChiselGeneratorAnnotation(dut)) ++ - optionsManager.chiselOptions.toAnnotations ++ - optionsManager.firrtlOptions.toAnnotations ++ - optionsManager.commonOptions.toAnnotations - - val targets = - Seq( Dependency[DriverCompatibility.AddImplicitOutputFile], - Dependency[DriverCompatibility.AddImplicitOutputAnnotationFile], - Dependency[DriverCompatibility.DisableFirrtlStage], - Dependency[ChiselStage], - Dependency[DriverCompatibility.MutateOptionsManager], - Dependency[DriverCompatibility.ReEnableFirrtlStage], - Dependency[DriverCompatibility.FirrtlPreprocessing], - Dependency[chisel3.stage.phases.MaybeFirrtlStage] ) - val currentState = - Seq( Dependency[firrtl.stage.phases.DriverCompatibility.AddImplicitFirrtlFile], - Dependency[chisel3.stage.phases.Convert] ) - - val phases: Seq[Phase] = new PhaseManager(targets, currentState) { - override val wrappers = Seq( DeletedWrapper(_: Phase) ) - }.transformOrder - - val annosx = try { - phases.foldLeft(annos)( (a, p) => p.transform(a) ) - } catch { - /* ChiselStage and FirrtlStage can throw StageError. Since Driver is not a StageMain, it cannot catch these. While - * Driver is deprecated and removed in 3.2.1+, the Driver catches all errors. - */ - case e: StageError => annos - } - - view[ChiselExecutionResult](annosx) - } - - /** - * Run the chisel3 compiler and possibly the firrtl compiler with options specified via an array of Strings - * - * @param args The options specified, command line style - * @param dut The device under test - * @return An execution result with useful stuff, or failure with message - */ - @deprecated("Use chisel3.stage.ChiselStage.execute. This will be removed in 3.4.", "3.2.2") - def execute(args: Array[String], dut: () => RawModule): ChiselExecutionResult = { - val optionsManager = new ExecutionOptionsManager("chisel3") with HasChiselExecutionOptions with HasFirrtlOptions - - optionsManager.parse(args) match { - case true => - execute(optionsManager, dut) - case _ => - ChiselExecutionFailure("could not parse results") - } - } - - /** - * This is just here as command line way to see what the options are - * It will not successfully run - * TODO: Look into dynamic class loading as way to make this main useful - * - * @param args unused args - */ - @deprecated("Use chisel3.stage.ChiselMain. This will be removed in 3.4.", "3.2.2") - def main(args: Array[String]) { - execute(Array("--help"), null) - } - - val version = BuildInfo.version - val chiselVersionString = BuildInfo.toString -} diff --git a/src/main/scala/chisel3/aop/Select.scala b/src/main/scala/chisel3/aop/Select.scala index e2689f39..2384c4d3 100644 --- a/src/main/scala/chisel3/aop/Select.scala +++ b/src/main/scala/chisel3/aop/Select.scala @@ -3,12 +3,16 @@ package chisel3.aop import chisel3._ -import chisel3.experimental.{BaseModule, FixedPoint} -import chisel3.internal.HasId +import chisel3.internal.{HasId} +import chisel3.experimental.BaseModule +import chisel3.experimental.FixedPoint import chisel3.internal.firrtl._ +import chisel3.internal.PseudoModule +import chisel3.internal.BaseModule.ModuleClone import firrtl.annotations.ReferenceTarget import scala.collection.mutable +import chisel3.internal.naming.chiselName /** Use to select Chisel components in a module, after that module has been constructed * Useful for adding additional Chisel annotations or for use within an [[Aspect]] @@ -81,8 +85,13 @@ object Select { def instances(module: BaseModule): Seq[BaseModule] = { check(module) module._component.get match { - case d: DefModule => d.commands.collect { - case i: DefInstance => i.id + case d: DefModule => d.commands.flatMap { + case i: DefInstance => i.id match { + case m: ModuleClone[_] if !m._madeFromDefinition => None + case _: PseudoModule => throw new Exception("Aspect APIs are currently incompatible with Definition/Instance") + case other => Some(other) + } + case _ => None } case other => Nil } @@ -248,7 +257,7 @@ object Select { case other => } }) - predicatedConnects + predicatedConnects.toSeq } /** Selects all stop statements, and includes the predicates surrounding the stop statement @@ -264,7 +273,7 @@ object Select { case other => } }) - stops + stops.toSeq } /** Selects all printf statements, and includes the predicates surrounding the printf statement @@ -276,11 +285,11 @@ object Select { val printfs = mutable.ArrayBuffer[Printf]() searchWhens(module, (cmd: Command, preds: Seq[Predicate]) => { cmd match { - case chisel3.internal.firrtl.Printf(_, clock, pable) => printfs += Printf(preds, pable, getId(clock).asInstanceOf[Clock]) + case chisel3.internal.firrtl.Printf(id, _, clock, pable) => printfs += Printf(id, preds, pable, getId(clock).asInstanceOf[Clock]) case other => } }) - printfs + printfs.toSeq } // Checks that a module has finished its construction @@ -321,7 +330,7 @@ object Select { } } catch { case e: ChiselException => i.getOptionRef.get match { - case l: LitArg => l.num.intValue().toString + case l: LitArg => l.num.intValue.toString } } @@ -413,7 +422,7 @@ object Select { * @param pable * @param clock */ - case class Printf(preds: Seq[Predicate], pable: Printable, clock: Clock) extends Serializeable { + case class Printf(id: printf.Printf, preds: Seq[Predicate], pable: Printable, clock: Clock) extends Serializeable { def serialize: String = { s"printf when(${preds.map(_.serialize).mkString(" & ")}) on ${getName(clock)}: $pable" } diff --git a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala index 39590b93..1a476f61 100644 --- a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala +++ b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala @@ -4,6 +4,7 @@ package chisel3.aop.injecting import chisel3.{Module, ModuleAspect, RawModule, withClockAndReset} import chisel3.aop._ +import chisel3.experimental.hierarchy.IsInstantiable import chisel3.internal.{Builder, DynamicContext} import chisel3.internal.firrtl.DefModule import chisel3.stage.DesignAnnotation @@ -54,13 +55,14 @@ abstract class InjectorAspect[T <: RawModule, M <: RawModule]( * @return */ final def toAnnotation(modules: Iterable[M], circuit: String, moduleNames: Seq[String]): AnnotationSeq = { - val dynamicContext = new DynamicContext() - // Add existing module names into the namespace. If injection logic instantiates new modules - // which would share the same name, they will get uniquified accordingly - moduleNames.foreach { n => - dynamicContext.globalNamespace.name(n) - } RunFirrtlTransformAnnotation(new InjectingTransform) +: modules.map { module => + val dynamicContext = new DynamicContext(annotationsInAspect) + // Add existing module names into the namespace. If injection logic instantiates new modules + // which would share the same name, they will get uniquified accordingly + moduleNames.foreach { n => + dynamicContext.globalNamespace.name(n) + } + val (chiselIR, _) = Builder.build(Module(new ModuleAspect(module) { module match { case x: Module => withClockAndReset(x.clock, x.reset) { injection(module) } @@ -75,16 +77,20 @@ abstract class InjectorAspect[T <: RawModule, M <: RawModule]( val annotations = chiselIR.annotations.map(_.toFirrtl).filterNot{ a => a.isInstanceOf[DesignAnnotation[_]] } + /** Statements to be injected via aspect. */ val stmts = mutable.ArrayBuffer[ir.Statement]() + /** Modules to be injected via aspect. */ val modules = Aspect.getFirrtl(chiselIR.copy(components = comps)).modules.flatMap { + // for "container" modules, inject their statements case m: firrtl.ir.Module if m.name == module.name => stmts += m.body Nil + // for modules to be injected case other: firrtl.ir.DefModule => Seq(other) } - InjectStatement(ModuleTarget(circuit, module.name), ir.Block(stmts), modules, annotations) + InjectStatement(ModuleTarget(circuit, module.name), ir.Block(stmts.toSeq), modules, annotations) }.toSeq } } diff --git a/src/main/scala/chisel3/compatibility.scala b/src/main/scala/chisel3/compatibility.scala index e62cba7d..dde2321d 100644 --- a/src/main/scala/chisel3/compatibility.scala +++ b/src/main/scala/chisel3/compatibility.scala @@ -363,8 +363,6 @@ package object Chisel { implicit class fromIntToWidth(x: Int) extends chisel3.fromIntToWidth(x) type BackendCompilationUtilities = firrtl.util.BackendCompilationUtilities - @deprecated("Please switch to chisel3.stage.ChiselStage", "3.4") - val Driver = chisel3.Driver val ImplicitConversions = chisel3.util.ImplicitConversions // Deprecated as of Chisel3 @@ -400,8 +398,8 @@ package object Chisel { } // Deprecated as of Chsiel3 - @throws(classOf[Exception]) object throwException { + @throws(classOf[Exception]) def apply(s: String, t: Throwable = null): Nothing = { val xcpt = new Exception(s, t) throw xcpt @@ -455,7 +453,6 @@ package object Chisel { val Log2 = chisel3.util.Log2 - val unless = chisel3.util.unless type SwitchContext[T <: Bits] = chisel3.util.SwitchContext[T] val is = chisel3.util.is val switch = chisel3.util.switch diff --git a/src/main/scala/chisel3/internal/firrtl/Emitter.scala b/src/main/scala/chisel3/internal/firrtl/Emitter.scala index 354be0c0..a94558ce 100644 --- a/src/main/scala/chisel3/internal/firrtl/Emitter.scala +++ b/src/main/scala/chisel3/internal/firrtl/Emitter.scala @@ -1,194 +1,23 @@ // SPDX-License-Identifier: Apache-2.0 package chisel3.internal.firrtl -import chisel3._ -import chisel3.experimental.{Interval, _} -import chisel3.internal.BaseBlackBox -private[chisel3] object Emitter { - def emit(circuit: Circuit): String = new Emitter(circuit).toString -} - -private class Emitter(circuit: Circuit) { - override def toString: String = res.toString - - private def emitPort(e: Port, topDir: SpecifiedDirection=SpecifiedDirection.Unspecified): String = { - val resolvedDir = SpecifiedDirection.fromParent(topDir, e.dir) - val dirString = resolvedDir match { - case SpecifiedDirection.Unspecified | SpecifiedDirection.Output => "output" - case SpecifiedDirection.Flip | SpecifiedDirection.Input => "input" - } - val clearDir = resolvedDir match { - case SpecifiedDirection.Input | SpecifiedDirection.Output => true - case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => false - } - s"$dirString ${e.id.getRef.name} : ${emitType(e.id, clearDir)}" - } - - private def emitType(d: Data, clearDir: Boolean = false): String = d match { - case d: Clock => "Clock" - case _: AsyncReset => "AsyncReset" - case _: ResetType => "Reset" - case d: chisel3.core.EnumType => s"UInt${d.width}" - case d: UInt => s"UInt${d.width}" - case d: SInt => s"SInt${d.width}" - case d: FixedPoint => s"Fixed${d.width}${d.binaryPoint}" - case d: Interval => - val binaryPointString = d.binaryPoint match { - case UnknownBinaryPoint => "" - case KnownBinaryPoint(value) => s".$value" - } - d.toType - case d: Analog => s"Analog${d.width}" - case d: Vec[_] => s"${emitType(d.sample_element, clearDir)}[${d.length}]" - case d: Record => { - val childClearDir = clearDir || - d.specifiedDirection == SpecifiedDirection.Input || d.specifiedDirection == SpecifiedDirection.Output - def eltPort(elt: Data): String = (childClearDir, firrtlUserDirOf(elt)) match { - case (true, _) => - s"${elt.getRef.name} : ${emitType(elt, true)}" - case (false, SpecifiedDirection.Unspecified | SpecifiedDirection.Output) => - s"${elt.getRef.name} : ${emitType(elt, false)}" - case (false, SpecifiedDirection.Flip | SpecifiedDirection.Input) => - s"flip ${elt.getRef.name} : ${emitType(elt, false)}" - } - d.elements.toIndexedSeq.reverse.map(e => eltPort(e._2)).mkString("{", ", ", "}") - } - } - - private def firrtlUserDirOf(d: Data): SpecifiedDirection = d match { - case d: Vec[_] => - SpecifiedDirection.fromParent(d.specifiedDirection, firrtlUserDirOf(d.sample_element)) - case d => d.specifiedDirection - } - - private def emit(e: Command, ctx: Component): String = { - val firrtlLine = e match { - case e: DefPrim[_] => s"node ${e.name} = ${e.op.name}(${e.args.map(_.fullName(ctx)).mkString(", ")})" - case e: DefWire => s"wire ${e.name} : ${emitType(e.id)}" - case e: DefReg => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)}" - case e: DefRegInit => s"reg ${e.name} : ${emitType(e.id)}, ${e.clock.fullName(ctx)} with : (reset => (${e.reset.fullName(ctx)}, ${e.init.fullName(ctx)}))" - case e: DefMemory => s"cmem ${e.name} : ${emitType(e.t)}[${e.size}]" - case e: DefSeqMemory => s"smem ${e.name} : ${emitType(e.t)}[${e.size}], ${e.readUnderWrite}" - case e: DefMemPort[_] => s"${e.dir} mport ${e.name} = ${e.source.fullName(ctx)}[${e.index.fullName(ctx)}], ${e.clock.fullName(ctx)}" - case e: Connect => s"${e.loc.fullName(ctx)} <= ${e.exp.fullName(ctx)}" - case e: BulkConnect => s"${e.loc1.fullName(ctx)} <- ${e.loc2.fullName(ctx)}" - case e: Attach => e.locs.map(_.fullName(ctx)).mkString("attach (", ", ", ")") - case e: Stop => s"stop(${e.clock.fullName(ctx)}, UInt<1>(1), ${e.ret})" - case e: Printf => - val (fmt, args) = e.pable.unpack(ctx) - val printfArgs = Seq(e.clock.fullName(ctx), "UInt<1>(1)", - "\"" + printf.format(fmt) + "\"") ++ args - printfArgs mkString ("printf(", ", ", ")") - case e: Verification => s"${e.op}(${e.clock.fullName(ctx)}, ${e.predicate.fullName(ctx)}, " + - s"UInt<1>(1), " + "\"" + s"${printf.format(e.message)}" + "\")" - case e: DefInvalid => s"${e.arg.fullName(ctx)} is invalid" - case e: DefInstance => s"inst ${e.name} of ${e.id.name}" - case w: WhenBegin => - // When consequences are always indented - indent() - s"when ${w.pred.fullName(ctx)} :" - case w: WhenEnd => - // If a when has no else, the indent level must be reset to the enclosing block - unindent() - if (!w.hasAlt) { for (i <- 0 until w.firrtlDepth) { unindent() } } - s"skip" - case a: AltBegin => - // Else blocks are always indented - indent() - s"else :" - case o: OtherwiseEnd => - // Chisel otherwise: ends all FIRRTL associated a Chisel when, resetting indent level - for (i <- 0 until o.firrtlDepth) { unindent() } - s"skip" - } - firrtlLine + e.sourceInfo.makeMessage(" " + _) - } - - private def emitParam(name: String, p: Param): String = { - val str = p match { - case IntParam(value) => value.toString - case DoubleParam(value) => value.toString - case StringParam(str) => "\"" + str + "\"" - case RawParam(str) => "'" + str + "'" - } - s"parameter $name = $str" - } - - /** Generates the FIRRTL module declaration. - */ - private def moduleDecl(m: Component): String = m.id match { - case _: BaseBlackBox => newline + s"extmodule ${m.name} : " - case _: RawModule => newline + s"module ${m.name} : " - } - - /** Generates the FIRRTL module definition. - */ - private def moduleDefn(m: Component): String = { - val body = new StringBuilder - withIndent { - for (p <- m.ports) { - val portDef = m match { - case bb: DefBlackBox => emitPort(p, bb.topDir) - case mod: DefModule => emitPort(p) - } - body ++= newline + portDef - } - body ++= newline +import scala.collection.immutable.LazyList // Needed for 2.12 alias +import firrtl.ir.Serializer - m match { - case bb: DefBlackBox => - // Firrtl extmodule can overrule name - body ++= newline + s"defname = ${bb.id.desiredName}" - body ++= newline + (bb.params map { case (n, p) => emitParam(n, p) } mkString newline) - case mod: DefModule => { - // Preprocess whens & elsewhens, marking those that have no alternative - val procMod = mod.copy(commands = processWhens(mod.commands)) - for (cmd <- procMod.commands) { body ++= newline + emit(cmd, procMod)} - } - } - body ++= newline - } - body.toString() - } - - /** Returns the FIRRTL declaration and body of a module, or nothing if it's a - * duplicate of something already emitted (on the basis of simple string - * matching). - */ - private def emit(m: Component): String = { - // Generate the body. - val sb = new StringBuilder - sb.append(moduleDecl(m)) - sb.append(moduleDefn(m)) - sb.result +private[chisel3] object Emitter { + def emit(circuit: Circuit): String = { + val fcircuit = Converter.convertLazily(circuit) + Serializer.serialize(fcircuit) } - /** Preprocess the command queue, marking when/elsewhen statements - * that have no alternatives (elsewhens or otherwise). These - * alternative-free statements reset the indent level to the - * enclosing block upon emission. - */ - private def processWhens(cmds: Seq[Command]): Seq[Command] = { - if (cmds.isEmpty) { - Seq.empty - } else { - cmds.zip(cmds.tail).map{ - case (a: WhenEnd, b: AltBegin) => a.copy(hasAlt = true) - case (a, b) => a - } ++ cmds.lastOption + def emitLazily(circuit: Circuit): Iterable[String] = { + val result = LazyList(s"circuit ${circuit.name} :\n") + val modules = circuit.components.view.map(Converter.convert) + val moduleStrings = modules.flatMap { m => + Array(Serializer.serialize(m, 1), "\n\n") } + result ++ moduleStrings } - - 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() } - - private val res = new StringBuilder() - res ++= s";${BuildInfo.toString}\n" - res ++= s"circuit ${circuit.name} : " - withIndent { circuit.components.foreach(c => res ++= emit(c)) } - res ++= newline } + diff --git a/src/main/scala/chisel3/stage/ChiselAnnotations.scala b/src/main/scala/chisel3/stage/ChiselAnnotations.scala index bbe86ab4..de47ef36 100644 --- a/src/main/scala/chisel3/stage/ChiselAnnotations.scala +++ b/src/main/scala/chisel3/stage/ChiselAnnotations.scala @@ -3,7 +3,7 @@ package chisel3.stage import firrtl.annotations.{Annotation, NoTargetAnnotation} -import firrtl.options.{CustomFileEmission, HasShellOptions, OptionsException, ShellOption, StageOptions, Unserializable} +import firrtl.options.{BufferedCustomFileEmission, CustomFileEmission, HasShellOptions, OptionsException, ShellOption, StageOptions, Unserializable} import firrtl.options.Viewer.view import chisel3.{ChiselException, Module} import chisel3.RawModule @@ -56,15 +56,7 @@ case class ChiselGeneratorAnnotation(gen: () => RawModule) extends NoTargetAnnot /** Run elaboration on the Chisel module generator function stored by this [[firrtl.annotations.Annotation]] */ - def elaborate: AnnotationSeq = try { - val (circuit, dut) = Builder.build(Module(gen())) - Seq(ChiselCircuitAnnotation(circuit), DesignAnnotation(dut)) - } catch { - case e @ (_: OptionsException | _: ChiselException) => throw e - case e: Throwable => - throw new ChiselException(s"Exception thrown when elaborating ChiselGeneratorAnnotation", e) - } - + def elaborate: AnnotationSeq = (new chisel3.stage.phases.Elaborate).transform(Seq(this)) } object ChiselGeneratorAnnotation extends HasShellOptions { @@ -131,7 +123,7 @@ import CircuitSerializationAnnotation._ */ case class CircuitSerializationAnnotation(circuit: Circuit, filename: String, format: Format) extends NoTargetAnnotation - with CustomFileEmission { + with BufferedCustomFileEmission { /* Caching the hashCode for a large circuit is necessary due to repeated queries. * Not caching the hashCode will cause severe performance degredations for large [[Circuit]]s. */ @@ -141,14 +133,16 @@ case class CircuitSerializationAnnotation(circuit: Circuit, filename: String, fo protected def suffix: Option[String] = Some(format.extension) - // TODO Use lazy Iterables so that we don't have to materialize full intermediate data structures - override def getBytes: Iterable[Byte] = format match { - case FirrtlFileFormat => OldEmitter.emit(circuit).getBytes + override def getBytesBuffered: Iterable[Array[Byte]] = format match { + case FirrtlFileFormat => + OldEmitter.emitLazily(circuit) + .map(_.getBytes) + // TODO Use lazy Iterables so that we don't have to materialize full intermediate data structures case ProtoBufFileFormat => val ostream = new java.io.ByteArrayOutputStream val modules = circuit.components.map(m => () => chisel3.internal.firrtl.Converter.convert(m)) firrtl.proto.ToProto.writeToStreamFast(ostream, firrtl.ir.NoInfo, modules, circuit.name) - ostream.toByteArray + List(ostream.toByteArray) } } diff --git a/src/main/scala/chisel3/stage/ChiselStage.scala b/src/main/scala/chisel3/stage/ChiselStage.scala index aae7ad8d..0c76f411 100644 --- a/src/main/scala/chisel3/stage/ChiselStage.scala +++ b/src/main/scala/chisel3/stage/ChiselStage.scala @@ -13,7 +13,7 @@ import firrtl.{ VerilogEmitter, SystemVerilogEmitter } -import firrtl.options.{Dependency, Phase, PhaseManager, Shell, Stage, StageError, StageMain} +import firrtl.options.{Dependency, Phase, PhaseManager, Shell, Stage, StageMain} import firrtl.options.phases.DeletedWrapper import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlCli, RunFirrtlTransformAnnotation} import firrtl.options.Viewer.view @@ -28,7 +28,7 @@ class ChiselStage extends Stage { override def prerequisites = Seq.empty override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq.empty + override def optionalPrerequisiteOf = Seq(Dependency[firrtl.stage.FirrtlStage]) override def invalidates(a: Phase) = false val shell: Shell = new Shell("chisel") with ChiselCli with FirrtlCli @@ -42,28 +42,12 @@ class ChiselStage extends Stage { } } - def run(annotations: AnnotationSeq): AnnotationSeq = try { - phaseManager.transform(annotations) - } catch { - case ce: ChiselException => - val stackTrace = if (!view[ChiselOptions](annotations).printFullStackTrace) { - ce.chiselStackTrace - } else { - val sw = new StringWriter - ce.printStackTrace(new PrintWriter(sw)) - sw.toString - } - Predef - .augmentString(stackTrace) - .lines - .foreach(line => println(s"${ErrorLog.errTag} $line")) - throw new StageError(cause=ce) - } + def run(annotations: AnnotationSeq): AnnotationSeq = phaseManager.transform(annotations) /** Convert a Chisel module to a CHIRRTL string * @param gen a call-by-name Chisel module * @param args additional command line arguments to pass to Chisel - * param annotations additional annotations to pass to Chisel + * @param annotations additional annotations to pass to Chisel * @return a string containing the Verilog output */ final def emitChirrtl( @@ -86,7 +70,7 @@ class ChiselStage extends Stage { /** Convert a Chisel module to a FIRRTL string * @param gen a call-by-name Chisel module * @param args additional command line arguments to pass to Chisel - * param annotations additional annotations to pass to Chisel + * @param annotations additional annotations to pass to Chisel * @return a string containing the FIRRTL output */ final def emitFirrtl( @@ -106,7 +90,7 @@ class ChiselStage extends Stage { /** Convert a Chisel module to Verilog * @param gen a call-by-name Chisel module * @param args additional command line arguments to pass to Chisel - * param annotations additional annotations to pass to Chisel + * @param annotations additional annotations to pass to Chisel * @return a string containing the Verilog output */ final def emitVerilog( @@ -126,7 +110,7 @@ class ChiselStage extends Stage { /** Convert a Chisel module to SystemVerilog * @param gen a call-by-name Chisel module * @param args additional command line arguments to pass to Chisel - * param annotations additional annotations to pass to Chisel + * @param annotations additional annotations to pass to Chisel * @return a string containing the SystemVerilog output */ final def emitSystemVerilog( @@ -188,6 +172,26 @@ object ChiselStage { .get } + /** Return a [[firrtl.ir.Circuit]] for a [[chisel3.internal.firrtl.Circuit]](aka chirrtl) + * @param chirrtl [[chisel3.internal.firrtl.Circuit]] which need to be converted to [[firrtl.ir.Circuit]] + */ + def convert(chirrtl: cir.Circuit): fir.Circuit = { + val phase = new ChiselPhase { + override val targets = Seq( + Dependency[chisel3.stage.phases.AddImplicitOutputFile], + Dependency[chisel3.stage.phases.AddImplicitOutputAnnotationFile], + Dependency[chisel3.stage.phases.MaybeAspectPhase], + Dependency[chisel3.stage.phases.Convert] ) + } + + phase + .transform(Seq(ChiselCircuitAnnotation(chirrtl))) + .collectFirst { + case FirrtlCircuitAnnotation(a) => a + } + .get + } + /** Return a CHIRRTL string for a Chisel module * @param gen a call-by-name Chisel module */ diff --git a/src/main/scala/chisel3/stage/phases/AspectPhase.scala b/src/main/scala/chisel3/stage/phases/AspectPhase.scala index 1a27b8fa..72965861 100644 --- a/src/main/scala/chisel3/stage/phases/AspectPhase.scala +++ b/src/main/scala/chisel3/stage/phases/AspectPhase.scala @@ -29,7 +29,7 @@ class AspectPhase extends Phase { case other => Seq(other) } if(dut.isDefined) { - val newAnnotations = aspects.flatMap { _.resolveAspect(dut.get) } + val newAnnotations = aspects.flatMap { _.resolveAspect(dut.get, remainingAnnotations) } remainingAnnotations ++ newAnnotations } else annotations } diff --git a/src/main/scala/chisel3/stage/phases/Convert.scala b/src/main/scala/chisel3/stage/phases/Convert.scala index bf42b58a..b5b01b8d 100644 --- a/src/main/scala/chisel3/stage/phases/Convert.scala +++ b/src/main/scala/chisel3/stage/phases/Convert.scala @@ -29,8 +29,7 @@ class Convert extends Phase { /* Convert all Chisel Annotations to FIRRTL Annotations */ a .circuit - .annotations - .map(_.toFirrtl) ++ + .firrtlAnnotations ++ a .circuit .annotations diff --git a/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala b/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala index 659914ae..9305c5c9 100644 --- a/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala +++ b/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala @@ -16,153 +16,5 @@ import chisel3.stage.{ChiselStage, NoRunFirrtlCompilerAnnotation, ChiselOutputFi * Primarily, this object includes [[firrtl.options.Phase Phase]]s that generate [[firrtl.annotations.Annotation]]s * derived from the deprecated [[firrtl.stage.phases.DriverCompatibility.TopNameAnnotation]]. */ -object DriverCompatibility { - - /** Adds a [[ChiselOutputFileAnnotation]] derived from a [[TopNameAnnotation]] if no [[ChiselOutputFileAnnotation]] - * already exists. If no [[TopNameAnnotation]] exists, then no [[firrtl.stage.OutputFileAnnotation]] is added. ''This is not a - * replacement for [[chisel3.stage.phases.AddImplicitOutputFile AddImplicitOutputFile]] as this only adds an output - * file based on a discovered top name and not on a discovered elaborated circuit.'' Consequently, this will provide - * the correct behavior before a circuit has been elaborated. - * @note the output suffix is unspecified and will be set by the underlying [[firrtl.EmittedComponent]] - */ - private [chisel3] class AddImplicitOutputFile extends Phase { - - override def prerequisites = Seq.empty - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq(Dependency[chisel3.stage.ChiselStage]) - override def invalidates(a: Phase) = false - - def transform(annotations: AnnotationSeq): AnnotationSeq = { - val hasOutputFile = annotations - .collectFirst{ case a: ChiselOutputFileAnnotation => a } - .isDefined - lazy val top = annotations.collectFirst{ case TopNameAnnotation(a) => a } - - if (!hasOutputFile && top.isDefined) { - ChiselOutputFileAnnotation(top.get) +: annotations - } else { - annotations - } - } - } - - /** If a [[firrtl.options.OutputAnnotationFileAnnotation]] does not exist, this adds one derived from a - * [[TopNameAnnotation]]. ''This is not a replacement for [[chisel3.stage.phases.AddImplicitOutputAnnotationFile]] as - * this only adds an output annotation file based on a discovered top name.'' Consequently, this will provide the - * correct behavior before a circuit has been elaborated. - * @note the output suffix is unspecified and will be set by [[firrtl.options.phases.WriteOutputAnnotations]] - */ - private[chisel3] class AddImplicitOutputAnnotationFile extends Phase { - - override def prerequisites = Seq.empty - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq(Dependency[chisel3.stage.ChiselStage]) - override def invalidates(a: Phase) = false - - def transform(annotations: AnnotationSeq): AnnotationSeq = - annotations - .collectFirst{ case _: OutputAnnotationFileAnnotation => annotations } - .getOrElse{ - val top = annotations.collectFirst{ case TopNameAnnotation(a) => a } - if (top.isDefined) { - OutputAnnotationFileAnnotation(top.get) +: annotations - } else { - annotations - } - } - } - - private[chisel3] case object RunFirrtlCompilerAnnotation extends NoTargetAnnotation - - /** Disables the execution of [[firrtl.stage.FirrtlStage]]. This can be used to call [[chisel3.stage.ChiselStage]] and - * guarantee that the FIRRTL compiler will not run. This is necessary for certain [[chisel3.Driver]] compatibility - * situations where you need to do something between Chisel compilation and FIRRTL compilations, e.g., update a - * mutable data structure. - */ - private[chisel3] class DisableFirrtlStage extends Phase { - - override def prerequisites = Seq.empty - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq(Dependency[ChiselStage]) - override def invalidates(a: Phase) = false - - def transform(annotations: AnnotationSeq): AnnotationSeq = annotations - .collectFirst { case NoRunFirrtlCompilerAnnotation => annotations } - .getOrElse { Seq(RunFirrtlCompilerAnnotation, NoRunFirrtlCompilerAnnotation) ++ annotations } - } - - private[chisel3] class ReEnableFirrtlStage extends Phase { - - override def prerequisites = Seq(Dependency[DisableFirrtlStage], Dependency[ChiselStage]) - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq.empty - override def invalidates(a: Phase) = false - - def transform(annotations: AnnotationSeq): AnnotationSeq = annotations - .collectFirst { case RunFirrtlCompilerAnnotation => - val a: AnnotationSeq = annotations.filter { - case NoRunFirrtlCompilerAnnotation | RunFirrtlCompilerAnnotation => false - case _ => true - } - a - } - .getOrElse{ annotations } - - } - - private[chisel3] case class OptionsManagerAnnotation( - manager: ExecutionOptionsManager with HasChiselExecutionOptions with HasFirrtlOptions) - extends NoTargetAnnotation with Unserializable - - /** Mutate an input [[firrtl.ExecutionOptionsManager]] based on information encoded in an [[firrtl.AnnotationSeq]]. - * This is intended to be run between [[chisel3.stage.ChiselStage ChiselStage]] and [[firrtl.stage.FirrtlStage]] if - * you want to have backwards compatibility with an [[firrtl.ExecutionOptionsManager]]. - */ - private[chisel3] class MutateOptionsManager extends Phase { - - override def prerequisites = Seq(Dependency[chisel3.stage.ChiselStage]) - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq(Dependency[ReEnableFirrtlStage]) - override def invalidates(a: Phase) = false - - def transform(annotations: AnnotationSeq): AnnotationSeq = { - - val optionsManager = annotations - .collectFirst{ case OptionsManagerAnnotation(a) => a } - .getOrElse{ throw new OptionsException( - "An OptionsManagerException must exist for Chisel Driver compatibility mode") } - - val firrtlCircuit = annotations.collectFirst{ case FirrtlCircuitAnnotation(a) => a } - optionsManager.firrtlOptions = optionsManager.firrtlOptions.copy( - firrtlCircuit = firrtlCircuit, - annotations = optionsManager.firrtlOptions.annotations ++ annotations, - customTransforms = optionsManager.firrtlOptions.customTransforms ++ - annotations.collect{ case RunFirrtlTransformAnnotation(a) => a } ) - - annotations - - } - - } - - /** A [[Phase]] that lets us run - * @todo a better solution than the current state hack below may be needed - */ - private [chisel3] class FirrtlPreprocessing extends Phase { - - override def prerequisites = Seq(Dependency[ChiselStage], Dependency[MutateOptionsManager], Dependency[ReEnableFirrtlStage]) - override def optionalPrerequisites = Seq.empty - override def optionalPrerequisiteOf = Seq(Dependency[MaybeFirrtlStage]) - override def invalidates(a: Phase) = false - - private val phases = - Seq( new firrtl.stage.phases.DriverCompatibility.AddImplicitOutputFile, - new firrtl.stage.phases.DriverCompatibility.AddImplicitEmitter ) - - override def transform(annotations: AnnotationSeq): AnnotationSeq = - phases - .foldLeft(annotations)( (a, p) => p.transform(a) ) - - } - -} +@deprecated("This object contains no public members. This will be removed in Chisel 3.6.", "Chisel 3.5") +object DriverCompatibility diff --git a/src/main/scala/chisel3/stage/phases/Elaborate.scala b/src/main/scala/chisel3/stage/phases/Elaborate.scala index 04cfc33e..e8f2623e 100644 --- a/src/main/scala/chisel3/stage/phases/Elaborate.scala +++ b/src/main/scala/chisel3/stage/phases/Elaborate.scala @@ -2,14 +2,13 @@ package chisel3.stage.phases -import java.io.{PrintWriter, StringWriter} - -import chisel3.ChiselException -import chisel3.internal.ErrorLog -import chisel3.stage.{ChiselGeneratorAnnotation, ChiselOptions} +import chisel3.Module +import chisel3.internal.ExceptionHelpers.ThrowableHelpers +import chisel3.internal.{Builder, DynamicContext} +import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation, ChiselOptions, DesignAnnotation} import firrtl.AnnotationSeq +import firrtl.options.Phase import firrtl.options.Viewer.view -import firrtl.options.{OptionsException, Phase} /** Elaborate all [[chisel3.stage.ChiselGeneratorAnnotation]]s into [[chisel3.stage.ChiselCircuitAnnotation]]s. */ @@ -21,8 +20,19 @@ class Elaborate extends Phase { override def invalidates(a: Phase) = false def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap { - case a: ChiselGeneratorAnnotation => a.elaborate - case a => Some(a) + case ChiselGeneratorAnnotation(gen) => try { + val (circuit, dut) = Builder.build(Module(gen()), new DynamicContext(annotations)) + Seq(ChiselCircuitAnnotation(circuit), DesignAnnotation(dut)) + } catch { + /* if any throwable comes back and we're in "stack trace trimming" mode, then print an error and trim the stack trace + */ + case scala.util.control.NonFatal(a) => + if (!view[ChiselOptions](annotations).printFullStackTrace) { + a.trimStackTraceToUserCode() + } + throw(a) + } + case a => Some(a) } } diff --git a/src/main/scala/chisel3/util/BitPat.scala b/src/main/scala/chisel3/util/BitPat.scala index 40563e23..0dcb2466 100644 --- a/src/main/scala/chisel3/util/BitPat.scala +++ b/src/main/scala/chisel3/util/BitPat.scala @@ -23,6 +23,7 @@ object BitPat { // If ? parsing is to be exposed, the return API needs further scrutiny // (especially with things like mask polarity). require(x.head == 'b', "BitPats must be in binary and be prefixed with 'b'") + require(x.length > 1, "BitPat width cannot be 0.") var bits = BigInt(0) var mask = BigInt(0) var count = 0 @@ -57,6 +58,22 @@ object BitPat { */ def dontCare(width: Int): BitPat = BitPat("b" + ("?" * width)) + /** Creates a [[BitPat]] of all 1 of the specified bitwidth. + * + * @example {{{ + * val myY = BitPat.Y(4) // equivalent to BitPat("b1111") + * }}} + */ + def Y(width: Int = 1): BitPat = BitPat("b" + ("1" * width)) + + /** Creates a [[BitPat]] of all 0 of the specified bitwidth. + * + * @example {{{ + * val myN = BitPat.N(4) // equivalent to BitPat("b0000") + * }}} + */ + def N(width: Int = 1): BitPat = BitPat("b" + ("0" * width)) + /** Allows BitPats to be used where a UInt is expected. * * @note the BitPat must not have don't care bits (will error out otherwise) @@ -91,12 +108,6 @@ object BitPat { /** @group SourceInfoTransformMacro */ def do_=/= (that: BitPat) (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = that =/= x - - final def != (that: BitPat): Bool = macro SourceInfoTransform.thatArg - @chiselRuntimeDeprecated - @deprecated("Use '=/=', which avoids potential precedence problems", "3.0") - def do_!= (that: BitPat) - (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = that != x } } @@ -111,8 +122,29 @@ object BitPat { */ sealed class BitPat(val value: BigInt, val mask: BigInt, width: Int) extends SourceInfoDoc { def getWidth: Int = width + def apply(x: Int): BitPat = macro SourceInfoTransform.xArg + def apply(x: Int, y: Int): BitPat = macro SourceInfoTransform.xyArg def === (that: UInt): Bool = macro SourceInfoTransform.thatArg def =/= (that: UInt): Bool = macro SourceInfoTransform.thatArg + def ## (that: BitPat): BitPat = macro SourceInfoTransform.thatArg + override def equals(obj: Any): Boolean = { + obj match { + case y: BitPat => value == y.value && mask == y.mask && getWidth == y.getWidth + case _ => false + } + } + + /** @group SourceInfoTransformMacro */ + def do_apply(x: Int)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): BitPat = { + do_apply(x, x) + } + + /** @group SourceInfoTransformMacro */ + def do_apply(x: Int, y: Int)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): BitPat = { + require(width > x && y >= 0, s"Invalid bit range ($x, $y), index should be bounded by (${width - 1}, 0)") + require(x >= y, s"Invalid bit range ($x, $y), x should be greater or equal to y.") + BitPat(s"b${rawString.slice(width - x - 1, width - y)}") + } /** @group SourceInfoTransformMacro */ def do_=== (that: UInt) @@ -124,12 +156,19 @@ sealed class BitPat(val value: BigInt, val mask: BigInt, width: Int) extends Sou (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { !(this === that) } - - def != (that: UInt): Bool = macro SourceInfoTransform.thatArg - @chiselRuntimeDeprecated - @deprecated("Use '=/=', which avoids potential precedence problems", "3.0") - def do_!= (that: UInt) - (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { - this =/= that + /** @group SourceInfoTransformMacro */ + def do_##(that: BitPat)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): BitPat = { + new BitPat((value << that.getWidth) + that.value, (mask << that.getWidth) + that.mask, this.width + that.getWidth) } + + /** Generate raw string of a BitPat. */ + def rawString: String = Seq.tabulate(width) { i => + (value.testBit(width - i - 1), mask.testBit(width - i - 1)) match { + case (true, true) => "1" + case (false, true) => "0" + case (_, false) => "?" + } + }.mkString + + override def toString = s"BitPat($rawString)" } diff --git a/src/main/scala/chisel3/util/BlackBoxUtils.scala b/src/main/scala/chisel3/util/BlackBoxUtils.scala index 21bd4dfa..443d7f3e 100644 --- a/src/main/scala/chisel3/util/BlackBoxUtils.scala +++ b/src/main/scala/chisel3/util/BlackBoxUtils.scala @@ -4,15 +4,39 @@ package chisel3.util import chisel3._ import chisel3.experimental.{ChiselAnnotation, RunFirrtlTransform} -import firrtl.transforms.{BlackBoxPathAnno, BlackBoxResourceAnno, BlackBoxInlineAnno, BlackBoxSourceHelper} +import firrtl.transforms.{BlackBoxPathAnno, BlackBoxResourceAnno, BlackBoxInlineAnno, BlackBoxSourceHelper, + BlackBoxNotFoundException} +import firrtl.annotations.ModuleName +import logger.LazyLogging + +private[util] object BlackBoxHelpers { + + implicit class BlackBoxInlineAnnoHelpers(anno: BlackBoxInlineAnno.type) extends LazyLogging { + /** Generate a BlackBoxInlineAnno from a Java Resource and a module name. */ + def fromResource(resourceName: String, moduleName: ModuleName) = try { + val blackBoxFile = os.resource / os.RelPath(resourceName.dropWhile(_ == '/')) + val contents = os.read(blackBoxFile) + if (contents.size > BigInt(2).pow(20)) { + val message = + s"Black box resource $resourceName, which will be converted to an inline annotation, is greater than 1 MiB." + + "This may affect compiler performance. Consider including this resource via a black box path." + logger.warn(message) + } + BlackBoxInlineAnno(moduleName, blackBoxFile.last, contents) + } catch { + case e: os.ResourceNotFoundException => + throw new BlackBoxNotFoundException(resourceName, e.getMessage) + } + } +} + +import BlackBoxHelpers._ trait HasBlackBoxResource extends BlackBox { self: BlackBox => - @deprecated("Use addResource instead", "3.2") - def setResource(blackBoxResource: String): Unit = addResource(blackBoxResource) - - /** Copies a resource file to the target directory + /** Copies a Java resource containing some text into the output directory. This is typically used to copy a Verilog file + * to the final output directory, but may be used to copy any Java resource (e.g., a C++ testbench). * * Resource files are located in project_root/src/main/resources/. * Example of adding the resource file project_root/src/main/resources/blackbox.v: @@ -22,7 +46,7 @@ trait HasBlackBoxResource extends BlackBox { */ def addResource(blackBoxResource: String): Unit = { val anno = new ChiselAnnotation with RunFirrtlTransform { - def toFirrtl = BlackBoxResourceAnno(self.toNamed, blackBoxResource) + def toFirrtl = BlackBoxInlineAnno.fromResource(blackBoxResource, self.toNamed) def transformClass = classOf[BlackBoxSourceHelper] } chisel3.experimental.annotate(anno) diff --git a/src/main/scala/chisel3/util/Conditional.scala b/src/main/scala/chisel3/util/Conditional.scala index b934f27f..1ac94bfe 100644 --- a/src/main/scala/chisel3/util/Conditional.scala +++ b/src/main/scala/chisel3/util/Conditional.scala @@ -12,15 +12,6 @@ import scala.reflect.macros.blackbox._ import chisel3._ import chisel3.internal.sourceinfo.SourceInfo -@deprecated("The unless conditional is deprecated, use when(!condition){...} instead", "3.2") -object unless { - /** Does the same thing as [[when$ when]], but with the condition inverted. - */ - def apply(c: Bool)(block: => Any) { - when (!c) { block } - } -} - /** Implementation details for [[switch]]. See [[switch]] and [[chisel3.util.is is]] for the * user-facing API. * @note DO NOT USE. This API is subject to change without warning. diff --git a/src/main/scala/chisel3/util/Decoupled.scala b/src/main/scala/chisel3/util/Decoupled.scala index 032d731d..8909ffe3 100644 --- a/src/main/scala/chisel3/util/Decoupled.scala +++ b/src/main/scala/chisel3/util/Decoupled.scala @@ -108,7 +108,7 @@ object Decoupled @chiselName def apply[T <: Data](irr: IrrevocableIO[T]): DecoupledIO[T] = { require(DataMirror.directionOf(irr.bits) == Direction.Output, "Only safe to cast produced Irrevocable bits to Decoupled.") - val d = Wire(new DecoupledIO(irr.bits)) + val d = Wire(new DecoupledIO(chiselTypeOf(irr.bits))) d.bits := irr.bits d.valid := irr.valid irr.ready := d.ready @@ -139,7 +139,7 @@ object Irrevocable */ def apply[T <: Data](dec: DecoupledIO[T]): IrrevocableIO[T] = { require(DataMirror.directionOf(dec.bits) == Direction.Input, "Only safe to cast consumed Decoupled bits to Irrevocable.") - val i = Wire(new IrrevocableIO(dec.bits)) + val i = Wire(new IrrevocableIO(chiselTypeOf(dec.bits))) dec.bits := i.bits dec.valid := i.valid i.ready := dec.ready @@ -163,8 +163,9 @@ object DeqIO { /** An I/O Bundle for Queues * @param gen The type of data to queue * @param entries The max number of entries in the queue. + * @param hasFlush A boolean for whether the generated Queue is flushable */ -class QueueIO[T <: Data](private val gen: T, val entries: Int) extends Bundle +class QueueIO[T <: Data](private val gen: T, val entries: Int, val hasFlush: Boolean = false) extends Bundle { // See github.com/freechipsproject/chisel3/issues/765 for why gen is a private val and proposed replacement APIs. /* These may look inverted, because the names (enq/deq) are from the perspective of the client, @@ -177,6 +178,9 @@ class QueueIO[T <: Data](private val gen: T, val entries: Int) extends Bundle val deq = Flipped(DeqIO(gen)) /** The current amount of data in the queue */ val count = Output(UInt(log2Ceil(entries + 1).W)) + /** When asserted, reset the enqueue and dequeue pointers, effectively flushing the queue (Optional IO for a flushable Queue)*/ + val flush = if (hasFlush) Some(Input(Bool())) else None + } /** A hardware module implementing a Queue @@ -186,7 +190,8 @@ class QueueIO[T <: Data](private val gen: T, val entries: Int) extends Bundle * combinationally coupled. * @param flow True if the inputs can be consumed on the same cycle (the inputs "flow" through the queue immediately). * The ''valid'' signals are coupled. - * + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element. + * @param hasFlush True if generated queue requires a flush feature * @example {{{ * val q = Module(new Queue(UInt(), 16)) * q.io.enq <> producer.io.out @@ -197,7 +202,9 @@ class QueueIO[T <: Data](private val gen: T, val entries: Int) extends Bundle class Queue[T <: Data](val gen: T, val entries: Int, val pipe: Boolean = false, - val flow: Boolean = false) + val flow: Boolean = false, + val useSyncReadMem: Boolean = false, + val hasFlush: Boolean = false) (implicit compileOptions: chisel3.CompileOptions) extends Module() { require(entries > -1, "Queue must have non-negative number of entries") @@ -213,19 +220,20 @@ class Queue[T <: Data](val gen: T, } } - val io = IO(new QueueIO(genType, entries)) - - val ram = Mem(entries, genType) + val io = IO(new QueueIO(genType, entries, hasFlush)) + val ram = if (useSyncReadMem) SyncReadMem(entries, genType, SyncReadMem.WriteFirst) else Mem(entries, genType) val enq_ptr = Counter(entries) val deq_ptr = Counter(entries) val maybe_full = RegInit(false.B) - val ptr_match = enq_ptr.value === deq_ptr.value val empty = ptr_match && !maybe_full val full = ptr_match && maybe_full val do_enq = WireDefault(io.enq.fire()) val do_deq = WireDefault(io.deq.fire()) + val flush = io.flush.getOrElse(false.B) + // when flush is high, empty the queue + // Semantically, any enqueues happen before the flush. when (do_enq) { ram(enq_ptr.value) := io.enq.bits enq_ptr.inc() @@ -236,10 +244,23 @@ class Queue[T <: Data](val gen: T, when (do_enq =/= do_deq) { maybe_full := do_enq } + when(flush) { + enq_ptr.reset() + deq_ptr.reset() + maybe_full := false.B + } io.deq.valid := !empty io.enq.ready := !full - io.deq.bits := ram(deq_ptr.value) + + if (useSyncReadMem) { + val deq_ptr_next = Mux(deq_ptr.value === (entries.U - 1.U), 0.U, deq_ptr.value + 1.U) + val r_addr = WireDefault(Mux(do_deq, deq_ptr_next, deq_ptr.value)) + io.deq.bits := ram.read(r_addr) + } + else { + io.deq.bits := ram(deq_ptr.value) + } if (flow) { when (io.enq.valid) { io.deq.valid := true.B } @@ -255,6 +276,7 @@ class Queue[T <: Data](val gen: T, } val ptr_diff = enq_ptr.value - deq_ptr.value + if (isPow2(entries)) { io.count := Mux(maybe_full && ptr_match, entries.U, 0.U) | ptr_diff } else { @@ -285,7 +307,9 @@ object Queue enq: ReadyValidIO[T], entries: Int = 2, pipe: Boolean = false, - flow: Boolean = false): DecoupledIO[T] = { + flow: Boolean = false, + useSyncReadMem: Boolean = false, + hasFlush: Boolean = false): DecoupledIO[T] = { if (entries == 0) { val deq = Wire(new DecoupledIO(chiselTypeOf(enq.bits))) deq.valid := enq.valid @@ -293,7 +317,7 @@ object Queue enq.ready := deq.ready deq } else { - val q = Module(new Queue(chiselTypeOf(enq.bits), entries, pipe, flow)) + val q = Module(new Queue(chiselTypeOf(enq.bits), entries, pipe, flow, useSyncReadMem, hasFlush)) q.io.enq.valid := enq.valid // not using <> so that override is allowed q.io.enq.bits := enq.bits enq.ready := q.io.enq.ready @@ -311,8 +335,9 @@ object Queue enq: ReadyValidIO[T], entries: Int = 2, pipe: Boolean = false, - flow: Boolean = false): IrrevocableIO[T] = { - val deq = apply(enq, entries, pipe, flow) + flow: Boolean = false, + useSyncReadMem: Boolean = false): IrrevocableIO[T] = { + val deq = apply(enq, entries, pipe, flow, useSyncReadMem) require(entries > 0, "Zero-entry queues don't guarantee Irrevocability") val irr = Wire(new IrrevocableIO(chiselTypeOf(deq.bits))) irr.bits := deq.bits diff --git a/src/main/scala/chisel3/util/ExtModuleUtils.scala b/src/main/scala/chisel3/util/ExtModuleUtils.scala index 831639be..62f384bc 100644 --- a/src/main/scala/chisel3/util/ExtModuleUtils.scala +++ b/src/main/scala/chisel3/util/ExtModuleUtils.scala @@ -3,7 +3,10 @@ package chisel3.util import chisel3.experimental.{ChiselAnnotation, ExtModule, RunFirrtlTransform} -import firrtl.transforms.{BlackBoxPathAnno, BlackBoxResourceAnno, BlackBoxInlineAnno, BlackBoxSourceHelper} +import firrtl.transforms.{BlackBoxPathAnno, BlackBoxResourceAnno, BlackBoxInlineAnno, BlackBoxSourceHelper, + BlackBoxNotFoundException} + +import BlackBoxHelpers._ trait HasExtModuleResource extends ExtModule { self: ExtModule => @@ -18,7 +21,7 @@ trait HasExtModuleResource extends ExtModule { */ def addResource(blackBoxResource: String): Unit = { val anno = new ChiselAnnotation with RunFirrtlTransform { - def toFirrtl = BlackBoxResourceAnno(self.toNamed, blackBoxResource) + def toFirrtl = BlackBoxInlineAnno.fromResource(blackBoxResource, self.toNamed) def transformClass = classOf[BlackBoxSourceHelper] } chisel3.experimental.annotate(anno) diff --git a/src/main/scala/chisel3/util/MixedVec.scala b/src/main/scala/chisel3/util/MixedVec.scala index a632ec3a..14d6be38 100644 --- a/src/main/scala/chisel3/util/MixedVec.scala +++ b/src/main/scala/chisel3/util/MixedVec.scala @@ -91,6 +91,10 @@ final class MixedVec[T <: Data](private val eltsIn: Seq[T]) extends Record with eltsIn.foreach(e => requireIsChiselType(e)) } + // In Scala 2.13, this is protected in IndexedSeq, must override as public because it's public in + // Record + override def className: String = "MixedVec" + // Clone the inputs so that we have our own references. private val elts: IndexedSeq[T] = eltsIn.map(_.cloneTypeFull).toIndexedSeq diff --git a/src/main/scala/chisel3/util/Reg.scala b/src/main/scala/chisel3/util/Reg.scala index 982d80d0..e2b5d172 100644 --- a/src/main/scala/chisel3/util/Reg.scala +++ b/src/main/scala/chisel3/util/Reg.scala @@ -42,14 +42,7 @@ object ShiftRegister * val regDelayTwo = ShiftRegister(nextVal, 2, ena) * }}} */ - def apply[T <: Data](in: T, n: Int, en: Bool = true.B): T = { - // The order of tests reflects the expected use cases. - if (n != 0) { - RegEnable(apply(in, n-1, en), en) - } else { - in - } - } + def apply[T <: Data](in: T, n: Int, en: Bool = true.B): T = ShiftRegisters(in, n, en).lastOption.getOrElse(in) /** Returns the n-cycle delayed version of the input signal with reset initialization. * @@ -62,12 +55,30 @@ object ShiftRegister * val regDelayTwoReset = ShiftRegister(nextVal, 2, 0.U, ena) * }}} */ - def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): T = { - // The order of tests reflects the expected use cases. - if (n != 0) { - RegEnable(apply(in, n-1, resetData, en), resetData, en) - } else { - in - } - } + def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): T = ShiftRegisters(in, n, resetData, en).lastOption.getOrElse(in) +} + + +object ShiftRegisters +{ + /** Returns a sequence of delayed input signal registers from 1 to n. + * + * @param in input to delay + * @param n number of cycles to delay + * @param en enable the shift + * + */ + def apply[T <: Data](in: T, n: Int, en: Bool = true.B): Seq[T] = + Seq.iterate(in, n + 1)(util.RegEnable(_, en)).drop(1) + + /** Returns delayed input signal registers with reset initialization from 1 to n. + * + * @param in input to delay + * @param n number of cycles to delay + * @param resetData reset value for each register in the shift + * @param en enable the shift + * + */ + def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): Seq[T] = + Seq.iterate(in, n + 1)(util.RegEnable(_, resetData, en)).drop(1) } diff --git a/src/main/scala/chisel3/util/experimental/BoringUtils.scala b/src/main/scala/chisel3/util/experimental/BoringUtils.scala index 18551da8..f2a3e757 100644 --- a/src/main/scala/chisel3/util/experimental/BoringUtils.scala +++ b/src/main/scala/chisel3/util/experimental/BoringUtils.scala @@ -19,7 +19,7 @@ class BoringUtilsException(message: String) extends Exception(message) /** Utilities for generating synthesizable cross module references that "bore" through the hierarchy. The underlying * cross module connects are handled by FIRRTL's Wiring Transform. * - * Consider the following exmple where you want to connect a component in one module to a component in another. Module + * Consider the following example where you want to connect a component in one module to a component in another. Module * `Constant` has a wire tied to `42` and `Expect` will assert unless connected to `42`: * {{{ * class Constant extends Module { @@ -45,8 +45,8 @@ class BoringUtilsException(message: String) extends Exception(message) * * ===Hierarchical Boring=== * - * Hierarchcical boring involves connecting one sink instance to another source instance in a parent module. Below, - * module `Top` contains an instance of `Cosntant` and `Expect`. Using [[BoringUtils.bore]], we can connect + * Hierarchical boring involves connecting one sink instance to another source instance in a parent module. Below, + * module `Top` contains an instance of `Constant` and `Expect`. Using [[BoringUtils.bore]], we can connect * `constant.x` to `expect.y`. * * {{{ @@ -89,7 +89,7 @@ class BoringUtilsException(message: String) extends Exception(message) * ==Comments== * * Both hierarchical and non-hierarchical boring emit FIRRTL annotations that describe sources and sinks. These are - * matched by a `name` key that indicates they should be wired together. Hierarhical boring safely generates this name + * matched by a `name` key that indicates they should be wired together. Hierarchical boring safely generates this name * automatically. Non-hierarchical boring unsafely relies on user input to generate this name. Use of non-hierarchical * naming may result in naming conflicts that the user must handle. * @@ -115,7 +115,7 @@ object BoringUtils { /** Add a named source cross module reference * @param component source circuit component * @param name unique identifier for this source - * @param disableDedup disable dedupblication of this source component (this should be true if you are trying to wire + * @param disableDedup disable deduplication of this source component (this should be true if you are trying to wire * from specific identical sources differently) * @param uniqueName if true, this will use a non-conflicting name from the global namespace * @return the name used @@ -137,7 +137,7 @@ object BoringUtils { def transformClass = classOf[WiringTransform] }, new ChiselAnnotation { def toFirrtl = DontTouchAnnotation(component.toNamed) } ) ++ maybeDedup - annotations.map(annotate(_)) + annotations.foreach(annotate(_)) id } @@ -146,8 +146,8 @@ object BoringUtils { * @param name unique identifier for this sink that must resolve to * @param disableDedup disable deduplication of this sink component (this should be true if you are trying to wire * specific, identical sinks differently) - * @param forceExists if true, require that the provided `name` paramater already exists in the global namespace - * @throws BoringUtilsException if name is expected to exist and itdoesn't + * @param forceExists if true, require that the provided `name` parameter already exists in the global namespace + * @throws BoringUtilsException if name is expected to exist and it doesn't */ def addSink( component: InstanceId, @@ -169,7 +169,7 @@ object BoringUtils { Seq(new ChiselAnnotation with RunFirrtlTransform { def toFirrtl = SinkAnnotation(component.toNamed, name) def transformClass = classOf[WiringTransform] }) ++ maybeDedup - annotations.map(annotate(_)) + annotations.foreach(annotate(_)) } /** Connect a source to one or more sinks @@ -187,7 +187,7 @@ object BoringUtils { case _: Exception => "bore" } val genName = addSource(source, boringName, true, true) - sinks.map(addSink(_, genName, true, true)) + sinks.foreach(addSink(_, genName, true, true)) genName } diff --git a/src/main/scala/chisel3/util/experimental/LoadMemoryTransform.scala b/src/main/scala/chisel3/util/experimental/LoadMemoryTransform.scala index d91d97b7..93981485 100644 --- a/src/main/scala/chisel3/util/experimental/LoadMemoryTransform.scala +++ b/src/main/scala/chisel3/util/experimental/LoadMemoryTransform.scala @@ -40,7 +40,7 @@ case class ChiselLoadMemoryAnnotation[T <: Data]( } -/** [[loadMemoryFromFile]] is an annotation generator that helps with loading a memory from a text file. This relies on +/** [[loadMemoryFromFile]] is an annotation generator that helps with loading a memory from a text file as a bind module. This relies on * Verilator and Verilog's `\$readmemh` or `\$readmemb`. The [[https://github.com/freechipsproject/treadle Treadle * backend]] can also recognize this annotation and load memory at run-time. * @@ -116,6 +116,86 @@ object loadMemoryFromFile { } } + +/** [[loadMemoryFromFileInline]] is an annotation generator that helps with loading a memory from a text file inlined in + * the Verilog module. This relies on Verilator and Verilog's `\$readmemh` or `\$readmemb`. + * The [[https://github.com/freechipsproject/treadle Treadlebackend]] can also recognize this annotation and load memory at run-time. + * + * This annotation, when the FIRRTL compiler runs, triggers the [[MemoryFileInlineAnnotation]] that will add Verilog + * directives inlined to the module enabling the specified memories to be initialized from files. + * The module supports both `hex` and `bin` files by passing the appropriate [[MemoryLoadFileType.FileType]] argument with + * [[MemoryLoadFileType.Hex]] or [[MemoryLoadFileType.Binary]]. Hex is the default. + * + * ==Example module== + * + * Consider a simple Module containing a memory: + * {{{ + * import chisel3._ + * class UsesMem(memoryDepth: Int, memoryType: Data) extends Module { + * val io = IO(new Bundle { + * val address = Input(UInt(memoryType.getWidth.W)) + * val value = Output(memoryType) + * }) + * val memory = Mem(memoryDepth, memoryType) + * io.value := memory(io.address) + * } + * }}} + * + * ==Above module with annotation== + * + * To load this memory from the file `/workspace/workdir/mem1.hex.txt` just add an import and annotate the memory: + * {{{ + * import chisel3._ + * import chisel3.util.experimental.loadMemoryFromFileInline // <<-- new import here + * class UsesMem(memoryDepth: Int, memoryType: Data) extends Module { + * val io = IO(new Bundle { + * val address = Input(UInt(memoryType.getWidth.W)) + * val value = Output(memoryType) + * }) + * val memory = Mem(memoryDepth, memoryType) + * io.value := memory(io.address) + * loadMemoryFromFileInline(memory, "/workspace/workdir/mem1.hex.txt") // <<-- Note the annotation here + * } + * }}} + * + * ==Example file format== + * + * A memory file should consist of ASCII text in either hex or binary format. The following example shows such a + * file formatted to use hex: + * {{{ + * 0 + * 7 + * d + * 15 + * }}} + * + * A binary file can be similarly constructed. + * Chisel does not validate the file format or existence. It is supposed to be in a path accessible by the synthesis + * tool together with the generated Verilog. + * + * @see Chisel3 Wiki entry on + * [[https://github.com/freechipsproject/chisel3/wiki/Chisel-Memories#loading-memories-in-simulation "Loading Memories + * in Simulation"]] + */ +object loadMemoryFromFileInline { + + + /** Annotate a memory such that it can be initialized inline using a file + * @param memory the memory + * @param fileName the file used for initialization + * @param hexOrBinary whether the file uses a hex or binary number representation + */ + def apply[T <: Data]( + memory: MemBase[T], + fileName: String, + hexOrBinary: MemoryLoadFileType.FileType = MemoryLoadFileType.Hex + ): Unit = { + annotate(new ChiselAnnotation { + override def toFirrtl = MemoryFileInlineAnnotation(memory.toTarget, fileName, hexOrBinary) + }) + } +} + /** This transform only is activated if Verilog is being generated (determined by presence of the proper emit * annotation) when activated it creates additional Verilog files that contain modules bound to the modules that * contain an initializable memory diff --git a/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala b/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala new file mode 100644 index 00000000..2b50ef90 --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/DecodeTableAnnotation.scala @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import firrtl.annotations.{Annotation, ReferenceTarget, SingleTargetAnnotation} + +/** DecodeTableAnnotation used to store a decode result for a specific [[TruthTable]]. + * This is useful for saving large [[TruthTable]] during a elaboration time. + * + * @note user should manage the correctness of [[minimizedTable]]. + * + * @param target output wire of a decoder. + * @param truthTable input [[TruthTable]] encoded in a serialized [[TruthTable]]. + * @param minimizedTable minimized [[truthTable]] encoded in a serialized [[TruthTable]]. + */ +case class DecodeTableAnnotation( + target: ReferenceTarget, + truthTable: String, + minimizedTable: String) + extends SingleTargetAnnotation[ReferenceTarget] { + override def duplicate(n: ReferenceTarget): Annotation = this.copy(target = n) +} diff --git a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala new file mode 100644 index 00000000..1d725875 --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import chisel3.util.BitPat +import logger.LazyLogging + +case object EspressoNotFoundException extends Exception + +object EspressoMinimizer extends Minimizer with LazyLogging { + def minimize(table: TruthTable): TruthTable = + TruthTable.merge(TruthTable.split(table).map{case (table, indexes) => (espresso(table), indexes)}) + + def espresso(table: TruthTable): TruthTable = { + def writeTable(table: TruthTable): String = { + def invert(string: String) = string + .replace('0', 't') + .replace('1', '0') + .replace('t', '1') + val defaultType: Char = { + val t = table.default.rawString.toCharArray.distinct + require(t.length == 1, "Internal Error: espresso only accept unified default type.") + t.head + } + val tableType: String = defaultType match { + case '?' => "fr" + case _ => "fd" + } + val rawTable = table + .toString + .split("\n") + .filter(_.contains("->")) + .mkString("\n") + .replace("->", " ") + .replace('?', '-') + // invert all output, since espresso cannot handle default is on. + val invertRawTable = rawTable + .split("\n") + .map(_.split(" ")) + .map(row => s"${row(0)} ${invert(row(1))}") + .mkString("\n") + s""".i ${table.inputWidth} + |.o ${table.outputWidth} + |.type ${tableType} + |""".stripMargin ++ (if (defaultType == '1') invertRawTable else rawTable) + } + + def readTable(espressoTable: String): Map[BitPat, BitPat] = { + def bitPat(espresso: String): BitPat = BitPat("b" + espresso.replace('-', '?')) + + espressoTable + .split("\n") + .filterNot(_.startsWith(".")) + .map(_.split(' ')) + .map(row => bitPat(row(0)) -> bitPat(row(1))) + .toMap + } + + val input = writeTable(table) + logger.trace(s"""espresso input table: + |$input + |""".stripMargin) + val output = try { + os.proc("espresso").call(stdin = input).out.chunks.mkString + } catch { + case e: java.io.IOException if e.getMessage.contains("error=2, No such file or directory") => throw EspressoNotFoundException + } + logger.trace(s"""espresso output table: + |$output + |""".stripMargin) + TruthTable(readTable(output), table.default) + } +} diff --git a/src/main/scala/chisel3/util/experimental/decode/Minimizer.scala b/src/main/scala/chisel3/util/experimental/decode/Minimizer.scala new file mode 100644 index 00000000..86847710 --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/Minimizer.scala @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +abstract class Minimizer { + /** Minimize a multi-input multi-output logic function given by the truth table `table`, with function output values + * on unspecified inputs treated as `default`, and return a minimized PLA-like representation of the function. + * + * Each bit of `table[]._1` encodes one 1-bit input variable of the logic function, and each bit of `default` and + * `table[]._2` represents one 1-bit output value of the function. + * + * @param table Truth table, can have don't cares in both inputs and outputs, specified as [(inputs, outputs), ...] + * @return Minimized truth table, [(inputs, outputs), ...] + * + * @example {{{ + * minimize(BitPat("b?"), Seq( + * (BitPat("b000"), BitPat("b0")), + * // (BitPat("b001"), BitPat("b?")), // same as default, can be omitted + * // (BitPat("b010"), BitPat("b?")), // same as default, can be omitted + * (BitPat("b011"), BitPat("b0")), + * (BitPat("b100"), BitPat("b1")), + * (BitPat("b101"), BitPat("b1")), + * (BitPat("b110"), BitPat("b0")), + * (BitPat("b111"), BitPat("b1")), + * )) + * }}} + */ + def minimize(table: TruthTable): TruthTable +}
\ No newline at end of file diff --git a/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala new file mode 100644 index 00000000..c1533f44 --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/QMCMinimizer.scala @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import chisel3.util.BitPat + +import scala.annotation.tailrec +import scala.math.Ordered.orderingToOrdered +import scala.language.implicitConversions + +object QMCMinimizer extends Minimizer { + private implicit def toImplicant(x: BitPat): Implicant = new Implicant(x) + + private class Implicant(val bp: BitPat) { + var isPrime: Boolean = true + + def width = bp.getWidth + + override def equals(that: Any): Boolean = that match { + case x: Implicant => bp.value == x.bp.value && bp.mask == x.bp.mask + case _ => false + } + + override def hashCode = bp.value.toInt + + /** Check whether two implicants have the same value on all of the cared bits (intersection). + * + * {{{ + * value ^^ x.value // bits that are different + * (bits that are different) & x.mask // bits that are different and `this` care + * (bits that are different and `this` care) & y.mask // bits that are different and `both` care + * (bits that are different and both care) == 0 // no (bits that are different and we both care) exists + * no (bits that are different and we both care) exists // all cared bits are the same, two terms intersect + * }}} + * + * @param y Implicant to be checked with + * @return Whether two implicants intersect + */ + def intersects(y: Implicant): Boolean = ((bp.value ^ y.bp.value) & bp.mask & y.bp.mask) == 0 + + /** Check whether two implicants are similar. + * Two implicants are "similar" when they satisfy all the following rules: + * 1. have the same mask ('?'s are at the same positions) + * 1. values only differ by one bit + * 1. the bit at the differed position of this term is '1' (that of the other term is '0') + * + * @example this = 11?0, x = 10?0 -> similar + * @example this = 11??, x = 10?0 -> not similar, violated rule 1 + * @example this = 11?1, x = 10?0 -> not similar, violated rule 2 + * @example this = 10?0, x = 11?0 -> not similar, violated rule 3 + * @param y Implicant to be checked with + * @return Whether this term is similar to the other + */ + def similar(y: Implicant): Boolean = { + val diff = bp.value - y.bp.value + bp.mask == y.bp.mask && bp.value > y.bp.value && (diff & diff - 1) == 0 + } + + /** Merge two similar implicants + * Rule of merging: '0' and '1' merge to '?' + * + * @param y Term to be merged with + * @return A new term representing the merge result + */ + def merge(y: Implicant): Implicant = { + require(similar(y), s"merge is only reasonable when $this is similar to $y") + + // if two term can be merged, then they both are not prime implicants. + isPrime = false + y.isPrime = false + val bit = bp.value - y.bp.value + new BitPat(bp.value &~ bit, bp.mask &~ bit, width) + } + + /** Check all bits in `x` cover the correspond position in `y`. + * + * Rule to define coverage relationship among `0`, `1` and `?`: + * 1. '?' covers '0' and '1', '0' covers '0', '1' covers '1' + * 1. '1' doesn't cover '?', '1' doesn't cover '0' + * 1. '0' doesn't cover '?', '0' doesn't cover '1' + * + * For all bits that `x` don't care, `y` can be `0`, `1`, `?` + * For all bits that `x` care, `y` must be the same value and not masked. + * {{{ + * (~x.mask & -1) | ((x.mask) & ((x.value xnor y.value) & y.mask)) = -1 + * -> ~x.mask | ((x.mask) & ((x.value xnor y.value) & y.mask)) = -1 + * -> ~x.mask | ((x.value xnor y.value) & y.mask) = -1 + * -> x.mask & ~((x.value xnor y.value) & y.mask) = 0 + * -> x.mask & (~(x.value xnor y.value) | ~y.mask) = 0 + * -> x.mask & ((x.value ^ y.value) | ~y.mask) = 0 + * -> ((x.value ^ y.value) & x.mask | ~y.mask & x.mask) = 0 + * }}} + * + * @param y to check is covered by `x` or not. + * @return Whether `x` covers `y` + */ + def covers(y: Implicant): Boolean = ((bp.value ^ y.bp.value) & bp.mask | ~y.bp.mask & bp.mask) == 0 + + override def toString = (if (!isPrime) "Non" else "") + "Prime" + bp.toString.replace("BitPat", "Implicant") + } + + /** + * If two terms have different value, then their order is determined by the value, or by the mask. + */ + private implicit def ordering: Ordering[Implicant] = new Ordering[Implicant] { + override def compare(x: Implicant, y: Implicant): Int = + if (x.bp.value < y.bp.value || x.bp.value == y.bp.value && x.bp.mask > y.bp.mask) -1 else 1 + } + + /** Calculate essential prime implicants based on previously calculated prime implicants and all implicants. + * + * @param primes Prime implicants + * @param minterms All implicants + * @return (a, b, c) + * a: essential prime implicants + * b: nonessential prime implicants + * c: implicants that are not cover by any of the essential prime implicants + */ + private def getEssentialPrimeImplicants(primes: Seq[Implicant], minterms: Seq[Implicant]): (Seq[Implicant], Seq[Implicant], Seq[Implicant]) = { + // primeCovers(i): implicants that `prime(i)` covers + val primeCovers = primes.map(p => minterms.filter(p.covers)) + // eliminate prime implicants that can be covered by other prime implicants + for (((icover, pi), i) <- (primeCovers zip primes).zipWithIndex) { + for (((jcover, pj), _) <- (primeCovers zip primes).zipWithIndex.drop(i + 1)) { + // we prefer prime implicants with wider implicants coverage + if (icover.size > jcover.size && jcover.forall(pi.covers)) { + // calculate essential prime implicants with `pj` eliminated from prime implicants table + return getEssentialPrimeImplicants(primes.filter(_ != pj), minterms) + } + } + } + + // implicants that only one prime implicant covers + val essentiallyCovered = minterms.filter(t => primes.count(_.covers(t)) == 1) + // essential prime implicants, prime implicants that covers only one implicant + val essential = primes.filter(p => essentiallyCovered.exists(p.covers)) + // {nonessential} = {prime implicants} - {essential prime implicants} + val nonessential = primes.filterNot(essential contains _) + // implicants that no essential prime implicants covers + val uncovered = minterms.filterNot(t => essential.exists(_.covers(t))) + if (essential.isEmpty || uncovered.isEmpty) + (essential, nonessential, uncovered) + else { + // now there are implicants (`uncovered`) that are covered by multiple nonessential prime implicants (`nonessential`) + // need to reduce prime implicants + val (a, b, c) = getEssentialPrimeImplicants(nonessential, uncovered) + (essential ++ a, b, c) + } + } + + /** Use [[https://en.wikipedia.org/wiki/Petrick%27s_method]] to select a [[Seq]] of nonessential prime implicants + * that covers all implicants that are not covered by essential prime implicants. + * + * @param implicants Nonessential prime implicants + * @param minterms Implicants that are not covered by essential prime implicants + * @return Selected nonessential prime implicants + */ + private def getCover(implicants: Seq[Implicant], minterms: Seq[Implicant]): Seq[Implicant] = { + /** Calculate the implementation cost (using comparators) of a list of implicants, more don't cares is cheaper + * + * @param cover Implicant list + * @return How many comparators need to implement this list of implicants + */ + def getCost(cover: Seq[Implicant]): Int = cover.map(_.bp.mask.bitCount).sum + + /** Determine if one combination of prime implicants is cheaper when implementing as comparators. + * Shorter term list is cheaper, term list with more don't cares is cheaper (less comparators) + * + * @param a Operand a + * @param b Operand b + * @return `a` < `b` + */ + def cheaper(a: Seq[Implicant], b: Seq[Implicant]): Boolean = { + val ca = getCost(a) + val cb = getCost(b) + + /** If `a` < `b` + * + * Like comparing the dictionary order of two strings. + * Define `a` < `b` if both `a` and `b` are empty. + * + * @param a Operand a + * @param b Operand b + * @return `a` < `b` + */ + @tailrec + def listLess(a: Seq[Implicant], b: Seq[Implicant]): Boolean = b.nonEmpty && (a.isEmpty || a.head < b.head || a.head == b.head && listLess(a.tail, b.tail)) + + ca < cb || ca == cb && listLess(a.sortWith(_ < _), b.sortWith(_ < _)) + } + + // if there are no implicant that is not covered by essential prime implicants, which means all implicants are + // covered by essential prime implicants, there is no need to apply Petrick's method + if (minterms.nonEmpty) { + // cover(i): nonessential prime implicants that covers `minterms(i)` + val cover = minterms.map(m => implicants.filter(_.covers(m))) + // all subsets of `cover`, NP algorithm, O(2 ^ len(cover)) + val all = cover.tail.foldLeft(cover.head.map(Set(_)))((c0, c1) => c0.flatMap(a => c1.map(a + _))) + all.map(_.toList).reduceLeft((a, b) => if (cheaper(a, b)) a else b) + } else + Seq[Implicant]() + } + + def minimize(table: TruthTable): TruthTable = { + require(table.table.nonEmpty, "Truth table must not be empty") + + // extract decode table to inputs and outputs + val (inputs, outputs) = table.table.unzip + + require(outputs.map(_.getWidth == table.default.getWidth).reduce(_ && _), "All output BitPats and default BitPat must have the same length") + require(if (inputs.toSeq.length > 1) inputs.tail.map(_.width == inputs.head.width).reduce(_ && _) else true, "All input BitPats must have the same length") + + // make sure no two inputs specified in the truth table intersect + for (t <- inputs.tails; if t.nonEmpty) + for (u <- t.tail) + require(!t.head.intersects(u), "truth table entries " + t.head + " and " + u + " overlap") + + // number of inputs + val n = inputs.head.width + // number of outputs + val m = outputs.head.getWidth + + // for all outputs + val minimized = (0 until m).flatMap(i => { + val outputBp = BitPat("b" + "?" * (m - i - 1) + "1" + "?" * i) + + // Minterms, implicants that makes the output to be 1 + val mint: Seq[Implicant] = table.table.filter { case (_, t) => t.mask.testBit(i) && t.value.testBit(i) }.keys.map(toImplicant).toSeq + // Maxterms, implicants that makes the output to be 0 + val maxt: Seq[Implicant] = table.table.filter { case (_, t) => t.mask.testBit(i) && !t.value.testBit(i) }.keys.map(toImplicant).toSeq + // Don't cares, implicants that can produce either 0 or 1 as output + val dc: Seq[Implicant] = table.table.filter { case (_, t) => !t.mask.testBit(i) }.keys.map(toImplicant).toSeq + + val (implicants, defaultToDc) = table.default match { + case x if x.mask.testBit(i) && !x.value.testBit(i) => // default to 0 + (mint ++ dc, false) + case x if x.mask.testBit(i) && x.value.testBit(i) => // default to 1 + (maxt ++ dc, false) + case x if !x.mask.testBit(i) => // default to ? + (mint, true) + } + + implicants.foreach(_.isPrime = true) + val cols = (0 to n).reverse.map(b => implicants.filter(b == _.bp.mask.bitCount)) + val mergeTable = cols.map( + c => (0 to n).map( + b => collection.mutable.Set(c.filter(b == _.bp.value.bitCount):_*) + ) + ) + + // O(n ^ 3) + for (i <- 0 to n) { + for (j <- 0 until n - i) { + mergeTable(i)(j).foreach(a => mergeTable(i + 1)(j) ++= mergeTable(i)(j + 1).filter(_ similar a).map(_ merge a)) + } + if (defaultToDc) { + for (j <- 0 until n - i) { + for (a <- mergeTable(i)(j).filter(_.isPrime)) { + if (a.bp.mask.testBit(i) && !a.bp.value.testBit(i)) { + // this bit is `0` + val t = new BitPat(a.bp.value.setBit(i), a.bp.mask, a.width) + if (!maxt.exists(_.intersects(t))) mergeTable(i + 1)(j) += t merge a + } + } + for (a <- mergeTable(i)(j + 1).filter(_.isPrime)) { + if (a.bp.mask.testBit(i) && a.bp.value.testBit(i)) { + // this bit is `1` + val t = new BitPat(a.bp.value.clearBit(i), a.bp.mask, a.width) + if (!maxt.exists(_.intersects(t))) mergeTable(i + 1)(j) += a merge t + } + } + } + } + } + + val primeImplicants = mergeTable.flatten.flatten.filter(_.isPrime).sortWith(_ < _) + + // O(len(primeImplicants) ^ 4) + val (essentialPrimeImplicants, nonessentialPrimeImplicants, uncoveredImplicants) = + getEssentialPrimeImplicants(primeImplicants, implicants) + + (essentialPrimeImplicants ++ getCover(nonessentialPrimeImplicants, uncoveredImplicants)).map(a => (a.bp, outputBp)) + }) + + minimized.tail.foldLeft(table.copy(table = Map(minimized.head))) { case (tb, t) => + if (tb.table.exists(x => x._1 == t._1)) { + tb.copy(table = tb.table.map { case (k, v) => + if (k == t._1) { + def ones(bitPat: BitPat) = bitPat.rawString.zipWithIndex.collect{case ('1', x) => x} + (k, BitPat("b" + (0 until v.getWidth).map(i => if ((ones(v) ++ ones(t._2)).contains(i)) "1" else "?").mkString)) + } else (k, v) + }) + } else { + tb.copy(table = tb.table + t) + } + } + } +}
\ No newline at end of file diff --git a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala new file mode 100644 index 00000000..683de16b --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import chisel3.util.BitPat + +final class TruthTable(val table: Map[BitPat, BitPat], val default: BitPat) { + + def inputWidth = table.head._1.getWidth + + def outputWidth = table.head._2.getWidth + + override def toString: String = { + def writeRow(map: (BitPat, BitPat)): String = + s"${map._1.rawString}->${map._2.rawString}" + + (table.map(writeRow) ++ Seq(s"${" "*(inputWidth + 2)}${default.rawString}")).toSeq.sorted.mkString("\n") + } + + def copy(table: Map[BitPat, BitPat] = this.table, default: BitPat = this.default) = new TruthTable(table, default) + + override def equals(y: Any): Boolean = { + y match { + case y: TruthTable => toString == y.toString + case _ => false + } + } +} + +object TruthTable { + /** Parse TruthTable from its string representation. */ + def apply(tableString: String): TruthTable = { + TruthTable( + tableString + .split("\n") + .filter(_.contains("->")) + .map(_.split("->").map(str => BitPat(s"b$str"))) + .map(bps => bps(0) -> bps(1)) + .toSeq, + BitPat(s"b${tableString.split("\n").filterNot(_.contains("->")).head.replace(" ", "")}") + ) + } + + /** Convert a table and default output into a [[TruthTable]]. */ + def apply(table: Iterable[(BitPat, BitPat)], default: BitPat): TruthTable = { + require(table.map(_._1.getWidth).toSet.size == 1, "input width not equal.") + require(table.map(_._2.getWidth).toSet.size == 1, "output width not equal.") + val outputWidth = table.map(_._2.getWidth).head + new TruthTable(table.toSeq.groupBy(_._1.toString).map { case (key, values) => + // merge same input inputs. + values.head._1 -> BitPat(s"b${ + Seq.tabulate(outputWidth) { i => + val outputSet = values.map(_._2) + .map(_.rawString) + .map(_ (i)) + .toSet + .filterNot(_ == '?') + require(outputSet.size != 2, s"TruthTable conflict in :\n${values.map { case (i, o) => s"${i.rawString}->${o.rawString}" }.mkString("\n")}") + outputSet.headOption.getOrElse('?') + }.mkString + }") + }, default) + } + + + /** consume 1 table, split it into up to 3 tables with the same default bits. + * + * @return table and its indexes from original bits. + * @note + * Since most of minimizer(like espresso) cannot handle a multiple default table. + * It is useful to split a table into 3 tables based on the default type. + */ + private[decode] def split( + table: TruthTable + ): Seq[(TruthTable, Seq[Int])] = { + def bpFilter(bitPat: BitPat, indexes: Seq[Int]): BitPat = + BitPat(s"b${bitPat.rawString.zipWithIndex.filter(b => indexes.contains(b._2)).map(_._1).mkString}") + + def tableFilter(indexes: Seq[Int]): Option[(TruthTable, Seq[Int])] = { + if(indexes.nonEmpty) Some((TruthTable( + table.table.map { case (in, out) => in -> bpFilter(out, indexes) }, + bpFilter(table.default, indexes) + ), indexes)) else None + } + + def index(bitPat: BitPat, bpType: Char): Seq[Int] = + bitPat.rawString.zipWithIndex.filter(_._1 == bpType).map(_._2) + + Seq('1', '0', '?').flatMap(t => tableFilter(index(table.default, t))) + } + + /** consume tables, merge it into single table with different default bits. + * + * @note + * Since most of minimizer(like espresso) cannot handle a multiple default table. + * It is useful to split a table into 3 tables based on the default type. + */ + private[decode] def merge( + tables: Seq[(TruthTable, Seq[Int])] + ): TruthTable = { + def reIndex(bitPat: BitPat, table: TruthTable, indexes: Seq[Int]): Seq[(Char, Int)] = + (table.table.map(a => a._1.toString -> a._2).getOrElse(bitPat.toString, BitPat.dontCare(indexes.size))).rawString.zip(indexes) + def bitPat(indexedChar: Seq[(Char, Int)]) = BitPat(s"b${indexedChar + .sortBy(_._2) + .map(_._1) + .mkString}") + TruthTable( + tables + .flatMap(_._1.table.keys) + .map { key => + key -> bitPat(tables.flatMap { case (table, indexes) => reIndex(key, table, indexes) }) + } + .toMap, + bitPat(tables.flatMap { case (table, indexes) => table.default.rawString.zip(indexes) }) + ) + } +} diff --git a/src/main/scala/chisel3/util/experimental/decode/decoder.scala b/src/main/scala/chisel3/util/experimental/decode/decoder.scala new file mode 100644 index 00000000..42e374d1 --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/decoder.scala @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import chisel3._ +import chisel3.experimental.{ChiselAnnotation, annotate} +import chisel3.util.{BitPat, pla} +import chisel3.util.experimental.getAnnotations +import firrtl.annotations.Annotation +import logger.LazyLogging + +object decoder extends LazyLogging { + /** Use a specific [[Minimizer]] to generated decoded signals. + * + * @param minimizer specific [[Minimizer]], can be [[QMCMinimizer]] or [[EspressoMinimizer]]. + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ + def apply(minimizer: Minimizer, input: UInt, truthTable: TruthTable): UInt = { + val minimizedTable = getAnnotations().collect { + case DecodeTableAnnotation(_, in, out) => TruthTable(in) -> TruthTable(out) + }.toMap.getOrElse(truthTable, minimizer.minimize(truthTable)) + if (minimizedTable.table.isEmpty) { + val outputs = Wire(UInt(minimizedTable.default.getWidth.W)) + outputs := minimizedTable.default.value.U(minimizedTable.default.getWidth.W) + outputs + } else { + val (plaInput, plaOutput) = + pla(minimizedTable.table.toSeq, BitPat(minimizedTable.default.value.U(minimizedTable.default.getWidth.W))) + + annotate(new ChiselAnnotation { + override def toFirrtl: Annotation = + DecodeTableAnnotation(plaOutput.toTarget, truthTable.toString, minimizedTable.toString) + }) + + plaInput := input + plaOutput + } + } + + /** Use [[EspressoMinimizer]] to generated decoded signals. + * + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ + def espresso(input: UInt, truthTable: TruthTable): UInt = apply(EspressoMinimizer, input, truthTable) + + /** Use [[QMCMinimizer]] to generated decoded signals. + * + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ + def qmc(input: UInt, truthTable: TruthTable): UInt = apply(QMCMinimizer, input, truthTable) + + /** try to use [[EspressoMinimizer]] to decode `input` by `truthTable` + * if `espresso` not exist in your PATH environment it will fall back to [[QMCMinimizer]], and print a warning. + * + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ + def apply(input: UInt, truthTable: TruthTable): UInt = { + def qmcFallBack(input: UInt, truthTable: TruthTable) = { + """fall back to QMC. + |Quine-McCluskey is a NP complete algorithm, may run forever or run out of memory in large decode tables. + |To get rid of this warning, please use `decoder.qmc` directly, or add espresso to your PATH. + |""".stripMargin + qmc(input, truthTable) + } + + try espresso(input, truthTable) catch { + case EspressoNotFoundException => + logger.error(s"espresso is not found in your PATH:\n${sys.env("PATH").split(":").mkString("\n")}".stripMargin) + qmcFallBack(input, truthTable) + case e: java.io.IOException => + logger.error(s"espresso failed to run with ${e.getMessage}") + qmcFallBack(input, truthTable) + } + } +} diff --git a/src/main/scala/chisel3/util/experimental/getAnnotations.scala b/src/main/scala/chisel3/util/experimental/getAnnotations.scala new file mode 100644 index 00000000..dc9b75ee --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/getAnnotations.scala @@ -0,0 +1,9 @@ +package chisel3.util.experimental + +import chisel3.internal.Builder +import firrtl.AnnotationSeq + +object getAnnotations { + /** Returns the global Annotations */ + def apply(): AnnotationSeq = Builder.annotationSeq +} diff --git a/src/main/scala/chisel3/util/pla.scala b/src/main/scala/chisel3/util/pla.scala new file mode 100644 index 00000000..c57ca962 --- /dev/null +++ b/src/main/scala/chisel3/util/pla.scala @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util + +import chisel3._ + +object pla { + + /** Construct a [[https://en.wikipedia.org/wiki/Programmable_logic_array]] from specified table. + * + * Each position in the input matrix corresponds to an input variable where + * `0` implies the corresponding input literal appears complemented in the product term. + * `1` implies the input literal appears uncomplemented in the product term + * `?` implies the input literal does not appear in the product term. + * + * For each output + * a `1` means this product term makes the function value to `1` + * and a `0` or `?` means this product term make the function value to `0` + * + * @note There is one special case which we call it `? -> 1`. In this scenario, for some of the output functions (bits), + * all input terms that make this function value to `1` is purely composed by `?`. In a real pla, this will result in + * no connection to the gates in the AND Plane (verilog `z` on gate inputs), which in turn makes the outputs of the + * AND Plane undetermined (verilog `x` on outputs). This is not desired behavior in most cases, for example the + * minimization result of following truth table: + * 0 -> 1 + * 1 -> 1 + * which is: + * ? -> 1 + * actually means something other than a verilog `x`. To ease the generation of minimized truth tables, this pla + * generation api will hard wire outputs to a `1` on this special case. + * This behavior is formally described as: if product terms that make one function value to `1` is solely consisted + * of don't-cares (`?`s), then this function is implemented as a constant `1`. + * + * @param table A [[Seq]] of inputs -> outputs mapping + * @param invert A [[BitPat]] specify which bit of the output should be inverted. `1` means the correspond position + * of the output should be inverted in the PLA, a `0` or a `?` means direct output from the OR matrix. + * @return the (input, output) [[Wire]] of [[UInt]] of the constructed pla. + * {{{ + * // A 1-of-8 decoder (like the 74xx138) can be constructed as follow + * val (inputs, outputs) = pla(Seq( + * (BitPat("b000"), BitPat("b00000001")), + * (BitPat("b001"), BitPat("b00000010")), + * (BitPat("b010"), BitPat("b00000100")), + * (BitPat("b011"), BitPat("b00001000")), + * (BitPat("b100"), BitPat("b00010000")), + * (BitPat("b101"), BitPat("b00100000")), + * (BitPat("b110"), BitPat("b01000000")), + * (BitPat("b111"), BitPat("b10000000")), + * )) + * }}} + */ + def apply(table: Seq[(BitPat, BitPat)], invert: BitPat = BitPat("b0")): (UInt, UInt) = { + require(table.nonEmpty, "pla table must not be empty") + + val (inputTerms, outputTerms) = table.unzip + require( + inputTerms.map(_.getWidth).distinct.size == 1, + "all `BitPat`s in the input part of specified PLA table must have the same width" + ) + require( + outputTerms.map(_.getWidth).distinct.size == 1, + "all `BitPat`s in the output part of specified PLA table must have the same width" + ) + + // now all inputs / outputs have the same width + val numberOfInputs = inputTerms.head.getWidth + val numberOfOutputs = outputTerms.head.getWidth + + val inverterMask = invert.value & invert.mask + if (inverterMask.bitCount != 0) + require(invert.getWidth == numberOfOutputs, + "non-zero inverter mask must have the same width as the output part of specified PLA table" + ) + + // input wires of the generated PLA + val inputs = Wire(UInt(numberOfInputs.W)) + val invInputs = ~inputs + + // output wires of the generated PLA + val outputs = Wire(UInt(numberOfOutputs.W)) + + // the AND matrix + // use `term -> AND line` map to reuse AND matrix output lines + val andMatrixOutputs: Map[String, Bool] = inputTerms.map { t => + val andMatrixInput = Seq + .tabulate(numberOfInputs) { i => + if (t.mask.testBit(i)) { + Some( + if (t.value.testBit(i)) inputs(i) + else invInputs(i) + ) + } else { + None + } + } + .flatten + if (andMatrixInput.nonEmpty) t.toString -> Cat(andMatrixInput).andR() else t.toString -> true.B + }.toMap + + // the OR matrix + val orMatrixOutputs: UInt = Cat( + Seq + .tabulate(numberOfOutputs) { i => + val andMatrixLines = table + // OR matrix composed by input terms which makes this output bit a `1` + .filter { + case (_, or) => or.mask.testBit(i) && or.value.testBit(i) + }.map { + case (inputTerm, _) => + andMatrixOutputs(inputTerm.toString) + } + if (andMatrixLines.isEmpty) false.B + else Cat(andMatrixLines).orR() + } + .reverse + ) + + // the INV matrix, useful for decoders + val invMatrixOutputs: UInt = Cat( + Seq + .tabulate(numberOfOutputs) { i => + if (inverterMask.testBit(i)) ~orMatrixOutputs(i) + else orMatrixOutputs(i) + } + .reverse + ) + + outputs := invMatrixOutputs + + (inputs, outputs) + } +} diff --git a/src/main/scala/chisel3/verilog.scala b/src/main/scala/chisel3/verilog.scala new file mode 100644 index 00000000..a91444de --- /dev/null +++ b/src/main/scala/chisel3/verilog.scala @@ -0,0 +1,15 @@ +package chisel3 + +import chisel3.stage.ChiselStage +import firrtl.AnnotationSeq + +object getVerilogString { + def apply(gen: => RawModule): String = ChiselStage.emitVerilog(gen) +} + +object emitVerilog { + def apply(gen: => RawModule, args: Array[String] = Array.empty, + annotations: AnnotationSeq = Seq.empty): Unit = { + (new ChiselStage).emitVerilog(gen, args, annotations) + } +} |
