summaryrefslogtreecommitdiff
path: root/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala
blob: 8c85b6d1941df233a79da8b601710aa2e6b11fe6 (plain)
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// SPDX-License-Identifier: Apache-2.0

package chisel3.util.experimental.decode

import chisel3.util.BitPat
import logger.LazyLogging

case object EspressoNotFoundException extends Exception

/** A [[Minimizer]] implementation to use espresso to minimize the [[TruthTable]].
  *
  * espresso uses heuristic algorithm providing a sub-optimized) result.
  * For implementation details, please refer to:
  * [[https://www.springerprofessional.de/en/logic-minimization-algorithms-for-vlsi-synthesis/13780088]]
  *
  * a espresso executable should be downloaded from [[https://github.com/chipsalliance/espresso]]
  *
  * If user want to user the this [[Minimizer]], a espresso executable should be added to system PATH environment.
  */
object EspressoMinimizer extends Minimizer with LazyLogging {
  def minimize(table: TruthTable): TruthTable =
    TruthTable.merge(TruthTable.split(table).map { case (table, indexes) => (espresso(table), indexes) })

  private def espresso(table: TruthTable): TruthTable = {
    def writeTable(table: TruthTable): String = {
      def invert(string: String) = string
        .replace('0', 't')
        .replace('1', '0')
        .replace('t', '1')
      val defaultType: Char = {
        val t = table.default.rawString.toCharArray.distinct
        require(t.length == 1, "Internal Error: espresso only accept unified default type.")
        t.head
      }
      val tableType: String = defaultType match {
        case '?' => "fr"
        case _   => "fd"
      }
      val rawTable = table.toString
        .split("\n")
        .filter(_.contains("->"))
        .mkString("\n")
        .replace("->", " ")
        .replace('?', '-')
      // invert all output, since espresso cannot handle default is on.
      val invertRawTable = rawTable
        .split("\n")
        .map(_.split(" "))
        .map(row => s"${row(0)} ${invert(row(1))}")
        .mkString("\n")
      s""".i ${table.inputWidth}
         |.o ${table.outputWidth}
         |.type ${tableType}
         |""".stripMargin ++ (if (defaultType == '1') invertRawTable else rawTable)
    }

    def readTable(espressoTable: String) = {
      def bitPat(espresso: String): BitPat = BitPat("b" + espresso.replace('-', '?'))

      val out = espressoTable
        .split("\n")
        .filterNot(_.startsWith("."))
        .map(_.split(' '))
        .map(row => bitPat(row(0)) -> bitPat(row(1)))
      // special case for 0 and DontCare, if output is not couple to input
      if (out.isEmpty)
        Array(
          (
            BitPat(s"b${"?" * table.inputWidth}"),
            BitPat(s"b${"0" * table.outputWidth}")
          )
        )
      else out
    }

    val input = writeTable(table)
    logger.trace(s"""espresso input table:
                    |$input
                    |""".stripMargin)
    val output =
      try {
        os.proc("espresso").call(stdin = input).out.chunks.mkString
      } catch {
        case e: java.io.IOException if e.getMessage.contains("error=2, No such file or directory") =>
          throw EspressoNotFoundException
      }
    logger.trace(s"""espresso output table:
                    |$output
                    |""".stripMargin)
    TruthTable.fromEspressoOutput(readTable(output), table.default)
  }
}