VivadoComposer.scala 10.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//
// Copyright (C) 2016 Jens Korinth, TU Darmstadt
//
// This file is part of Tapasco (TPC).
//
// Tapasco is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Tapasco is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Tapasco.  If not, see <http://www.gnu.org/licenses/>.
//
/**
 * @file     VivadoComposer.scala
 * @brief    Composer implementation for Vivado Design Suite.
 * @authors  J. Korinth, TU Darmstadt (jk@esa.cs.tu-darmstadt.de)
 **/
package de.tu_darmstadt.cs.esa.tapasco.activity.composers
import  de.tu_darmstadt.cs.esa.tapasco.Common
import  de.tu_darmstadt.cs.esa.tapasco.base._
import  de.tu_darmstadt.cs.esa.tapasco.base.json._
import  de.tu_darmstadt.cs.esa.tapasco.base.tcl._
import  de.tu_darmstadt.cs.esa.tapasco.filemgmt.LogTrackingFileWatcher
import  de.tu_darmstadt.cs.esa.tapasco.util._
import  de.tu_darmstadt.cs.esa.tapasco.reports._
import  de.tu_darmstadt.cs.esa.tapasco.dse.Heuristics
import  de.tu_darmstadt.cs.esa.tapasco.filemgmt.FileAssetManager
import  java.nio.file._
import  scala.sys.process.{Process, ProcessLogger}
import  scala.util.Properties.{lineSeparator => NL}
import  ComposeResult._
import  LogFormatter._

/** Implementation of [[Composer]] for Vivado Design Suite. */
class VivadoComposer()(implicit cfg: Configuration) extends Composer {
  import VivadoComposer._
  private[this] val logger = de.tu_darmstadt.cs.esa.tapasco.Logging.logger(this.getClass)

  /** @inheritdoc */
  def maxMemoryUsagePerProcess: Int = VIVADO_PROCESS_PEAK_MEM

  /** @inheritdoc */
49
50
  def compose(bd: Composition, target: Target, f: Heuristics.Frequency = 0, features: Seq[Feature] = Seq())
             (implicit cfg: Configuration): Composer.Result = {
51
52
    logger.debug("VivadoComposer uses at most {} threads", cfg.maxThreads getOrElse "unlimited")
    // create output struct
53
    val files = VivadoComposer.Files(bd, target, f, features)
54
55
56
57
58
59
60
61
62
63
    // create log tracker
    val lt = new LogTrackingFileWatcher(Some(logger))
    // create output directory
    java.nio.file.Files.createDirectories(files.outdir)
    // dump configuration
    Configuration.to(cfg, files.outdir.resolve("config.json"))
    // create Tcl script
    mkTclScript(fromTemplate = Common.commonDir.resolve("design.master.tcl.template"),
                to           = files.tclFile,
                projectName  = Composer.mkProjectName(bd, target, f),
64
                header       = makeHeader(bd, target, f, features),
65
66
67
68
                target       = target,
                composition  = composition(bd, target))

    logger.info("Vivado starting run {}: output in {}", files.runName: Any, files.logFile)
69
    files.logFile.toFile.delete
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
    cfg.verbose foreach { mode =>
      logger.info("verbose mode {} is active, starting to watch {}", mode: Any, files.logFile)
      lt += files.logFile
    }

    // Vivado shell command
    val vivadoCmd = Seq("vivado", "-mode", "batch", "-source", files.tclFile.toString,
        "-notrace", "-nojournal", "-log", files.logFile.toString)
    logger.debug("Vivado shell command: {}", vivadoCmd mkString " ")

    // execute Vivado (max runtime: 1 day)
    val r = InterruptibleProcess(Process(vivadoCmd, files.outdir.toFile),
        waitMillis = Some(24 * 60 * 60 * 1000)).!(ProcessLogger(
          stdoutString => logger.trace("Vivado: {}", stdoutString),
          stderrString => logger.trace("Vivado ERR: {}", stderrString)
        ))

    // check retcode
    if (r == InterruptibleProcess.TIMEOUT_RETCODE) {
      logger.error("Vivado timeout for %s in '%s'".format(files.runName, files.outdir))
      Composer.Result(Timeout, log = files.log, util = None, timing = None, power = None)
    } else if (r != 0) {
      logger.error("Vivado finished with non-zero exit code: %d for %s in '%s'"
        .format(r, files.runName, files.outdir))
      Composer.Result(files.log map (_.result) getOrElse OtherError, log = files.log,
          util = None, timing = None, power = None)
    } else {
      // check for timing failure
      if (files.tim.isEmpty) {
        throw new Exception("could not parse timing report: '%s'".format(files.timFile.toString))
      } else {
        Composer.Result(checkTimingFailure(files), Some(files.bitFile.toString),
          files.log, files.util, files.tim, files.pwr)
      }
    }
  }

