aboutsummaryrefslogtreecommitdiff
path: root/src/test/scala/firrtlTests/InfoSpec.scala
blob: 99000d3734090a4acc69bb86944f027b2cbe6f4a (plain)
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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
// SPDX-License-Identifier: Apache-2.0

package firrtlTests

import firrtl._
import firrtl.ir._
import firrtl.testutils._
import FirrtlCheckers._
import firrtl.Parser.AppendInfo

class InfoSpec extends FirrtlFlatSpec with FirrtlMatchers {
  def compile(input: String): CircuitState =
    (new VerilogCompiler).compileAndEmit(CircuitState(parse(input), ChirrtlForm), List.empty)
  def compileBody(body: String) = {
    val str = """
                |circuit Test :
                |  module Test :
                |""".stripMargin + body.split("\n").mkString("    ", "\n    ", "")
    compile(str)
  }

  // Some useful constants to use and look for
  val Info1 = FileInfo(StringLit("Source.scala 1:4"))
  val Info2 = FileInfo(StringLit("Source.scala 2:4"))
  val Info3 = FileInfo(StringLit("Source.scala 3:4"))
  val Info4 = FileInfo(StringLit("Source.scala 4:4"))

  "Source locators on module ports" should "be propagated to Verilog" in {
    val result = compileBody(s"""
                                |input x : UInt<8> $Info1
                                |output y : UInt<8> $Info2
                                |y <= x""".stripMargin)
    result should containTree { case Port(Info1, "x", Input, _) => true }
    result should containLine(s"input [7:0] x, //$Info1")
    result should containTree { case Port(Info2, "y", Output, _) => true }
    result should containLine(s"output [7:0] y //$Info2")
  }

  "Source locators on aggregates" should "be propagated to Verilog" in {
    val result = compileBody(s"""
                                |input io : { x : UInt<8>, flip y : UInt<8> } $Info1
                                |io.y <= io.x""".stripMargin)
    result should containTree { case Port(Info1, "io_x", Input, _) => true }
    result should containLine(s"input [7:0] io_x, //$Info1")
    result should containTree { case Port(Info1, "io_y", Output, _) => true }
    result should containLine(s"output [7:0] io_y //$Info1")
  }

  "Source locators" should "be propagated on declarations" in {
    val result = compileBody(s"""
                                |input clock : Clock
                                |input x : UInt<8>
                                |output y : UInt<8>
                                |reg r : UInt<8>, clock $Info1
                                |wire w : UInt<8> $Info2
                                |node n = or(w, x) $Info3
                                |w <= and(x, r)
                                |r <= or(n, r)
                                |y <= r""".stripMargin)
    result should containTree { case DefRegister(Info1, "r", _, _, _, _) => true }
    result should containLine(s"reg [7:0] r; //$Info1")
    result should containTree { case DefNode(Info2, "w", _) => true }
    result should containLine(s"wire [7:0] w = x & r; //$Info2") // Node "w" declaration in Verilog
    result should containTree { case DefNode(Info3, "n", _) => true }
    result should containLine(s"wire [7:0] n = w | x; //$Info3")
  }

  it should "be propagated on memories" in {
    val result = compileBody(s"""
                                |input clock : Clock
                                |input addr : UInt<5>
                                |output z : UInt<8>
                                |mem m: $Info1
                                |  data-type => UInt<8>
                                |  depth => 32
                                |  read-latency => 0
                                |  write-latency => 1
                                |  reader => r
                                |  writer => w
                                |m.r.clk <= clock
                                |m.r.addr <= addr
                                |m.r.en <= UInt(1)
                                |m.w.clk <= clock
                                |m.w.addr <= addr
                                |m.w.en <= UInt(0)
                                |m.w.data <= UInt(0)
                                |m.w.mask <= UInt(0)
                                |z <= m.r.data
                                |""".stripMargin)

    result should containTree { case DefMemory(Info1, "m", _, _, _, _, _, _, _, _) => true }
    result should containLine(s"reg [7:0] m [0:31]; //$Info1")
    result should containLine(s"wire [7:0] m_r_data; //$Info1")
    result should containLine(s"wire [4:0] m_r_addr; //$Info1")
    result should containLine(s"wire [7:0] m_w_data; //$Info1")
    result should containLine(s"wire [4:0] m_w_addr; //$Info1")
    result should containLine(s"wire  m_w_mask; //$Info1")
    result should containLine(s"wire  m_w_en; //$Info1")
    result should containLine(s"assign m_r_data = m[m_r_addr]; //$Info1")
    result should containLine(s"m[m_w_addr] <= m_w_data; //$Info1")
  }

