summaryrefslogtreecommitdiff
path: root/src/main/scala/chisel3/testers/TesterDriver.scala
blob: 28ce74454d6882e32f48b6a9d200ec61bda45e2e (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
// SPDX-License-Identifier: Apache-2.0

package chisel3.testers

import java.io._

import chisel3._
import chisel3.stage.phases.{Convert, Elaborate, Emitter}
import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation, ChiselStage, NoRunFirrtlCompilerAnnotation}
import firrtl.AnnotationSeq
import firrtl.annotations.NoTargetAnnotation
import firrtl.options.{Dependency, Phase, PhaseManager, TargetDirAnnotation, Unserializable}
import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlStage}
import firrtl.transforms.BlackBoxSourceHelper.writeResourceToDirectory
import firrtl.{annoSeqToSeq, seqToAnnoSeq}

object TesterDriver extends BackendCompilationUtilities {

  private[chisel3] trait Backend extends NoTargetAnnotation with Unserializable {
    def execute(
      t:                    () => BasicTester,
      additionalVResources: Seq[String] = Seq(),
      annotations:          AnnotationSeq = Seq(),
      nameHint:             Option[String] = None
    ): Boolean
  }
  case object VerilatorBackend extends Backend {

    /** For use with modules that should successfully be elaborated by the
      * frontend, and which can be turned into executables with assertions.
      */
    def execute(
      t:                    () => BasicTester,
      additionalVResources: Seq[String] = Seq(),
      annotations:          AnnotationSeq = Seq(),
      nameHint:             Option[String] = None
    ): Boolean = {
      val pm = new PhaseManager(
        targets = Seq(Dependency[AddImplicitTesterDirectory], Dependency[Emitter], Dependency[Convert])
      )

      val annotationsx = pm.transform(ChiselGeneratorAnnotation(finishWrapper(t)) +: annotations)

      val target: String = annotationsx.collectFirst { case FirrtlCircuitAnnotation(cir) => cir.main }.get
      val path = annotationsx.collectFirst { case TargetDirAnnotation(dir) => dir }.map(new File(_)).get

      // Copy CPP harness and other Verilog sources from resources into files
      val cppHarness = new File(path, "top.cpp")
      copyResourceToFile("/chisel3/top.cpp", cppHarness)
      // NOTE: firrtl.Driver.execute() may end up copying these same resources in its BlackBoxSourceHelper code.
      // As long as the same names are used for the output files, and we avoid including duplicate files
      //  in BackendCompilationUtilities.verilogToCpp(), we should be okay.
      // To that end, we use the same method to write the resource to the target directory.
      val additionalVFiles = additionalVResources.map((name: String) => {
        writeResourceToDirectory(name, path)
      })

      (new FirrtlStage).execute(Array("--compiler", "verilog"), annotationsx)

      // Use sys.Process to invoke a bunch of backend stuff, then run the resulting exe
      if (
        (verilogToCpp(target, path, additionalVFiles, cppHarness) #&&
          cppToExe(target, path)).! == 0
      ) {
        executeExpectingSuccess(target, path)
      } else {
        false
      }
    }
  }

  val defaultBackend: Backend = VerilatorBackend

  /** Use this to force a test to be run only with backends that are restricted to verilator backend
    */
  def verilatorOnly: AnnotationSeq = Seq(VerilatorBackend)

  /** Set the target directory to the name of the top module after elaboration */
  final class AddImplicitTesterDirectory extends Phase {
    override def prerequisites = Seq(Dependency[Elaborate])
    override def optionalPrerequisites = Seq.empty
    override def optionalPrerequisiteOf = Seq(Dependency[Emitter])
    override def invalidates(a: Phase) = false

    override def transform(a: AnnotationSeq) = a.flatMap {
      case a @ ChiselCircuitAnnotation(circuit) =>
        Seq(
          a,
          TargetDirAnnotation(
            firrtl.util.BackendCompilationUtilities.createTestDirectory(circuit.name).getAbsolutePath.toString
          )
        )
      case a => Seq(a)
    }
  }

  /** For use with modules that should successfully be elaborated by the
    * frontend, and which can be turned into executables with assertions.
    */
  def execute(
    t:                    () => BasicTester,
    additionalVResources: Seq[String] = Seq(),
    annotations:          AnnotationSeq = Seq(),
    nameHint:             Option[String] = None
  ): Boolean = {

    val backendAnnotations = annotations.collect { case anno: Backend => anno }
    val backendAnnotation = if (backendAnnotations.length == 1) {
      backendAnnotations.head
    } else if (backendAnnotations.isEmpty) {
      defaultBackend
    } else {
      throw new ChiselException(s"Only one backend annotation allowed, found: ${backendAnnotations.mkString(", ")}")
    }
    backendAnnotation.execute(t, additionalVResources, annotations, nameHint)
  }

  /**
    * Calls the finish method of an BasicTester or a class that extends it.
    * The finish method is a hook for code that augments the circuit built in the constructor.
    */
  def finishWrapper(test: () => BasicTester): () => BasicTester = { () =>
    {
      val tester = test()
      tester.finish()
      tester
    }
  }

}