Skip to content
This repository has been archived by the owner on Jun 16, 2024. It is now read-only.

Zigsim / ST-20 #3

Merged
merged 7 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
build/
/build/

.bsp/
target/
Expand Down
31 changes: 15 additions & 16 deletions src/main/scala/ee/hrzn/chryse/ChryseApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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),
),
)

Expand Down
18 changes: 8 additions & 10 deletions src/main/scala/ee/hrzn/chryse/ChryseScallopConf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down Expand Up @@ -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 <file> to the executable)",
descr = "Output a VCD file when running simulation (passes --vcd <file> to the simulation executable)",
)
val trailing = trailArg[List[String]](
name = "<arg> ...",
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)
Expand Down
7 changes: 3 additions & 4 deletions src/main/scala/ee/hrzn/chryse/ExampleApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
})),
},
)
}
111 changes: 111 additions & 0 deletions src/main/scala/ee/hrzn/chryse/build/CommandRunner.scala
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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())
}
}
71 changes: 71 additions & 0 deletions src/main/scala/ee/hrzn/chryse/build/CompilationUnit.scala
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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())
}
33 changes: 33 additions & 0 deletions src/main/scala/ee/hrzn/chryse/build/filesInDirWithExt.scala
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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))
}
Loading