Add logging and update validation

This commit is contained in:
2025-10-06 16:27:06 +02:00
parent 22a1f31683
commit be691c96be
22 changed files with 209 additions and 93 deletions

View File

@@ -1,6 +1,5 @@
package com.minres.tgc.hammer package com.minres.tgc.hammer
import com.minres.tgc.hammer.FileUtils.*
import os.Path import os.Path
object Global { object Global {

View File

@@ -11,7 +11,7 @@ object Main {
var conf: HammerConf = uninitialized var conf: HammerConf = uninitialized
def main(args: Array[String]): Unit = { def main(args: Array[String]): Unit = {
var logger = Logger("TGCHammer") val logger = Logger("TGCHammer")
conf = new HammerConf(args.toIndexedSeq) conf = new HammerConf(args.toIndexedSeq)
val logLevel = conf.verbose() match { 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) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).asInstanceOf[ch.qos.logback.classic.Logger].setLevel(logLevel)
os.makeDir(Global.OUT_DIR) logger.trace(s"Creating output directory ${Global.OUT_DIR}")
os.makeDir(Global.TMP_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 { conf.subcommand match {
case Some(c: MySubcommand) => case Some(c: MySubcommand) =>
logger.info(s"Executing subcommand ${c.name}")
val tasks = c.getRequiredTasks val tasks = c.getRequiredTasks
logger.debug(s"Subcommand requires ${tasks.size} tasks")
logger.info(s"Validating tasks")
tasks.foreach(_.validate()) tasks.foreach(_.validate())
logger.info(s"Executing tasks")
tasks.foreach(_.execute()) tasks.foreach(_.execute())
case _ => case _ =>
logger.error(s"Found no subcommand, see help below")
conf.printHelp()
} }
} }
} }

View File

@@ -2,16 +2,25 @@ package com.minres.tgc.hammer.cli
import org.rogach.scallop.* import org.rogach.scallop.*
import os.Path import os.Path
import com.minres.tgc.hammer.FileUtils.* import com.minres.tgc.hammer.util.FileUtils.*
class HammerConf(arguments: Seq[String]) extends ScallopConf(arguments) { class HammerConf(arguments: Seq[String]) extends ScallopConf(arguments) {
val outputDirectory: ScallopOption[Path] = opt[Path](default = Some("output".path()), descr = "The base output directory") 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 LongnailCommand)
addSubcommand(new TreenailCommand) addSubcommand(new TreenailCommand)
addSubcommand(new LongnailMergeCommand)
addSubcommand(new LongnailSchedCommand) 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() verify()
} }

View File

@@ -1,19 +1,22 @@
package com.minres.tgc.hammer.cli 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.Global.*
import com.minres.tgc.hammer.tasks.{Task, TreenailTask} import com.minres.tgc.hammer.tasks.{Task, TreenailTask}
import com.minres.tgc.hammer.tasks.longnail.{LongnailHLSTask, LongnailMergeTask, LongnailScheduleTask, SchedulingParameters} import com.minres.tgc.hammer.tasks.longnail.{LongnailHLSTask, LongnailMergeTask, LongnailScheduleTask, SchedulingParameters}
import com.minres.tgc.hammer.util.{Logging, ValidationUtils}
import org.rogach.scallop.* import org.rogach.scallop.*
import os.Path import os.Path
import scala.collection.mutable 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 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 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) val schedParams: SchedulingParameters = addConfigElement(new SchedulingParameters)
check(inputFiles)(checkPathsIsFile)
banner( banner(
"""Run the complete longnail flow from CoreDSL/MLIR to receive a SystemVerilog representation of the ISAXes """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 |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 += LongnailMergeTask(allMlirFiles, concatInput, mergedInput)
tasks += LongnailScheduleTask(mergedInput, getCoreDatasheet, schedParams) tasks += LongnailScheduleTask(mergedInput, getCoreDatasheet, schedParams)
} }
val verilogOutput = OUT_DIR / "verilog"
os.makeDir.all(verilogOutput)
if (useMinIISolution()) { if (useMinIISolution()) {
tasks += LongnailHLSTask(schedParams.schedulingSolutionFile(), None, OUT_DIR) tasks += LongnailHLSTask(schedParams.schedulingSolutionFile(), None, verilogOutput)
} else { } else {
??? ???
} }
tasks.toSeq tasks.toSeq
} }
override def cleanup(): Unit = {
log.info(s"Finished, verilog output in ${OUT_DIR / "verilog"}")
}
} }

