Skip to content
Permalink
1.15.1
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
191 lines (165 sloc) 7.28 KB
//See LICENSE for license details.
package firesim
import java.io.File
import scala.io.Source
import scala.sys.process.{stringSeqToProcess, ProcessLogger}
/**
* An base class for implementing FireSim integration tests that call out to the Make
* buildsystem. These tests typically have three steps whose results are tracked by scalatest:
* 1) Elaborate the target and compile it through golden gate (ElaborateAndCompile)
* 2) Compile a metasimulator for the generated RTL (compileMlSimulator)
* 3) Run the metasimulator. Running a metasimualtion is somewaht
* target-specific and is handled differently by different subclasses.
*
* Some tests inspect the simulation outputs, or run other simulators. See the
* [[TutorialSuite]] for examples of that.
*
* NB: Not thread-safe.
*/
abstract class TestSuiteCommon extends org.scalatest.flatspec.AnyFlatSpec {
def targetTuple: String
def commonMakeArgs: Seq[String]
val platformName = "f1"
val replayBackends = Seq("rtl")
val platformMakeArgs = Seq(s"PLATFORM=$platformName")
// Check if we are running out of Chipyard by checking for the existence of a firesim/sim directory
val firesimDir = {
val cwd = System.getProperty("user.dir")
val firesimAsLibDir = new File(cwd, "sims/firesim/sim")
if (firesimAsLibDir.exists()) {
firesimAsLibDir
} else {
new File(cwd)
}
}
var ciSkipElaboration: Boolean = false
var transitiveFailure: Boolean = false
override def withFixture(test: NoArgTest) = {
// Perform setup
ciSkipElaboration = test.configMap.getOptional[String]("ci-skip-elaboration")
.map { _.toBoolean }
.getOrElse(false)
if (transitiveFailure) {
org.scalatest.Canceled("Due to prior failure")
} else {
super.withFixture(test)
}
}
// These mirror those in the make files; invocation of the MIDAS compiler
// is the one stage of the tests we don't invoke the Makefile for
lazy val genDir = new File(firesimDir, s"generated-src/${platformName}/${targetTuple}")
lazy val outDir = new File(firesimDir, s"output/${platformName}/${targetTuple}")
implicit def toStr(f: File): String = f.toString replace (File.separator, "/")
// Defines a make target that will build all prerequistes for downstream
// tests that require a Scala invocation.
def elaborateMakeTarget: Seq[String] = Seq("compile")
def makeCommand(makeArgs: String*): Seq[String] = {
Seq("make", "-C", s"$firesimDir") ++ makeArgs.toSeq ++ commonMakeArgs ++ platformMakeArgs
}
// Runs make passing default args to specify the right target design, project and platform
def make(makeArgs: String*): Int = {
val cmd = makeCommand(makeArgs:_*)
println("Running: %s".format(cmd mkString " "))
cmd.!
}
// As above, but if the RC is non-zero, cancels all downstream tests. This
// is used to prevent re-invoking make on a target with a dependency on the
// result of this recipe. Which would lead to a second failure.
def makeCriticalDependency(makeArgs: String*): Int = {
val returnCode = make(makeArgs:_*)
transitiveFailure = returnCode != 0
returnCode
}
def clean() { make("clean") }
def mkdirs() { genDir.mkdirs; outDir.mkdirs }
def isCmdAvailable(cmd: String) =
Seq("which", cmd) ! ProcessLogger(_ => {}) == 0
// Running all scala-invocations required to take the design to verilog.
// Generally elaboration + GG compilation.
def elaborateAndCompile(behaviorDescription: String = "elaborate and compile through GG sucessfully") {
it should behaviorDescription in {
// Under CI, if make failed during elaboration we catch it here without
// attempting to rebuild
val target = (if (ciSkipElaboration) Seq("-q") else Seq()) ++ elaborateMakeTarget
assert(makeCriticalDependency(target:_*) == 0)
}
}
// Compiles a MIDAS-level RTL simulator of the target
def compileMlSimulator(b: String, debug: Boolean = false) {
if (isCmdAvailable(b)) {
it should s"compile sucessfully to ${b}" + { if (debug) " with waves enabled" else "" } in {
assert(makeCriticalDependency(s"$b%s".format(if (debug) "-debug" else "")) == 0)
}
}
}
/**
* Extracts all lines in a file that begin with a specific prefix, removing
* extra whitespace between the prefix and the remainder of the line
*
* @param filename Input file
* @param prefix The per-line prefix to filter with
* @param linesToDrop Some number of matched lines to be removed
* @param headerLines An initial number of lines to drop before filtering.
* Assertions, Printf output have a single line header.
* MLsim stdout has some unused output, so set this to 1 by default
*
*/
def extractLines(filename: File, prefix: String, linesToDrop: Int = 0, headerLines: Int = 1): Seq[String] = {
val lines = Source.fromFile(filename).getLines.toList.drop(headerLines)
lines.filter(_.startsWith(prefix))
.dropRight(linesToDrop)
.map(_.stripPrefix(prefix).replaceAll(" +", " "))
}
/**
* Diffs two sets of lines. Wrap calls to this function in a scalatest
* behavior spec. @param aName and @param bName can be used to provide more
* insightful assertion messages in scalatest reporting.
*/
def diffLines(
aLines: Seq[String],
bLines: Seq[String],
aName: String = "Actual output",
bName: String = "Expected output"): Unit = {
assert(aLines.size == bLines.size && aLines.nonEmpty,
s"\n${aName} length (${aLines.size}) and ${bName} length (${bLines.size}) differ.")
for ((a, b) <- bLines.zip(aLines)) {
assert(a == b)
}
}
}
/**
* Hijacks TestSuiteCommon (mostly for make related features) to run the synthesizable unit tests.
*/
abstract class MidasUnitTestSuite(unitTestConfig: String, shouldFail: Boolean = false) extends TestSuiteCommon {
val targetTuple = unitTestConfig
// GENERATED_DIR & OUTPUT_DIR are only used to properly invoke `make clean`
val commonMakeArgs = Seq(s"UNITTEST_CONFIG=${unitTestConfig}",
s"GENERATED_DIR=${genDir}",
s"OUTPUT_DIR=${outDir}")
// Use the default recipe which also will compile verilator since there's no
// separate target for just elaboration
override def elaborateMakeTarget = Seq("compile-midas-unittests")
override lazy val genDir = new File(s"generated-src/unittests/${targetTuple}")
override lazy val outDir = new File(s"output/unittests/${targetTuple}")
def runUnitTestSuite(backend: String, debug: Boolean = false) {
val testSpecString = if (shouldFail) "fail" else "pass" + s" when running under ${backend}"
if (isCmdAvailable(backend)) {
lazy val result = make("run-midas-unittests%s".format(if (debug) "-debug" else ""),
s"EMUL=$backend")
it should testSpecString in {
if (shouldFail) assert(result != 0) else assert(result == 0)
}
} else {
ignore should testSpecString in { }
}
}
mkdirs
behavior of s"MIDAS unittest: ${unitTestConfig}"
elaborateAndCompile("elaborate sucessfully")
runUnitTestSuite("verilator")
}
class AllMidasUnitTests extends MidasUnitTestSuite("AllUnitTests") {
runUnitTestSuite("vcs")
}
// Need to get VCS to return non-zero exitcodes when $fatal is called
class FailingUnitTests extends MidasUnitTestSuite("TimeOutCheck", shouldFail = true)