diff options
Diffstat (limited to 'src/test')
| -rw-r--r-- | src/test/scala/firrtl/ir/StructuralHashSpec.scala | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/src/test/scala/firrtl/ir/StructuralHashSpec.scala b/src/test/scala/firrtl/ir/StructuralHashSpec.scala new file mode 100644 index 00000000..17fe0b84 --- /dev/null +++ b/src/test/scala/firrtl/ir/StructuralHashSpec.scala @@ -0,0 +1,277 @@ +// See LICENSE for license details. + +package firrtl.ir + +import firrtl.PrimOps._ +import org.scalatest.flatspec.AnyFlatSpec + +class StructuralHashSpec extends AnyFlatSpec { + private def hash(n: DefModule): HashCode = StructuralHash.sha256(n, n => n) + private def hash(c: Circuit): HashCode = StructuralHash.sha256Node(c) + private def hash(e: Expression): HashCode = StructuralHash.sha256Node(e) + private def hash(t: Type): HashCode = StructuralHash.sha256Node(t) + private def hash(s: Statement): HashCode = StructuralHash.sha256Node(s) + private val highFirrtlCompiler = new firrtl.stage.transforms.Compiler( + targets = firrtl.stage.Forms.HighForm + ) + private def parse(circuit: String): Circuit = { + val rawFirrtl = firrtl.Parser.parse(circuit) + // TODO: remove requirement that Firrtl needs to be type checked. + // The only reason this is needed for the structural hash right now is because we + // define bundles with the same list of field types to be the same, regardless of the + // name of these fields. Thus when the fields are accessed, we need to know their position + // in order to appropriately hash them. + highFirrtlCompiler.transform(firrtl.CircuitState(rawFirrtl, Seq())).circuit + } + + private val b0 = UIntLiteral(0,IntWidth(1)) + private val b1 = UIntLiteral(1,IntWidth(1)) + private val add = DoPrim(Add, Seq(b0, b1), Seq(), UnknownType) + + it should "generate the same hash if the objects are structurally the same" in { + assert(hash(b0) == hash(UIntLiteral(0,IntWidth(1)))) + assert(hash(b0) != hash(UIntLiteral(1,IntWidth(1)))) + assert(hash(b0) != hash(UIntLiteral(1,IntWidth(2)))) + + assert(hash(b1) == hash(UIntLiteral(1,IntWidth(1)))) + assert(hash(b1) != hash(UIntLiteral(0,IntWidth(1)))) + assert(hash(b1) != hash(UIntLiteral(1,IntWidth(2)))) + } + + it should "ignore expression types" in { + assert(hash(add) == hash(DoPrim(Add, Seq(b0, b1), Seq(), UnknownType))) + assert(hash(add) == hash(DoPrim(Add, Seq(b0, b1), Seq(), UIntType(UnknownWidth)))) + assert(hash(add) != hash(DoPrim(Add, Seq(b0, b0), Seq(), UnknownType))) + } + + it should "ignore variable names" in { + val a = + """circuit a: + | module a: + | input x : UInt<1> + | output y: UInt<1> + | y <= x + |""".stripMargin + + assert(hash(parse(a)) == hash(parse(a)), "the same circuit should always be equivalent") + + val b = + """circuit a: + | module a: + | input abc : UInt<1> + | output haha: UInt<1> + | haha <= abc + |""".stripMargin + + assert(hash(parse(a)) == hash(parse(b)), "renaming ports should not affect the hash by default") + + val c = + """circuit a: + | module a: + | input x : UInt<1> + | output y: UInt<1> + | y <= and(x, UInt<1>(0)) + |""".stripMargin + + assert(hash(parse(a)) != hash(parse(c)), "changing an expression should affect the hash") + + val d = + """circuit c: + | module c: + | input abc : UInt<1> + | output haha: UInt<1> + | haha <= abc + |""".stripMargin + + assert(hash(parse(a)) != hash(parse(d)), "circuits with different names are always different") + assert(hash(parse(a).modules.head) == hash(parse(d).modules.head), + "modules with different names can be structurally different") + + // for the Dedup pass we do need a way to take the port names into account + assert(StructuralHash.sha256WithSignificantPortNames(parse(a).modules.head) != + StructuralHash.sha256WithSignificantPortNames(parse(b).modules.head), + "renaming ports does affect the hash if we ask to") + } + + + it should "not ignore port names if asked to" in { + val e = + """circuit a: + | module a: + | input x : UInt<1> + | wire y: UInt<1> + | y <= x + |""".stripMargin + + val f = + """circuit a: + | module a: + | input z : UInt<1> + | wire y: UInt<1> + | y <= z + |""".stripMargin + + val g = + """circuit a: + | module a: + | input x : UInt<1> + | wire z: UInt<1> + | z <= x + |""".stripMargin + + assert(StructuralHash.sha256WithSignificantPortNames(parse(e).modules.head) != + StructuralHash.sha256WithSignificantPortNames(parse(f).modules.head), + "renaming ports does affect the hash if we ask to") + assert(StructuralHash.sha256WithSignificantPortNames(parse(e).modules.head) == + StructuralHash.sha256WithSignificantPortNames(parse(g).modules.head), + "renaming internal wires should never affect the hash") + assert(hash(parse(e).modules.head) == hash(parse(g).modules.head), + "renaming internal wires should never affect the hash") + } + + it should "not ignore port bundle names if asked to" in { + val e = + """circuit a: + | module a: + | input x : {x: UInt<1>} + | wire y: {x: UInt<1>} + | y.x <= x.x + |""".stripMargin + + val f = + """circuit a: + | module a: + | input x : {z: UInt<1>} + | wire y: {x: UInt<1>} + | y.x <= x.z + |""".stripMargin + + val g = + """circuit a: + | module a: + | input x : {x: UInt<1>} + | wire y: {z: UInt<1>} + | y.z <= x.x + |""".stripMargin + + assert(hash(parse(e).modules.head) == hash(parse(f).modules.head), + "renaming port bundles does normally not affect the hash") + assert(StructuralHash.sha256WithSignificantPortNames(parse(e).modules.head) != + StructuralHash.sha256WithSignificantPortNames(parse(f).modules.head), + "renaming port bundles does affect the hash if we ask to") + assert(StructuralHash.sha256WithSignificantPortNames(parse(e).modules.head) == + StructuralHash.sha256WithSignificantPortNames(parse(g).modules.head), + "renaming internal wire bundles should never affect the hash") + assert(hash(parse(e).modules.head) == hash(parse(g).modules.head), + "renaming internal wire bundles should never affect the hash") + } + + + it should "fail on Info" in { + // it does not make sense to hash Info nodes + assertThrows[RuntimeException] { + StructuralHash.sha256Node(FileInfo(StringLit(""))) + } + } + + "Bundles with different field names" should "be structurally equivalent" in { + def parse(str: String): BundleType = { + val src = + s"""circuit c: + | module c: + | input z: $str + |""".stripMargin + val c = firrtl.Parser.parse(src) + val tpe = c.modules.head.ports.head.tpe + tpe.asInstanceOf[BundleType] + } + + val a = "{x: UInt<1>, y: UInt<1>}" + assert(hash(parse(a)) == hash(parse(a)), "the same bundle should always be equivalent") + + val b = "{z: UInt<1>, y: UInt<1>}" + assert(hash(parse(a)) == hash(parse(b)), "changing a field name should maintain equivalence") + + val c = "{x: UInt<2>, y: UInt<1>}" + assert(hash(parse(a)) != hash(parse(c)), "changing a field type should not maintain equivalence") + + val d = "{x: UInt<1>, y: {y: UInt<1>}}" + assert(hash(parse(a)) != hash(parse(d)), "changing the structure should not maintain equivalence") + + assert(hash(parse("{z: {y: {x: UInt<1>}}, a: UInt<1>}")) == hash(parse("{a: {b: {c: UInt<1>}}, z: UInt<1>}"))) + } + + "ExtModules with different names but the same defname" should "be structurally equivalent" in { + val a = + """circuit a: + | extmodule a: + | input x : UInt<1> + | defname = xyz + |""".stripMargin + + val b = + """circuit b: + | extmodule b: + | input y : UInt<1> + | defname = xyz + |""".stripMargin + + // Q: should extmodule portnames always be significant since they map to the verilog pins? + // A: It would be a bug for two exmodules in the same circuit to have the same defname but different + // port names. This should be detected by an earlier pass and thus we do not have to deal with that situation. + assert(hash(parse(a).modules.head) == hash(parse(b).modules.head), + "two ext modules with the same defname and the same type and number of ports") + assert(StructuralHash.sha256WithSignificantPortNames(parse(a).modules.head) != + StructuralHash.sha256WithSignificantPortNames(parse(b).modules.head), + "two ext modules with significant port names") + } + + "Blocks and empty statements" should "not affect structural equivalence" in { + val stmtA = DefNode(NoInfo, "a", UIntLiteral(1)) + val stmtB = DefNode(NoInfo, "b", UIntLiteral(1)) + + val a = Block(Seq(Block(Seq(stmtA)), stmtB)) + val b = Block(Seq(stmtA, stmtB)) + assert(hash(a) == hash(b)) + + val c = Block(Seq(Block(Seq(Block(Seq(stmtA, stmtB)))))) + assert(hash(a) == hash(c)) + + val d = Block(Seq(stmtA)) + assert(hash(a) != hash(d)) + + val e = Block(Seq(Block(Seq(stmtB)), stmtB)) + assert(hash(a) != hash(e)) + + val f = Block(Seq(Block(Seq(Block(Seq(stmtA, EmptyStmt, stmtB)))))) + assert(hash(a) == hash(f)) + } + + "Conditionally" should "properly separate if and else branch" in { + val stmtA = DefNode(NoInfo, "a", UIntLiteral(1)) + val stmtB = DefNode(NoInfo, "b", UIntLiteral(1)) + val cond = UIntLiteral(1) + + val a = Conditionally(NoInfo, cond, stmtA, stmtB) + val b = Conditionally(NoInfo, cond, Block(Seq(stmtA)), stmtB) + assert(hash(a) == hash(b)) + + val c = Conditionally(NoInfo, cond, Block(Seq(stmtA)), Block(Seq(EmptyStmt, stmtB))) + assert(hash(a) == hash(c)) + + val d = Block(Seq(Conditionally(NoInfo, cond, stmtA, EmptyStmt), stmtB)) + assert(hash(a) != hash(d)) + + val e = Conditionally(NoInfo, cond, stmtA, EmptyStmt) + val f = Conditionally(NoInfo, cond, EmptyStmt, stmtA) + assert(hash(e) != hash(f)) + } +} + +private case object DebugHasher extends Hasher { + override def update(b: Byte): Unit = println(s"b(${b.toInt & 0xff})") + override def update(i: Int): Unit = println(s"i(${i})") + override def update(l: Long): Unit = println(s"l(${l})") + override def update(s: String): Unit = println(s"s(${s})") + override def update(b: Array[Byte]): Unit = println(s"bytes(${b.map(x => x.toInt & 0xff).mkString(", ")})") +}
\ No newline at end of file |
