From 479526a0fbe4cf5a21f10f491d940f1dc3627f2e Mon Sep 17 00:00:00 2001 From: Albin Chaboissier Date: Fri, 7 Nov 2025 12:02:17 +0100 Subject: [PATCH] Adds parallel testing --- Makefile | 3 +- build.sc | 52 ++++++----- src/test/scala/projet/ZzTopAllSpec.scala | 109 +++++++++++++++++++++++ 3 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 src/test/scala/projet/ZzTopAllSpec.scala diff --git a/Makefile b/Makefile index 8585b46..6569419 100644 --- a/Makefile +++ b/Makefile @@ -98,7 +98,8 @@ mill: autotest: mill test-bench/mem/tests/$(PROG).mem ##! Lance la simulation automatique pour tous les tests ou juste celui fourni dans PROG -testall: mill $(MEM_TESTS) +testall: mill compile + @PROOT=$(PWD) ./mill TPchisel.test.testOnly $(TOP_REP).ZzTopAllSpec simulation: compile ##! Lance gtkwave sur le test fourni dans PROG @$(call check_vars,PROG) diff --git a/build.sc b/build.sc index 84d50fe..38b5000 100644 --- a/build.sc +++ b/build.sc @@ -14,28 +14,34 @@ import mill.bsp._ // See: https://github.com/com-lihaoyi/mill/issues/3840 object `TPchisel` extends SbtModule { m => - override def millSourcePath = super.millSourcePath / os.up - override def scalaVersion = "2.13.16" - override def scalacOptions = Seq( - "-language:reflectiveCalls", - "-deprecation", - "-feature", - "-Xcheckinit", - "-Ymacro-annotations", - "-unchecked", - "-Xfatal-warnings", - "-Ywarn-dead-code", - "-Ywarn-unused" - ) - override def ivyDeps = Agg( - ivy"org.chipsalliance::chisel:7.2.0", - ) - override def scalacPluginIvyDeps = Agg( - ivy"org.chipsalliance:::chisel-plugin:7.2.0", - ) - object test extends SbtTests with TestModule.ScalaTest { - override def ivyDeps = m.ivyDeps() ++ Agg( - ivy"org.scalatest::scalatest::3.2.19" + override def millSourcePath = super.millSourcePath / os.up + override def scalaVersion = "2.13.16" + override def scalacOptions = Seq( + "-language:reflectiveCalls", + "-deprecation", + "-feature", + "-Xcheckinit", + "-Ymacro-annotations", + "-unchecked", + "-Xfatal-warnings", + "-Ywarn-dead-code", + "-Ywarn-unused" ) - } + override def ivyDeps = Agg( + ivy"org.chipsalliance::chisel:7.2.0" + ) + override def scalacPluginIvyDeps = Agg( + ivy"org.chipsalliance:::chisel-plugin:7.2.0" + ) + object test extends SbtTests with TestModule.ScalaTest { + override def ivyDeps = m.ivyDeps() ++ Agg( + ivy"org.scalatest::scalatest::3.2.19" + ) + + override def forkArgs = super.forkArgs() ++ Seq( + "-Dscalatest.parallel.enabled=true", + "-Dscalatest.parallel.mode=concurrent", + "-Dscalatest.parallel.config.fixed.pool.size=12" + ) + } } diff --git a/src/test/scala/projet/ZzTopAllSpec.scala b/src/test/scala/projet/ZzTopAllSpec.scala new file mode 100644 index 0000000..961e603 --- /dev/null +++ b/src/test/scala/projet/ZzTopAllSpec.scala @@ -0,0 +1,109 @@ +package projet + +import chisel3.simulator.scalatest.ChiselSim +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.must.Matchers +import java.nio.file.{Files, Paths} +import scala.jdk.CollectionConverters._ +import scala.io.Source +import scala.collection.mutable.ListBuffer +import java.nio.file.FileSystems +import java.nio.file.Path +import org.scalatest.ParallelTestExecution + +class ZzTopAllSpec + extends AnyFreeSpec + with Matchers + with ParallelTestExecution + with ChiselSim { + + def expectedValuesFromAsm(path: String): Seq[BigInt] = { + val line = + Source.fromFile(path).getLines().find(_.startsWith("# expected:")) + line match { + case Some(l) => + l.stripPrefix("# expected:") + .split(",") + .map(_.trim) + .filter(_.nonEmpty) + .map(str => BigInt(str, 16)) + .toSeq + case None => + Seq.empty + } + } + + def failTrace(msg: String = "Erreur", trace: ListBuffer[String]): Unit = { + val prefix = s"🛑 $msg\n" + val traceStr = + if (trace.nonEmpty) + "Trace:\n" + trace.map(" - " + _).mkString("\n") + "\n" + else "" + fail(prefix + traceStr) + } + + def expectNextValue( + dut: ZzTop, + expected: BigInt, + timeout: Int, + trace: ListBuffer[String] + ): Unit = { + var cycles = 0 + dut.clock.step(12) // Attente d'un cycle proc (12 cycles systeme) + // Attendre une écriture dans x31 + while (!dut.io.valid_x31.get.peek().litToBoolean && cycles < timeout) { + dut.clock.step(12) // Attente d'un cycle proc + cycles += 1 + trace += f"Attente $cycles cycles" + } + + if (cycles < timeout) { + // Vérifier la nouvelle valeur + val got = dut.io.x31.get.peek().litValue + trace += f"Valeur attendue = 0x$expected%08X, reçue = 0x$got%08X" + if (got != expected) failTrace("Mauvaise sortie", trace) + } else { + trace += f"Valeur attendue = 0x$expected%08X, reçue = ⏰" + failTrace("Simulation bloquée", trace) + } + } + + // Fonction de test commune + def testWith(file: String): Unit = { + val path = file.replace("/mem/", "/").replace(".mem", ".s") + if (Files.exists(Paths.get(path))) { + val expected = expectedValuesFromAsm(path) + val test_name = file.hashCode(); + + simulate( + new ZzTop(file, true), + subdirectory = Some(test_name.toString()) + ) { dut => + val trace = ListBuffer[String]() + expected.foreach { value => + expectNextValue(dut, value, timeout = 100, trace) + } + } + } + } + + val dir = + FileSystems.getDefault().getPath(sys.env("PROOT") + "/bench/mem/tests") + // val suffix = ".mem" + + // val path = dir + name + suffix + // print(path) + + var tests = Files + .walk(dir) + .iterator() + .asScala + .filter(path => path.getFileName().toString().split('.').last == "mem") + .toArray[Path]; + + for (t <- tests) { + s"Sim ${t.toString()}" in { + testWith(t.toString()); + } + } +}