EvaluateIP.scala 8.45 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
//
// Copyright (C) 2014 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     EvaluateIP.scala
 * @brief    Contains the code for the out-of-context synthesis activity.
 * @authors  J. Korinth, TU Darmstadt (jk@esa.cs.tu-darmstadt.de)
 **/
package de.tu_darmstadt.cs.esa.tapasco.activity
import  de.tu_darmstadt.cs.esa.tapasco._
26
27
import  de.tu_darmstadt.cs.esa.tapasco.base._
import  de.tu_darmstadt.cs.esa.tapasco.filemgmt.LogTrackingFileWatcher
28
29
import  de.tu_darmstadt.cs.esa.tapasco.reports._
import  de.tu_darmstadt.cs.esa.tapasco.util._
30
import  java.nio.file.{Files, Path}
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import  scala.sys.process._

/** EvaluateIP is the out-of-context synthesis activity.
  * The EvaluateIP activity performs an out-of-context synthesis and
  * place-and-route of the given IP core and a [[base.Target]] to get an
  * estimate on the area utilization and max. operating frequency
  * of the design. This data can be used in design space exploration.
  * Conventions: There must be a main clock port in the top-level
  * module that contains either 'clk' or 'CLK' in its name.
  */
object EvaluateIP {
  private implicit final val logger =
    de.tu_darmstadt.cs.esa.tapasco.Logging.logger(getClass)

  /** Template for the XML report file (similar to Vivado HLS). */
  final val reportTemplate = Common.commonDir.resolve("ip_report.xml.template")
  /** Template for the out-of-context synth+PnR script. */
  final val tclTemplate = Common.commonDir.resolve("evaluate_ip.tcl.template")

50
51
52
53
54
55
56
57
58
  def captureProcessOutput(input : java.io.InputStream) : Unit = {
    val buffer = new Array[Byte](1024)
    val stringBuilder : StringBuilder = new StringBuilder
    Stream.continually(input.read(buffer)).takeWhile(_ != -1).foreach(stringBuilder.append(buffer, 0, _))
    logger.debug(stringBuilder.toString())
    input.close()
  }

  // custom ProcessIO: Write stdout and stderr to DEBUG-level log.
59
60
  private final val io = new ProcessIO(
    stdin => {stdin.close()},
61
62
    captureProcessOutput,
    captureProcessOutput
63
64
65
66
67
68
69
  )

  /** Managment of temporary files, directories, reports. */
  private final class Files(zipFile: Path, reportFile: Path) {
    lazy val rpt_timing = reportFile.resolveSibling("timing.rpt")
    lazy val rpt_util   = reportFile.resolveSibling("utilization.rpt")
    lazy val rpt_power  = reportFile.resolveSibling("power.rpt")
70
    lazy val rpt_port   = reportFile.resolveSibling("port.rpt")
71
72
    lazy val s_dcp      = reportFile.resolveSibling("out-of-context_synth.dcp")
    lazy val i_dcp      = reportFile.resolveSibling("out-of-context_impl.dcp")
73
74
75
    lazy val zip        = zipFile
    lazy val vlnv       = VLNV.fromZip(zipFile)
    lazy val baseDir    = Files.createTempDirectory(null)
76
77
78
79
80
81
    lazy val logFile    = baseDir.resolve("evaluate.log")
    lazy val tclFile    = baseDir.resolve("evaluate.tcl")
  }

