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
|
// See LICENSE for license details.
package firrtl.transforms
import java.io.{File, FileNotFoundException, FileInputStream, FileOutputStream, PrintWriter}
import firrtl._
import firrtl.Utils.throwInternalError
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"
}
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"
}
/** 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 firrtl.Transform {
private val DefaultTargetDir = new File(".")
override def inputForm: CircuitForm = LowForm
override def outputForm: CircuitForm = LowForm
/** 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) =
annos.foldLeft((ListSet.empty[BlackBoxHelperAnno], DefaultTargetDir)) {
case ((acc, tdir), anno) => anno match {
case BlackBoxTargetDirAnno(dir) =>
val targetDir = new File(dir)
if (!targetDir.exists()) { FileUtils.makeDirectory(targetDir.getAbsolutePath) }
(acc, targetDir)
case a: BlackBoxHelperAnno => (acc + a, tdir)
case _ => (acc, tdir)
}
}
/**
* 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 (annos, targetDir) = collectAnnos(state.annotations)
val resourceFiles: ListSet[File] = annos.collect {
case BlackBoxResourceAnno(_, resourceId) =>
BlackBoxSourceHelper.writeResourceToDirectory(resourceId, targetDir)
case BlackBoxPathAnno(_, path) =>
val fileName = path.split("/").last
val fromFile = new File(path)
val toFile = new File(targetDir, fileName)
val inputStream = 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) =>
BlackBoxSourceHelper.writeTextToFile(text, file)
file
}
BlackBoxSourceHelper.writeFileList(resourceFiles ++ inlineFiles, targetDir)
state
}
}
object BlackBoxSourceHelper {
/**
* 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
*/
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()
}
val fileListName = "firrtl_black_box_resource_files.f"
def writeFileList(files: ListSet[File], targetDir: File) {
if (files.nonEmpty) {
// 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.
writeTextToFile(files.map(_.getCanonicalPath).mkString("\n"), new File(targetDir, fileListName))
}
}
def writeTextToFile(text: String, file: File) {
val out = new PrintWriter(file)
out.write(text)
out.close()
}
}
|