  it should "be propagated on instances" in {
    val result = compile(s"""
                            |circuit Test :
                            |  module Child :
                            |    output io : { flip in : UInt<8>, out : UInt<8> }
                            |    io.out <= io.in
                            |  module Test :
                            |    output io : { flip in : UInt<8>, out : UInt<8> }
                            |    inst c of Child $Info1
                            |    io <= c.io
                            |""".stripMargin)
    result should containTree { case WDefInstance(Info1, "c", "Child", _) => true }
    result should containLine(s"Child c ( //$Info1")
  }

  it should "be propagated across direct node assignments and connections" in {
    val result = compile(s"""
                            |circuit Test :
                            |  module Test :
                            |    input in : UInt<8>
                            |    output out : UInt<8>
                            |    node a = in $Info1
                            |    node b = a
                            |    out <= b
                            |""".stripMargin)
    result should containTree { case Connect(Info1, Reference("out", _, _, _), Reference("in", _, _, _)) => true }
    result should containLine(s"assign out = in; //$Info1")
  }

  "source locators" should "be propagated through ExpandWhens" in {
    val input =
      """
        |;buildInfoPackage: chisel3, version: 3.1-SNAPSHOT, scalaVersion: 2.11.7, sbtVersion: 0.13.11, builtAtString: 2016-11-26 18:48:38.030, builtAtMillis: 1480186118030
        |circuit GCD :
        |  module GCD :
        |    input clock : Clock
        |    input reset : UInt<1>
        |    output io : {flip a : UInt<32>, flip b : UInt<32>, flip e : UInt<1>, z : UInt<32>, v : UInt<1>}
        |
        |    io is invalid
        |    io is invalid
        |    reg x : UInt<32>, clock @[GCD.scala 15:14]
        |    reg y : UInt<32>, clock @[GCD.scala 16:14]
        |    node _T_14 = gt(x, y) @[GCD.scala 17:11]
        |    when _T_14 : @[GCD.scala 17:18]
        |      node _T_15 = sub(x, y) @[GCD.scala 17:27]
        |      node _T_16 = tail(_T_15, 1) @[GCD.scala 17:27]
        |      x <= _T_16 @[GCD.scala 17:22]
        |      skip @[GCD.scala 17:18]
        |    node _T_18 = eq(_T_14, UInt<1>("h00")) @[GCD.scala 17:18]
        |    when _T_18 : @[GCD.scala 18:18]
        |      node _T_19 = sub(y, x) @[GCD.scala 18:27]
        |      node _T_20 = tail(_T_19, 1) @[GCD.scala 18:27]
        |      y <= _T_20 @[GCD.scala 18:22]
        |      skip @[GCD.scala 18:18]
        |    when io.e : @[GCD.scala 19:15]
        |      x <= io.a @[GCD.scala 19:19]
        |      y <= io.b @[GCD.scala 19:30]
        |      skip @[GCD.scala 19:15]
        |    io.z <= x @[GCD.scala 20:8]
        |    node _T_22 = eq(y, UInt<1>("h00")) @[GCD.scala 21:13]
        |    io.v <= _T_22 @[GCD.scala 21:8]
        |
      """.stripMargin

    val result = (new LowFirrtlCompiler).compileAndEmit(CircuitState(parse(input), ChirrtlForm), List.empty)
    result should containLine("node _GEN_0 = mux(_T_14, _T_16, x) @[GCD.scala 15:14 17:{18,22}]")
    result should containLine("node _GEN_2 = mux(io_e, io_a, _GEN_0) @[GCD.scala 19:{15,19}]")
    result should containLine("x <= _GEN_2")
    result should containLine("node _GEN_1 = mux(_T_18, _T_20, y) @[GCD.scala 16:14 18:{18,22}]")
    result should containLine("node _GEN_3 = mux(io_e, io_b, _GEN_1) @[GCD.scala 19:{15,30}]")
    result should containLine("y <= _GEN_3")
  }

