From 57a7c00a2e1846eb5f1e0355d38a45c40793a835 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sun, 11 Jul 2021 02:43:03 +0800 Subject: [PATCH 1/7] add espresso to CI. --- .github/workflows/test.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6d68ad1a534..b2620d2f4f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,7 @@ jobs: scala: ["2.13.6", "2.12.13"] verilator: ["4.204"] z3: ["4.8.10"] + espresso: ["2.4"] runs-on: ${{ matrix.system }} steps: @@ -74,6 +75,28 @@ jobs: sudo make install verilator --version + - name: Cache Espresso ${{ matrix.espresso }} + uses: actions/cache@v2 + id: cache-espresso + with: + path: espresso-${{ matrix.espresso }} + key: ${{ matrix.system }}-espresso-${{ matrix.espresso }} + - name: Compile Espresso ${{ matrix.espresso }} + if: steps.cache-espresso.outputs.cache-hit != 'true' + run: | + wget https://github.com/chipsalliance/espresso/archive/refs/tags/v${{ matrix.espresso }}.tar.gz + tar xvf v${{ matrix.espresso }}.tar.gz + cd espresso-${{ matrix.espresso }} + mkdir -p build + cd build + cmake .. + make + - name: Install Espresso ${{ matrix.espresso }} + run: | + cd espresso-${{ matrix.espresso }}/build + sudo make install + + - name: Setup Scala uses: olafurpg/setup-scala@v10 with: From 1b1a801d1fe9f81a68c2b5b615d8923e7c2af302 Mon Sep 17 00:00:00 2001 From: Haoran Yuan Date: Thu, 6 May 2021 16:23:56 +0000 Subject: [PATCH 2/7] Implement espressoMinimizer --- .../decode/EspressoMinimizer.scala | 69 +++++++++++++++++++ .../experimental/minimizer/EspressoSpec.scala | 9 +++ 2 files changed, 78 insertions(+) create mode 100644 src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala create mode 100644 src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala diff --git a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala new file mode 100644 index 00000000000..6763600b8c5 --- /dev/null +++ b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.util.experimental.decode + +import chisel3.util.BitPat +import logger.LazyLogging + +object EspressoMinimizer extends Minimizer with LazyLogging { + def minimize(table: TruthTable): TruthTable = + TruthTable.merge(TruthTable.split(table).map{case (table, indexes) => (espresso(table), indexes)}) + + 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.toString.drop(7).dropRight(1).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): Map[BitPat, BitPat] = { + def bitPat(espresso: String): BitPat = BitPat("b" + espresso.replace('-', '?')) + + espressoTable + .split("\n") + .filterNot(_.startsWith(".")) + .map(_.split(' ')) + .map(row => bitPat(row(0)) -> bitPat(row(1))) + .toMap + } + + // Since Espresso don't implements pipe, we use a temp file to do so. + val input = writeTable(table) + logger.trace(s"""espresso input table: + |$input + |""".stripMargin) + val f = os.temp(input) + val o = os.proc("espresso", f).call().out.chunks.mkString + logger.trace(s"""espresso output table: + |$o + |""".stripMargin) + TruthTable(readTable(o), table.default) + } +} diff --git a/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala b/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala new file mode 100644 index 00000000000..f3270cae0b1 --- /dev/null +++ b/src/test/scala/chiselTests/util/experimental/minimizer/EspressoSpec.scala @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental.minimizer +import chisel3.util.experimental.decode.EspressoMinimizer +import chisel3.util.experimental.decode.Minimizer + +class EspressoSpec extends MinimizerSpec { + override def minimizer: Minimizer = EspressoMinimizer +} From 86ca3e292a0091ad5b187f612949d3cd7b4b69f9 Mon Sep 17 00:00:00 2001 From: Boyang Han Date: Fri, 9 Jul 2021 21:29:54 -0700 Subject: [PATCH 3/7] Fix truth table indexing --- .../scala/chisel3/util/experimental/decode/TruthTable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala index ca0ff8b4545..f4f200cebaf 100644 --- a/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala +++ b/src/main/scala/chisel3/util/experimental/decode/TruthTable.scala @@ -99,7 +99,7 @@ object TruthTable { tables: Seq[(TruthTable, Seq[Int])] ): TruthTable = { def reIndex(bitPat: BitPat, table: TruthTable, indexes: Seq[Int]): Seq[(Char, Int)] = - bpStr(table.table.getOrElse(bitPat, BitPat.dontCare(indexes.size))).zip(indexes) + bpStr(table.table.map(a => a._1.toString -> a._2).getOrElse(bitPat.toString, BitPat.dontCare(indexes.size))).zip(indexes) def bitPat(indexedChar: Seq[(Char, Int)]) = BitPat(s"b${indexedChar .sortBy(_._2) .map(_._1) From 3e5437ea2fe1b90b8ec7da543d3159a69698d5f7 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Wed, 16 Jun 2021 03:44:08 +0000 Subject: [PATCH 4/7] add shortcut to espresso and qmc. --- .../util/experimental/decode/decoder.scala | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/main/scala/chisel3/util/experimental/decode/decoder.scala b/src/main/scala/chisel3/util/experimental/decode/decoder.scala index 8168824fc8d..87083e0d926 100644 --- a/src/main/scala/chisel3/util/experimental/decode/decoder.scala +++ b/src/main/scala/chisel3/util/experimental/decode/decoder.scala @@ -10,6 +10,13 @@ import firrtl.annotations.Annotation import logger.LazyLogging object decoder extends LazyLogging { + /** Use a specific [[Minimizer]] to generated decoded signals. + * + * @param minimizer specific [[Minimizer]], can be [[QMCMinimizer]] or [[EspressoMinimizer]]. + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ def apply(minimizer: Minimizer, input: UInt, truthTable: TruthTable): UInt = { val minimizedTable = getAnnotations().collect { case DecodeTableAnnotation(_, in, out) => TruthTable(in) -> TruthTable(out) @@ -31,4 +38,38 @@ object decoder extends LazyLogging { plaOutput } } + + /** Use [[EspressoMinimizer]] to generated decoded signals. + * + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ + def espresso(input: UInt, truthTable: TruthTable): UInt = apply(EspressoMinimizer, input, truthTable) + + /** Use [[QMCMinimizer]] to generated decoded signals. + * + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ + def qmc(input: UInt, truthTable: TruthTable): UInt = apply(QMCMinimizer, input, truthTable) + + /** try to use [[EspressoMinimizer]] to decode `input` by `truthTable` + * if `espresso` not exist in your PATH environment it will fall back to [[QMCMinimizer]], and print a warning. + * + * @param input input signal that contains decode table input + * @param truthTable [[TruthTable]] to decode user input. + * @return decode table output. + */ + def apply(input: UInt, truthTable: TruthTable): UInt = try espresso(input, truthTable) catch { + case _: java.io.IOException => + logger.error( + """espresso is not found in your PATH, fall back to QMC. + |Quine-McCluskey is a NP complete algorithm, may run forever or run out of memory in large decode tables. + |To get rid of this warning, please use `decoder.qmc` directly, or add espresso to your PATH. + |""".stripMargin + ) + qmc(input, truthTable) + } } From c450bb4987a25d18a1ccb52692644796c78942ae Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Tue, 13 Jul 2021 04:21:12 +0800 Subject: [PATCH 5/7] add EspressoNotFoundException. --- .../util/experimental/decode/EspressoMinimizer.scala | 8 +++++++- .../scala/chisel3/util/experimental/decode/decoder.scala | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala index 6763600b8c5..b028bfcf379 100644 --- a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala +++ b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala @@ -5,6 +5,8 @@ package chisel3.util.experimental.decode import chisel3.util.BitPat import logger.LazyLogging +case object EspressoNotFoundException extends Exception + object EspressoMinimizer extends Minimizer with LazyLogging { def minimize(table: TruthTable): TruthTable = TruthTable.merge(TruthTable.split(table).map{case (table, indexes) => (espresso(table), indexes)}) @@ -60,7 +62,11 @@ object EspressoMinimizer extends Minimizer with LazyLogging { |$input |""".stripMargin) val f = os.temp(input) - val o = os.proc("espresso", f).call().out.chunks.mkString + val o = try { + os.proc("espresso", f).call().out.chunks.mkString + } catch { + case _ : java.io.IOException => throw EspressoNotFoundException + } logger.trace(s"""espresso output table: |$o |""".stripMargin) diff --git a/src/main/scala/chisel3/util/experimental/decode/decoder.scala b/src/main/scala/chisel3/util/experimental/decode/decoder.scala index 87083e0d926..3b6bc7be855 100644 --- a/src/main/scala/chisel3/util/experimental/decode/decoder.scala +++ b/src/main/scala/chisel3/util/experimental/decode/decoder.scala @@ -63,7 +63,7 @@ object decoder extends LazyLogging { * @return decode table output. */ def apply(input: UInt, truthTable: TruthTable): UInt = try espresso(input, truthTable) catch { - case _: java.io.IOException => + case EspressoNotFoundException => logger.error( """espresso is not found in your PATH, fall back to QMC. |Quine-McCluskey is a NP complete algorithm, may run forever or run out of memory in large decode tables. From 424a54470f0ffc55a2a94a76f403efef73743588 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Tue, 13 Jul 2021 09:54:39 +0800 Subject: [PATCH 6/7] only catch "No such file or directory" --- .../chisel3/util/experimental/decode/EspressoMinimizer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala index b028bfcf379..0351a46af91 100644 --- a/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala +++ b/src/main/scala/chisel3/util/experimental/decode/EspressoMinimizer.scala @@ -65,7 +65,7 @@ object EspressoMinimizer extends Minimizer with LazyLogging { val o = try { os.proc("espresso", f).call().out.chunks.mkString } catch { - case _ : java.io.IOException => throw EspressoNotFoundException + case e: java.io.IOException if e.getMessage.contains("error=2, No such file or directory") => throw EspressoNotFoundException } logger.trace(s"""espresso output table: |$o From 4ecd25f214e67d92b532e7fb94a55263bcff840a Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Wed, 14 Jul 2021 02:39:27 +0800 Subject: [PATCH 7/7] catch all IOException. --- .../util/experimental/decode/decoder.scala | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/scala/chisel3/util/experimental/decode/decoder.scala b/src/main/scala/chisel3/util/experimental/decode/decoder.scala index 3b6bc7be855..42e374d1445 100644 --- a/src/main/scala/chisel3/util/experimental/decode/decoder.scala +++ b/src/main/scala/chisel3/util/experimental/decode/decoder.scala @@ -62,14 +62,22 @@ object decoder extends LazyLogging { * @param truthTable [[TruthTable]] to decode user input. * @return decode table output. */ - def apply(input: UInt, truthTable: TruthTable): UInt = try espresso(input, truthTable) catch { - case EspressoNotFoundException => - logger.error( - """espresso is not found in your PATH, fall back to QMC. - |Quine-McCluskey is a NP complete algorithm, may run forever or run out of memory in large decode tables. - |To get rid of this warning, please use `decoder.qmc` directly, or add espresso to your PATH. - |""".stripMargin - ) + def apply(input: UInt, truthTable: TruthTable): UInt = { + def qmcFallBack(input: UInt, truthTable: TruthTable) = { + """fall back to QMC. + |Quine-McCluskey is a NP complete algorithm, may run forever or run out of memory in large decode tables. + |To get rid of this warning, please use `decoder.qmc` directly, or add espresso to your PATH. + |""".stripMargin qmc(input, truthTable) + } + + try espresso(input, truthTable) catch { + case EspressoNotFoundException => + logger.error(s"espresso is not found in your PATH:\n${sys.env("PATH").split(":").mkString("\n")}".stripMargin) + qmcFallBack(input, truthTable) + case e: java.io.IOException => + logger.error(s"espresso failed to run with ${e.getMessage}") + qmcFallBack(input, truthTable) + } } }