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 8084cca..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) @@ -70,26 +70,25 @@ 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 (cxxrtlPlatforms.length > 1) + cxxrtlPlatforms + .find(_.id == conf.cxxrtl.platform.get()) .get else - conf.cxxsim.platformChoices(0) - tasks.CxxsimTask( + cxxrtlPlatforms(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..b679396 100644 --- a/src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala +++ b/src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala @@ -90,16 +90,14 @@ private[chryse] class ChryseScallopConf(chryse: ChryseApp, args: Array[String]) } addSubcommand(build) - object cxxsim extends Subcommand("cxxsim") { - banner("Run the C++ simulator tests.") - - val platformChoices = chryse.cxxrtlOptions.map(_.platforms).getOrElse(Seq()) + object cxxrtl extends Subcommand("cxxrtl") { + banner("Run the CXXRTL simulator tests.") 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,16 +126,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 simulation 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) + if (chryse.cxxrtlPlatforms.nonEmpty) + addSubcommand(cxxrtl) for { sc <- chryse.additionalSubcommands } addSubcommand(sc) 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 27af0db..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,11 +19,78 @@ 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 -abstract case class CxxrtlPlatform(id: String) extends ElaboratablePlatform { +import scala.sys.process._ + +abstract class CxxrtlPlatform(val id: String) extends ElaboratablePlatform { type TopPlatform[Top <: Module] = Top + val simDir: String = "cxxrtl" + val blackboxes: Seq[Class[_ <: ExtModule]] = Seq() + + 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, + 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(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(CmdStep.Link, 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..b474646 --- /dev/null +++ b/src/main/scala/ee/hrzn/chryse/platform/cxxrtl/CxxrtlZigPlatform.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.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 cxxFlags = super.cxxFlags ++ 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(CmdStep.Link, linkCu) + + FileUtils.copyFile( + new File(s"$simDir/zig-out/bin/$simDir"), + new File(binPath), + ) + } +} 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..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,9 +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] @@ -44,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 de9b0da..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,9 +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] @@ -46,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 b766f0e..0000000 --- a/src/main/scala/ee/hrzn/chryse/tasks/BaseTask.scala +++ /dev/null @@ -1,210 +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 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 - -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)) - - 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 - - private def formattedCmd(cmd: Seq[String]): String = { - def fmtPart(part: String) = - specialChar.replaceAllIn(part, Regex.quoteReplacement("\\") + "$0") - cmd.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], - ): 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]], - ): Unit = { - cmds.foreach(reportCmd(step, CmdActionRun, _)) - val processes = cmds.map(cmd => (cmd, cmd.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, cu.cmd)) - runCmds(step, run.map(_.cmd)) - 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, - 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 - }) - } - } - } - - 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..d71e16b 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/BuildTask.scala @@ -20,13 +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, @@ -38,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)) @@ -53,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) @@ -75,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/CxxsimTask.scala b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala similarity index 65% rename from src/main/scala/ee/hrzn/chryse/tasks/CxxsimTask.scala rename to src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala index 3591a2f..4d2aa06 100644 --- a/src/main/scala/ee/hrzn/chryse/tasks/CxxsimTask.scala +++ b/src/main/scala/ee/hrzn/chryse/tasks/CxxrtlTask.scala @@ -21,8 +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 @@ -30,12 +32,9 @@ 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 CxxsimTask extends BaseTask { - private val cxxsimDir = "cxxsim" - private val baseCxxOpts = Seq("-std=c++17", "-g", "-pedantic", "-Wall", - "-Wextra", "-Wno-zero-length-array", "-Wno-unused-parameter") - +private[chryse] object CxxrtlTask { case class Options( debug: Boolean, optimize: Boolean, @@ -48,10 +47,11 @@ private[chryse] object CxxsimTask extends BaseTask { def apply[P <: CxxrtlPlatform]( chryse: ChryseApp, platform: P, - appOptions: CxxrtlOptions, runOptions: Options, ): Unit = { - println(s"Building cxxsim ${platform.id} ...") + val buildDir = platform.buildDir + + println(s"Building ${platform.id} (cxxrtl) ...") Files.createDirectories(Paths.get(buildDir, platform.id)) if (runOptions.force) { @@ -62,19 +62,19 @@ private[chryse] object CxxsimTask 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) } @@ -103,57 +103,49 @@ private[chryse] object CxxsimTask extends BaseTask { yosysScriptPath, ), ) - runCu(CmdStepSynthesise, yosysCu) - - val ccs = Seq(ccPath) ++ filesInDirWithExt(cxxsimDir, ".cc") - val headers = filesInDirWithExt(cxxsimDir, ".h").toSeq - - val yosysDatDir = Seq("yosys-config", "--datdir").!!.trim() - val cxxOpts = new mutable.ArrayBuffer[String] - cxxOpts.appendAll(baseCxxOpts) - cxxOpts.append(s"-DCLOCK_HZ=${platform.clockHz}") - if (runOptions.debug) cxxOpts.append("-g") - if (runOptions.optimize) cxxOpts.append("-O3") - - def buildPathForCc(cc: String) = - cc.replace(s"$cxxsimDir/", 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. - val cus = for { + runCu(CmdStep.Synthesise, yosysCu) + + val ccs = platform.ccs(ccPath) + + 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 + .replaceFirstIn(cc, s"$buildDir/${platform.id}/") + "\\.cc$".r.replaceFirstIn(inBuildDir, ".o") + } + + val ccCus = for { cc <- ccs obj = buildPathForCc(cc) - cmd = compileCmdForCc(cc, obj) - } yield CompilationUnit(Some(cc), headers, obj, cmd) + cmd = + platform.compileCmdForCc( + buildDir, + finalCxxFlags.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(CmdStep.Compile, ccCus) val binPath = s"$buildDir/${platform.id}/$name" - val linkCu = CompilationUnit( - None, - cus.map(_.outPath), + + platform.link( + ccCus.map(_.outPath), binPath, - Seq("c++", "-o", binPath) ++ cxxOpts ++ cus.map( - _.outPath, - ) ++ appOptions.allLdFlags, + finalCxxFlags.toSeq, + platform.ldFlags, + runOptions.optimize, ) - runCu(CmdStepLink, linkCu) if (runOptions.compileOnly) return @@ -163,7 +155,7 @@ private[chryse] object CxxsimTask extends BaseTask { } val binCmd = Seq(binPath) ++ binArgs ++ runOptions.args - reportCmd(CmdStepExecute, CmdActionRun, binCmd) + reportCmd(CmdStep.Execute, CmdAction.Run, (binCmd, None)) val rc = binCmd.! println(s"$name exited with return code $rc")