Compare commits

...

29 Commits

Author SHA1 Message Date
25c93b8a73 add keccak example (WIP) 2025-10-07 14:57:13 +02:00
4dcb6efcef fix init workspace 2025-10-07 14:56:56 +02:00
420e50f23a Use TMP_DIR for calls 2025-10-07 14:54:09 +02:00
6c7e7f496b Update readme
Add workspace template
2025-10-07 14:15:26 +02:00
44ab3f4622 update logging 2025-10-07 14:12:59 +02:00
2738df3bff Add default to printToolOutput option 2025-10-06 18:34:37 +02:00
80c9d458b4 improve exception handling 2025-10-06 18:33:36 +02:00
1b450758d3 fix descr 2025-10-06 18:30:46 +02:00
7954a9dbe3 Handle tool output 2025-10-06 18:29:03 +02:00
3824dd4d29 fix scheduling parameters 2025-10-06 17:47:50 +02:00
32e626a5a1 fix and validate core selection 2025-10-06 17:45:04 +02:00
e6f3474fd9 Fix task execution andlogging 2025-10-06 17:38:40 +02:00
5218ed37b1 fix defaults 2025-10-06 17:29:33 +02:00
3d981d16ed fix defaults 2025-10-06 17:15:59 +02:00
9d0c71dedd fix 2025-10-06 16:38:42 +02:00
3858761e66 Lazy init default values 2025-10-06 16:37:18 +02:00
be691c96be Add logging and update validation 2025-10-06 16:27:06 +02:00
22a1f31683 Adding in/out file validation 2025-10-02 20:47:12 +02:00
47614ad47f Add Longnail complete command 2025-10-02 18:23:31 +02:00
69b63d93da Update file handling and descriptions 2025-10-02 12:46:32 +02:00
9f5c9b4fc3 Fix launch of external program 2025-10-02 10:22:38 +02:00
66fb0e24c8 fix command groups 2025-10-01 18:53:49 +02:00
39f0e052c1 debug schedule task 2025-10-01 18:48:59 +02:00
67ff78c8bc Merge remote-tracking branch 'refs/remotes/origin/main' 2025-10-01 18:32:29 +02:00
4fd033ee82 Add command group 2025-10-01 18:31:43 +02:00
78a96932d7 Create envrc in workspace 2025-10-01 18:13:30 +02:00
8a5262d117 Fix missing parameters 2025-10-01 18:13:06 +02:00
ea43453f07 Merge commit '93dc3dd955acfe259f4ed4033fa68e5d19423e0b' 2025-10-01 18:01:46 +02:00
eaf935177a Remove basedir config 2025-10-01 17:59:30 +02:00
40 changed files with 888 additions and 163 deletions

View File

@@ -1,3 +1,5 @@
# TGC Hammer # TGC Hammer
This is TGC-Hammer, the HLS flow for custom ISA-Extensions. This is the base repo TGC-Hammer, the HLS flow for custom ISA-Extensions. It is used for building the toolflow executable, as well as the various tools (Longnail, SCAIE-V, ...).
To use it, create a new workspace folder by calling the `init-workspace.sh` script from an empty folder. This will create the base workspace structure, including a README about usage details. The workspace will always use the executables from this repo, so if you rebuild them, the new ones will automatically be used.

View File

@@ -29,5 +29,7 @@ else
echo "export TGC_HAMMER_WORKDIR=\"$PWD\"" >> source.sh echo "export TGC_HAMMER_WORKDIR=\"$PWD\"" >> source.sh
echo "export PATH=\"${TGC_HAMMER_HOME}/toolflow/target/universal/stage/bin:$PATH\"" >> source.sh echo "export PATH=\"${TGC_HAMMER_HOME}/toolflow/target/universal/stage/bin:$PATH\"" >> source.sh
cp -r ${TGC_HAMMER_HOME}/wsTemplate/. $PWD
mkdir -p output mkdir -p output
fi fi

View File

@@ -11,5 +11,7 @@ lazy val root = project
libraryDependencies += "org.rogach" %% "scallop" % "5.2.0", libraryDependencies += "org.rogach" %% "scallop" % "5.2.0",
libraryDependencies += "com.lihaoyi" %% "os-lib" % "0.11.5", libraryDependencies += "com.lihaoyi" %% "os-lib" % "0.11.5",
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.6",
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.5.19",
libraryDependencies += "org.scalameta" %% "munit" % "1.2.0" % Test libraryDependencies += "org.scalameta" %% "munit" % "1.2.0" % Test
) )

View File

@@ -1,11 +0,0 @@
package com.minres.tgc.hammer
import os.*
object FileUtils {
def changeExtension(path: os.Path, newExt: String): os.Path = {
val baseName = path.baseName
val newName = s"$baseName.$newExt"
path / os.up / newName
}
}

View File

@@ -8,6 +8,9 @@ object Global {
lazy val WORKDIR: Path = pathFromEnv("TGC_HAMMER_WORKDIR").get lazy val WORKDIR: Path = pathFromEnv("TGC_HAMMER_WORKDIR").get
lazy val TREENAIL: Path = HAMMER / "deps" / "treenail" lazy val TREENAIL: Path = HAMMER / "deps" / "treenail"
lazy val LONGNAIL: Path = HAMMER / "deps" / "longnail" lazy val LONGNAIL: Path = HAMMER / "deps" / "longnail"
lazy val BASE_DIR: Path = Path(Main.conf.baseDirectory(), os.pwd) lazy val BASE_DIR: Path = os.pwd
def OUT_DIR: Path = Main.conf.outputDirectory()
def TMP_DIR: Path = OUT_DIR / "tmp"
def LOG_DIR: Path = OUT_DIR / "logs"
lazy val CORE_DATASHEETS: Path = HAMMER / "coreDatasheets" lazy val CORE_DATASHEETS: Path = HAMMER / "coreDatasheets"
} }

View File