  /** @inheritdoc */
  def clean(bd: Composition, target: Target, f: Double = 0)(implicit cfg: Configuration): Unit = {
    Common.getFiles(cfg.outputDir(bd, target, f).resolve("microarch").toFile).filter(_.isFile).map(_.delete)
    Common.getFiles(cfg.outputDir(bd, target, f).resolve("microarch").toFile).filter(_.isDirectory).map(_.deleteOnExit)
    Common.getFiles(cfg.outputDir(bd, target, f).resolve("user_ip").toFile).filter(_.isFile).map(_.delete)
    Common.getFiles(cfg.outputDir(bd, target, f).resolve("user_ip").toFile).filter(_.isDirectory).map(_.deleteOnExit)
  }

  /** @inheritdoc */
  def cleanAll(bd: Composition, target: Target, f: Double = 0)(implicit cfg: Configuration): Unit = {
    Common.getFiles(cfg.outputDir(bd, target, f).toFile).filter(_.isFile).map(_.delete)
    Common.getFiles(cfg.outputDir(bd, target, f).toFile).filter(_.isDirectory).map(_.deleteOnExit)
  }

  /** Check for timing failure in report. */
  private def checkTimingFailure(files: Files): ComposeResult = {
    val wns = files.tim map (_.worstNegativeSlack) getOrElse Double.NegativeInfinity
    if (wns < SLACK_THRESHOLD) {
      logger.error("Vivado finished, but did not achieve timing closure for %s, WNS: %1.3f, max delay path: %s in '%s'"
        .format(files.runName, wns, files.tim.map(_.maxDelayPath), files.outdir))
      TimingFailure
    } else {
      logger.info("Vivado finished successfully for %s, WNS: %1.3f, bitstream file is here: '%s'"
        .format(files.runName, wns, files.bitFile))
      Success
    }
  }

  /** Writes the .tcl script for Vivado. */
  private def mkTclScript(fromTemplate: Path, to: Path, projectName: String, header: String, target: Target,
      composition: String): Unit = {
    // needles for template
    val needles: scala.collection.mutable.Map[String, String] = scala.collection.mutable.Map(
      "PROJECT_NAME"     -> "microarch",
      "BITSTREAM_NAME"   -> projectName,
      "HEADER"           -> header,
      "PRELOAD_FILES"    -> "",
      "PART"             -> target.pd.part,
      "BOARD_PART"       -> (target.pd.boardPart getOrElse ""),
      "BOARD_PRESET"     -> (target.pd.boardPreset getOrElse ""),
      "PLATFORM_TCL"     -> target.pd.tclLibrary.toString,
      "ARCHITECTURE_TCL" -> target.ad.tclLibrary.toString,
      "COMPOSITION"      -> composition
    )

    // write Tcl script
    Template.interpolateFile(
      Template.DEFAULT_NEEDLE,
      fromTemplate.toString,
      to.toString,
      needles)
  }