View File

@@ -3,11 +3,12 @@ package com.minres.tgc.hammer.cli
import com.minres.tgc.hammer.tasks.{CopyTask, Task, TreenailTask} import com.minres.tgc.hammer.tasks.{CopyTask, Task, TreenailTask}
import org.rogach.scallop.* import org.rogach.scallop.*
import os.Path 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.Global.*
import com.minres.tgc.hammer.tasks.longnail.LongnailMergeTask 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 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 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() val overwrite: ScallopOption[Boolean] = toggle()
@@ -19,16 +20,24 @@ class LongnailMergeCommand extends MySubcommand("mergeISAX") {
val (coreDSLFiles, mlirFiles) = inputFiles().partition(_.ext == "core_desc") val (coreDSLFiles, mlirFiles) = inputFiles().partition(_.ext == "core_desc")
if (inputFiles().size == 1) { if (inputFiles().size == 1) {
log.debug(s"Only one input file, no merge required...")
if (coreDSLFiles.size == 1) { if (coreDSLFiles.size == 1) {
log.trace(s"CoreDSL input file, creating translation task to MLIR...")
Seq(TreenailTask(coreDSLFiles.head, output())) Seq(TreenailTask(coreDSLFiles.head, output()))
} else { } else {
log.trace(s"MLIR input file, creating copy task...")
Seq(CopyTask(mlirFiles.head, output())) Seq(CopyTask(mlirFiles.head, output()))
} }
} else { } 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 treenailTasks = coreDSLFiles.map(i => TreenailTask(i, TMP_DIR / s"${i.baseName}.mlir"))
val allMlirFiles = mlirFiles ++ treenailTasks.map(_.output) val allMlirFiles = mlirFiles ++ treenailTasks.map(_.output)
val concatInput = TMP_DIR / "concat.mlir" val concatInput = TMP_DIR / "concat.mlir"
treenailTasks :+ LongnailMergeTask(allMlirFiles, concatInput, output()) treenailTasks :+ LongnailMergeTask(allMlirFiles, concatInput, output())
} }
} }
override def cleanup(): Unit = {
log.info(s"Finished, merged MLIR output in ${output()}")
}
} }

View File

