From be691c96be377b1ec5e1fcf7fa0677e2e3c27411 Mon Sep 17 00:00:00 2001 From: Johannes Wirth Date: Mon, 6 Oct 2025 16:27:06 +0200 Subject: [PATCH] Add logging and update validation --- .../scala/com/minres/tgc/hammer/Global.scala | 1 - .../scala/com/minres/tgc/hammer/Main.scala | 15 +++++-- .../minres/tgc/hammer/cli/HammerConf.scala | 15 +++++-- .../tgc/hammer/cli/LongnailCommand.scala | 15 +++++-- .../tgc/hammer/cli/LongnailMergeCommand.scala | 13 +++++- .../tgc/hammer/cli/LongnailSchedCommand.scala | 14 +++++- .../minres/tgc/hammer/cli/MySubcommand.scala | 39 +++------------- .../tgc/hammer/cli/TreenailCommand.scala | 10 ++++- .../tgc/hammer/options/BaseOption.scala | 2 +- .../tgc/hammer/options/CommandGroup.scala | 1 + .../tgc/hammer/options/OptionGroup.scala | 24 +++++++++- .../minres/tgc/hammer/tasks/CopyTask.scala | 2 +- .../com/minres/tgc/hammer/tasks/Task.scala | 3 +- .../tgc/hammer/tasks/TreenailTask.scala | 3 +- .../tasks/longnail/LongnailHLSTask.scala | 6 ++- .../tasks/longnail/LongnailMergeTask.scala | 4 +- .../tasks/longnail/LongnailScheduleTask.scala | 23 ++-------- .../tasks/longnail/SchedulingParameters.scala | 36 ++++++++------- .../tgc/hammer/{ => util}/FileUtils.scala | 9 +++- .../com/minres/tgc/hammer/util/Logging.scala | 13 ++++++ .../tgc/hammer/util/ValidationBase.scala | 10 +++++ .../tgc/hammer/util/ValidationUtils.scala | 44 +++++++++++++++++++ 22 files changed, 209 insertions(+), 93 deletions(-) rename toolflow/src/main/scala/com/minres/tgc/hammer/{ => util}/FileUtils.scala (79%) create mode 100644 toolflow/src/main/scala/com/minres/tgc/hammer/util/Logging.scala create mode 100644 toolflow/src/main/scala/com/minres/tgc/hammer/util/ValidationBase.scala create mode 100644 toolflow/src/main/scala/com/minres/tgc/hammer/util/ValidationUtils.scala diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/Global.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/Global.scala index bfaa8fa..c55e142 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/Global.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/Global.scala @@ -1,6 +1,5 @@ package com.minres.tgc.hammer -import com.minres.tgc.hammer.FileUtils.* import os.Path object Global { diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/Main.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/Main.scala index 6d4392a..0381f0c 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/Main.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/Main.scala @@ -11,7 +11,7 @@ object Main { var conf: HammerConf = uninitialized def main(args: Array[String]): Unit = { - var logger = Logger("TGCHammer") + val logger = Logger("TGCHammer") conf = new HammerConf(args.toIndexedSeq) val logLevel = conf.verbose() match { @@ -22,14 +22,23 @@ object Main { } LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).asInstanceOf[ch.qos.logback.classic.Logger].setLevel(logLevel) - os.makeDir(Global.OUT_DIR) - os.makeDir(Global.TMP_DIR) + logger.trace(s"Creating output directory ${Global.OUT_DIR}") + if (conf.emptyOutputDir()) os.remove.all(Global.OUT_DIR) + os.makeDir.all(Global.OUT_DIR) + logger.trace(s"Creating temp directory ${Global.TMP_DIR}") + os.makeDir.all(Global.TMP_DIR) conf.subcommand match { case Some(c: MySubcommand) => + logger.info(s"Executing subcommand ${c.name}") val tasks = c.getRequiredTasks + logger.debug(s"Subcommand requires ${tasks.size} tasks") + logger.info(s"Validating tasks") tasks.foreach(_.validate()) + logger.info(s"Executing tasks") tasks.foreach(_.execute()) case _ => + logger.error(s"Found no subcommand, see help below") + conf.printHelp() } } } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/HammerConf.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/HammerConf.scala index 6ab7eb2..29b0fc8 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/HammerConf.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/HammerConf.scala @@ -2,16 +2,25 @@ package com.minres.tgc.hammer.cli import org.rogach.scallop.* import os.Path -import com.minres.tgc.hammer.FileUtils.* +import com.minres.tgc.hammer.util.FileUtils.* class HammerConf(arguments: Seq[String]) extends ScallopConf(arguments) { val outputDirectory: ScallopOption[Path] = opt[Path](default = Some("output".path()), descr = "The base output directory") - val verbose: ScallopOption[Int] = tally() + val verbose: ScallopOption[Int] = tally(short = 'v', descr = "Controls the logging of tgc-hammer itself, using it multiple times (-vv) will print more. (0x => WARN, 1x => INFO, 2x => DEBUG, 3x => TRACE)") + val emptyOutputDir: ScallopOption[Boolean] = toggle(default = Some(true), descrYes = "Whether to empty the output directory at the start; defaults to true") addSubcommand(new LongnailCommand) addSubcommand(new TreenailCommand) + addSubcommand(new LongnailMergeCommand) addSubcommand(new LongnailSchedCommand) - requireSubcommand() + banner("""This program offers several subcommands to execute different tasks. + |The main subcommand to create the hardware module of an ISAX is `isaxHLS`. + |For more information about the several subcommands use `tgc-hammer subcommand --help` with the name of the respective subcommand. + | + |Usage: tgc-hammer isaxHLS -c VexRiscv --useMinIISolution isax.core_desc + |""".stripMargin) + + shortSubcommandsHelp(true) verify() } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailCommand.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailCommand.scala index 67f1e5a..e3ceeaa 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailCommand.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailCommand.scala @@ -1,19 +1,22 @@ package com.minres.tgc.hammer.cli -import com.minres.tgc.hammer.FileUtils.* +import com.minres.tgc.hammer.util.FileUtils.* import com.minres.tgc.hammer.Global.* import com.minres.tgc.hammer.tasks.{Task, TreenailTask} import com.minres.tgc.hammer.tasks.longnail.{LongnailHLSTask, LongnailMergeTask, LongnailScheduleTask, SchedulingParameters} +import com.minres.tgc.hammer.util.{Logging, ValidationUtils} import org.rogach.scallop.* import os.Path import scala.collection.mutable -class LongnailCommand extends MySubcommand("isaxHLS") with CoreSelection { +class LongnailCommand extends MySubcommand("isaxHLS") with CoreSelection with Logging[LongnailCommand] with ValidationUtils { val inputFiles: ScallopOption[List[Path]] = trailArg[List[Path]]("inputFiles", group = mainGroup, descr = "One or multiple input files; Both .core_desc as well as .mlir are supported (also mixed)") val useMinIISolution: ScallopOption[Boolean] = toggle(name = "useMinIISolution", group = mainGroup, default = Some(false), descrYes = "Whether to automatically choose the scheduling solution with the lowest II. If not activated, a manual selection during execution will be required!") val schedParams: SchedulingParameters = addConfigElement(new SchedulingParameters) + check(inputFiles)(checkPathsIsFile) + banner( """Run the complete longnail flow from CoreDSL/MLIR to receive a SystemVerilog representation of the ISAXes |Usage: tgc-hammer isaxHLS -c VexRiscv --useMinIISolution isax.core_desc @@ -34,11 +37,17 @@ class LongnailCommand extends MySubcommand("isaxHLS") with CoreSelection { tasks += LongnailMergeTask(allMlirFiles, concatInput, mergedInput) tasks += LongnailScheduleTask(mergedInput, getCoreDatasheet, schedParams) } + val verilogOutput = OUT_DIR / "verilog" + os.makeDir.all(verilogOutput) if (useMinIISolution()) { - tasks += LongnailHLSTask(schedParams.schedulingSolutionFile(), None, OUT_DIR) + tasks += LongnailHLSTask(schedParams.schedulingSolutionFile(), None, verilogOutput) } else { ??? } tasks.toSeq } + + override def cleanup(): Unit = { + log.info(s"Finished, verilog output in ${OUT_DIR / "verilog"}") + } } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailMergeCommand.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailMergeCommand.scala index ea16913..ca05a4b 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailMergeCommand.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailMergeCommand.scala @@ -3,11 +3,12 @@ package com.minres.tgc.hammer.cli import com.minres.tgc.hammer.tasks.{CopyTask, Task, TreenailTask} import org.rogach.scallop.* import os.Path -import com.minres.tgc.hammer.FileUtils.* +import com.minres.tgc.hammer.util.FileUtils.* import com.minres.tgc.hammer.Global.* import com.minres.tgc.hammer.tasks.longnail.LongnailMergeTask +import com.minres.tgc.hammer.util.{Logging, ValidationUtils} -class LongnailMergeCommand extends MySubcommand("mergeISAX") { +class LongnailMergeCommand extends MySubcommand("mergeISAX") with Logging[LongnailMergeCommand] with ValidationUtils { val inputFiles: ScallopOption[List[Path]] = trailArg[List[Path]]("inputFiles", group = mainGroup, descr = "One or multiple input files; Both .core_desc as well as .mlir are supported (also mixed)") val output: ScallopOption[Path] = opt[Path](short = 'o', default = Some("merged.mlir".path()), descr = "The .mlir file containing the merged ISAXes") val overwrite: ScallopOption[Boolean] = toggle() @@ -19,16 +20,24 @@ class LongnailMergeCommand extends MySubcommand("mergeISAX") { val (coreDSLFiles, mlirFiles) = inputFiles().partition(_.ext == "core_desc") if (inputFiles().size == 1) { + log.debug(s"Only one input file, no merge required...") if (coreDSLFiles.size == 1) { + log.trace(s"CoreDSL input file, creating translation task to MLIR...") Seq(TreenailTask(coreDSLFiles.head, output())) } else { + log.trace(s"MLIR input file, creating copy task...") Seq(CopyTask(mlirFiles.head, output())) } } else { + log.debug(s"Multiple input files, creating translation tasks to MLIR and merge task...") val treenailTasks = coreDSLFiles.map(i => TreenailTask(i, TMP_DIR / s"${i.baseName}.mlir")) val allMlirFiles = mlirFiles ++ treenailTasks.map(_.output) val concatInput = TMP_DIR / "concat.mlir" treenailTasks :+ LongnailMergeTask(allMlirFiles, concatInput, output()) } } + + override def cleanup(): Unit = { + log.info(s"Finished, merged MLIR output in ${output()}") + } } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailSchedCommand.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailSchedCommand.scala index 938875a..06adc9f 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailSchedCommand.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/LongnailSchedCommand.scala @@ -1,17 +1,19 @@ package com.minres.tgc.hammer.cli -import com.minres.tgc.hammer.FileUtils.* +import com.minres.tgc.hammer.util.FileUtils.* import com.minres.tgc.hammer.Global.* import com.minres.tgc.hammer.tasks.longnail.{LongnailMergeTask, LongnailScheduleTask} import com.minres.tgc.hammer.tasks.{Task, TreenailTask} import com.minres.tgc.hammer.tasks.longnail.SchedulingParameters +import com.minres.tgc.hammer.util.{Logging, ValidationUtils} import org.rogach.scallop.* import os.Path -class LongnailSchedCommand extends MySubcommand("scheduleISAX") with CoreSelection { +class LongnailSchedCommand extends MySubcommand("scheduleISAX") with CoreSelection with Logging[LongnailSchedCommand] with ValidationUtils { val inputFiles: ScallopOption[List[Path]] = trailArg[List[Path]]("inputFiles", group = mainGroup, descr = "One or multiple input files; Both .core_desc as well as .mlir are supported (also mixed)") val schedParams: SchedulingParameters = addConfigElement(new SchedulingParameters) + check(inputFiles)(checkPathsIsFile) banner( """Generate Scheduling information for the provided ISAXes using Longnail @@ -19,15 +21,23 @@ class LongnailSchedCommand extends MySubcommand("scheduleISAX") with CoreSelecti |""".stripMargin) override def getRequiredTasks: Seq[Task] = { + log.debug(s"Creating translation task to MLIR for CoreDSL inputs...") val (coreDSLFiles, mlirFiles) = inputFiles().partition(_.ext == "core_desc") val treenailTasks = coreDSLFiles.map(i => TreenailTask(i, TMP_DIR / s"${i.baseName}.mlir")) val allMlirFiles = mlirFiles ++ treenailTasks.map(_.output) if (allMlirFiles.size == 1) { + log.debug(s"Single input file, only creating schedule task...") treenailTasks :+ LongnailScheduleTask(allMlirFiles.head, getCoreDatasheet, schedParams) } else { val mergedInput = TMP_DIR / "merged.mlir" val concatInput = TMP_DIR / "concat.mlir" + log.debug(s"Multiple input files, creating merge and schedule task...") treenailTasks :+ LongnailMergeTask(allMlirFiles, concatInput, mergedInput) :+ LongnailScheduleTask(mergedInput, getCoreDatasheet, schedParams) } } + + override def cleanup(): Unit = { + log.info(s"Finished, scheduling solutions in ${schedParams.schedulingSolutionFile()}") + log.info(s"Scheduling selection file (KConf) in ${schedParams.schedule.schedulingSelectionFile()}") + } } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/MySubcommand.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/MySubcommand.scala index 19c60a8..c66c3f3 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/MySubcommand.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/MySubcommand.scala @@ -3,55 +3,28 @@ package com.minres.tgc.hammer.cli import com.minres.tgc.hammer.options.ConfigElement import com.minres.tgc.hammer.tasks.Task import org.rogach.scallop.{ScallopOption, ScallopOptionGroup, Subcommand, Util} +import com.minres.tgc.hammer.util.{Checker, ValidationBase} -type Checker[T] = ScallopOption[T] => Either[String,Unit] - -abstract class MySubcommand(name: String) extends Subcommand(name) { +abstract class MySubcommand(val name: String) extends Subcommand(name) with ValidationBase[ScallopOption] { protected val mainGroup: ScallopOptionGroup = group() def getRequiredTasks: Seq[Task] + def cleanup(): Unit def addConfigElement[T <: ConfigElement](elem: T): T = { elem.init(this, null) elem } - def checkPred[T](option: ScallopOption[T], predicate: T => Boolean, format: String): Either[String,Unit] = { - option.toOption.map { - case o if predicate(o) => (Right(())) - case o => Left(Util.format(format, o)) - }.getOrElse(Right(())) + + def check[T](option: ScallopOption[T])(check: Checker[T]): Unit = { + addValidation(check(option)) } - def checkPredIt[T, CC <: Iterable](option: ScallopOption[CC[T]], predicate: T => Boolean, format: String): Either[String,Unit] = { - option.toOption.map(options => { - val problems = options.filterNot(predicate) - problems match { - case Nil => Right(()) - case problem :: Nil => Left(Util.format(format, problem)) - case _ => Left(s"Multiple: ${Util.format(format, problems.mkString(", "))}") - } - }).getOrElse(Right(())) - } - - def checkPathExists: Checker[os.Path] = path => checkPred(path, os.exists, "File '%s' does not exist") - def checkPathDoesNotExist: Checker[os.Path] = path => checkPred(path, !os.exists(_), "File '%s' already exists") - def checkPathIsFile: Checker[os.Path] = path => checkPred(path, os.isFile, "File '%s' is not a file") - def checkPathIsDir: Checker[os.Path] = path => checkPred(path, os.isDir, "File '%s' is not a directory") - - - def checkPathsExists: Checker[List[os.Path]] = path => checkPredIt(path, os.exists, "File(s) '%s' do not exist") - def checkPathsDoesNotExists: Checker[List[os.Path]] = path => checkPredIt(path, !os.exists(_), "File(s) '%s' already exist") - def checkPathsIsFile: Checker[List[os.Path]] = path => checkPredIt(path, os.isFile, "File(s) '%s' are not files") - def checkPathsIsDir: Checker[List[os.Path]] = path => checkPredIt(path, os.isDir, "File(s) '%s' are not directories") - - def check[T](option: ScallopOption[T])(check: Checker[T]): Unit = addValidation(check(option)) def checkPred[T, U](condOpt: ScallopOption[U], cond: U => Boolean)(option: ScallopOption[T])(check: Checker[T]): Unit = addValidation { condOpt.toOption match { case Some(value) if cond(value) => check(option) case _ => Right(()) } } - def checkIf[T](condOpt: ScallopOption[Boolean])(option: ScallopOption[T])(check: Checker[T]): Unit = checkPred(condOpt, identity)(option)(check) - def checkNotIf[T](condOpt: ScallopOption[Boolean])(option: ScallopOption[T])(check: Checker[T]): Unit = checkPred(condOpt, !_)(option)(check) } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/TreenailCommand.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/TreenailCommand.scala index 2879e33..0b432e2 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/cli/TreenailCommand.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/cli/TreenailCommand.scala @@ -1,16 +1,18 @@ package com.minres.tgc.hammer.cli -import com.minres.tgc.hammer.FileUtils.* +import com.minres.tgc.hammer.util.FileUtils.* import com.minres.tgc.hammer.tasks.{Task, TreenailTask} +import com.minres.tgc.hammer.util.{Logging, ValidationUtils} import org.rogach.scallop.* import os.Path -class TreenailCommand extends MySubcommand("parseCoreDSL") { +class TreenailCommand extends MySubcommand("parseCoreDSL") with Logging[TreenailCommand] with ValidationUtils { val coreDSL: ScallopOption[Path] = trailArg[Path]("coreDSL", descr = "The .core_desc input file to be converted") val output: ScallopOption[Path] = opt[Path](short = 'o', default = Some("isax.mlir".path()), descr = "The .mlir file containing the converted ISAX") val overwrite: ScallopOption[Boolean] = toggle() check(coreDSL)(checkPathIsFile) + check(coreDSL)(checkPathIsCoreDSL) checkNotIf(overwrite)(output)(checkPathDoesNotExist) banner( @@ -23,4 +25,8 @@ class TreenailCommand extends MySubcommand("parseCoreDSL") { TreenailTask(coreDSL(), output()) ) } + + override def cleanup(): Unit = { + log.info(s"Finished, MLIR output in ${output()}") + } } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/options/BaseOption.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/options/BaseOption.scala index 25c4f01..0339085 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/options/BaseOption.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/options/BaseOption.scala @@ -7,7 +7,7 @@ import scala.compiletime.uninitialized trait BaseOption[T](using conv: ValueConverter[T]) extends ConfigElement { protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[T] - private var scallop: ScallopOption[T] = uninitialized + protected[options] var scallop: ScallopOption[T] = uninitialized def init(scallopConf: ScallopConf, group: ScallopOptionGroup): Unit = { scallop = createScallop(scallopConf, group) } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/options/CommandGroup.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/options/CommandGroup.scala index 80ebcfe..ff34758 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/options/CommandGroup.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/options/CommandGroup.scala @@ -11,5 +11,6 @@ class CommandGroup(name: String) extends BaseGroup { override def init(scallopConf: ScallopConf, group: ScallopOptionGroup): Unit = { options.foreach(_.init(scallopConf, group)) + initValidation(scallopConf) } } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/options/OptionGroup.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/options/OptionGroup.scala index 87fbdc8..5b4a1b4 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/options/OptionGroup.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/options/OptionGroup.scala @@ -1,18 +1,39 @@ package com.minres.tgc.hammer.options +import com.minres.tgc.hammer.util.{Checker, ValidationBase, ValidationUtils} import org.rogach.scallop.{ScallopConf, ScallopOptionGroup, ValueConverter} import os.Shellable import scala.collection.mutable -trait BaseGroup extends ConfigElement { +trait BaseGroup extends ConfigElement with ValidationBase[BaseOption] with ValidationUtils { protected val options = mutable.ListBuffer[ConfigElement]() protected def add[T <: ConfigElement](option: T): T = { options += option option } + def initValidation(scallopConf: ScallopConf): Unit = { + validations.foreach(fn => scallopConf.addValidation(fn())) + } + + private var validations: List[() => Either[String, Unit]] = Nil + + private def addValidation(fn: () => Either[String, Unit]): Unit = { + validations :+= fn + } + + def check[T](option: BaseOption[T])(check: Checker[T]): Unit = { + addValidation(() => check(option.scallop)) + } + def checkPred[T, U](condOpt: BaseOption[U], cond: U => Boolean)(option: BaseOption[T])(check: Checker[T]): Unit = addValidation { () => + condOpt.scallop.toOption match { + case Some(value) if cond(value) => check(option.scallop) + case _ => Right(()) + } + } + def value[T](cliName: String, toolName: String, descr: String = "", default: => Option[T] = None, required: Boolean = false, cliShort: Char = '\u0000')(using conv: ValueConverter[T]): ValueOption[T] = add(ValueOption(cliName, toolName, descr, default, required, cliShort)) def valueS[T](name: String, descr: String = "", default: => Option[T] = None, required: Boolean = false, cliShort: Char = '\u0000')(using conv: ValueConverter[T]): ValueOption[T] = add(ValueOption(name, name, descr, default, required, cliShort)) @@ -36,5 +57,6 @@ trait OptionGroup extends BaseGroup { def init(scallopConf: ScallopConf, g: ScallopOptionGroup): Unit = { val group = scallopConf.group(name) options.foreach(_.init(scallopConf, group)) + initValidation(scallopConf) } } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/CopyTask.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/CopyTask.scala index 1b6cc48..c74852b 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/CopyTask.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/CopyTask.scala @@ -4,7 +4,7 @@ import os.* case class CopyTask(from: Path, to: Path) extends Task { override def validate(): Unit = { - + assert(isFile(from), "Input file does not exist") } override def execute(): Unit = { diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/Task.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/Task.scala index 648945f..f6b1136 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/Task.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/Task.scala @@ -1,8 +1,9 @@ package com.minres.tgc.hammer.tasks +import com.minres.tgc.hammer.util.Logging import os.{Path, Shellable} -trait Task { +trait Task extends Logging[Task] { def validate(): Unit def execute(): Unit diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/TreenailTask.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/TreenailTask.scala index 7f72128..e3c14fd 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/TreenailTask.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/TreenailTask.scala @@ -1,7 +1,7 @@ package com.minres.tgc.hammer.tasks import com.minres.tgc.hammer.Global - +import com.minres.tgc.hammer.util.FileUtils.* import os.* case class TreenailTask(coreDSLInput: Path, output: Path) extends Task { @@ -9,6 +9,7 @@ case class TreenailTask(coreDSLInput: Path, output: Path) extends Task { override def validate(): Unit = { assert(isFile(EXECUTABLE), "Treenail Executable is missing, build Treenail") + assert(isCoreDSLFile(coreDSLInput), "Input file does not exist") } override def execute(): Unit = { runExecutable(EXECUTABLE, "-o", output, coreDSLInput) diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailHLSTask.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailHLSTask.scala index 065c3c1..92063a0 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailHLSTask.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailHLSTask.scala @@ -1,10 +1,14 @@ package com.minres.tgc.hammer.tasks.longnail -import os.Path +import com.minres.tgc.hammer.util.FileUtils.* +import os.* class LongnailHLSTask(schedulingSolutionFile: Path, schedulingSelectionFile: Option[Path], outDirectory: Path) extends LongnailBaseTask { override def validate(): Unit = { super.validate() + assert(isMLIRFile(schedulingSolutionFile), "Scheduling solution file does not exist") + schedulingSelectionFile.foreach(f => assert(isFile(f), "Scheduling selection file does not exist")) + assert(isDir(outDirectory), "Output directory does not exist") } override def execute(): Unit = { diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailMergeTask.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailMergeTask.scala index 919487e..f1fb963 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailMergeTask.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailMergeTask.scala @@ -1,10 +1,12 @@ package com.minres.tgc.hammer.tasks.longnail -import os.Path +import com.minres.tgc.hammer.util.FileUtils.* +import os.* class LongnailMergeTask(mlirFiles: Seq[Path], concatMLIR: Path, mergedMLIR: Path) extends LongnailBaseTask { override def validate(): Unit = { super.validate() + mlirFiles.foreach(f => assert(f.isMLIRFile, s"Input file $f does not exist or is not an MLIR file")) } override def execute(): Unit = { diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailScheduleTask.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailScheduleTask.scala index 7d014d1..836f60b 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailScheduleTask.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/LongnailScheduleTask.scala @@ -1,33 +1,18 @@ package com.minres.tgc.hammer.tasks.longnail -import com.minres.tgc.hammer.options.* import com.minres.tgc.hammer.tasks.longnail.LongnailBaseTask -import os.Path +import com.minres.tgc.hammer.util.FileUtils.isMLIRFile +import os.* class LongnailScheduleTask(isaxMLIR: Path, coreDatasheet: Path, params: SchedulingParameters) extends LongnailBaseTask { override def validate(): Unit = { super.validate() + assert(isMLIRFile(isaxMLIR), "Input file does not exist") + assert(isFile(coreDatasheet), "Core datasheet does not exist") } override def execute(): Unit = { - /*runExecutable(EXECUTABLE, - "--lower-coredsl-to-lil", - "--max-unroll-factor", params.maxLoopUnrollFactor, - "--schedule-lil", - "--datasheet", params.base.coreDatasheet, - "--library", params.base.cellLibrary, - "--opTyLibrary", params.base.opTyLibrary, - "--clockTime", params.base.clockPeriod, - "--schedulingAlgo", params.base.schedulingAlgo, - "--solver", params.base.ilpSolver, - "--schedulingTimeout", params.schedulingTimeout, - "--schedRefineTimeout", params.schedulingRefineTimeout, - "--solSelKconfPath", params.schedulingKconf, - "--verbose", params.base.verbose.toString, - "-o", params.schedulingMLIR, - isaxMLIR - )*/ runExecutable(EXECUTABLE, "--lower-coredsl-to-lil", params.getToolParameters, diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/SchedulingParameters.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/SchedulingParameters.scala index 74106f4..6fec9a6 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/SchedulingParameters.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/tasks/longnail/SchedulingParameters.scala @@ -1,6 +1,6 @@ package com.minres.tgc.hammer.tasks.longnail -import com.minres.tgc.hammer.FileUtils.* +import com.minres.tgc.hammer.util.FileUtils.* import com.minres.tgc.hammer.Global.OUT_DIR import com.minres.tgc.hammer.options.* import org.rogach.scallop.* @@ -9,22 +9,26 @@ import os.Path class SchedulingParameters extends OptionGroup { override def name: String = "Longnail Scheduling Args" - add(new CommandGroup("--prepare-schedule-lil") { - choiceS(Seq("LEGACY", "MS", "PAMS", "PARAMS", "MI_MS", "MI_PAMS", "MI_PARAMS"), name = "schedulingAlgo", default = Some("LEGACY"), descr = + class PrepareOptions extends CommandGroup("--prepare-schedule-lil") { + val schedulingAlgo = choiceS(Seq("LEGACY", "MS", "PAMS", "PARAMS", "MI_MS", "MI_PAMS", "MI_PARAMS"), name = "schedulingAlgo", default = Some("LEGACY"), descr = "Scheduling algorithm used by Longnail; Modulo Scheduling (MS) can be extended with Predicate-aware (PA) and Resource-aware (RA), Inter-Instruction sharing is activated in the MI variants") - value[Path](cliName = "cellLibrary", toolName = "library", descr = "The cell library used by Longnail (example: longnail/test/LILToHW/longnail*.yaml") - valueS[Path](name = "opTyLibrary", descr = "The operator type model used for detailed data (e.g. from OL SKY130 in longnail/opTyLibraries/OL2.yaml)") - }) + val cellLibrary = value[Path](cliName = "cellLibrary", toolName = "library", descr = "The cell library used by Longnail (example: longnail/test/LILToHW/longnail*.yaml") + val opTyLibrary = valueS[Path](name = "opTyLibrary", descr = "The operator type model used for detailed data (e.g. from OL SKY130 in longnail/opTyLibraries/OL2.yaml)") + check(cellLibrary)(checkPathIsFile) + check(opTyLibrary)(checkPathIsFile) + } + val prepare = add(new PrepareOptions) - add(new CommandGroup("--schedule-lil") { - valueS[Int](name = "schedulingTimeout", default = Some(10), descr = "Longnail scheduling timeout in seconds") - value[Int](cliName = "schedulingRefineTimeout", toolName = "schedRefineTimeout", default = Some(10), descr = "Longnail schedule refinement timeout in seconds") - value[Path](cliName = "schedulingKconf", toolName = "solSelKconfPath", default = Some(OUT_DIR / "scheduling.KConfig"), descr = "Path for the created KConfig file for selecting a scheduling solution") - choice(Seq("CBC", "GLPK", "SCIP", "HIGHS", "GUROBI", "CPLEX", "XPRESS", "COPT"), cliName = "ilpSolver", toolName = "solver", default = Some("CBC"), descr = "The ILP solver used by Longnail; currently only CBC is tested") - toggle(cliName = "verboseSched", toolName = "verbose", descr = "Enable verbose ILP solver messages") - value[Int](cliName = "clockPeriod", toolName = "clockTime", descr = "The target clock period; uses same time unit as delays in opTyLibrary") - }) + class ScheduleOptions extends CommandGroup("--schedule-lil") { + val schedulingTimeout = valueS[Int](name = "schedulingTimeout", default = Some(10), descr = "Longnail scheduling timeout in seconds") + val schedulingRefineTimeout = value[Int](cliName = "schedulingRefineTimeout", toolName = "schedRefineTimeout", default = Some(10), descr = "Longnail schedule refinement timeout in seconds") + val schedulingSelectionFile = value[Path](cliName = "schedulingKconf", toolName = "solSelKconfPath", default = Some(OUT_DIR / "scheduling.KConfig"), descr = "Path for the created KConfig file for selecting a scheduling solution") + val ilpSolver = choice(Seq("CBC", "GLPK", "SCIP", "HIGHS", "GUROBI", "CPLEX", "XPRESS", "COPT"), cliName = "ilpSolver", toolName = "solver", default = Some("CBC"), descr = "The ILP solver used by Longnail; currently only CBC is tested") + val verboseSched = toggle(cliName = "verboseSched", toolName = "verbose", descr = "Enable verbose ILP solver messages") + val clockPeriod = value[Int](cliName = "clockPeriod", toolName = "clockTime", descr = "The target clock period; uses same time unit as delays in opTyLibrary") + } + val schedule = add(new ScheduleOptions) - value[Int](cliName = "--maxLoopUnrollFactor", toolName = "max-unroll-factor", default = Some(16), descr = "Longnail max loop unroll factor") - val schedulingSolutionFile: ValueOption[Path] = value[Path](cliName = "--schedulingMLIR", toolName = "o", default = Some(OUT_DIR / "scheduling_solutions.mlir"), descr = "Output file with different scheduling solutions") + val maxLoopUnrollFactor = value[Int](cliName = "--maxLoopUnrollFactor", toolName = "max-unroll-factor", default = Some(16), descr = "Longnail max loop unroll factor") + val schedulingSolutionFile = value[Path](cliName = "--schedulingMLIR", toolName = "o", default = Some(OUT_DIR / "scheduling_solutions.mlir"), descr = "Output file with different scheduling solutions") } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/FileUtils.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/util/FileUtils.scala similarity index 79% rename from toolflow/src/main/scala/com/minres/tgc/hammer/FileUtils.scala rename to toolflow/src/main/scala/com/minres/tgc/hammer/util/FileUtils.scala index 26b496f..bac0e6e 100644 --- a/toolflow/src/main/scala/com/minres/tgc/hammer/FileUtils.scala +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/util/FileUtils.scala @@ -1,8 +1,8 @@ -package com.minres.tgc.hammer +package com.minres.tgc.hammer.util +import com.minres.tgc.hammer.Global.BASE_DIR import org.rogach.scallop.{ScallopOption, Util, ValueConverter, listArgConverter, singleArgConverter} import os.* -import Global.* import scala.collection.IterableOps @@ -13,6 +13,10 @@ object FileUtils { path / os.up / newName } + extension (x: os.Path) + def isMLIRFile: Boolean = isFile(x) && x.ext == "mlir" + def isCoreDSLFile: Boolean = isFile(x) && x.ext == "core_desc" + extension (x: String) def asPath(relative: Path): Path = { Path(x, relative) @@ -31,4 +35,5 @@ object FileUtils { implicit val osPathRelBaseListConverter: ValueConverter[List[Path]] = { listArgConverter[Path](_.asPath(BASE_DIR)) } + } diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/util/Logging.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/util/Logging.scala new file mode 100644 index 0000000..f48f770 --- /dev/null +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/util/Logging.scala @@ -0,0 +1,13 @@ +package com.minres.tgc.hammer.util + +import com.typesafe.scalalogging.Logger + +import scala.reflect.ClassTag + +trait Logging[T: ClassTag] { + protected val log: Logger = Logger[T] + + protected def assert(boolean: Boolean, msg: String): Unit = { + if (!boolean) log.error(msg) + } +} diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/util/ValidationBase.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/util/ValidationBase.scala new file mode 100644 index 0000000..da566fa --- /dev/null +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/util/ValidationBase.scala @@ -0,0 +1,10 @@ +package com.minres.tgc.hammer.util + +import org.rogach.scallop.ScallopOption + +trait ValidationBase[O[_]] { + def check[T](option: O[T])(check: Checker[T]): Unit + def checkPred[T, U](condOpt: O[U], cond: U => Boolean)(option: O[T])(check: Checker[T]): Unit + def checkIf[T](condOpt: O[Boolean])(option: O[T])(check: Checker[T]): Unit = checkPred(condOpt, identity)(option)(check) + def checkNotIf[T](condOpt: O[Boolean])(option: O[T])(check: Checker[T]): Unit = checkPred(condOpt, !_)(option)(check) +} diff --git a/toolflow/src/main/scala/com/minres/tgc/hammer/util/ValidationUtils.scala b/toolflow/src/main/scala/com/minres/tgc/hammer/util/ValidationUtils.scala new file mode 100644 index 0000000..ed939e3 --- /dev/null +++ b/toolflow/src/main/scala/com/minres/tgc/hammer/util/ValidationUtils.scala @@ -0,0 +1,44 @@ +package com.minres.tgc.hammer.util + +import org.rogach.scallop.{ScallopOption, Util} + + +type Checker[T] = ScallopOption[T] => Either[String,Unit] + +trait ValidationUtils { this: ValidationBase[?] => + + + private def checkPredicate[T](option: ScallopOption[T], predicate: T => Boolean, format: String): Either[String,Unit] = { + option.toOption.map { + case o if predicate(o) => Right(()) + case o => Left(Util.format(format, o)) + }.getOrElse(Right(())) + } + + private def checkPredicateIt[T, CC <: Iterable](option: ScallopOption[CC[T]], predicate: T => Boolean, format: String): Either[String,Unit] = { + option.toOption.map(options => { + val problems = options.filterNot(predicate) + problems match { + case Nil => Right(()) + case problem :: Nil => Left(Util.format(format, problem)) + case _ => Left(s"Multiple: ${Util.format(format, problems.mkString(", "))}") + } + }).getOrElse(Right(())) + } + + def checkPathExists: Checker[os.Path] = path => checkPredicate(path, os.exists, "File '%s' does not exist") + def checkPathDoesNotExist: Checker[os.Path] = path => checkPredicate(path, !os.exists(_), "File '%s' already exists") + def checkPathIsFile: Checker[os.Path] = path => checkPredicate(path, os.isFile, "File '%s' is not a file") + def checkPathIsDir: Checker[os.Path] = path => checkPredicate(path, os.isDir, "File '%s' is not a directory") + + + def checkPathsExists: Checker[List[os.Path]] = path => checkPredicateIt(path, os.exists, "File(s) '%s' do not exist") + def checkPathsDoesNotExists: Checker[List[os.Path]] = path => checkPredicateIt(path, !os.exists(_), "File(s) '%s' already exist") + def checkPathsIsFile: Checker[List[os.Path]] = path => checkPredicateIt(path, os.isFile, "File(s) '%s' are not files") + def checkPathsIsDir: Checker[List[os.Path]] = path => checkPredicateIt(path, os.isDir, "File(s) '%s' are not directories") + + def checkPathIsCoreDSL: Checker[os.Path] = path => checkPredicate(path, p => p.ext == "core_desc", "File '%s' is not a CoreDSL file") + + + +}