Introduced a full verilator/vcs/debug matrix (#1435)

This PR moves the paramterization of test harnesses to the toplevel.
Slightly re-wrote tests to avoid duplication of running logic.
This commit is contained in:
Nandor Licker 2023-02-19 13:31:03 +02:00 committed by GitHub
parent 9818dbcae3
commit 797e6e41bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 546 additions and 594 deletions

View File

@ -19,7 +19,6 @@ project {
"glob:**firesim-lib/src/main/scala/passes/ILATopWiring.scala",
"glob:**firesim-lib/src/main/scala/util/Configs.scala",
"glob:**firesim-lib/src/test/scala/EmitCIElaborationScript.scala",
"glob:**firesim-lib/src/test/scala/TestSuiteCommon.scala",
"glob:**midas/src/main/scala/junctions/Nasti2AXI4.scala",
"glob:**midas/src/main/scala/junctions/ReorderQueue.scala",
"glob:**midas/src/main/scala/junctions/addrmap.scala",
@ -195,8 +194,6 @@ project {
"glob:**src/main/scala/midasexamples/Util.scala",
"glob:**src/main/scala/midasexamples/VerilogAccumulator.scala",
"glob:**src/main/scala/midasexamples/WireInterconnect.scala",
"glob:**src/test/scala/fasedtests/FASEDTestSuite.scala",
"glob:**src/test/scala/midasexamples/TutorialSuite.scala"
]
}

View File

@ -6,39 +6,35 @@ import scala.io.Source
import scala.sys.process.{stringSeqToProcess, ProcessLogger}
import freechips.rocketchip.config.Config
/**
* A base class that captures the platform-specific parts of the configuration of a test.
/** A base class that captures the platform-specific parts of the configuration of a test.
*
* @param platformName Name of the target platform (f1 or vitis)
* @param configs List of platform-specific configuration classes
* @param platformName
* Name of the target platform (f1 or vitis)
* @param configs
* List of platform-specific configuration classes
*/
abstract class BasePlatformConfig(val platformName: String, val configs: Seq[Class[_ <: Config]])
/**
* 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.
/** 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.
* Some tests inspect the simulation outputs, or run other simulators. See the [[TutorialSuite]] for examples of that.
*
* NB: Not thread-safe.
*/
abstract class TestSuiteBase extends org.scalatest.flatspec.AnyFlatSpec {
def commonMakeArgs: Seq[String]
def targetName: String
def targetConfigs: String = "NoConfig"
def targetName: String
def targetConfigs: String = "NoConfig"
def platformMakeArgs: Seq[String] = Seq()
val replayBackends = Seq("rtl")
// 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 cwd = System.getProperty("user.dir")
val firesimAsLibDir = new File(cwd, "sims/firesim/sim")
if (firesimAsLibDir.exists()) {
firesimAsLibDir
@ -50,9 +46,10 @@ abstract class TestSuiteBase extends org.scalatest.flatspec.AnyFlatSpec {
var ciSkipElaboration: Boolean = false
var transitiveFailure: Boolean = false
override def withFixture(test: NoArgTest) = {
// Perform setup
ciSkipElaboration = test.configMap.getOptional[String]("ci-skip-elaboration")
override def withFixture(test: NoArgTest) = {
// Perform setup
ciSkipElaboration = test.configMap
.getOptional[String]("ci-skip-elaboration")
.map { _.toBoolean }
.getOrElse(false)
if (transitiveFailure) {
@ -60,9 +57,9 @@ abstract class TestSuiteBase extends org.scalatest.flatspec.AnyFlatSpec {
} else {
super.withFixture(test)
}
}
}
implicit def toStr(f: File): String = f.toString replace (File.separator, "/")
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.
@ -74,8 +71,8 @@ abstract class TestSuiteBase extends org.scalatest.flatspec.AnyFlatSpec {
// 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 " "))
val cmd = makeCommand(makeArgs: _*)
println("Running: %s".format(cmd.mkString(" ")))
cmd.!
}
@ -83,7 +80,7 @@ abstract class TestSuiteBase extends org.scalatest.flatspec.AnyFlatSpec {
// 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:_*)
val returnCode = make(makeArgs: _*)
transitiveFailure = returnCode != 0
returnCode
}
@ -100,16 +97,7 @@ abstract class TestSuiteBase extends org.scalatest.flatspec.AnyFlatSpec {
// 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)
}
assert(makeCriticalDependency(target: _*) == 0)
}
}
}
@ -118,20 +106,70 @@ abstract class TestSuiteCommon(targetProject: String) extends TestSuiteBase {
def platformConfigs: Seq[Class[_ <: Config]] = Seq()
def basePlatformConfig: BasePlatformConfig
def run(
backend: String,
debug: Boolean = false,
logFile: Option[File] = None,
waveform: Option[File] = None,
args: Seq[String] = Nil,
) = {
val makeArgs = Seq(
s"run-$backend%s".format(if (debug) "-debug" else ""),
"LOGFILE=%s".format(logFile.map(toStr).getOrElse("")),
"WAVEFORM=%s".format(waveform.map(toStr).getOrElse("")),
"ARGS=%s".format(args.mkString(" ")),
)
if (isCmdAvailable(backend)) {
make(makeArgs: _*)
} else 0
}
def platformConfigString = (platformConfigs ++ basePlatformConfig.configs).map(_.getSimpleName).mkString("_")
override val platformMakeArgs = Seq(s"PLATFORM=${basePlatformConfig.platformName}")
override val commonMakeArgs = Seq(s"TARGET_PROJECT=${targetProject}",
s"DESIGN=${targetName}",
s"TARGET_CONFIG=${targetConfigs}",
s"PLATFORM_CONFIG=${platformConfigString}")
override val commonMakeArgs = Seq(
s"TARGET_PROJECT=${targetProject}",
s"DESIGN=${targetName}",
s"TARGET_CONFIG=${targetConfigs}",
s"PLATFORM_CONFIG=${platformConfigString}",
)
val targetTuple = s"${targetName}-${targetConfigs}-${platformConfigString}"
// 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/${basePlatformConfig.platformName}/${targetTuple}")
lazy val outDir = new File(firesimDir, s"output/${basePlatformConfig.platformName}/${targetTuple}")
lazy val genDir = new File(firesimDir, s"generated-src/${basePlatformConfig.platformName}/${targetTuple}")
lazy val outDir = new File(firesimDir, s"output/${basePlatformConfig.platformName}/${targetTuple}")
def mkdirs() { genDir.mkdirs; outDir.mkdirs }
// Compiles a MIDAS-level RTL simulator of the target
def compileMlSimulator(b: String, debug: Boolean) {
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)
}
}
}
/** Method to be implemented by tests, providing an invocation to a simulation run and checks.
*/
def defineTests(backend: String, debug: Boolean): Unit
// Overrideable methods to specify test configurations.
def simulators: Seq[String] = Seq("verilator", "vcs")
def debugFlags: Seq[Boolean] = Seq(false)
// Define test rules across the matrix of simulators and debug flags.
mkdirs()
for (simulator <- simulators) {
if (isCmdAvailable(simulator)) {
for (debugFlag <- debugFlags) {
behavior.of(s"$targetName with ${simulator}${if (debugFlag) "-debug" else ""}")
elaborateAndCompile()
compileMlSimulator(simulator, debugFlag)
defineTests(simulator, debugFlag)
}
}
}
}

View File

@ -54,10 +54,13 @@ TARGET_SOURCE_DIRS ?=
# while showing error messages, then it runs sbt again with errors disabled to
# capture the classpath from the output (errors are dumped to stdout otherwise).
define build_classpath
$(SBT_NON_THIN) \
bash -c '\
export TMP=$(shell mktemp); \
($(SBT_NON_THIN) \
--error \
"set showSuccess := false; project $(1); compile; package; export $(2):fullClasspath" \
| head -n 1 | tr -d '\n' > $@
> $$TMP || (cat $$TMP | head -n -1 && rm $$TMP && exit 1)) && \
((cat $$TMP | head -n 1 | tr -d "\n" > $@) 2> /dev/null || true; rm -f $$TMP)'
endef
# Helpers to identify all source files of the FireSim project.

View File