  "source locators for append option" should "use multiinfo" in {
    val input = """circuit Top :
                  |  module Top :
                  |    input clock : Clock
                  |    input in: UInt<32>
                  |    output out: UInt<32>
                  |    out <= in @[Top.scala 15:14]
                  |""".stripMargin
    val circuit = firrtl.Parser.parse(input.split("\n").toIterator, AppendInfo("myfile.fir"))
    val circuitState = CircuitState(circuit, UnknownForm)
    val expectedInfos = Seq(FileInfo(StringLit("Top.scala 15:14")), FileInfo(StringLit("myfile.fir 6:4")))
    circuitState should containTree { case MultiInfo(`expectedInfos`) => true }
  }

  "source locators for basic register updates" should "be propagated to Verilog" in {
    val result = compileBody(s"""
                                |input clock : Clock
                                |input reset : UInt<1>
                                |output io : { flip in : UInt<8>, out : UInt<8>}
                                |reg r : UInt<8>, clock
                                |r <= io.in $Info1
                                |io.out <= r
                                |""".stripMargin)
    result should containLine(s"r <= io_in; //$Info1")
  }

  "source locators for register reset" should "be propagated to Verilog" in {
    val result = compileBody(s"""
                                |input clock : Clock
                                |input reset : UInt<1>
                                |output io : { flip in : UInt<8>, out : UInt<8>}
                                |reg r : UInt<8>, clock with : (reset => (reset, UInt<8>("h0"))) $Info3
                                |r <= io.in $Info1
                                |io.out <= r
                                |""".stripMargin)
    result should containLine(s"if (reset) begin //$Info3")
    result should containLine(s"r <= 8'h0; //$Info3")
    result should containLine(s"r <= io_in; //$Info1")
  }

  "source locators for complex register updates" should "be propagated to Verilog" in {
    val result = compileBody(s"""
                                |input clock : Clock
                                |input reset : UInt<1>
                                |output io : { flip in : UInt<8>, flip a : UInt<1>, out : UInt<8>}
                                |reg r : UInt<8>, clock with : (reset => (reset, UInt<8>("h0"))) $Info1
                                |r <= UInt<2>(2) $Info2
                                |when io.a : $Info3
                                |  r <= io.in $Info4
                                |io.out <= r
                                |""".stripMargin)
    result should containLine(s"if (reset) begin //$Info1")
    result should containLine(s"r <= 8'h0; //$Info1")
    result should containLine(s"end else if (io_a) begin //$Info3")
    result should containLine(s"r <= io_in; //$Info4")
    result should containLine(s"r <= 8'h2; //$Info2")
  }

  "FileInfo" should "be able to contain a escaped characters" in {
    def input(info: String): String =
      s"""circuit m: @[$info]
         |  module m:
         |    skip
         |""".stripMargin
    def parseInfo(info: String): FileInfo = {
      firrtl.Parser.parse(input(info)).info.asInstanceOf[FileInfo]
    }

    parseInfo("test\\ntest").escaped should be("test\\ntest")
    parseInfo("test\\ntest").unescaped should be("test\ntest")
    parseInfo("test\\ttest").escaped should be("test\\ttest")
    parseInfo("test\\ttest").unescaped should be("test\ttest")
    parseInfo("test\\\\test").escaped should be("test\\\\test")
    parseInfo("test\\\\test").unescaped should be("test\\test")
    parseInfo("test\\]test").escaped should be("test\\]test")
    parseInfo("test\\]test").unescaped should be("test]test")
    parseInfo("test[\\][\\]test").escaped should be("test[\\][\\]test")
    parseInfo("test[\\][\\]test").unescaped should be("test[][]test")
  }