@@ -1,17 +1,19 @@
package com.minres.tgc.hammer.cli 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.Global.*
import com.minres.tgc.hammer.tasks.longnail.{LongnailMergeTask, LongnailScheduleTask} import com.minres.tgc.hammer.tasks.longnail.{LongnailMergeTask, LongnailScheduleTask}
import com.minres.tgc.hammer.tasks.{Task, TreenailTask} import com.minres.tgc.hammer.tasks.{Task, TreenailTask}
import com.minres.tgc.hammer.tasks.longnail.SchedulingParameters import com.minres.tgc.hammer.tasks.longnail.SchedulingParameters
import com.minres.tgc.hammer.util.{Logging, ValidationUtils}
import org.rogach.scallop.* import org.rogach.scallop.*
import os.Path 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 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) val schedParams: SchedulingParameters = addConfigElement(new SchedulingParameters)
check(inputFiles)(checkPathsIsFile)
banner( banner(
"""Generate Scheduling information for the provided ISAXes using Longnail """Generate Scheduling information for the provided ISAXes using Longnail
@@ -19,15 +21,23 @@ class LongnailSchedCommand extends MySubcommand("scheduleISAX") with CoreSelecti
|""".stripMargin) |""".stripMargin)
override def getRequiredTasks: Seq[Task] = { 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 (coreDSLFiles, mlirFiles) = inputFiles().partition(_.ext == "core_desc")
val treenailTasks = coreDSLFiles.map(i => TreenailTask(i, TMP_DIR / s"${i.baseName}.mlir")) val treenailTasks = coreDSLFiles.map(i => TreenailTask(i, TMP_DIR / s"${i.baseName}.mlir"))
val allMlirFiles = mlirFiles ++ treenailTasks.map(_.output) val allMlirFiles = mlirFiles ++ treenailTasks.map(_.output)
if (allMlirFiles.size == 1) { if (allMlirFiles.size == 1) {
log.debug(s"Single input file, only creating schedule task...")
treenailTasks :+ LongnailScheduleTask(allMlirFiles.head, getCoreDatasheet, schedParams) treenailTasks :+ LongnailScheduleTask(allMlirFiles.head, getCoreDatasheet, schedParams)
} else { } else {
val mergedInput = TMP_DIR / "merged.mlir" val mergedInput = TMP_DIR / "merged.mlir"
val concatInput = TMP_DIR / "concat.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) 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()}")
}
} }

View File

@@ -3,55 +3,28 @@ package com.minres.tgc.hammer.cli
import com.minres.tgc.hammer.options.ConfigElement import com.minres.tgc.hammer.options.ConfigElement
import com.minres.tgc.hammer.tasks.Task import com.minres.tgc.hammer.tasks.Task
import org.rogach.scallop.{ScallopOption, ScallopOptionGroup, Subcommand, Util} 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(val name: String) extends Subcommand(name) with ValidationBase[ScallopOption] {
abstract class MySubcommand(name: String) extends Subcommand(name) {
protected val mainGroup: ScallopOptionGroup = group() protected val mainGroup: ScallopOptionGroup = group()
def getRequiredTasks: Seq[Task] def getRequiredTasks: Seq[Task]
def cleanup(): Unit
def addConfigElement[T <: ConfigElement](elem: T): T = { def addConfigElement[T <: ConfigElement](elem: T): T = {
elem.init(this, null) elem.init(this, null)
elem elem
} }
def checkPred[T](option: ScallopOption[T], predicate: T => Boolean, format: String): Either[String,Unit] = {
option.toOption.map { def check[T](option: ScallopOption[T])(check: Checker[T]): Unit = {
case o if predicate(o) => (Right(())) addValidation(check(option))
case o => Left(Util.format(format, o))
}.getOrElse(Right(()))
} }
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 { def checkPred[T, U](condOpt: ScallopOption[U], cond: U => Boolean)(option: ScallopOption[T])(check: Checker[T]): Unit = addValidation {
condOpt.toOption match { condOpt.toOption match {
case Some(value) if cond(value) => check(option) case Some(value) if cond(value) => check(option)
case _ => Right(()) 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)
} }

View File

@@ -1,16 +1,18 @@
package com.minres.tgc.hammer.cli 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.tasks.{Task, TreenailTask}
import com.minres.tgc.hammer.util.{Logging, ValidationUtils}
import org.rogach.scallop.* import org.rogach.scallop.*
import os.Path 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 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 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() val overwrite: ScallopOption[Boolean] = toggle()
check(coreDSL)(checkPathIsFile) check(coreDSL)(checkPathIsFile)
check(coreDSL)(checkPathIsCoreDSL)
checkNotIf(overwrite)(output)(checkPathDoesNotExist) checkNotIf(overwrite)(output)(checkPathDoesNotExist)
banner( banner(
@@ -23,4 +25,8 @@ class TreenailCommand extends MySubcommand("parseCoreDSL") {
TreenailTask(coreDSL(), output()) TreenailTask(coreDSL(), output())
) )
} }
override def cleanup(): Unit = {
log.info(s"Finished, MLIR output in ${output()}")
}
} }

View File