  /** Produces the Tcl dictionary for the given composition and the IP catalog setup code for Vivado. **/
  private def composition(bd: Composition, target: Target): String = {
    // find all cores
    val cores = (for {
      ce <- bd.composition
    } yield ce.kernel -> FileAssetManager.entities.core(ce.kernel, target)).toMap
    // check that all cores are found, else abort
    if (cores.values map (_.isEmpty) reduce (_ || _)) {
      throw new Exception("could not find all required cores for target %s, missing: %s"
          .format(target, cores filter (_._2.isEmpty) map (_._1) mkString ", "))
    }

    val elems = for {
      ce <- bd.composition
      cd <- cores(ce.kernel)
      vl = VLNV.fromZip(cd.zipPath)
    } yield (ce.kernel, ce.count, cd, cd.zipPath, vl)

    val repoPaths =
      "set_property IP_REPO_PATHS \"[pwd]/user_ip " + Common.commonDir + "\" [current_project]" + NL +
      "file delete -force [pwd]/user_ip" + NL +
      "file mkdir [pwd]/user_ip" + NL +
      "update_ip_catalog" + NL +
      elems.map(_._4.toString).map(zp => "update_ip_catalog -add_ip " + zp + " -repo_path ./user_ip").mkString(NL) + NL +
      "update_ip_catalog" + NL

    repoPaths + (for (i <- 0 until elems.length) yield
      List(
        "dict set kernels {" + i + "} vlnv {" + elems(i)._5 + "}",
        "dict set kernels {" + i + "} count {" + elems(i)._2 + "}",
        "dict set kernels {" + i + "} id {" + elems(i)._3.id + "}",
        ""
     ).mkString(NL)).mkString(NL)
  }

  /** Produces the header section of the main Tcl file, containing several global vars. **/
196
  private def makeHeader(bd: Composition, target: Target, f: Heuristics.Frequency, features: Seq[Feature]): String =
197
198
199
200
201
202
    "set tapasco_freq %3.0f%s".format(f, NL) +
    (target.pd.hostFrequency map (f => "set tapasco_host_freq %3.0f%s".format(f, NL)) getOrElse "") +
    (target.pd.memFrequency map (f => "set tapasco_mem_freq %3.0f%s".format(f, NL)) getOrElse "") +
    (target.pd.boardPreset map (bp => "set tapasco_board_preset %s%s".format(bp, NL)) getOrElse "") +
    (cfg.maxThreads map (mt => "set tapasco_jobs %d%s".format(mt, NL)) getOrElse "") +
    (cfg.maxThreads map (mt => "set_param general.maxThreads %d%s".format(mt, NL)) getOrElse "") +
203
    (features.map { f => new FeatureTclPrinter().toTcl(f) } mkString NL)
204
205
206
207
208
209
210
211
212
213
214
215
216
}

/** Companion object of [[VivadoComposer]]. */
object VivadoComposer {
  /** peak memory requirements **/
  final val VIVADO_PROCESS_PEAK_MEM: Int = 15
  /** Slack threshold for WNS relaxation. */
  final val SLACK_THRESHOLD: Double = -0.3

  /** Output files and directories for a run. */
  private final case class Files(c: Composition, t: Target, f: Heuristics.Frequency, fs: Seq[Feature])
                                (implicit cfg: Configuration) {
    lazy val outdir: Path    = cfg.outputDir(c, t, f, fs)
217
    lazy val logFile: Path   = outdir.resolve("%s.log".format(Composer.mkProjectName(c, t, f)))
218
    lazy val tclFile: Path   = outdir.resolve("%s.tcl".format(t.pd.name))
219
    lazy val bitFile: Path   = logFile.resolveSibling("%s.bit".format(Composer.mkProjectName(c, t, f)))
220
221
222
223
224
225
226
227
228
229
    lazy val runName: String = "%s with %s[F=%1.3f]".format(logformat(c), t, f)
    lazy val pwrFile: Path   = logFile.resolveSibling("power.txt")
    lazy val timFile: Path   = logFile.resolveSibling("timing.txt")
    lazy val utilFile: Path  = logFile.resolveSibling("utilization.txt")
    lazy val log             = ComposerLog(logFile)
    lazy val pwr             = PowerReport(pwrFile)
    lazy val tim             = TimingReport(timFile)
    lazy val util            = UtilizationReport(utilFile)
  }
}