1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
|
// SPDX-License-Identifier: Apache-2.0
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 "generate the same hash String if the objects are structurally the same" in {
assert(hash(b0).toHashString == hash(UIntLiteral(0, IntWidth(1))).toHashString)
assert(hash(b0).toHashString != hash(UIntLiteral(1, IntWidth(1))).toHashString)
assert(hash(b0).toHashString != hash(UIntLiteral(1, IntWidth(2))).toHashString)
assert(hash(b1).toHashString == hash(UIntLiteral(1, IntWidth(1))).toHashString)
assert(hash(b1).toHashString != hash(UIntLiteral(0, IntWidth(1))).toHashString)
assert(hash(b1).toHashString != hash(UIntLiteral(1, IntWidth(2))).toHashString)
}
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(", ")})")
}
|