@@ -7,7 +7,7 @@ import scala.compiletime.uninitialized
trait BaseOption[T](using conv: ValueConverter[T]) extends ConfigElement { trait BaseOption[T](using conv: ValueConverter[T]) extends ConfigElement {
protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[T] 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 = { def init(scallopConf: ScallopConf, group: ScallopOptionGroup): Unit = {
scallop = createScallop(scallopConf, group) scallop = createScallop(scallopConf, group)
} }

View File

@@ -11,5 +11,6 @@ class CommandGroup(name: String) extends BaseGroup {
override def init(scallopConf: ScallopConf, group: ScallopOptionGroup): Unit = { override def init(scallopConf: ScallopConf, group: ScallopOptionGroup): Unit = {
options.foreach(_.init(scallopConf, group)) options.foreach(_.init(scallopConf, group))
initValidation(scallopConf)
} }
} }

View File

@@ -1,18 +1,39 @@
package com.minres.tgc.hammer.options package com.minres.tgc.hammer.options
import com.minres.tgc.hammer.util.{Checker, ValidationBase, ValidationUtils}
import org.rogach.scallop.{ScallopConf, ScallopOptionGroup, ValueConverter} import org.rogach.scallop.{ScallopConf, ScallopOptionGroup, ValueConverter}
import os.Shellable import os.Shellable
import scala.collection.mutable import scala.collection.mutable
trait BaseGroup extends ConfigElement { trait BaseGroup extends ConfigElement with ValidationBase[BaseOption] with ValidationUtils {
protected val options = mutable.ListBuffer[ConfigElement]() protected val options = mutable.ListBuffer[ConfigElement]()
protected def add[T <: ConfigElement](option: T): T = { protected def add[T <: ConfigElement](option: T): T = {
options += option options += option
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 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)) 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 = { def init(scallopConf: ScallopConf, g: ScallopOptionGroup): Unit = {
val group = scallopConf.group(name) val group = scallopConf.group(name)
options.foreach(_.init(scallopConf, group)) options.foreach(_.init(scallopConf, group))
initValidation(scallopConf)
} }
} }

View File

@@ -4,7 +4,7 @@ import os.*
case class CopyTask(from: Path, to: Path) extends Task { case class CopyTask(from: Path, to: Path) extends Task {
override def validate(): Unit = { override def validate(): Unit = {
assert(isFile(from), "Input file does not exist")
} }
override def execute(): Unit = { override def execute(): Unit = {

View File

@@ -1,8 +1,9 @@
package com.minres.tgc.hammer.tasks package com.minres.tgc.hammer.tasks
import com.minres.tgc.hammer.util.Logging
import os.{Path, Shellable} import os.{Path, Shellable}
trait Task { trait Task extends Logging[Task] {
def validate(): Unit def validate(): Unit
def execute(): Unit def execute(): Unit

View File

@@ -1,7 +1,7 @@
package com.minres.tgc.hammer.tasks package com.minres.tgc.hammer.tasks
import com.minres.tgc.hammer.Global import com.minres.tgc.hammer.Global
import com.minres.tgc.hammer.util.FileUtils.*
import os.* import os.*
case class TreenailTask(coreDSLInput: Path, output: Path) extends Task { 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 = { override def validate(): Unit = {
assert(isFile(EXECUTABLE), "Treenail Executable is missing, build Treenail") assert(isFile(EXECUTABLE), "Treenail Executable is missing, build Treenail")
assert(isCoreDSLFile(coreDSLInput), "Input file does not exist")
} }
override def execute(): Unit = { override def execute(): Unit = {
runExecutable(EXECUTABLE, "-o", output, coreDSLInput) runExecutable(EXECUTABLE, "-o", output, coreDSLInput)

View File

@@ -1,10 +1,14 @@
package com.minres.tgc.hammer.tasks.longnail 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 { class LongnailHLSTask(schedulingSolutionFile: Path, schedulingSelectionFile: Option[Path], outDirectory: Path) extends LongnailBaseTask {
override def validate(): Unit = { override def validate(): Unit = {
super.validate() 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 = { override def execute(): Unit = {

View File

@@ -1,10 +1,12 @@
package com.minres.tgc.hammer.tasks.longnail 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 { class LongnailMergeTask(mlirFiles: Seq[Path], concatMLIR: Path, mergedMLIR: Path) extends LongnailBaseTask {
override def validate(): Unit = { override def validate(): Unit = {
super.validate() 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 = { override def execute(): Unit = {

View File

@@ -1,33 +1,18 @@
package com.minres.tgc.hammer.tasks.longnail package com.minres.tgc.hammer.tasks.longnail
import com.minres.tgc.hammer.options.*
import com.minres.tgc.hammer.tasks.longnail.LongnailBaseTask 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 { class LongnailScheduleTask(isaxMLIR: Path, coreDatasheet: Path, params: SchedulingParameters) extends LongnailBaseTask {
override def validate(): Unit = { override def validate(): Unit = {
super.validate() super.validate()
assert(isMLIRFile(isaxMLIR), "Input file does not exist")
assert(isFile(coreDatasheet), "Core datasheet does not exist")
} }
override def execute(): Unit = { 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, runExecutable(EXECUTABLE,
"--lower-coredsl-to-lil", "--lower-coredsl-to-lil",
params.getToolParameters, params.getToolParameters,

View File

@@ -1,6 +1,6 @@
package com.minres.tgc.hammer.tasks.longnail 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.Global.OUT_DIR
import com.minres.tgc.hammer.options.* import com.minres.tgc.hammer.options.*
import org.rogach.scallop.* import org.rogach.scallop.*
@@ -9,22 +9,26 @@ import os.Path
class SchedulingParameters extends OptionGroup { class SchedulingParameters extends OptionGroup {
override def name: String = "Longnail Scheduling Args" override def name: String = "Longnail Scheduling Args"
add(new CommandGroup("--prepare-schedule-lil") { class PrepareOptions extends CommandGroup("--prepare-schedule-lil") {
choiceS(Seq("LEGACY", "MS", "PAMS", "PARAMS", "MI_MS", "MI_PAMS", "MI_PARAMS"), name = "schedulingAlgo", default = Some("LEGACY"), descr = 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") "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") val cellLibrary = 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 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") { class ScheduleOptions extends CommandGroup("--schedule-lil") {
valueS[Int](name = "schedulingTimeout", default = Some(10), descr = "Longnail scheduling timeout in seconds") val schedulingTimeout = 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") val schedulingRefineTimeout = 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") 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")
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 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")
toggle(cliName = "verboseSched", toolName = "verbose", descr = "Enable verbose ILP solver messages") val verboseSched = 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") 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 maxLoopUnrollFactor = 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 schedulingSolutionFile = value[Path](cliName = "--schedulingMLIR", toolName = "o", default = Some(OUT_DIR / "scheduling_solutions.mlir"), descr = "Output file with different scheduling solutions")
} }

View File

@@ -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 org.rogach.scallop.{ScallopOption, Util, ValueConverter, listArgConverter, singleArgConverter}
import os.* import os.*
import Global.*
import scala.collection.IterableOps import scala.collection.IterableOps
@@ -13,6 +13,10 @@ object FileUtils {
path / os.up / newName 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) extension (x: String)
def asPath(relative: Path): Path = { def asPath(relative: Path): Path = {
Path(x, relative) Path(x, relative)
@@ -31,4 +35,5 @@ object FileUtils {
implicit val osPathRelBaseListConverter: ValueConverter[List[Path]] = { implicit val osPathRelBaseListConverter: ValueConverter[List[Path]] = {
listArgConverter[Path](_.asPath(BASE_DIR)) listArgConverter[Path](_.asPath(BASE_DIR))
} }
} }

View File

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

View File

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

View File

@@ -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")
}