@ -9,10 +9,11 @@ import freechips.rocketchip.config.Parameters
import midas.targetutils.{TriggerSource, TriggerSink, SynthesizePrintf}
trait TriggerPredicatedPrintfConsts {
val assertTriggerCycle = 100
val deassertTriggerCycle = 1000
object TriggerPredicatedPrintfConsts {
val assertTriggerCycle: Int = 100
val deassertTriggerCycle: Int = 1000
}
/**
* An example module that uses the trigger system to enable a printf in a
* desired region of interest.
@ -24,7 +25,8 @@ trait TriggerPredicatedPrintfConsts {
* instances of this module.
*/
class TriggerPredicatedPrintfDUT(printfPrefix: String = "SYNTHESIZED_PRINT ")
extends Module with TriggerPredicatedPrintfConsts{
extends Module {
import TriggerPredicatedPrintfConsts._
val io = IO(new Bundle{})
// An inner class to reduce namespace bloat in midasexamples package

View File

@ -25,33 +25,6 @@ abstract class BridgeSuite(
) extends TestSuiteCommon("bridges")
with Matchers {
def run(
backend: String,
debug: Boolean = false,
logFile: Option[File] = None,
waveform: Option[File] = None,
args: Seq[String] = Nil,
) = {
val makeArgs = Seq(
s"run-$backend%s".format(if (debug) "-debug" else ""),
"LOGFILE=%s".format(logFile.map(toStr).getOrElse("")),
"WAVEFORM=%s".format(waveform.map(toStr).getOrElse("")),
"ARGS=%s".format(args.mkString(" ")),
)
if (isCmdAvailable(backend)) {
make(makeArgs: _*)
} else 0
}
/** Runs MIDAS-level simulation on the design.
*
* @param backend
* Backend simulator: "verilator" or "vcs"
* @param debug
* When true, captures waves from the simulation
*/
def runTest(backend: String, debug: Boolean = false)
/** Helper to generate tests strings.
*/
def getTestString(length: Int): String = {
@ -59,45 +32,30 @@ abstract class BridgeSuite(
val alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
(1 to length).map(_ => alpha(gen.nextInt(alpha.length))).mkString
}
mkdirs()
behavior.of(s"$targetName")
elaborateAndCompile()
for (backend <- Seq("vcs", "verilator")) {
compileMlSimulator(backend)
val testEnvStr = s"pass in ${backend} MIDAS-level simulation"
if (isCmdAvailable(backend)) {
it should testEnvStr in {
runTest(backend)
}
} else {
ignore should testEnvStr in {}
}
}
}
class UARTTest(targetConfig: BasePlatformConfig) extends BridgeSuite("UARTModule", "UARTConfig", targetConfig) {
override def runTest(backend: String, debug: Boolean) {
// Generate a short test string.
val data = getTestString(16)
override def defineTests(backend: String, debug: Boolean) {
it should "echo input to output" in {
// Generate a short test string.
val data = getTestString(16)
// Create an input file.
val input = File.createTempFile("input", ".txt")
input.deleteOnExit()
val inputWriter = new BufferedWriter(new FileWriter(input))
inputWriter.write(data)
inputWriter.flush()
inputWriter.close()
// Create an input file.
val input = File.createTempFile("input", ".txt")
input.deleteOnExit()
val inputWriter = new BufferedWriter(new FileWriter(input))
inputWriter.write(data)
inputWriter.flush()
inputWriter.close()
// Create an output file to write to.
val output = File.createTempFile("output", ".txt")
// Create an output file to write to.
val output = File.createTempFile("output", ".txt")
val runResult = run(backend, debug, args = Seq(s"+uart-in0=${input.getPath}", s"+uart-out0=${output.getPath}"))
assert(runResult == 0)
val result = scala.io.Source.fromFile(output.getPath).mkString
result should equal(data)
val runResult = run(backend, debug, args = Seq(s"+uart-in0=${input.getPath}", s"+uart-out0=${output.getPath}"))
assert(runResult == 0)
val result = scala.io.Source.fromFile(output.getPath).mkString
result should equal(data)
}
}
}
@ -106,32 +64,34 @@ class UARTVitisTest extends UARTTest(BaseConfigs.Vitis)
class BlockDevTest(targetConfig: BasePlatformConfig)
extends BridgeSuite("BlockDevModule", "BlockDevConfig", targetConfig) {
override def runTest(backend: String, debug: Boolean) {
// Generate a random string spanning 2 sectors with a fixed seed.
val data = getTestString(1024)
override def defineTests(backend: String, debug: Boolean) {
it should "copy from one device to another" in {
// Generate a random string spanning 2 sectors with a fixed seed.
val data = getTestString(1024)
// Create an input file.
val input = File.createTempFile("input", ".txt")
input.deleteOnExit()
val inputWriter = new BufferedWriter(new FileWriter(input))
inputWriter.write(data)
inputWriter.flush()
inputWriter.close()
// Create an input file.
val input = File.createTempFile("input", ".txt")
input.deleteOnExit()
val inputWriter = new BufferedWriter(new FileWriter(input))
inputWriter.write(data)
inputWriter.flush()
inputWriter.close()
// Pre-allocate space in the output.
val output = File.createTempFile("output", ".txt")
output.deleteOnExit()
val outputWriter = new BufferedWriter(new FileWriter(output))
for (i <- 1 to data.size) {
outputWriter.write('x')
// Pre-allocate space in the output.
val output = File.createTempFile("output", ".txt")
output.deleteOnExit()
val outputWriter = new BufferedWriter(new FileWriter(output))
for (i <- 1 to data.size) {
outputWriter.write('x')
}
outputWriter.flush()
outputWriter.close()
val runResult = run(backend, debug, args = Seq(s"+blkdev0=${input.getPath}", s"+blkdev1=${output.getPath}"))
assert(runResult == 0)
val result = scala.io.Source.fromFile(output.getPath).mkString
result should equal(data)
}
outputWriter.flush()
outputWriter.close()
val runResult = run(backend, debug, args = Seq(s"+blkdev0=${input.getPath}", s"+blkdev1=${output.getPath}"))
assert(runResult == 0)
val result = scala.io.Source.fromFile(output.getPath).mkString
result should equal(data)
}
}

View File

@ -19,38 +19,40 @@ abstract class TracerVTestBase(
trace: Option[Int] = None,
start: Option[String] = None,
) extends BridgeSuite("TracerVModule", s"TracerVModuleTestCount${width}", platformConfig) {
override def runTest(backend: String, debug: Boolean) {
// Create an expected file.
val expected = File.createTempFile("expected", ".txt")
expected.deleteOnExit()
override def defineTests(backend: String, debug: Boolean) {
it should "dump traced instructions" in {
// Create an expected file.
val expected = File.createTempFile("expected", ".txt")
expected.deleteOnExit()
// Create the output file. tracerv will always append '-C0' to the end of the path provided in the plusarg
val output = File.createTempFile("output", ".txt-C0")
output.deleteOnExit()
val outputPath = output.getPath.stripSuffix("-C0")
// Create the output file. tracerv will always append '-C0' to the end of the path provided in the plusarg
val output = File.createTempFile("output", ".txt-C0")
output.deleteOnExit()
val outputPath = output.getPath.stripSuffix("-C0")
// group the optional function args together with the correct plusarg string names
val optionalArgs = Seq(
("+trace-select=", trace),
("+trace-start=", start),
)
// group the optional function args together with the correct plusarg string names
val optionalArgs = Seq(
("+trace-select=", trace),
("+trace-start=", start),
)
// a seq starting with fixed plusargs, ending with optional plusargs
// the optional plusargs are properly constructed or dropped
val args = Seq(
s"+tracefile=${outputPath}",
s"+tracerv-expected-output=${expected.getPath}",
) ++ optionalArgs.collect { case (a, Some(b)) =>
s"${a}${b}"
// a seq starting with fixed plusargs, ending with optional plusargs
// the optional plusargs are properly constructed or dropped
val args = Seq(
s"+tracefile=${outputPath}",
s"+tracerv-expected-output=${expected.getPath}",
) ++ optionalArgs.collect { case (a, Some(b)) =>
s"${a}${b}"
}
val runResult =
run(backend, false, args = args)
assert(runResult == 0)
val expectedContents = scala.io.Source.fromFile(expected.getPath).mkString
val outputContents = scala.io.Source.fromFile(output.getPath).mkString
outputContents should equal(expectedContents)
}
val runResult =
run(backend, false, args = args)
assert(runResult == 0)
val expectedContents = scala.io.Source.fromFile(expected.getPath).mkString
val outputContents = scala.io.Source.fromFile(output.getPath).mkString
outputContents should equal(expectedContents)
}
}

View File

@ -5,12 +5,12 @@ package firesim.fasedtests
import java.io.File
import scala.io.Source
import scala.concurrent.{Future, Await, ExecutionContext}
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.util.Random
import org.scalatest.Suites
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.system.{RocketTestSuite, BenchmarkTestSuite}
import freechips.rocketchip.system.{BenchmarkTestSuite, RocketTestSuite}
import freechips.rocketchip.system.TestGeneration._
import freechips.rocketchip.system.DefaultTestSuites._
@ -19,7 +19,7 @@ import firesim.TestSuiteUtil._
import firesim.BasePlatformConfig
import firesim.midasexamples.BaseConfigs
/** Different runtime-configuration modes extend this trait. See below. */
/** Different runtime-configuration modes extend this trait. See below. */
sealed trait RuntimeConfig {
def behaviorString: String
}
@ -35,27 +35,31 @@ case object EmptyRuntimeConfig extends RuntimeConfig {
}
/** Specific an alternate path to a conf file. */
case class CustomRuntimeConfig(pathRelativeToSim: String) extends RuntimeConfig {
case class CustomRuntimeConfig(pathRelativeToSim: String) extends RuntimeConfig {
val behaviorString = s"with runtime conf ${pathRelativeToSim}"
}
/**
* A specialization of TestSuiteCommon for FASED-specific testing. Mostly
* handles differences in the makefrag vs midasexamples..
/** A specialization of TestSuiteCommon for FASED-specific testing. Mostly handles differences in the makefrag vs
* midasexamples..
*
* @param targetName DESIGN: target top-level module class
* @param targetConfigs TARGET_CONFIG: config string to parameterize the target
* @param platformConfigs PLATFORM_CONFIG: config string to configure GG
* @param baseRuntimeConfig Default runtime conf handling for runtest
* @param additionalPlusArgs Non-standard plusargs to add to runTest invocations by default
* @param targetName
* DESIGN: target top-level module class
* @param targetConfigs
* TARGET_CONFIG: config string to parameterize the target
* @param platformConfigs
* PLATFORM_CONFIG: config string to configure GG
* @param baseRuntimeConfig
* Default runtime conf handling for runtest
* @param additionalPlusArgs
* Non-standard plusargs to add to runTest invocations by default
*/
abstract class FASEDTest(
override val targetName: String,
override val targetConfigs: String,
platformConfigs: String = "",
baseRuntimeConfig: RuntimeConfig = DefaultRuntimeConfig,
additionalPlusArgs: Seq[String] = Seq(),
) extends firesim.TestSuiteCommon("fasedtests") {
override val targetName: String,
override val targetConfigs: String,
platformConfigs: String = "",
baseRuntimeConfig: RuntimeConfig = DefaultRuntimeConfig,
additionalPlusArgs: Seq[String] = Seq(),
) extends firesim.TestSuiteCommon("fasedtests") {
override def basePlatformConfig = BaseConfigs.F1
@ -63,41 +67,38 @@ abstract class FASEDTest(
import ExecutionContext.Implicits.global
def invokeMlSimulator(backend: String, debug: Boolean, args: Seq[String]) = {
make((s"run-${backend}%s".format(if (debug) "-debug" else "") +: args):_*)
make((s"run-${backend}%s".format(if (debug) "-debug" else "") +: args): _*)
}
def runTest(
backend: String,
debug: Boolean = false,
logFile: Option[File] = None,
baseRuntimeConfig: RuntimeConfig = baseRuntimeConfig,
additionalPlusArgs: Seq[String] = additionalPlusArgs,
additionalMakeArgs: Seq[String] = Seq(),
behaviorSpec: Option[String] = None) = {
backend: String,
debug: Boolean,
logFile: Option[File] = None,
baseRuntimeConfig: RuntimeConfig = baseRuntimeConfig,
additionalPlusArgs: Seq[String] = additionalPlusArgs,
additionalMakeArgs: Seq[String] = Seq(),
behaviorSpec: Option[String] = None,
) = {
val runtimeConfArg: Option[String] = baseRuntimeConfig match {
case DefaultRuntimeConfig => None
case EmptyRuntimeConfig => Some(s"COMMON_SIM_ARGS=")
case DefaultRuntimeConfig => None
case EmptyRuntimeConfig => Some(s"COMMON_SIM_ARGS=")
case CustomRuntimeConfig(path) => Some(s"COMMON_SIM_ARGS=${Source.fromFile(path).getLines.mkString(" ")}")
}
val plusArgs = Seq(s"""EXTRA_SIM_ARGS=${additionalPlusArgs.mkString(" ")}""")
val logArg = logFile.map { logName => s"LOGFILE=${logName}" }
val plusArgs = Seq(s"""EXTRA_SIM_ARGS=${additionalPlusArgs.mkString(" ")}""")
val logArg = logFile.map { logName => s"LOGFILE=${logName}" }
val makeArgs =
runtimeConfArg ++:
plusArgs ++:
logArg ++:
additionalMakeArgs
plusArgs ++:
logArg ++:
additionalMakeArgs
if (isCmdAvailable(backend)) {
it should behaviorSpec.getOrElse(s"run on $backend") in {
assert(invokeMlSimulator(backend, debug, makeArgs) == 0)
}
it should behaviorSpec.getOrElse(s"run") in {
assert(invokeMlSimulator(backend, debug, makeArgs) == 0)
}
}
compileMlSimulator("verilator", false)
runTest("verilator", false)
override def defineTests(backend: String, debug: Boolean): Unit = runTest(backend, debug)
}
class AXI4FuzzerLBPTest extends FASEDTest("AXI4Fuzzer", "DefaultConfig")
@ -105,31 +106,32 @@ class AXI4FuzzerLBPTest extends FASEDTest("AXI4Fuzzer", "DefaultConfig")
// Sanity checks that target output is the same when using the default runtime
// configuration and the hardwired values.
class CheckHardwiredValuesTest extends FASEDTest("AXI4Fuzzer", "NT10e3_AddrBits16_DefaultConfig") {
val logA = new File(s"$outDir/using-runtime-conf.out")
runTest(
"verilator",
logFile = Some(logA),
behaviorSpec = Some("run using a runtime.conf"))
override def defineTests(backend: String, debug: Boolean) {
val logA = new File(s"$outDir/using-runtime-conf.out")
runTest(backend, debug, logFile = Some(logA), behaviorSpec = Some("run using a runtime.conf"))
val logB = new File(s"$outDir/using-hardwired-settings.out")
runTest(
"verilator",
logFile = Some(logB),
baseRuntimeConfig = EmptyRuntimeConfig,
additionalPlusArgs = Seq("+mm_useHardwareDefaultRuntimeSettings_0"),
behaviorSpec = Some("run using initialization values"))
val logB = new File(s"$outDir/using-hardwired-settings.out")
runTest(
backend,
debug,
logFile = Some(logB),
baseRuntimeConfig = EmptyRuntimeConfig,
additionalPlusArgs = Seq("+mm_useHardwareDefaultRuntimeSettings_0"),
behaviorSpec = Some("run using initialization values"),
)
"Initialization values for configuration registers" should "produce the same target behavior as using the default runtime.conf" in {
val aLines = extractLines(logA, "AXI4FuzzMaster_0", headerLines = 0)
val bLines = extractLines(logB, "AXI4FuzzMaster_0", headerLines = 0)
diffLines(aLines, bLines, logA.getName, logB.getName)
"Initialization values for configuration registers" should "produce the same target behavior as using the default runtime.conf" in {
val aLines = extractLines(logA, "AXI4FuzzMaster_0", headerLines = 0)
val bLines = extractLines(logB, "AXI4FuzzMaster_0", headerLines = 0)
diffLines(aLines, bLines, logA.getName, logB.getName)
}
}
}
class AXI4FuzzerMultiChannelTest extends FASEDTest("AXI4Fuzzer", "FuzzMask3FFF_QuadFuzzer_QuadChannel_DefaultConfig")
class AXI4FuzzerFCFSTest extends FASEDTest("AXI4Fuzzer", "FCFSConfig")
class AXI4FuzzerFRFCFSTest extends FASEDTest("AXI4Fuzzer", "FRFCFSConfig")
class AXI4FuzzerLLCDRAMTest extends FASEDTest("AXI4Fuzzer", "LLCDRAMConfig") {
class AXI4FuzzerFCFSTest extends FASEDTest("AXI4Fuzzer", "FCFSConfig")
class AXI4FuzzerFRFCFSTest extends FASEDTest("AXI4Fuzzer", "FRFCFSConfig")
class AXI4FuzzerLLCDRAMTest extends FASEDTest("AXI4Fuzzer", "LLCDRAMConfig") {
//override def runTests = {
// // Check that the memory model uses the correct number of MSHRs
// val maxMSHRs = targetParams(LlcKey).get.mshrs.max
@ -144,23 +146,21 @@ class AXI4FuzzerLLCDRAMTest extends FASEDTest("AXI4Fuzzer", "LLCDRAMConfig") {
}
// Generate a target memory system that uses the whole host memory system.
class BaselineMultichannelTest extends FASEDTest(
"AXI4Fuzzer",
"AddrBits22_QuadFuzzer_DefaultConfig",
"AddrBits22_SmallQuadChannelHostConfig") {
runTest("vcs", true)
}
class BaselineMultichannelTest
extends FASEDTest("AXI4Fuzzer", "AddrBits22_QuadFuzzer_DefaultConfig", "AddrBits22_SmallQuadChannelHostConfig")
// Checks that id-reallocation works for platforms with limited ID space
class NarrowIdConstraint extends FASEDTest("AXI4Fuzzer", "DefaultConfig", "ConstrainedIdHostConfig")
// Suite Collections for CI
class CIGroupA extends Suites(
new AXI4FuzzerLBPTest,
new AXI4FuzzerFRFCFSTest
)
class CIGroupA
extends Suites(
new AXI4FuzzerLBPTest,
new AXI4FuzzerFRFCFSTest,
)
class CIGroupB extends Suites(
new AXI4FuzzerLLCDRAMTest,
new NarrowIdConstraint
)
class CIGroupB
extends Suites(
new AXI4FuzzerLLCDRAMTest,
new NarrowIdConstraint,
)

View File

@ -30,7 +30,6 @@ abstract class FireSimTestSuite(
override val platformConfigs: Seq[Class[_ <: Config]] = Seq(),
N: Int = 8,
) extends TestSuiteCommon("firesim") {
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
@ -47,68 +46,32 @@ abstract class FireSimTestSuite(
)
}
def runTest(backend: String, name: String, debug: Boolean, additionalArgs: Seq[String] = Nil) = {
compileMlSimulator(backend, debug)
if (isCmdAvailable(backend)) {
it should s"pass in ML simulation on ${backend}" in {
assert(invokeMlSimulator(backend, name, debug, additionalArgs) == 0)
def runTest(backend: String, debug: Boolean, name: String, additionalArgs: Seq[String] = Nil) = {
it should s"pass in ML simulation on ${backend}" in {
assert(invokeMlSimulator(backend, name, debug, additionalArgs) == 0)
}
}
def runSuite(backend: String, debug: Boolean)(suite: RocketTestSuite) {
val postfix = suite match {
case _: BenchmarkTestSuite | _: BlockdevTestSuite | _: NICTestSuite => ".riscv"
case _ => ""
}
it should s"pass all tests in ${suite.makeTargetName}" in {
val results = suite.names.toSeq.sliding(N, N).map { t =>
val subresults = t.map(name => Future(name -> invokeMlSimulator(backend, s"$name$postfix", debug)))
Await.result(Future.sequence(subresults), Duration.Inf)
}
results.flatten.foreach { case (name, exitcode) =>
assert(exitcode == 0, s"Failed $name")
}
}
}
def runSuite(backend: String, debug: Boolean = false)(suite: RocketTestSuite) {
// compile emulators
behavior.of(s"${suite.makeTargetName} running on $backend")
if (isCmdAvailable(backend)) {
val postfix = suite match {
case _: BenchmarkTestSuite | _: BlockdevTestSuite | _: NICTestSuite => ".riscv"
case _ => ""
}
it should s"pass all tests in ${suite.makeTargetName}" in {
val results = suite.names.toSeq.sliding(N, N).map { t =>
val subresults = t.map(name => Future(name -> invokeMlSimulator(backend, s"$name$postfix", debug)))
Await.result(Future.sequence(subresults), Duration.Inf)
}
results.flatten.foreach { case (name, exitcode) =>
assert(exitcode == 0, s"Failed $name")
}
}
} else {
ignore should s"pass $backend"
}
override def defineTests(backend: String, debug: Boolean) {
runTest(backend, debug, "rv64ui-p-simple", Seq(s"""EXTRA_SIM_ARGS=+trace-humanreadable0"""))
runSuite(backend, debug)(benchmarks)
}
// Checks the collected trace log matches the behavior of a chisel printf
def diffTracelog(verilatedLog: String) {
behavior.of("captured instruction trace")
it should s"match the chisel printf in ${verilatedLog}" in {
def getLines(file: File): Seq[String] = Source.fromFile(file).getLines.toList
val printfPrefix = "TRACEPORT 0: "
val verilatedOutput = getLines(new File(outDir, s"/${verilatedLog}")).collect({
case line if line.startsWith(printfPrefix) => line.stripPrefix(printfPrefix)
})
// Last bit indicates the core was under reset; reject those tokens
// Tail to drop the first token which is initialized in the channel
val synthPrintOutput = getLines(new File(genDir, s"/TRACEFILE")).tail.filter(line => (line.last.toInt & 1) == 0)
assert(
math.abs(verilatedOutput.size - synthPrintOutput.size) <= 1,
s"\nPrintf Length: ${verilatedOutput.size}, Trace Length: ${synthPrintOutput.size}",
)
assert(verilatedOutput.nonEmpty)
for ((vPrint, sPrint) <- verilatedOutput.zip(synthPrintOutput)) {
assert(vPrint == sPrint)
}
}
}
mkdirs
behavior.of(s"Tuple: ${targetTuple}")
elaborateAndCompile()
runTest("verilator", "rv64ui-p-simple", false, Seq(s"""EXTRA_SIM_ARGS=+trace-humanreadable0"""))
runSuite("verilator")(benchmarks)
}
class SimpleRocketF1Tests
@ -178,7 +141,6 @@ class CVA6F1Tests
BaseConfigs.F1,
)
// This test suite only mirrors what is run in CI. CI invokes each test individually, using a testOnly call.
class CITests
extends Suites(
new SimpleRocketF1Tests,

View File

@ -11,18 +11,21 @@ class AssertModuleF1Test extends TutorialSuite("AssertModule")
class MulticlockAssertF1Test extends TutorialSuite("MulticlockAssertModule")
class AssertTortureTest extends TutorialSuite("AssertTorture") with AssertTortureConstants {
def checkClockDomainAssertionOrder(clockIdx: Int): Unit = {
it should s"capture asserts in the same order as the reference printfs in clock domain $clockIdx" in {
val verilatedLogFile = new File(outDir, s"/${targetName}.verilator.out")
// Diff parts of the simulation's stdout against itself, as the synthesized
// assertion messages are dumped to the same file as printfs in the RTL
val expected = extractLines(verilatedLogFile, prefix = s"${printfPrefix}${clockPrefix(clockIdx)}")
val actual = extractLines(verilatedLogFile, prefix = s"Assertion failed: ${clockPrefix(clockIdx)}")
diffLines(expected, actual)
override def defineTests(backend: String, debug: Boolean) {
it should "run in the simulator" in {
assert(run(backend, debug, args = simulationArgs) == 0)
}
for (clockIdx <- 0 until 4) {
it should s"capture asserts in the same order as the reference printfs in clock domain $clockIdx" in {
val logFile = new File(outDir, s"/${targetName}.${backend}.out")
// Diff parts of the simulation's stdout against itself, as the synthesized
// assertion messages are dumped to the same file as printfs in the RTL
val expected = extractLines(logFile, prefix = s"${printfPrefix}${clockPrefix(clockIdx)}")
val actual = extractLines(logFile, prefix = s"Assertion failed: ${clockPrefix(clockIdx)}")
diffLines(expected, actual)
}
}
}
// TODO: Create a target-parameters instance we can inspect here
Seq.tabulate(4)(i => checkClockDomainAssertionOrder(i))
}
class AssertGlobalResetConditionF1Test extends TutorialSuite("AssertGlobalResetCondition")

View File

@ -11,6 +11,7 @@ import firesim.BasePlatformConfig
abstract class AutoCounterSuite(
targetName: String,
checks: Seq[(String, String)],
targetConfigs: String = "NoConfig",
platformConfigs: Seq[Class[_ <: Config]] = Seq(),
basePlatformConfig: BasePlatformConfig = BaseConfigs.F1,
@ -19,7 +20,7 @@ abstract class AutoCounterSuite(
/** Compares an AutoCounter output CSV against a reference generated using in-circuit printfs.
*/
def checkAutoCounterCSV(filename: String, stdoutPrefix: String) {
def checkAutoCounterCSV(backend: String, filename: String, stdoutPrefix: String) {
it should s"produce a csv file (${filename}) that matches in-circuit printf output" in {
val scrubWhitespace = raw"\s*(.*)\s*".r
def splitAtCommas(s: String) = {
@ -32,7 +33,7 @@ abstract class AutoCounterSuite(
.map(scrubWhitespace.findFirstMatchIn(_).get.group(1))
}
val refLogFile = new File(outDir, s"/${targetName}.${backendSimulator}.out")
val refLogFile = new File(outDir, s"/${targetName}.${backend}.out")
val acFile = new File(genDir, s"/${filename}")
val refVersion :: refClockInfo :: refLabelLine :: refDescLine :: refOutput =
@ -65,35 +66,54 @@ abstract class AutoCounterSuite(
}
}
}
override def defineTests(backend: String, debug: Boolean) {
it should "run in the simulator" in {
assert(run(backend, debug, args = simulationArgs) == 0)
}
for ((filename, prefix) <- checks) {
checkAutoCounterCSV(backend, filename, prefix)
}
}
}
class AutoCounterModuleF1Test
extends AutoCounterSuite(
"AutoCounterModule",
Seq(("autocounter0.csv", "AUTOCOUNTER_PRINT ")),
simulationArgs = Seq("+autocounter-readrate=1000", "+autocounter-filename-base=autocounter"),
) {
checkAutoCounterCSV("autocounter0.csv", "AUTOCOUNTER_PRINT ")
}
)
class AutoCounter32bRolloverTest
extends AutoCounterSuite(
"AutoCounter32bRollover",
Seq(("autocounter0.csv", "AUTOCOUNTER_PRINT ")),
simulationArgs = Seq("+autocounter-readrate=1000", "+autocounter-filename-base=autocounter"),
) {
checkAutoCounterCSV("autocounter0.csv", "AUTOCOUNTER_PRINT ")
}
)
class AutoCounterCoverModuleF1Test
extends AutoCounterSuite(
"AutoCounterCoverModule",
Seq(("autocounter0.csv", "AUTOCOUNTER_PRINT ")),
simulationArgs = Seq("+autocounter-readrate=1000", "+autocounter-filename-base=autocounter"),
) {
checkAutoCounterCSV("autocounter0.csv", "AUTOCOUNTER_PRINT ")
}
)
class MulticlockAutoCounterF1Test
extends AutoCounterSuite(
"MulticlockAutoCounterModule",
Seq(
("autocounter0.csv", "AUTOCOUNTER_PRINT "),
("autocounter1.csv", "AUTOCOUNTER_PRINT_SLOWCLOCK "),
),
simulationArgs = Seq("+autocounter-readrate=1000", "+autocounter-filename-base=autocounter"),
)
class AutoCounterGlobalResetConditionF1Test
extends AutoCounterSuite(
extends TutorialSuite(
"AutoCounterGlobalResetCondition",
simulationArgs = Seq("+autocounter-readrate=1000", "+autocounter-filename-base=autocounter"),
simulationArgs = Seq("+autocounter-readrate=1000", "+autocounter-filename-base=autocounter"),
) {
def assertCountsAreZero(filename: String, clockDivision: Int) {
s"Counts reported in ${filename}" should "always be zero" in {
val log = new File(genDir, s"/${filename}")
@ -110,17 +130,14 @@ class AutoCounterGlobalResetConditionF1Test
}
}
}
assertCountsAreZero("autocounter0.csv", clockDivision = 1)
assertCountsAreZero("autocounter1.csv", clockDivision = 2)
}
class MulticlockAutoCounterF1Test
extends AutoCounterSuite(
"MulticlockAutoCounterModule",
simulationArgs = Seq("+autocounter-readrate=1000", "+autocounter-filename-base=autocounter"),
) {
checkAutoCounterCSV("autocounter0.csv", "AUTOCOUNTER_PRINT ")
checkAutoCounterCSV("autocounter1.csv", "AUTOCOUNTER_PRINT_SLOWCLOCK ")
override def defineTests(backend: String, debug: Boolean) {
it should "run in the simulator" in {
assert(run(backend, debug, args = simulationArgs) == 0)
}
assertCountsAreZero("autocounter0.csv", clockDivision = 1)
assertCountsAreZero("autocounter1.csv", clockDivision = 2)
}
}
class AutoCounterCITests

View File

@ -10,35 +10,35 @@ class GCDF1Test extends TutorialSuite("GCD", basePlatformConfig = BaseConfigs
class GCDVitisTest extends TutorialSuite("GCD", basePlatformConfig = BaseConfigs.Vitis)
// Hijack Parity to test all of the Midas-level backends
class ParityF1Test extends TutorialSuite("Parity", basePlatformConfig = BaseConfigs.F1) {
runTest("verilator", true)
runTest("vcs", true)
}
class ParityVitisTest extends TutorialSuite("Parity", basePlatformConfig = BaseConfigs.Vitis) {
runTest("verilator", true)
runTest("vcs", true)
}
class ParityF1Test extends TutorialSuite("Parity", basePlatformConfig = BaseConfigs.F1)
class ParityVitisTest extends TutorialSuite("Parity", basePlatformConfig = BaseConfigs.Vitis)
class CustomConstraintsF1Test extends TutorialSuite("CustomConstraints") {
def readLines(filename: String): List[String] = {
val file = new File(genDir, s"/${filename}")
Source.fromFile(file).getLines.toList
}
it should s"generate synthesis XDC file" in {
val xdc = readLines("FireSim-generated.synthesis.xdc")
xdc should contain("constrain_synth1")
(atLeast(1, xdc) should fullyMatch).regex("constrain_synth2 \\[reg firesim_top/.*/dut/r0\\]".r)
}
it should s"generate implementation XDC file" in {
val xdc = readLines("FireSim-generated.implementation.xdc")
xdc should contain("constrain_impl1")
(atLeast(1, xdc) should fullyMatch).regex("constrain_impl2 \\[reg WRAPPER_INST/CL/firesim_top/.*/dut/r1]".r)
override def defineTests(backend: String, debug: Boolean) {
def readLines(filename: String): List[String] = {
val file = new File(genDir, s"/${filename}")
Source.fromFile(file).getLines.toList
}
it should s"generate synthesis XDC file" in {
val xdc = readLines("FireSim-generated.synthesis.xdc")
xdc should contain("constrain_synth1")
(atLeast(1, xdc) should fullyMatch).regex("constrain_synth2 \\[reg firesim_top/.*/dut/r0\\]".r)
}
it should s"generate implementation XDC file" in {
val xdc = readLines("FireSim-generated.implementation.xdc")
xdc should contain("constrain_impl1")
(atLeast(1, xdc) should fullyMatch).regex("constrain_impl2 \\[reg WRAPPER_INST/CL/firesim_top/.*/dut/r1]".r)
}
}
}
class TerminationF1Test extends TutorialSuite("TerminationModule") {
(1 to 10).foreach { x =>
runTest(backendSimulator, args = Seq("+termination-bridge-tick-rate=10", s"+fuzz-seed=${x}"), shouldPass = true)
override def defineTests(backend: String, debug: Boolean) {
(1 to 10).foreach { x =>
val args = Seq("+termination-bridge-tick-rate=10", s"+fuzz-seed=${x}")
assert(run(backend, debug, args = args) == 0)
}
}
}

View File

@ -11,15 +11,20 @@ import firesim.BasePlatformConfig
abstract class FMRSuite(
targetName: String,
expectedValue: Double,
error: Double = 0.0,
targetConfigs: String = "NoConfig",
platformConfigs: Seq[Class[_ <: Config]] = Seq(),
basePlatformConfig: BasePlatformConfig = BaseConfigs.F1,
simulationArgs: Seq[String] = Seq(),
) extends TutorialSuite(targetName, targetConfigs, platformConfigs, basePlatformConfig, simulationArgs) {
// Checks that a bridge generated log in ${genDir}/${synthLog} is empty
def expectedFMR(expectedValue: Double, error: Double = 0.0) {
override def defineTests(backend: String, debug: Boolean) {
it should "run in the simulator" in {
assert(run(backend, debug, args = simulationArgs) == 0)
}
it should s"run with an FMR between ${expectedValue - error} and ${expectedValue + error}" in {
val verilatedLogFile = new File(outDir, s"/${targetName}.${backendSimulator}.out")
val verilatedLogFile = new File(outDir, s"/${targetName}.${backend}.out")
val lines = Source.fromFile(verilatedLogFile).getLines.toList.reverse
val fmrRegex = raw"^FMR: (\d*\.\d*)".r
val fmr = lines.collectFirst { case fmrRegex(value) =>
@ -32,28 +37,21 @@ abstract class FMRSuite(
}
}
class MultiRegfileFMRF1Test extends FMRSuite("MultiRegfileFMR") {
class MultiRegfileFMRF1Test extends FMRSuite("MultiRegfileFMR", expectedValue = 2.0 * MultiRegfile.nCopiesToTime) {
// A threaded model that relies on another model to implement an internal
// combinational path (like an extracted memory model) will only simulate
// one target thread-cycle every two host cycles.
expectedFMR(2.0 * MultiRegfile.nCopiesToTime)
}
class MultiSRAMFMRF1Test extends FMRSuite("MultiSRAMFMR") {
expectedFMR(MultiRegfile.nCopiesToTime) // No comb paths -> 1:1
class MultiSRAMFMRF1Test extends FMRSuite("MultiSRAMFMR", expectedValue = MultiRegfile.nCopiesToTime) {
// No comb paths -> 1:1
}
class PassthroughModelTest extends FMRSuite("PassthroughModel") {
expectedFMR(2.0)
}
class PassthroughModelTest extends FMRSuite("PassthroughModel", expectedValue = 2.0)
class PassthroughModelNestedTest extends FMRSuite("PassthroughModelNested") {
expectedFMR(2.0)
}
class PassthroughModelNestedTest extends FMRSuite("PassthroughModelNested", expectedValue = 2.0)
class PassthroughModelBridgeSourceTest extends FMRSuite("PassthroughModelBridgeSource") {
expectedFMR(1.0)
}
class PassthroughModelBridgeSourceTest extends FMRSuite("PassthroughModelBridgeSource", expectedValue = 1.0)
class FMRCITests
extends Suites(

View File

@ -5,10 +5,7 @@ package firesim.midasexamples
import org.scalatest.Suites
class WireInterconnectF1Test extends TutorialSuite("WireInterconnect")
class TrivialMulticlockF1Test extends TutorialSuite("TrivialMulticlock") {
runTest("verilator", true)
runTest("vcs", true)
}
class TrivialMulticlockF1Test extends TutorialSuite("TrivialMulticlock")
class TriggerWiringModuleF1Test extends TutorialSuite("TriggerWiringModule")

View File

@ -15,31 +15,9 @@ import firesim.{BasePlatformConfig, TestSuiteCommon}
abstract class LoadMemTest(
override val basePlatformConfig: BasePlatformConfig
) extends TestSuiteCommon("midasexamples")
) extends TutorialSuite("LoadMemModule", platformConfigs = Seq(classOf[NoSynthAsserts]))
with Matchers {
override def targetConfigs = "NoConfig"
override def targetName = "LoadMemModule"
override def platformConfigs = Seq(classOf[NoSynthAsserts])
def run(
backend: String,
debug: Boolean = false,
logFile: Option[File] = None,
waveform: Option[File] = None,
args: Seq[String] = Nil,
) = {
val makeArgs = Seq(
s"run-$backend%s".format(if (debug) "-debug" else ""),
"LOGFILE=%s".format(logFile.map(toStr).getOrElse("")),
"WAVEFORM=%s".format(waveform.map(toStr).getOrElse("")),
"ARGS=%s".format(args.mkString(" ")),
)
if (isCmdAvailable(backend)) {
make(makeArgs: _*)
} else 0
}
/** Helper to generate tests strings.
*/
def getTestInput(length: Int): String = {
@ -59,50 +37,33 @@ abstract class LoadMemTest(
* @param debug
* When true, captures waves from the simulation
*/
def runTest(backend: String, debug: Boolean) {
// Generate a random string spanning 2 sectors with a fixed seed.
val numLines = 128
val data = getTestInput(numLines)
override def defineTests(backend: String, debug: Boolean) {
it should "read data provided by LoadMem" in {
// Generate a random string spanning 2 sectors with a fixed seed.
val numLines = 128
val data = getTestInput(numLines)
// Create an input file.
val input = File.createTempFile("input", ".txt")
//input.deleteOnExit()
val inputWriter = new BufferedWriter(new FileWriter(input))
inputWriter.write(data)
inputWriter.flush()
inputWriter.close()
// Create an input file.
val input = File.createTempFile("input", ".txt")
input.deleteOnExit()
val inputWriter = new BufferedWriter(new FileWriter(input))
inputWriter.write(data)
inputWriter.flush()
inputWriter.close()
// Pre-allocate space in the output.
val output = File.createTempFile("output", ".txt")
//output.deleteOnExit()
val outputWriter = new BufferedWriter(new FileWriter(output))
for (i <- 1 to data.size) {
outputWriter.write('x')
}
outputWriter.flush()
outputWriter.close()
// Create an output file.
val output = File.createTempFile("output", ".txt")
val runResult =
run(backend, debug, args = Seq(s"+n=${numLines} +loadmem=${input.getPath}", s"+test-dump-file=${output.getPath}"))
assert(runResult == 0)
val result = scala.io.Source.fromFile(output.getPath).mkString
result should equal(data + "\n")
}
mkdirs()
behavior.of(s"$targetName")
elaborateAndCompile()
for (backend <- Seq("vcs", "verilator")) {
compileMlSimulator(backend)
val testEnvStr = s"pass in ${backend} MIDAS-level simulation"
if (isCmdAvailable(backend)) {
it should testEnvStr in {
runTest(backend, false)
}
} else {
ignore should testEnvStr in {}
// Run the test and compare the two files.
assert(
run(
backend,
debug,
args = Seq(s"+n=${numLines} +loadmem=${input.getPath}", s"+test-dump-file=${output.getPath}"),
) == 0
)
val result = scala.io.Source.fromFile(output.getPath).mkString
result should equal(data + "\n")
}
}
}

View File

@ -14,29 +14,34 @@ trait PlusArgsKey {
class PlusArgsGroup68Bit
extends TutorialSuite("PlusArgsModule", "PlusArgsModuleTestConfigGroup68Bit")
with PlusArgsKey {
it should "provide the correct default value, 3 slice" in {
assert(run("verilator", false, args = Seq(getKey(0, 0))) == 0)
}
it should "accept an int from the command line" in {
assert(run("verilator", false, args = Seq(s"+plusar_v=3", getKey(0, 1))) == 0)
assert(run("verilator", false, args = Seq(s"+plusar_v=${BigInt("f00000000", 16)}", getKey(0, 2))) == 0)
assert(run("verilator", false, args = Seq(s"+plusar_v=${BigInt("f0000000000000000", 16)}", getKey(0, 3))) == 0)
}
override def defineTests(backend: String, debug: Boolean) {
it should "provide the correct default value, 3 slice" in {
assert(run(backend, debug, args = Seq(getKey(0, 0))) == 0)
}
it should "reject large runtime values" in {
assert(run("verilator", false, args = Seq(s"+plusar_v=${BigInt("ff0000000000000000", 16)}", getKey(0, 4))) != 0)
it should "accept an int from the command line" in {
assert(run(backend, debug, args = Seq(s"+plusar_v=3", getKey(0, 1))) == 0)
assert(run(backend, debug, args = Seq(s"+plusar_v=${BigInt("f00000000", 16)}", getKey(0, 2))) == 0)
assert(run(backend, debug, args = Seq(s"+plusar_v=${BigInt("f0000000000000000", 16)}", getKey(0, 3))) == 0)
}
it should "reject large runtime values" in {
assert(run(backend, debug, args = Seq(s"+plusar_v=${BigInt("ff0000000000000000", 16)}", getKey(0, 4))) != 0)
}
}
}
class PlusArgsGroup29Bit
extends TutorialSuite("PlusArgsModule", "PlusArgsModuleTestConfigGroup29Bit")
with PlusArgsKey {
it should "provide the correct default value, 1 slice" in {
assert(run("verilator", false, args = Seq(getKey(1, 0))) == 0)
}
override def defineTests(backend: String, debug: Boolean) {
it should "provide the correct default value, 1 slice" in {
assert(run(backend, debug, args = Seq(getKey(1, 0))) == 0)
}
it should "accept an int from the command line, 1 slice" in {
assert(run("verilator", false, args = Seq(s"+plusar_v=${BigInt("1eadbeef", 16)}", getKey(1, 1))) == 0)
it should "accept an int from the command line, 1 slice" in {
assert(run(backend, debug, args = Seq(s"+plusar_v=${BigInt("1eadbeef", 16)}", getKey(1, 1))) == 0)
}
}
}

View File

@ -21,19 +21,17 @@ abstract class PrintfSuite(
* target-side cycle prefix
*/
def checkPrintCycles(filename: String, startCycle: Int, endCycle: Int, linesPerCycle: Int) {
it should "have synthesized printfs in the desired cycles" in {
val synthLogFile = new File(genDir, s"/${filename}")
val synthPrintOutput = extractLines(synthLogFile, prefix = "")
val length = synthPrintOutput.size
assert(length == linesPerCycle * (endCycle - startCycle + 1))
for ((line, idx) <- synthPrintOutput.zipWithIndex) {
val currentCycle = idx / linesPerCycle + startCycle
val printRegex = raw"^CYCLE:\s*(\d*) SYNTHESIZED_PRINT CYCLE:\s*(\d*).*".r
line match {
case printRegex(cycleA, cycleB) =>
assert(cycleA.toInt == currentCycle)
assert(cycleB.toInt == currentCycle)
}
val synthLogFile = new File(genDir, s"/${filename}")
val synthPrintOutput = extractLines(synthLogFile, prefix = "")
val length = synthPrintOutput.size
assert(length == linesPerCycle * (endCycle - startCycle + 1))
for ((line, idx) <- synthPrintOutput.zipWithIndex) {
val currentCycle = idx / linesPerCycle + startCycle
val printRegex = raw"^CYCLE:\s*(\d*) SYNTHESIZED_PRINT CYCLE:\s*(\d*).*".r
line match {
case printRegex(cycleA, cycleB) =>
assert(cycleA.toInt == currentCycle)
assert(cycleB.toInt == currentCycle)
}
}
}
@ -41,26 +39,27 @@ abstract class PrintfSuite(
// Checks that a bridge generated log in ${genDir}/${synthLog} matches output
// generated directly by the RTL simulator (usually with printfs)
def diffSynthesizedLog(
backend: String,
synthLog: String,
stdoutPrefix: String = "SYNTHESIZED_PRINT ",
synthPrefix: String = "SYNTHESIZED_PRINT ",
synthLinesToDrop: Int = 0,
) {
behavior.of(s"${synthLog}")
it should "match the prints generated by the verilated design" in {
val verilatedLogFile = new File(outDir, s"/${targetName}.${backendSimulator}.out")
val synthLogFile = new File(genDir, s"/${synthLog}")
val verilatedOutput = extractLines(verilatedLogFile, stdoutPrefix).sorted
val synthPrintOutput = extractLines(synthLogFile, synthPrefix, synthLinesToDrop).sorted
diffLines(verilatedOutput, synthPrintOutput)
}
val verilatedLogFile = new File(outDir, s"/${targetName}.${backend}.out")
val synthLogFile = new File(genDir, s"/${synthLog}")
val verilatedOutput = extractLines(verilatedLogFile, stdoutPrefix).sorted
val synthPrintOutput = extractLines(synthLogFile, synthPrefix, synthLinesToDrop).sorted
diffLines(verilatedOutput, synthPrintOutput)
}
def assertSynthesizedLogEmpty(synthLog: String) {
s"${synthLog}" should "be empty" in {
val synthLogFile = new File(genDir, s"/${synthLog}")
val lines = extractLines(synthLogFile, prefix = "")
assert(lines.isEmpty)
def addChecks(backend: String): Unit
override def defineTests(backend: String, debug: Boolean) {
it should "run in the simulator" in {
assert(run(backend, debug, args = simulationArgs) == 0)
}
it should "should produce the expected output" in {
addChecks(backend)
}
}
}
@ -71,8 +70,9 @@ abstract class PrintModuleTest(val platform: BasePlatformConfig)
simulationArgs = Seq("+print-no-cycle-prefix", "+print-file=synthprinttest.out"),
basePlatformConfig = platform,
) {
runTest("vcs", true)
diffSynthesizedLog("synthprinttest.out0")
override def addChecks(backend: String) {
diffSynthesizedLog(backend, "synthprinttest.out0")
}
}
class PrintfModuleF1Test extends PrintModuleTest(BaseConfigs.F1)
@ -84,7 +84,9 @@ abstract class NarrowPrintfModuleTest(val platform: BasePlatformConfig)
simulationArgs = Seq("+print-no-cycle-prefix", "+print-file=synthprinttest.out"),
basePlatformConfig = platform,
) {
diffSynthesizedLog("synthprinttest.out0")
override def addChecks(backend: String) {
diffSynthesizedLog(backend, "synthprinttest.out0")
}
}
class NarrowPrintfModuleF1Test extends NarrowPrintfModuleTest(BaseConfigs.F1)
@ -96,58 +98,79 @@ abstract class MulticlockPrintfModuleTest(val platform: BasePlatformConfig)
simulationArgs = Seq("+print-file=synthprinttest.out", "+print-no-cycle-prefix"),
basePlatformConfig = platform,
) {
diffSynthesizedLog("synthprinttest.out0")
diffSynthesizedLog(
"synthprinttest.out1",
stdoutPrefix = "SYNTHESIZED_PRINT_HALFRATE ",
synthPrefix = "SYNTHESIZED_PRINT_HALFRATE ",
// Corresponds to a single cycle of extra output.
synthLinesToDrop = 4,
)
override def addChecks(backend: String) {
diffSynthesizedLog(backend, "synthprinttest.out0")
diffSynthesizedLog(
backend,
"synthprinttest.out1",
stdoutPrefix = "SYNTHESIZED_PRINT_HALFRATE ",
synthPrefix = "SYNTHESIZED_PRINT_HALFRATE ",
// Corresponds to a single cycle of extra output.
synthLinesToDrop = 4,
)
}
}
class MulticlockPrintF1Test extends MulticlockPrintfModuleTest(BaseConfigs.F1)
class MulticlockPrintVitisTest extends MulticlockPrintfModuleTest(BaseConfigs.Vitis)
class PrintfCycleBoundsF1Test extends PrintfCycleBoundsTestBase(startCycle = 172, endCycle = 9377)
class AutoCounterPrintfF1Test
extends PrintfSuite(
"AutoCounterPrintfModule",
simulationArgs = Seq("+print-file=synthprinttest.out"),
platformConfigs = Seq(classOf[AutoCounterPrintf]),
) {
override def addChecks(backend: String) {
diffSynthesizedLog(backend, "synthprinttest.out0", stdoutPrefix = "AUTOCOUNTER_PRINT CYCLE", synthPrefix = "CYCLE")
}
}
class TriggerPredicatedPrintfF1Test
extends PrintfSuite("TriggerPredicatedPrintf", simulationArgs = Seq("+print-file=synthprinttest.out"))
with TriggerPredicatedPrintfConsts {
val startCycle = assertTriggerCycle + 2
val endCycle = deassertTriggerCycle + 2
checkPrintCycles("synthprinttest.out0", startCycle, endCycle, linesPerCycle = 2)
extends PrintfSuite("TriggerPredicatedPrintf", simulationArgs = Seq("+print-file=synthprinttest.out")) {
override def addChecks(backend: String) {
import TriggerPredicatedPrintfConsts._
checkPrintCycles("synthprinttest.out0", assertTriggerCycle + 2, deassertTriggerCycle + 2, linesPerCycle = 2)
}
}
class PrintfCycleBoundsTestBase(startCycle: Int, endCycle: Int)
extends PrintfSuite(
"PrintfModule",
simulationArgs = Seq(
simulationArgs = Seq(
"+print-file=synthprinttest.out",
s"+print-start=${startCycle}",
s"+print-end=${endCycle}",
),
) {
checkPrintCycles("synthprinttest.out0", startCycle, endCycle, linesPerCycle = 4)
override def addChecks(backend: String) {
checkPrintCycles("synthprinttest.out0", startCycle, endCycle, linesPerCycle = 4)
}
}
class PrintfCycleBoundsF1Test extends PrintfCycleBoundsTestBase(startCycle = 172, endCycle = 9377)
class PrintfGlobalResetConditionTest
extends PrintfSuite(
extends TutorialSuite(
"PrintfGlobalResetCondition",
simulationArgs = Seq("+print-no-cycle-prefix", "+print-file=synthprinttest.out"),
) {
// The log should be empty.
assertSynthesizedLogEmpty("synthprinttest.out0")
assertSynthesizedLogEmpty("synthprinttest.out1")
}
class AutoCounterPrintfF1Test
extends PrintfSuite(
"AutoCounterPrintfModule",
simulationArgs = Seq("+print-file=synthprinttest.out"),
platformConfigs = Seq(classOf[AutoCounterPrintf]),
) {
diffSynthesizedLog("synthprinttest.out0", stdoutPrefix = "AUTOCOUNTER_PRINT CYCLE", synthPrefix = "CYCLE")
def assertSynthesizedLogEmpty(synthLog: String) {
s"${synthLog}" should "be empty" in {
val synthLogFile = new File(genDir, s"/${synthLog}")
val lines = extractLines(synthLogFile, prefix = "")
assert(lines.isEmpty)
}
}
override def defineTests(backend: String, debug: Boolean) {
it should "run in the simulator" in {
assert(run(backend, debug, args = simulationArgs) == 0)
}
// The log should be empty.
assertSynthesizedLogEmpty("synthprinttest.out0")
assertSynthesizedLogEmpty("synthprinttest.out1")
}
}
class PrintfSynthesisCITests

View File

@ -2,19 +2,24 @@
package firesim.midasexamples
import org.scalatest.Suites
class ResetPulseBridgeActiveHighTest
extends TutorialSuite(
"ResetPulseBridgeTest",
// Disable assertion synthesis to rely on native chisel assertions to catch bad behavior
platformConfigs = Seq(classOf[NoSynthAsserts]),
simulationArgs = Seq(s"+reset-pulse-length0=${ResetPulseBridgeTestConsts.maxPulseLength}"),
) {
runTest(
backendSimulator,
args = Seq(s"+reset-pulse-length0=${ResetPulseBridgeTestConsts.maxPulseLength + 1}"),
shouldPass = false,
)
}
)
class ResetPulseBridgeActiveHighFailTest
extends TutorialSuite(
"ResetPulseBridgeTest",
// Disable assertion synthesis to rely on native chisel assertions to catch bad behavior
platformConfigs = Seq(classOf[NoSynthAsserts]),
simulationArgs = Seq(s"+reset-pulse-length0=${ResetPulseBridgeTestConsts.maxPulseLength + 1}"),
shouldPass = false,
)
class ResetPulseBridgeActiveLowTest
extends TutorialSuite(
@ -22,10 +27,21 @@ class ResetPulseBridgeActiveLowTest
targetConfigs = "ResetPulseBridgeActiveLowConfig",
platformConfigs = Seq(classOf[NoSynthAsserts]),
simulationArgs = Seq(s"+reset-pulse-length0=${ResetPulseBridgeTestConsts.maxPulseLength}"),
) {
runTest(
backendSimulator,
args = Seq(s"+reset-pulse-length0=${ResetPulseBridgeTestConsts.maxPulseLength + 1}"),
shouldPass = false,
)
}
)
class ResetPulseBridgeActiveLowFailTest
extends TutorialSuite(
"ResetPulseBridgeTest",
targetConfigs = "ResetPulseBridgeActiveLowConfig",
platformConfigs = Seq(classOf[NoSynthAsserts]),
simulationArgs = Seq(s"+reset-pulse-length0=${ResetPulseBridgeTestConsts.maxPulseLength + 1}"),
shouldPass = false,
)
class ResetPulseBridgeTests
extends Suites(
new ResetPulseBridgeActiveHighTest,
new ResetPulseBridgeActiveHighFailTest,
new ResetPulseBridgeActiveLowTest,
new ResetPulseBridgeActiveLowFailTest,
)

View File

@ -9,94 +9,62 @@ import org.scalatest.Suites
import org.scalatest.matchers.should._
import freechips.rocketchip.config.Config
import firesim.{TestSuiteCommon, BasePlatformConfig}
import firesim.{BasePlatformConfig, TestSuiteCommon}
object BaseConfigs {
case object F1 extends BasePlatformConfig("f1", Seq(classOf[DefaultF1Config]))
case object F1 extends BasePlatformConfig("f1", Seq(classOf[DefaultF1Config]))
case object Vitis extends BasePlatformConfig("vitis", Seq(classOf[DefaultVitisConfig]))
}
abstract class TutorialSuite(
val targetName: String,
override val targetConfigs: String = "NoConfig",
override val platformConfigs: Seq[Class[_ <: Config]] = Seq(),
override val basePlatformConfig: BasePlatformConfig = BaseConfigs.F1,
val simulationArgs: Seq[String] = Seq()
) extends TestSuiteCommon("midasexamples") with Matchers {
val targetName: String,
override val targetConfigs: String = "NoConfig",
override val platformConfigs: Seq[Class[_ <: Config]] = Seq(),
override val basePlatformConfig: BasePlatformConfig = BaseConfigs.F1,
val simulationArgs: Seq[String] = Seq(),
val shouldPass: Boolean = true,
) extends TestSuiteCommon("midasexamples")
with Matchers {
val backendSimulator = "verilator"
def run(backend: String,
debug: Boolean = false,
logFile: Option[File] = None,
waveform: Option[File] = None,
args: Seq[String] = Nil) = {
val makeArgs = Seq(
s"run-$backend%s".format(if (debug) "-debug" else ""),
"LOGFILE=%s".format(logFile map toStr getOrElse ""),
"WAVEFORM=%s".format(waveform map toStr getOrElse ""),
"ARGS=%s".format(args mkString " "))
if (isCmdAvailable(backend)) {
make(makeArgs:_*)
} else 0
}
/**
* Runs MIDAS-level simulation on the design.
*
* @param b Backend simulator: "verilator" or "vcs"
* @param debug When true, captures waves from the simulation
* @param args A seq of PlusArgs to pass to the simulator.
* @param shouldPass When false, asserts the test returns a non-zero code
*/
def runTest(b: String, debug: Boolean = false, args: Seq[String] = simulationArgs, shouldPass: Boolean = true) {
val prefix = if (shouldPass) "pass in " else "fail in "
val testEnvStr = s"${b} MIDAS-level simulation"
override def defineTests(backend: String, debug: Boolean) {
val prefix = if (shouldPass) "pass in " else "fail "
val wavesStr = if (debug) " with waves enabled" else ""
val argStr = " with args: " + args.mkString(" ")
val argStr = " with args: " + simulationArgs.mkString(" ")
val haveThisBehavior = prefix + testEnvStr + wavesStr + argStr
val haveThisBehavior = prefix + wavesStr + argStr
if (isCmdAvailable(b)) {
it should haveThisBehavior in {
assert((run(b, debug, args = args) == 0) == shouldPass)
}
} else {
ignore should haveThisBehavior in { }
it should haveThisBehavior in {
assert((run(backend, debug, args = simulationArgs) == 0) == shouldPass)
}
}
mkdirs()
behavior of s"$targetName"
elaborateAndCompile()
compileMlSimulator(backendSimulator)
runTest(backendSimulator)
}
class VitisCITests extends Suites (
new GCDVitisTest,
new ParityVitisTest,
new PrintfModuleVitisTest,
new MulticlockPrintVitisTest,
)
class VitisCITests
extends Suites(
new GCDVitisTest,
new ParityVitisTest,
new PrintfModuleVitisTest,
new MulticlockPrintVitisTest,
)
// These groups are vestigial from CircleCI container limits
class CIGroupA extends Suites(
new ChiselExampleDesigns,
new PrintfSynthesisCITests,
new firesim.fasedtests.CIGroupA,
new AutoCounterCITests,
new ResetPulseBridgeActiveHighTest,
new ResetPulseBridgeActiveLowTest,
)
class CIGroupA
extends Suites(
new ChiselExampleDesigns,
new PrintfSynthesisCITests,
new firesim.fasedtests.CIGroupA,
new AutoCounterCITests,
new ResetPulseBridgeActiveHighTest,
new ResetPulseBridgeActiveLowTest,
)
class CIGroupB extends Suites(
new AssertionSynthesisCITests,
new GoldenGateMiscCITests,
new firesim.fasedtests.CIGroupB,
new firesim.AllMidasUnitTests,
new firesim.FailingUnitTests,
new FMRCITests,
new VitisCITests
)
class CIGroupB
extends Suites(
new AssertionSynthesisCITests,
new GoldenGateMiscCITests,
new firesim.fasedtests.CIGroupB,
new firesim.AllMidasUnitTests,
new firesim.FailingUnitTests,
new FMRCITests,
new VitisCITests,
)