From 356d3e5224f6e94f0fb5cd5fc81ef5d63a2f0382 Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Fri, 27 Sep 2024 22:29:18 +0900 Subject: [PATCH] Add Yosys integration This adds Yosys integration --- CMakeLists.txt | 33 + include/circt/Conversion/CMakeLists.txt | 5 + include/circt/Conversion/ExportRTLIL.h | 41 + include/circt/Conversion/Passes.h | 1 + include/circt/Conversion/Passes.td | 44 + include/circt/Dialect/Seq/SeqVisitor.h | 102 ++ .../YosysIntegration/round-trip.mlir | 84 ++ integration_test/YosysIntegration/synth.mlir | 60 ++ integration_test/lit.cfg.py | 4 + integration_test/lit.site.cfg.py.in | 1 + lib/Conversion/CMakeLists.txt | 5 + lib/Conversion/ExportYosys/CMakeLists.txt | 36 + lib/Conversion/ExportYosys/ExportRTLIL.cpp | 944 +++++++++++++++++ lib/Conversion/ExportYosys/ImportRTLIL.cpp | 960 ++++++++++++++++++ lib/Conversion/ExportYosys/README.md | 53 + .../ExportYosys/RTLILConverterInternal.h | 43 + lib/Conversion/ExportYosys/YosysOptimizer.cpp | 250 +++++ lib/Dialect/HW/HWInstanceImplementation.cpp | 3 + test/lit.site.cfg.py.in | 1 + tools/circt-opt/CMakeLists.txt | 1 + tools/circt-translate/CMakeLists.txt | 4 + tools/circt-translate/circt-translate.cpp | 8 + tools/firtool/CMakeLists.txt | 1 + 23 files changed, 2684 insertions(+) create mode 100644 include/circt/Conversion/ExportRTLIL.h create mode 100644 include/circt/Dialect/Seq/SeqVisitor.h create mode 100644 integration_test/YosysIntegration/round-trip.mlir create mode 100644 integration_test/YosysIntegration/synth.mlir create mode 100644 lib/Conversion/ExportYosys/CMakeLists.txt create mode 100644 lib/Conversion/ExportYosys/ExportRTLIL.cpp create mode 100644 lib/Conversion/ExportYosys/ImportRTLIL.cpp create mode 100644 lib/Conversion/ExportYosys/README.md create mode 100644 lib/Conversion/ExportYosys/RTLILConverterInternal.h create mode 100644 lib/Conversion/ExportYosys/YosysOptimizer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 059cb2337faa..eae7ff673dae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -614,6 +614,39 @@ else() set(ARCILATOR_JIT_ENABLED 0) endif() +option(CIRCT_YOSYS_INTEGRATION_ENABLED "Enables the Yosys integration." OFF) +llvm_canonicalize_cmake_booleans(CIRCT_YOSYS_INTEGRATION_ENABLED) + +if(CIRCT_YOSYS_INTEGRATION_ENABLED) + # TODO: Switch to use the installed yosys + # FIXME: Yosys doesn't use cmake so need to manually configure + message(STATUS "Yosys integration is enabled") + include(ExternalProject) + ExternalProject_Add(yosys + URL https://github.com/YosysHQ/yosys/archive/refs/tags/yosys-0.40.zip + URL_HASH SHA256=3f20dcaeee9be882a818885d857ff476e8cc491657447f2c3629da82723ccd1f + SOURCE_DIR "${CMAKE_BINARY_DIR}/third-party/yosys" + BINARY_DIR "${CMAKE_BINARY_DIR}/third-party/yosys" + INSTALL_DIR "${CMAKE_BINARY_DIR}/third-party/yosys" + # CONFIGURE_COMMAND sed -i "s/ENABLE_LIBYOSYS := 0/ENABLE_LIBYOSYS := 1/" Makefile && make config-gcc # TODO: Switch `make config-clang` or `make config-gcc` based on CXX_COMPILER + CONFIGURE_COMMAND make config-gcc && echo "ENABLE_LIBYOSYS := 1" >> Makefile.conf && echo "PREFIX := ${CMAKE_BINARY_DIR}" >> Makefile.conf + BUILD_COMMAND make # TODO: Make the number of workers configurable. + INSTALL_COMMAND make install + TEST_COMMAND "" + BUILD_BYPRODUCTS "${CMAKE_BINARY_DIR}/third-party/yosys/libyosys.so" + ) + ExternalProject_Get_Property(yosys INSTALL_DIR) + include_directories(${INSTALL_DIR}/share/include) + add_library (libyosys SHARED IMPORTED) + add_dependencies(libyosys yosys) + set_target_properties(libyosys PROPERTIES + IMPORTED_LOCATION "${INSTALL_DIR}/libyosys.so" + INSTALL_RPATH "${INSTALL_RPATH}" + INTERFACE_INCLUDE_DIRECTORIES "${INSTALL_DIR}/share/include/" + ) + set(CMAKE_BUILD_RPATH "${INSTALL_DIR}") +endif() + #------------------------------------------------------------------------------- # Directory setup #------------------------------------------------------------------------------- diff --git a/include/circt/Conversion/CMakeLists.txt b/include/circt/Conversion/CMakeLists.txt index bc227db34cb6..cc10570c4180 100644 --- a/include/circt/Conversion/CMakeLists.txt +++ b/include/circt/Conversion/CMakeLists.txt @@ -1,5 +1,10 @@ set(LLVM_TARGET_DEFINITIONS Passes.td) +if(CIRCT_YOSYS_INTEGRATION_ENABLED) +mlir_tablegen(Passes.h.inc -gen-pass-decls -name Conversion -DCIRCT_YOSYS_INTEGRATION_ENABLED) +else() mlir_tablegen(Passes.h.inc -gen-pass-decls -name Conversion) +endif() + mlir_tablegen(Conversion.capi.h.inc -gen-pass-capi-header --prefix Conversion) mlir_tablegen(Conversion.capi.cpp.inc -gen-pass-capi-impl --prefix Conversion) add_public_tablegen_target(CIRCTConversionPassIncGen) diff --git a/include/circt/Conversion/ExportRTLIL.h b/include/circt/Conversion/ExportRTLIL.h new file mode 100644 index 000000000000..88668405649e --- /dev/null +++ b/include/circt/Conversion/ExportRTLIL.h @@ -0,0 +1,41 @@ +//===- ExportRTLIL.h - Export core dialects to Yosys RTLIL --===-*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares passes which lower core dialects to RTLIL in-memory IR. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_EXPORTRTLIL_H +#define CIRCT_CONVERSION_EXPORTRTLIL_H + +#include "circt/Support/LLVM.h" +#include +#include +#include "llvm/ADT/ArrayRef.h" + +namespace mlir { +class Pass; +} // namespace mlir + +namespace circt { + +#define GEN_PASS_DECL_EXPORTYOSYS +#define GEN_PASS_DECL_EXPORTYOSYSPARALLEL +#include "circt/Conversion/Passes.h.inc" + +std::unique_ptr createYosysOptimizer(); +std::unique_ptr createYosysOptimizerParallel(); + +/// Register the `(import|export)-rtlil` MLIR translation. +void registerRTLILImport(); +void registerRTLILExport(); +void registerRTLILTranslation(); + +} // namespace circt + +#endif // CIRCT_CONVERSION_EXPORTRTLIL_H diff --git a/include/circt/Conversion/Passes.h b/include/circt/Conversion/Passes.h index 9d6b2d2aacc2..b3d2835336e6 100644 --- a/include/circt/Conversion/Passes.h +++ b/include/circt/Conversion/Passes.h @@ -25,6 +25,7 @@ #include "circt/Conversion/DCToHW.h" #include "circt/Conversion/ExportChiselInterface.h" #include "circt/Conversion/ExportVerilog.h" +#include "circt/Conversion/ExportRTLIL.h" #include "circt/Conversion/FIRRTLToHW.h" #include "circt/Conversion/FSMToSV.h" #include "circt/Conversion/HWArithToHW.h" diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 791c998bfd32..89b1e110ba5f 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -790,4 +790,48 @@ def LowerSimToSV: Pass<"lower-sim-to-sv", "mlir::ModuleOp"> { ]; } +//===----------------------------------------------------------------------===// +// Yosys Integration +//===----------------------------------------------------------------------===// + +#ifdef CIRCT_YOSYS_INTEGRATION_ENABLED +def YosysOptimizer: Pass<"yosys-optimizer", "mlir::ModuleOp"> { + let summary = "Translate core dialects into yosys's RTL IR"; + let constructor = "createYosysOptimizer()"; + let options = [ + ListOption<"passes", "passes", "std::string", + "Specify Yosys passes to run">, + Option<"topModule", "top-module", "std::string", + "", "top module name">, + Option<"redirectLog", "redirect-log", "bool", + "false", ""> + ]; + let dependentDialects = [ + "circt::comb::CombDialect", + "circt::seq::SeqDialect", + "circt::sv::SVDialect", + "circt::hw::HWDialect" + ]; +} + +def YosysOptimizerParallel: Pass<"yosys-optimizer-parallel", "mlir::ModuleOp"> { + let summary = "Translate core dialects into yosys's RTL IR"; + let constructor = "createYosysOptimizerParallel()"; + let options = [ + ListOption<"passes", "passes", "std::string", + "Specify Yosys passes to run">, + Option<"topModule", "top-module", "std::string", + "", "top module name">, + Option<"workspace", "workspace", "std::string", + "", "workspace to run yosys"> + ]; + let dependentDialects = [ + "circt::comb::CombDialect", + "circt::seq::SeqDialect", + "circt::sv::SVDialect", + "circt::hw::HWDialect" + ]; +} +#endif + #endif // CIRCT_CONVERSION_PASSES_TD diff --git a/include/circt/Dialect/Seq/SeqVisitor.h b/include/circt/Dialect/Seq/SeqVisitor.h new file mode 100644 index 000000000000..4bb84d37468c --- /dev/null +++ b/include/circt/Dialect/Seq/SeqVisitor.h @@ -0,0 +1,102 @@ +//===- SeqVisitors.h - Seq Dialect Visitors ---------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines visitors for Seq dialect operations. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SEQ_SEQVISITORS_H +#define CIRCT_DIALECT_SEQ_SEQVISITORS_H + +#include "circt/Dialect/Seq/SeqOps.h" +#include "llvm/ADT/TypeSwitch.h" + +namespace circt { +namespace seq { + +/// This helps visit TypeOp nodes. +template +class SeqOpVisitor { +public: + ResultType dispatchSeqOpVisitor(Operation *op, ExtraArgs... args) { + auto *thisCast = static_cast(this); + return TypeSwitch(op) + .template Case< + // Registers. + CompRegOp, CompRegClockEnabledOp, ShiftRegOp, FirRegOp, FIFOOp, + // Memories. + HLMemOp, ReadPortOp, WritePortOp, FirMemOp, FirMemReadOp, + FirMemWriteOp, FirMemReadWriteOp, + // Clock. + ClockGateOp, ClockMuxOp, ClockDividerOp, ClockInverterOp, + ConstClockOp, ToClockOp, FromClockOp>([&](auto expr) -> ResultType { + return thisCast->visitSeq(expr, args...); + }) + .Default([&](auto expr) -> ResultType { + return thisCast->visitInvalidSeqOp(op, args...); + }); + } + + /// This callback is invoked on any non-expression operations. + ResultType visitInvalidSeqOp(Operation *op, ExtraArgs... args) { + op->emitOpError("unknown seq op"); + abort(); + } + + /// This callback is invoked on any combinational operations that are not + /// handled by the concrete visitor. + ResultType visitUnhandledSeqOp(Operation *op, ExtraArgs... args) { + return ResultType(); + } + +#define HANDLE(OPTYPE, OPKIND) \ + ResultType visitSeq(OPTYPE op, ExtraArgs... args) { \ + return static_cast(this)->visit##OPKIND##SeqOp(op, \ + args...); \ + } + + // Registers. + HANDLE(CompRegOp, Unhandled); + HANDLE(CompRegClockEnabledOp, Unhandled); + HANDLE(ShiftRegOp, Unhandled); + + HANDLE(FirRegOp, Unhandled); + HANDLE(FIFOOp, Unhandled); + + // Memories. + HANDLE(HLMemOp, Unhandled); + HANDLE(ReadPortOp, Unhandled); + HANDLE(WritePortOp, Unhandled); + + // FIRRTL memory ops. + HANDLE(FirMemOp, Unhandled); + HANDLE(FirMemReadOp, Unhandled); + HANDLE(FirMemWriteOp, Unhandled); + HANDLE(FirMemReadWriteOp, Unhandled); + + // Clock gate. + HANDLE(ClockGateOp, Unhandled); + HANDLE(ClockMuxOp, Unhandled); + HANDLE(ClockDividerOp, Unhandled); + HANDLE(ClockInverterOp, Unhandled); + + // Tied-off clock + HANDLE(ConstClockOp, Unhandled); + + // Clock casts. + HANDLE(ToClockOp, Unhandled); + HANDLE(FromClockOp, Unhandled); + +#undef HANDLE +}; + +} // namespace seq +} // namespace circt + +#endif // CIRCT_DIALECT_SEQ_SEQVISITORS_H diff --git a/integration_test/YosysIntegration/round-trip.mlir b/integration_test/YosysIntegration/round-trip.mlir new file mode 100644 index 000000000000..45026b2347aa --- /dev/null +++ b/integration_test/YosysIntegration/round-trip.mlir @@ -0,0 +1,84 @@ +// REQUIRES: yosys-integration + +// RUN: circt-translate --export-rtlil %s | circt-translate --import-rtlil | circt-opt -canonicalize | FileCheck %s + +// CHECK-LABEL: hw.module @Arith(in %in1 : i2, in %in2 : i2, in %in3 : i2, out add : i2, out sub : i2, out mul : i2, out and : i2, out or : i2, out xor : i2) +hw.module @Arith(in %in1 : i2, in %in2 : i2, in %in3: i2, out add : i2, out sub: i2, out mul: i2, out and: i2, out or: i2, out xor: i2 ) { + %0 = comb.add %in1, %in2, %in3: i2 + %1 = comb.sub %in1, %in2: i2 + %2 = comb.mul %in1, %in2, %in3: i2 + %3 = comb.and %in1, %in2, %in3: i2 + %4 = comb.or %in1, %in2, %in3: i2 + %5 = comb.xor %in1, %in2, %in3: i2 + // CHECK-NEXT: %0 = comb.add %in1, %in2, %in3 : i2 + // CHECK-NEXT: %1 = comb.sub %in1, %in2 : i2 + // CHECK-NEXT: %2 = comb.mul %in1, %in2, %in3 : i2 + // CHECK-NEXT: %3 = comb.and %in1, %in2, %in3 : i2 + // CHECK-NEXT: %4 = comb.or %in1, %in2, %in3 : i2 + // CHECK-NEXT: %5 = comb.xor %in1, %in2, %in3 : i2 + // CHECK-NEXT: hw.output %0, %1, %2, %3, %4, %5 : i2, i2, i2, i2, i2, i2 + hw.output %0, %1, %2, %3, %4, %5 : i2, i2, i2, i2, i2, i2 +} + +// CHECK-LABEL: hw.module @ICmp(in %a : i2, in %b : i2, out eq : i1, out ne : i1, out slt : i1, out sle : i1, out sgt : i1, out sge : i1, out ult : i1, out ule : i1, out ugt : i1, out uge : i1) +hw.module @ICmp(in %a : i2, in %b : i2, out eq : i1, + out ne: i1, out slt: i1, out sle: i1, out sgt: i1, out sge: i1, + out ult: i1, out ule: i1, out ugt: i1, out uge: i1 + ) { + %eq = comb.icmp eq %a, %b : i2 + %ne = comb.icmp ne %a, %b : i2 + %slt = comb.icmp slt %a, %b : i2 + %sle = comb.icmp sle %a, %b : i2 + %sgt = comb.icmp sgt %a, %b : i2 + %sge = comb.icmp sge %a, %b : i2 + %ult = comb.icmp ult %a, %b : i2 + %ule = comb.icmp ule %a, %b : i2 + %ugt = comb.icmp ugt %a, %b : i2 + %uge = comb.icmp uge %a, %b : i2 + // CHECK-NEXT: %0 = comb.icmp eq %a, %b : i2 + // CHECK-NEXT: %1 = comb.icmp ne %a, %b : i2 + // CHECK-NEXT: %2 = comb.icmp slt %a, %b : i2 + // CHECK-NEXT: %3 = comb.icmp sle %a, %b : i2 + // CHECK-NEXT: %4 = comb.icmp sgt %a, %b : i2 + // CHECK-NEXT: %5 = comb.icmp sge %a, %b : i2 + // CHECK-NEXT: %6 = comb.icmp ult %a, %b : i2 + // CHECK-NEXT: %7 = comb.icmp ule %a, %b : i2 + // CHECK-NEXT: %8 = comb.icmp ugt %a, %b : i2 + // CHECK-NEXT: %9 = comb.icmp uge %a, %b : i2 + // CHECK-NEXT: hw.output %0, %1, %2, %3, %4, %5, %6, %7, %8, %9 : i1, i1, i1, i1, i1, i1, i1, i1, i1, i1 + hw.output %eq, %ne, %slt, %sle, %sgt, %sge, %ult, %ule, %ugt, %uge : i1, i1, i1, i1, i1, i1, i1, i1, i1, i1 +} + + +// CHECK-LABEL: hw.module @counter(in %clk : i1, out o : i8) +hw.module @counter(in %clk: i1, out o: i8) { + // CHECK-NEXT: %c1_i8 = hw.constant 1 : i8 + // CHECK-NEXT: %0 = seq.to_clock %clk + // CHECK-NEXT: %reg = seq.compreg %1, %0 : i8 + // CHECK-NEXT: %1 = comb.add %reg, %c1_i8 : i8 + // CHECK-NEXT: hw.output %reg : i8 + %seq_clk = seq.to_clock %clk + %reg = seq.compreg %added, %seq_clk : i8 + %one = hw.constant 1 : i8 + %added = comb.add %reg, %one : i8 + hw.output %reg : i8 +} + +// CHECK-LABEL: hw.module @misc(in %cond : i1, in %in1 : i2, in %in2 : i2, in %in3 : i5, out mux : i2, out extract : i2, out concat : i9, out replicate : i6, out shl : i5, out parity : i1) +hw.module @misc(in %cond:i1, in %in1 : i2, in %in2 : i2, in %in3: i5, + out mux : i2, out extract: i2, out concat: i9, out replicate: i6, out shl: i5, out parity: i1 ) { + // CHECK-NEXT: %0 = comb.extract %in3 from 3 : (i5) -> i2 + // CHECK-NEXT: %1 = comb.concat %in1, %in2, %in3 : i2, i2, i5 + // CHECK-NEXT: %2 = comb.replicate %in1 : (i2) -> i6 + // CHECK-NEXT: %3 = comb.mux %cond, %in1, %in2 : i2 + // CHECK-NEXT: %4 = comb.shl %in3, %in3 : i5 + // CHECK-NEXT: %5 = comb.parity %in3 : i5 + // CHECK-NEXT: hw.output %3, %0, %1, %2, %4, %5 : i2, i2, i9, i6, i5, i1 + %mux = comb.mux %cond, %in1, %in2 : i2 + %extract = comb.extract %in3 from 3 : (i5) -> i2 + %concat = comb.concat %in1, %in2, %in3 : i2, i2, i5 + %replicate = comb.replicate %in1 : (i2) -> i6 + %shl = comb.shl %in3, %in3 : i5 + %partiy = comb.parity %in3 : i5 + hw.output %mux, %extract, %concat, %replicate, %shl, %partiy: i2, i2, i9, i6, i5, i1 +} diff --git a/integration_test/YosysIntegration/synth.mlir b/integration_test/YosysIntegration/synth.mlir new file mode 100644 index 000000000000..322fc04283a3 --- /dev/null +++ b/integration_test/YosysIntegration/synth.mlir @@ -0,0 +1,60 @@ +// REQUIRES: libz3 +// REQUIRES: circt-lec-jit +// REQUIRES: yosys-integration + +// Run synthesis and check the LEC. +// RUN: circt-opt --pass-pipeline='builtin.module(yosys-optimizer{passes=synth},canonicalize)' -o %t.mlir %s + +// RUN: circt-lec %s %t.mlir -c1=Arith -c2=Arith --shared-libs=%libz3 | FileCheck %s --check-prefix=COMB +// COMB: c1 == c2 + +hw.module @Arith(in %in1 : i2, in %in2 : i2, out add : i2, out sub: i2, out mul: i2, out and: i2, out or: i2, out xor: i2 ) { + %0 = comb.add %in1, %in2: i2 + %1 = comb.sub %in1, %in2: i2 + %2 = comb.mul %in1, %in2: i2 + %3 = comb.and %in1, %in2: i2 + %4 = comb.or %in1, %in2: i2 + %5 = comb.xor %in1, %in2: i2 + hw.output %0, %1, %2, %3, %4, %5 : i2, i2, i2, i2, i2, i2 +} + +// RUN: circt-lec %s %t.mlir -c1=ICmp -c2=ICmp --shared-libs=%libz3 | FileCheck %s --check-prefix=ICMP +// ICMP: c1 == c2 +hw.module @ICmp(in %a : i2, in %b : i2, out eq : i1, + out ne: i1, out slt: i1, out sle: i1, out sgt: i1, out sge: i1, + out ult: i1, out ule: i1, out ugt: i1, out uge: i1 + ) { + %eq = comb.icmp eq %a, %b : i2 + %ne = comb.icmp ne %a, %b : i2 + %slt = comb.icmp slt %a, %b : i2 + %sle = comb.icmp sle %a, %b : i2 + %sgt = comb.icmp sgt %a, %b : i2 + %sge = comb.icmp sge %a, %b : i2 + %ult = comb.icmp ult %a, %b : i2 + %ule = comb.icmp ule %a, %b : i2 + %ugt = comb.icmp ugt %a, %b : i2 + %uge = comb.icmp uge %a, %b : i2 + hw.output %eq, %ne, %slt, %sle, %sgt, %sge, %ult, %ule, %ugt, %uge : i1, i1, i1, i1, i1, i1, i1, i1, i1, i1 +} + +// RUN: circt-lec %s %t.mlir -c1=misc -c2=misc --shared-libs=%libz3 | FileCheck %s --check-prefix=MISC +// MISC: c1 == c2 +hw.module @misc(in %cond:i1, in %in1 : i2, in %in2 : i2, in %in3: i5, + out mux : i2, out extract: i2, out concat: i9, out replicate: i6, out shl: i5, out parity: i1 ) { + %mux = comb.mux %cond, %in1, %in2 : i2 + %extract = comb.extract %in3 from 3 : (i5) -> i2 + %concat = comb.concat %in1, %in2, %in3 : i2, i2, i5 + %replicate = comb.replicate %in1 : (i2) -> i6 + %shl = comb.shl %in3, %in3 : i5 + %partiy = comb.parity %in3 : i5 + hw.output %mux, %extract, %concat, %replicate, %shl, %partiy: i2, i2, i9, i6, i5, i1 +} + +// These are incorrectly lowered now(LEC failure). +// * comb.shrs +// * hw.array_create + hw.array_get +// hw.module @MultibitMux(in %a_0 : i1, in %a_1 : i1, in %a_2 : i1, in %sel : i2, out b : i1) { +// %0 = hw.array_create %a_0, %a_2, %a_1, %a_0 : i1 +// %1 = hw.array_get %0[%sel] : !hw.array<4xi1>, i2 +// hw.output %1 : i1 +// } diff --git a/integration_test/lit.cfg.py b/integration_test/lit.cfg.py index a7a18ded8152..f58a95c834c3 100644 --- a/integration_test/lit.cfg.py +++ b/integration_test/lit.cfg.py @@ -225,6 +225,10 @@ config.available_features.add('slang') tools.append('circt-verilog') +# Add yosys-integration if the yosys_integration_enabled is enabled. +if config.yosys_integration_enabled: + config.available_features.add('yosys-integration') + # Add arcilator JIT if MLIR's execution engine is enabled. if config.arcilator_jit_enabled: config.available_features.add('arcilator-jit') diff --git a/integration_test/lit.site.cfg.py.in b/integration_test/lit.site.cfg.py.in index 4d63e8ff3b28..539e21235a88 100644 --- a/integration_test/lit.site.cfg.py.in +++ b/integration_test/lit.site.cfg.py.in @@ -42,6 +42,7 @@ config.verilator_path = "@VERILATOR_PATH@" config.esi_cosim = "@ESI_COSIM@" config.timeout = "@CIRCT_INTEGRATION_TIMEOUT@" config.yosys_path = "@YOSYS_PATH@" +config.yosys_integration_enabled = @CIRCT_YOSYS_INTEGRATION_ENABLED@ config.quartus_path = "@QUARTUS_PATH@" config.vivado_path = "@VIVADO_PATH@" config.questa_path = "@QUESTA_PATH@" diff --git a/lib/Conversion/CMakeLists.txt b/lib/Conversion/CMakeLists.txt index 04bc5bfa9d71..03f7da84baaa 100644 --- a/lib/Conversion/CMakeLists.txt +++ b/lib/Conversion/CMakeLists.txt @@ -35,3 +35,8 @@ add_subdirectory(LTLToCore) if(CIRCT_SLANG_FRONTEND_ENABLED) add_subdirectory(ImportVerilog) endif() + +if(CIRCT_YOSYS_INTEGRATION_ENABLED) + add_subdirectory(ExportYosys) +endif() + diff --git a/lib/Conversion/ExportYosys/CMakeLists.txt b/lib/Conversion/ExportYosys/CMakeLists.txt new file mode 100644 index 000000000000..d8a72d5a4a7b --- /dev/null +++ b/lib/Conversion/ExportYosys/CMakeLists.txt @@ -0,0 +1,36 @@ +if (MSVC) +else () + add_compile_options(-D_YOSYS_) + add_compile_options(-DYOSYS_ENABLE_ABC) + add_compile_options(-DYOSYS_ENABLE_GLOB) + add_compile_options(-DYOSYS_ENABLE_PLUGINS) + add_compile_options(-DYOSYS_ENABLE_READLINE) + add_compile_options(-DYOSYS_ENABLE_COVER) + add_compile_options(-DYOSYS_ENABLE_ZLIB) + add_compile_options(-fexceptions) + add_compile_options(-Wno-c++98-compat-extra-semi) + add_compile_options(-Wno-cast-qual) +endif () + +add_circt_translation_library(CIRCTExportYosys + ExportRTLIL.cpp + ImportRTLIL.cpp + YosysOptimizer.cpp + + DEPENDS + CIRCTConversionPassIncGen + + LINK_COMPONENTS + Core + + LINK_LIBS PUBLIC + MLIRPass + MLIRTranslateLib + MLIRIR + MLIRTransforms + CIRCTHW + CIRCTSV + CIRCTOM + CIRCTComb + libyosys +) diff --git a/lib/Conversion/ExportYosys/ExportRTLIL.cpp b/lib/Conversion/ExportYosys/ExportRTLIL.cpp new file mode 100644 index 000000000000..4c7d1a198309 --- /dev/null +++ b/lib/Conversion/ExportYosys/ExportRTLIL.cpp @@ -0,0 +1,944 @@ +//===- ExportRTLIL.cpp - Export core dialects to RTLIL --------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines an exporter for Yosys RTLIL. +// +//===----------------------------------------------------------------------===// +#include "circt/Conversion/ExportRTLIL.h" +#include "RTLILConverterInternal.h" +#include "circt/Dialect/Comb/CombVisitors.h" +#include "circt/Dialect/HW/HWInstanceGraph.h" +#include "circt/Dialect/HW/HWVisitors.h" +#include "circt/Dialect/OM/OMDialect.h" +#include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/Seq/SeqVisitor.h" +#include "circt/Support/BackedgeBuilder.h" +#include "circt/Support/Namespace.h" +#include "mlir/IR/Builders.h" +#include "mlir/Support/FileUtilities.h" +#include "mlir/Tools/mlir-translate/Translation.h" +#include "llvm/Support/Debug.h" + +// Yosys headers. +#include "backends/rtlil/rtlil_backend.h" +#include "kernel/rtlil.h" +#include "kernel/yosys.h" + +#define DEBUG_TYPE "export-rtlil" + +using namespace circt; +using namespace hw; +using namespace comb; +using namespace Yosys; +using namespace rtlil; + +std::string circt::rtlil::getEscapedName(StringRef name) { + return RTLIL::escape_id(name.str()); +} + +namespace { +int64_t getBitWidthSeq(Type type) { + if (isa(type)) + return 1; + return getBitWidth(type); +} + +struct ExportRTLILDesign; +struct ExportRTLILModule + : public hw::TypeOpVisitor, + public hw::StmtVisitor, + public comb::CombinationalVisitor, + public seq::SeqOpVisitor { + + FailureOr + createCell(Location loc, llvm::StringRef cellName, + llvm::StringRef instanceName, + ArrayRef> parameters, + ArrayRef> ports); + + std::string getLocationStr(Location loc) { + llvm::SetVector locs; + SmallVector worklist{loc}; + while (!worklist.empty()) { + auto val = worklist.pop_back_val(); + if (auto file = dyn_cast(val)) + locs.insert(file); + if (auto file = dyn_cast(val)) + worklist.append(file.getLocations().begin(), file.getLocations().end()); + } + std::string locStr; + for (auto loc : locs) { + locStr += loc.getFilename().getValue(); + locStr += ":" + std::to_string(loc.getLine()); + locStr += ":" + std::to_string(loc.getColumn()); + } + return locStr; + } + + Yosys::Wire *createWire(Type type, StringAttr name, LocationAttr loc = {}) { + int64_t width = getBitWidthSeq(type); + + if (width < 0) + return nullptr; + + auto *wire = + rtlilModule->addWire(name ? getNewName(name) : getNewName(), width); + if (loc) + wire->set_src_attribute(getLocationStr(loc)); + return wire; + } + + LogicalResult createAndSetWire(Value value, StringAttr name) { + auto *wire = createWire(value.getType(), name, value.getLoc()); + if (!wire) + return failure(); + return setValue(value, wire); + } + + LogicalResult setValue(Value value, RTLIL::Wire *wire) { + return success(mapping.insert({value, wire}).second); + } + + FailureOr getValue(Value value) { + auto it = mapping.find(value); + if (it != mapping.end()) + return SigSpec(it->second); + return failure(); + } + + circt::Namespace moduleNameSpace; + RTLIL::IdString getNewName(StringRef name = "") { + if (name.empty()) + return NEW_ID; + + return getEscapedName(moduleNameSpace.newName(name)); + } + + LogicalResult setLowering(Value value, SigSpec s) { + auto it = mapping.find(value); + assert(it != mapping.end()); + rtlilModule->connect(it->second, s); + return success(); + } + + template + LogicalResult setLowering(Value value, SigSpecTy s) { + return setLowering(value, SigSpec(s)); + } + + LogicalResult lowerPorts(); + LogicalResult lowerBody(); + + ExportRTLILModule(ExportRTLILDesign &circuitConverter, + Yosys::RTLIL::Module *rtlilModule, hw::HWModuleLike module, + bool definedAsBlackBox) + : circuitConverter(circuitConverter), rtlilModule(rtlilModule), + module(module), definedAsBlackBox(definedAsBlackBox) {} + + DenseMap mapping; + struct MemoryInfo { + RTLIL::Memory *mem; + unsigned portId; + }; + + using hw::TypeOpVisitor::visitTypeOp; + using hw::StmtVisitor::visitStmt; + using comb::CombinationalVisitor::visitComb; + using seq::SeqOpVisitor::visitSeq; + + LogicalResult visitOp(Operation *op) { + return dispatchCombinationalVisitor(op); + } + LogicalResult visitUnhandledTypeOp(Operation *op) { + return op->emitError() << "unsupported op"; + } + LogicalResult visitUnhandledExpr(Operation *op) { + return op->emitError() << "unsupported op"; + } + LogicalResult visitInvalidComb(Operation *op) { + return dispatchTypeOpVisitor(op); + } + LogicalResult visitUnhandledComb(Operation *op) { + return visitUnhandledExpr(op); + } + LogicalResult visitInvalidTypeOp(Operation *op) { + return dispatchStmtVisitor(op); + } + LogicalResult visitInvalidStmt(Operation *op) { + return dispatchSeqOpVisitor(op); + } + LogicalResult visitUnhandledStmt(Operation *op) { + return visitUnhandledExpr(op); + } + LogicalResult visitInvalidSeqOp(Operation *op) { + return visitUnhandledExpr(op); + } + LogicalResult visitUnhandledSeqOp(Operation *op) { + return visitUnhandledExpr(op); + } + + FailureOr getParameter(Attribute attr); + + // HW type op. + RTLIL::Const getConstant(IntegerAttr attr); + RTLIL::Const getConstant(const APInt &attr); + + LogicalResult visitTypeOp(ConstantOp op) { + return setLowering(op, getConstant(op.getValueAttr())); + } + + // HW stmt op. + LogicalResult visitStmt(OutputOp op); + LogicalResult visitStmt(InstanceOp op); + + // HW expr ops. + LogicalResult visitTypeOp(AggregateConstantOp op); + LogicalResult visitTypeOp(ArrayCreateOp op); + LogicalResult visitTypeOp(ArrayGetOp op); + LogicalResult visitTypeOp(ArrayConcatOp op); + + // Comb op. + LogicalResult visitComb(AddOp op); + LogicalResult visitComb(SubOp op); + LogicalResult visitComb(MulOp op); + LogicalResult visitComb(AndOp op); + LogicalResult visitComb(OrOp op); + LogicalResult visitComb(XorOp op); + LogicalResult visitComb(MuxOp op); + LogicalResult visitComb(ExtractOp op); + LogicalResult visitComb(ICmpOp op); + LogicalResult visitComb(ConcatOp op); + LogicalResult visitComb(ShlOp op); + LogicalResult visitComb(ShrSOp op); + LogicalResult visitComb(ShrUOp op); + LogicalResult visitComb(ReplicateOp op); + LogicalResult visitComb(ParityOp op); + + // Seq op. + LogicalResult visitSeq(seq::FirRegOp op); + LogicalResult visitSeq(seq::FirMemOp op); + LogicalResult visitSeq(seq::FirMemWriteOp op); + LogicalResult visitSeq(seq::FirMemReadOp op); + LogicalResult visitSeq(seq::FirMemReadWriteOp op); + LogicalResult visitSeq(seq::FromClockOp op); + LogicalResult visitSeq(seq::ToClockOp op); + LogicalResult visitSeq(seq::CompRegOp op); + + template + LogicalResult emitVariadicOp(Operation *op, BinaryFn fn) { + // Construct n-1 binary op (currently linear) chains. + // TODO: Need to create a tree? + std::optional cur; + auto locStr = getLocationStr(op->getLoc()); + for (auto operand : op->getOperands()) { + auto result = getValue(operand); + if (failed(result)) + return failure(); + cur = + cur ? fn(getNewName(), *cur, result.value(), locStr) : result.value(); + } + + return setLowering(op->getResult(0), cur.value()); + } + + template + LogicalResult emitBinaryOp(Operation *op, BinaryFn fn) { + assert(op->getNumOperands() == 2 && "only expect binary op"); + auto lhs = getValue(op->getOperand(0)); + auto rhs = getValue(op->getOperand(1)); + auto locStr = getLocationStr(op->getLoc()); + if (failed(lhs) || failed(rhs)) + return failure(); + return setLowering(op->getResult(0), + fn(getNewName(), lhs.value(), rhs.value(), locStr)); + } + + template + LogicalResult emitUnaryOp(Operation *op, UnaryFn fn) { + assert(op->getNumOperands() == 1 && "only expect unary op"); + auto input = getValue(op->getOperand(0)); + if (failed(input)) + return failure(); + return setLowering(op->getResult(0), fn(getNewName(), input.value())); + } + + ExportRTLILDesign &circuitConverter; + DenseMap memoryMapping; + SmallVector outputs; + const bool definedAsBlackBox; + + Yosys::RTLIL::Module *rtlilModule; + hw::HWModuleLike module; +}; + +struct ExportRTLILDesign { + ExportRTLILDesign(Yosys::RTLIL::Design *design, + hw::InstanceGraph *instanceGraph) + : design(design), instanceGraph(instanceGraph) {} + llvm::DenseMap moduleMapping; + LogicalResult addModule(hw::HWModuleLike op, bool defineAsBlackBox = false); + LogicalResult run(); + + SmallVector> converter; + Yosys::RTLIL::Design *design; + hw::InstanceGraph *instanceGraph; +}; +} // namespace + +RTLIL::Const ExportRTLILModule::getConstant(const APInt &value) { + auto width = value.getBitWidth(); + if (width <= 32) + return RTLIL::Const(value.getZExtValue(), value.getBitWidth()); + + // TODO: Use more efficient encoding. + std::vector result(width, false); + for (size_t i = 0; i < width; ++i) + result[i] = value[i]; + + return RTLIL::Const(result); +} + +RTLIL::Const ExportRTLILModule::getConstant(IntegerAttr attr) { + return getConstant(attr.getValue()); +} + +FailureOr ExportRTLILModule::getParameter(Attribute attr) { + return TypeSwitch>(attr) + .Case([&](auto a) { return getConstant(a); }) + .Case( + [&](StringAttr a) { return RTLIL::Const(a.getValue().str()); }) + .Default([](auto) { return failure(); }); +} + +LogicalResult ExportRTLILModule::lowerPorts() { + ModulePortInfo ports(module.getPortList()); + size_t inputPos = 0; + for (auto [idx, port] : llvm::enumerate(ports)) { + auto *wire = createWire(port.type, port.name, port.loc); + if (!wire) + return mlir::emitError(port.loc) << "unknown type"; + // NOTE: Port id is 1-indexed. + wire->port_id = idx + 1; + if (port.isOutput()) { + wire->port_output = true; + outputs.push_back(wire); + } else if (port.isInput()) { + if (!definedAsBlackBox) + if (failed( + setValue(module.getBodyBlock()->getArgument(inputPos++), wire))) + return failure(); + wire->port_input = true; + } else { + return module.emitError() << "inout is unssuported"; + } + } + // Need to call fixup ports after port mutations. + rtlilModule->fixup_ports(); + return success(); +} + +LogicalResult ExportRTLILModule::lowerBody() { + if (module + .walk([this](Operation *op) { + for (auto result : op->getResults()) { + // TODO: Use SSA name. + StringAttr name = {}; + if (getBitWidthSeq(result.getType()) >= 0) + if (failed(createAndSetWire(result, name))) + return WalkResult::interrupt(); + } + return WalkResult::advance(); + }) + .wasInterrupted()) + return failure(); + + auto result = + module + .walk([this](Operation *op) { + if (module == op) + return WalkResult::advance(); + + if (isa( + op->getDialect())) { + LLVM_DEBUG(llvm::dbgs() << "Visiting " << *op << "\n"); + if (failed(visitOp(op))) { + op->emitError() << "lowering failed"; + return WalkResult::interrupt(); + } + LLVM_DEBUG(llvm::dbgs() << "Success \n"); + } else { + // Ignore Verif, LTL and Sim etc. + } + return WalkResult::advance(); + }) + .wasInterrupted(); + return LogicalResult::success(!result); +} + +//===----------------------------------------------------------------------===// +// HW Ops. +//===----------------------------------------------------------------------===// + +LogicalResult ExportRTLILModule::visitStmt(OutputOp op) { + assert(op.getNumOperands() == outputs.size()); + for (auto [wire, op] : llvm::zip(outputs, op.getOperands())) { + auto result = getValue(op); + if (failed(result)) + return failure(); + rtlilModule->connect(Yosys::SigSpec(wire), result.value()); + } + + return success(); +} + +LogicalResult ExportRTLILModule::visitStmt(InstanceOp op) { + // Ignore bound. + if (op->hasAttr("doNotPrint") || op.getNumResults() == 0) + return success(); + auto it = + circuitConverter.moduleMapping.find(op.getModuleNameAttr().getAttr()); + IdString id; + if (it == circuitConverter.moduleMapping.end()) { + // Ext module. + auto referredMod = + circuitConverter.instanceGraph->lookup(op.getModuleNameAttr().getAttr()) + ->getModule(); + if (auto extmod = + dyn_cast(referredMod.getOperation())) + id = getEscapedName(extmod.getVerilogModuleName()); + } else { + id = it->second->name; + } + + auto *cell = rtlilModule->addCell(getNewName(op.getInstanceName()), id); + auto connect = [&](ArrayAttr names, auto values) -> LogicalResult { + for (auto [portName, value] : + llvm::zip(names.getAsRange(), values)) { + auto loweredValue = getValue(value); + if (failed(loweredValue)) { + return op.emitError() << "port " << portName << " was not lowered"; + } + cell->connections_.insert( + {getEscapedName(portName), loweredValue.value()}); + } + return success(); + }; + + if (failed(connect(op.getArgNames(), op.getOperands())) || + failed(connect(op.getResultNames(), op.getResults()))) + return failure(); + + return success(); +} + +LogicalResult ExportRTLILModule::visitTypeOp(AggregateConstantOp op) { + SigSpec ret; + SmallVector worklist{op.getFieldsAttr()}; + while (!worklist.empty()) { + auto val = worklist.pop_back_val(); + if (auto array = dyn_cast(val)) + for (auto a : llvm::reverse(array)) + worklist.push_back(a); + else if (auto intVal = dyn_cast(val)) { + ret.append(getConstant(intVal)); + } + } + return success(); +} + +LogicalResult ExportRTLILModule::visitTypeOp(ArrayCreateOp op) { + SigSpec ret; + for (auto operand : llvm::reverse(op.getOperands())) { + auto result = getValue(operand); + if (failed(result)) + return result; + ret.append(result.value()); + } + return setLowering(op, ret); +} + +LogicalResult ExportRTLILModule::visitTypeOp(ArrayGetOp op) { + auto input = getValue(op.getInput()); + auto index = getValue(op.getIndex()); + auto result = getValue(op); + if (failed(input) || failed(index) || failed(result)) + return failure(); + + auto width = + hw::type_cast(op.getInput().getType()).getNumElements(); + index.value().extend_u0(width); + auto sig = rtlilModule->Mul( + NEW_ID, index.value(), + getConstant( + APInt(1ll << op.getIndex().getType().getIntOrFloatBitWidth(), width)), + false); + + (void)rtlilModule->addShiftx(NEW_ID, input.value(), sig, result.value()); + return success(); +} + +LogicalResult ExportRTLILModule::visitTypeOp(ArrayConcatOp op) { + SigSpec ret; + // The order is opposite so reverse it. + for (auto operand : llvm::reverse(op.getOperands())) { + auto result = getValue(operand); + if (failed(result)) + return result; + ret.append(result.value()); + } + return setLowering(op, ret); +} + +//===----------------------------------------------------------------------===// +// Comb Ops. +//===----------------------------------------------------------------------===// + +LogicalResult ExportRTLILModule::visitComb(AddOp op) { + return emitVariadicOp(op, [&](auto name, auto l, auto r, const auto &loc) { + return rtlilModule->Add(name, l, r, false, loc); + }); +} + +LogicalResult ExportRTLILModule::visitComb(SubOp op) { + return emitVariadicOp(op, [&](auto name, auto l, auto r, const auto &loc) { + return rtlilModule->Sub(name, l, r, false, loc); + }); +} + +LogicalResult ExportRTLILModule::visitComb(MuxOp op) { + auto cond = getValue(op.getCond()); + auto high = getValue(op.getTrueValue()); + auto low = getValue(op.getFalseValue()); + if (failed(cond) || failed(high) || failed(low)) + return failure(); + + return setLowering(op, rtlilModule->Mux(getNewName(), low.value(), + high.value(), cond.value())); +} + +LogicalResult ExportRTLILModule::visitComb(AndOp op) { + return emitVariadicOp(op, [&](auto name, auto l, auto r, auto &loc) { + return rtlilModule->And(name, l, r, false, loc); + }); +} + +LogicalResult ExportRTLILModule::visitComb(MulOp op) { + return emitVariadicOp(op, [&](auto name, auto l, auto r, const auto &loc) { + return rtlilModule->Mul(name, l, r, false, loc); + }); +} + +LogicalResult ExportRTLILModule::visitComb(OrOp op) { + return emitVariadicOp(op, [&](auto name, auto l, auto r, const auto &loc) { + return rtlilModule->Or(name, l, r, false, loc); + }); +} + +LogicalResult ExportRTLILModule::visitComb(XorOp op) { + return emitVariadicOp(op, [&](auto name, auto l, auto r, const auto &loc) { + return rtlilModule->Xor(name, l, r, false, loc); + }); +} + +LogicalResult ExportRTLILModule::visitComb(ExtractOp op) { + auto result = getValue(op.getOperand()); + if (failed(result)) + return result; + auto sig = result.value().extract(op.getLowBit(), + op.getType().getIntOrFloatBitWidth()); + return setLowering(op, sig); +} + +LogicalResult ExportRTLILModule::visitComb(ConcatOp op) { + SigSpec ret; + for (auto operand : llvm::reverse(op.getOperands())) { + auto result = getValue(operand); + if (failed(result)) + return result; + ret.append(result.value()); + } + // TODO: Check endian. + return setLowering(op, ret); +} + +LogicalResult ExportRTLILModule::visitComb(ShlOp op) { + return emitBinaryOp(op, [&](auto name, auto l, auto r, const auto &loc) { + return rtlilModule->Shl(name, l, r, false, loc); + }); +} + +LogicalResult ExportRTLILModule::visitComb(ShrUOp op) { + return emitBinaryOp(op, [&](auto name, auto l, auto r, const auto &loc) { + return rtlilModule->Shr(name, l, r, false, loc); + }); +} +LogicalResult ExportRTLILModule::visitComb(ShrSOp op) { + return emitBinaryOp(op, [&](auto name, auto l, auto r, const auto &loc) { + // TODO: Make sure it's correct + return rtlilModule->Sshr(name, l, r, false, loc); + }); +} + +LogicalResult ExportRTLILModule::visitComb(ReplicateOp op) { + auto value = getValue(op.getOperand()); + if (failed(value)) + return failure(); + return setLowering(op, value.value().repeat(op.getMultiple())); +} + +LogicalResult ExportRTLILModule::visitComb(ParityOp op) { + return emitUnaryOp(op, [&](auto name, auto input) { + return rtlilModule->ReduceXor(name, input); + }); +} + +LogicalResult ExportRTLILModule::visitComb(ICmpOp op) { + return emitBinaryOp(op, [&](auto name, auto l, auto r, const auto &loc) { + switch (op.getPredicate()) { + case ICmpPredicate::eq: + case ICmpPredicate::ceq: + case ICmpPredicate::weq: + return rtlilModule->Eq(name, l, r, false, loc); + case ICmpPredicate::ne: + case ICmpPredicate::cne: + case ICmpPredicate::wne: + return rtlilModule->Ne(name, l, r, false, loc); + case ICmpPredicate::slt: + return rtlilModule->Lt(name, l, r, /*is_signed=*/true, loc); + case ICmpPredicate::sle: + return rtlilModule->Le(name, l, r, /*is_signed=*/true, loc); + case ICmpPredicate::sgt: + return rtlilModule->Gt(name, l, r, /*is_signed=*/true, loc); + case ICmpPredicate::sge: + return rtlilModule->Ge(name, l, r, /*is_signed=*/true, loc); + case ICmpPredicate::ult: + return rtlilModule->Lt(name, l, r, /*is_signed=*/false, loc); + case ICmpPredicate::ule: + return rtlilModule->Le(name, l, r, /*is_signed=*/false, loc); + case ICmpPredicate::ugt: + return rtlilModule->Gt(name, l, r, /*is_signed=*/false, loc); + case ICmpPredicate::uge: + return rtlilModule->Ge(name, l, r, /*is_signed=*/false, loc); + } + }); +} + +//===----------------------------------------------------------------------===// +// Seq Ops. +//===----------------------------------------------------------------------===// + +LogicalResult ExportRTLILModule::visitSeq(seq::FirRegOp op) { + // TODO: Support a register with preset. + if (op.getPresetAttr()) + return op.emitError(); + + auto result = getValue(op.getResult()); + auto clock = getValue(op.getClk()); + auto next = getValue(op.getNext()); + + if (failed(result) || failed(clock) || failed(next)) + return failure(); + + if (op.getReset()) { + // addSdff + auto reset = getValue(op.getReset()); + if (failed(reset)) + return reset; + + auto constOp = op.getResetValue().getDefiningOp(); + if (op.getIsAsync()) { + // Adlatch. + if (!constOp) + return failure(); + rtlilModule->addSdff(getNewName(op.getName()), clock.value(), + reset.value(), next.value(), result.value(), + getConstant(constOp.getValueAttr())); + return success(); + } + + if (constOp) { + rtlilModule->addSdff(getNewName(op.getName()), clock.value(), + reset.value(), next.value(), result.value(), + getConstant(constOp.getValueAttr())); + return success(); + } + return op.emitError() << "lowering for non-constant reset value is " + "currently not implemented"; + } + + rtlilModule->addDff(getNewName(op.getName()), clock.value(), next.value(), + result.value()); + return success(); +} + +LogicalResult ExportRTLILModule::visitSeq(seq::CompRegOp op) { + auto result = getValue(op.getResult()); + auto clock = getValue(op.getClk()); + auto next = getValue(op.getInput()); + + if (failed(result) || failed(clock) || failed(next)) + return failure(); + + if (op.getReset()) { + // addSdff + auto reset = getValue(op.getReset()); + if (failed(reset)) + return reset; + + auto constOp = op.getResetValue().getDefiningOp(); + + if (constOp) { + rtlilModule->addSdff(getNewName(op.getName() ? *op.getName() : ""), + clock.value(), reset.value(), next.value(), + result.value(), getConstant(constOp.getValueAttr())); + return success(); + } + return op.emitError() << "lowering for non-constant reset value is " + "currently not implemented"; + } + + rtlilModule->addDff(getNewName(op.getName() ? *op.getName() : ""), + clock.value(), next.value(), result.value()); + return success(); +} + +LogicalResult ExportRTLILModule::visitSeq(seq::FirMemOp op) { + auto *mem = new RTLIL::Memory(); + mem->width = op.getType().getWidth(); + mem->size = op.getType().getDepth(); + mem->name = getNewName("Memory"); + rtlilModule->memories[mem->name] = mem; + MemoryInfo memInfo; + memInfo.mem = mem; + memInfo.portId = 0; + memoryMapping.insert({op, memInfo}); + return success(); +} + +FailureOr ExportRTLILModule::createCell( + Location loc, llvm::StringRef cellName, llvm::StringRef instanceName, + ArrayRef> parameters, + ArrayRef> ports) { + auto *cell = + rtlilModule->addCell(getNewName(instanceName), getEscapedName(cellName)); + cell->set_src_attribute(getLocationStr(loc)); + for (auto [portName, value] : ports) { + auto loweredValue = getValue(value); + if (failed(loweredValue)) + return failure(); + cell->connections_.insert({getEscapedName(portName), loweredValue.value()}); + } + for (auto [portName, value] : parameters) { + auto loweredValue = getParameter(value); + if (failed(loweredValue)) + return failure(); + cell->parameters.insert({getEscapedName(portName), loweredValue.value()}); + } + return cell; +} + +LogicalResult ExportRTLILModule::visitSeq(seq::FirMemWriteOp op) { + // cell $memwr_v2 $auto$proc_memwr.cc:45:proc_memwr$49 + // parameter \ABITS 5 + // parameter \CLK_ENABLE 1'1 + // parameter \CLK_POLARITY 1'1 + // parameter \MEMID "\\Memory" + // parameter \PORTID 1 + // parameter \PRIORITY_MASK 1'1 + // parameter \WIDTH 65 + // connect \ADDR $1$memwr$\Memory$mem.sv:29$2_ADDR[4:0]$15 + // connect \CLK \W0_clk + // connect \DATA $1$memwr$\Memory$mem.sv:29$2_DATA[64:0]$16 + // connect \EN $1$memwr$\Memory$mem.sv:29$2_EN[64:0]$17 + // end + auto firmem = op.getMemory().getDefiningOp(); + if (!firmem) + return failure(); + SmallVector> ports{ + {"ADDR", op.getAddress()}, + {"CLK", op.getClk()}, + {"DATA", op.getData()}, + {"EN", op.getData()}}; + OpBuilder builder(module.getContext()); + auto trueConst = builder.getIntegerAttr(builder.getI1Type(), 1); + auto widthConst = builder.getI32IntegerAttr( + llvm::Log2_64_Ceil(firmem.getType().getDepth())); + + auto it = memoryMapping.find(firmem); + assert(it != memoryMapping.end() && "firmem should be visited"); + auto memName = builder.getStringAttr(it->second.mem->name.str()); + auto portId = builder.getI32IntegerAttr(++it->second.portId); + auto width = builder.getI32IntegerAttr(firmem.getType().getWidth()); + SmallVector> parameters{ + {"ABITS", widthConst}, {"CLK_ENABLE", trueConst}, + {"CLK_POLARITY", trueConst}, {"MEMID", memName}, + {"PORTID", portId}, {"WIDTH", width}, + {"PRIORITY_MASK", trueConst}}; + + auto cell = + createCell(op->getLoc(), "$memwr_v2", "mem_write", parameters, ports); + if (failed(cell)) + return cell; + + if (op.getEnable()) { + auto enable = getValue(op.getEnable()); + } + + return success(); +} +LogicalResult ExportRTLILModule::visitSeq(seq::FirMemReadOp op) { + // rtlilModule->addCell("$memrd", id) + // Memrd + // cell $memrd + // parameter \ABITS 5 + // parameter \CLK_ENABLE 0 + // parameter \CLK_POLARITY 0 + // parameter \MEMID "\\Memory" + // parameter \TRANSPARENT 0 + // parameter \WIDTH 65 + // connect \ADDR \R0_addr + // connect \CLK 1'x + // connect \DATA $memrd$\Memory$mem.sv:45$18_DATA + // connect \EN 1'x + // end + auto firmem = op.getMemory().getDefiningOp(); + if (!firmem) + return failure(); + SmallVector> ports{ + {"ADDR", op.getAddress()}, {"CLK", op.getClk()}, {"DATA", op.getData()}}; + if (op.getEnable()) + ports.emplace_back("EN", op.getEnable()); + OpBuilder builder(module.getContext()); + auto trueConst = builder.getIntegerAttr(builder.getI1Type(), 1); + auto falseConst = builder.getIntegerAttr(builder.getI1Type(), 0); + + auto widthConst = builder.getI32IntegerAttr( + llvm::Log2_64_Ceil(firmem.getType().getDepth())); + + auto it = memoryMapping.find(firmem); + assert(it != memoryMapping.end() && "firmem should be visited"); + auto memName = builder.getStringAttr(it->second.mem->name.str()); + auto width = builder.getI32IntegerAttr(firmem.getType().getWidth()); + SmallVector> parameters{ + {"ABITS", widthConst}, + {"CLK_ENABLE", op.getEnable() ? trueConst : falseConst}, + {"CLK_POLARITY", falseConst}, + {"TRANSPARENT", falseConst}, + {"MEMID", memName}, + {"WIDTH", width}}; + + auto cell = createCell(op->getLoc(), "$memrd", "mem_read", parameters, ports); + if (failed(cell)) + return cell; + if (!op.getEnable()) { + (*cell)->connections_.insert( + {getEscapedName("EN"), SigSpec(getConstant(APInt(1, 1)))}); + } + + return success(); +} +LogicalResult ExportRTLILModule::visitSeq(seq::FirMemReadWriteOp op) { + return failure(); +} + +LogicalResult ExportRTLILModule::visitSeq(seq::FromClockOp op) { + auto result = getValue(op.getInput()); + if (failed(result)) + return result; + return setLowering(op, result.value()); +} + +LogicalResult ExportRTLILModule::visitSeq(seq::ToClockOp op) { + auto result = getValue(op.getInput()); + if (failed(result)) + return result; + return setLowering(op, result.value()); +} + +void circt::rtlil::init_yosys(bool enableLog) { + // Set up yosys. + Yosys::log_streams.clear(); + if (enableLog) + Yosys::log_streams.push_back(&std::cerr); + Yosys::log_error_stderr = true; + Yosys::yosys_setup(); +} + +LogicalResult ExportRTLILDesign::addModule(hw::HWModuleLike op, + bool defineAsBlackBox) { + defineAsBlackBox |= isa(op); + if (design->has(getEscapedName(op.getModuleName()))) { + return success(defineAsBlackBox); + } + auto *newModule = design->addModule(getEscapedName(op.getModuleName())); + if (!moduleMapping.insert({op.getModuleNameAttr(), newModule}).second) + return failure(); + if (defineAsBlackBox) + newModule->set_bool_attribute(ID::blackbox); + converter.emplace_back(std::make_unique( + *this, newModule, op, defineAsBlackBox)); + return converter.back()->lowerPorts(); +} + +LogicalResult ExportRTLILDesign::run() { + for (auto &c : converter) + if (!c->definedAsBlackBox && failed(c->lowerBody())) + return failure(); + return success(); +} + +mlir::FailureOr> +circt::rtlil::exportRTLILDesign(ArrayRef modules, + ArrayRef blackBox, + hw::InstanceGraph &instanceGraph) { + auto theDesign = std::make_unique(); + auto *design = theDesign.get(); + ExportRTLILDesign exporter(design, &instanceGraph); + + for (auto op : modules) + if (failed(exporter.addModule(op))) + return failure(); + + for (auto op : blackBox) { + if (failed(exporter.addModule(op, /*defineAsBlackBox=*/true))) + return failure(); + } + + if (failed(exporter.run())) + return failure(); + + return std::move(theDesign); +} + +mlir::FailureOr> +circt::rtlil::exportRTLILDesign(mlir::ModuleOp module) { + hw::InstanceGraph instanceGraph(module); + SmallVector modules(module.getOps()); + return exportRTLILDesign(modules, {}, instanceGraph); +} + +void circt::rtlil::registerRTLILExport() { + static mlir::TranslateFromMLIRRegistration toRTLIL( + "export-rtlil", "export RTLIL", + [](ModuleOp module, llvm::raw_ostream &os) { + init_yosys(false); + auto value = exportRTLILDesign(module); + if (failed(value)) + return failure(); + std::ostringstream stream; + RTLIL_BACKEND::dump_design(stream, value->get(), false); + os << stream.str(); + return success(); + }, + [](mlir::DialectRegistry ®istry) { + registry.insert(); + registry.insert(); + registry.insert(); + registry.insert(); + }); +} diff --git a/lib/Conversion/ExportYosys/ImportRTLIL.cpp b/lib/Conversion/ExportYosys/ImportRTLIL.cpp new file mode 100644 index 000000000000..8c603339b6d4 --- /dev/null +++ b/lib/Conversion/ExportYosys/ImportRTLIL.cpp @@ -0,0 +1,960 @@ +//===- ImportRTLIL.cpp - RTLIL import implementation ----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the import of RTLIL designs into CIRCT. +// +//===----------------------------------------------------------------------===// + +#include "RTLILConverterInternal.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/Seq/SeqOps.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Support/FileUtilities.h" +#include "mlir/Tools/mlir-translate/Translation.h" +#include "llvm/ADT/MapVector.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/SourceMgr.h" + +// Yosys headers. +#include "kernel/rtlil.h" +#include "kernel/yosys.h" + +#define DEBUG_TYPE "yosys-optimizer" + +using namespace circt; +using namespace hw; +using namespace comb; +using namespace Yosys; +using namespace rtlil; + +namespace { +class ImportRTLILModule; + +// Base class for cell pattern handling +class CellPatternBase { +public: + CellPatternBase(StringRef typeName, ArrayRef inputPortNames, + StringRef outputPortName) + : typeName(typeName), inputPortNames(inputPortNames), + outputPortName(outputPortName){}; + LogicalResult convert(ImportRTLILModule &importer, Cell *cell); + virtual ~CellPatternBase() {} + +private: + virtual Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) = 0; + + SmallString<4> typeName; + SmallVector> inputPortNames; + SmallString<4> outputPortName; +}; + +struct ImportRTLILDesign { + RTLIL::Design *design; + ImportRTLILDesign(RTLIL::Design *design) : design(design) {} + LogicalResult run(mlir::ModuleOp module); + using ModuleMappingTy = DenseMap; + ModuleMappingTy moduleMapping; +}; + +static StringRef getStrippedName(const Yosys::RTLIL::IdString &str) { + StringRef s(str.c_str()); + return s.starts_with("\\") ? s.drop_front(1) : s; +} + +// Class for importing a single RTLIL module +class ImportRTLILModule { +public: + ImportRTLILModule(MLIRContext *context, const ImportRTLILDesign &importer, + RTLIL::Module *rtlilModule, OpBuilder &moduleBuilder); + + hw::HWModuleLike getModuleOp() { return module; } + LogicalResult + importBody(const ImportRTLILDesign::ModuleMappingTy &moduleMapping); + const auto &getExternalModules() const { return exeternalModules; } + + friend class CellPatternBase; + +private: + RTLIL::Module *rtlilModule; + hw::HWModuleLike module; + // MLIRContext *context; + // const ImportRTLILDesign &importer; + + StringAttr getStringAttr(const Yosys::RTLIL::IdString &str) const { + StringRef s(str.c_str()); + return builder->getStringAttr(getStrippedName(str)); + } + + StringAttr getStringAttr(Yosys::RTLIL::Wire *wire) const { + return getStringAttr(wire->name); + } + + mlir::TypedValue getInOutValue(Location loc, + const SigSpec &sigSpec); + LogicalResult connect(Location loc, mlir::TypedValue lhs, + Value rhs); + + LogicalResult connect(Location loc, const SigSpec &lhs, const SigSpec &rhs); + + DenseMap wireMapping; + sv::WireOp getWireValue(RTLIL::Wire *wire) const { + return wireMapping.lookup(getStringAttr(wire)); + } + + Value getValueForWire(const RTLIL::Wire *wire) const { + return mapping.at(getStringAttr(wire->name)); + } + + IntegerAttr getIntegerAttr(const RTLIL::Const &c) { + APInt a = APInt::getZero(c.size()); + for (auto [idx, b] : llvm::enumerate(c.bits)) { + if (b == RTLIL::State::S0) { + } else if (b == RTLIL::State::S1) { + a.setBit(idx); + } else { + mlir::emitError(module.getLoc()) + << " non-binary constant is not supported yet"; + return {}; + } + return builder->getIntegerAttr(builder->getIntegerType(c.size()), a); + } + + return builder->getIntegerAttr(builder->getIntegerType(c.size()), + APInt(c.size(), c.as_int())); + } + + Value getValueForSigSpec(const RTLIL::SigSpec &sigSpec) { + // Wire. + if (sigSpec.is_wire()) + return getValueForWire(sigSpec.as_wire()); + + // Fully undef. Lower it to constant X. + if (sigSpec.is_fully_undef()) + return builder->create( + builder->getUnknownLoc(), + builder->getIntegerType(sigSpec.as_const().size())); + + // Fully const. Lower it to constant. + if (sigSpec.is_fully_const()) + return builder->create( + builder->getUnknownLoc(), getIntegerAttr(sigSpec.as_const())); + + if (sigSpec.is_bit()) { + auto bit = sigSpec.as_bit(); + if (!bit.wire) { + module.emitError() << "is not wire"; + return {}; + } + auto v = getValueForWire(bit.wire); + auto width = 1; + auto offset = bit.offset; + return builder->create(v.getLoc(), v, offset, width); + } + + if (sigSpec.is_chunk()) { + auto chunk = sigSpec.as_chunk(); + if (!chunk.wire) { + module.emitError() << "is not wire"; + return {}; + } + auto v = getValueForWire(chunk.wire); + auto width = chunk.width; + auto offset = chunk.offset; + return builder->create(v.getLoc(), v, offset, width); + } + + SmallVector chunks; + for (auto w : llvm::reverse(sigSpec.chunks())) + chunks.push_back(getValueForSigSpec(w)); + return builder->create(builder->getUnknownLoc(), chunks); + } + + // Get the value of a port of a cell. + Value getPortValue(RTLIL::Cell *cell, StringRef portName) { + return getValueForSigSpec(cell->getPort(getEscapedName(portName))); + } + SigSpec getPortSig(RTLIL::Cell *cell, StringRef portName) { + return cell->getPort(getEscapedName(portName)); + } + + LogicalResult + importCell(const ImportRTLILDesign::ModuleMappingTy &moduleMapping, + RTLIL::Cell *cell); + + DenseMap mapping; + std::unique_ptr builder; + llvm::MapVector exeternalModules; + std::unique_ptr block; + + llvm::StringMap> handler; + + template + void addPattern(StringRef typeName, Args... args); + + template + void addOpPattern(StringRef typeName, ArrayRef inputPortNames, + StringRef outputPortName); + template + void addOpPatternBinary(StringRef typeName); + + void registerPatterns(); +}; + +//===----------------------------------------------------------------------===// +// Cell Patterns +//===----------------------------------------------------------------------===// + +template +struct CellOpPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + return builder.create(location, inputValues, false); + } +}; + +template +struct BinNonCommutativeOpPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + return builder.create(location, inputValues[0], + inputValues[1], false); + } +}; + +template +struct AndOrNotOpPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + auto notB = comb::createOrFoldNot(location, inputValues[1], builder, false); + + Value value; + if (isAnd) + value = builder.create( + location, ArrayRef{inputValues[0], notB}, false); + else + value = builder.create( + location, ArrayRef{inputValues[0], notB}, false); + return value; + } +}; +struct NorOpPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + auto aOrB = builder.create(location, inputValues, false); + return comb::createOrFoldNot(location, aOrB, builder, false); + } +}; +struct NandOpPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + auto aOrB = builder.create(location, inputValues, false); + return comb::createOrFoldNot(location, aOrB, builder, false); + } +}; + +struct NotOpPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + return comb::createOrFoldNot(location, inputValues[0], builder, false); + } +}; + +struct MuxOpPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + return builder.create(location, inputValues[2], inputValues[1], + inputValues[0]); + } +}; + +struct ParityOpPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + return builder.create(location, inputValues[0]); + } +}; + +struct ICmpOpPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + ICmpOpPattern(StringRef typeName, ICmpPredicate pred) + : CellPatternBase(typeName, {"A", "B"}, "Y"), upred(pred), spred(pred){}; + + ICmpOpPattern(StringRef typeName, ICmpPredicate pred, + ICmpPredicate signedPred) + : CellPatternBase(typeName, {"A", "B"}, "Y"), upred(pred), + spred(signedPred){}; + + static FailureOr isSigned(Location location, Cell *cell) { + bool aSigned = cell->getParam(getEscapedName("A_SIGNED")).as_bool(); + bool bSigned = cell->getParam(getEscapedName("B_SIGNED")).as_bool(); + if (aSigned && bSigned) + return true; + if (!aSigned && !bSigned) + return false; + // Currently unsupported. + return mlir::emitError(location) << "A_SIGNED and B_SIGNED don't match"; + } + + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + auto singed = isSigned(location, cell); + if (failed(singed)) + return {}; + + return builder.create(location, *singed ? spred : upred, + inputValues[0], inputValues[1]); + } + +private: + circt::comb::ICmpPredicate upred, spred; +}; + +struct XnorOpPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + auto aAndB = builder.create(location, inputValues, false); + auto notA = comb::createOrFoldNot(location, inputValues[0], builder, false); + auto notB = comb::createOrFoldNot(location, inputValues[1], builder, false); + + auto notAnds = + builder.create(location, ArrayRef{notA, notB}, false); + + return builder.create(location, ArrayRef{aAndB, notAnds}, + false); + } +}; + +// TODO: Add async. +template +struct DFFPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + auto resetValue = + builder.create(location, APInt(1, resetValueConst)); + auto clk = builder.create(location, inputValues[0]); + return builder.create( + location, inputValues[1], clk, inputValues[2], resetValue, + builder.getStringAttr(getStrippedName(cell->name))); + } +}; + +struct ResetlessDFFPattern : public CellPatternBase { + using CellPatternBase::CellPatternBase; + Value convert(Cell *cell, OpBuilder &builder, Location location, + ValueRange inputValues) override { + auto clk = builder.create(location, inputValues[0]); + auto compregOp = + builder.create(location, inputValues[1], clk); + compregOp.setName(getStrippedName(cell->name)); + return compregOp; + } +}; + +} // namespace + +template +void ImportRTLILModule::addPattern(StringRef typeName, Args... args) { + handler.insert({typeName, std::make_unique(typeName, args...)}); +} + +template +void ImportRTLILModule::addOpPattern(StringRef typeName, + ArrayRef inputPortNames, + StringRef outputPortName) { + handler.insert({typeName, std::make_unique>( + typeName, inputPortNames, outputPortName)}); +} + +template +void ImportRTLILModule::addOpPatternBinary(StringRef typeName) { + handler.insert({typeName, std::make_unique>( + typeName, ArrayRef{"A", "B"}, "Y")}); +} + +ImportRTLILModule::ImportRTLILModule(MLIRContext *context, + const ImportRTLILDesign &importer, + RTLIL::Module *rtlilModule, + OpBuilder &moduleBuilder) + : rtlilModule(rtlilModule) { + builder = std::make_unique(context); + block = std::make_unique(); + registerPatterns(); + size_t size = rtlilModule->ports.size(); + SmallVector ports(size); + SmallVector values(size); + auto modName = getStringAttr(rtlilModule->name); + + size_t numInput = 0, numOutput = 0; + for (const auto &port : rtlilModule->ports) { + auto *wire = rtlilModule->wires_[port]; + assert(wire->port_input || wire->port_output); + + // Port id is 1-indexed. + size_t portId = wire->port_id - 1; + size_t argNum = (wire->port_output ? numOutput : numInput)++; + ports[portId].name = getStringAttr(wire->name); + ports[portId].argNum = argNum; + ports[portId].type = builder->getIntegerType(wire->width); + ports[portId].dir = + wire->port_output ? hw::ModulePort::Output : hw::ModulePort::Input; + } + SmallVector defaultParameters; + for (auto [paramName, param] : rtlilModule->parameter_default_values) { + auto value = + IntegerAttr::get(builder->getIntegerType(param.size()), param.as_int()); + auto type = builder->getIntegerType(param.size()); + auto name = getStringAttr(paramName); + defaultParameters.push_back( + builder->getAttr(name, type, value)); + } + auto paramArgs = builder->getArrayAttr(defaultParameters); + + if (rtlilModule->get_blackbox_attribute()) { + module = moduleBuilder.create( + UnknownLoc::get(context), modName, ports, modName, paramArgs); + module.setPrivate(); + } else + module = moduleBuilder.create(UnknownLoc::get(context), + modName, ports, paramArgs); +} + +mlir::TypedValue +ImportRTLILModule::getInOutValue(Location loc, const SigSpec &sigSpec) { + if (sigSpec.is_wire()) + return getWireValue(sigSpec.as_wire()); + + // Const cannot be inout. + if (sigSpec.is_fully_const()) + return {}; + + // Bit selection. + if (sigSpec.is_bit()) { + auto bit = sigSpec.as_bit(); + if (!bit.wire) { + module.emitError() << "is not wire"; + return {}; + } + auto wire = getWireValue(bit.wire); + assert(wire); + auto idx = builder->create( + wire.getLoc(), + APInt(bit.wire->width <= 1 ? 1 : llvm::Log2_32_Ceil(bit.wire->width), + bit.offset)); + return builder->create(wire.getLoc(), wire, idx); + } + + // Range selection. + if (sigSpec.is_chunk()) { + auto chunk = sigSpec.as_chunk(); + if (!chunk.wire) { + mlir::emitError(loc) << "unsupported chunk states"; + return {}; + } + + auto wire = getWireValue(chunk.wire); + auto arrayLength = + cast(wire.getElementType()).getNumElements(); + auto idx = builder->create( + wire.getLoc(), + APInt(arrayLength <= 1 ? 1 : llvm::Log2_32_Ceil(arrayLength), + chunk.offset)); + return builder->create(loc, wire, idx, + chunk.width); + } + + // Concat ref. + auto size = sigSpec.size(); + auto newWire = builder->create( + loc, hw::ArrayType::get(builder->getI1Type(), size)); + size_t newOffest = 0; + for (auto sig : sigSpec.chunks()) { + if (!sig.is_wire()) { + mlir::emitError(loc) << "unsupported chunk"; + return {}; + } + auto child = wireMapping.lookup(getStringAttr(sig.wire)); + auto childSize = + cast(child.getElementType()).getNumElements(); + auto width = sig.width; + auto offset = sig.offset; + auto idx = builder->create( + loc, APInt(childSize <= 1 ? 1 : llvm::Log2_32_Ceil(childSize), offset)); + auto parent = + builder->create(loc, child, idx, width); + + auto newIndex = builder->create( + loc, APInt(size <= 1 ? 1 : llvm::Log2_32_Ceil(size), newOffest)); + auto newRhs = builder->create( + loc, newWire, newIndex, width); + + // Make sure offset is correct. + // parent <= wire[t, offset] + builder->create( + loc, parent, builder->create(loc, newRhs)); + } + + return newWire; +} + +LogicalResult ImportRTLILModule::connect(Location loc, + mlir::TypedValue lhs, + Value rhs) { + if (lhs.getType().getElementType() != rhs.getType()) + rhs = builder->create(loc, lhs.getType().getElementType(), + rhs); + builder->create(loc, lhs, rhs); + return success(); +} + +LogicalResult ImportRTLILModule::connect(Location loc, const SigSpec &lhs, + const SigSpec &rhs) { + auto lhsValue = getInOutValue(loc, lhs); + Value output = getValueForSigSpec(rhs); + if (!lhsValue || !output) + return mlir::emitError(loc) << "unsupported connection"; + + return connect(loc, lhsValue, output); +} + +LogicalResult ImportRTLILModule::importCell( + const ImportRTLILDesign::ModuleMappingTy &moduleMapping, + RTLIL::Cell *cell) { + auto cellName = getStringAttr(cell->type); + LLVM_DEBUG(llvm::dbgs() << "Importing Cell " << cellName << "\n";); + // Standard cells. + auto it = handler.find(cellName); + auto location = builder->getUnknownLoc(); + + if (it == handler.end()) { + + SmallVector> lhsValues; + SmallVector>> + lhsValuesWithIndex; + SmallVector values; + SmallVector> rhsValuesWithIndex; + SmallVector ports; + HWModuleLike referredModule; + auto *referredRTLILModule = rtlilModule->design->module(cell->type); + for (auto [lhs, rhs] : cell->connections()) { + hw::PortInfo hwPort; + hwPort.name = getStringAttr(lhs); + auto portIdx = + referredRTLILModule ? referredRTLILModule->wire(lhs)->port_id : 0; + + if (cell->output(lhs)) { + hwPort.dir = hw::PortInfo::Output; + lhsValuesWithIndex.push_back({portIdx, getInOutValue(location, rhs)}); + if (!lhsValuesWithIndex.back().second) + return mlir::emitError(location) + << "port lowering failed cell name=" << cellName + + << " port name=" << lhs.c_str(); + auto array = dyn_cast( + lhsValuesWithIndex.back().second.getType().getElementType()); + hwPort.type = + builder->getIntegerType(array ? array.getNumElements() : 1); + } else { + hwPort.dir = hw::PortInfo::Input; + rhsValuesWithIndex.push_back({portIdx, getValueForSigSpec(rhs)}); + + if (!rhsValuesWithIndex.back().second) + return mlir::emitError(location) + << "port lowering failed cell name=" << cellName + << " port name=" << lhs.c_str(); + + hwPort.type = rhsValuesWithIndex.back().second.getType(); + } + + ports.push_back(hwPort); + } + ArrayAttr paramDecl, paramArgs; + if (cell->parameters.size()) { + SmallVector parameters, defaultParameters; + for (auto [paramName, param] : cell->parameters) { + auto value = IntegerAttr::get(builder->getIntegerType(param.size()), + param.as_int()); + auto type = builder->getIntegerType(param.size()); + auto name = getStringAttr(paramName); + parameters.push_back( + builder->getAttr(name, type, Attribute())); + defaultParameters.push_back( + builder->getAttr(name, type, value)); + } + paramDecl = builder->getArrayAttr(parameters); + paramArgs = builder->getArrayAttr(defaultParameters); + } + + if (cellName.getValue().starts_with("$")) { + // Yosys std cells. Just lower it to external module instances for now. + auto &extMod = exeternalModules[cellName]; + if (!extMod) { + OpBuilder::InsertionGuard guard(*builder); + builder->setInsertionPointToStart(block.get()); + SmallString<16> name; + name += "yosys_builtin_cell"; + name += cellName; + extMod = builder->create(location, cellName, + ports, name, paramDecl); + extMod.setPrivate(); + } + referredModule = extMod; + } else { + // Otherwise lower it to an instance. + auto mod = moduleMapping.lookup(cellName); + if (!mod) + return failure(); + referredModule = mod; + } + + if (referredRTLILModule) { + std::sort(lhsValuesWithIndex.begin(), lhsValuesWithIndex.end(), + [](const auto &lhs, const auto &rhs) { + return lhs.first < rhs.first; + }); + std::sort(rhsValuesWithIndex.begin(), rhsValuesWithIndex.end(), + [](const auto &lhs, const auto &rhs) { + return lhs.first < rhs.first; + }); + } + for (auto t : lhsValuesWithIndex) + lhsValues.push_back(t.second); + for (auto t : rhsValuesWithIndex) + values.push_back(t.second); + + auto result = builder->create( + location, referredModule, getStringAttr(cell->name), values, paramArgs); + assert(result.getNumResults() == lhsValues.size()); + for (auto [lhs, rhs] : llvm::zip(lhsValues, result.getResults())) + if (failed(connect(location, lhs, (Value)rhs))) + return failure(); + return success(); + } + auto result = it->second->convert(*this, cell); + return result; +} + +// RTLIL importer creates a bunch of array wires to mimic bit-sensitive +// assignments. Eliminate all of them in the post-process. +struct Range { + Range() = default; + sv::WireOp root = {}; + int64_t start = 0; + int64_t width = 0; +}; + +static std::optional getConstantValue(Value value) { + auto constantIndex = value.template getDefiningOp(); + if (constantIndex) + if (constantIndex.getValue().getBitWidth() <= 63) + return constantIndex.getValue().getZExtValue(); + + return {}; +} + +static Range getRoot(Value value) { + auto op = value.getDefiningOp(); + if (!op) + return Range(); + auto width = + hw::getBitWidth(cast(value.getType()).getElementType()); + return TypeSwitch(op) + .Case([&](auto op) { return Range{op, 0, width}; }) + .Case([&](sv::ArrayIndexInOutOp op) { + auto result = getRoot(op.getInput()); + auto index = getConstantValue(op.getIndex()); + if (!index) + return Range(); + result.start += *index; + result.width = width; + return result; + }) + .Case([&](sv::IndexedPartSelectInOutOp op) { + auto result = getRoot(op.getInput()); + auto index = getConstantValue(op.getBase()); + if (!index) + return Range(); + result.start += *index; + result.width = width; + return result; + }) + .Default([](auto) { return Range(); }); +} +// Eliminate all SV wires used in the module. +static LogicalResult cleanUpHWModule(hw::HWModuleOp module) { + llvm::MapVector>> + writes; + llvm::MapVector>> reads; + module.walk([&](sv::WireOp wire) { writes.insert({wire, {}}); }); + module.walk([&](sv::AssignOp assign) { + auto range = getRoot(assign.getDest()); + if (range.root) + writes[range.root].emplace_back(range, assign); + }); + module.walk([&](sv::ReadInOutOp read) { + auto range = getRoot(read.getInput()); + if (range.root) + reads[range.root].emplace_back(range, read); + }); + for (auto &[wire, elements] : writes) { + // Sort by start index. + llvm::sort(elements.begin(), elements.end(), + [](const auto &lhs, const auto &rhs) { + return lhs.first.start < rhs.first.start; + }); + int64_t currentIdx = 0; + bool failed = false; + for (auto elem : elements) { + if (elem.first.start != currentIdx || elem.first.width < 0) { + failed = true; + break; + } + currentIdx += elem.first.width; + } + + if (failed) + continue; + + // Fully written. + if (currentIdx == hw::getBitWidth(wire.getType().getElementType())) { + OpBuilder builder(wire); + SmallVector concatInputs; + for (auto elem : elements) { + // Bitcast to integer and comb concat. + auto bitcast = builder.createOrFold( + wire.getLoc(), builder.getIntegerType(elem.first.width), + elem.second.getSrc()); + concatInputs.push_back(bitcast); + elem.second->erase(); + } + std::reverse(concatInputs.begin(), concatInputs.end()); + auto concat = + builder.createOrFold(wire.getLoc(), concatInputs); + + for (auto &[range, readOp] : reads[wire]) { + auto extract = builder.create( + wire.getLoc(), concat, range.start, range.width); + readOp.replaceAllUsesWith(extract); + readOp.getDefiningOp()->erase(); + } + } + } + + return success(); +} +LogicalResult ImportRTLILModule::importBody( + const ImportRTLILDesign::ModuleMappingTy &moduleMapping) { + if (isa(module)) + return success(); + + SmallVector> outputs; + builder->setInsertionPointToStart(module.getBodyBlock()); + // Init wires. + ModulePortInfo portInfo(module.getPortList()); + for (auto wire : rtlilModule->wires()) { + if (wire->port_input) { + auto arg = module.getBodyBlock()->getArgument( + portInfo.at(wire->port_id - 1).argNum); + mapping.insert({getStringAttr(wire->name), arg}); + } else { + auto loc = builder->getUnknownLoc(); + auto w = builder->create( + loc, hw::ArrayType::get(builder->getIntegerType(1), wire->width)); + auto read = builder->create( + loc, builder->getIntegerType(wire->width), + builder->create(loc, w)); + mapping.insert({getStringAttr(wire), read}); + wireMapping.insert({getStringAttr(wire), w}); + if (wire->port_output) { + outputs.emplace_back(wire->port_id - 1, read); + } + } + } + + llvm::sort( + outputs.begin(), outputs.end(), + [](const auto &lhs, const auto &rhs) { return lhs.first < rhs.first; }); + SmallVector results; + for (auto p : llvm::map_range(outputs, [](auto &p) { return p.second; })) + results.push_back(p); + + // Ensure terminator. + module.getBodyBlock()->getTerminator()->setOperands(results); + builder->setInsertionPointToStart(module.getBodyBlock()); + + // Import connections. + for (auto con : rtlilModule->connections()) { + if (failed(connect(builder->getUnknownLoc(), con.first, con.second))) + return failure(); + } + + // Import cells. + for (auto cell : rtlilModule->cells()) { + if (failed(importCell(moduleMapping, cell))) + return failure(); + } + + // Clean up the module by removing SV constructs. + if (auto hwModule = dyn_cast(*module)) + if (failed(cleanUpHWModule(hwModule))) + return failure(); + + return success(); +} + +LogicalResult CellPatternBase::convert(ImportRTLILModule &importer, + Cell *cell) { + SmallVector inputs; + for (auto name : inputPortNames) { + inputs.push_back(importer.getPortValue(cell, name)); + if (!inputs.back()) + return failure(); + } + auto location = importer.builder->getUnknownLoc(); + + auto rhsValue = convert(cell, *importer.builder, location, inputs); + if (!rhsValue) + return failure(); + auto lhsSig = importer.getPortSig(cell, outputPortName); + auto lhsValue = importer.getInOutValue(location, lhsSig); + return importer.connect(location, lhsValue, rhsValue); +} + +LogicalResult ImportRTLILDesign::run(mlir::ModuleOp module) { + SmallVector modules; + OpBuilder builder(module); + builder.setInsertionPointToStart(module.getBody()); + for (auto mod : design->modules()) { + modules.emplace_back(module.getContext(), *this, mod, builder); + auto moduleOp = modules.back().getModuleOp(); + moduleMapping.insert({moduleOp.getNameAttr(), moduleOp}); + } + llvm::DenseSet extMap; + for (auto &mod : modules) { + if (failed(mod.importBody(moduleMapping))) + return failure(); + for (auto [str, ext] : mod.getExternalModules()) { + auto it = extMap.insert(str).second; + if (it) { + ext->moveBefore(module.getBody(), module.getBody()->begin()); + } + } + } + + return success(); +} + +LogicalResult circt::rtlil::importRTLILDesign(RTLIL::Design *design, + ModuleOp module) { + ImportRTLILDesign importer(design); + return importer.run(module); +} + +void ImportRTLILModule::registerPatterns() { + // Comb primitive cells. + addOpPatternBinary("$add"); + addOpPatternBinary("$and"); + addOpPatternBinary("$or"); + addOpPatternBinary("$xor"); + addOpPatternBinary("$mul"); + addPattern>( + "$sub", ArrayRef{"A", "B"}, "Y"); + addPattern>( + "$shl", ArrayRef{"A", "B"}, "Y"); + addPattern("$mux", ArrayRef{"A", "B", "S"}, "Y"); + addPattern("$reduce_xor", ArrayRef{"A"}, "Y"); + + // ICmp. + addPattern("$eq", ICmpPredicate::eq); + addPattern("$ne", ICmpPredicate::ne); + addPattern("$le", ICmpPredicate::ule, ICmpPredicate::sle); + addPattern("$lt", ICmpPredicate::ult, ICmpPredicate::slt); + addPattern("$ge", ICmpPredicate::uge, ICmpPredicate::sge); + addPattern("$gt", ICmpPredicate::ugt, ICmpPredicate::sgt); + + // FIXME: Check the CLK_POLARITY attribute. + addPattern("$dff", ArrayRef{"CLK", "D"}, "Q"); + + // Post-synthesis gate cells. + addOpPatternBinary("$_XOR_"); + addOpPatternBinary("$_AND_"); + addOpPatternBinary("$_OR_"); + addPattern>( + "$_ANDNOT_", ArrayRef{"A", "B"}, "Y"); + addPattern>( + "$_ORNOT_", ArrayRef{"A", "B"}, "Y"); + addPattern("$_XNOR_", ArrayRef{"A", "B"}, "Y"); + addPattern("$_NOR_", ArrayRef{"A", "B"}, "Y"); + addPattern("$_NAND_", ArrayRef{"A", "B"}, "Y"); + addPattern("$_NOT_", ArrayRef{"A"}, "Y"); + addPattern("$_MUX_", ArrayRef{"A", "B", "S"}, "Y"); + addPattern>( + "$_SDFF_PP0_", ArrayRef{"C", "D", "R"}, "Q"); + addPattern>( + "$_SDFF_PP1_", ArrayRef{"C", "D", "R"}, "Q"); + addPattern("$_DFF_P_", ArrayRef{"C", "D"}, + "Q"); +} + +void circt::rtlil::registerRTLILImport() { + static mlir::TranslateToMLIRRegistration fromRTLIL( + "import-rtlil", "import RTLIL", + [](llvm::SourceMgr &sourceMgr, MLIRContext *context) { + context->loadDialect(); + + OwningOpRef module( + ModuleOp::create(UnknownLoc::get(context))); + if (sourceMgr.getNumBuffers() != 1) { + module = {}; + return module; + } + std::error_code ec; + SmallString<128> fileName; + if ((ec = llvm::sys::fs::createTemporaryFile("yosys", "rtlil", + fileName))) { + module->emitError() << ec.message(); + module = {}; + return module; + } + + StringRef ref = + sourceMgr.getMemoryBuffer(sourceMgr.getMainFileID())->getBuffer(); + std::ofstream myfile(fileName.c_str()); + myfile.write(ref.data(), ref.size()); + myfile.close(); + + init_yosys(false); + + auto design = std::make_unique(); + std::string cmd = "read_rtlil "; + cmd += fileName; + Yosys::run_pass(cmd, design.get()); + + if (failed(importRTLILDesign(design.get(), module.get()))) { + module->emitError() << "import failed"; + module = {}; + } + return module; + }); +} \ No newline at end of file diff --git a/lib/Conversion/ExportYosys/README.md b/lib/Conversion/ExportYosys/README.md new file mode 100644 index 000000000000..38d6c8b5f694 --- /dev/null +++ b/lib/Conversion/ExportYosys/README.md @@ -0,0 +1,53 @@ +# Yosys Integration + +Yosys is de-facto standard as an opensource RTL synthesis and verification tool. +The purpose of the yosys integration is to serve as a baseline for circt-based synthesis flow. + +## Build +Yosys integration is disabled by default. To enable Yosys integration, you need to build CIRCT with `-DCIRCT_YOSYS_INTEGRATION_ENABLED=ON`. + +## Tranlation between RTLIL + +`circt-translate` provides subcommand for translation between CIRCT IR and RTLIL. + +* CIRCT IR -> RTLIL + +```bash +circt-tranlsate --export-rtlil +``` +Currently only subest of core dialects are translated during translation to RTLIL. + +* RTLIL -> CIRCT IR + +```bash +circt-tranlsate --import-rtlil +``` + +## Run Yosys passes on CIRCT IR + +To run Yosys passes on CIRCT there are two passes `yosys-optimizer` and `yosys-optimizer-parallel`. + +```bash +# Run synth (canonicalizer is currently required to get "clean" core dialect operations) +circt-opt -pass-pipeline='builtin.module(yosys-optimizer{passes=synth},canonicalize)' + +# Pipe Yosys log to the stderr via `redirect-log=true`. +circt-opt -pass-pipeline='builtin.module(yosys-optimizer{passes=synth redirect-log=true})' + +# Run synth_xilinx (symbol-dce is recommended to remove unused external modules) +circt-opt -pass-pipeline='builtin.module(yosys-optimizer{passes=synth_xilinx},canonicalize,symbol-dce)' + +# Run multiple yosys pass (comma separated). +circt-opt -pass-pipeline='builtin.module(yosys-optimizer{passes=write_rtlil,write_verilog,synth},canonicalize)' + +# Emit Verilog using yosys backend. It's recommended to run `opt` for the fair comparison +circt-opt -pass-pipeline='builtin.module(yosys-optimizer{passes=opt,write_verilog})' +``` + +### CIRCT as a parallel Yosys driver + +Yosys has a globally context which is a not thread-safe so we cannot parallelly run `yosys-optimizer` on each HW module. As a workaround CIRCT provides a `yosys-optimizer-parallel` pass that parallelly invokes yosys in child processes. `yosys-optimizer-parallel` cannot be used for transformation that requires module hierarchly (e.g. inlining/flattening etc) + +## Testing + +Testing Yosys integration is tricky since RTLIL textual format could differ between Yosys versions. Currently we test the correctness of RTLIL translation by running LEC on the CIRCT IR after import with the original CIRCT IR after export. diff --git a/lib/Conversion/ExportYosys/RTLILConverterInternal.h b/lib/Conversion/ExportYosys/RTLILConverterInternal.h new file mode 100644 index 000000000000..bfff21f2b9df --- /dev/null +++ b/lib/Conversion/ExportYosys/RTLILConverterInternal.h @@ -0,0 +1,43 @@ +#ifndef CIRCT_CONVERSION_EXPORTYOSYS_RTLILCONVERTERINTERNAL +#define CIRCT_CONVERSION_EXPORTYOSYS_RTLILCONVERTERINTERNAL + +#include "circt/Support/LLVM.h" +#include "mlir/Support/LogicalResult.h" +#include + +namespace Yosys::RTLIL { +struct Design; +struct Module; +} // namespace Yosys::RTLIL + +namespace mlir { +class ModuleOp; +} + +namespace circt { +namespace hw { +class HWModuleLike; +class InstanceGraph; +} // namespace hw +namespace rtlil { + +mlir::FailureOr> +exportRTLILDesign(ArrayRef modules, + ArrayRef blackBox, + hw::InstanceGraph &instanceGraph); + +mlir::FailureOr> + exportRTLILDesign(mlir::ModuleOp); + +mlir::LogicalResult importRTLILDesign(Yosys::RTLIL::Design *design, + mlir::ModuleOp module); + +std::string getEscapedName(llvm::StringRef name); +void init_yosys(bool redirectLog = true); + +void registerRTLILImport(); +void registerRTLILExport(); +} // namespace rtlil +} // namespace circt + +#endif diff --git a/lib/Conversion/ExportYosys/YosysOptimizer.cpp b/lib/Conversion/ExportYosys/YosysOptimizer.cpp new file mode 100644 index 000000000000..e002898f3543 --- /dev/null +++ b/lib/Conversion/ExportYosys/YosysOptimizer.cpp @@ -0,0 +1,250 @@ +//===- LowerFirMem.cpp - Seq FIRRTL memory lowering -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This transform translate Seq FirMem ops to instances of HW generated modules. +// +//===----------------------------------------------------------------------===// +#include "RTLILConverterInternal.h" +#include "circt/Conversion/ExportRTLIL.h" +#include "circt/Dialect/Comb/CombVisitors.h" +#include "circt/Dialect/HW/HWInstanceGraph.h" +#include "circt/Dialect/HW/HWVisitors.h" +#include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/Seq/SeqVisitor.h" +#include "circt/Support/BackedgeBuilder.h" +#include "circt/Support/Namespace.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/Threading.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Support/FileUtilities.h" +#include "llvm/ADT/FunctionExtras.h" +#include "llvm/ADT/MapVector.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Mutex.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/ToolOutputFile.h" + +// Yosys headers. +#include "backends/rtlil/rtlil_backend.h" +#include "kernel/rtlil.h" +#include "kernel/yosys.h" + +#define DEBUG_TYPE "yosys-optimizer" + +using namespace circt; +using namespace hw; +using namespace comb; +using namespace Yosys; +using namespace rtlil; + +namespace { +#define GEN_PASS_DECL_YOSYSOPTIMIZER +#define GEN_PASS_DECL_YOSYSOPTIMIZERPARALLEL +#define GEN_PASS_DEF_YOSYSOPTIMIZER +#define GEN_PASS_DEF_YOSYSOPTIMIZERPARALLEL +#include "circt/Conversion/Passes.h.inc" + +struct YosysOptimizerPass + : public impl::YosysOptimizerBase { + void runOnOperation() override; +}; +struct YosysOptimizerParallelPass + : public impl::YosysOptimizerParallelBase { + void runOnOperation() override; +}; + +} // namespace + +void YosysOptimizerPass::runOnOperation() { + init_yosys(redirectLog.getValue()); + auto theDesign = circt::rtlil::exportRTLILDesign(getOperation()); + if (failed(theDesign)) + return signalPassFailure(); + + auto *design = theDesign->get(); + + if (!topModule.empty()) { + std::string command = "hierarchy -top "; + command += topModule; + Yosys::run_pass(command, design); + } + + for (auto pass : passes) + Yosys::run_pass(pass, design); + + // Erase all ops before importing them. + // TODO: We should preserve unrelated parts. + while (getOperation().begin() != getOperation().end()) + getOperation().begin()->erase(); + + auto result = circt::rtlil::importRTLILDesign(design, getOperation()); + if (failed(result)) + return signalPassFailure(); +} + +static llvm::DenseSet designSet(InstanceGraph &instanceGraph, + StringAttr dut) { + auto dutModule = instanceGraph.lookup(dut); + if (!dutModule) + return {}; + SmallVector worklist{dutModule}; + DenseSet visited; + while (!worklist.empty()) { + auto *mod = worklist.pop_back_val(); + if (!mod) + continue; + if (!visited.insert(mod->getModule().getModuleNameAttr()).second) + continue; + for (auto inst : *mod) { + if (!inst) + continue; + + igraph::InstanceOpInterface t = inst->getInstance(); + assert(t); + if (t->hasAttr("doNotPrint") || t->getNumResults() == 0) + continue; + worklist.push_back(inst->getTarget()); + } + } + return visited; +} + +LogicalResult runYosys(Location loc, StringRef inputFilePath, + std::string command) { + auto yosysPath = llvm::sys::findProgramByName("yosys"); + if (!yosysPath) { + return mlir::emitError(loc) << "cannot find 'yosys' executable. Please add " + "yosys to PATH. Error message='" + << yosysPath.getError().message() << "'"; + } + StringRef commands[] = {"-q", "-p", command, "-f", "rtlil", inputFilePath}; + auto exitCode = llvm::sys::ExecuteAndWait(yosysPath.get(), commands); + return success(exitCode == 0); +} + +void YosysOptimizerParallelPass::runOnOperation() { + // Set up yosys. + init_yosys(false); + auto &theInstanceGraph = getAnalysis(); + auto &table = getAnalysis(); + + if (topModule.empty()) { + getOperation().emitError() << "top module must be specified"; + return signalPassFailure(); + } + auto dut = StringAttr::get(&getContext(), topModule.getValue()); + DenseSet designs; + if (table.lookup(dut)) + designs = designSet(theInstanceGraph, dut); + auto isInDesign = [&](StringAttr mod) -> bool { + if (designs.empty()) + return true; + return designs.count(mod); + }; + SmallVector> results; + for (auto op : getOperation().getOps()) { + auto *node = theInstanceGraph.lookup(op.getModuleNameAttr()); + if (!isInDesign(op.getModuleNameAttr())) + continue; + + SmallVector blackboxes; + for (auto instance : *node) { + auto mod = instance->getTarget()->getModule(); + blackboxes.push_back(mod); + } + + auto theDesign = + circt::rtlil::exportRTLILDesign({op}, blackboxes, theInstanceGraph); + if (failed(theDesign)) + return signalPassFailure(); + + auto *design = theDesign->get(); + + std::error_code ec; + SmallString<128> fileName; + if ((ec = llvm::sys::fs::createTemporaryFile("yosys", "rtlil", fileName))) { + op.emitError() << ec.message(); + return signalPassFailure(); + } + + results.emplace_back(op, fileName); + + { + std::ofstream myfile(fileName.c_str()); + RTLIL_BACKEND::dump_design(myfile, design, false); + myfile.close(); + } + } + llvm::sys::SmartMutex mutex; + auto logger = [&mutex](auto callback) { + llvm::sys::SmartScopedLock lock(mutex); + callback(); + }; + std::string commands; + bool first = true; + for (const auto &i : passes) { + if (first) + first = false; + else + commands += "; "; + + commands += i; + } + if (failed(mlir::failableParallelForEachN( + &getContext(), 0, results.size(), [&](auto i) { + auto op = results[i].first; + auto test = results[i].second; + logger([&] { + llvm::errs() << "[yosys-optimizer] Running [" << i + 1 << "/" + << results.size() << "] " << op.getModuleName() + << "\n"; + }); + + auto result = runYosys(op.getLoc(), test, commands); + + logger([&] { + llvm::errs() << "[yosys-optimizer] Finished [" << i + 1 << "/" + << results.size() << "] " << op.getModuleName() + << "\n"; + }); + + // Remove temporary rtlil if success. + if (succeeded(result)) + llvm::sys::fs::remove(test); + else + op.emitError() << "Found error in yosys" + << "\n"; + + // TODO: Get the result and update the IR. + return result; + }))) + return signalPassFailure(); + + emitError(getOperation().getLoc()) + << "Currently `yosys-optimizer-parallel` doesn't update the result IR."; + return signalPassFailure(); +} + +//===----------------------------------------------------------------------===// +// Pass Infrastructure +//===----------------------------------------------------------------------===// + +std::unique_ptr circt::createYosysOptimizer() { + return std::make_unique(); +} + +std::unique_ptr circt::createYosysOptimizerParallel() { + return std::make_unique(); +} + +/// Entry point as an MLIR translation. +void circt::registerRTLILTranslation() { + rtlil::registerRTLILExport(); + rtlil::registerRTLILImport(); +} diff --git a/lib/Dialect/HW/HWInstanceImplementation.cpp b/lib/Dialect/HW/HWInstanceImplementation.cpp index 1677d9e8edb2..57371f4e493b 100644 --- a/lib/Dialect/HW/HWInstanceImplementation.cpp +++ b/lib/Dialect/HW/HWInstanceImplementation.cpp @@ -150,6 +150,9 @@ LogicalResult instance_like_impl::verifyParameters(ArrayAttr parameters, ArrayAttr moduleParameters, const EmitErrorFn &emitError) { + // TODO: This is a workaround for the issue that an instance doesn't specify a + // default parameter. + return success(); // Check parameters match up. auto numParameters = parameters.size(); if (numParameters != moduleParameters.size()) { diff --git a/test/lit.site.cfg.py.in b/test/lit.site.cfg.py.in index 3eb9be6b7a36..92a94a40cc90 100644 --- a/test/lit.site.cfg.py.in +++ b/test/lit.site.cfg.py.in @@ -39,6 +39,7 @@ config.verilator_path = "@VERILATOR_PATH@" config.zlib = "@HAVE_ZLIB@" config.scheduling_or_tools = "@SCHEDULING_OR_TOOLS@" config.slang_frontend_enabled = @CIRCT_SLANG_FRONTEND_ENABLED@ +config.yosys_integration_enabled = @CIRCT_YOSYS_INTEGRATION_ENABLED@ # Support substitution of the tools_dir with user parameters. This is # used when we can't determine the tool dir at configuration time. diff --git a/tools/circt-opt/CMakeLists.txt b/tools/circt-opt/CMakeLists.txt index b933045537bd..e8fb7902095e 100644 --- a/tools/circt-opt/CMakeLists.txt +++ b/tools/circt-opt/CMakeLists.txt @@ -23,6 +23,7 @@ target_link_libraries(circt-opt CIRCTBMCTransforms CIRCTExportChiselInterface CIRCTExportVerilog + CIRCTExportYosys CIRCTLECTransforms CIRCTTransforms diff --git a/tools/circt-translate/CMakeLists.txt b/tools/circt-translate/CMakeLists.txt index 322e6373396d..35d59a72237e 100644 --- a/tools/circt-translate/CMakeLists.txt +++ b/tools/circt-translate/CMakeLists.txt @@ -5,6 +5,10 @@ if(CIRCT_SLANG_FRONTEND_ENABLED) add_compile_definitions(CIRCT_SLANG_FRONTEND_ENABLED) endif() +if(CIRCT_YOSYS_INTEGRATION_ENABLED) + add_compile_definitions(CIRCT_YOSYS_INTEGRATION_ENABLED) +endif() + set(LLVM_LINK_COMPONENTS Support ) diff --git a/tools/circt-translate/circt-translate.cpp b/tools/circt-translate/circt-translate.cpp index c7daf06e3c8d..eb24251641d5 100644 --- a/tools/circt-translate/circt-translate.cpp +++ b/tools/circt-translate/circt-translate.cpp @@ -22,6 +22,10 @@ #include "circt/Conversion/ImportVerilog.h" #endif +#ifdef CIRCT_YOSYS_INTEGRATION_ENABLED +#include "circt/Conversion/ExportRTLIL.h" +#endif + int main(int argc, char **argv) { // Set the bug report message to indicate users should file issues on // llvm/circt and not llvm/llvm-project. @@ -32,6 +36,10 @@ int main(int argc, char **argv) { circt::registerFromVerilogTranslation(); #endif +#ifdef CIRCT_YOSYS_INTEGRATION_ENABLED + circt::registerRTLILTranslation(); +#endif + return mlir::failed( mlir::mlirTranslateMain(argc, argv, "CIRCT Translation Testing Tool")); } diff --git a/tools/firtool/CMakeLists.txt b/tools/firtool/CMakeLists.txt index 6d1650537aee..7bf044404831 100644 --- a/tools/firtool/CMakeLists.txt +++ b/tools/firtool/CMakeLists.txt @@ -13,6 +13,7 @@ target_link_libraries(firtool PRIVATE CIRCTExportVerilog CIRCTImportFIRFile CIRCTFIRRTLTransforms + CIRCTExportYosys CIRCTHWTransforms CIRCTOMTransforms CIRCTVerifTransforms