@@ -1,6 +1,10 @@
package com.minres.tgc.hammer package com.minres.tgc.hammer
import ch.qos.logback.classic.Level
import com.minres.tgc.hammer.cli.{HammerConf, MySubcommand} import com.minres.tgc.hammer.cli.{HammerConf, MySubcommand}
import com.minres.tgc.hammer.util.AssertException
import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory
import scala.compiletime.uninitialized import scala.compiletime.uninitialized
@@ -8,14 +12,41 @@ object Main {
var conf: HammerConf = uninitialized var conf: HammerConf = uninitialized
def main(args: Array[String]): Unit = { def main(args: Array[String]): Unit = {
val logger = Logger("TGCHammer")
conf = new HammerConf(args.toIndexedSeq) conf = new HammerConf(args.toIndexedSeq)
val logLevel = conf.verbose() match {
case 0 => Level.INFO
case 1 => Level.INFO
case 2 => Level.DEBUG
case _ => Level.TRACE
}
LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).asInstanceOf[ch.qos.logback.classic.Logger].setLevel(logLevel)
logger.trace(s"Creating output directory ${Global.OUT_DIR}")
if (conf.emptyOutputDir()) os.remove.all(Global.OUT_DIR)
os.makeDir.all(Global.OUT_DIR)
os.makeDir.all(Global.LOG_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
tasks.foreach(_.validate()) logger.debug(s"Subcommand requires ${tasks.size} tasks")
tasks.foreach(_.execute()) try {
tasks.foreach(_.run())
c.cleanup()
} catch {
case e: AssertException =>
logger.error(s"Error during task execution, see above!")
case e: Exception =>
logger.error(s"General exception ${e.getMessage}")
e.printStackTrace()
}
case _ => case _ =>
logger.error(s"Found no subcommand, see help below")
conf.printHelp()
} }
} }
} }

View File

@@ -4,7 +4,14 @@ import com.minres.tgc.hammer.Global.CORE_DATASHEETS
import org.rogach.scallop.* import org.rogach.scallop.*
trait CoreSelection { this: MySubcommand => trait CoreSelection { this: MySubcommand =>
val core: ScallopOption[String] = opt[String](group = mainGroup) val core: ScallopOption[String] = opt[String](group = mainGroup, required = true, descr = "The core to be extended; core datasheets are in coreDatasheets/")
def getCoreDatasheet: os.Path = CORE_DATASHEETS / s"${core()}.yaml"
def getCoreDatasheet: os.Path = CORE_DATASHEETS / s"$core.yaml" addValidation {
if (os.isFile(getCoreDatasheet)) {
Right(())
} else {
Left(s"Core ${core()} not supported, no core datasheet at ${getCoreDatasheet}")
}
}
} }

View File

@@ -1,25 +1,28 @@
package com.minres.tgc.hammer.cli package com.minres.tgc.hammer.cli
import com.minres.tgc.hammer.Global import com.minres.tgc.hammer.Global.LOG_DIR
import org.rogach.scallop.{ScallopConf, ScallopOption, ValueConverter, fileConverter, listArgConverter, singleArgConverter} import org.rogach.scallop.*
import os.Path import os.Path
import com.minres.tgc.hammer.util.FileUtils.*
import java.io.File
class HammerConf(arguments: Seq[String]) extends ScallopConf(arguments) { class HammerConf(arguments: Seq[String]) extends ScallopConf(arguments) {
val baseDirectory: ScallopOption[File] = opt[File](default = Some(os.pwd.toIO)) val outputDirectory: ScallopOption[Path] = opt[Path](default = Some("output".path()), descr = "The base output directory")
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")
val printToolOutput: ScallopOption[Boolean] = toggle(descrYes = s"Whether to directly print all output of the underlying tools to the terminal. Otherwise the output will be written to files in `logs` folder in the selected outputDirectory", default = Some(false))
addSubcommand(new LongnailCommand)
addSubcommand(new TreenailCommand) addSubcommand(new TreenailCommand)
addSubcommand(new LongnailMergeCommand)
addSubcommand(new LongnailSchedCommand) addSubcommand(new LongnailSchedCommand)
validateFileIsDirectory(baseDirectory) 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()
} }
implicit val osPathConverter: ValueConverter[os.Path] = {
singleArgConverter[os.Path](os.Path(_, Global.BASE_DIR))
}
implicit val osPathListConverter: ValueConverter[List[os.Path]] = {
listArgConverter[os.Path](os.Path(_, Global.BASE_DIR))
}

View File