  it should "be compressed in Verilog whenever possible" in {
    def result(info1: String, info2: String, info3: String) = compileBody(
      s"""output out:UInt<32>
         |input b:UInt<32>
         |input c:UInt<1>
         |input d:UInt<32>
         |wire a:UInt<32>
         |when c : @[$info1]
         |  a <= b @[$info2]
         |else :
         |  a <= d @[$info3]
         |out <= add(a,a)""".stripMargin
    )

    // Keep different file infos separated
    result("A 1:1", "B 1:1", "C 1:1") should containLine("  wire [31:0] a = c ? b : d; // @[A 1:1 B 1:1 C 1:1]")
    // Compress only 2 FileInfos of the same file
    result("A 1:1", "A 2:3", "C 1:1") should containLine("  wire [31:0] a = c ? b : d; // @[A 1:1 2:3 C 1:1]")
    // Conmpress 3 lines from the same file into one single FileInfo
    result("A 1:2", "A 2:4", "A 3:6") should containLine("  wire [31:0] a = c ? b : d; // @[A 1:2 2:4 3:6]")
    // Compress two columns from the same line, and one different line into one FileInfo
    result("A 1:2", "A 1:4", "A 2:3") should containLine("  wire [31:0] a = c ? b : d; // @[A 1:{2,4} 2:3]")
    // Compress three (or more...) columns from the same line into one FileInfo
    result("A 1:2", "A 1:3", "A 1:4") should containLine("  wire [31:0] a = c ? b : d; // @[A 1:{2,3,4}]")

    // Ignore already-compressed MultiInfos - for when someone may serialize a module first and compile the parsed firrtl into Verilog
    result("A 1:{2,3,4}", "", "") should containLine(
      "  wire [31:0] a = c ? b : d; // @[A 1:{2,3,4}]"
    )
    // Merge additional FileInfos together, but ignore compressed MultiInfos if they are present
    result("A 1:{2,3,4}", "B 2:3", "B 4:5") should containLine(
      "  wire [31:0] a = c ? b : d; // @[A 1:{2,3,4} B 2:3 4:5]"
    )
    result("A 2:3", "B 1:{2,3,4}", "C 4:5") should containLine(
      "  wire [31:0] a = c ? b : d; // @[A 2:3 B 1:{2,3,4} C 4:5]"
    )
  }

  it should "not be compressed if it has a non-conforming format" in {
    // Sample module from the firrtl spec for file info comments
    val result = compileBody(
      """output out:UInt @["myfile.txt: 16, 3"]
        |input b:UInt<32> @["myfile.txt: 17, 3"]
        |input c:UInt<1> @["myfile.txt: 18, 3"]
        |input d:UInt<16> @["myfile.txt: 19, 3"]
        |wire a:UInt @["myfile.txt: 21, 8"]
        |when c : @["myfile.txt: 24, 8"]
        |  a <= b @["myfile.txt: 27, 16"]
        |else :
        |  a <= d @["myfile.txt: 29, 17"]
        |out <= add(a,a) @["myfile.txt: 34, 4"]
        |""".stripMargin
    )

    // Should compile to the following lines in the test module
    val check = Seq(
      """  output [32:0] out, // @[\"myfile.txt: 16, 3\"]""",
      """  input  [31:0] b, // @[\"myfile.txt: 17, 3\"]""",
      """  input         c, // @[\"myfile.txt: 18, 3\"]""",
      """  input  [15:0] d // @[\"myfile.txt: 19, 3\"]""",
      """  wire [31:0] a = c ? b : {{16'd0}, d}; // @[\"myfile.txt: 24, 8\" \"myfile.txt: 27, 16\" \"myfile.txt: 29, 17\"]""",
      """  assign out = a + a; // @[\"myfile.txt: 34, 4\"]"""
    )

    for (line <- check)
      result should containLine(line)
  }

  "Source Locators" should "not lose information when going through serialization + parsing" in {
    def check(info: ir.Info): Unit = {
      assert(Parser.parseInfo(info.serialize) == info)
    }

    check(ir.NoInfo)
    check(ir.FileInfo("B"))
    check(ir.FileInfo("A 4:5"))
    check(ir.FileInfo("A 4:6"))
    check(ir.MultiInfo(ir.FileInfo("A 4:5"), ir.FileInfo("B 5:5")))
    check(ir.MultiInfo(ir.FileInfo("A 4:5"), ir.FileInfo("A 5:5")))
  }
}