diff options
| author | Jack Koenig | 2020-01-07 19:34:29 -0800 |
|---|---|---|
| committer | GitHub | 2020-01-07 19:34:29 -0800 |
| commit | d5dd427c0267dc143d4297d5fd0716f19cd7634b (patch) | |
| tree | 589e59ef4e2563ca67e695f476ed67a8f8ef5aa5 | |
| parent | 66f354558a21cd0d339968b3665b44c17c2c16e8 (diff) | |
| parent | e27bb38cf5b3ee8135bf416c2532b2abc2fc5ae4 (diff) | |
Merge pull request #1264 from freechipsproject/cleanup-verilog-emitter-casts
Cleanup verilog emitter casts
| -rw-r--r-- | src/main/scala/firrtl/Emitter.scala | 85 | ||||
| -rw-r--r-- | src/main/scala/firrtl/Utils.scala | 11 | ||||
| -rw-r--r-- | src/main/scala/firrtl/transforms/ConstantPropagation.scala | 31 | ||||
| -rw-r--r-- | src/main/scala/firrtl/transforms/InlineCasts.scala | 70 | ||||
| -rw-r--r-- | src/main/scala/firrtl/transforms/LegalizeClocks.scala | 69 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/AsyncResetSpec.scala | 6 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/ConstantPropagationTests.scala | 26 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/VerilogEmitterTests.scala | 99 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/transforms/LegalizeClocks.scala | 67 |
9 files changed, 414 insertions, 50 deletions
diff --git a/src/main/scala/firrtl/Emitter.scala b/src/main/scala/firrtl/Emitter.scala index 95c762ae..2d98cf04 100644 --- a/src/main/scala/firrtl/Emitter.scala +++ b/src/main/scala/firrtl/Emitter.scala @@ -281,29 +281,35 @@ class VerilogEmitter extends SeqTransform with Emitter { case _ => throwInternalError(s"attempt to print unrecognized expression: $e") } + // NOTE: We emit SInts as regular Verilog unsigned wires/regs so the real type of any SInt + // reference is actually unsigned in the emitted Verilog. Thus we must cast refs as necessary + // to ensure Verilog operations are signed. def op_stream(doprim: DoPrim): Seq[Any] = { - def cast_if(e: Expression): Any = { - doprim.args find (_.tpe match { - case (_: SIntType) => true - case (_) => false - }) match { - case None => e - case Some(_) => e.tpe match { - case (_: SIntType) => Seq("$signed(", e, ")") - case (_: UIntType) => Seq("$signed({1'b0,", e, "})") - case _ => throwInternalError(s"unrecognized type: $e") + // Cast to SInt, don't cast multiple times + def doCast(e: Expression): Any = e match { + case DoPrim(AsSInt, Seq(arg), _,_) => doCast(arg) + case slit: SIntLiteral => slit + case other => Seq("$signed(", other, ")") + } + def castIf(e: Expression): Any = { + if (doprim.args.exists(_.tpe.isInstanceOf[SIntType])) { + e.tpe match { + case _: SIntType => doCast(e) + case _ => throwInternalError(s"Unexpected non-SInt type for $e in $doprim") } + } else { + e } } def cast(e: Expression): Any = doprim.tpe match { - case (t: UIntType) => e - case (t: SIntType) => Seq("$signed(",e,")") - case _ => throwInternalError(s"cast - unrecognized type: $e") + case _: UIntType => e + case _: SIntType => doCast(e) + case _ => throwInternalError(s"Unexpected type for $e in $doprim") } - def cast_as(e: Expression): Any = e.tpe match { - case (t: UIntType) => e - case (t: SIntType) => Seq("$signed(",e,")") - case _ => throwInternalError(s"cast_as - unrecognized type: $e") + def castAs(e: Expression): Any = e.tpe match { + case _: UIntType => e + case _: SIntType => doCast(e) + case _ => throwInternalError(s"Unexpected type for $e in $doprim") } def a0: Expression = doprim.args.head def a1: Expression = doprim.args(1) @@ -313,12 +319,14 @@ class VerilogEmitter extends SeqTransform with Emitter { def checkArgumentLegality(e: Expression): Unit = e match { case _: UIntLiteral | _: SIntLiteral | _: WRef | _: WSubField => case DoPrim(Not, args, _,_) => args.foreach(checkArgumentLegality) + case DoPrim(op, args, _,_) if isCast(op) => args.foreach(checkArgumentLegality) case _ => throw EmitterException(s"Can't emit ${e.getClass.getName} as PrimOp argument") } def checkCatArgumentLegality(e: Expression): Unit = e match { case _: UIntLiteral | _: SIntLiteral | _: WRef | _: WSubField => case DoPrim(Not, args, _,_) => args.foreach(checkArgumentLegality) + case DoPrim(op, args, _,_) if isCast(op) => args.foreach(checkArgumentLegality) case DoPrim(Cat, args, _, _) => args foreach(checkCatArgumentLegality) case _ => throw EmitterException(s"Can't emit ${e.getClass.getName} as PrimOp argument") } @@ -337,22 +345,23 @@ class VerilogEmitter extends SeqTransform with Emitter { doprim.op match { case Cat => doprim.args foreach(checkCatArgumentLegality) + case cast if isCast(cast) => // Casts are allowed to wrap any Expression case other => doprim.args foreach checkArgumentLegality } doprim.op match { - case Add => Seq(cast_if(a0), " + ", cast_if(a1)) - case Addw => Seq(cast_if(a0), " + ", cast_if(a1)) - case Sub => Seq(cast_if(a0), " - ", cast_if(a1)) - case Subw => Seq(cast_if(a0), " - ", cast_if(a1)) - case Mul => Seq(cast_if(a0), " * ", cast_if(a1)) - case Div => Seq(cast_if(a0), " / ", cast_if(a1)) - case Rem => Seq(cast_if(a0), " % ", cast_if(a1)) - case Lt => Seq(cast_if(a0), " < ", cast_if(a1)) - case Leq => Seq(cast_if(a0), " <= ", cast_if(a1)) - case Gt => Seq(cast_if(a0), " > ", cast_if(a1)) - case Geq => Seq(cast_if(a0), " >= ", cast_if(a1)) - case Eq => Seq(cast_if(a0), " == ", cast_if(a1)) - case Neq => Seq(cast_if(a0), " != ", cast_if(a1)) + case Add => Seq(castIf(a0), " + ", castIf(a1)) + case Addw => Seq(castIf(a0), " + ", castIf(a1)) + case Sub => Seq(castIf(a0), " - ", castIf(a1)) + case Subw => Seq(castIf(a0), " - ", castIf(a1)) + case Mul => Seq(castIf(a0), " * ", castIf(a1)) + case Div => Seq(castIf(a0), " / ", castIf(a1)) + case Rem => Seq(castIf(a0), " % ", castIf(a1)) + case Lt => Seq(castIf(a0), " < ", castIf(a1)) + case Leq => Seq(castIf(a0), " <= ", castIf(a1)) + case Gt => Seq(castIf(a0), " > ", castIf(a1)) + case Geq => Seq(castIf(a0), " >= ", castIf(a1)) + case Eq => Seq(castIf(a0), " == ", castIf(a1)) + case Neq => Seq(castIf(a0), " != ", castIf(a1)) case Pad => val w = bitWidth(a0.tpe) val diff = c0 - w @@ -364,10 +373,10 @@ class VerilogEmitter extends SeqTransform with Emitter { case (_: SIntType) => Seq("{{", diff, "{", a0, "[", w - 1, "]}},", a0, "}") case (_) => Seq("{{", diff, "'d0}, ", a0, "}") } - case AsUInt => Seq("$unsigned(", a0, ")") - case AsSInt => Seq("$signed(", a0, ")") - case AsClock => Seq(a0) - case AsAsyncReset => Seq(a0) + // Because we don't support complex Expressions, all casts are ignored + // This simplifies handling of assignment of a signed expression to an unsigned LHS value + // which does not require a cast in Verilog + case AsUInt | AsSInt | AsClock | AsAsyncReset => Seq(a0) case Dshlw => Seq(cast(a0), " << ", a1) case Dshl => Seq(cast(a0), " << ", a1) case Dshr => doprim.tpe match { @@ -384,9 +393,9 @@ class VerilogEmitter extends SeqTransform with Emitter { case (_: SIntType) => Seq(cast(a0)) } case Not => Seq("~", a0) - case And => Seq(cast_as(a0), " & ", cast_as(a1)) - case Or => Seq(cast_as(a0), " | ", cast_as(a1)) - case Xor => Seq(cast_as(a0), " ^ ", cast_as(a1)) + case And => Seq(castAs(a0), " & ", castAs(a1)) + case Or => Seq(castAs(a0), " | ", castAs(a1)) + case Xor => Seq(castAs(a0), " ^ ", castAs(a1)) case Andr => Seq("&", cast(a0)) case Orr => Seq("|", cast(a0)) case Xorr => Seq("^", cast(a0)) @@ -967,6 +976,8 @@ class VerilogEmitter extends SeqTransform with Emitter { new BlackBoxSourceHelper, new ReplaceTruncatingArithmetic, new InlineNotsTransform, + new InlineCastsTransform, + new LegalizeClocksTransform, new FlattenRegUpdate, new DeadCodeElimination, passes.VerilogModulusCleanup, diff --git a/src/main/scala/firrtl/Utils.scala b/src/main/scala/firrtl/Utils.scala index 8a76aca6..6cb309b3 100644 --- a/src/main/scala/firrtl/Utils.scala +++ b/src/main/scala/firrtl/Utils.scala @@ -188,6 +188,17 @@ object Utils extends LazyLogging { case sx => sx } + /** Returns true if PrimOp is a cast, false otherwise */ + def isCast(op: PrimOp): Boolean = op match { + case AsUInt | AsSInt | AsClock | AsAsyncReset | AsFixedPoint => true + case _ => false + } + /** Returns true if Expression is a casting PrimOp, false otherwise */ + def isCast(expr: Expression): Boolean = expr match { + case DoPrim(op, _,_,_) if isCast(op) => true + case _ => false + } + /** Provide a nice name to create a temporary **/ def niceName(e: Expression): String = niceName(1)(e) def niceName(depth: Int)(e: Expression): String = { diff --git a/src/main/scala/firrtl/transforms/ConstantPropagation.scala b/src/main/scala/firrtl/transforms/ConstantPropagation.scala index a008a4d3..20b24e60 100644 --- a/src/main/scala/firrtl/transforms/ConstantPropagation.scala +++ b/src/main/scala/firrtl/transforms/ConstantPropagation.scala @@ -286,16 +286,33 @@ class ConstantPropagation extends Transform with ResolvedAnnotationPaths { case UIntLiteral(v, IntWidth(w)) => UIntLiteral(v ^ ((BigInt(1) << w.toInt) - 1), IntWidth(w)) case _ => e } - case AsUInt => e.args.head match { - case SIntLiteral(v, IntWidth(w)) => UIntLiteral(v + (if (v < 0) BigInt(1) << w.toInt else 0), IntWidth(w)) - case u: UIntLiteral => u - case _ => e - } + case AsUInt => + e.args.head match { + case SIntLiteral(v, IntWidth(w)) => UIntLiteral(v + (if (v < 0) BigInt(1) << w.toInt else 0), IntWidth(w)) + case arg => arg.tpe match { + case _: UIntType => arg + case _ => e + } + } case AsSInt => e.args.head match { case UIntLiteral(v, IntWidth(w)) => SIntLiteral(v - ((v >> (w.toInt-1)) << w.toInt), IntWidth(w)) - case s: SIntLiteral => s - case _ => e + case arg => arg.tpe match { + case _: SIntType => arg + case _ => e + } } + case AsClock => + val arg = e.args.head + arg.tpe match { + case ClockType => arg + case _ => e + } + case AsAsyncReset => + val arg = e.args.head + arg.tpe match { + case AsyncResetType => arg + case _ => e + } case Pad => e.args.head match { case UIntLiteral(v, IntWidth(w)) => UIntLiteral(v, IntWidth(e.consts.head max w)) case SIntLiteral(v, IntWidth(w)) => SIntLiteral(v, IntWidth(e.consts.head max w)) diff --git a/src/main/scala/firrtl/transforms/InlineCasts.scala b/src/main/scala/firrtl/transforms/InlineCasts.scala new file mode 100644 index 00000000..e504eb70 --- /dev/null +++ b/src/main/scala/firrtl/transforms/InlineCasts.scala @@ -0,0 +1,70 @@ +package firrtl +package transforms + +import firrtl.ir._ +import firrtl.Mappers._ + +import firrtl.Utils.{isCast, NodeMap} + +object InlineCastsTransform { + + // Checks if an Expression is made up of only casts terminated by a Literal or Reference + // There must be at least one cast + // Note that this can have false negatives but MUST NOT have false positives + private def isSimpleCast(castSeen: Boolean)(expr: Expression): Boolean = expr match { + case _: WRef | _: Literal | _: WSubField => castSeen + case DoPrim(op, args, _,_) if isCast(op) => args.forall(isSimpleCast(true)) + case _ => false + } + + /** Recursively replace [[WRef]]s with new [[Expression]]s + * + * @param replace a '''mutable''' HashMap mapping [[WRef]]s to values with which the [[WRef]] + * will be replaced. It is '''not''' mutated in this function + * @param expr the Expression being transformed + * @return Returns expr with [[WRef]]s replaced by values found in replace + */ + def onExpr(replace: NodeMap)(expr: Expression): Expression = { + expr.map(onExpr(replace)) match { + case e @ WRef(name, _,_,_) => + replace.get(name) + .filter(isSimpleCast(castSeen=false)) + .getOrElse(e) + case e @ DoPrim(op, Seq(WRef(name, _,_,_)), _,_) if isCast(op) => + replace.get(name) + .map(value => e.copy(args = Seq(value))) + .getOrElse(e) + case other => other // Not a candidate + } + } + + /** Inline casts in a Statement + * + * @param netlist a '''mutable''' HashMap mapping references to [[firrtl.ir.DefNode DefNode]]s to their connected + * [[firrtl.ir.Expression Expression]]s. This function '''will''' mutate it if stmt is a [[firrtl.ir.DefNode + * DefNode]] with a value that is a cast [[PrimOp]] + * @param stmt the Statement being searched for nodes and transformed + * @return Returns stmt with casts inlined + */ + def onStmt(netlist: NodeMap)(stmt: Statement): Statement = + stmt.map(onStmt(netlist)).map(onExpr(netlist)) match { + case node @ DefNode(_, name, value) => + netlist(name) = value + node + case other => other + } + + /** Replaces truncating arithmetic in a Module */ + def onMod(mod: DefModule): DefModule = mod.map(onStmt(new NodeMap)) +} + +/** Inline nodes that are simple casts */ +class InlineCastsTransform extends Transform { + def inputForm = LowForm + def outputForm = LowForm + + def execute(state: CircuitState): CircuitState = { + val modulesx = state.circuit.modules.map(InlineCastsTransform.onMod(_)) + state.copy(circuit = state.circuit.copy(modules = modulesx)) + } +} diff --git a/src/main/scala/firrtl/transforms/LegalizeClocks.scala b/src/main/scala/firrtl/transforms/LegalizeClocks.scala new file mode 100644 index 00000000..1c2fc045 --- /dev/null +++ b/src/main/scala/firrtl/transforms/LegalizeClocks.scala @@ -0,0 +1,69 @@ +package firrtl +package transforms + +import firrtl.ir._ +import firrtl.Mappers._ +import firrtl.Utils.isCast + +// Fixup otherwise legal Verilog that lint tools and other tools don't like +// Currently: +// - don't emit "always @(posedge <literal>)" +// Hitting this case is rare, but legal FIRRTL +// TODO This should be unified with all Verilog legalization transforms +object LegalizeClocksTransform { + + // Checks if an Expression is illegal in use in a @(posedge <Expression>) construct + // Legality is defined here by what standard lint tools accept + // Currently only looks for literals nested within casts + private def illegalClockExpr(expr: Expression): Boolean = expr match { + case _: Literal => true + case DoPrim(op, args, _,_) if isCast(op) => args.exists(illegalClockExpr) + case _ => false + } + + /** Legalize Clocks in a Statement + * + * Enforces legal Verilog semantics on all Clock Expressions. + * Legal is defined as what standard lint tools accept. + * Currently only Literal Expressions (guarded by casts) are handled. + * + * @note namespace is lazy because it should not typically be needed + */ + def onStmt(namespace: => Namespace)(stmt: Statement): Statement = + stmt.map(onStmt(namespace)) match { + // Proper union types would deduplicate this code + case r: DefRegister if illegalClockExpr(r.clock) => + val node = DefNode(r.info, namespace.newTemp, r.clock) + val rx = r.copy(clock = WRef(node)) + Block(Seq(node, rx)) + case p: Print if illegalClockExpr(p.clk) => + val node = DefNode(p.info, namespace.newTemp, p.clk) + val px = p.copy(clk = WRef(node)) + Block(Seq(node, px)) + case s: Stop if illegalClockExpr(s.clk) => + val node = DefNode(s.info, namespace.newTemp, s.clk) + val sx = s.copy(clk = WRef(node)) + Block(Seq(node, sx)) + case other => other + } + + def onMod(mod: DefModule): DefModule = { + // It's actually *extremely* important that this Namespace is a lazy val + // onStmt accepts it lazily so that we don't perform the namespacing traversal unless necessary + // If we were to inline the declaration, it would create a Namespace for every problem, causing + // name collisions + lazy val namespace = Namespace(mod) + mod.map(onStmt(namespace)) + } +} + +/** Ensure Clocks to be emitted are legal Verilog */ +class LegalizeClocksTransform extends Transform { + def inputForm = LowForm + def outputForm = LowForm + + def execute(state: CircuitState): CircuitState = { + val modulesx = state.circuit.modules.map(LegalizeClocksTransform.onMod(_)) + state.copy(circuit = state.circuit.copy(modules = modulesx)) + } +} diff --git a/src/test/scala/firrtlTests/AsyncResetSpec.scala b/src/test/scala/firrtlTests/AsyncResetSpec.scala index ed90954b..f347ec14 100644 --- a/src/test/scala/firrtlTests/AsyncResetSpec.scala +++ b/src/test/scala/firrtlTests/AsyncResetSpec.scala @@ -65,10 +65,10 @@ class AsyncResetSpec extends FirrtlFlatSpec { |z <= asAsyncReset(a) |""".stripMargin ) - result should containLine ("assign v = $unsigned(a);") - result should containLine ("assign w = $signed(a);") + result should containLine ("assign v = a;") + result should containLine ("assign w = a;") result should containLine ("assign x = a;") - result should containLine ("assign y = $signed(a);") + result should containLine ("assign y = a;") result should containLine ("assign z = a;") } diff --git a/src/test/scala/firrtlTests/ConstantPropagationTests.scala b/src/test/scala/firrtlTests/ConstantPropagationTests.scala index 71709255..af186cda 100644 --- a/src/test/scala/firrtlTests/ConstantPropagationTests.scala +++ b/src/test/scala/firrtlTests/ConstantPropagationTests.scala @@ -3,8 +3,6 @@ package firrtlTests import firrtl._ -import firrtl.ir.Circuit -import firrtl.Parser.IgnoreInfo import firrtl.passes._ import firrtl.transforms._ @@ -824,6 +822,30 @@ class ConstantPropagationSingleModule extends ConstantPropagationSpec { """.stripMargin (parse(exec(input))) should be(parse(check)) } + + def castCheck(tpe: String, cast: String): Unit = { + val input = + s"""circuit Top : + | module Top : + | input x : $tpe + | output z : $tpe + | z <= $cast(x) + """.stripMargin + val check = + s"""circuit Top : + | module Top : + | input x : $tpe + | output z : $tpe + | z <= x + """.stripMargin + (parse(exec(input)).serialize) should be (parse(check).serialize) + } + it should "optimize unnecessary casts" in { + castCheck("UInt<4>", "asUInt") + castCheck("SInt<4>", "asSInt") + castCheck("Clock", "asClock") + castCheck("AsyncReset", "asAsyncReset") + } } // More sophisticated tests of the full compiler diff --git a/src/test/scala/firrtlTests/VerilogEmitterTests.scala b/src/test/scala/firrtlTests/VerilogEmitterTests.scala index 0376a830..bb7659e9 100644 --- a/src/test/scala/firrtlTests/VerilogEmitterTests.scala +++ b/src/test/scala/firrtlTests/VerilogEmitterTests.scala @@ -182,6 +182,16 @@ class DoPrimVerilog extends FirrtlFlatSpec { } class VerilogEmitterSpec extends FirrtlFlatSpec { + private def compile(input: String): CircuitState = + (new VerilogCompiler).compileAndEmit(CircuitState(parse(input), ChirrtlForm), List.empty) + private def compileBody(body: String): CircuitState = { + val str = """ + |circuit Test : + | module Test : + |""".stripMargin + body.split("\n").mkString(" ", "\n ", "") + compile(str) + } + "Ports" should "emit with widths aligned and names aligned" in { val compiler = new VerilogCompiler val input = @@ -378,7 +388,6 @@ class VerilogEmitterSpec extends FirrtlFlatSpec { output.circuit.serialize should be (parse(check_firrtl).serialize) } - behavior of "Register Updates" they should "emit using 'else if' constructs" in { @@ -520,6 +529,94 @@ class VerilogEmitterSpec extends FirrtlFlatSpec { result should containLine ("tmp <= in_9;") } + "SInt addition" should "have casts" in { + val compiler = new VerilogCompiler + val result = compileBody( + """input x : SInt<4> + |input y : SInt<4> + |output z : SInt + |z <= add(x, y) + |""".stripMargin + ) + result should containLine("assign z = $signed(x) + $signed(y);") + } + + it should "NOT cast SInt literals" in { + val compiler = new VerilogCompiler + val result = compileBody( + """input x : SInt<4> + |output z : SInt + |z <= add(x, SInt(-1)) + |""".stripMargin + ) + result should containLine("assign z = $signed(x) + -4'sh1;") + } + + it should "inline asSInt casts" in { + val compiler = new VerilogCompiler + val result = compileBody( + """input x : UInt<4> + |input y : UInt<4> + |output z : SInt + |node _T_1 = asSInt(x) + |z <= add(_T_1, asSInt(y)) + |""".stripMargin + ) + result should containLine("assign z = $signed(x) + $signed(y);") + } + + "Verilog Emitter" should "drop asUInt casts on Clocks" in { + val compiler = new VerilogCompiler + val result = compileBody( + """input x : Clock + |input y : Clock + |output z : UInt<1> + |node _T_1 = asUInt(x) + |z <= eq(_T_1, asUInt(y)) + |""".stripMargin + ) + result should containLine("assign z = x == y;") + } + + it should "drop asClock casts on UInts" in { + val compiler = new VerilogCompiler + val result = compileBody( + """input x : UInt<1> + |input y : UInt<1> + |output z : Clock + |node _T_1 = eq(x, y) + |z <= asClock(_T_1) + |""".stripMargin + ) + result should containLine("assign z = x == y;") + } + + it should "drop asUInt casts on AsyncResets" in { + val compiler = new VerilogCompiler + val result = compileBody( + """input x : AsyncReset + |input y : AsyncReset + |output z : UInt<1> + |node _T_1 = asUInt(x) + |z <= eq(_T_1, asUInt(y)) + |""".stripMargin + ) + result should containLine("assign z = x == y;") + } + + it should "drop asAsyncReset casts on UInts" in { + val compiler = new VerilogCompiler + val result = compileBody( + """input x : UInt<1> + |input y : UInt<1> + |output z : AsyncReset + |node _T_1 = eq(x, y) + |z <= asAsyncReset(_T_1) + |""".stripMargin + ) + result should containLine("assign z = x == y;") + } + } class VerilogDescriptionEmitterSpec extends FirrtlFlatSpec { diff --git a/src/test/scala/firrtlTests/transforms/LegalizeClocks.scala b/src/test/scala/firrtlTests/transforms/LegalizeClocks.scala new file mode 100644 index 00000000..5c2412ae --- /dev/null +++ b/src/test/scala/firrtlTests/transforms/LegalizeClocks.scala @@ -0,0 +1,67 @@ +// See LICENSE for license details. + +package firrtlTests.transforms + +import firrtl._ +import firrtlTests.FirrtlFlatSpec +import firrtlTests.FirrtlCheckers._ + +class LegalizeClocksTransformSpec extends FirrtlFlatSpec { + def compile(input: String): CircuitState = + (new MinimumVerilogCompiler).compileAndEmit(CircuitState(parse(input), ChirrtlForm), Nil) + + behavior of "LegalizeClocksTransform" + + it should "not emit @(posedge 1'h0) for stop" in { + val input = + """circuit test : + | module test : + | stop(asClock(UInt(1)), UInt(1), 1) + |""".stripMargin + val result = compile(input) + result should containLine (s"always @(posedge _GEN_0) begin") + result.getEmittedCircuit.value shouldNot include ("always @(posedge 1") + } + + it should "not emit @(posedge 1'h0) for printf" in { + val input = + """circuit test : + | module test : + | printf(asClock(UInt(1)), UInt(1), "hi") + |""".stripMargin + val result = compile(input) + result should containLine (s"always @(posedge _GEN_0) begin") + result.getEmittedCircuit.value shouldNot include ("always @(posedge 1") + } + + it should "not emit @(posedge 1'h0) for reg" in { + val input = + """circuit test : + | module test : + | output out : UInt<8> + | input in : UInt<8> + | reg r : UInt<8>, asClock(UInt(0)) + | r <= in + | out <= r + |""".stripMargin + val result = compile(input) + result should containLine (s"always @(posedge _GEN_0) begin") + result.getEmittedCircuit.value shouldNot include ("always @(posedge 1") + } + + it should "deduplicate injected nodes for literal clocks" in { + val input = + """circuit test : + | module test : + | printf(asClock(UInt(1)), UInt(1), "hi") + | stop(asClock(UInt(1)), UInt(1), 1) + |""".stripMargin + val result = compile(input) + result should containLine (s"wire _GEN_0;") + // Check that there's only 1 _GEN_0 instantiation + val verilog = result.getEmittedCircuit.value + val matches = "wire\\s+_GEN_0;".r.findAllIn(verilog) + matches.size should be (1) + + } +} |