@@ -0,0 +1,53 @@
package com.minres.tgc.hammer.cli
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 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
|""".stripMargin)
override def getRequiredTasks: Seq[Task] = {
val tasks = mutable.ListBuffer[Task]()
val (coreDSLFiles, mlirFiles) = inputFiles().partition(_.ext == "core_desc")
val treenailTasks = coreDSLFiles.map(i => TreenailTask(i, TMP_DIR / s"${i.baseName}.mlir"))
tasks ++= treenailTasks
val allMlirFiles = mlirFiles ++ treenailTasks.map(_.output)
if (allMlirFiles.size == 1) {
tasks += LongnailScheduleTask(allMlirFiles.head, getCoreDatasheet, schedParams)
} else {
val mergedInput = TMP_DIR / "merged.mlir"
val concatInput = TMP_DIR / "concat.mlir"
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, verilogOutput)
} else {
???
}
tasks.toSeq
}
override def cleanup(): Unit = {
log.info(s"Finished, verilog output in ${OUT_DIR / "verilog"}")
}
}

View File

@@ -1,5 +0,0 @@
package com.minres.tgc.hammer.cli
class LongnailHLSCommand {
}

View File

@@ -1,5 +1,43 @@
package com.minres.tgc.hammer.cli package com.minres.tgc.hammer.cli
class LongnailMergeCommand { import com.minres.tgc.hammer.tasks.{CopyTask, Task, TreenailTask}
import org.rogach.scallop.*
import os.Path
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") 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()
check(inputFiles)(checkPathsIsFile)
checkNotIf(overwrite)(output)(checkPathDoesNotExist)
override def getRequiredTasks: Seq[Task] = {
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()}")
}
} }

View File

@@ -1,29 +1,43 @@
package com.minres.tgc.hammer.cli package com.minres.tgc.hammer.cli
import com.minres.tgc.hammer.FileUtils.changeExtension 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.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 org.rogach.scallop.ScallopOption import com.minres.tgc.hammer.util.{Logging, ValidationUtils}
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) 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 outputDirectory: ScallopOption[Path] = opt[Path](group = mainGroup) val schedParams: SchedulingParameters = addConfigElement(new SchedulingParameters)
val schedParams: SchedulingParameters = addOptionGroup(new SchedulingParameters)
validateOSPathIsDirectory(outputDirectory) check(inputFiles)(checkPathsIsFile)
banner(
"""Generate Scheduling information for the provided ISAXes using Longnail
|Usage: tgc-hammer scheduleISAX -c VexRiscv isax.core_desc
|""".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, changeExtension(i, "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 = outputDirectory() / "merged.mlir" val mergedInput = TMP_DIR / "merged.mlir"
val concatInput = outputDirectory() / "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

@@ -1,30 +1,30 @@
package com.minres.tgc.hammer.cli package com.minres.tgc.hammer.cli
import com.minres.tgc.hammer.options.{BaseOption, OptionGroup} 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}
import java.nio.file.{Files, Path} 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 addOptions(options: Seq[BaseOption[?]]): Unit = { def addConfigElement[T <: ConfigElement](elem: T): T = {
options.foreach(_.init(this, null)) elem.init(this, null)
} elem
def addOptionGroup[T <: OptionGroup](group: T): T = {
group.init(this)
group
} }
def validateOSPathIsDirectory(pathOption: ScallopOption[os.Path]): Unit = addValidation {
pathOption.toOption def check[T](option: ScallopOption[T])(check: Checker[T]): Unit = {
.map { addValidation(check(option))
case path if Files.isDirectory(path.toNIO) => Right(()) }
case path => Left(Util.format("File '%s' is not a directory", path))
} def checkPred[T, U](condOpt: ScallopOption[U], cond: U => Boolean)(option: ScallopOption[T])(check: Checker[T]): Unit = addValidation {
.getOrElse(Right(())) condOpt.toOption match {
case Some(value) if cond(value) => check(option)
case _ => Right(())
}
} }
} }

View File

@@ -1,21 +1,32 @@
package com.minres.tgc.hammer.cli package com.minres.tgc.hammer.cli
import com.minres.tgc.hammer.Global 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 java.io.File
import os.Path import os.Path
class TreenailCommand extends MySubcommand("parseCoreDSL") { class TreenailCommand extends MySubcommand("parseCoreDSL") with Logging[TreenailCommand] with ValidationUtils {
val coreDSL: ScallopOption[File] = trailArg[File]("coreDSL") 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(os.Path("isax.mlir", Global.BASE_DIR))) 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()
validateFileIsFile(coreDSL) check(coreDSL)(checkPathIsFile)
check(coreDSL)(checkPathIsCoreDSL)
checkNotIf(overwrite)(output)(checkPathDoesNotExist)
banner(
"""Parse a CoreDSL input using Treenail into the MLIR representation
|Usage: tgc-hammer parseCoreDSL -o isax.mlir isax.core_desc
|""".stripMargin)
override def getRequiredTasks: Seq[Task] = { override def getRequiredTasks: Seq[Task] = {
Seq( Seq(
new TreenailTask(Path(coreDSL(), os.pwd), Path(output(), os.pwd)) TreenailTask(coreDSL(), output())
) )
} }
override def cleanup(): Unit = {
log.info(s"Finished, MLIR output in ${output()}")
}
} }

View File

@@ -5,16 +5,22 @@ import os.Shellable
import scala.compiletime.uninitialized import scala.compiletime.uninitialized
trait BaseOption[T](using conv: ValueConverter[T]) { 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
def init(scallopConf: ScallopConf, group: ScallopOptionGroup): Unit = scallop = createScallop(scallopConf, group)
def get: T = scallop() protected[options] var scallop: ScallopOption[T] = uninitialized
def apply: T = get
def getToolParameters: Seq[Shellable] = Seq(s"--$toolName", getToolArg) def init(scallopConf: ScallopConf, group: ScallopOptionGroup): Unit = {
scallop = createScallop(scallopConf, group)
}
def default: () => Option[T]
def get: Option[T] = scallop.toOption.orElse(default())
def apply(): T = get.get
def getToolParameters: Seq[Shellable] = if (get.isDefined) Seq(s"$toolName", get.get.toString) else Seq()
def toolName: String def toolName: String
def getToolArg: String = get.toString }
}

View File

@@ -5,8 +5,8 @@ import org.rogach.scallop.*
enum Color: enum Color:
case Red, Green, Blue case Red, Green, Blue
class ChoiceOption(choices: Seq[String], cliName: String, val toolName: String, descr: String, default: => Option[String], required: Boolean, cliShort: Char) extends BaseOption[String] { class ChoiceOption(choices: Seq[String], cliName: String, val toolName: String, descr: String, val default: () => Option[String], required: Boolean, cliShort: Char) extends BaseOption[String] {
override protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[String] = { override protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[String] = {
conf.choice(choices, name = cliName, short = cliShort, descr = descr, default = default, required = required, group = group) conf.choice(choices, name = cliName, short = cliShort, descr = descr, required = required, group = group)
} }
} }

View File

@@ -0,0 +1,16 @@
package com.minres.tgc.hammer.options
import org.rogach.scallop.{ScallopConf, ScallopOptionGroup}
import os.Shellable
class CommandGroup(name: String) extends BaseGroup {
override def getToolParameters: Seq[Shellable] = {
val sub = options.toSeq.map(_.getToolParameters.flatMap(_.value).mkString("=")).filter(_.nonEmpty)
Seq(s"$name=\'${sub.mkString(" ")}\'")
}
override def init(scallopConf: ScallopConf, group: ScallopOptionGroup): Unit = {
options.foreach(_.init(scallopConf, group))
initValidation(scallopConf)
}
}

View File

@@ -0,0 +1,10 @@
package com.minres.tgc.hammer.options
import org.rogach.scallop.{ScallopConf, ScallopOptionGroup}
import os.Shellable
trait ConfigElement {
def getToolParameters: Seq[Shellable]
def init(scallopConf: ScallopConf, group: ScallopOptionGroup): Unit
}

View File

@@ -1,38 +1,62 @@
package com.minres.tgc.hammer.options package com.minres.tgc.hammer.options
import org.rogach.scallop.{ScallopConf, ValueConverter} import com.minres.tgc.hammer.util.{Checker, ValidationBase, ValidationUtils}
import org.rogach.scallop.{ScallopConf, ScallopOptionGroup, ValueConverter}
import os.Shellable import os.Shellable
import scala.collection.mutable import scala.collection.mutable
export com.minres.tgc.hammer.cli.osPathConverter trait BaseGroup extends ConfigElement with ValidationBase[BaseOption] with ValidationUtils {
protected val options = mutable.ListBuffer[ConfigElement]()
trait OptionGroup { protected def add[T <: ConfigElement](option: T): T = {
def name: String
private val options = mutable.ListBuffer[BaseOption[?]]()
protected def add[T <: BaseOption[?]](option: T): T = {
options += option options += option
option option
} }
def getToolParameters: Seq[Shellable] = options.toSeq.flatMap(_.getToolParameters) def initValidation(scallopConf: ScallopConf): Unit = {
validations.foreach(fn => scallopConf.addValidation(fn()))
def init(scallopConf: ScallopConf): Unit = {
val group = scallopConf.group(name)
options.foreach(_.init(scallopConf, group))
} }
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 trail[T](toolName: String, descr: String = "", default: => Option[T] = None, required: Boolean = false)(using conv: ValueConverter[T]): TrailOption[T] = add(TrailOption(toolName, descr, default, required)) private var validations: List[() => Either[String, Unit]] = Nil
def choice(choices: Seq[String], cliName: String, toolName: String, descr: String = "", default: => Option[String] = None, required: Boolean = false, cliShort: Char = '\u0000'): ChoiceOption = add(ChoiceOption(choices, cliName, toolName, descr, default, required, cliShort)) private def addValidation(fn: () => Either[String, Unit]): Unit = {
def choiceS(choices: Seq[String], name: String, descr: String = "", default: => Option[String] = None, required: Boolean = false, cliShort: Char = '\u0000'): ChoiceOption = add(ChoiceOption(choices, name, name, descr, default, required, cliShort)) 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))
def trail[T](toolName: String, descr: String = "", default: () => Option[T] = () => None, required: Boolean = false)(using conv: ValueConverter[T]): TrailOption[T] = add(TrailOption(toolName, descr, default, required))
def choice(choices: Seq[String], cliName: String, toolName: String, descr: String = "", default: () => Option[String] = () => None, required: Boolean = false, cliShort: Char = '\u0000'): ChoiceOption = add(ChoiceOption(choices, cliName, toolName, descr, default, required, cliShort))
def choiceS(choices: Seq[String], name: String, descr: String = "", default: () => Option[String] = () => None, required: Boolean = false, cliShort: Char = '\u0000'): ChoiceOption = add(ChoiceOption(choices, name, name, descr, default, required, cliShort))
def tally(cliName: String, toolName: String, descr: String = "", required: Boolean = false, cliShort: Char = '\u0000'): TallyOption = add(TallyOption(cliName, toolName, descr, required, cliShort)) def tally(cliName: String, toolName: String, descr: String = "", required: Boolean = false, cliShort: Char = '\u0000'): TallyOption = add(TallyOption(cliName, toolName, descr, required, cliShort))
def tallyS(name: String, descr: String = "", required: Boolean = false, cliShort: Char = '\u0000'): TallyOption = add(TallyOption(name, name, descr, required, cliShort)) def tallyS(name: String, descr: String = "", required: Boolean = false, cliShort: Char = '\u0000'): TallyOption = add(TallyOption(name, name, descr, required, cliShort))
def toggle(cliName: String, toolName: String, descr: String = "", default: => Option[Boolean] = None, required: Boolean = false, cliShort: Char = '\u0000'): ToggleOption = add(ToggleOption(cliName, toolName, descr, default, required, cliShort)) def toggle(cliName: String, toolName: String, descr: String = "", default: () => Option[Boolean] = () => None, required: Boolean = false, cliShort: Char = '\u0000'): ToggleOption = add(ToggleOption(cliName, toolName, descr, default, required, cliShort))
def toggleS(name: String, descr: String = "", default: => Option[Boolean] = None, required: Boolean = false, cliShort: Char = '\u0000'): ToggleOption = add(ToggleOption(name, name, descr, default, required, cliShort)) def toggleS(name: String, descr: String = "", default: () => Option[Boolean] = () => None, required: Boolean = false, cliShort: Char = '\u0000'): ToggleOption = add(ToggleOption(name, name, descr, default, required, cliShort))
}
trait OptionGroup extends BaseGroup {
def name: String
override def getToolParameters: Seq[Shellable] = options.toSeq.flatMap(_.getToolParameters)
def init(scallopConf: ScallopConf, g: ScallopOptionGroup): Unit = {
val group = scallopConf.group(name)
options.foreach(_.init(scallopConf, group))
initValidation(scallopConf)
}
} }

View File

@@ -6,4 +6,6 @@ class TallyOption(cliName: String, val toolName: String, descr: String, required
override protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[Int] = { override protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[Int] = {
conf.tally(name = cliName, short = cliShort, descr = descr, group = group) conf.tally(name = cliName, short = cliShort, descr = descr, group = group)
} }
override def default: () => Option[Int] = () => None
} }

View File

@@ -2,9 +2,9 @@ package com.minres.tgc.hammer.options
import org.rogach.scallop.* import org.rogach.scallop.*
class ToggleOption(cliName: String, val toolName: String, descr: String, default: => Option[Boolean], required: Boolean, cliShort: Char) extends BaseOption[Boolean] { class ToggleOption(cliName: String, val toolName: String, descr: String, val default: () => Option[Boolean], required: Boolean, cliShort: Char) extends BaseOption[Boolean] {
override protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[Boolean] = { override protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[Boolean] = {
conf.toggle(name = cliName, short = cliShort, descrYes = descr, default = default, required = required, group = group) conf.toggle(name = cliName, short = cliShort, descrYes = descr, required = required, group = group)
} }
} }

View File

@@ -2,8 +2,8 @@ package com.minres.tgc.hammer.options
import org.rogach.scallop.* import org.rogach.scallop.*
class TrailOption[T](val toolName: String, descr: String, default: => Option[T], required: Boolean)(using conv: ValueConverter[T]) extends BaseOption[T] { class TrailOption[T](val toolName: String, descr: String, val default: () => Option[T], required: Boolean)(using conv: ValueConverter[T]) extends BaseOption[T] {
override protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[T] = { override protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[T] = {
conf.trailArg[T](descr = descr, default = default, required = required, group = group) conf.trailArg[T](descr = descr, required = required, group = group)
} }
} }

View File

@@ -2,8 +2,8 @@ package com.minres.tgc.hammer.options
import org.rogach.scallop.* import org.rogach.scallop.*
class ValueOption[T](cliName: String, val toolName: String, descr: String, default: => Option[T], required: Boolean, cliShort: Char)(using conv: ValueConverter[T]) extends BaseOption[T] { class ValueOption[T](cliName: String, val toolName: String, descr: String, val default: () => Option[T], required: Boolean, cliShort: Char)(using conv: ValueConverter[T]) extends BaseOption[T] {
override protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[T] = { override protected def createScallop(conf: ScallopConf, group: ScallopOptionGroup): ScallopOption[T] = {
conf.opt[T](name = cliName, short = cliShort, descr = descr, default = default, required = required, group = group) conf.opt[T](name = cliName, short = cliShort, descr = descr, required = required, group = group)
} }
} }

View File

@@ -0,0 +1,13 @@
package com.minres.tgc.hammer.tasks
import os.*
case class CopyTask(from: Path, to: Path) extends TaskImpl[CopyTask] {
override def validate(): Unit = {
assert(isFile(from), "Input file does not exist")
}
override def execute(): Unit = {
copy(from, to)
}
}

View File

@@ -1,12 +1,32 @@
package com.minres.tgc.hammer.tasks package com.minres.tgc.hammer.tasks
import com.minres.tgc.hammer.Global.TMP_DIR
import com.minres.tgc.hammer.Main
import com.minres.tgc.hammer.util.Logging
import os.{Path, Shellable} import os.{Path, Shellable}
import scala.reflect.ClassTag
trait Task { trait Task {
def validate(): Unit def validate(): Unit
def execute(): Unit def execute(): Unit
def runExecutable(execPath: Path, args: Shellable*): os.CommandResult = { def run(): Unit = {
os.proc(execPath, args).call() validate()
execute()
}
}
trait TaskImpl[T <: Task : ClassTag] extends Task with Logging[T] {
def runExecutable(execPath: Path, args: Shellable*)(logFile: os.Path): os.CommandResult = {
val command = s"$execPath ${args.flatMap(_.value).mkString(" ")}"
log.debug(s"Running external program: ")
log.debug(command)
val output: os.ProcessOutput = if (Main.conf.printToolOutput())
os.Inherit
else
logFile
os.proc("bash", "-c", command).call(cwd = TMP_DIR, stdout = output)
} }
} }

View File

@@ -1,16 +1,17 @@
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 TaskImpl[TreenailTask] {
private val EXECUTABLE = Global.TREENAIL / "app" / "build" / "install" / "app" / "bin" / "app" private val EXECUTABLE = TREENAIL / "app" / "build" / "install" / "app" / "bin" / "app"
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)(LOG_DIR / "treenail.log")
} }
} }

View File

@@ -2,10 +2,10 @@ package com.minres.tgc.hammer.tasks.longnail
import com.minres.tgc.hammer.Global import com.minres.tgc.hammer.Global
import com.minres.tgc.hammer.options.* import com.minres.tgc.hammer.options.*
import com.minres.tgc.hammer.tasks.Task import com.minres.tgc.hammer.tasks.{Task, TaskImpl}
import os.{Path, isFile} import os.{Path, isFile}
trait LongnailBaseTask extends Task { trait LongnailBaseTask[T <: LongnailBaseTask[T]] extends TaskImpl[T] {
protected val EXECUTABLE: os.Path = Global.LONGNAIL / "build" / "bin" / "longnail-opt" protected val EXECUTABLE: os.Path = Global.LONGNAIL / "build" / "bin" / "longnail-opt"
override def validate(): Unit = { override def validate(): Unit = {

View File

@@ -1,18 +1,22 @@
package com.minres.tgc.hammer.tasks.longnail package com.minres.tgc.hammer.tasks.longnail
import os.Path import com.minres.tgc.hammer.Global.LOG_DIR
import com.minres.tgc.hammer.util.FileUtils.*
import os.*
class LongnailHLSTask(schedulingFile: Option[Path], outDirectory: Path) extends LongnailBaseTask { class LongnailHLSTask(schedulingSolutionFile: Path, schedulingSelectionFile: Option[Path], outDirectory: Path) extends LongnailBaseTask[LongnailHLSTask] {
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 = {
runExecutable(EXECUTABLE, runExecutable(EXECUTABLE,
"--lower-lil-to-hw", schedulingSelectionFile match {
schedulingFile match { case Some(value) => s"--lower-lil-to-hw=solutionSelection=$value"
case Some(value) => s"--solutionSelection $value" case None => "--lower-lil-to-hw=forceUseMinIISolution=true"
case None => "--forceUseMinIISolution=true"
}, },
"--simplify-structure", "--simplify-structure",
"--cse", "--cse",
@@ -22,8 +26,8 @@ class LongnailHLSTask(schedulingFile: Option[Path], outDirectory: Path) extends
"--hw-cleanup", "--hw-cleanup",
"--prettify-verilog", "--prettify-verilog",
"--hw-legalize-modules", "--hw-legalize-modules",
"--export-split-verilog", s"--export-split-verilog=dir-name=$outDirectory",
"--dir-name", outDirectory schedulingSolutionFile
) )(LOG_DIR / "longnail_hls.log")
} }
} }

View File

@@ -1,10 +1,13 @@
package com.minres.tgc.hammer.tasks.longnail package com.minres.tgc.hammer.tasks.longnail
import os.Path import com.minres.tgc.hammer.Global.LOG_DIR
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[LongnailMergeTask] {
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 = {
@@ -15,6 +18,6 @@ class LongnailMergeTask(mlirFiles: Seq[Path], concatMLIR: Path, mergedMLIR: Path
runExecutable(EXECUTABLE, runExecutable(EXECUTABLE,
"--merge-multiple-isaxes", concatMLIR, "--merge-multiple-isaxes", concatMLIR,
"-o", mergedMLIR "-o", mergedMLIR
) )(LOG_DIR / "longnail_merge.log")
} }
} }

View File

@@ -1,40 +1,24 @@
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.Global.LOG_DIR
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[LongnailScheduleTask] {
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",
"--schedule-lil",
"--datasheet", coreDatasheet,
params.getToolParameters, params.getToolParameters,
isaxMLIR isaxMLIR
) )(LOG_DIR / "longnail_schedule.log")
} }
} }

View File

@@ -1,21 +1,34 @@
package com.minres.tgc.hammer.tasks.longnail package com.minres.tgc.hammer.tasks.longnail
import com.minres.tgc.hammer.util.FileUtils.*
import com.minres.tgc.hammer.Global.OUT_DIR
import com.minres.tgc.hammer.options.* import com.minres.tgc.hammer.options.*
import os.Path
import org.rogach.scallop.* import org.rogach.scallop.*
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"
value[Int](cliName = "maxLoopUnrollFactor", toolName = "max-unroll-factor") class PrepareOptions extends CommandGroup("--prepare-schedule-lil") {
valueS[Int](name = "schedulingTimeout") val schedulingAlgo = choiceS(Seq("LEGACY", "MS", "PAMS", "PARAMS", "MI_MS", "MI_PAMS", "MI_PARAMS"), name = "schedulingAlgo", default = () => Some("LEGACY"), descr =
value[Int](cliName = "schedulingRefineTimeout", toolName = "schedRefineTimeout") "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 = "schedulingKconf", toolName = "solSelKconfPath") val cellLibrary = value[Path](cliName = "cellLibrary", toolName = "library", descr = "The cell library used by Longnail (example: longnail/test/LILToHW/longnail*.yaml")
value[Path](cliName = "schedulingMLIR", toolName = "o") 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)")
choiceS(Seq("LEGACY", "MS", "PAMS", "PARAMS", "MI_MS", "MI_PAMS", "MI_PARAMS"), name = "schedulingAlgo", default = Some("LEGACY")) check(cellLibrary)(checkPathIsFile)
choice(Seq("CBC", "GLPK", "SCIP", "HIGHS", "GUROBI", "CPLEX", "XPRESS", "COPT"), cliName = "ilpSolver", toolName = "solver", default = Some("CBC")) check(opTyLibrary)(checkPathIsFile)
toggle(cliName = "verboseSched", toolName = "verbose") }
valueS[Path](name = "opTyLibrary") val prepare = add(new PrepareOptions)
value[Int](cliName = "clockPeriod", toolName = "clockTime")
value[Path](cliName = "cellLibrary", toolName = "library") 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", default = () => Some(10))
}
val schedule = add(new ScheduleOptions)
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")
} }

View File

@@ -0,0 +1,3 @@
package com.minres.tgc.hammer.util
case class AssertException() extends Exception

View File

@@ -0,0 +1,39 @@
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 scala.collection.IterableOps
object FileUtils {
def changeExtension(path: os.Path, newExt: String): os.Path = {
val baseName = path.baseName
val newName = s"$baseName.$newExt"
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)
}
def path(): Path = asPath(BASE_DIR)
extension [T <: Iterable](x: IterableOps[String, T, T[String]])
def asPath(relative: Path): T[Path] = {
x.map(_.asPath(relative))
}
def path(): T[Path] = asPath(BASE_DIR)
implicit val osPathRelBaseConverter: ValueConverter[Path] = {
singleArgConverter[Path](_.asPath(BASE_DIR))
}
implicit val osPathRelBaseListConverter: ValueConverter[List[Path]] = {
listArgConverter[Path](_.asPath(BASE_DIR))
}
}

View File

@@ -0,0 +1,16 @@
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)
throw AssertException()
}
}
}

View File

@@ -0,0 +1,8 @@
package com.minres.tgc.hammer.util
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")
}

1
wsTemplate/.envrc Normal file
View File

@@ -0,0 +1 @@
source source.sh

32
wsTemplate/README.md Normal file
View File

@@ -0,0 +1,32 @@
# TGC Hammer Workspace
This is a workspace for using the TGC Hammer toolchain for ISAX-HLS and integration into TGC cores.
The required tools and executables are all located in the base TGC-Hammer repo from which this workspace was created.
To use the toolchain just enter this directory, `direnv` will then automatically setup all required environment variables.
Alternatively you can execute `source source.sh`.
## Usage
The executable for the toolchain is called `tgc-hammer`, it features various subcommands for different tasks.
The two main ones `isaxHLS` and `isaxCore` are described in the sections below.
It also features additionally subcommands for individual steps (e.g. just translate CoreDSL to MLIR), for more information see `tgc-hammer --help`.
### isaxHLS
This subcommand takes one or multiple input files describing the ISAXES and runs the Longnail HLS tool to create SystemVerilog respresentations for them.
The base command looks like this: `tgc-hammer isaxHLS -c VexRiscv --useMinIISolution isax.core_desc`
The `-c` or `--core` option is required, it provides Longnail with the Core Datasheet detailing core-specific scheduling information.
Currently the `--useMinIISolution` option is also required, manually selecting scheduling solutions is WIP.
The command ends with the input files. If multiple ones are specified, they are merged using Longnail before the scheduling and HLS. Both CoreDSL and already translated MLIR files are supported (also mixed).
For additional options see `tgc-hammer isaxHLS --help`.
### isaxCore
WIP

View File

@@ -0,0 +1,257 @@
InstructionSet Zx_keccak {
architectural_state {
unsigned int XLEN=32;
register unsigned<XLEN> X[32] [[is_main_reg]];
extern unsigned<8> MEM[1 << XLEN] [[is_main_mem]];
const unsigned int RV_CAUSE_ILLEGAL_INSTRUCTION = 0x02;
register unsigned<64> K[32];
register unsigned<8> LFSR;
}
functions {
unsigned<64> ROL64(unsigned<64> val, unsigned<64> shift) {
return (val << shift) | (val >> (64 - shift));
}
unsigned<64> readLane(unsigned int x, unsigned int y) {
return K[x + 5 * y];
}
unsigned<64> XORLane(unsigned int x, unsigned int y, unsigned<64> value) {
K[x + 5 * y] = K[x + 5 * y] ^ value;
return 0;
}
unsigned<64> writeLane(unsigned int x, unsigned int y, unsigned<64> value) {
K[x + 5 * y] = value;
return 0;
}
unsigned<1> LFSR86540() {
unsigned<1> result = (unsigned<1>)(LFSR & 0x01);
if ((LFSR & 0x80) != 0) {
// Primitive polynomial over GF(2): x^8+x^6+x^5+x^4+1
LFSR = (LFSR << 1) ^ 0x71;
} else {
LFSR = LFSR << 1;
}
return result;
}
}
instructions {
// Custom-0 Opcode
LK64 {
encoding: imm[11:0] :: rs1[4:0] :: 3'b000 :: rd[4:0] :: 7'b0001011;
assembly: "{name(rd)}, {imm}({name(rs1)})";
behavior: {
unsigned<XLEN> load_address = (unsigned<XLEN>)(X[rs1] + (signed<12>)imm);
K[rd]= MEM[load_address+7:load_address];
}
}
LK128 {
encoding: imm[11:0] :: rs1[4:0] :: 3'b010 :: rd[4:0] :: 7'b0001011;
assembly: "{name(rd)}, {imm}({name(rs1)})";
behavior: if(rd%2 == 1) ; else {
unsigned<XLEN> base_address = (unsigned<XLEN>)(X[rs1] + (signed<12>)imm);
for(unsigned int i = 0; i<2; i++){
unsigned<XLEN> load_address = (unsigned<XLEN>)(base_address + 8*i);
K[rd+i]= MEM[load_address+7:load_address];
}
}
}
LK256 {
encoding: imm[11:0] :: rs1[4:0] :: 3'b100 :: rd[4:0] :: 7'b0001011;
assembly: "{name(rd)}, {imm}({name(rs1)})";
behavior: if(rd%4 == 1) ; else {
unsigned<XLEN> base_address = (unsigned<XLEN>)(X[rs1] + (signed<12>)imm);
for(unsigned int i = 0; i<4; i++){
unsigned<XLEN> load_address = (unsigned<XLEN>)(base_address + 8*i);
K[rd+i]= MEM[load_address+7:load_address];
}
}
}
LK512 {
encoding: imm[11:0] :: rs1[4:0] :: 3'b110 :: rd[4:0] :: 7'b0001011;
assembly: "{name(rd)}, {imm}({name(rs1)})";
behavior: if(rd%8 == 1) ; else {
unsigned<XLEN> base_address = (unsigned<XLEN>)(X[rs1] + (signed<12>)imm);
for(unsigned int i = 0; i<8; i++){
unsigned<XLEN> load_address = (unsigned<XLEN>)(base_address + 8*i);
K[rd+i]= MEM[load_address+7:load_address];
}
}
}
SK64 {
encoding: imm[11:5] :: rs2[4:0] :: rs1[4:0] :: 3'b001 :: imm[4:0] :: 7'b0001011;
assembly: "{name(rs2)}, {imm}({name(rs1)})";
behavior: {
unsigned<XLEN> store_address = (unsigned<XLEN>)(X[rs1] + (signed)imm);
MEM[store_address+7:store_address] = K[rs2];
}
}
SK128 {
encoding: imm[11:5] :: rs2[4:0] :: rs1[4:0] :: 3'b011 :: imm[4:0] :: 7'b0001011;
assembly: "{name(rs2)}, {imm}({name(rs1)})";
behavior: if(rs2%2 == 1) ; else {
unsigned<XLEN> base_address = (unsigned<XLEN>)(X[rs1] + (signed<12>)imm);
for(unsigned int i = 0; i<2; i++){
unsigned<XLEN> store_address = (unsigned<XLEN>)(base_address + 8*i);
MEM[store_address+7:store_address] = K[rs2+i];
}
}
}
SK256 {
encoding: imm[11:5] :: rs2[4:0] :: rs1[4:0] :: 3'b101 :: imm[4:0] :: 7'b0001011;
assembly: "{name(rs2)}, {imm}({name(rs1)})";
behavior: if(rs2%4 == 1) ; else {
unsigned<XLEN> base_address = (unsigned<XLEN>)(X[rs1] + (signed<12>)imm);
for(unsigned int i = 0; i<4; i++){
unsigned<XLEN> store_address = (unsigned<XLEN>)(base_address + 8*i);
MEM[store_address+7:store_address] = K[rs2+i];
}
}
}
SK512 {
encoding: imm[11:5] :: rs2[4:0] :: rs1[4:0] :: 3'b111 :: imm[4:0] :: 7'b0001011;
assembly: "{name(rs2)}, {imm}({name(rs1)})";
behavior: if(rs2%8 == 1) ; else {
unsigned<XLEN> base_address = (unsigned<XLEN>)(X[rs1] + (signed<12>)imm);
for(unsigned int i = 0; i<8; i++){
unsigned<XLEN> store_address = (unsigned<XLEN>)(base_address + 8*i);
MEM[store_address+7:store_address] = K[rs2+i];
}
}
}
KECCAK_THETA {
encoding: 7'b0000000 :: 5'b00000 :: 5'b00000 :: 3'b000 :: 5'b00000 :: 7'b0001011;
assembly: "keccak.theta";
behavior: {
// Temporary storage for C array (5 columns)
unsigned<64> C0, C1, C2, C3, C4;
unsigned<64> D;
// Compute the parity of the columns
C0 = readLane(0, 0) ^ readLane(0, 1) ^ readLane(0, 2) ^ readLane(0, 3) ^ readLane(0, 4);
C1 = readLane(1, 0) ^ readLane(1, 1) ^ readLane(1, 2) ^ readLane(1, 3) ^ readLane(1, 4);
C2 = readLane(2, 0) ^ readLane(2, 1) ^ readLane(2, 2) ^ readLane(2, 3) ^ readLane(2, 4);
C3 = readLane(3, 0) ^ readLane(3, 1) ^ readLane(3, 2) ^ readLane(3, 3) ^ readLane(3, 4);
C4 = readLane(4, 0) ^ readLane(4, 1) ^ readLane(4, 2) ^ readLane(4, 3) ^ readLane(4, 4);
// Apply theta effect to column 0
D = C4 ^ ROL64(C1, 1);
XORLane(0, 0, D);
XORLane(0, 1, D);
XORLane(0, 2, D);
XORLane(0, 3, D);
XORLane(0, 4, D);
// Apply theta effect to column 1
D = C0 ^ ROL64(C2, 1);
XORLane(1, 0, D);
XORLane(1, 1, D);
XORLane(1, 2, D);
XORLane(1, 3, D);
XORLane(1, 4, D);
// Apply theta effect to column 2
D = C1 ^ ROL64(C3, 1);
XORLane(2, 0, D);
XORLane(2, 1, D);
XORLane(2, 2, D);
XORLane(2, 3, D);
XORLane(2, 4, D);
// Apply theta effect to column 3
D = C2 ^ ROL64(C4, 1);
XORLane(3, 0, D);
XORLane(3, 1, D);
XORLane(3, 2, D);
XORLane(3, 3, D);
XORLane(3, 4, D);
// Apply theta effect to column 4
D = C3 ^ ROL64(C0, 1);
XORLane(4, 0, D);
XORLane(4, 1, D);
XORLane(4, 2, D);
XORLane(4, 3, D);
XORLane(4, 4, D);
}
}
KECCAK_RHO_PI {
encoding: 7'b0000000 :: 5'b00000 :: 5'b00000 :: 3'b001 :: 5'b00000 :: 7'b0001011;
assembly: "keccak.rhopi";
behavior: {
unsigned<64> current, temp;
unsigned int x, y;
unsigned int r;
unsigned int Y;
// Start at coordinates (1, 0)
x = 1;
y = 0;
current = readLane(x, y);
// Iterate over ((0 1)(2 3))^t * (1 0) for 0 ≤ t ≤ 23
for(unsigned int t = 0; t < 24; t++) {
// Compute the rotation constant r = (t+1)(t+2)/2
r = ((t + 1) * (t + 2) / 2) % 64;
// Compute ((0 1)(2 3)) * (x y)
Y = (2 * x + 3 * y) % 5;
x = y;
y = Y;
// Swap current and state(x,y), and rotate
temp = readLane(x, y);
writeLane(x, y, ROL64(current, r));
current = temp;
}
}
}
KECCAK_CHI {
encoding: 7'b0000000 :: 5'b00000 :: 5'b00000 :: 3'b010 :: 5'b00000 :: 7'b0001011;
assembly: "keccak.chi";
behavior: {
unsigned<64> T0, T1, T2, T3, T4;
unsigned int y;
for(y = 0; y < 5; y++) {
/* Take a copy of the plane */
T0 = readLane(0, y);
T1 = readLane(1, y);
T2 = readLane(2, y);
T3 = readLane(3, y);
T4 = readLane(4, y);
/* Compute χ on the plane */
writeLane(0, y, T0 ^ (~T1 & T2));
writeLane(1, y, T1 ^ (~T2 & T3));
writeLane(2, y, T2 ^ (~T3 & T4));
writeLane(3, y, T3 ^ (~T4 & T0));
writeLane(4, y, T4 ^ (~T0 & T1));
}
}
}
KECCAK_IOTA {
encoding: 7'b0000000 :: 5'b00000 :: 5'b00000 :: 3'b011 :: 5'b00000 :: 7'b0001011;
assembly: "keccak.iota";
behavior: {
unsigned int j;
for (j = 0; j < 7; j++) {
unsigned int bitPosition = (unsigned int)((1 << j) - 1);
if (LFSR86540()) {
XORLane(0, 0, (unsigned<64>)1 << bitPosition);
}
}
}
}
KECCAK_LFSR_RESET {
encoding: 7'b0000000 :: 5'b00000 :: 5'b00000 :: 3'b100 :: 5'b00000 :: 7'b0001011;
assembly: "keccak.lfsr.reset";
behavior: {
LFSR = 0x01;
}
}
}
}

View File

@@ -0,0 +1,79 @@
InstructionSet Zxlwc_ascon {
architectural_state {
unsigned int XLEN=32;
register unsigned<XLEN> X[32] [[is_main_reg]];
}
functions {
unsigned<64> _ror64(unsigned<64> val, unsigned<64> shift_amount) {
return (val >> shift_amount) | (val << (64 - shift_amount));
}
// ROT_0 = { 19, 61, 1, 10, 7 }
unsigned<64> rot_0(unsigned<5> imm){
unsigned<64> ret;
if(imm == 0)
ret = 19;
else if(imm==1)
ret = 61;
else if(imm==2)
ret = 1;
else if(imm==3)
ret = 10;
else if(imm==4)
ret = 7;
else ret = 0;
return ret;
}
// ROT_1 = { 28, 39, 6, 17, 41 }
unsigned<64> rot_1(unsigned<5> imm){
unsigned<64> ret;
if(imm == 0)
ret = 28;
else if(imm==1)
ret = 39;
else if(imm==2)
ret = 6;
else if(imm==3)
ret = 17;
else if(imm==4)
ret = 41;
else ret = 0;
return ret;
}
}
instructions{
ASCON_SIGMA_LO [[enable=XLEN==32]] {
encoding: 2'b00 :: imm[4:0] :: rs2[4:0] :: rs1[4:0] :: 3'b111 :: rd[4:0] :: 7'b0101011;
behavior: {
unsigned<32> x_hi = (unsigned<32>)X[rs2];
unsigned<32> x_lo = (unsigned<32>)X[rs1];
unsigned<64> x = x_hi :: x_lo;
unsigned<64> r = x ^ _ror64(x, rot_0(imm)) ^ _ror64(x, rot_1(imm));
X[rd] = (unsigned<XLEN>)r[31:0];
}
}
ASCON_SIGMA_HI [[enable=XLEN==32]] {
encoding: 2'b01 :: imm[4:0] :: rs2[4:0] :: rs1[4:0] :: 3'b111 :: rd[4:0] :: 7'b0101011;
behavior: {
unsigned<32> x_hi = (unsigned<32>)X[rs2];
unsigned<32> x_lo = (unsigned<32>)X[rs1];
unsigned<64> x = x_hi :: x_lo;
unsigned<64> r = x ^ _ror64(x, rot_0(imm)) ^ _ror64(x, rot_1(imm));
X[rd] = (unsigned<XLEN>)r[63:32];
}
}
ASCON_SIGMA [[enable=XLEN==64]] {
encoding: 2'b10 :: imm[4:0] :: 5'b00000 :: rs1[4:0] :: 3'b110 :: rd[4:0] :: 7'b0101011;
behavior: {
unsigned<64> x = X[rs1];
unsigned<64> r = x ^ _ror64(x, rot_0(imm)) ^ _ror64(x, rot_1(imm));
X[rd] = (unsigned<XLEN>)r;
}
}
}
}