diff --git a/WORKSPACE b/WORKSPACE index f08e2fe3fa..14e609a0b7 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -154,6 +154,8 @@ hedron_compile_commands_setup() # install dependencies for yosys/ABC circuit optimizers http_archive( name = "rules_hdl", + patch_args = ["-p1"], + patches = ["//bazel:yosys.patch"], # Commit on 2023-06-13, current as of 2023-06-13. sha256 = "21307b0c14a036f1b4879c8f1d4d50a115053eb87c428307d4d6569c3e7ba859", strip_prefix = "bazel_rules_hdl-e6540a5bccbfb124aec0b19deaa9cf855781b3a5", diff --git a/bazel/yosys.patch b/bazel/yosys.patch new file mode 100644 index 0000000000..eae1e60458 --- /dev/null +++ b/bazel/yosys.patch @@ -0,0 +1,13 @@ +diff --git a/dependency_support/at_clifford_yosys/bundled.BUILD.bazel b/dependency_support/at_clifford_yosys/bundled.BUILD.bazel +--- a/dependency_support/at_clifford_yosys/bundled.BUILD.bazel ++++ b/dependency_support/at_clifford_yosys/bundled.BUILD.bazel +@@ -256,8 +256,9 @@ + ] + ) + [ + ":common_techlibs" + ], ++ visibility = ["//visibility:public"], + ) + + yosys_syntax_check( + name = "testing_yosys_syntax_check_build_rule", diff --git a/include/Transforms/YosysOptimizer/BUILD b/include/Transforms/YosysOptimizer/BUILD new file mode 100644 index 0000000000..dc286f3982 --- /dev/null +++ b/include/Transforms/YosysOptimizer/BUILD @@ -0,0 +1,35 @@ +load("@llvm-project//mlir:tblgen.bzl", "gentbl_cc_library") + +package( + default_applicable_licenses = ["@heir//:license"], + default_visibility = ["//visibility:public"], +) + +exports_files( + [ + "YosysOptimizer.h", + ], +) + +gentbl_cc_library( + name = "pass_inc_gen", + tbl_outs = [ + ( + [ + "-gen-pass-decls", + "-name=YosysOptimizer", + ], + "YosysOptimizer.h.inc", + ), + ( + ["-gen-pass-doc"], + "YosysOptimizer.md", + ), + ], + tblgen = "@llvm-project//mlir:mlir-tblgen", + td_file = "YosysOptimizer.td", + deps = [ + "@llvm-project//mlir:OpBaseTdFiles", + "@llvm-project//mlir:PassBaseTdFiles", + ], +) diff --git a/include/Transforms/YosysOptimizer/YosysOptimizer.h b/include/Transforms/YosysOptimizer/YosysOptimizer.h new file mode 100644 index 0000000000..06927f3e61 --- /dev/null +++ b/include/Transforms/YosysOptimizer/YosysOptimizer.h @@ -0,0 +1,20 @@ +#ifndef INCLUDE_TRANSFORMS_YOSYSOPTIMIZER_YOSYSOPTIMIZER_H_ +#define INCLUDE_TRANSFORMS_YOSYSOPTIMIZER_YOSYSOPTIMIZER_H_ + +#include "mlir/include/mlir/Pass/Pass.h" // from @llvm-project + +namespace mlir { +namespace heir { + +std::unique_ptr createYosysOptimizer(std::string_view runfiles); + +#define GEN_PASS_DECL +#include "include/Transforms/YosysOptimizer/YosysOptimizer.h.inc" + +#define GEN_PASS_REGISTRATION +#include "include/Transforms/YosysOptimizer/YosysOptimizer.h.inc" + +} // namespace heir +} // namespace mlir + +#endif // INCLUDE_TRANSFORMS_YOSYSOPTIMIZER_YOSYSOPTIMIZER_H_ diff --git a/include/Transforms/YosysOptimizer/YosysOptimizer.td b/include/Transforms/YosysOptimizer/YosysOptimizer.td new file mode 100644 index 0000000000..3e5ad414fd --- /dev/null +++ b/include/Transforms/YosysOptimizer/YosysOptimizer.td @@ -0,0 +1,19 @@ +#ifndef INCLUDE_TRANSFORMS_YOSYSOPTIMIZER_YOSYSOPTIMIZER_TD_ +#define INCLUDE_TRANSFORMS_YOSYSOPTIMIZER_YOSYSOPTIMIZER_TD_ + +include "mlir/Pass/PassBase.td" + +def YosysOptimizer : Pass<"yosys-optimizer"> { + let summary = "Invoke Yosys to perform circuit optimization."; + + let description = [{ + This pass invokes Yosys to convert an arithmetic circuit to an optimized + boolean circuit that uses the arith and comb dialects. + }]; + let dependentDialects = [ + "mlir::arith::ArithDialect", + "mlir::heir::comb::CombDialect", + ]; +} + +#endif // INCLUDE_TRANSFORMS_YOSYSOPTIMIZER_YOSYSOPTIMIZER_TD_ diff --git a/lib/Transforms/YosysOptimizer/BUILD b/lib/Transforms/YosysOptimizer/BUILD new file mode 100644 index 0000000000..7c490344f9 --- /dev/null +++ b/lib/Transforms/YosysOptimizer/BUILD @@ -0,0 +1,80 @@ +# YosysOptimizer pass + +package( + default_applicable_licenses = ["@heir//:license"], + default_visibility = ["//visibility:public"], +) + +cc_library( + name = "RTLILImporter", + srcs = ["RTLILImporter.cpp"], + hdrs = ["RTLILImporter.h"], + deps = [ + "@at_clifford_yosys//:kernel", + "@heir//lib/Dialect/Comb/IR:Dialect", + "@llvm-project//mlir:ArithDialect", + "@llvm-project//mlir:FuncDialect", + "@llvm-project//mlir:IR", + "@llvm-project//mlir:TransformUtils", + ], +) + +cc_library( + name = "LUTImporter", + srcs = ["LUTImporter.cpp"], + hdrs = ["LUTImporter.h"], + deps = [ + ":RTLILImporter", + "@at_clifford_yosys//:kernel", + "@heir//lib/Dialect/Comb/IR:Dialect", + "@llvm-project//llvm:Support", + "@llvm-project//mlir:ArithDialect", + "@llvm-project//mlir:FuncDialect", + "@llvm-project//mlir:IR", + ], +) + +cc_test( + name = "LUTImporterTest", + size = "small", + srcs = ["LUTImporterTest.cpp"], + data = glob([ + "tests/*.rtlil", + ]), + deps = [ + ":LUTImporter", + "@at_clifford_yosys//:kernel", + "@at_clifford_yosys//:version", + "@googletest//:gtest", + "@heir//lib/Dialect/Comb/IR:Dialect", + "@llvm-project//mlir:ArithDialect", + "@llvm-project//mlir:IR", + ], +) + +cc_library( + name = "YosysOptimizer", + srcs = ["YosysOptimizer.cpp"], + hdrs = [ + "@heir//include/Transforms/YosysOptimizer:YosysOptimizer.h", + ], + data = [ + ":yosys/map_lut_to_lut3.v", + "@at_clifford_yosys//:share_files", + "@edu_berkeley_abc//:abc", + ], + deps = [ + ":LUTImporter", + "@at_clifford_yosys//:kernel", + "@at_clifford_yosys//:version", + "@bazel_tools//tools/cpp/runfiles", + "@heir//include/Transforms/YosysOptimizer:pass_inc_gen", + "@heir//lib/Dialect/Comb/IR:Dialect", + "@heir//lib/Target/Verilog:VerilogEmitter", + "@llvm-project//llvm:Support", + "@llvm-project//mlir:FuncDialect", + "@llvm-project//mlir:IR", + "@llvm-project//mlir:Pass", + "@llvm-project//mlir:Transforms", + ], +) diff --git a/lib/Transforms/YosysOptimizer/LUTImporter.cpp b/lib/Transforms/YosysOptimizer/LUTImporter.cpp new file mode 100644 index 0000000000..8992cf3ff9 --- /dev/null +++ b/lib/Transforms/YosysOptimizer/LUTImporter.cpp @@ -0,0 +1,53 @@ +#include "lib/Transforms/YosysOptimizer/LUTImporter.h" + +#include "include/Dialect/Comb/IR/CombOps.h" +#include "kernel/rtlil.h" // from @at_clifford_yosys +#include "llvm/include/llvm/Support/FormatVariadic.h" // from @llvm-project +#include "mlir/include/mlir/IR/ImplicitLocOpBuilder.h" // from @llvm-project +#include "mlir/include/mlir/IR/Operation.h" // from @llvm-project + +namespace mlir { +namespace heir { + +mlir::Operation *LUTImporter::createOp(Yosys::RTLIL::Cell *cell, + SmallVector &inputs, + ImplicitLocOpBuilder &b) const { + assert(cell->type.begins_with("\\lut")); + + // Create truth table from cell attributes. + int lutSize; + StringRef(cell->type.substr(4, 1)).getAsInteger(10, lutSize); + SmallVector lutValues(1 << lutSize, false); + + for (int i = 0; i < lutValues.size(); i++) { + auto lutStr = + cell->getPort(Yosys::RTLIL::IdString(llvm::formatv("\\P{0}", i))); + lutValues[i] = lutStr.as_bool(); + } + + auto lookupTable = b.getBoolArrayAttr(llvm::ArrayRef(lutValues)); + return b.create(inputs, lookupTable); +} + +SmallVector LUTImporter::getInputs( + Yosys::RTLIL::Cell *cell) const { + assert(cell->type.begins_with("\\lut") && "expected lut cells"); + + // Return all non-P, non-Y named attributes. + SmallVector inputs; + for (auto &conn : cell->connections()) { + if (conn.first.contains("P") || conn.first.contains("Y")) { + continue; + } + inputs.push_back(conn.second); + } + return inputs; +} + +Yosys::RTLIL::SigSpec LUTImporter::getOutput(Yosys::RTLIL::Cell *cell) const { + assert(cell->type.begins_with("\\lut")); + return cell->getPort(Yosys::RTLIL::IdString("\\Y")); +} + +} // namespace heir +} // namespace mlir diff --git a/lib/Transforms/YosysOptimizer/LUTImporter.h b/lib/Transforms/YosysOptimizer/LUTImporter.h new file mode 100644 index 0000000000..81d6159485 --- /dev/null +++ b/lib/Transforms/YosysOptimizer/LUTImporter.h @@ -0,0 +1,28 @@ +#ifndef HEIR_LIB_TRANSFORMS_YOSYSOPTIMIZER_LUTIMPORTER_H_ +#define HEIR_LIB_TRANSFORMS_YOSYSOPTIMIZER_LUTIMPORTER_H_ + +#include "kernel/rtlil.h" // from @at_clifford_yosys +#include "lib/Transforms/YosysOptimizer/RTLILImporter.h" + +namespace mlir { +namespace heir { + +// LUTImporter implements the RTLILConfig for importing RTLIL that uses LUTs. +class LUTImporter : public RTLILImporter { + public: + LUTImporter(MLIRContext *context) : RTLILImporter(context) {} + + protected: + Operation *createOp(Yosys::RTLIL::Cell *cell, SmallVector &inputs, + ImplicitLocOpBuilder &b) const override; + + SmallVector getInputs( + Yosys::RTLIL::Cell *cell) const override; + + Yosys::RTLIL::SigSpec getOutput(Yosys::RTLIL::Cell *cell) const override; +}; + +} // namespace heir +} // namespace mlir + +#endif // HEIR_LIB_TRANSFORMS_YOSYSOPTIMIZER_LUTIMPORTER_H_ diff --git a/lib/Transforms/YosysOptimizer/LUTImporterTest.cpp b/lib/Transforms/YosysOptimizer/LUTImporterTest.cpp new file mode 100644 index 0000000000..0e0ab22836 --- /dev/null +++ b/lib/Transforms/YosysOptimizer/LUTImporterTest.cpp @@ -0,0 +1,164 @@ +#include "gmock/gmock.h" // from @googletest +#include "gtest/gtest.h" // from @googletest +#include "include/Dialect/Comb/IR/CombOps.h" +#include "kernel/yosys.h" // from @at_clifford_yosys +#include "lib/Transforms/YosysOptimizer/LUTImporter.h" +#include "mlir/include/mlir/Dialect/Arith/IR/Arith.h" // from @llvm-project +#include "mlir/include/mlir/IR/MLIRContext.h" // from @llvm-project + +namespace mlir::heir { +namespace { + +using ::testing::ElementsAreArray; +using ::testing::Test; + +// Returns a list of cell names that are topologically ordered using the Yosys +// toder output. This is extracted from the lines containing cells in the +// output: +// -- Running command `torder -stop * P*;' -- + +// 14. Executing TORDER pass (print cells in topological order). +// module test_add +// cell $abc$167$auto$blifparse.cc:525:parse_blif$168 +// cell $abc$167$auto$blifparse.cc:525:parse_blif$170 +// cell $abc$167$auto$blifparse.cc:525:parse_blif$169 +// cell $abc$167$auto$blifparse.cc:525:parse_blif$171 +llvm::SmallVector getTopologicalOrder( + std::stringstream &torderOutput) { + llvm::SmallVector cells; + std::string line; + while (std::getline(torderOutput, line)) { + auto lineCell = line.find("cell "); + if (lineCell != std::string::npos) { + cells.push_back(line.substr(lineCell + 5, std::string::npos)); + } + } + return cells; +} + +class LUTImporterTestFixture : public Test { + protected: + void SetUp() override { + context.loadDialect(); + Yosys::yosys_setup(); + } + + func::FuncOp runImporter(const std::string &rtlil) { + Yosys::run_pass("read_rtlil " + rtlil); + Yosys::run_pass("proc; hierarchy -generate lut* o:Y i:P i:*;"); + + // Get topological ordering. + std::stringstream cellOrder; + Yosys::log_streams.push_back(&cellOrder); + Yosys::run_pass("torder -stop i P*;"); + Yosys::log_streams.clear(); + + LUTImporter lutImporter = LUTImporter(&context); + + auto topologicalOrder = getTopologicalOrder(cellOrder); + Yosys::RTLIL::Design *design = Yosys::yosys_get_design(); + auto func = + lutImporter.importModule(design->top_module(), topologicalOrder); + return func; + } + + void TearDown() override { Yosys::run_pass("delete"); } + + MLIRContext context; +}; + +// Note that we cannot lower truth tables to LLVM, so we must assert the IR +// rather than executing the code. +TEST_F(LUTImporterTestFixture, AddOneLUT3) { + std::vector> expectedLuts = { + {0, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 1, 0}, + {0, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 1, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 1, 1, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 1}, + {0, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 1, 0}, + {0, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 1, 0, 0, 0, 0, 0}, + {1, 0, 0, 0, 0, 0, 0, 0}}; + + auto func = + runImporter("lib/Transforms/YosysOptimizer/tests/add_one_lut3.rtlil"); + + auto funcType = func.getFunctionType(); + EXPECT_EQ(funcType.getNumInputs(), 1); + EXPECT_EQ(funcType.getInput(0).getIntOrFloatBitWidth(), 8); + EXPECT_EQ(funcType.getNumResults(), 1); + EXPECT_EQ(funcType.getResult(0).getIntOrFloatBitWidth(), 8); + + auto combOps = func.getOps().begin(); + for (size_t i = 0; i < expectedLuts.size(); i++) { + SmallVector table(llvm::map_range( + (*combOps++).getLookupTableAttr().getAsValueRange(), + [](const APInt &a) { return !a.isZero(); })); + EXPECT_THAT(table, ElementsAreArray(expectedLuts[i])); + } +} + +TEST_F(LUTImporterTestFixture, AddOneLUT5) { + auto func = + runImporter("lib/Transforms/YosysOptimizer/tests/add_one_lut5.rtlil"); + + auto funcType = func.getFunctionType(); + EXPECT_EQ(funcType.getNumInputs(), 1); + EXPECT_EQ(funcType.getInput(0).getIntOrFloatBitWidth(), 8); + EXPECT_EQ(funcType.getNumResults(), 1); + EXPECT_EQ(funcType.getResult(0).getIntOrFloatBitWidth(), 8); + + auto combOps = func.getOps(); + for (auto combOp : combOps) { + SmallVector table(llvm::map_range( + combOp.getLookupTableAttr().getAsValueRange(), + [](const APInt &a) { return !a.isZero(); })); + EXPECT_EQ(table.size(), 32); + } +} + +// This test doubles the input, which simply connects the output wire to the +// first bits of the input and a constant. +// comb.concat inputs are written in MSB to LSB ordering. See +// https://circt.llvm.org/docs/Dialects/Comb/RationaleComb/#endianness-operand-ordering-and-internal-representation +TEST_F(LUTImporterTestFixture, DoubleInput) { + auto func = + runImporter("lib/Transforms/YosysOptimizer/tests/double_input.rtlil"); + + auto funcType = func.getFunctionType(); + EXPECT_EQ(funcType.getNumInputs(), 1); + EXPECT_EQ(funcType.getInput(0).getIntOrFloatBitWidth(), 8); + EXPECT_EQ(funcType.getNumResults(), 1); + EXPECT_EQ(funcType.getResult(0).getIntOrFloatBitWidth(), 8); + + auto returnOp = *func.getOps().begin(); + auto concatOp = returnOp.getOperands()[0].getDefiningOp(); + ASSERT_TRUE(concatOp); + EXPECT_EQ(concatOp->getNumOperands(), 8); + arith::ConstantOp constOp = + concatOp->getOperands()[7].getDefiningOp(); + ASSERT_TRUE(constOp); + auto constVal = dyn_cast(constOp.getValue()); + ASSERT_TRUE(constVal); + EXPECT_EQ(constVal.getInt(), 0); +} + +TEST_F(LUTImporterTestFixture, MultipleInputs) { + auto func = + runImporter("lib/Transforms/YosysOptimizer/tests/multiple_inputs.rtlil"); + + auto funcType = func.getFunctionType(); + EXPECT_EQ(funcType.getNumInputs(), 2); + EXPECT_EQ(funcType.getInput(0).getIntOrFloatBitWidth(), 8); + EXPECT_EQ(funcType.getInput(1).getIntOrFloatBitWidth(), 8); + EXPECT_EQ(funcType.getNumResults(), 1); + EXPECT_EQ(funcType.getResult(0).getIntOrFloatBitWidth(), 8); +} + +} // namespace +} // namespace mlir::heir + +// We use a custom main function here to avoid issues with Yosys' main driver. +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/lib/Transforms/YosysOptimizer/RTLILImporter.cpp b/lib/Transforms/YosysOptimizer/RTLILImporter.cpp new file mode 100644 index 0000000000..40e0107e54 --- /dev/null +++ b/lib/Transforms/YosysOptimizer/RTLILImporter.cpp @@ -0,0 +1,175 @@ +#include "lib/Transforms/YosysOptimizer/RTLILImporter.h" + +#include "include/Dialect/Comb/IR/CombOps.h" +#include "kernel/rtlil.h" // from @at_clifford_yosys +#include "mlir/include/mlir/Dialect/Arith/IR/Arith.h" // from @llvm-project +#include "mlir/include/mlir/Dialect/Func/IR/FuncOps.h" // from @llvm-project +#include "mlir/include/mlir/IR/ImplicitLocOpBuilder.h" // from @llvm-project +#include "mlir/include/mlir/IR/Operation.h" // from @llvm-project +#include "mlir/include/mlir/Transforms/FoldUtils.h" // from @llvm-project + +namespace mlir { +namespace heir { + +using ::Yosys::RTLIL::Module; +using ::Yosys::RTLIL::SigSpec; +using ::Yosys::RTLIL::Wire; + +llvm::SmallVector getTopologicalOrder( + std::stringstream &torderOutput) { + llvm::SmallVector cells; + std::string line; + while (std::getline(torderOutput, line)) { + auto lineCell = line.find("cell "); + if (lineCell != std::string::npos) { + cells.push_back(line.substr(lineCell + 5, std::string::npos)); + } + } + return cells; +} + +void RTLILImporter::addWireValue(Wire *wire, Value value) { + wireNameToValue.insert(std::make_pair(wire->name.str(), value)); +} + +Value RTLILImporter::getWireValue(Wire *wire) { + auto wireName = wire->name.str(); + assert(wireNameToValue.contains(wireName)); + return wireNameToValue.at(wireName); +} + +Value RTLILImporter::getBit(const SigSpec &conn, ImplicitLocOpBuilder &b) { + // Because the cells are in topological order, and Yosys should have + // removed redundant wire-wire mappings, the cell's inputs must be a bit + // of an input wire, in the map of already defined wires (which are + // bits), or a constant bit. + assert(conn.is_wire() || conn.is_fully_const() || conn.is_bit()); + if (conn.is_wire()) { + auto name = conn.as_wire()->name.str(); + assert(wireNameToValue.contains(name)); + return wireNameToValue[name]; + } + if (conn.is_fully_const()) { + auto bit = conn.as_const(); + auto constantOp = b.createOrFold( + b.getIntegerAttr(b.getIntegerType(1), bit.as_int())); + return constantOp; + } + // Extract the bit of the multi-bit input wire. + assert(conn.as_bit().is_wire()); + auto bit = conn.as_bit(); + auto argA = getWireValue(bit.wire); + auto extractOp = + b.createOrFold(b.getI1Type(), argA, bit.offset); + return extractOp; +} + +void RTLILImporter::addResultBit( + const SigSpec &conn, Value result, + llvm::MapVector> &retBitValues) { + assert(conn.is_wire() || conn.is_bit()); + if (conn.is_wire()) { + addWireValue(conn.as_wire(), result); + return; + } + // This must be a bit of the multi-bit output wire. + auto bit = conn.as_bit(); + assert(bit.is_wire() && retBitValues.contains(bit.wire)); + auto offset = retBitValues[bit.wire].size() - bit.offset - 1; + retBitValues[bit.wire][offset] = result; +} + +func::FuncOp RTLILImporter::importModule( + Module *module, const SmallVector &cellOrdering) { + // Gather input and output wires of the module to match up with the block + // arguments. + SmallVector argTypes; + SmallVector wireArgs; + SmallVector retTypes; + SmallVector wireRet; + + OpBuilder builder(context); + // Maintain a map from RTLIL output wires to the Values that comprise it + // in order to reconstruct the multi-bit output. + llvm::MapVector> retBitValues; + for (auto *wire : module->wires()) { + // The RTLIL module may also have intermediate wires that are neither inputs + // nor outputs. + if (wire->port_input) { + argTypes.push_back(builder.getIntegerType(wire->width)); + wireArgs.push_back(wire); + } else if (wire->port_output) { + retTypes.push_back(builder.getIntegerType(wire->width)); + wireRet.push_back(wire); + retBitValues[wire].resize(wire->width); + } + } + + // Build function. + // TODO(https://github.com/google/heir/issues/111): Pass in data to fix + // function location. + FunctionType funcType = builder.getFunctionType(argTypes, retTypes); + auto function = func::FuncOp::create( + builder.getUnknownLoc(), module->name.str().replace(0, 1, ""), funcType); + function.setPrivate(); + + auto *block = function.addEntryBlock(); + auto b = ImplicitLocOpBuilder::atBlockBegin(function.getLoc(), block); + + // Map the RTLIL wires to the block arguments' Values. + for (auto i = 0; i < wireArgs.size(); i++) { + addWireValue(wireArgs[i], block->getArgument(i)); + } + + // Convert cells to Operations according to topological order. + for (const auto &cellName : cellOrdering) { + assert(module->cells_.count("\\" + cellName) != 0 && + "expected cell in RTLIL design"); + auto *cell = module->cells_["\\" + cellName]; + + SmallVector inputValues; + for (const auto &conn : getInputs(cell)) { + inputValues.push_back(getBit(conn, b)); + } + auto *op = createOp(cell, inputValues, b); + auto resultConn = getOutput(cell); + addResultBit(resultConn, op->getResult(0), retBitValues); + } + + // Wire up remaining connections. + for (const auto &conn : module->connections()) { + auto output = conn.first; + // These must be output wire connections (either an output bit or a bit of a + // multi-bit output wire). + assert(output.is_wire() || output.as_bit().is_wire()); + assert(retBitValues.contains(output.as_wire()) || + retBitValues.contains(output.as_bit().wire)); + if (conn.second.chunks().size() == 1) { + Value connValue = getBit(conn.second, b); + addResultBit(output, connValue, retBitValues); + } else { + auto chunks = conn.second.chunks(); + for (size_t i = 0; i < output.size(); i++) { + Value connValue = getBit(conn.second.bits().at(i), b); + addResultBit(output.bits().at(i), connValue, retBitValues); + } + } + } + + // Concatenate result bits if needed, and return result. + SmallVector returnValues; + for (const auto &[resultWire, retBits] : retBitValues) { + if (retBits.size() > 1) { + auto concatOp = b.create(retBits); + returnValues.push_back(concatOp.getResult()); + } else { + returnValues.push_back(getWireValue(resultWire)); + } + } + b.create(returnValues); + + return function; +} + +} // namespace heir +} // namespace mlir diff --git a/lib/Transforms/YosysOptimizer/RTLILImporter.h b/lib/Transforms/YosysOptimizer/RTLILImporter.h new file mode 100644 index 0000000000..f5e7d39fd2 --- /dev/null +++ b/lib/Transforms/YosysOptimizer/RTLILImporter.h @@ -0,0 +1,69 @@ +#ifndef HEIR_LIB_TRANSFORMS_YOSYSOPTIMIZER_RTLILIMPORTER_H_ +#define HEIR_LIB_TRANSFORMS_YOSYSOPTIMIZER_RTLILIMPORTER_H_ + +#include "kernel/rtlil.h" // from @at_clifford_yosys +#include "mlir/include/mlir/Dialect/Func/IR/FuncOps.h" // from @llvm-project +#include "mlir/include/mlir/IR/ImplicitLocOpBuilder.h" // from @llvm-project +#include "mlir/include/mlir/IR/Operation.h" // from @llvm-project + +namespace mlir { +namespace heir { + +// Returns a list of cell names that are topologically ordered using the Yosys +// toder output. This is extracted from the lines containing cells in the +// output: +// -- Running command `torder -stop * P*;' -- + +// 14. Executing TORDER pass (print cells in topological order). +// module test_add +// cell $abc$167$auto$blifparse.cc:525:parse_blif$168 +// cell $abc$167$auto$blifparse.cc:525:parse_blif$170 +// cell $abc$167$auto$blifparse.cc:525:parse_blif$169 +// cell $abc$167$auto$blifparse.cc:525:parse_blif$171 +llvm::SmallVector getTopologicalOrder( + std::stringstream &torderOutput); + +class RTLILImporter { + public: + RTLILImporter(MLIRContext *context) : context(context) {} + + // importModule imports an RTLIL module to an MLIR function using the provided + // config. cellOrdering is a topologically sorted list of cells that can be + // used to sequentially create the MLIR representation. + func::FuncOp importModule(Yosys::RTLIL::Module *module, + const SmallVector &cellOrdering); + + protected: + // cellToOp converts an RTLIL cell to an MLIR operation. + virtual Operation *createOp(Yosys::RTLIL::Cell *cell, + SmallVector &inputs, + ImplicitLocOpBuilder &b) const = 0; + + // Returns a list of RTLIL cell inputs. + virtual SmallVector getInputs( + Yosys::RTLIL::Cell *cell) const = 0; + + // Returns an RTLIL cell output. + virtual Yosys::RTLIL::SigSpec getOutput(Yosys::RTLIL::Cell *cell) const = 0; + + private: + MLIRContext *context; + + llvm::StringMap wireNameToValue; + Value getWireValue(Yosys::RTLIL::Wire *wire); + void addWireValue(Yosys::RTLIL::Wire *wire, Value value); + + // getBit gets the MLIR Value corresponding to the given connection. This + // assumes that the connection is a single bit. + Value getBit(const Yosys::RTLIL::SigSpec &conn, ImplicitLocOpBuilder &b); + + // addResultBit assigns an mlir result to the result connection. + void addResultBit( + const Yosys::RTLIL::SigSpec &conn, Value result, + llvm::MapVector> &retBitValues); +}; + +} // namespace heir +} // namespace mlir + +#endif // HEIR_LIB_TRANSFORMS_YOSYSOPTIMIZER_RTLILIMPORTER_H_ diff --git a/lib/Transforms/YosysOptimizer/YosysOptimizer.cpp b/lib/Transforms/YosysOptimizer/YosysOptimizer.cpp new file mode 100644 index 0000000000..c80d223c19 --- /dev/null +++ b/lib/Transforms/YosysOptimizer/YosysOptimizer.cpp @@ -0,0 +1,91 @@ +#include "include/Transforms/YosysOptimizer/YosysOptimizer.h" + +#include "include/Dialect/Comb/IR/CombOps.h" +#include "include/Target/Verilog/VerilogEmitter.h" +#include "kernel/yosys.h" // from @at_clifford_yosys +#include "lib/Transforms/YosysOptimizer/LUTImporter.h" +#include "llvm/include/llvm/Support/FormatVariadic.h" // from @llvm-project +#include "mlir/include/mlir/Dialect/Arith/IR/Arith.h" // from @llvm-project +#include "mlir/include/mlir/Dialect/Func/IR/FuncOps.h" // from @llvm-project +#include "mlir/include/mlir/IR/IRMapping.h" // from @llvm-project + +namespace mlir { +namespace heir { + +// $0: verilog filename +// $1: function name +// $2: yosys runfiles +constexpr std::string_view kYosysTemplate = R"( +read_verilog {0}; +hierarchy -check -top \{1}; +proc; memory; +techmap -map {2}/techlibs/techmap.v; opt; +abc -exe {2}/../edu_berkeley_abc/abc -lut 3; +opt_clean -purge; +rename -hide */c:*; rename -enumerate */c:*; +techmap -map {2}/../heir/lib/Transforms/YosysOptimizer/yosys/map_lut_to_lut3.v; opt_clean -purge; +hierarchy -generate * o:Y i:*; opt; opt_clean -purge; +clean; +)"; + +struct YosysOptimizer + : public PassWrapper> { + YosysOptimizer(std::string_view runfiles) : runfiles(runfiles) {} + + void runOnOperation() override; + + llvm::StringRef getArgument() const override { return "yosys-optimizer"; } + + void getDependentDialects(mlir::DialectRegistry ®istry) const override { + registry.insert(); + } + + private: + std::string_view runfiles; +}; + +// Globally optimize an MLIR module. +void YosysOptimizer::runOnOperation() { + getOperation().walk([&](func::FuncOp op) { + // Translate function to Verilog. Translation will fail if the func + // contains unsupported operations. + // FIXME: Directly convert MLIR to Yosys' AST instead of using Verilog. + char *filename = tmpnam(NULL); + std::error_code EC; + llvm::raw_fd_ostream of(filename, EC); + if (failed(translateToVerilog(op, of)) || EC) { + return WalkResult::interrupt(); + } + of.close(); + + // Invoke Yosys to translate to a combinational circuit and optimize. + Yosys::yosys_setup(); + Yosys::log_error_stderr = true; + Yosys::run_pass(llvm::formatv(kYosysTemplate.data(), filename, + op.getSymName(), runfiles)); + + // Translate to MLIR and insert into the func + std::stringstream cellOrder; + Yosys::log_streams.push_back(&cellOrder); + Yosys::run_pass("torder -stop * P*;"); + Yosys::log_streams.clear(); + auto topologicalOrder = getTopologicalOrder(cellOrder); + + // Insert the optimized MLIR. + LUTImporter lutImporter = LUTImporter(&getContext()); + Yosys::RTLIL::Design *design = Yosys::yosys_get_design(); + func::FuncOp func = + lutImporter.importModule(design->top_module(), topologicalOrder); + op.getBody().takeBody(func.getBody()); + + return WalkResult::advance(); + }); + Yosys::yosys_shutdown(); +} + +std::unique_ptr createYosysOptimizer(std::string_view runfiles) { + return std::make_unique(runfiles); +} + +} // namespace heir +} // namespace mlir diff --git a/lib/Transforms/YosysOptimizer/tests/add_one_lut3.rtlil b/lib/Transforms/YosysOptimizer/tests/add_one_lut3.rtlil new file mode 100644 index 0000000000..00af8b3524 --- /dev/null +++ b/lib/Transforms/YosysOptimizer/tests/add_one_lut3.rtlil @@ -0,0 +1,191 @@ +autoidx 1 + +attribute \cells_not_processed 1 +module \add_one + + wire width 8 input 1 \x + + wire width 8 output 2 \out + + wire \_02_ + + wire \_01_ + + wire \_00_ + + attribute \module_not_derived 1 + cell \lut3 \_13_ + connect \Y \out [0] + connect \P7 0 + connect \P6 0 + connect \P5 0 + connect \P4 0 + connect \P3 0 + connect \P2 0 + connect \P1 1'0 + connect \P0 1'1 + connect \C 0 + connect \B 0 + connect \A \x [0] + end + + attribute \module_not_derived 1 + cell \lut3 \_12_ + connect \Y \_02_ + connect \P7 1'1 + connect \P6 1'0 + connect \P5 1'0 + connect \P4 1'0 + connect \P3 1'0 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'0 + connect \C \x [6] + connect \B \x [5] + connect \A \_01_ + end + + attribute \module_not_derived 1 + cell \lut3 \_11_ + connect \Y \out [7] + connect \P7 0 + connect \P6 0 + connect \P5 0 + connect \P4 0 + connect \P3 1'0 + connect \P2 1'1 + connect \P1 1'1 + connect \P0 1'0 + connect \C 0 + connect \B \x [7] + connect \A \_02_ + end + + attribute \module_not_derived 1 + cell \lut3 \_10_ + connect \Y \out [6] + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'1 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'0 + connect \C \x [6] + connect \B \x [5] + connect \A \_01_ + end + + attribute \module_not_derived 1 + cell \lut3 \_09_ + connect \Y \_01_ + connect \P7 1'1 + connect \P6 1'0 + connect \P5 1'0 + connect \P4 1'0 + connect \P3 1'0 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'0 + connect \C \x [4] + connect \B \x [3] + connect \A \_00_ + end + + attribute \module_not_derived 1 + cell \lut3 \_08_ + connect \Y \out [5] + connect \P7 0 + connect \P6 0 + connect \P5 0 + connect \P4 0 + connect \P3 1'0 + connect \P2 1'1 + connect \P1 1'1 + connect \P0 1'0 + connect \C 0 + connect \B \x [5] + connect \A \_01_ + end + + attribute \module_not_derived 1 + cell \lut3 \_07_ + connect \Y \out [4] + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'1 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'0 + connect \C \x [4] + connect \B \x [3] + connect \A \_00_ + end + + attribute \module_not_derived 1 + cell \lut3 \_06_ + connect \Y \_00_ + connect \P7 1'1 + connect \P6 1'0 + connect \P5 1'0 + connect \P4 1'0 + connect \P3 1'0 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'0 + connect \C \x [2] + connect \B \x [1] + connect \A \x [0] + end + + attribute \module_not_derived 1 + cell \lut3 \_05_ + connect \Y \out [3] + connect \P7 0 + connect \P6 0 + connect \P5 0 + connect \P4 0 + connect \P3 1'0 + connect \P2 1'1 + connect \P1 1'1 + connect \P0 1'0 + connect \C 0 + connect \B \x [3] + connect \A \_00_ + end + + attribute \module_not_derived 1 + cell \lut3 \_04_ + connect \Y \out [2] + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'1 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'0 + connect \C \x [2] + connect \B \x [1] + connect \A \x [0] + end + + attribute \module_not_derived 1 + cell \lut3 \_03_ + connect \Y \out [1] + connect \P7 0 + connect \P6 0 + connect \P5 0 + connect \P4 0 + connect \P3 1'0 + connect \P2 1'1 + connect \P1 1'1 + connect \P0 1'0 + connect \C 0 + connect \B \x [1] + connect \A \x [0] + end +end diff --git a/lib/Transforms/YosysOptimizer/tests/add_one_lut5.rtlil b/lib/Transforms/YosysOptimizer/tests/add_one_lut5.rtlil new file mode 100644 index 0000000000..4ce250e7dc --- /dev/null +++ b/lib/Transforms/YosysOptimizer/tests/add_one_lut5.rtlil @@ -0,0 +1,390 @@ + +autoidx 1 + +attribute \cells_not_processed 1 +module \add_one + + wire width 8 input 1 \x + + wire width 8 output 2 \out + + wire \_0_ + + attribute \module_not_derived 1 + cell \lut5 \_9_ + connect \Y \out [0] + connect \P9 0 + connect \P8 0 + connect \P7 0 + connect \P6 0 + connect \P5 0 + connect \P4 0 + connect \P31 0 + connect \P30 0 + connect \P3 0 + connect \P29 0 + connect \P28 0 + connect \P27 0 + connect \P26 0 + connect \P25 0 + connect \P24 0 + connect \P23 0 + connect \P22 0 + connect \P21 0 + connect \P20 0 + connect \P2 0 + connect \P19 0 + connect \P18 0 + connect \P17 0 + connect \P16 0 + connect \P15 0 + connect \P14 0 + connect \P13 0 + connect \P12 0 + connect \P11 0 + connect \P10 0 + connect \P1 1'0 + connect \P0 1'1 + connect \E 0 + connect \D 0 + connect \C 0 + connect \B 0 + connect \A \x [0] + end + + attribute \module_not_derived 1 + cell \lut5 \_8_ + connect \Y \out [7] + connect \P9 1'1 + connect \P8 1'1 + connect \P7 1'1 + connect \P6 1'0 + connect \P5 1'0 + connect \P4 1'0 + connect \P31 0 + connect \P30 0 + connect \P3 1'0 + connect \P29 0 + connect \P28 0 + connect \P27 0 + connect \P26 0 + connect \P25 0 + connect \P24 0 + connect \P23 0 + connect \P22 0 + connect \P21 0 + connect \P20 0 + connect \P2 1'0 + connect \P19 0 + connect \P18 0 + connect \P17 0 + connect \P16 0 + connect \P15 1'0 + connect \P14 1'1 + connect \P13 1'1 + connect \P12 1'1 + connect \P11 1'1 + connect \P10 1'1 + connect \P1 1'0 + connect \P0 1'0 + connect \E 0 + connect \D \x [7] + connect \C \x [6] + connect \B \x [5] + connect \A \_0_ + end + + attribute \module_not_derived 1 + cell \lut5 \_7_ + connect \Y \out [6] + connect \P9 0 + connect \P8 0 + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'1 + connect \P31 0 + connect \P30 0 + connect \P3 1'1 + connect \P29 0 + connect \P28 0 + connect \P27 0 + connect \P26 0 + connect \P25 0 + connect \P24 0 + connect \P23 0 + connect \P22 0 + connect \P21 0 + connect \P20 0 + connect \P2 1'0 + connect \P19 0 + connect \P18 0 + connect \P17 0 + connect \P16 0 + connect \P15 0 + connect \P14 0 + connect \P13 0 + connect \P12 0 + connect \P11 0 + connect \P10 0 + connect \P1 1'0 + connect \P0 1'0 + connect \E 0 + connect \D 0 + connect \C \x [6] + connect \B \x [5] + connect \A \_0_ + end + + attribute \module_not_derived 1 + cell \lut5 \_6_ + connect \Y \_0_ + connect \P9 1'0 + connect \P8 1'0 + connect \P7 1'0 + connect \P6 1'0 + connect \P5 1'0 + connect \P4 1'0 + connect \P31 1'1 + connect \P30 1'0 + connect \P3 1'0 + connect \P29 1'0 + connect \P28 1'0 + connect \P27 1'0 + connect \P26 1'0 + connect \P25 1'0 + connect \P24 1'0 + connect \P23 1'0 + connect \P22 1'0 + connect \P21 1'0 + connect \P20 1'0 + connect \P2 1'0 + connect \P19 1'0 + connect \P18 1'0 + connect \P17 1'0 + connect \P16 1'0 + connect \P15 1'0 + connect \P14 1'0 + connect \P13 1'0 + connect \P12 1'0 + connect \P11 1'0 + connect \P10 1'0 + connect \P1 1'0 + connect \P0 1'0 + connect \E \x [4] + connect \D \x [2] + connect \C \x [3] + connect \B \x [1] + connect \A \x [0] + end + + attribute \module_not_derived 1 + cell \lut5 \_5_ + connect \Y \out [5] + connect \P9 0 + connect \P8 0 + connect \P7 0 + connect \P6 0 + connect \P5 0 + connect \P4 0 + connect \P31 0 + connect \P30 0 + connect \P3 1'0 + connect \P29 0 + connect \P28 0 + connect \P27 0 + connect \P26 0 + connect \P25 0 + connect \P24 0 + connect \P23 0 + connect \P22 0 + connect \P21 0 + connect \P20 0 + connect \P2 1'1 + connect \P19 0 + connect \P18 0 + connect \P17 0 + connect \P16 0 + connect \P15 0 + connect \P14 0 + connect \P13 0 + connect \P12 0 + connect \P11 0 + connect \P10 0 + connect \P1 1'1 + connect \P0 1'0 + connect \E 0 + connect \D 0 + connect \C 0 + connect \B \x [5] + connect \A \_0_ + end + + attribute \module_not_derived 1 + cell \lut5 \_4_ + connect \Y \out [4] + connect \P9 1'0 + connect \P8 1'0 + connect \P7 1'0 + connect \P6 1'0 + connect \P5 1'0 + connect \P4 1'0 + connect \P31 1'0 + connect \P30 1'1 + connect \P3 1'0 + connect \P29 1'1 + connect \P28 1'1 + connect \P27 1'1 + connect \P26 1'1 + connect \P25 1'1 + connect \P24 1'1 + connect \P23 1'1 + connect \P22 1'1 + connect \P21 1'1 + connect \P20 1'1 + connect \P2 1'0 + connect \P19 1'1 + connect \P18 1'1 + connect \P17 1'1 + connect \P16 1'1 + connect \P15 1'1 + connect \P14 1'0 + connect \P13 1'0 + connect \P12 1'0 + connect \P11 1'0 + connect \P10 1'0 + connect \P1 1'0 + connect \P0 1'0 + connect \E \x [4] + connect \D \x [2] + connect \C \x [3] + connect \B \x [1] + connect \A \x [0] + end + + attribute \module_not_derived 1 + cell \lut5 \_3_ + connect \Y \out [3] + connect \P9 1'1 + connect \P8 1'1 + connect \P7 1'1 + connect \P6 1'0 + connect \P5 1'0 + connect \P4 1'0 + connect \P31 0 + connect \P30 0 + connect \P3 1'0 + connect \P29 0 + connect \P28 0 + connect \P27 0 + connect \P26 0 + connect \P25 0 + connect \P24 0 + connect \P23 0 + connect \P22 0 + connect \P21 0 + connect \P20 0 + connect \P2 1'0 + connect \P19 0 + connect \P18 0 + connect \P17 0 + connect \P16 0 + connect \P15 1'0 + connect \P14 1'1 + connect \P13 1'1 + connect \P12 1'1 + connect \P11 1'1 + connect \P10 1'1 + connect \P1 1'0 + connect \P0 1'0 + connect \E 0 + connect \D \x [3] + connect \C \x [2] + connect \B \x [1] + connect \A \x [0] + end + + attribute \module_not_derived 1 + cell \lut5 \_2_ + connect \Y \out [2] + connect \P9 0 + connect \P8 0 + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'1 + connect \P31 0 + connect \P30 0 + connect \P3 1'1 + connect \P29 0 + connect \P28 0 + connect \P27 0 + connect \P26 0 + connect \P25 0 + connect \P24 0 + connect \P23 0 + connect \P22 0 + connect \P21 0 + connect \P20 0 + connect \P2 1'0 + connect \P19 0 + connect \P18 0 + connect \P17 0 + connect \P16 0 + connect \P15 0 + connect \P14 0 + connect \P13 0 + connect \P12 0 + connect \P11 0 + connect \P10 0 + connect \P1 1'0 + connect \P0 1'0 + connect \E 0 + connect \D 0 + connect \C \x [2] + connect \B \x [1] + connect \A \x [0] + end + + attribute \module_not_derived 1 + cell \lut5 \_1_ + connect \Y \out [1] + connect \P9 0 + connect \P8 0 + connect \P7 0 + connect \P6 0 + connect \P5 0 + connect \P4 0 + connect \P31 0 + connect \P30 0 + connect \P3 1'0 + connect \P29 0 + connect \P28 0 + connect \P27 0 + connect \P26 0 + connect \P25 0 + connect \P24 0 + connect \P23 0 + connect \P22 0 + connect \P21 0 + connect \P20 0 + connect \P2 1'1 + connect \P19 0 + connect \P18 0 + connect \P17 0 + connect \P16 0 + connect \P15 0 + connect \P14 0 + connect \P13 0 + connect \P12 0 + connect \P11 0 + connect \P10 0 + connect \P1 1'1 + connect \P0 1'0 + connect \E 0 + connect \D 0 + connect \C 0 + connect \B \x [1] + connect \A \x [0] + end +end diff --git a/lib/Transforms/YosysOptimizer/tests/double_input.rtlil b/lib/Transforms/YosysOptimizer/tests/double_input.rtlil new file mode 100644 index 0000000000..52fd74659c --- /dev/null +++ b/lib/Transforms/YosysOptimizer/tests/double_input.rtlil @@ -0,0 +1,8 @@ +module \double_in + + wire width 8 input 1 \x + + wire width 8 output 2 \out + + connect \out { \x [6:0] 1'0 } +end diff --git a/lib/Transforms/YosysOptimizer/tests/multiple_inputs.rtlil b/lib/Transforms/YosysOptimizer/tests/multiple_inputs.rtlil new file mode 100644 index 0000000000..9e439c1b44 --- /dev/null +++ b/lib/Transforms/YosysOptimizer/tests/multiple_inputs.rtlil @@ -0,0 +1,266 @@ + +autoidx 1 + +attribute \cells_not_processed 1 +module \add_two + + wire width 8 input 2 \y + + wire width 8 input 1 \x + + wire width 8 output 3 \out + + wire \_06_ + + wire \_05_ + + wire \_04_ + + wire \_03_ + + wire \_02_ + + wire \_01_ + + wire \_00_ + + attribute \module_not_derived 1 + cell \lut3 \_21_ + connect \Y \out [0] + connect \P7 0 + connect \P6 0 + connect \P5 0 + connect \P4 0 + connect \P3 1'0 + connect \P2 1'1 + connect \P1 1'1 + connect \P0 1'0 + connect \C 0 + connect \B \y [0] + connect \A \x [0] + end + + attribute \module_not_derived 1 + cell \lut3 \_20_ + connect \Y \_06_ + connect \P7 1'0 + connect \P6 1'0 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'1 + connect \P0 1'1 + connect \C \y [6] + connect \B \x [6] + connect \A \_05_ + end + + attribute \module_not_derived 1 + cell \lut3 \_19_ + connect \Y \out [7] + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'1 + connect \C \y [7] + connect \B \x [7] + connect \A \_06_ + end + + attribute \module_not_derived 1 + cell \lut3 \_18_ + connect \Y \_05_ + connect \P7 1'0 + connect \P6 1'0 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'1 + connect \P0 1'1 + connect \C \y [5] + connect \B \x [5] + connect \A \_04_ + end + + attribute \module_not_derived 1 + cell \lut3 \_17_ + connect \Y \out [6] + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'1 + connect \C \y [6] + connect \B \x [6] + connect \A \_05_ + end + + attribute \module_not_derived 1 + cell \lut3 \_16_ + connect \Y \_04_ + connect \P7 1'0 + connect \P6 1'0 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'1 + connect \P0 1'1 + connect \C \y [4] + connect \B \x [4] + connect \A \_03_ + end + + attribute \module_not_derived 1 + cell \lut3 \_15_ + connect \Y \out [5] + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'1 + connect \C \y [5] + connect \B \x [5] + connect \A \_04_ + end + + attribute \module_not_derived 1 + cell \lut3 \_14_ + connect \Y \_03_ + connect \P7 1'0 + connect \P6 1'0 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'1 + connect \P0 1'1 + connect \C \y [3] + connect \B \x [3] + connect \A \_02_ + end + + attribute \module_not_derived 1 + cell \lut3 \_13_ + connect \Y \out [4] + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'1 + connect \C \y [4] + connect \B \x [4] + connect \A \_03_ + end + + attribute \module_not_derived 1 + cell \lut3 \_12_ + connect \Y \_02_ + connect \P7 1'0 + connect \P6 1'0 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'1 + connect \P0 1'1 + connect \C \y [2] + connect \B \x [2] + connect \A \_01_ + end + + attribute \module_not_derived 1 + cell \lut3 \_11_ + connect \Y \out [3] + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'1 + connect \C \y [3] + connect \B \x [3] + connect \A \_02_ + end + + attribute \module_not_derived 1 + cell \lut3 \_10_ + connect \Y \_01_ + connect \P7 1'0 + connect \P6 1'0 + connect \P5 1'0 + connect \P4 1'1 + connect \P3 1'0 + connect \P2 1'1 + connect \P1 1'1 + connect \P0 1'1 + connect \C \y [1] + connect \B \x [1] + connect \A \_00_ + end + + attribute \module_not_derived 1 + cell \lut3 \_09_ + connect \Y \out [2] + connect \P7 1'0 + connect \P6 1'1 + connect \P5 1'1 + connect \P4 1'0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'1 + connect \C \y [2] + connect \B \x [2] + connect \A \_01_ + end + + attribute \module_not_derived 1 + cell \lut3 \_08_ + connect \Y \_00_ + connect \P7 0 + connect \P6 0 + connect \P5 0 + connect \P4 0 + connect \P3 1'1 + connect \P2 1'0 + connect \P1 1'0 + connect \P0 1'0 + connect \C 0 + connect \B \y [0] + connect \A \x [0] + end + + attribute \module_not_derived 1 + cell \lut3 \_07_ + connect \Y \out [1] + connect \P7 1'1 + connect \P6 1'0 + connect \P5 1'0 + connect \P4 1'1 + connect \P3 1'0 + connect \P2 1'1 + connect \P1 1'1 + connect \P0 1'0 + connect \C \y [1] + connect \B \x [1] + connect \A \_00_ + end +end diff --git a/lib/Transforms/YosysOptimizer/yosys/map_lut_to_lut3.v b/lib/Transforms/YosysOptimizer/yosys/map_lut_to_lut3.v new file mode 100644 index 0000000000..a4a108f698 --- /dev/null +++ b/lib/Transforms/YosysOptimizer/yosys/map_lut_to_lut3.v @@ -0,0 +1,82 @@ +// tech mapping module that converts auto-generated \$lut cells to valid +// liberty cells +// +// Unlike map_lut_to_lutmux.ys, this techmapping ensures all output cells have +// the same bit-width, which is required for parallelism in some FHE backends, +// i.e., the truth table sizes and input dimensions must all be the same. +// Since bootstrap has the same latency regardless of the LUT size, it's not +// problem to pad with zeros. + +(* techmap_celltype = "\$lut" *) +module _tjc_lut3 (A, Y); + parameter LUT = 8'h0; + parameter WIDTH = 3; + input [WIDTH-1:0] A; + output Y; + + wire _TECHMAP_FAIL_ = (WIDTH != 3); + + wire [2**WIDTH-1:0] PBITS = LUT; + + (* LUT=LUT *) + lut3 _TECHMAP_REPLACE_(.C(A[2]), .B(A[1]), .A(A[0]), + .P7(PBITS[7]), + .P6(PBITS[6]), + .P5(PBITS[5]), + .P4(PBITS[4]), + .P3(PBITS[3]), + .P2(PBITS[2]), + .P1(PBITS[1]), + .P0(PBITS[0]), + .Y(Y)); +endmodule + + +(* techmap_celltype = "\$lut" *) +module _tjc_lut2 (A, Y); + parameter LUT = 4'h0; + parameter WIDTH = 2; + input [WIDTH-1:0] A; + output Y; + + wire _TECHMAP_FAIL_ = (WIDTH != 2); + + wire [2**WIDTH-1:0] PBITS = LUT; + + (* LUT=LUT *) + lut3 _TECHMAP_REPLACE_(.C(0), .B(A[1]), .A(A[0]), + .P7(0), + .P6(0), + .P5(0), + .P4(0), + .P3(PBITS[3]), + .P2(PBITS[2]), + .P1(PBITS[1]), + .P0(PBITS[0]), + .Y(Y)); +endmodule + + +(* techmap_celltype = "\$lut" *) +module _tjc_lut1 (A, Y); + parameter LUT = 2'h0; + parameter WIDTH = 1; + input [WIDTH-1:0] A; + output Y; + + wire _TECHMAP_FAIL_ = (WIDTH != 1); + + wire [2**WIDTH-1:0] PBITS = LUT; + + (* LUT=LUT *) + lut3 _TECHMAP_REPLACE_(.C(0), .B(0), .A(A[0]), + .P7(0), + .P6(0), + .P5(0), + .P4(0), + .P3(0), + .P2(0), + .P1(PBITS[1]), + .P0(PBITS[0]), + .Y(Y)); +endmodule diff --git a/tests/yosys_optimizer/BUILD b/tests/yosys_optimizer/BUILD new file mode 100644 index 0000000000..6c9032391a --- /dev/null +++ b/tests/yosys_optimizer/BUILD @@ -0,0 +1,13 @@ +load("//bazel:lit.bzl", "glob_lit_tests") + +package( + default_applicable_licenses = ["@heir//:license"], + default_visibility = ["//visibility:public"], +) + +glob_lit_tests( + name = "all_tests", + data = ["@heir//tests:test_utilities"], + driver = "@heir//tests:run_lit.sh", + test_file_exts = ["mlir"], +) diff --git a/tests/yosys_optimizer/add_one.mlir b/tests/yosys_optimizer/add_one.mlir new file mode 100644 index 0000000000..731e8ab144 --- /dev/null +++ b/tests/yosys_optimizer/add_one.mlir @@ -0,0 +1,14 @@ +// RUN: heir-opt --yosys-optimizer %s | FileCheck %s + +// CHECK: module +module { + func.func @add_one(%in: i8) -> (i8) { + // CHECK: comb.truth_table + %0 = arith.constant 1 : i8 + // CHECK-NOT arith.addi + %1 = arith.addi %in, %0 : i8 + // CHECK: comb.concat + // CHECK-NEXT: return + return %1 : i8 + } +} diff --git a/tools/BUILD b/tools/BUILD index 14ffc61583..0fc25e2cd9 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -11,6 +11,7 @@ cc_binary( srcs = ["heir-opt.cpp"], includes = ["include"], deps = [ + "@bazel_tools//tools/cpp/runfiles", "@heir//lib/Conversion/BGVToPoly", "@heir//lib/Conversion/MemrefToArith:ExpandCopy", "@heir//lib/Conversion/MemrefToArith:MemrefToArithRegistration", @@ -23,6 +24,7 @@ cc_binary( "@heir//lib/Dialect/PolyExt/IR:Dialect", "@heir//lib/Dialect/Secret/IR:Dialect", "@heir//lib/Dialect/Secret/Transforms", + "@heir//lib/Transforms/YosysOptimizer", "@llvm-project//mlir:AffineDialect", "@llvm-project//mlir:AffineTransforms", "@llvm-project//mlir:AllPassesAndDialects", diff --git a/tools/heir-opt.cpp b/tools/heir-opt.cpp index b90e8208ef..4f01896ece 100644 --- a/tools/heir-opt.cpp +++ b/tools/heir-opt.cpp @@ -9,6 +9,7 @@ #include "include/Dialect/PolyExt/IR/PolyExtDialect.h" #include "include/Dialect/Secret/IR/SecretDialect.h" #include "include/Dialect/Secret/Transforms/Passes.h" +#include "include/Transforms/YosysOptimizer/YosysOptimizer.h" #include "mlir/include/mlir/Conversion/TosaToLinalg/TosaToLinalg.h" // from @llvm-project #include "mlir/include/mlir/Dialect/Affine/IR/AffineOps.h" // from @llvm-project #include "mlir/include/mlir/Dialect/Affine/Passes.h" // from @llvm-project @@ -27,6 +28,9 @@ #include "mlir/include/mlir/Pass/PassRegistry.h" // from @llvm-project #include "mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h" // from @llvm-project #include "mlir/include/mlir/Transforms/Passes.h" // from @llvm-project +#include "tools/cpp/runfiles/runfiles.h" + +using bazel::tools::cpp::runfiles::Runfiles; void tosaPipelineBuilder(mlir::OpPassManager &manager) { // TOSA to linalg @@ -76,6 +80,12 @@ void tosaPipelineBuilder(mlir::OpPassManager &manager) { } int main(int argc, char **argv) { + std::string error; + std::unique_ptr runfiles(Runfiles::Create(argv[0], &error)); + if (runfiles == nullptr) { + return EXIT_FAILURE; + } + mlir::DialectRegistry registry; registry.insert(); registry.insert(); @@ -103,6 +113,13 @@ int main(int argc, char **argv) { mlir::heir::poly::registerPolyToStandardPasses(); mlir::heir::bgv::registerBGVToPolyPasses(); + std::string yosysRunfileDir = runfiles->Rlocation("at_clifford_yosys"); + mlir::PassPipelineRegistration<>( + "yosys-optimizer", "Run passes to optimize yosys modules", + [yosysRunfileDir](mlir::OpPassManager &pm) { + pm.addPass(mlir::heir::createYosysOptimizer(yosysRunfileDir)); + }); + mlir::PassPipelineRegistration<>( "heir-tosa-to-arith", "Run passes to lower TOSA models with stripped quant types to arithmetic",