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
|
// See LICENSE for license details.
package firrtl.util
import java.io._
import java.nio.file.Files
import java.text.SimpleDateFormat
import java.util.Calendar
import firrtl.FirrtlExecutionOptions
import scala.sys.process.{ProcessBuilder, ProcessLogger, _}
trait BackendCompilationUtilities {
/** Parent directory for tests */
lazy val TestDirectory = new File("test_run_dir")
def timeStamp: String = {
val format = new SimpleDateFormat("yyyyMMddHHmmss")
val now = Calendar.getInstance.getTime
format.format(now)
}
/**
* Copy the contents of a resource to a destination file.
* @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()
}
/** Create a test directory
*
* Will create outer directory called testName then inner directory based on
* the current time
*/
def createTestDirectory(testName: String): File = {
val outer = new File(TestDirectory, testName)
outer.mkdirs()
Files.createTempDirectory(outer.toPath, timeStamp).toFile
}
def makeHarness(template: String => String, post: String)(f: File): File = {
val prefix = f.toString.split("/").last
val vf = new File(f.toString + post)
val w = new FileWriter(vf)
w.write(template(prefix))
w.close()
vf
}
/**
* compule chirrtl to verilog by using a separate process
*
* @param prefix basename of the file
* @param dir directory where file lives
* @return true if compiler completed successfully
*/
def firrtlToVerilog(prefix: String, dir: File): ProcessBuilder = {
Process(
Seq("firrtl",
"-i", s"$prefix.fir",
"-o", s"$prefix.v",
"-X", "verilog"),
dir)
}
/** Generates a Verilator invocation to convert Verilog sources to C++
* simulation sources.
*
* The Verilator prefix will be V\$dutFile, and running this will generate
* C++ sources and headers as well as a makefile to compile them.
*
* Verilator will automatically locate the top-level module as the one among
* all the files which are not included elsewhere. If multiple ones exist,
* the compilation will fail.
*
* If the file BlackBoxSourceHelper.fileListName exists in the output directory,
* it contains a list of source files to be included. Filter out any files in the vSources
* sequence that are in this file so we don't include the same file multiple times.
* This complication is an attempt to work-around the fact that clients used to have to
* explicitly include additional Verilog sources. Now, more of that is automatic.
*
* @param dutFile name of the DUT .v without the .v extension
* @param dir output directory
* @param vSources list of additional Verilog sources to compile
* @param cppHarness C++ testharness to compile/link against
*/
def verilogToCpp(
dutFile: String,
dir: File,
vSources: Seq[File],
cppHarness: File,
suppressVcd: Boolean = false
): ProcessBuilder = {
val topModule = dutFile
val list_file = new File(dir, firrtl.transforms.BlackBoxSourceHelper.fileListName)
val blackBoxVerilogList = {
if(list_file.exists()) {
Seq("-f", list_file.getAbsolutePath)
}
else {
Seq.empty[String]
}
}
// Don't include the same file multiple times.
// If it's in BlackBoxSourceHelper.fileListName, don't explicitly include it on the command line.
// Build a set of canonical file paths to use as a filter to exclude already included additional Verilog sources.
val blackBoxHelperFiles: Set[String] = {
if(list_file.exists()) {
io.Source.fromFile(list_file).getLines.toSet
}
else {
Set.empty
}
}
val vSourcesFiltered = vSources.filterNot(f => blackBoxHelperFiles.contains(f.getCanonicalPath))
val command = Seq(
"verilator",
"--cc", s"${dir.getAbsolutePath}/$dutFile.v"
) ++
blackBoxVerilogList ++
vSourcesFiltered.flatMap(file => Seq("-v", file.getCanonicalPath)) ++
Seq("--assert",
"-Wno-fatal",
"-Wno-WIDTH",
"-Wno-STMTDLY"
) ++
{ if(suppressVcd) { Seq.empty } else { Seq("--trace")} } ++
Seq(
"-O1",
"--top-module", topModule,
"+define+TOP_TYPE=V" + dutFile,
s"+define+PRINTF_COND=!$topModule.reset",
s"+define+STOP_COND=!$topModule.reset",
"-CFLAGS",
s"""-Wno-undefined-bool-conversion -O1 -DTOP_TYPE=V$dutFile -DVL_USER_FINISH -include V$dutFile.h""",
"-Mdir", dir.getAbsolutePath,
"--exe", cppHarness.getAbsolutePath)
System.out.println(s"${command.mkString(" ")}") // scalastyle:ignore regex
command
}
def cppToExe(prefix: String, dir: File): ProcessBuilder =
Seq("make", "-C", dir.toString, "-j", "-f", s"V$prefix.mk", s"V$prefix")
def executeExpectingFailure(
prefix: String,
dir: File,
assertionMsg: String = ""): Boolean = {
var triggered = false
val assertionMessageSupplied = assertionMsg != ""
val e = Process(s"./V$prefix", dir) !
ProcessLogger(line => {
triggered = triggered || (assertionMessageSupplied && line.contains(assertionMsg))
System.out.println(line) // scalastyle:ignore regex
})
// Fail if a line contained an assertion or if we get a non-zero exit code
// or, we get a SIGABRT (assertion failure) and we didn't provide a specific assertion message
triggered || (e != 0 && (e != 134 || !assertionMessageSupplied))
}
def executeExpectingSuccess(prefix: String, dir: File): Boolean = {
!executeExpectingFailure(prefix, dir)
}
/** Creates and runs a Yosys script that creates and runs SAT on a miter
* circuit. Returns true if SAT succeeds, false otherwise
*
* The custom and reference Verilog files must not contain any modules with
* the same name otherwise Yosys will not be able to create a miter circuit
*
* @param customTop name of the DUT with custom transforms without the .v
* extension
* @param referenceTop name of the DUT without custom transforms without the
* .v extension
* @param testDir directory containing verilog files
* @param resets signals to set for SAT, format is
* (timestep, signal, value)
*/
def yosysExpectSuccess(customTop: String,
referenceTop: String,
testDir: File,
resets: Seq[(Int, String, Int)] = Seq.empty): Boolean = {
!yosysExpectFailure(customTop, referenceTop, testDir, resets)
}
/** Creates and runs a Yosys script that creates and runs SAT on a miter
* circuit. Returns false if SAT succeeds, true otherwise
*
* The custom and reference Verilog files must not contain any modules with
* the same name otherwise Yosys will not be able to create a miter circuit
*
* @param customTop name of the DUT with custom transforms without the .v
* extension
* @param referenceTop name of the DUT without custom transforms without the
* .v extension
* @param testDir directory containing verilog files
* @param resets signals to set for SAT, format is
* (timestep, signal, value)
*/
def yosysExpectFailure(customTop: String,
referenceTop: String,
testDir: File,
resets: Seq[(Int, String, Int)] = Seq.empty): Boolean = {
val setSignals = resets.map(_._2).toSet[String].map(s => s"-set in_$s 0").mkString(" ")
val setAtSignals = resets.map {
case (timestep, signal, value) => s"-set-at $timestep in_$signal $value"
}.mkString(" ")
val scriptFileName = s"${testDir.getAbsolutePath}/yosys_script"
val yosysScriptWriter = new PrintWriter(scriptFileName)
yosysScriptWriter.write(
s"""read_verilog ${testDir.getAbsolutePath}/$customTop.v
|read_verilog ${testDir.getAbsolutePath}/$referenceTop.v
|prep; proc; opt; memory
|miter -equiv -flatten $customTop $referenceTop miter
|hierarchy -top miter
|sat -verify -tempinduct -prove trigger 0 $setSignals $setAtSignals -seq 1 miter"""
.stripMargin)
yosysScriptWriter.close()
val resultFileName = testDir.getAbsolutePath + "/yosys_results"
val command = s"yosys -s $scriptFileName" #> new File(resultFileName)
command.! != 0
}
}
|