From 21dc8456516e9000f80cfe06bbf50accb6683a91 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 7 Jun 2024 11:54:23 +0300 Subject: [PATCH 1/7] s/cxxsim/cxxrtl/ --- src/main/scala/ee/hrzn/chryse/ChryseApp.scala | 26 +++++++++---------- .../ee/hrzn/chryse/ChryseScallopConf.scala | 10 +++---- .../{CxxsimTask.scala => CxxrtlTask.scala} | 12 ++++----- 3 files changed, 24 insertions(+), 24 deletions(-) rename src/main/scala/ee/hrzn/chryse/tasks/{CxxsimTask.scala => CxxrtlTask.scala} (94%) diff --git a/src/main/scala/ee/hrzn/chryse/ChryseApp.scala b/src/main/scala/ee/hrzn/chryse/ChryseApp.scala index 8084cca..1db0279 100644 --- a/src/main/scala/ee/hrzn/chryse/ChryseApp.scala +++ b/src/main/scala/ee/hrzn/chryse/ChryseApp.scala @@ -70,26 +70,26 @@ abstract class ChryseApp { ), ) - case Some(conf.cxxsim) => + case Some(conf.cxxrtl) => println(conf.versionBanner) val platform = - if (conf.cxxsim.platformChoices.length > 1) - conf.cxxsim.platformChoices - .find(_.id == conf.cxxsim.platform.get()) + if (conf.cxxrtl.platformChoices.length > 1) + conf.cxxrtl.platformChoices + .find(_.id == conf.cxxrtl.platform.get()) .get else - conf.cxxsim.platformChoices(0) - tasks.CxxsimTask( + conf.cxxrtl.platformChoices(0) + tasks.CxxrtlTask( this, platform, cxxrtlOptions.get, - tasks.CxxsimTask.Options( - conf.cxxsim.debug(), - conf.cxxsim.optimize(), - conf.cxxsim.force(), - conf.cxxsim.compileOnly(), - conf.cxxsim.vcd.toOption, - conf.cxxsim.trailing.getOrElse(List.empty), + tasks.CxxrtlTask.Options( + conf.cxxrtl.debug(), + conf.cxxrtl.optimize(), + conf.cxxrtl.force(), + conf.cxxrtl.compileOnly(), + conf.cxxrtl.vcd.toOption, + conf.cxxrtl.trailing.getOrElse(List.empty), ), ) diff --git a/src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala b/src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala index 83fcbde..06fd3fb 100644 --- a/src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala +++ b/src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala @@ -90,8 +90,8 @@ private[chryse] class ChryseScallopConf(chryse: ChryseApp, args: Array[String]) } addSubcommand(build) - object cxxsim extends Subcommand("cxxsim") { - banner("Run the C++ simulator tests.") + object cxxrtl extends Subcommand("cxxrtl") { + banner("Run the CXXRTL simulator tests.") val platformChoices = chryse.cxxrtlOptions.map(_.platforms).getOrElse(Seq()) @@ -128,16 +128,16 @@ private[chryse] class ChryseScallopConf(chryse: ChryseApp, args: Array[String]) val vcd = opt[String]( argName = "file", - descr = "Output a VCD file when running cxxsim (passes --vcd to the executable)", + descr = "Output a VCD file when running simulation (passes --vcd to the executable)", ) val trailing = trailArg[List[String]]( name = " ...", - descr = "Other arguments for the cxxsim executable", + descr = "Other arguments for the simulation executable", required = false, ) } if (chryse.cxxrtlOptions.isDefined) - addSubcommand(cxxsim) + addSubcommand(cxxrtl) for { sc <- chryse.additionalSubcommands } addSubcommand(sc) diff --git a/src/main/scala/ee/hrzn/chryse/tasks/CxxsimTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala similarity index 94% rename from src/main/scala/ee/hrzn/chryse/tasks/CxxsimTask.scala rename to src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala index 3591a2f..338d87b 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/CxxsimTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala @@ -31,8 +31,8 @@ import java.nio.file.Paths import scala.collection.mutable import scala.sys.process._ -private[chryse] object CxxsimTask extends BaseTask { - private val cxxsimDir = "cxxsim" +private[chryse] object CxxrtlTask extends BaseTask { + private val simDir = "cxxrtl" private val baseCxxOpts = Seq("-std=c++17", "-g", "-pedantic", "-Wall", "-Wextra", "-Wno-zero-length-array", "-Wno-unused-parameter") @@ -51,7 +51,7 @@ private[chryse] object CxxsimTask extends BaseTask { appOptions: CxxrtlOptions, runOptions: Options, ): Unit = { - println(s"Building cxxsim ${platform.id} ...") + println(s"Building ${platform.id} (cxxrtl) ...") Files.createDirectories(Paths.get(buildDir, platform.id)) if (runOptions.force) { @@ -105,8 +105,8 @@ private[chryse] object CxxsimTask extends BaseTask { ) runCu(CmdStepSynthesise, yosysCu) - val ccs = Seq(ccPath) ++ filesInDirWithExt(cxxsimDir, ".cc") - val headers = filesInDirWithExt(cxxsimDir, ".h").toSeq + val ccs = Seq(ccPath) ++ filesInDirWithExt(simDir, ".cc") + val headers = filesInDirWithExt(simDir, ".h").toSeq val yosysDatDir = Seq("yosys-config", "--datdir").!!.trim() val cxxOpts = new mutable.ArrayBuffer[String] @@ -116,7 +116,7 @@ private[chryse] object CxxsimTask extends BaseTask { if (runOptions.optimize) cxxOpts.append("-O3") def buildPathForCc(cc: String) = - cc.replace(s"$cxxsimDir/", s"$buildDir/${platform.id}/") + cc.replace(s"$simDir/", s"$buildDir/${platform.id}/") .replace(".cc", ".o") def compileCmdForCc(cc: String, obj: String): Seq[String] = Seq( From 28b8326816b36d591726457f80dfbed23a70d572 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 7 Jun 2024 12:48:57 +0300 Subject: [PATCH 2/7] BaseTask: extract CompilationUnit. --- .../chryse/platform/ecp5/Ecp5Platform.scala | 1 + .../chryse/platform/ice40/Ice40Platform.scala | 1 + .../scala/ee/hrzn/chryse/tasks/BaseTask.scala | 52 ++----------------- .../ee/hrzn/chryse/tasks/BuildTask.scala | 1 + .../ee/hrzn/chryse/tasks/CxxrtlTask.scala | 1 + 5 files changed, 8 insertions(+), 48 deletions(-) diff --git a/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Platform.scala b/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Platform.scala index 1874738..b93bea8 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Platform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Platform.scala @@ -20,6 +20,7 @@ package ee.hrzn.chryse.platform.ecp5 import chisel3._ import ee.hrzn.chryse.ChryseApp +import ee.hrzn.chryse.build.CompilationUnit import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources import ee.hrzn.chryse.tasks.BaseTask diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Platform.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Platform.scala index de9b0da..2ee00bf 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Platform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Platform.scala @@ -20,6 +20,7 @@ package ee.hrzn.chryse.platform.ice40 import chisel3._ import ee.hrzn.chryse.ChryseApp +import ee.hrzn.chryse.build.CompilationUnit import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources import ee.hrzn.chryse.tasks.BaseTask diff --git a/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala index b766f0e..00cbe5a 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala @@ -19,16 +19,16 @@ package ee.hrzn.chryse.tasks import ee.hrzn.chryse.ChryseAppStepFailureException +import ee.hrzn.chryse.build.CompilationUnit import java.io.PrintWriter import java.nio.file.Files import java.nio.file.Paths -import java.security.MessageDigest -import java.util.HexFormat import scala.jdk.CollectionConverters._ import scala.sys.process._ import scala.util.matching.Regex +// TODO: just about anything goes in here. Refactor. trait BaseTask { protected val buildDir = "build" @@ -70,6 +70,8 @@ trait BaseTask { private val specialChar = "[^a-zA-Z0-9,./=+-_:@%^]".r + // This isn't rigorous and it isn't meant to be — for displaying on stdout + // only. private def formattedCmd(cmd: Seq[String]): String = { def fmtPart(part: String) = specialChar.replaceAllIn(part, Regex.quoteReplacement("\\") + "$0") @@ -161,50 +163,4 @@ trait BaseTask { } } } - - case class CompilationUnit( - val primaryInPath: Option[String], - val otherInPaths: Seq[String], - val outPath: String, - val cmd: Seq[String], - ) { - val digestPath = s"$outPath.dig" - private val sortedInPaths = otherInPaths.appendedAll(primaryInPath).sorted - - private def addIntToDigest(n: Int)(implicit digest: MessageDigest): Unit = - digest.update(String.format("%08x", n).getBytes("UTF-8")) - - private def addStringToDigest(s: String)(implicit - digest: MessageDigest, - ): Unit = - addBytesToDigest(s.getBytes("UTF-8")) - - private def addBytesToDigest( - b: Array[Byte], - )(implicit digest: MessageDigest): Unit = { - addIntToDigest(b.length) - digest.update(b) - } - - private def digestInsWithCmd(): String = { - implicit val digest = MessageDigest.getInstance("SHA-256") - addIntToDigest(sortedInPaths.length) - for { inPath <- sortedInPaths } { - addStringToDigest(inPath) - addBytesToDigest(Files.readAllBytes(Paths.get(inPath))) - } - addIntToDigest(cmd.length) - for { el <- cmd } - addStringToDigest(el) - HexFormat.of().formatHex(digest.digest()) - } - - def isUpToDate(): Boolean = - Files.exists(Paths.get(digestPath)) && Files.readString( - Paths.get(digestPath), - ) == digestInsWithCmd() - - def markUpToDate(): Unit = - writePath(digestPath, digestInsWithCmd()) - } } diff --git a/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala index 622b1c1..26e811c 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala @@ -20,6 +20,7 @@ package ee.hrzn.chryse.tasks import circt.stage.ChiselStage import ee.hrzn.chryse.ChryseApp +import ee.hrzn.chryse.build.CompilationUnit import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources diff --git a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala index 338d87b..d5875c5 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala @@ -21,6 +21,7 @@ package ee.hrzn.chryse.tasks import circt.stage.ChiselStage import ee.hrzn.chryse.ChryseApp import ee.hrzn.chryse.ChryseAppStepFailureException +import ee.hrzn.chryse.build.CompilationUnit import ee.hrzn.chryse.platform.cxxrtl.BlackBoxGenerator import ee.hrzn.chryse.platform.cxxrtl.CxxrtlOptions import ee.hrzn.chryse.platform.cxxrtl.CxxrtlPlatform From 4652a0249792c2c62bf048dd8a44d905828b9013 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 7 Jun 2024 12:49:05 +0300 Subject: [PATCH 3/7] CxxrtlTask: move cxxOpts to CxxrtlPlatform. --- .../scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala | 3 +++ src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala index 27af0db..08d5647 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala @@ -24,6 +24,9 @@ import ee.hrzn.chryse.platform.ElaboratablePlatform abstract case class CxxrtlPlatform(id: String) extends ElaboratablePlatform { type TopPlatform[Top <: Module] = Top + var cxxOpts: Seq[String] = Seq("-std=c++17", "-g", "-pedantic", "-Wall", + "-Wextra", "-Wno-zero-length-array", "-Wno-unused-parameter") + override def apply[Top <: Module](genTop: => Top) = genTop } diff --git a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala index d5875c5..607e868 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala @@ -34,8 +34,6 @@ import scala.sys.process._ private[chryse] object CxxrtlTask extends BaseTask { private val simDir = "cxxrtl" - private val baseCxxOpts = Seq("-std=c++17", "-g", "-pedantic", "-Wall", - "-Wextra", "-Wno-zero-length-array", "-Wno-unused-parameter") case class Options( debug: Boolean, @@ -111,7 +109,7 @@ private[chryse] object CxxrtlTask extends BaseTask { val yosysDatDir = Seq("yosys-config", "--datdir").!!.trim() val cxxOpts = new mutable.ArrayBuffer[String] - cxxOpts.appendAll(baseCxxOpts) + cxxOpts.appendAll(platform.cxxOpts) cxxOpts.append(s"-DCLOCK_HZ=${platform.clockHz}") if (runOptions.debug) cxxOpts.append("-g") if (runOptions.optimize) cxxOpts.append("-O3") From 990fc103d25c5dd7b86d3aadfa43d2e6850a3b1c Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 7 Jun 2024 13:39:55 +0300 Subject: [PATCH 4/7] CxxrtlTask: build with Zig when asked. --- .../platform/cxxrtl/CxxrtlPlatform.scala | 11 ++++- .../scala/ee/hrzn/chryse/tasks/BaseTask.scala | 8 ---- .../ee/hrzn/chryse/tasks/CxxrtlTask.scala | 46 +++++++++++-------- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala index 08d5647..ab44da5 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala @@ -19,14 +19,23 @@ package ee.hrzn.chryse.platform.cxxrtl import chisel3._ +import ee.hrzn.chryse.build.filesInDirWithExt import ee.hrzn.chryse.platform.ElaboratablePlatform -abstract case class CxxrtlPlatform(id: String) extends ElaboratablePlatform { +abstract case class CxxrtlPlatform(id: String, useZig: Boolean = false) + extends ElaboratablePlatform { type TopPlatform[Top <: Module] = Top var cxxOpts: Seq[String] = Seq("-std=c++17", "-g", "-pedantic", "-Wall", "-Wextra", "-Wno-zero-length-array", "-Wno-unused-parameter") + def ccs(simDir: String, cxxrtlCcPath: String): Seq[String] = + Seq(cxxrtlCcPath) ++ filesInDirWithExt(simDir, ".cc") + + // XXX: just depend on what look like headers for now. + def depsFor(simDir: String, ccPath: String): Seq[String] = + filesInDirWithExt(simDir, ".h").toSeq + override def apply[Top <: Module](genTop: => Top) = genTop } diff --git a/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala index 00cbe5a..edceafc 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala @@ -124,14 +124,6 @@ trait BaseTask { run.foreach(_.markUpToDate()) } - protected def filesInDirWithExt(dir: String, ext: String): Iterator[String] = - Files - .walk(Paths.get(dir), 1) - .iterator - .asScala - .map(_.toString) - .filter(_.endsWith(ext)) - protected def logFileBetween( path: String, start: Regex, diff --git a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala index 607e868..15f6da4 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala @@ -31,6 +31,7 @@ import java.nio.file.Files import java.nio.file.Paths import scala.collection.mutable import scala.sys.process._ +import scala.util.matching.Regex private[chryse] object CxxrtlTask extends BaseTask { private val simDir = "cxxrtl" @@ -104,8 +105,7 @@ private[chryse] object CxxrtlTask extends BaseTask { ) runCu(CmdStepSynthesise, yosysCu) - val ccs = Seq(ccPath) ++ filesInDirWithExt(simDir, ".cc") - val headers = filesInDirWithExt(simDir, ".h").toSeq + val ccs = platform.ccs(simDir, ccPath) val yosysDatDir = Seq("yosys-config", "--datdir").!!.trim() val cxxOpts = new mutable.ArrayBuffer[String] @@ -114,27 +114,29 @@ private[chryse] object CxxrtlTask extends BaseTask { if (runOptions.debug) cxxOpts.append("-g") if (runOptions.optimize) cxxOpts.append("-O3") - def buildPathForCc(cc: String) = - cc.replace(s"$simDir/", s"$buildDir/${platform.id}/") - .replace(".cc", ".o") - - def compileCmdForCc(cc: String, obj: String): Seq[String] = Seq( - "c++", - s"-I$buildDir/${platform.id}", - s"-I$buildDir", // XXX: other artefacts the user might generate - s"-I$yosysDatDir/include/backends/cxxrtl/runtime", - "-c", - cc, - "-o", - obj, - ) ++ cxxOpts ++ appOptions.allCxxFlags - - // XXX: depend on what look like headers for now. + def buildPathForCc(cc: String) = { + val inBuildDir = ("^" + Regex.quote(s"$simDir/")).r + .replaceFirstIn(cc, s"$buildDir/${platform.id}/") + "\\.cc$".r.replaceFirstIn(inBuildDir, ".o") + } + + def compileCmdForCc(cc: String, obj: String): Seq[String] = + (if (platform.useZig) Seq("zig") else Seq()) ++ Seq( + "c++", + s"-I$buildDir/${platform.id}", + s"-I$buildDir", // XXX: other artefacts the user might generate + s"-I$yosysDatDir/include/backends/cxxrtl/runtime", + "-c", + cc, + "-o", + obj, + ) ++ cxxOpts ++ appOptions.allCxxFlags + val cus = for { cc <- ccs obj = buildPathForCc(cc) cmd = compileCmdForCc(cc, obj) - } yield CompilationUnit(Some(cc), headers, obj, cmd) + } yield CompilationUnit(Some(cc), platform.depsFor(simDir, cc), obj, cmd) // clangd won't look deeper than $buildDir, so just overwrite. writePath(s"$buildDir/compile_commands.json") { wr => @@ -148,7 +150,11 @@ private[chryse] object CxxrtlTask extends BaseTask { None, cus.map(_.outPath), binPath, - Seq("c++", "-o", binPath) ++ cxxOpts ++ cus.map( + (if (platform.useZig) Seq("zig") else Seq()) ++ Seq( + "c++", + "-o", + binPath, + ) ++ cxxOpts ++ cus.map( _.outPath, ) ++ appOptions.allLdFlags, ) From 5085bb07b66aa422cd13e0621e29b1643dfb624b Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 7 Jun 2024 14:34:59 +0300 Subject: [PATCH 5/7] CxxrtlTask: actually use "zig build" in final step, supply parameters. --- .../platform/cxxrtl/CxxrtlPlatform.scala | 2 +- .../scala/ee/hrzn/chryse/tasks/BaseTask.scala | 21 ++++--- .../ee/hrzn/chryse/tasks/CxxrtlTask.scala | 62 ++++++++++++++----- 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala index ab44da5..db19123 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala @@ -22,7 +22,7 @@ import chisel3._ import ee.hrzn.chryse.build.filesInDirWithExt import ee.hrzn.chryse.platform.ElaboratablePlatform -abstract case class CxxrtlPlatform(id: String, useZig: Boolean = false) +abstract case class CxxrtlPlatform(id: String, zig: Boolean = false) extends ElaboratablePlatform { type TopPlatform[Top <: Module] = Top diff --git a/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala index edceafc..2b11360 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala @@ -21,6 +21,7 @@ package ee.hrzn.chryse.tasks import ee.hrzn.chryse.ChryseAppStepFailureException import ee.hrzn.chryse.build.CompilationUnit +import java.io.File import java.io.PrintWriter import java.nio.file.Files import java.nio.file.Paths @@ -49,7 +50,7 @@ trait BaseTask { writePath(path)(_.write(content)) protected def runCmd(step: CmdStep, cmd: Seq[String]) = - runCmds(step, Seq(cmd)) + runCmds(step, Seq((cmd, None))) sealed protected class CmdStep(s: String) { override def toString() = s @@ -72,10 +73,11 @@ trait BaseTask { // This isn't rigorous and it isn't meant to be — for displaying on stdout // only. - private def formattedCmd(cmd: Seq[String]): String = { + private def formattedCmd(cmd: (Seq[String], Option[String])): String = { def fmtPart(part: String) = specialChar.replaceAllIn(part, Regex.quoteReplacement("\\") + "$0") - cmd.map(fmtPart).mkString(" ") + cmd._2.map(dir => s"(in $dir/) ").getOrElse("") + + cmd._1.map(fmtPart).mkString(" ") } sealed protected trait CmdAction @@ -85,7 +87,7 @@ trait BaseTask { protected def reportCmd( step: CmdStep, action: CmdAction, - cmd: Seq[String], + cmd: (Seq[String], Option[String]), ): Unit = { val paddedAction = action match { case CmdActionRun => "[run] " @@ -96,10 +98,13 @@ trait BaseTask { protected def runCmds( step: CmdStep, - cmds: Iterable[Seq[String]], + cmds: Iterable[(Seq[String], Option[String])], ): Unit = { cmds.foreach(reportCmd(step, CmdActionRun, _)) - val processes = cmds.map(cmd => (cmd, cmd.run())) + val processes = cmds.map { cmd => + val pb = Process(cmd._1, cmd._2.map(new File(_))) + (cmd, pb.run()) + } // TODO: consider an upper limit on concurrency. val failed = processes.collect { case (cmd, proc) if proc.exitValue() != 0 => cmd @@ -119,8 +124,8 @@ trait BaseTask { cus: Iterable[CompilationUnit], ): Unit = { val (skip, run) = cus.partition(_.isUpToDate()) - skip.foreach(cu => reportCmd(step, CmdActionSkip, cu.cmd)) - runCmds(step, run.map(_.cmd)) + skip.foreach(cu => reportCmd(step, CmdActionSkip, _)) + runCmds(step, run.map(cu => (cu.cmd, cu.chdir))) run.foreach(_.markUpToDate()) } diff --git a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala index 15f6da4..27069f5 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala @@ -22,11 +22,13 @@ import circt.stage.ChiselStage import ee.hrzn.chryse.ChryseApp import ee.hrzn.chryse.ChryseAppStepFailureException import ee.hrzn.chryse.build.CompilationUnit +import ee.hrzn.chryse.build.filesInDirWithExt import ee.hrzn.chryse.platform.cxxrtl.BlackBoxGenerator import ee.hrzn.chryse.platform.cxxrtl.CxxrtlOptions import ee.hrzn.chryse.platform.cxxrtl.CxxrtlPlatform import org.apache.commons.io.FileUtils +import java.io.File import java.nio.file.Files import java.nio.file.Paths import scala.collection.mutable @@ -111,6 +113,13 @@ private[chryse] object CxxrtlTask extends BaseTask { val cxxOpts = new mutable.ArrayBuffer[String] cxxOpts.appendAll(platform.cxxOpts) cxxOpts.append(s"-DCLOCK_HZ=${platform.clockHz}") + if (platform.zig) + cxxOpts.appendAll( + Seq( + "-DCXXRTL_INCLUDE_CAPI_IMPL", + "-DCXXRTL_INCLUDE_VCD_CAPI_IMPL", + ), + ) if (runOptions.debug) cxxOpts.append("-g") if (runOptions.optimize) cxxOpts.append("-O3") @@ -121,7 +130,7 @@ private[chryse] object CxxrtlTask extends BaseTask { } def compileCmdForCc(cc: String, obj: String): Seq[String] = - (if (platform.useZig) Seq("zig") else Seq()) ++ Seq( + (if (platform.zig) Seq("zig") else Seq()) ++ Seq( "c++", s"-I$buildDir/${platform.id}", s"-I$buildDir", // XXX: other artefacts the user might generate @@ -146,19 +155,44 @@ private[chryse] object CxxrtlTask extends BaseTask { runCus(CmdStepCompile, cus) val binPath = s"$buildDir/${platform.id}/$name" - val linkCu = CompilationUnit( - None, - cus.map(_.outPath), - binPath, - (if (platform.useZig) Seq("zig") else Seq()) ++ Seq( - "c++", - "-o", + + if (platform.zig) { + // TODO: extract into a CxxrtlPlatform subclass, make the defines configurable there. + val linkCu = CompilationUnit( + None, + cus.map(_.outPath) ++ filesInDirWithExt(simDir, ".zig"), binPath, - ) ++ cxxOpts ++ cus.map( - _.outPath, - ) ++ appOptions.allLdFlags, - ) - runCu(CmdStepLink, linkCu) + Seq( + "zig", + "build", + s"-Dyosys_data_dir=$yosysDatDir", + s"-Dcxxrtl_o_paths=${cus.map(p => s"../${p.outPath}").mkString(",")}", + ) + ++ (if (runOptions.optimize) Seq("-Doptimize=ReleaseFast") + else Seq()), + chdir = Some(simDir), + ) + runCu(CmdStepLink, linkCu) + + FileUtils.copyFile( + new File(s"$simDir/zig-out/bin/$simDir"), + new File(binPath), + ) + } else { + val linkCu = CompilationUnit( + None, + cus.map(_.outPath), + binPath, + Seq( + "c++", + "-o", + binPath, + ) ++ cxxOpts ++ cus.map( + _.outPath, + ) ++ appOptions.allLdFlags, + ) + runCu(CmdStepLink, linkCu) + } if (runOptions.compileOnly) return @@ -168,7 +202,7 @@ private[chryse] object CxxrtlTask extends BaseTask { } val binCmd = Seq(binPath) ++ binArgs ++ runOptions.args - reportCmd(CmdStepExecute, CmdActionRun, binCmd) + reportCmd(CmdStepExecute, CmdActionRun, (binCmd, None)) val rc = binCmd.! println(s"$name exited with return code $rc") From 7b5a47850143f4cce1d46191a8a46a137445b947 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 7 Jun 2024 14:54:18 +0300 Subject: [PATCH 6/7] CxxrtlZigPlatform: factor out. --- .../platform/cxxrtl/CxxrtlPlatform.scala | 52 ++++++++- .../platform/cxxrtl/CxxrtlZigPlatform.scala | 51 +++++++++ .../ee/hrzn/chryse/tasks/CxxrtlTask.scala | 101 +++++------------- 3 files changed, 125 insertions(+), 79 deletions(-) create mode 100644 src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlZigPlatform.scala diff --git a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala index db19123..6e9adb4 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala @@ -19,23 +19,65 @@ package ee.hrzn.chryse.platform.cxxrtl import chisel3._ +import ee.hrzn.chryse.build.CompilationUnit import ee.hrzn.chryse.build.filesInDirWithExt import ee.hrzn.chryse.platform.ElaboratablePlatform +import ee.hrzn.chryse.tasks.BaseTask -abstract case class CxxrtlPlatform(id: String, zig: Boolean = false) - extends ElaboratablePlatform { +import scala.sys.process._ + +abstract class CxxrtlPlatform(val id: String) + extends ElaboratablePlatform + with BaseTask { type TopPlatform[Top <: Module] = Top - var cxxOpts: Seq[String] = Seq("-std=c++17", "-g", "-pedantic", "-Wall", + val simDir = "cxxrtl" + + lazy val yosysDatDir = Seq("yosys-config", "--datdir").!!.trim() + def cxxOpts: Seq[String] = Seq("-std=c++17", "-g", "-pedantic", "-Wall", "-Wextra", "-Wno-zero-length-array", "-Wno-unused-parameter") - def ccs(simDir: String, cxxrtlCcPath: String): Seq[String] = + def compileCmdForCc( + buildDir: String, + finalCxxOpts: Seq[String], + cc: String, + obj: String, + ): Seq[String] = + Seq( + "c++", + s"-I$buildDir/$id", + s"-I$buildDir", // XXX: other artefacts the user might generate + s"-I$yosysDatDir/include/backends/cxxrtl/runtime", + "-c", + cc, + "-o", + obj, + ) ++ finalCxxOpts + + def ccs(cxxrtlCcPath: String): Seq[String] = Seq(cxxrtlCcPath) ++ filesInDirWithExt(simDir, ".cc") // XXX: just depend on what look like headers for now. - def depsFor(simDir: String, ccPath: String): Seq[String] = + def depsFor(ccPath: String): Seq[String] = filesInDirWithExt(simDir, ".h").toSeq + def link( + ccOutPaths: Seq[String], + binPath: String, + finalCxxOpts: Seq[String], + allLdFlags: Seq[String], + optimize: Boolean, + ): Unit = { + val linkCu = CompilationUnit( + None, + ccOutPaths, + binPath, + Seq("c++", "-o", binPath) ++ finalCxxOpts ++ ccOutPaths ++ allLdFlags, + ) + + runCu(CmdStepLink, linkCu) + } + override def apply[Top <: Module](genTop: => Top) = genTop } diff --git a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlZigPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlZigPlatform.scala new file mode 100644 index 0000000..7107d4f --- /dev/null +++ b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlZigPlatform.scala @@ -0,0 +1,51 @@ +package ee.hrzn.chryse.platform.cxxrtl + +import ee.hrzn.chryse.build.CompilationUnit +import ee.hrzn.chryse.build.filesInDirWithExt +import org.apache.commons.io.FileUtils +import java.io.File + +abstract class CxxrtlZigPlatform(id: String) extends CxxrtlPlatform(id) { + override def cxxOpts = super.cxxOpts ++ Seq( + "-DCXXRTL_INCLUDE_CAPI_IMPL", + "-DCXXRTL_INCLUDE_VCD_CAPI_IMPL", + ) + + override def compileCmdForCc( + buildDir: String, + finalCxxOpts: Seq[String], + cc: String, + obj: String, + ) = + Seq("zig") ++ + super.compileCmdForCc(buildDir, finalCxxOpts, cc, obj) + + override def link( + ccOutPaths: Seq[String], + binPath: String, + finalCxxOpts: Seq[String], + allLdFlags: Seq[String], + optimize: Boolean, + ) = { + val linkCu = CompilationUnit( + None, + ccOutPaths ++ filesInDirWithExt(simDir, ".zig"), + binPath, + Seq( + "zig", + "build", + s"-Dyosys_data_dir=$yosysDatDir", + s"-Dcxxrtl_o_paths=${ccOutPaths.map(p => s"../$p").mkString(",")}", + ) + ++ (if (optimize) Seq("-Doptimize=ReleaseFast") + else Seq()), + chdir = Some(simDir), + ) + runCu(CmdStepLink, linkCu) + + FileUtils.copyFile( + new File(s"$simDir/zig-out/bin/$simDir"), + new File(binPath), + ) + } +} diff --git a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala index 27069f5..c8db9db 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala @@ -22,13 +22,11 @@ import circt.stage.ChiselStage import ee.hrzn.chryse.ChryseApp import ee.hrzn.chryse.ChryseAppStepFailureException import ee.hrzn.chryse.build.CompilationUnit -import ee.hrzn.chryse.build.filesInDirWithExt import ee.hrzn.chryse.platform.cxxrtl.BlackBoxGenerator import ee.hrzn.chryse.platform.cxxrtl.CxxrtlOptions import ee.hrzn.chryse.platform.cxxrtl.CxxrtlPlatform import org.apache.commons.io.FileUtils -import java.io.File import java.nio.file.Files import java.nio.file.Paths import scala.collection.mutable @@ -36,8 +34,6 @@ import scala.sys.process._ import scala.util.matching.Regex private[chryse] object CxxrtlTask extends BaseTask { - private val simDir = "cxxrtl" - case class Options( debug: Boolean, optimize: Boolean, @@ -107,92 +103,49 @@ private[chryse] object CxxrtlTask extends BaseTask { ) runCu(CmdStepSynthesise, yosysCu) - val ccs = platform.ccs(simDir, ccPath) - - val yosysDatDir = Seq("yosys-config", "--datdir").!!.trim() - val cxxOpts = new mutable.ArrayBuffer[String] - cxxOpts.appendAll(platform.cxxOpts) - cxxOpts.append(s"-DCLOCK_HZ=${platform.clockHz}") - if (platform.zig) - cxxOpts.appendAll( - Seq( - "-DCXXRTL_INCLUDE_CAPI_IMPL", - "-DCXXRTL_INCLUDE_VCD_CAPI_IMPL", - ), - ) - if (runOptions.debug) cxxOpts.append("-g") - if (runOptions.optimize) cxxOpts.append("-O3") + val ccs = platform.ccs(ccPath) + + val finalCxxOpts = new mutable.ArrayBuffer[String] + finalCxxOpts.appendAll(platform.cxxOpts) + finalCxxOpts.append(s"-DCLOCK_HZ=${platform.clockHz}") + if (runOptions.debug) finalCxxOpts.append("-g") + if (runOptions.optimize) finalCxxOpts.append("-O3") + finalCxxOpts.appendAll(appOptions.allCxxFlags) def buildPathForCc(cc: String) = { - val inBuildDir = ("^" + Regex.quote(s"$simDir/")).r + val inBuildDir = ("^" + Regex.quote(s"${platform.simDir}/")).r .replaceFirstIn(cc, s"$buildDir/${platform.id}/") "\\.cc$".r.replaceFirstIn(inBuildDir, ".o") } - def compileCmdForCc(cc: String, obj: String): Seq[String] = - (if (platform.zig) Seq("zig") else Seq()) ++ Seq( - "c++", - s"-I$buildDir/${platform.id}", - s"-I$buildDir", // XXX: other artefacts the user might generate - s"-I$yosysDatDir/include/backends/cxxrtl/runtime", - "-c", - cc, - "-o", - obj, - ) ++ cxxOpts ++ appOptions.allCxxFlags - - val cus = for { + val ccCus = for { cc <- ccs obj = buildPathForCc(cc) - cmd = compileCmdForCc(cc, obj) - } yield CompilationUnit(Some(cc), platform.depsFor(simDir, cc), obj, cmd) + cmd = + platform.compileCmdForCc( + buildDir, + finalCxxOpts.toSeq, + cc, + obj, + ) + } yield CompilationUnit(Some(cc), platform.depsFor(cc), obj, cmd) // clangd won't look deeper than $buildDir, so just overwrite. writePath(s"$buildDir/compile_commands.json") { wr => - upickle.default.writeTo(cus.map(ClangdEntry(_)), wr) + upickle.default.writeTo(ccCus.map(ClangdEntry(_)), wr) } - runCus(CmdStepCompile, cus) + runCus(CmdStepCompile, ccCus) val binPath = s"$buildDir/${platform.id}/$name" - if (platform.zig) { - // TODO: extract into a CxxrtlPlatform subclass, make the defines configurable there. - val linkCu = CompilationUnit( - None, - cus.map(_.outPath) ++ filesInDirWithExt(simDir, ".zig"), - binPath, - Seq( - "zig", - "build", - s"-Dyosys_data_dir=$yosysDatDir", - s"-Dcxxrtl_o_paths=${cus.map(p => s"../${p.outPath}").mkString(",")}", - ) - ++ (if (runOptions.optimize) Seq("-Doptimize=ReleaseFast") - else Seq()), - chdir = Some(simDir), - ) - runCu(CmdStepLink, linkCu) - - FileUtils.copyFile( - new File(s"$simDir/zig-out/bin/$simDir"), - new File(binPath), - ) - } else { - val linkCu = CompilationUnit( - None, - cus.map(_.outPath), - binPath, - Seq( - "c++", - "-o", - binPath, - ) ++ cxxOpts ++ cus.map( - _.outPath, - ) ++ appOptions.allLdFlags, - ) - runCu(CmdStepLink, linkCu) - } + platform.link( + ccCus.map(_.outPath), + binPath, + finalCxxOpts.toSeq, + appOptions.allLdFlags, + runOptions.optimize, + ) if (runOptions.compileOnly) return From 81361b9045666304d9f79c12376b306b3f6b3ff4 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 7 Jun 2024 17:12:05 +0300 Subject: [PATCH 7/7] oh! big refactor --- .gitignore | 2 +- src/main/scala/ee/hrzn/chryse/ChryseApp.scala | 11 +- .../ee/hrzn/chryse/ChryseScallopConf.scala | 10 +- .../scala/ee/hrzn/chryse/ExampleApp.scala | 7 +- .../ee/hrzn/chryse/build/CommandRunner.scala | 111 ++++++++++++ .../hrzn/chryse/build/CompilationUnit.scala | 71 ++++++++ .../hrzn/chryse/build/filesInDirWithExt.scala | 33 ++++ .../ee/hrzn/chryse/build/logFileBetween.scala | 58 +++++++ .../writePath.scala} | 30 ++-- .../ee/hrzn/chryse/platform/ChryseTop.scala | 17 +- .../platform/ElaboratablePlatform.scala | 8 + .../platform/cxxrtl/CxxrtlPlatform.scala | 31 +++- .../platform/cxxrtl/CxxrtlZigPlatform.scala | 24 ++- .../chryse/platform/ecp5/Ecp5Platform.scala | 108 ++++++------ .../hrzn/chryse/platform/ecp5/Ecp5Top.scala | 4 +- .../chryse/platform/ecp5/Ulx3SPlatform.scala | 29 ++-- .../chryse/platform/ice40/Ice40Platform.scala | 107 +++++------- .../hrzn/chryse/platform/ice40/Ice40Top.scala | 4 +- .../scala/ee/hrzn/chryse/tasks/BaseTask.scala | 163 ------------------ .../ee/hrzn/chryse/tasks/BuildTask.scala | 11 +- .../ee/hrzn/chryse/tasks/CxxrtlTask.scala | 36 ++-- 21 files changed, 499 insertions(+), 376 deletions(-) create mode 100644 src/main/scala/ee/hrzn/chryse/build/CommandRunner.scala create mode 100644 src/main/scala/ee/hrzn/chryse/build/CompilationUnit.scala create mode 100644 src/main/scala/ee/hrzn/chryse/build/filesInDirWithExt.scala create mode 100644 src/main/scala/ee/hrzn/chryse/build/logFileBetween.scala rename src/main/scala/ee/hrzn/chryse/{platform/cxxrtl/CxxrtlOptions.scala => build/writePath.scala} (53%) delete mode 100644 src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala diff --git a/.gitignore b/.gitignore index 10cb7aa..424b8b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -build/ +/build/ .bsp/ target/ diff --git a/src/main/scala/ee/hrzn/chryse/ChryseApp.scala b/src/main/scala/ee/hrzn/chryse/ChryseApp.scala index 1db0279..0a933fa 100644 --- a/src/main/scala/ee/hrzn/chryse/ChryseApp.scala +++ b/src/main/scala/ee/hrzn/chryse/ChryseApp.scala @@ -22,14 +22,14 @@ import chisel3.Module import ee.hrzn.chryse.platform.Platform import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources -import ee.hrzn.chryse.platform.cxxrtl.CxxrtlOptions +import ee.hrzn.chryse.platform.cxxrtl.CxxrtlPlatform abstract class ChryseApp { val name: String def genTop()(implicit platform: Platform): Module val targetPlatforms: Seq[PlatformBoard[_ <: PlatformBoardResources]] + val cxxrtlPlatforms: Seq[CxxrtlPlatform] = Seq() val additionalSubcommands: Seq[ChryseSubcommand] = Seq() - val cxxrtlOptions: Option[CxxrtlOptions] = None def main(args: Array[String]): Unit = { val conf = new ChryseScallopConf(this, args) @@ -73,16 +73,15 @@ abstract class ChryseApp { case Some(conf.cxxrtl) => println(conf.versionBanner) val platform = - if (conf.cxxrtl.platformChoices.length > 1) - conf.cxxrtl.platformChoices + if (cxxrtlPlatforms.length > 1) + cxxrtlPlatforms .find(_.id == conf.cxxrtl.platform.get()) .get else - conf.cxxrtl.platformChoices(0) + cxxrtlPlatforms(0) tasks.CxxrtlTask( this, platform, - cxxrtlOptions.get, tasks.CxxrtlTask.Options( conf.cxxrtl.debug(), conf.cxxrtl.optimize(), diff --git a/src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala b/src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala index 06fd3fb..b679396 100644 --- a/src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala +++ b/src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala @@ -93,13 +93,11 @@ private[chryse] class ChryseScallopConf(chryse: ChryseApp, args: Array[String]) object cxxrtl extends Subcommand("cxxrtl") { banner("Run the CXXRTL simulator tests.") - val platformChoices = chryse.cxxrtlOptions.map(_.platforms).getOrElse(Seq()) - val platform = - if (platformChoices.length > 1) + if (chryse.cxxrtlPlatforms.length > 1) Some( choice( - platformChoices.map(_.id), + chryse.cxxrtlPlatforms.map(_.id), name = "platform", argName = "platform", descr = "CXXRTL platform to use.", @@ -128,7 +126,7 @@ private[chryse] class ChryseScallopConf(chryse: ChryseApp, args: Array[String]) val vcd = opt[String]( argName = "file", - descr = "Output a VCD file when running simulation (passes --vcd to the executable)", + descr = "Output a VCD file when running simulation (passes --vcd to the simulation executable)", ) val trailing = trailArg[List[String]]( name = " ...", @@ -136,7 +134,7 @@ private[chryse] class ChryseScallopConf(chryse: ChryseApp, args: Array[String]) required = false, ) } - if (chryse.cxxrtlOptions.isDefined) + if (chryse.cxxrtlPlatforms.nonEmpty) addSubcommand(cxxrtl) for { sc <- chryse.additionalSubcommands } diff --git a/src/main/scala/ee/hrzn/chryse/ExampleApp.scala b/src/main/scala/ee/hrzn/chryse/ExampleApp.scala index e283ed3..ad254f2 100644 --- a/src/main/scala/ee/hrzn/chryse/ExampleApp.scala +++ b/src/main/scala/ee/hrzn/chryse/ExampleApp.scala @@ -20,7 +20,6 @@ package ee.hrzn.chryse import chisel3._ import ee.hrzn.chryse.platform.Platform -import ee.hrzn.chryse.platform.cxxrtl.CxxrtlOptions import ee.hrzn.chryse.platform.cxxrtl.CxxrtlPlatform import ee.hrzn.chryse.platform.ecp5.Lfe5U_85F import ee.hrzn.chryse.platform.ecp5.Ulx3SPlatform @@ -33,9 +32,9 @@ private[chryse] object ExampleApp extends ChryseApp { override def genTop()(implicit platform: Platform) = new Top override val targetPlatforms = Seq(IceBreakerPlatform(), Ulx3SPlatform(Lfe5U_85F)) - override val cxxrtlOptions = Some( - CxxrtlOptions(platforms = Seq(new CxxrtlPlatform("ex") { + override val cxxrtlPlatforms = Seq( + new CxxrtlPlatform("ex") { val clockHz = 3_000_000 - })), + }, ) } diff --git a/src/main/scala/ee/hrzn/chryse/build/CommandRunner.scala b/src/main/scala/ee/hrzn/chryse/build/CommandRunner.scala new file mode 100644 index 0000000..4f26048 --- /dev/null +++ b/src/main/scala/ee/hrzn/chryse/build/CommandRunner.scala @@ -0,0 +1,111 @@ +/* Copyright © 2024 Asherah Connor. + * + * This file is part of Chryse. + * + * Chryse is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Chryse is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chryse. If not, see . + */ + +package ee.hrzn.chryse.build + +import ee.hrzn.chryse.ChryseAppStepFailureException + +import java.io.File +import scala.sys.process.Process +import scala.util.matching.Regex + +object CommandRunner { + sealed case class CmdStep(s: String) { + override def toString() = s + } + object CmdStep { + object Synthesise extends CmdStep("synthesise") + object PNR extends CmdStep("place&route") + object Pack extends CmdStep("pack") + object Program extends CmdStep("program") + object Compile extends CmdStep("compile") + object Link extends CmdStep("link") + object Execute extends CmdStep("execute") + } + + private def paddedStep(step: CmdStep): String = { + val r = s"($step)" + val spaces = "(place&route) ".length() - r.length() + r + " " * spaces + } + + private val specialChar = "[^a-zA-Z0-9,./=+-_:@%^]".r + + // This isn't rigorous and it isn't meant to be — for displaying on stdout + // only. + private def formattedCmd(cmd: (Seq[String], Option[String])): String = { + def fmtPart(part: String) = + specialChar.replaceAllIn(part, Regex.quoteReplacement("\\") + "$0") + cmd._2.map(dir => s"(in $dir/) ").getOrElse("") + + cmd._1.map(fmtPart).mkString(" ") + } + + sealed trait CmdAction + object CmdAction { + final case object Run extends CmdAction + final case object Skip extends CmdAction + } + + def reportCmd( + step: CmdStep, + action: CmdAction, + cmd: (Seq[String], Option[String]), + ): Unit = { + val paddedAction = action match { + case CmdAction.Run => "[run] " + case CmdAction.Skip => "[skip] " + } + println(s"${paddedStep(step)} $paddedAction ${formattedCmd(cmd)}") + } + + def runCmd(step: CmdStep, cmd: Seq[String]) = + runCmds(step, Seq((cmd, None))) + + def runCmds( + step: CmdStep, + cmds: Iterable[(Seq[String], Option[String])], + ): Unit = { + cmds.foreach(reportCmd(step, CmdAction.Run, _)) + val processes = cmds.map { cmd => + val pb = Process(cmd._1, cmd._2.map(new File(_))) + (cmd, pb.run()) + } + // TODO: consider an upper limit on concurrency. + val failed = processes.collect { + case (cmd, proc) if proc.exitValue() != 0 => cmd + } + if (!failed.isEmpty) { + println("the following process(es) failed:") + for { cmd <- failed } println(s" ${formattedCmd(cmd)}") + throw new ChryseAppStepFailureException(step.toString()) + } + } + + def runCu(step: CmdStep, cu: CompilationUnit) = + runCus(step, Seq(cu)) + + def runCus( + step: CmdStep, + cus: Iterable[CompilationUnit], + ): Unit = { + val (skip, run) = cus.partition(_.isUpToDate()) + skip.foreach(cu => reportCmd(step, CmdAction.Skip, (cu.cmd, cu.chdir))) + runCmds(step, run.map(cu => (cu.cmd, cu.chdir))) + run.foreach(_.markUpToDate()) + } +} diff --git a/src/main/scala/ee/hrzn/chryse/build/CompilationUnit.scala b/src/main/scala/ee/hrzn/chryse/build/CompilationUnit.scala new file mode 100644 index 0000000..2518a27 --- /dev/null +++ b/src/main/scala/ee/hrzn/chryse/build/CompilationUnit.scala @@ -0,0 +1,71 @@ +/* Copyright © 2024 Asherah Connor. + * + * This file is part of Chryse. + * + * Chryse is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Chryse is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chryse. If not, see . + */ + +package ee.hrzn.chryse.build + +import java.nio.file.Files +import java.nio.file.Paths +import java.security.MessageDigest +import java.util.HexFormat + +case class CompilationUnit( + val primaryInPath: Option[String], + val otherInPaths: Seq[String], + val outPath: String, + val cmd: Seq[String], + val chdir: Option[String] = None, +) { + val digestPath = s"$outPath.dig" + private val sortedInPaths = otherInPaths.appendedAll(primaryInPath).sorted + + private def addIntToDigest(n: Int)(implicit digest: MessageDigest): Unit = + digest.update(String.format("%08x", n).getBytes("UTF-8")) + + private def addStringToDigest(s: String)(implicit + digest: MessageDigest, + ): Unit = + addBytesToDigest(s.getBytes("UTF-8")) + + private def addBytesToDigest( + b: Array[Byte], + )(implicit digest: MessageDigest): Unit = { + addIntToDigest(b.length) + digest.update(b) + } + + private def digestInsWithCmd(): String = { + implicit val digest = MessageDigest.getInstance("SHA-256") + addIntToDigest(sortedInPaths.length) + for { inPath <- sortedInPaths } { + addStringToDigest(inPath) + addBytesToDigest(Files.readAllBytes(Paths.get(inPath))) + } + addIntToDigest(cmd.length) + for { el <- cmd } + addStringToDigest(el) + HexFormat.of().formatHex(digest.digest()) + } + + def isUpToDate(): Boolean = + Files.exists(Paths.get(digestPath)) && Files.readString( + Paths.get(digestPath), + ) == digestInsWithCmd() + + def markUpToDate(): Unit = + writePath(digestPath, digestInsWithCmd()) +} diff --git a/src/main/scala/ee/hrzn/chryse/build/filesInDirWithExt.scala b/src/main/scala/ee/hrzn/chryse/build/filesInDirWithExt.scala new file mode 100644 index 0000000..c963590 --- /dev/null +++ b/src/main/scala/ee/hrzn/chryse/build/filesInDirWithExt.scala @@ -0,0 +1,33 @@ +/* Copyright © 2024 Asherah Connor. + * + * This file is part of Chryse. + * + * Chryse is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Chryse is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chryse. If not, see . + */ + +package ee.hrzn.chryse.build + +import java.nio.file.Files +import java.nio.file.Path +import scala.jdk.CollectionConverters._ + +object filesInDirWithExt { + def apply(dir: String, ext: String): Iterator[String] = + Files + .walk(Path.of(dir), Integer.MAX_VALUE) + .iterator + .asScala + .map(_.toString) + .filter(_.endsWith(ext)) +} diff --git a/src/main/scala/ee/hrzn/chryse/build/logFileBetween.scala b/src/main/scala/ee/hrzn/chryse/build/logFileBetween.scala new file mode 100644 index 0000000..04418b0 --- /dev/null +++ b/src/main/scala/ee/hrzn/chryse/build/logFileBetween.scala @@ -0,0 +1,58 @@ +/* Copyright © 2024 Asherah Connor. + * + * This file is part of Chryse. + * + * Chryse is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Chryse is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chryse. If not, see . + */ + +package ee.hrzn.chryse.build + +import java.nio.file.Files +import java.nio.file.Path +import scala.jdk.CollectionConverters._ +import scala.util.matching.Regex + +object logFileBetween { + def apply( + path: String, + start: Regex, + end: Regex, + prefix: Option[String] = None, + ): Unit = { + var started = false + var ended = false + val lines = Files.lines(Path.of(path)).iterator.asScala + + while (!ended && lines.hasNext) { + val line = lines.next() + if (!started) { + started = start.matches(line) + } else if (end.matches(line)) { + ended = true + } else { + println(prefix match { + case Some(prefix) => + if ( + line.length >= prefix.length && line + .substring(0, prefix.length()) == prefix + ) + line.substring(prefix.length()) + else line + case None => + line + }) + } + } + } +} diff --git a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlOptions.scala b/src/main/scala/ee/hrzn/chryse/build/writePath.scala similarity index 53% rename from src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlOptions.scala rename to src/main/scala/ee/hrzn/chryse/build/writePath.scala index 0b79352..5f9a6f4 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlOptions.scala +++ b/src/main/scala/ee/hrzn/chryse/build/writePath.scala @@ -16,24 +16,20 @@ * along with Chryse. If not, see . */ -package ee.hrzn.chryse.platform.cxxrtl +package ee.hrzn.chryse.build -import chisel3.experimental.ExtModule +import java.io.PrintWriter -import scala.sys.process._ +object writePath { + def apply( + path: String, + )(action: PrintWriter => Unit): Unit = { + new PrintWriter(path, "UTF-8") { + try action(this) + finally close() + } + } -final case class CxxrtlOptions( - platforms: Seq[CxxrtlPlatform], - blackboxes: Seq[Class[_ <: ExtModule]] = Seq(), - cxxFlags: Seq[String] = Seq(), - ldFlags: Seq[String] = Seq(), - pkgConfig: Seq[String] = Seq(), - buildHooks: Seq[CxxrtlPlatform => Any] = Seq(), -) { - lazy val allCxxFlags: Seq[String] = cxxFlags ++ pkgConfig.flatMap( - Seq("pkg-config", "--cflags", _).!!.trim.split(' '), - ) - lazy val allLdFlags: Seq[String] = ldFlags ++ pkgConfig.flatMap( - Seq("pkg-config", "--libs", _).!!.trim.split(' '), - ) + def apply(path: String, content: String): Unit = + apply(path)(_.write(content)) } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala b/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala index 0a1c25f..4b54531 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ChryseTop.scala @@ -39,15 +39,16 @@ private[chryse] trait ChryseTop extends RawModule { ) sealed trait PlatformConnectResult - case class PlatformConnectResultUsePorts(topIo: Data, portIo: Data) - extends PlatformConnectResult - case object PlatformConnectResultFallthrough extends PlatformConnectResult - case object PlatformConnectResultNoop extends PlatformConnectResult + object PlatformConnectResult { + case class UsePorts(topIo: Data, portIo: Data) extends PlatformConnectResult + case object Fallthrough extends PlatformConnectResult + case object Noop extends PlatformConnectResult + } protected def platformConnect( name: String, res: ResourceData[_ <: Data], - ): PlatformConnectResult = PlatformConnectResultFallthrough + ): PlatformConnectResult = PlatformConnectResult.Fallthrough protected def platformPort[HW <: Data]( @annotation.unused res: ResourceData[HW], @@ -89,14 +90,14 @@ private[chryse] trait ChryseTop extends RawModule { case _ => platformConnect(name, res) match { - case PlatformConnectResultUsePorts(topIo, portIo) => + case PlatformConnectResult.UsePorts(topIo, portIo) => connected += name -> ConnectedResource( res.pinId.get, res.attributes, None, ) platformPort(res, topIo, portIo) - case PlatformConnectResultFallthrough => + case PlatformConnectResult.Fallthrough => if (res.ioInst.isDefined) { connected += name -> ConnectedResource( res.pinId.get, @@ -106,7 +107,7 @@ private[chryse] trait ChryseTop extends RawModule { val (topIo, portIo) = res.makeIoConnection() platformPort(res, topIo, portIo) } - case PlatformConnectResultNoop => + case PlatformConnectResult.Noop => } } } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ElaboratablePlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/ElaboratablePlatform.scala index 218f739..a46f4d5 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ElaboratablePlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ElaboratablePlatform.scala @@ -23,5 +23,13 @@ import chisel3._ trait ElaboratablePlatform extends Platform { type TopPlatform[Top <: Module] <: RawModule + var buildDir = "build" + + val firtoolOpts = Array( + "--lowering-options=disallowLocalVariables,disallowPackedArrays", + "-disable-all-randomization", + "-strip-debug-info", + ) + def apply[Top <: Module](top: => Top): TopPlatform[Top] } diff --git a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala index 6e9adb4..3c2466f 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlPlatform.scala @@ -19,23 +19,36 @@ package ee.hrzn.chryse.platform.cxxrtl import chisel3._ +import chisel3.experimental.ExtModule +import ee.hrzn.chryse.build.CommandRunner._ import ee.hrzn.chryse.build.CompilationUnit import ee.hrzn.chryse.build.filesInDirWithExt import ee.hrzn.chryse.platform.ElaboratablePlatform -import ee.hrzn.chryse.tasks.BaseTask import scala.sys.process._ -abstract class CxxrtlPlatform(val id: String) - extends ElaboratablePlatform - with BaseTask { +abstract class CxxrtlPlatform(val id: String) extends ElaboratablePlatform { type TopPlatform[Top <: Module] = Top - val simDir = "cxxrtl" + val simDir: String = "cxxrtl" + val blackboxes: Seq[Class[_ <: ExtModule]] = Seq() - lazy val yosysDatDir = Seq("yosys-config", "--datdir").!!.trim() - def cxxOpts: Seq[String] = Seq("-std=c++17", "-g", "-pedantic", "-Wall", - "-Wextra", "-Wno-zero-length-array", "-Wno-unused-parameter") + def preBuild(): Unit = {} + + lazy val yosysDatDir: String = Seq("yosys-config", "--datdir").!!.trim() + + def cxxFlags: Seq[String] = Seq( + "-std=c++17", + "-g", + "-pedantic", + "-Wall", + "-Wextra", + "-Wno-zero-length-array", + "-Wno-unused-parameter", + s"-DCLOCK_HZ=$clockHz", + ) + + def ldFlags: Seq[String] = Seq() def compileCmdForCc( buildDir: String, @@ -75,7 +88,7 @@ abstract class CxxrtlPlatform(val id: String) Seq("c++", "-o", binPath) ++ finalCxxOpts ++ ccOutPaths ++ allLdFlags, ) - runCu(CmdStepLink, linkCu) + runCu(CmdStep.Link, linkCu) } override def apply[Top <: Module](genTop: => Top) = diff --git a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlZigPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlZigPlatform.scala index 7107d4f..b474646 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlZigPlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlZigPlatform.scala @@ -1,12 +1,32 @@ +/* Copyright © 2024 Asherah Connor. + * + * This file is part of Chryse. + * + * Chryse is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Chryse is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Chryse. If not, see . + */ + package ee.hrzn.chryse.platform.cxxrtl +import ee.hrzn.chryse.build.CommandRunner._ import ee.hrzn.chryse.build.CompilationUnit import ee.hrzn.chryse.build.filesInDirWithExt import org.apache.commons.io.FileUtils + import java.io.File abstract class CxxrtlZigPlatform(id: String) extends CxxrtlPlatform(id) { - override def cxxOpts = super.cxxOpts ++ Seq( + override def cxxFlags = super.cxxFlags ++ Seq( "-DCXXRTL_INCLUDE_CAPI_IMPL", "-DCXXRTL_INCLUDE_VCD_CAPI_IMPL", ) @@ -41,7 +61,7 @@ abstract class CxxrtlZigPlatform(id: String) extends CxxrtlPlatform(id) { else Seq()), chdir = Some(simDir), ) - runCu(CmdStepLink, linkCu) + runCu(CmdStep.Link, linkCu) FileUtils.copyFile( new File(s"$simDir/zig-out/bin/$simDir"), diff --git a/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Platform.scala b/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Platform.scala index b93bea8..3a535a3 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Platform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Platform.scala @@ -20,10 +20,12 @@ package ee.hrzn.chryse.platform.ecp5 import chisel3._ import ee.hrzn.chryse.ChryseApp +import ee.hrzn.chryse.build.CommandRunner._ import ee.hrzn.chryse.build.CompilationUnit +import ee.hrzn.chryse.build.logFileBetween +import ee.hrzn.chryse.build.writePath import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources -import ee.hrzn.chryse.tasks.BaseTask trait Ecp5Platform { this: PlatformBoard[_ <: PlatformBoardResources] => type TopPlatform[Top <: Module] = Ecp5Top[Top] @@ -45,68 +47,58 @@ trait Ecp5Platform { this: PlatformBoard[_ <: PlatformBoardResources] => chryse: ChryseApp, topPlatform: Ecp5Top[_], jsonPath: String, - ): BuildResult = - buildImpl(this, chryse, topPlatform, jsonPath) + ): BuildResult = { + val name = chryse.name - private object buildImpl extends BaseTask { - def apply( - platform: PlatformBoard[_], - chryse: ChryseApp, - topPlatform: Ecp5Top[_], - jsonPath: String, - ): BuildResult = { - val name = chryse.name + val lpfPath = s"$buildDir/$id/$name.lpf" + writePath(lpfPath, topPlatform.lpf.toString()) - val lpfPath = s"$buildDir/${platform.id}/$name.lpf" - writePath(lpfPath, topPlatform.lpf.toString()) - - val textcfgPath = s"$buildDir/${platform.id}/$name.config" - val nextpnrLogPath = s"$buildDir/${platform.id}/$name.config.log" - val textcfgCu = CompilationUnit( - Some(jsonPath), - Seq(lpfPath), + val textcfgPath = s"$buildDir/$id/$name.config" + val nextpnrLogPath = s"$buildDir/$id/$name.config.log" + val textcfgCu = CompilationUnit( + Some(jsonPath), + Seq(lpfPath), + textcfgPath, + Seq( + "nextpnr-ecp5", + "-q", + "--log", + nextpnrLogPath, + "--json", + jsonPath, + "--lpf", + lpfPath, + "--textcfg", textcfgPath, - Seq( - "nextpnr-ecp5", - "-q", - "--log", - nextpnrLogPath, - "--json", - jsonPath, - "--lpf", - lpfPath, - "--textcfg", - textcfgPath, - ecp5Variant.arg, - "--package", - ecp5Package, - "--speed", - s"$ecp5Speed", - ), - ) - runCu(CmdStepPNR, textcfgCu) + ecp5Variant.arg, + "--package", + ecp5Package, + "--speed", + s"$ecp5Speed", + ), + ) + runCu(CmdStep.PNR, textcfgCu) - println() - println("Device utilisation:") - logFileBetween( - nextpnrLogPath, - raw"Info: Device utilisation:".r, - raw"Info: Placed .*".r, - Some("Info: "), - ) + println() + println("Device utilisation:") + logFileBetween( + nextpnrLogPath, + raw"Info: Device utilisation:".r, + raw"Info: Placed .*".r, + Some("Info: "), + ) - val bitPath = s"$buildDir/${platform.id}/$name.bit" - val svfPath = s"$buildDir/${platform.id}/$name.svf" - val bitCu = CompilationUnit( - Some(textcfgPath), - Seq(), - bitPath, - Seq("ecppack", "--input", textcfgPath, "--bit", bitPath, "--svf", - svfPath) ++ ecp5PackOpts, - ) - runCu(CmdStepPack, bitCu) + val bitPath = s"$buildDir/$id/$name.bit" + val svfPath = s"$buildDir/$id/$name.svf" + val bitCu = CompilationUnit( + Some(textcfgPath), + Seq(), + bitPath, + Seq("ecppack", "--input", textcfgPath, "--bit", bitPath, "--svf", + svfPath) ++ ecp5PackOpts, + ) + runCu(CmdStep.Pack, bitCu) - BuildResult(bitPath, svfPath) - } + BuildResult(bitPath, svfPath) } } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Top.scala b/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Top.scala index 5e30cec..af8a9b7 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Top.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ecp5Top.scala @@ -46,10 +46,10 @@ class Ecp5Top[Top <: Module]( val inst = Module(new USRMCLK) inst.USRMCLKI := res.ioInst.get inst.USRMCLKTS := 0.U - return PlatformConnectResultNoop + return PlatformConnectResult.Noop } - PlatformConnectResultFallthrough + PlatformConnectResult.Fallthrough } override protected def platformPort[HW <: Data]( diff --git a/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ulx3SPlatform.scala b/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ulx3SPlatform.scala index 053c36f..09044f2 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ulx3SPlatform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ecp5/Ulx3SPlatform.scala @@ -19,6 +19,7 @@ package ee.hrzn.chryse.platform.ecp5 import chisel3._ +import ee.hrzn.chryse.build.CommandRunner._ import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources import ee.hrzn.chryse.platform.ecp5.inst.IOType @@ -29,7 +30,6 @@ import ee.hrzn.chryse.platform.resource.Led import ee.hrzn.chryse.platform.resource.ResourceData import ee.hrzn.chryse.platform.resource.Spi import ee.hrzn.chryse.platform.resource.Uart -import ee.hrzn.chryse.tasks.BaseTask // TODO: restrict the variants to those the ULX3S was delivered with. // TODO: try one of these: https://github.com/emard/ulx3s/blob/master/doc/MANUAL.md#programming-over-wifi-esp32-micropython @@ -44,22 +44,17 @@ case class Ulx3SPlatform(ecp5Variant: Ecp5Variant) override val ecp5PackOpts = Seq("--compress") def program(bitAndSvf: BuildResult, mode: String): Unit = - programImpl(bitAndSvf, mode) - - private object programImpl extends BaseTask { - def apply(bitAndSvf: BuildResult, mode: String): Unit = - runCmd( - CmdStepProgram, - Seq( - "openFPGALoader", - "-v", - "-b", - "ulx3s", - if (mode == "sram") "-m" else "-f", - bitAndSvf.bitPath, - ), - ) - } + runCmd( + CmdStep.Program, + Seq( + "openFPGALoader", + "-v", + "-b", + "ulx3s", + if (mode == "sram") "-m" else "-f", + bitAndSvf.bitPath, + ), + ) val resources = new Ulx3SPlatformResources override val programmingModes = Seq( diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Platform.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Platform.scala index 2ee00bf..076fcf9 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Platform.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Platform.scala @@ -20,10 +20,12 @@ package ee.hrzn.chryse.platform.ice40 import chisel3._ import ee.hrzn.chryse.ChryseApp +import ee.hrzn.chryse.build.CommandRunner._ import ee.hrzn.chryse.build.CompilationUnit +import ee.hrzn.chryse.build.logFileBetween +import ee.hrzn.chryse.build.writePath import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources -import ee.hrzn.chryse.tasks.BaseTask trait Ice40Platform { this: PlatformBoard[_ <: PlatformBoardResources] => type TopPlatform[Top <: Module] = Ice40Top[Top] @@ -47,72 +49,57 @@ trait Ice40Platform { this: PlatformBoard[_ <: PlatformBoardResources] => chryse: ChryseApp, topPlatform: Ice40Top[_], jsonPath: String, - ): String = - buildImpl(this, chryse, topPlatform, jsonPath) + ): String = { + val name = chryse.name - private object buildImpl extends BaseTask { - def apply( - platform: PlatformBoard[_], - chryse: ChryseApp, - topPlatform: Ice40Top[_], - jsonPath: String, - ): String = { - val name = chryse.name + val pcfPath = s"$buildDir/$id/$name.pcf" + writePath(pcfPath, topPlatform.pcf.toString()) - val pcfPath = s"$buildDir/${platform.id}/$name.pcf" - writePath(pcfPath, topPlatform.pcf.toString()) - - val ascPath = s"$buildDir/${platform.id}/$name.asc" - val nextpnrLogPath = s"$buildDir/${platform.id}/$name.asc.log" - val ascCu = CompilationUnit( - Some(jsonPath), - Seq(pcfPath), + val ascPath = s"$buildDir/$id/$name.asc" + val nextpnrLogPath = s"$buildDir/$id/$name.asc.log" + val ascCu = CompilationUnit( + Some(jsonPath), + Seq(pcfPath), + ascPath, + Seq( + "nextpnr-ice40", + "-q", + "--log", + nextpnrLogPath, + "--json", + jsonPath, + "--pcf", + pcfPath, + "--asc", ascPath, - Seq( - "nextpnr-ice40", - "-q", - "--log", - nextpnrLogPath, - "--json", - jsonPath, - "--pcf", - pcfPath, - "--asc", - ascPath, - ice40Variant.arg, - "--package", - ice40Package, - ), - ) - runCu(CmdStepPNR, ascCu) + ice40Variant.arg, + "--package", + ice40Package, + ), + ) + runCu(CmdStep.PNR, ascCu) - println() - println("Device utilisation:") - logFileBetween( - nextpnrLogPath, - raw"Info: Device utilisation:".r, - raw"Info: Placed .*".r, - Some("Info: "), - ) + println() + println("Device utilisation:") + logFileBetween( + nextpnrLogPath, + raw"Info: Device utilisation:".r, + raw"Info: Placed .*".r, + Some("Info: "), + ) - val binPath = s"$buildDir/${platform.id}/$name.bin" - val binCu = CompilationUnit( - Some(ascPath), - Seq(), - binPath, - Seq("icepack", ascPath, binPath), - ) - runCu(CmdStepPack, binCu) + val binPath = s"$buildDir/$id/$name.bin" + val binCu = CompilationUnit( + Some(ascPath), + Seq(), + binPath, + Seq("icepack", ascPath, binPath), + ) + runCu(CmdStep.Pack, binCu) - binPath - } + binPath } def program(binPath: String, mode: String): Unit = - programImpl(binPath) - - private object programImpl extends BaseTask { - def apply(binPath: String): Unit = - runCmd(CmdStepProgram, Seq("iceprog", binPath)) - } + runCmd(CmdStep.Program, Seq("iceprog", binPath)) } diff --git a/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Top.scala b/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Top.scala index e679bd5..3be46c9 100644 --- a/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Top.scala +++ b/src/main/scala/ee/hrzn/chryse/platform/ice40/Ice40Top.scala @@ -51,10 +51,10 @@ class Ice40Top[Top <: Module]( ubtn_reset.get := topIo val portIo = IO(res.makeIo()).suggestName("ubtn") - return PlatformConnectResultUsePorts(topIo, portIo) + return PlatformConnectResult.UsePorts(topIo, portIo) } - PlatformConnectResultFallthrough + PlatformConnectResult.Fallthrough } override protected def platformPort[HW <: Data]( diff --git a/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala deleted file mode 100644 index 2b11360..0000000 --- a/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala +++ /dev/null @@ -1,163 +0,0 @@ -/* Copyright © 2024 Asherah Connor. - * - * This file is part of Chryse. - * - * Chryse is free software: you can redistribute it and/or modify it under the - * terms of the GNU Lesser General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * Chryse is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Chryse. If not, see . - */ - -package ee.hrzn.chryse.tasks - -import ee.hrzn.chryse.ChryseAppStepFailureException -import ee.hrzn.chryse.build.CompilationUnit - -import java.io.File -import java.io.PrintWriter -import java.nio.file.Files -import java.nio.file.Paths -import scala.jdk.CollectionConverters._ -import scala.sys.process._ -import scala.util.matching.Regex - -// TODO: just about anything goes in here. Refactor. -trait BaseTask { - protected val buildDir = "build" - - protected val firtoolOpts = Array( - "--lowering-options=disallowLocalVariables,disallowPackedArrays", - "-disable-all-randomization", - "-strip-debug-info", - ) - - protected def writePath(path: String)(action: PrintWriter => Unit): Unit = { - new PrintWriter(path, "UTF-8") { - try action(this) - finally close() - } - } - - protected def writePath(path: String, content: String): Unit = - writePath(path)(_.write(content)) - - protected def runCmd(step: CmdStep, cmd: Seq[String]) = - runCmds(step, Seq((cmd, None))) - - sealed protected class CmdStep(s: String) { - override def toString() = s - } - final protected case object CmdStepSynthesise extends CmdStep("synthesise") - final protected case object CmdStepPNR extends CmdStep("place&route") - final protected case object CmdStepPack extends CmdStep("pack") - final protected case object CmdStepProgram extends CmdStep("program") - final protected case object CmdStepCompile extends CmdStep("compile") - final protected case object CmdStepLink extends CmdStep("link") - final protected case object CmdStepExecute extends CmdStep("execute") - - private def paddedStep(step: CmdStep): String = { - val r = s"($step)" - val spaces = "(place&route) ".length() - r.length() - r + " " * spaces - } - - private val specialChar = "[^a-zA-Z0-9,./=+-_:@%^]".r - - // This isn't rigorous and it isn't meant to be — for displaying on stdout - // only. - private def formattedCmd(cmd: (Seq[String], Option[String])): String = { - def fmtPart(part: String) = - specialChar.replaceAllIn(part, Regex.quoteReplacement("\\") + "$0") - cmd._2.map(dir => s"(in $dir/) ").getOrElse("") + - cmd._1.map(fmtPart).mkString(" ") - } - - sealed protected trait CmdAction - final protected case object CmdActionRun extends CmdAction - final protected case object CmdActionSkip extends CmdAction - - protected def reportCmd( - step: CmdStep, - action: CmdAction, - cmd: (Seq[String], Option[String]), - ): Unit = { - val paddedAction = action match { - case CmdActionRun => "[run] " - case CmdActionSkip => "[skip] " - } - println(s"${paddedStep(step)} $paddedAction ${formattedCmd(cmd)}") - } - - protected def runCmds( - step: CmdStep, - cmds: Iterable[(Seq[String], Option[String])], - ): Unit = { - cmds.foreach(reportCmd(step, CmdActionRun, _)) - val processes = cmds.map { cmd => - val pb = Process(cmd._1, cmd._2.map(new File(_))) - (cmd, pb.run()) - } - // TODO: consider an upper limit on concurrency. - val failed = processes.collect { - case (cmd, proc) if proc.exitValue() != 0 => cmd - } - if (!failed.isEmpty) { - println("the following process(es) failed:") - for { cmd <- failed } println(s" ${formattedCmd(cmd)}") - throw new ChryseAppStepFailureException(step.toString()) - } - } - - protected def runCu(step: CmdStep, cu: CompilationUnit) = - runCus(step, Seq(cu)) - - protected def runCus( - step: CmdStep, - cus: Iterable[CompilationUnit], - ): Unit = { - val (skip, run) = cus.partition(_.isUpToDate()) - skip.foreach(cu => reportCmd(step, CmdActionSkip, _)) - runCmds(step, run.map(cu => (cu.cmd, cu.chdir))) - run.foreach(_.markUpToDate()) - } - - protected def logFileBetween( - path: String, - start: Regex, - end: Regex, - prefix: Option[String] = None, - ): Unit = { - var started = false - var ended = false - val lines = Files.lines(Paths.get(path)).iterator.asScala - - while (!ended && lines.hasNext) { - val line = lines.next() - if (!started) { - started = start.matches(line) - } else if (end.matches(line)) { - ended = true - } else { - println(prefix match { - case Some(prefix) => - if ( - line.length >= prefix.length && line - .substring(0, prefix.length()) == prefix - ) - line.substring(prefix.length()) - else line - case None => - line - }) - } - } - } -} diff --git a/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala index 26e811c..d71e16b 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala @@ -20,14 +20,17 @@ package ee.hrzn.chryse.tasks import circt.stage.ChiselStage import ee.hrzn.chryse.ChryseApp +import ee.hrzn.chryse.build.CommandRunner._ import ee.hrzn.chryse.build.CompilationUnit +import ee.hrzn.chryse.build.logFileBetween +import ee.hrzn.chryse.build.writePath import ee.hrzn.chryse.platform.PlatformBoard import ee.hrzn.chryse.platform.PlatformBoardResources import java.nio.file.Files import java.nio.file.Paths -private[chryse] object BuildTask extends BaseTask { +private[chryse] object BuildTask { case class Options( program: Boolean, programMode: String, @@ -39,6 +42,8 @@ private[chryse] object BuildTask extends BaseTask { platform: PlatformBoard[_ <: PlatformBoardResources], options: Options, ): Unit = { + val buildDir = platform.buildDir + println(s"Building for ${platform.id} ...") Files.createDirectories(Paths.get(buildDir, platform.id)) @@ -54,7 +59,7 @@ private[chryse] object BuildTask extends BaseTask { topPlatform.get }, if (options.fullStacktrace) Array("--full-stacktrace") else Array.empty, - firtoolOpts = firtoolOpts, + firtoolOpts = platform.firtoolOpts, ) writePath(verilogPath, verilog) @@ -76,7 +81,7 @@ private[chryse] object BuildTask extends BaseTask { jsonPath, Seq("yosys", "-q", "-g", "-l", yosysLogPath, "-s", yosysScriptPath), ) - runCu(CmdStepSynthesise, yosysCu) + runCu(CmdStep.Synthesise, yosysCu) logFileBetween( yosysLogPath, diff --git a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala index c8db9db..4d2aa06 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala @@ -21,9 +21,10 @@ package ee.hrzn.chryse.tasks import circt.stage.ChiselStage import ee.hrzn.chryse.ChryseApp import ee.hrzn.chryse.ChryseAppStepFailureException +import ee.hrzn.chryse.build.writePath +import ee.hrzn.chryse.build.CommandRunner._ import ee.hrzn.chryse.build.CompilationUnit import ee.hrzn.chryse.platform.cxxrtl.BlackBoxGenerator -import ee.hrzn.chryse.platform.cxxrtl.CxxrtlOptions import ee.hrzn.chryse.platform.cxxrtl.CxxrtlPlatform import org.apache.commons.io.FileUtils @@ -33,7 +34,7 @@ import scala.collection.mutable import scala.sys.process._ import scala.util.matching.Regex -private[chryse] object CxxrtlTask extends BaseTask { +private[chryse] object CxxrtlTask { case class Options( debug: Boolean, optimize: Boolean, @@ -46,9 +47,10 @@ private[chryse] object CxxrtlTask extends BaseTask { def apply[P <: CxxrtlPlatform]( chryse: ChryseApp, platform: P, - appOptions: CxxrtlOptions, runOptions: Options, ): Unit = { + val buildDir = platform.buildDir + println(s"Building ${platform.id} (cxxrtl) ...") Files.createDirectories(Paths.get(buildDir, platform.id)) @@ -60,19 +62,19 @@ private[chryse] object CxxrtlTask extends BaseTask { val name = chryse.name - appOptions.buildHooks.foreach(_(platform)) + platform.preBuild() val verilogPath = s"$buildDir/${platform.id}/$name.sv" val verilog = ChiselStage.emitSystemVerilog( platform(chryse.genTop()(platform)), - firtoolOpts = firtoolOpts, + firtoolOpts = platform.firtoolOpts, ) writePath(verilogPath, verilog) val blackboxIlPath = s"$buildDir/${platform.id}/$name-blackbox.il" writePath(blackboxIlPath) { wr => - for { (bb, bbIx) <- appOptions.blackboxes.zipWithIndex } { + for { (bb, bbIx) <- platform.blackboxes.zipWithIndex } { if (bbIx > 0) wr.write("\n") BlackBoxGenerator(wr, bb) } @@ -101,16 +103,14 @@ private[chryse] object CxxrtlTask extends BaseTask { yosysScriptPath, ), ) - runCu(CmdStepSynthesise, yosysCu) + runCu(CmdStep.Synthesise, yosysCu) val ccs = platform.ccs(ccPath) - val finalCxxOpts = new mutable.ArrayBuffer[String] - finalCxxOpts.appendAll(platform.cxxOpts) - finalCxxOpts.append(s"-DCLOCK_HZ=${platform.clockHz}") - if (runOptions.debug) finalCxxOpts.append("-g") - if (runOptions.optimize) finalCxxOpts.append("-O3") - finalCxxOpts.appendAll(appOptions.allCxxFlags) + val finalCxxFlags = new mutable.ArrayBuffer[String] + finalCxxFlags.appendAll(platform.cxxFlags) + if (runOptions.debug) finalCxxFlags.append("-g") + if (runOptions.optimize) finalCxxFlags.append("-O3") def buildPathForCc(cc: String) = { val inBuildDir = ("^" + Regex.quote(s"${platform.simDir}/")).r @@ -124,7 +124,7 @@ private[chryse] object CxxrtlTask extends BaseTask { cmd = platform.compileCmdForCc( buildDir, - finalCxxOpts.toSeq, + finalCxxFlags.toSeq, cc, obj, ) @@ -135,15 +135,15 @@ private[chryse] object CxxrtlTask extends BaseTask { upickle.default.writeTo(ccCus.map(ClangdEntry(_)), wr) } - runCus(CmdStepCompile, ccCus) + runCus(CmdStep.Compile, ccCus) val binPath = s"$buildDir/${platform.id}/$name" platform.link( ccCus.map(_.outPath), binPath, - finalCxxOpts.toSeq, - appOptions.allLdFlags, + finalCxxFlags.toSeq, + platform.ldFlags, runOptions.optimize, ) @@ -155,7 +155,7 @@ private[chryse] object CxxrtlTask extends BaseTask { } val binCmd = Seq(binPath) ++ binArgs ++ runOptions.args - reportCmd(CmdStepExecute, CmdActionRun, (binCmd, None)) + reportCmd(CmdStep.Execute, CmdAction.Run, (binCmd, None)) val rc = binCmd.! println(s"$name exited with return code $rc")