  /** Perform the evaluation.
    * @return true if successful **/
82
83
84
85
86
87
88
  def apply(zipFile: Path,
            targetPeriod: Double,
            targetPart: String,
            reportFile: Path,
            optimization: Int,
            synthOptions: Option[String] = None)
           (implicit cfg: Configuration): Boolean = {
89
90
91
92
93
94
95
96
97
    def deleteOnExit(f: java.io.File) = f.deleteOnExit
    //def deleteOnExit(f: java.io.File) = f        // keep files?

    // logging prefix
    val runPrefix = "evaluation of %s for %s@%1.3f MHz".format(zipFile, targetPart, 1000.0 / targetPeriod)

    // define report filenames
    Files.createDirectories(reportFile.getParent)
    val files = new Files(zipFile, reportFile)
98
    writeTclScript(files, targetPart, targetPeriod, optimization, synthOptions)
99

100
101
    val lt = new LogTrackingFileWatcher(Some(logger))
    cfg.verbose foreach { _ => lt += files.logFile }
102
103
104
105
106
107
108
109
110
111
112
113
    logger.info("starting {}, output in {}", runPrefix: Any, files.logFile)

    val vivadoCmd = Seq("vivado",
        "-mode", "batch",
        "-source", files.tclFile.toString,
        "-log", files.logFile.toString,
        "-notrace", "-nojournal")

    logger.trace("Vivado command: {}", vivadoCmd mkString " ")

    // execute Vivado (max runtime: 1d)
    val r = InterruptibleProcess(Process(vivadoCmd, files.baseDir.toFile),
114
        waitMillis = Some((if (optimization == 42) 14 else 1) * 24 * 60 * 60 * 1000)).!(io)
115

116
    cfg.verbose foreach { _ => lt.closeAll }
117
118
119
120
121
122
123
124

    if (r == InterruptibleProcess.TIMEOUT_RETCODE) {
      logger.error("%s: Vivado timeout error".format(runPrefix))
    } else {
      if (r == 0) {
        logger.trace("%s: Vivado finished successfully".format(runPrefix))
        val ur  = UtilizationReport(files.rpt_util).get
        val dpd = TimingReport(files.rpt_timing).get.dataPathDelay
125
126
        val numSlaves = PortReport(files.rpt_port).get.numSlaves
        writeXMLReport(reportFile, ur, dpd, targetPeriod, numSlaves)
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
        logger.info("{} finished successfully, report in {}", runPrefix: Any, reportFile)
        // clean up files on exit
        deleteOnExit(files.baseDir.toFile)
        deleteOnExit(files.baseDir.resolve(".Xil").toFile) // also remove Xilinx's crap
        deleteOnExit(files.tclFile.toFile)
        deleteOnExit(files.logFile.toFile)
      } else {
        logger.error("%s: Vivado finished with error (%d)".format(runPrefix, r))
      }
    }
    r == 0
  }

  /**
   * Writes the Tcl script for the out-of-context run.
   * @param files [[Files]] object for this run.
   * @param targetPart Part identifier of the target FPGA.
   * @param targetPeriod Target operating period.
   **/
146
147
148
149
150
  private def writeTclScript(files: Files,
                             targetPart: String,
                             targetPeriod: Double,
                             optimization: Int,
                             synthOptions: Option[String]): Unit = {
151
    val needles: scala.collection.mutable.Map[String, String] = scala.collection.mutable.Map(
152
153
154
      "BASE_DIR"           -> files.baseDir.toString,
      "ZIP_FILE"           -> files.zip.toString,
      "VLNV"               -> files.vlnv.toString,
155
156
157
158
159
      "PART"               -> targetPart,
      "PERIOD"             -> targetPeriod.toString,
      "REPORT_TIMING"      -> files.rpt_timing.toString,
      "REPORT_UTILIZATION" -> files.rpt_util.toString,
      "REPORT_POWER"       -> files.rpt_power.toString,
160
      "REPORT_PORT"        -> files.rpt_port.toString,
161
162
      "SYNTH_CHECKPOINT"   -> files.s_dcp.toString,
      "IMPL_CHECKPOINT"    -> files.i_dcp.toString,
163
164
      "OPTIMIZATION"       -> optimization.toString,
      "SYNTH_OPTIONS"      -> (synthOptions getOrElse "")
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
    )

    // write Tcl script
    Template.interpolateFile(
        Template.DEFAULT_NEEDLE,
        tclTemplate.toString,
        files.tclFile.toString,
        needles)
  }

  /**
   * Writes the output report in XML format (similar to Vivado HLS).
   * @param reportFile Output file name.
   * @param ur [[UtilizationReport]] instance.
   * @param dataPathDelay Delay on longest combinatorial path in datapath.
   * @param targetPeriod Target operating period.
   **/
  private def writeXMLReport(reportFile: Path, ur: UtilizationReport, dataPathDelay: Double,
183
      targetPeriod: Double, slaves : Int): Unit = {
184
185
186
187
188
189
190
191
192
193
194
195
    val needles = scala.collection.mutable.Map[String, String](
      "SLICE"      -> ur.used.SLICE.toString,
      "SLICES"     -> ur.available.SLICE.toString,
      "LUT"        -> ur.used.LUT.toString,
      "LUTS"       -> ur.available.LUT.toString,
      "FF"         -> ur.used.FF.toString,
      "FFS"        -> ur.available.FF.toString,
      "BRAM"       -> ur.used.BRAM.toString,
      "BRAMS"      -> ur.available.BRAM.toString,
      "DSP"        -> ur.used.DSP.toString,
      "DSPS"       -> ur.available.DSP.toString,
      "PERIOD"     -> targetPeriod.toString,
196
197
      "MIN_PERIOD" -> dataPathDelay.toString,
      "SLAVES"     -> slaves.toString
198
199
200
201
202
203
204
205
206
207
208
    )

    // write final report
    Template.interpolateFile(
        Template.DEFAULT_NEEDLE,
        reportTemplate.toString,
        reportFile.toString,
        needles
    )
  }
}