aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/firrtl/transforms/BlackBoxSourceHelper.scala
blob: 15878dc205829f8d1a639e896c56bf20f77f9c7d (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
// See LICENSE for license details.

package firrtl.transforms

import java.io.{File, FileNotFoundException, FileOutputStream, PrintWriter}

import firrtl._
import firrtl.annotations.{Annotation, ModuleName}

import scala.collection.mutable.ArrayBuffer


trait BlackBoxSource {
  def serialize: String
  def name: String
}

object BlackBoxSource {
  val MaxFields = 3

  def parse(s: String): Option[BlackBoxSource] = {
    s.split("\n", MaxFields).toList  match {
      case "resource" :: id ::  _ => Some(BlackBoxResource(id))
      case "inline" :: name :: text :: _ => Some(BlackBoxInline(name, text))
      case "targetDir" :: targetDir :: _ => Some(BlackBoxTargetDir(targetDir))
      case _ => throw new FIRRTLException(s"Error: Bad BlackBox annotations $s")
    }
  }
}

case class BlackBoxTargetDir(targetDir: String) extends BlackBoxSource {
  def serialize: String = s"targetDir\n$targetDir"
  def name: String = targetDir
}

case class BlackBoxResource(resourceId: String) extends BlackBoxSource {
  def serialize: String = s"resource\n$resourceId"
  def name: String = resourceId.split("/").last
}

case class BlackBoxInline(name: String, text: String) extends BlackBoxSource {
  def serialize: String = s"inline\n$name\n$text"
}

object BlackBoxSourceAnnotation {
  def apply(targetDir: ModuleName, value: String): Annotation = {
    assert(BlackBoxSource.parse(value).isDefined)
    Annotation(targetDir, classOf[DedupModules], value)
  }

  def unapply(a: Annotation): Option[(ModuleName, BlackBoxSource)] = a match {
    case Annotation(ModuleName(n, c), _, text) => Some((ModuleName(n, c), BlackBoxSource.parse(text).get))
    case _ => None
  }
}

/**
  * This transform handles the moving of verilator source for black boxes into the
  * target directory so that it can be accessed by verilator or other backend compilers
  * While parsing it's annotations it looks for a BlackBoxTargetDir annotation that
  * will set the directory where the verilog will be written.  This annotation is typically be
  * set by the execution harness, or directly in the tests
  */
class BlackBoxSourceHelper extends firrtl.Transform {
  private var targetDir: File = new File(".")
  private val fileList = new ArrayBuffer[String]

  override def inputForm: CircuitForm = LowForm
  override def outputForm: CircuitForm = LowForm

  /**
    * parse the annotations and convert the generic annotations to specific information
    * required to find the verilog
    * @note Side effect is that while converting a magic target dir annotation is found and sets the target
    * @param annos a list of generic annotations for this transform
    * @return
    */
  def getSources(annos: Seq[Annotation]): Seq[BlackBoxSource] = {
    annos.flatMap { anno => BlackBoxSource.parse(anno.value) }
      .flatMap {
        case BlackBoxTargetDir(dest) =>
          targetDir = new File(dest)
          if(! targetDir.exists()) { FileUtils.makeDirectory(targetDir.getAbsolutePath) }
          None
        case b: BlackBoxSource => Some(b)
        case _ => None
      }
      .sortBy(a => a.name)
      .distinct
  }

  /**
    * write the verilog source for each annotation to the target directory
    * @note the state is not changed by this transform
    * @param state Input Firrtl AST
    * @return A transformed Firrtl AST
    */
  override def execute(state: CircuitState): CircuitState = {
    val resultState = getMyAnnotations(state) match {
      case Nil => state
      case myAnnotations =>
        val sources = getSources(myAnnotations)
        sources.foreach {
          case BlackBoxResource(resourceId) =>
            val name = resourceId.split("/").last
            val outFile = new File(targetDir, name)
            BlackBoxSourceHelper.copyResourceToFile(resourceId,outFile)
            fileList += outFile.getAbsolutePath
          case BlackBoxInline(name, text) =>
            val outFile = new File(targetDir, name)
            val writer = new PrintWriter(outFile)
            writer.write(text)
            writer.close()
            fileList += outFile.getAbsolutePath
          case _ =>
        }
        state
    }
    if(fileList.nonEmpty) {
      val writer = new PrintWriter(new File(targetDir, BlackBoxSourceHelper.FileListName))
      writer.write(fileList.map { fileName => s"-v $fileName" }.mkString("\n"))
      writer.close()
    }

    resultState
  }
}

object BlackBoxSourceHelper {
  val FileListName = "black_box_verilog_files.f"
  /**
    * finds the named resource and writes into the directory
    * @param name the name of the resource
    * @param file the file to write it into
    */
  def copyResourceToFile(name: String, file: File) {
    val in = getClass.getResourceAsStream(name)
    if (in == null) {
      throw new FileNotFoundException(s"Resource '$name'")
    }
    val out = new FileOutputStream(file)
    Iterator.continually(in.read).takeWhile(-1 != _).foreach(out.write)
    out.close()
  }

}