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
|
// SPDX-License-Identifier: Apache-2.0
package firrtl.transforms
import java.io.{File, FileInputStream, FileNotFoundException, FileOutputStream, PrintWriter}
import firrtl._
import firrtl.annotations._
import scala.collection.immutable.ListSet
sealed trait BlackBoxHelperAnno extends Annotation
case class BlackBoxTargetDirAnno(targetDir: String) extends BlackBoxHelperAnno with NoTargetAnnotation {
override def serialize: String = s"targetDir\n$targetDir"
}
@deprecated("Use either a BlackBoxInlineAnno or a BlackBoxPathAnno", "FIRRTL 1.5.0")
case class BlackBoxResourceAnno(target: ModuleName, resourceId: String)
extends BlackBoxHelperAnno
with SingleTargetAnnotation[ModuleName] {
def duplicate(n: ModuleName) = this.copy(target = n)
override def serialize: String = s"resource\n$resourceId"
}
case class BlackBoxInlineAnno(target: ModuleName, name: String, text: String)
extends BlackBoxHelperAnno
with SingleTargetAnnotation[ModuleName] {
def duplicate(n: ModuleName) = this.copy(target = n)
override def serialize: String = s"inline\n$name\n$text"
}
case class BlackBoxPathAnno(target: ModuleName, path: String)
extends BlackBoxHelperAnno
with SingleTargetAnnotation[ModuleName] {
def duplicate(n: ModuleName) = this.copy(target = n)
override def serialize: String = s"path\n$path"
}
case class BlackBoxResourceFileNameAnno(resourceFileName: String) extends BlackBoxHelperAnno with NoTargetAnnotation {
override def serialize: String = s"resourceFileName\n$resourceFileName"
}
/** Exception indicating that a blackbox wasn't found
* @param fileName the name of the BlackBox file (only used for error message generation)
* @param e an underlying exception that generated this
*/
class BlackBoxNotFoundException(fileName: String, message: String)
extends FirrtlUserException(
s"BlackBox '$fileName' not found. Did you misspell it? Is it in src/{main,test}/resources?\n$message"
)
/** Handle source for Verilog ExtModules (BlackBoxes)
*
* This transform handles the moving of Verilog 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 Transform with DependencyAPIMigration {
import BlackBoxSourceHelper._
private val DefaultTargetDir = new File(".")
override def prerequisites = Seq.empty
override def optionalPrerequisites = Seq.empty
override def optionalPrerequisiteOf = Seq.empty
override def invalidates(a: Transform) = false
/** Collect BlackBoxHelperAnnos and and find the target dir if specified
* @param annos a list of generic annotations for this transform
* @return BlackBoxHelperAnnos and target directory
*/
def collectAnnos(annos: Seq[Annotation]): (ListSet[BlackBoxHelperAnno], File, File) =
annos.foldLeft((ListSet.empty[BlackBoxHelperAnno], DefaultTargetDir, new File(defaultFileListName))) {
case ((acc, tdir, flistName), anno) =>
anno match {
case BlackBoxTargetDirAnno(dir) =>
val targetDir = new File(dir)
if (!targetDir.exists()) { FileUtils.makeDirectory(targetDir.getAbsolutePath) }
(acc, targetDir, flistName)
case BlackBoxResourceFileNameAnno(fileName) => (acc, tdir, new File(fileName))
case a: BlackBoxHelperAnno => (acc + a, tdir, flistName)
case _ => (acc, tdir, flistName)
}
}
/**
* 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
* @throws BlackBoxNotFoundException if a Verilog source cannot be found
*/
override def execute(state: CircuitState): CircuitState = {
val (annos, targetDir, flistName) = collectAnnos(state.annotations)
val resourceFiles: ListSet[File] = annos.collect {
case BlackBoxResourceAnno(_, resourceId) =>
writeResourceToDirectory(resourceId, targetDir)
case BlackBoxPathAnno(_, path) =>
val fileName = path.split("/").last
val fromFile = new File(path)
val toFile = new File(targetDir, fileName)
val inputStream = safeFile(fromFile.toString)(new FileInputStream(fromFile).getChannel)
val outputStream = new FileOutputStream(toFile).getChannel
outputStream.transferFrom(inputStream, 0, Long.MaxValue)
toFile
}
val inlineFiles: ListSet[File] = annos.collect {
case BlackBoxInlineAnno(_, name, text) =>
val outFile = new File(targetDir, name)
(text, outFile)
}.map {
case (text, file) =>
writeTextToFile(text, file)
file
}
// Issue #917 - We don't want to list Verilog header files ("*.vh") in our file list - they will automatically be included by reference.
def isHeader(name: String) = name.endsWith(".h") || name.endsWith(".vh") || name.endsWith(".svh")
val verilogSourcesOnly = (resourceFiles ++ inlineFiles).filterNot { f => isHeader(f.getName()) }
val filelistFile = if (flistName.isAbsolute()) flistName else new File(targetDir, flistName.getName())
// We need the canonical path here, so verilator will create a path to the file that works from the targetDir,
// and, so we can compare the list of files automatically included, with an explicit list provided by the client
// and reject duplicates.
// If the path isn't canonical, when make tries to determine dependencies based on the *__ver.d file, we end up with errors like:
// make[1]: *** No rule to make target `test_run_dir/examples.AccumBlackBox_PeekPokeTest_Verilator345491158/AccumBlackBox.v', needed by `.../chisel-testers/test_run_dir/examples.AccumBlackBox_PeekPokeTest_Verilator345491158/VAccumBlackBoxWrapper.h'. Stop.
// or we end up including the same file multiple times.
if (verilogSourcesOnly.nonEmpty) {
writeTextToFile(verilogSourcesOnly.map(_.getCanonicalPath).mkString("\n") + "\n", filelistFile)
}
state
}
}
object BlackBoxSourceHelper {
/** Safely access a file converting [[FileNotFoundException]]s and [[NullPointerException]]s into
* [[BlackBoxNotFoundException]]s
* @param fileName the name of the file to be accessed (only used for error message generation)
* @param code some code to run
*/
private def safeFile[A](fileName: String)(code: => A) = try { code }
catch {
case e @ (_: FileNotFoundException | _: NullPointerException) =>
throw new BlackBoxNotFoundException(fileName, e.getMessage)
}
/**
* finds the named resource and writes into the directory
* @param name the name of the resource
* @param dir the directory in which to write the file
* @return the closed File object
*/
def writeResourceToDirectory(name: String, dir: File): File = {
val fileName = name.split("/").last
val outFile = new File(dir, fileName)
copyResourceToFile(name, outFile)
outFile
}
/**
* finds the named resource and writes into the directory
* @param name the name of the resource
* @param file the file to write it into
* @throws BlackBoxNotFoundException if the requested resource does not exist
*/
def copyResourceToFile(name: String, file: File): Unit = {
val in = getClass.getResourceAsStream(name)
val out = new FileOutputStream(file)
safeFile(name)(Iterator.continually(in.read).takeWhile(-1 != _).foreach(out.write))
out.close()
}
val defaultFileListName = "firrtl_black_box_resource_files.f"
def writeTextToFile(text: String, file: File): Unit = {
val out = new PrintWriter(file)
out.write(text)
out.close()
}
}
|