diff --git a/.gitignore b/.gitignore index aebb3216d..8ad32011b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ __pycache__ test-results target .ccls-cache +t1-sim-result/ diff --git a/build.sc b/build.sc index ed236ca3a..6a576625a 100644 --- a/build.sc +++ b/build.sc @@ -17,18 +17,18 @@ import $file.dependencies.rvdecoderdb.common import $file.common object v { - val scala = "2.13.14" + val scala = "2.13.14" val mainargs = ivy"com.lihaoyi::mainargs:0.5.0" - val oslib = ivy"com.lihaoyi::os-lib:0.9.1" - val upickle = ivy"com.lihaoyi::upickle:3.3.1" - val spire = ivy"org.typelevel::spire:latest.integration" + val oslib = ivy"com.lihaoyi::os-lib:0.9.1" + val upickle = ivy"com.lihaoyi::upickle:3.3.1" + val spire = ivy"org.typelevel::spire:latest.integration" val evilplot = ivy"io.github.cibotech::evilplot:latest.integration" } object chisel extends Chisel trait Chisel extends millbuild.dependencies.chisel.build.Chisel { - def crossValue = v.scala + def crossValue = v.scala override def millSourcePath = os.pwd / "dependencies" / "chisel" } @@ -36,11 +36,11 @@ object arithmetic extends Arithmetic trait Arithmetic extends millbuild.dependencies.arithmetic.common.ArithmeticModule { override def millSourcePath = os.pwd / "dependencies" / "arithmetic" / "arithmetic" - def scalaVersion = T(v.scala) + def scalaVersion = T(v.scala) - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) - def chiselIvy = None + def chiselIvy = None def chiselPluginIvy = None def spireIvy: T[Dep] = v.spire @@ -51,13 +51,13 @@ object axi4 extends AXI4 trait AXI4 extends millbuild.dependencies.`chisel-interface`.common.AXI4Module { override def millSourcePath = os.pwd / "dependencies" / "chisel-interface" / "axi4" - def scalaVersion = v.scala + def scalaVersion = v.scala def mainargsIvy = v.mainargs - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) - def chiselIvy = None + def chiselIvy = None def chiselPluginIvy = None } @@ -65,20 +65,20 @@ object hardfloat extends Hardfloat trait Hardfloat extends millbuild.dependencies.`berkeley-hardfloat`.common.HardfloatModule { override def millSourcePath = os.pwd / "dependencies" / "berkeley-hardfloat" / "hardfloat" - def scalaVersion = T(v.scala) + def scalaVersion = T(v.scala) - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) - def chiselIvy = None + def chiselIvy = None def chiselPluginIvy = None } object rvdecoderdb extends RVDecoderDB trait RVDecoderDB extends millbuild.dependencies.rvdecoderdb.common.RVDecoderDBJVMModule with ScalaModule { - def scalaVersion = T(v.scala) - def osLibIvy = v.oslib - def upickleIvy = v.upickle + def scalaVersion = T(v.scala) + def osLibIvy = v.oslib + def upickleIvy = v.upickle override def millSourcePath = os.pwd / "dependencies" / "rvdecoderdb" / "rvdecoderdb" } @@ -87,15 +87,15 @@ object t1 extends T1 trait T1 extends millbuild.common.T1Module with ScalafmtModule { def scalaVersion = T(v.scala) - def arithmeticModule = arithmetic - def axi4Module = axi4 - def hardfloatModule = hardfloat + def arithmeticModule = arithmetic + def axi4Module = axi4 + def hardfloatModule = hardfloat def rvdecoderdbModule = rvdecoderdb - def riscvOpcodesPath = T.input(PathRef(os.pwd / "dependencies" / "riscv-opcodes")) + def riscvOpcodesPath = T.input(PathRef(os.pwd / "dependencies" / "riscv-opcodes")) - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) - def chiselIvy = None + def chiselIvy = None def chiselPluginIvy = None } @@ -112,16 +112,16 @@ trait ConfigGen extends millbuild.common.ConfigGenModule with ScalafmtModule { object rocketv extends RocketV trait RocketV extends millbuild.common.RocketVModule with ScalafmtModule { - def scalaVersion = T(v.scala) + def scalaVersion = T(v.scala) def rvdecoderdbModule = rvdecoderdb - def riscvOpcodesPath = T.input(PathRef(os.pwd / "dependencies" / "riscv-opcodes")) - def hardfloatModule = hardfloat - def axi4Module = axi4 + def riscvOpcodesPath = T.input(PathRef(os.pwd / "dependencies" / "riscv-opcodes")) + def hardfloatModule = hardfloat + def axi4Module = axi4 - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) def chiselPluginIvy = None - def chiselIvy = None + def chiselIvy = None } object t1rocket extends T1Rocket @@ -129,12 +129,12 @@ object t1rocket extends T1Rocket trait T1Rocket extends millbuild.common.T1RocketModule with ScalafmtModule { def scalaVersion = T(v.scala) def rocketModule = rocketv - def t1Module = t1 + def t1Module = t1 - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) def chiselPluginIvy = None - def chiselIvy = None + def chiselIvy = None } object ipemu extends IPEmulator @@ -144,22 +144,22 @@ trait IPEmulator extends millbuild.common.IPEmulatorModule { def t1Module = t1 - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) def chiselPluginIvy = None - def chiselIvy = None + def chiselIvy = None } -object rocketemu extends RocketEmulator +object rocketemu extends RocketEmulator trait RocketEmulator extends millbuild.common.RocketEmulatorModule { def scalaVersion = T(v.scala) def rocketVModule = rocketv - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) def chiselPluginIvy = None - def chiselIvy = None + def chiselIvy = None } object t1rocketemu extends T1RocketEmulator @@ -169,10 +169,10 @@ trait T1RocketEmulator extends millbuild.common.T1RocketEmulatorModule { def t1rocketModule = t1rocket - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) def chiselPluginIvy = None - def chiselIvy = None + def chiselIvy = None } object panamaconverter extends PanamaConverter @@ -206,10 +206,10 @@ trait Elaborator extends millbuild.common.ElaboratorModule { def mainargsIvy = v.mainargs - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) def chiselPluginIvy = None - def chiselIvy = None + def chiselIvy = None } object omreaderlib extends OMReaderLib @@ -223,10 +223,10 @@ trait OMReaderLib extends millbuild.common.OMReaderLibModule { def mainargsIvy = v.mainargs - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) def chiselPluginIvy = None - def chiselIvy = None + def chiselIvy = None } object omreader extends OMReader @@ -235,34 +235,28 @@ trait OMReader extends millbuild.common.OMReaderModule { def scalaVersion = T(v.scala) def panamaconverterModule = panamaconverter - def omreaderlibModule = omreaderlib + def omreaderlibModule = omreaderlib def circtInstallPath = T.input(PathRef(os.Path(T.ctx().env("CIRCT_INSTALL_PATH")))) def mainargsIvy = v.mainargs - def chiselModule = Some(chisel) + def chiselModule = Some(chisel) def chiselPluginJar = T(Some(chisel.pluginModule.jar())) def chiselPluginIvy = None - def chiselIvy = None + def chiselIvy = None } -/** A simple release flow for T1 generator: - * package required dependency to flat jar. - * usage: - * mill t1package.{sourceJar,jar} - * out/t1package/sourceJar.dest/out.jar -> t1package-sources.jar - * out/t1package/jar.dest/out.jar -> t1package.jar - * out/t1package/chiselPluginJar.dest/out.jar -> chiselPlugin.jar - * these two jar is enough for this usages: - * object somepackagethatdependsont1 extends ScalaModule { - * def unmanagedClasspath = T(Seq(PathRef(os.pwd / "t1package.jar"), PathRef(os.pwd / "t1package-sources.jar"))) - * } - * For Jiuyang's Team, this is used for link T1 to NDA Blackboxes that cannot be open-sourced +/** A simple release flow for T1 generator: package required dependency to flat jar. usage: mill + * t1package.{sourceJar,jar} out/t1package/sourceJar.dest/out.jar -> t1package-sources.jar + * out/t1package/jar.dest/out.jar -> t1package.jar out/t1package/chiselPluginJar.dest/out.jar -> chiselPlugin.jar these + * two jar is enough for this usages: object somepackagethatdependsont1 extends ScalaModule { def unmanagedClasspath = + * T(Seq(PathRef(os.pwd / "t1package.jar"), PathRef(os.pwd / "t1package-sources.jar"))) } For Jiuyang's Team, this is + * used for link T1 to NDA Blackboxes that cannot be open-sourced */ object t1package extends ScalaModule { def scalaVersion = T(v.scala) - def moduleDeps = super.moduleDeps ++ Seq(t1, ipemu, panamaconverter, omreaderlib) + def moduleDeps = super.moduleDeps ++ Seq(t1, ipemu, panamaconverter, omreaderlib) override def sourceJar: T[PathRef] = T( Jvm.createJar( T.traverse(transitiveModuleDeps)(dep => T.sequence(Seq(dep.allSources, dep.resources, dep.compileResources)))() @@ -279,22 +273,3 @@ object t1package extends ScalaModule { PathRef(jar) } } - -trait ScriptModule extends ScalaModule { - val scala3 = "3.3.3" - val mainargs = ivy"com.lihaoyi::mainargs:0.5.0" - val oslib = ivy"com.lihaoyi::os-lib:0.10.0" - val upickle = ivy"com.lihaoyi::upickle:3.3.1" - - def scalaVersion = scala3 - def scalacOptions = Seq("-new-syntax") - override def ivyDeps = Agg(mainargs, oslib, upickle) - override def millSourcePath = os.pwd / "script" -} - -object emuHelper extends ScriptModule { - override def millSourcePath = super.millSourcePath / "emu" -} -object ciHelper extends ScriptModule { - override def millSourcePath = super.millSourcePath / "ci" -} diff --git a/nix/t1/mill-modules.nix b/nix/t1/mill-modules.nix index 5be6b53e6..44a8acf86 100644 --- a/nix/t1/mill-modules.nix +++ b/nix/t1/mill-modules.nix @@ -45,7 +45,7 @@ let ./../../common.sc ]; }; - millDepsHash = "sha256-Wkp2nLl6941F2ja3uQOpjB3S79vbDTAXStnDU1C6u3s="; + millDepsHash = "sha256-DQeKTqf+MES5ubORu+SMJEiqpOCXsS7VgxUnSqG12Bs="; nativeBuildInputs = [ dependencies.setupHook ]; }; diff --git a/nix/t1/omreader.nix b/nix/t1/omreader.nix index 03b0f8ea3..b51457f92 100644 --- a/nix/t1/omreader.nix +++ b/nix/t1/omreader.nix @@ -38,7 +38,7 @@ let ./../../common.sc ]; }; - millDepsHash = "sha256-Wkp2nLl6941F2ja3uQOpjB3S79vbDTAXStnDU1C6u3s="; + millDepsHash = "sha256-DQeKTqf+MES5ubORu+SMJEiqpOCXsS7VgxUnSqG12Bs="; nativeBuildInputs = [ dependencies.setupHook ]; }; diff --git a/script/build.sc b/script/build.sc new file mode 100644 index 000000000..ed06d5748 --- /dev/null +++ b/script/build.sc @@ -0,0 +1,22 @@ +import mill._ +import mill.scalalib._ +import mill.define.{Command, TaskModule} +import mill.scalalib.publish._ +import mill.scalalib.scalafmt._ +import mill.scalalib.TestModule.Utest +import mill.util.Jvm +import coursier.maven.MavenRepository + +trait ScriptModule extends ScalaModule { + val scala3 = "3.3.3" + val mainargs = ivy"com.lihaoyi::mainargs:0.5.0" + val oslib = ivy"com.lihaoyi::os-lib:0.10.0" + val upickle = ivy"com.lihaoyi::upickle:3.3.1" + + def scalaVersion = scala3 + def scalacOptions = Seq("-new-syntax", "-deprecation") + override def ivyDeps = Agg(mainargs, oslib, upickle) +} + +object emu extends ScriptModule {} +object ci extends ScriptModule {} diff --git a/script/ci/src/Main.scala b/script/ci/src/Main.scala index 345df8867..8e637dd54 100644 --- a/script/ci/src/Main.scala +++ b/script/ci/src/Main.scala @@ -241,7 +241,7 @@ object Main: Logger.info("Fetching CI results") val emuResultPath = os.Path( nixResolvePath( - s".#t1.$config.ip.run._all${buildType}EmuResult", + s".#t1.$config.ip.run._all${emuType}EmuResult", if emuType.toLowerCase() == "vcs" then Seq("--impure") else Seq() ) diff --git a/script/emu/src/Main.scala b/script/emu/src/Main.scala index c8bcec37f..34f71e715 100644 --- a/script/emu/src/Main.scala +++ b/script/emu/src/Main.scala @@ -3,7 +3,7 @@ package org.chipsalliance.t1.script -import mainargs.{main, arg, ParserForMethods, Leftover, Flag, TokensReader} +import mainargs.{arg, main, Flag, Leftover, ParserForMethods, TokensReader} import scala.io.AnsiColor._ object Logger { @@ -33,466 +33,264 @@ object Main: def read(strs: Seq[String]) = Right(os.Path(strs.head, os.pwd)) - def nixResolvePath(attr: String): String = - os.proc( + def resolveNixPath(attr: String, extraArgs: Seq[String] = Seq()): String = + Logger.trace(s"Running nix build ${attr}") + val args = Seq( "nix", "build", "--no-link", "--no-warn-dirty", "--print-out-paths", attr - ).call() - .out - .trim() + ) ++ extraArgs + os.proc(args).call().out.trim() def resolveTestElfPath( - config: String, - caseName: String, - forceX86: Boolean = false + config: String, + caseName: String, + forceX86: Boolean = false ): os.Path = val casePath = os.Path(caseName, os.pwd) - val caseAttrRoot = if (forceX86) then "cases-x86" else "cases" - - val finalPath = - if (os.exists(casePath)) then casePath - else - val nixArgs = Seq( - "nix", - "build", - "--no-link", - "--print-out-paths", - "--no-warn-dirty", - s".#t1.${config}.ip.${caseAttrRoot}.${caseName}" - ) - Logger.trace( - s"Running `${nixArgs.mkString(" ")}` to get test case ELF file" - ) - os.Path(os.proc(nixArgs).call().out.trim()) / "bin" / s"${caseName}.elf" - - Logger.trace(s"Using test ELF: ${finalPath}") - finalPath + if (os.exists(casePath)) then return casePath + + val caseAttrRoot = + if (forceX86) then ".#legacyPackages.x86_64-linux." + else ".#" + + val nixStorePath = resolveNixPath( + s"${caseAttrRoot}t1.${config}.ip.cases.${caseName}" + ) + val elfFilePath = os.Path(nixStorePath) / "bin" / s"${caseName}.elf" + + elfFilePath end resolveTestElfPath - def resolveEmulatorPath( - config: String, - emuType: String, - isTrace: Boolean = false + def resolveTestBenchPath( + config: String, + emuType: String ): os.Path = - // FIXME: replace with actual trace emulator here - val target = - if (isTrace) then s"${emuType}.verilator-emu" else s"${emuType}.verilator-emu" - val nixArgs = Seq( - "nix", - "build", - "--no-link", - "--print-out-paths", - "--no-warn-dirty", - s".#t1.${config}.${target}" - ) - Logger.trace(s"Running `${nixArgs.mkString(" ")}` to get emulator") + val emuPath = os.Path(emuType, os.pwd) + if (os.exists(emuPath)) then return emuPath - val finalPath = - os.Path(os.proc(nixArgs).call().out.trim()) / "bin" / "online_drive" - Logger.trace(s"Using emulator: ${finalPath}") + val nixStorePath = + if emuType.contains("vcs-") then resolveNixPath(s".#t1.${config}.ip.${emuType}", Seq("--impure")) + else resolveNixPath(s".#t1.${config}.ip.${emuType}") - finalPath - end resolveEmulatorPath + val elfFilePath = os + .walk(os.Path(nixStorePath) / "bin") + .filter(path => os.isFile(path)) + .lastOption + .getOrElse(Logger.fatal("no simulator found in given attribute")) - def resolveElaborateConfig( - configName: String - ): os.Path = - if os.exists(os.Path(configName, os.pwd)) then os.Path(configName) - else os.pwd / "configgen" / "generated" / s"$configName.json" - end resolveElaborateConfig + elfFilePath + end resolveTestBenchPath def prepareOutputDir( - outputDir: Option[String], - outputBaseDir: Option[String], - config: String, - emuType: String, - caseName: String + outputDir: String ): os.Path = - val pathTail = - if os.exists(os.Path(caseName, os.pwd)) || os.exists( - os.Path(config, os.pwd) - ) - then - // It is hard to canoncalize user specify path, so here we use date time instead - val now = java.time.LocalDateTime - .now() - .format( - java.time.format.DateTimeFormatter.ofPattern("yy-MM-dd-HH-mm-ss") - ) - os.RelPath(now) - else os.RelPath(s"$config/$caseName") - - val path = - if (outputDir.isEmpty) then - if (outputBaseDir.isEmpty) then - os.pwd / "testrun" / s"${emuType}emu" / pathTail - else os.Path(outputBaseDir.get, os.pwd) / pathTail - else os.Path(outputDir.get) - - os.makeDir.all(path) - path + val outputPath = os.Path(outputDir, os.pwd) + val currentDate = java.time.LocalDateTime + .now() + .format( + java.time.format.DateTimeFormatter.ofPattern("yy-MM-dd-HH-mm-ss") + ) + val resultPath = outputPath / "all" / currentDate + + os.makeDir.all(resultPath) + + val userPath = outputPath / "result" + os.remove(userPath, checkExists = false) + os.symlink(userPath, resultPath) + + userPath end prepareOutputDir + // naive case validation + def isValidCaseName(caseName: String): Boolean = + caseName.split("\\.").toSeq.length == 2 + + def tryRestoreFromCache(key: String, value: Option[String]): Option[String] = + val xdgDir = sys.env.get("XDG_CACHE_HOME") + val homeDir = sys.env.get("HOME") + + val cacheDir = + if xdgDir != None then os.Path(xdgDir.get) / "chipsalliance-t1" + else if homeDir != None then os.Path(homeDir.get) / ".cache" / "chipsalliance-t1" + else os.pwd / ".cache" + + os.makeDir.all(cacheDir) + + val cacheFile = cacheDir / "helper-cache.json" + val cache = + if os.exists(cacheFile) then ujson.read(os.read(cacheFile)) + else ujson.Obj() + + val ret = if value.isDefined then + cache(key) = value.get + value + else cache.obj.get(key).map(v => v.str) + + os.write.over(cacheFile, ujson.write(cache)) + + ret + end tryRestoreFromCache + def optionals(cond: Boolean, input: Seq[String]): Seq[String] = if (cond) then input else Seq() - // Should be configed via Nix - @main def ipemu( - @arg( - name = "case", - short = 'C', - doc = "name alias for loading test case" - ) testCase: String, - @arg( - name = "dramsim3-cfg", - short = 'd', - doc = "enable dramsim3, and specify its configuration file" - ) dramsim3Config: Option[String] = None, - @arg( - name = "frequency", - short = 'f', - doc = "frequency for the vector processor (in MHz)" - ) dramsim3Frequency: Double = 2000, - @arg( - name = "config", - short = 'c', - doc = "configuration name" - ) config: String, - @arg( - name = "trace", - short = 't', - doc = "use emulator with trace support" - ) trace: Flag = Flag(false), - @arg( - name = "verbose", - short = 'v', - doc = "set loglevel to debug" - ) verbose: Flag = Flag(false), - @arg( - name = "no-logging", - doc = "prevent emulator produce log (both console and file)" - ) noLog: Flag = Flag(false), - @arg( - name = "with-file-logging", - doc = """enable file logging, default is false. - |WARN: the emulator will write all the information in each cycle, which will produce a huge file log, use with care. - |""".stripMargin - ) withFileLog: Flag = Flag(false), - @arg( - name = "no-console-logging", - short = 'q', - doc = "prevent emulator print log to console" - ) noConsoleLog: Flag = Flag(false), - @arg( - name = "emulator-log-level", - doc = "Set the EMULATOR_*_LOG_LEVEL env" - ) emulatorLogLevel: String = "INFO", - @arg( - name = "emulator-log-file-path", - doc = "Set the logging output path" - ) emulatorLogFilePath: Option[os.Path] = None, - @arg( - name = "event-log-path", - doc = "Set the event log path" - ) eventLogFilePath: Option[os.Path] = None, - @arg( - name = "program-output-path", - doc = "Path to store the ELF stdout/stderr" - ) programOutputFilePath: Option[os.Path] = None, - @arg( - name = "out-dir", - doc = "path to save wave file and perf result file" - ) outDir: Option[String] = None, - @arg( - name = "base-out-dir", - doc = "save result files in {base_out_dir}/{config}/{case}/{run_config}" - ) baseOutDir: Option[String] = None, - @arg( - name = "emulator-path", - doc = "path to emulator" - ) emulatorPath: Option[String] = None, - @arg( - doc = "Force using x86_64 as cross compiling host platform" - ) forceX86: Boolean = false, - @arg( - name = "dump-from-cycle", - short = 'D', - doc = "Specify the dump starting point" - ) dumpCycle: String = "0.0", - @arg( - name = "cosim-timeout", - doc = "specify timeout cycle for cosim" - ) cosimTimeout: Int = 400000, - @arg( - name = "dry-run", - doc = "Print the final emulator command line" - ) dryRun: Flag = Flag(false) + def optionalMap[K, V](cond: Boolean, input: Map[K, V]): Map[K, V] = + if (cond) then input else null + + @main def run( + @arg( + name = "emu", + short = 'e', + doc = "Type for emulator, Eg. vcs-emu, verilator-emu-trace" + ) emuType: Option[String], + @arg( + name = "config", + short = 'c', + doc = "configuration name" + ) config: Option[String], + @arg( + name = "verbose", + short = 'v', + doc = "set loglevel to debug" + ) verbose: Flag = Flag(false), + @arg( + name = "out-dir", + doc = "path to save wave file and perf result file" + ) outDir: Option[String] = None, + @arg( + doc = "Cross compile RISC-V test case with x86-64 host tools" + ) forceX86: Boolean = false, + @arg( + name = "dry-run", + doc = "Print the final emulator command line and exit" + ) dryRun: Flag = Flag(false), + leftOver: Leftover[String] ): Unit = - val caseElfPath = resolveTestElfPath(config, testCase, forceX86) - val outputPath = - prepareOutputDir(outDir, baseOutDir, config, "ip", testCase) - val emulator = if (!emulatorPath.isEmpty) then - val emuPath = os.Path(emulatorPath.get, os.pwd) - if (!os.exists(emuPath)) then - sys.error(s"No emulator found at path: ${emulatorPath.get}") - - emuPath - else resolveEmulatorPath(config, "ip", trace.value) - - import scala.util.chaining._ - val elaborateConfig = resolveElaborateConfig(config) - .pipe(os.read) - .pipe(text => ujson.read(text)) - val tck = scala.math.pow(10, 3) / dramsim3Frequency - val emulatorLogPath = - if emulatorLogFilePath.isDefined then emulatorLogFilePath.get - else outputPath / "emulator.log" - val eventLogPath = - if eventLogFilePath.isDefined then eventLogFilePath.get - else outputPath / "rtl-event.jsonl" - val programOutputPath = - if programOutputFilePath.isDefined then programOutputFilePath.get - else outputPath / "mmio-store.txt" - if os.exists(programOutputPath) then os.remove(programOutputPath) - - def dumpCycleAsFloat() = - val ratio = dumpCycle.toFloat - if ratio < 0.0 || ratio > 1.0 then - Logger.error( - s"Can't use $dumpCycle as ratio, use 0 as waveform dump start point" - ) - 0 - else if ratio == 0.0 then 0 - else - val cycleRecordFilePath = - os.pwd / ".github" / "cases" / config / "default.json" - if !os.exists(cycleRecordFilePath) then - Logger.error( - s"$cycleRecordFilePath not found, please run this script at project root" - ) - sys.exit(1) - val cycleRecord = os - .read(cycleRecordFilePath) - .pipe(raw => ujson.read(raw)) - .obj(testCase) - if cycleRecord.isNull then - Logger.error( - s"Using ratio to specify ratio is only supported in raw test case name" - ) - sys.exit(1) - val cycle = cycleRecord.num - scala.math.floor(cycle * 10 * ratio).toInt - - val dumpStartPoint: Int = - try dumpCycle.toInt - catch - case _ => - try dumpCycleAsFloat() - catch - case _ => - Logger.error( - s"Unknown cycle $dumpCycle specified, using 0 as fallback" - ) - 0 + if leftOver.value.isEmpty then Logger.fatal("No test case name") + val caseName = leftOver.value.head + if !isValidCaseName(caseName) then Logger.fatal(s"invalid caseName '$caseName', expect 'A.B'") + + val finalEmuType = tryRestoreFromCache("emulator", emuType) + if finalEmuType.isEmpty then + Logger.fatal( + s"No cached emulator selection nor --emu argument was provided" + ) + + val isTrace = finalEmuType.get.endsWith("-trace") + + val finalConfig = tryRestoreFromCache("config", config) + if finalConfig.isEmpty then + Logger.fatal( + s"No cached config selection nor --config argument was provided" + ) + + Logger.info( + s"Using config=${BOLD}${finalConfig.get}${RESET} emulator=${BOLD}${finalEmuType.get}${RESET} case=${BOLD}$caseName${RESET}" + ) + + val caseElfPath = + resolveTestElfPath(finalConfig.get, caseName, forceX86) + val outputPath = prepareOutputDir(outDir.getOrElse("t1-sim-result")) + val emulator = resolveTestBenchPath(finalConfig.get, finalEmuType.get) + + val leftOverArguments = leftOver.value.dropWhile(arg => arg != "--") val processArgs = Seq( emulator.toString(), - "--elf-file", - caseElfPath.toString(), - "--wave-path", - (outputPath / "wave.fst").toString(), - "--timeout", - cosimTimeout.toString(), - s"--log-file=${emulatorLogPath}", - s"--log-level=${emulatorLogLevel}", - "--vlen", - elaborateConfig.obj("parameter").obj("vLen").toString(), - "--dlen", - elaborateConfig.obj("parameter").obj("dLen").toString() - // "--tck", - // tck.toString(), - // "--perf", - // (outputPath / "perf.txt").toString(), - // "--tl_bank_number", - // elaborateConfig - // .obj("parameter") - // .obj("lsuBankParameters") - // .arr - // .length - // .toString(), - // "--beat_byte", - // elaborateConfig - // .obj("parameter") - // .obj("lsuBankParameters") - // .arr(0) - // .obj("beatbyte") - // .toString(), - // "--program-output-path", - // programOutputPath.toString + s"+t1_elf_file=${caseElfPath}" ) - // ++ optionals(noLog.value, Seq("--no-logging")) - // ++ optionals((!withFileLog.value), Seq("--no-file-logging")) - // ++ optionals(noConsoleLog.value, Seq("--no-console-logging")) - // ++ optionals( - // dramsim3Config.isDefined, - // Seq( - // "--dramsim3-result", - // (outputPath / "dramsim3-logs").toString(), - // "--dramsim3-config", - // dramsim3Config.getOrElse("") - // ) - // ) - // ++ optionals( - // trace.value, - // Seq("--dump-from-cycle", dumpStartPoint.toString) - // ) + ++ optionals(isTrace, Seq(s"+t1_wave_path=${outputPath / "wave.fsdb"}")) + ++ optionals(!leftOverArguments.isEmpty, leftOverArguments) Logger.info(s"Starting IP emulator: `${processArgs.mkString(" ")}`") if dryRun.value then return - if os.exists(eventLogPath) then os.remove(eventLogPath) - os.proc(processArgs) - .call( - stderr = eventLogPath + val rtlEventPath = outputPath / "rtl-event.jsonl.zst" + val journalPath = outputPath / "online-drive-emu-journal" + val driverProc = os + .proc(processArgs) + .spawn( + stdout = journalPath, + env = optionalMap(verbose.value, Map("RUST_LOG" -> "TRACE")) ) - Logger.info(s"RTL event log saved to ${eventLogPath}") - - // if (!withFileLog.value) then - // Logger.info(s"Emulator log save to ${emulatorLogPath}") - // - // if (trace.value) then - // Logger.info(s"Trace file save to ${outputPath}/wave.fst") - end ipemu - - @main def listConfig(): Unit = - os.proc( - Seq( - "nix", - "run", - "--no-warn-dirty", - ".#t1.configgen", - "--", - "listConfigs" + val zstdProc = os + .proc(Seq("zstd", "-o", s"${rtlEventPath}")) + .call(stdin = driverProc.stderr, stdout = os.Inherit, stderr = os.Inherit) + driverProc.destroy() + + val statePath = outputPath / "driver-state.json" + os.write( + statePath, + ujson.write( + ujson.Obj("config" -> finalConfig.get, "elf" -> caseElfPath.toString, "event" -> rtlEventPath.toString) ) - ).call(cwd = os.pwd, stdout = os.Inherit, stderr = os.Inherit) - - @main def subsystemrtl( - @arg( - name = "config", - short = 'c', - doc = "Config to be elaborated for the subsystem RTL" - ) config: String, - @arg( - name = "out-link", - short = 'o', - doc = - "Path to be a symlink to the RTL build output, default using $config_subsystem_rtl" - ) outLink: Option[String] = None - ): Unit = - val finalOutLink = outLink.getOrElse(s"${config}_subsystem_rtl") - os.proc( - Seq( - "nix", - "build", - "--print-build-logs", - s".#t1.${config}.subsystem.rtl", - "--out-link", - finalOutLink - ) - ).call(stdout = os.Inherit, stderr = os.Inherit, stdin = os.Inherit) - Logger.info(s"RTLs store in $finalOutLink") + ) + Logger.info(s"Output saved under ${outputPath}") + end run @main - def difftest( - @arg( - name = "config", - short = 'c', - doc = "specify the elaborate config for running test case" - ) config: String, - @arg( - name = "case-attr", - short = 'C', - doc = "Specify test case attribute to run diff test" - ) caseAttr: String, - @arg( - name = "log-level", - short = 'L', - doc = "Specify log level to run diff test" - ) logLevel: String = "ERROR", - @arg( - name = "event-path", - short = 'e', - doc = "Specify the event log path to examinate" - ) eventPath: Option[String], - @arg( - name = "trace", - short = 'T', - doc = "Use trace emulator result" - ) trace: Flag + def offline( + @arg( + name = "config", + short = 'c', + doc = "specify the elaborate config for running test case" + ) config: Option[String], + @arg( + name = "case-attr", + short = 'C', + doc = "Specify test case attribute to run diff test" + ) caseAttr: Option[String], + @arg( + name = "event-path", + short = 'e', + doc = "Specify the event log path to examinate" + ) eventPath: Option[String], + @arg( + name = "verbose", + short = 'v', + doc = "Verbose output" + ) verbose: Flag = Flag(false), + @arg( + name = "out-dir", + doc = "path to save wave file and perf result file" + ) outDir: Option[String] = None ): Unit = - val difftest = if trace.value then - nixResolvePath(s".#t1.${config}.ip.difftest-trace") - else - nixResolvePath(s".#t1.${config}.ip.difftest") - - val fullCaseAttr = s".#t1.${config}.cases.${caseAttr}" - val caseElf = nixResolvePath(fullCaseAttr) - - import scala.util.chaining._ - val configJson = nixResolvePath(s".#t1.${config}.elaborateConfigJson") - .pipe(p => os.Path(p)) - .pipe(p => os.read(p)) - .pipe(text => ujson.read(text)) - val dLen = configJson.obj("parameter").obj("dLen").num.toInt - val vLen = configJson.obj("parameter").obj("vLen").num.toInt - - Logger.trace(s"Running emulator to get event log") - val eventLog = if eventPath.isDefined then - eventPath.get - else - if trace.value then - nixResolvePath(s"${fullCaseAttr}.emu-result.with-trace") - else - nixResolvePath(s"${fullCaseAttr}.emu-result") - - Logger.trace("Running zstd to get event log") - os.proc( - Seq( - "zstd", - "--decompress", - "-f", - s"${eventLog}/rtl-event.jsonl.zstd", - "-o", - s"${config}-${caseAttr}.event.jsonl" - ) - ).call(stdout = os.Inherit, stderr = os.Inherit) - Logger.info( - s"Starting difftest with DLEN ${dLen}, VLEN ${vLen} for ${fullCaseAttr}" - ) - os.proc( - Seq( - s"${difftest}/bin/offline", - "--vlen", - vLen.toString(), - "--dlen", - dLen.toString(), - "--elf-file", - s"${caseElf}/bin/${caseAttr}.elf", - "--log-file", - s"${config}-${caseAttr}.event.jsonl", - // FIXME: offline difftest doesn't support timeout argument RN - // "--timeout", - // "40000", - "--log-level", - s"${logLevel}" - ) - ).call(stdout = os.Inherit, stderr = os.Inherit) - Logger.info(s"PASS: ${caseAttr} (${config})") - end difftest + val logLevel = + if verbose.value then "trace" + else "info" + + val resultPath = os.Path(outDir.getOrElse("t1-sim-result"), os.pwd) / "result" + val lastState = + if os.exists(resultPath) then ujson.read(os.read(resultPath / "driver-state.json")) + else ujson.Obj() + + val finalConfig = + if config.isDefined then config.get + else lastState.obj.get("config").getOrElse(Logger.fatal("No driver-state.json nor --config")).str + + val offlineChecker = os.Path(resolveNixPath(s".#t1.${finalConfig}.ip.offline-checker")) / "bin" / "offline" + + val elfFile = + if caseAttr.isDefined then resolveTestElfPath(finalConfig, caseAttr.get).toString + else lastState.obj.get("elf").getOrElse(Logger.fatal("No driver-state.json nor --case-attr")).str + + val eventFile = + if eventPath.isDefined then os.Path(eventPath.get, os.pwd) + else os.Path(lastState.obj.get("event").getOrElse(Logger.fatal("")).str) + val decEventFile = resultPath / "rtl-event.jsonl" + Logger.info(s"Decompressing ${eventFile}") + os.proc("zstd", s"${eventFile}", "-d", "-f", "-o", s"${decEventFile}").call() + + val driverArgs: Seq[String] = + Seq(offlineChecker.toString, "--elf-file", elfFile, "--log-level", logLevel, "--log-file", decEventFile.toString) + Logger.info(s"Running offline checker: ${driverArgs.mkString(" ")}") + + os.proc(driverArgs).call(stdout = os.Inherit, stderr = os.Inherit) + end offline def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args) end Main