From 90088c2aa41377ba68917c34fe25e9d8cfcb4bd2 Mon Sep 17 00:00:00 2001 From: Andrew Lenharth Date: Sat, 4 Jan 2025 17:41:09 +0000 Subject: [PATCH] blif emission --- include/circt/Dialect/BLIF/BLIFEmitter.h | 30 +++ include/circt/InitAllTranslations.h | 2 + lib/Dialect/BLIF/CMakeLists.txt | 3 +- lib/Dialect/BLIF/Export/BLIFEmitter.cpp | 225 +++++++++++++++++++++++ lib/Dialect/BLIF/Export/CMakeLists.txt | 7 + 5 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 include/circt/Dialect/BLIF/BLIFEmitter.h create mode 100644 lib/Dialect/BLIF/Export/BLIFEmitter.cpp create mode 100644 lib/Dialect/BLIF/Export/CMakeLists.txt diff --git a/include/circt/Dialect/BLIF/BLIFEmitter.h b/include/circt/Dialect/BLIF/BLIFEmitter.h new file mode 100644 index 000000000000..a4ef6f1b61d3 --- /dev/null +++ b/include/circt/Dialect/BLIF/BLIFEmitter.h @@ -0,0 +1,30 @@ +//===- BLIFEmitter.h - BLIF dialect to .blif emitter ------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the interface to the .blif file emitter. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_BLIF_BLIFEMITTER_H +#define CIRCT_DIALECT_BLIF_BLIFEMITTER_H + +#include "circt/Support/LLVM.h" + +namespace circt { +namespace blif { + +mlir::LogicalResult exportBLIFFile(mlir::ModuleOp module, + llvm::raw_ostream &os); + +void registerToBLIFFileTranslation(); + +} // namespace blif +} // namespace circt + +#endif // CIRCT_DIALECT_BLIF_BLIFEMITTER_H diff --git a/include/circt/InitAllTranslations.h b/include/circt/InitAllTranslations.h index 1bf8b8db965f..4a96563d6d73 100644 --- a/include/circt/InitAllTranslations.h +++ b/include/circt/InitAllTranslations.h @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "circt/Dialect/Arc/ModelInfoExport.h" +#include "circt/Dialect/BLIF/BLIFEmitter.h" #include "circt/Dialect/BLIF/BLIFParser.h" #include "circt/Dialect/Calyx/CalyxEmitter.h" #include "circt/Dialect/ESI/ESIDialect.h" @@ -34,6 +35,7 @@ inline void registerAllTranslations() { static bool initOnce = []() { arc::registerArcModelInfoTranslation(); blif::registerFromBLIFFileTranslation(); + blif::registerToBLIFFileTranslation(); calyx::registerToCalyxTranslation(); firrtl::registerFromFIRFileTranslation(); firrtl::registerToFIRFileTranslation(); diff --git a/lib/Dialect/BLIF/CMakeLists.txt b/lib/Dialect/BLIF/CMakeLists.txt index 51e9d1c78cf3..bd4ed5bebc7a 100644 --- a/lib/Dialect/BLIF/CMakeLists.txt +++ b/lib/Dialect/BLIF/CMakeLists.txt @@ -23,4 +23,5 @@ add_dependencies(circt-headers CIRCTBLIFEnumsIncGen ) -add_subdirectory(Import) + add_subdirectory(Export) + add_subdirectory(Import) diff --git a/lib/Dialect/BLIF/Export/BLIFEmitter.cpp b/lib/Dialect/BLIF/Export/BLIFEmitter.cpp new file mode 100644 index 000000000000..010bb01242a7 --- /dev/null +++ b/lib/Dialect/BLIF/Export/BLIFEmitter.cpp @@ -0,0 +1,225 @@ +//===- BLIFEmitter.cpp - BLIF dialect to .blif emitter --------------------===// +// +// 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 implements a .blif file emitter. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/BLIF/BLIFEmitter.h" +#include "circt/Dialect/BLIF/BLIFDialect.h" +#include "circt/Dialect/BLIF/BLIFOps.h" +#include "circt/Dialect/HW/HWDialect.h" +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Tools/mlir-translate/Translation.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/ADT/TypeSwitch.h" +#include "llvm/Support/Debug.h" + +#define DEBUG_TYPE "export-blif" + +using namespace circt; +using namespace blif; + +//===----------------------------------------------------------------------===// +// Emitter +//===----------------------------------------------------------------------===// + +// NOLINTBEGIN(misc-no-recursion) +namespace { + +/// An emitter for FIRRTL dialect operations to .fir output. +struct Emitter { + Emitter(llvm::raw_ostream &os) : os(os) {} + LogicalResult finalize(); + + // Circuit/module emission + void emitModel(ModelOp op); + void emitModuleParameters(Operation *op, ArrayAttr parameters); + + // Statement emission + void emitCommand(LogicGateOp op); + void emitCommand(LatchGateOp op); + +private: + /// Emit an error and remark that emission failed. + InFlightDiagnostic emitError(Operation *op, const Twine &message) { + encounteredError = true; + return op->emitError(message); + } + + /// Emit an error and remark that emission failed. + InFlightDiagnostic emitOpError(Operation *op, const Twine &message) { + encounteredError = true; + return op->emitOpError(message); + } + +private: + /// Pretty printer. + llvm::raw_ostream &os; + + /// Whether we have encountered any errors during emission. + bool encounteredError = false; + + /// The names used to emit values already encountered. Anything that gets a + /// name in the output FIR is listed here, such that future expressions can + /// reference it. + DenseMap valueNames; + StringSet<> valueNamesStorage; + int nameCounter = 0; + + /// Return the name used during emission of a `Value`, or none if the value + /// has not yet been emitted or it was emitted inline. + StringRef lookupEmittedName(Value value) { + auto it = valueNames.find(value); + if (it != valueNames.end()) + return {it->second}; + std::string name = "v" + std::to_string(nameCounter++); + while (valueNamesStorage.count(name)) { + name = "v" + std::to_string(nameCounter++); + } + addValueName(value, name); + return valueNames.find(value)->second; + } + + void addValueName(Value value, StringAttr attr) { + valueNames.insert({value, attr.getValue()}); + } + void addValueName(Value value, StringRef str) { + auto it = valueNamesStorage.insert(str); + valueNames.insert({value, it.first->getKey()}); + } +}; +} // namespace + +LogicalResult Emitter::finalize() { return failure(encounteredError); } + +/// Emit an entire circuit. +void Emitter::emitModel(ModelOp op) { + os << ".model " << op.getSymName() << "\n"; + auto mType = op.getModuleType(); + auto clockMask = op.getClocks(); + + int inIdx = 0; + int outIdx = 0; + auto &body = op.getBody().front(); + auto term = body.getTerminator(); + for (auto [idx, port] : llvm::enumerate(mType.getPorts())) { + switch (port.dir) { + case hw::ModulePort::Direction::Input: + if (clockMask.extractBits(idx, 1).isZero()) + os << ".input " << port.name.getValue() << "\n"; + else + os << ".clock " << port.name.getValue() << "\n"; + addValueName(body.getArgument(inIdx++), port.name); + break; + case hw::ModulePort::Direction::Output: + os << ".output " << port.name.getValue() << "\n"; + addValueName(term->getOperand(outIdx++), port.name); + break; + case hw::ModulePort::Direction::InOut: + emitOpError(op, "inout ports are not expected"); + break; + } + } + + // Emit the model body. + for (auto &bodyOp : body) { + if (encounteredError) + return; + TypeSwitch(&bodyOp) + .Case([&](auto op) { emitCommand(op); }) + .Case([&](auto op) { emitCommand(op); }) + .Case([&](auto op) {}) + .Default([&](auto op) { + emitOpError(op, "not supported for emission inside model definition"); + }); + } + + os << ".end\n"; +} + +void Emitter::emitCommand(LogicGateOp op) { + os << ".names"; + for (auto input : op.getInputs()) + os << " " << lookupEmittedName(input); + os << " " << lookupEmittedName(op.getResult()) << "\n"; + + for (auto row : op.getFunc()) { + const char *names[] = {"0", "1", "-"}; + for (auto input : ArrayRef(row).drop_back()) + os << names[input]; + if (row.size() > 1) + os << " "; + os << names[row.back()] << "\n"; + } +} + +void Emitter::emitCommand(LatchGateOp op) { + os << ".latch"; + os << " " << lookupEmittedName(op.getInput()); + os << " " << lookupEmittedName(op.getOutput()); + bool emitClk = true; + switch (op.getMode()) { + case LatchModeEnum::Unspecified: + emitClk = false; + break; + case LatchModeEnum::FallingEdge: + os << " fe "; + break; + case LatchModeEnum::RisingEdge: + os << " re "; + break; + case LatchModeEnum::ActiveHigh: + os << " ah "; + break; + case LatchModeEnum::ActiveLow: + os << " al "; + break; + case LatchModeEnum::Asynchronous: + os << " as "; + break; + } + if (emitClk) { + if (op.getClock()) + os << lookupEmittedName(op.getClock()); + else + os << "NIL"; + } + if (op.getInitVal() != 3) + os << " " << op.getInitVal(); + os << "\n"; +} + +//===----------------------------------------------------------------------===// +// Driver +//===----------------------------------------------------------------------===// + +// Emit the specified BLIF circuit into the given output stream. +mlir::LogicalResult circt::blif::exportBLIFFile(mlir::ModuleOp module, + llvm::raw_ostream &os) { + Emitter emitter(os); + for (auto &op : *module.getBody()) { + if (auto circuitOp = dyn_cast(op)) + emitter.emitModel(circuitOp); + } + return emitter.finalize(); +} + +void circt::blif::registerToBLIFFileTranslation() { + static mlir::TranslateFromMLIRRegistration toBLIF( + "export-blif", "emit BLIF dialect operations to .blif output", + [](ModuleOp module, llvm::raw_ostream &os) { + return exportBLIFFile(module, os); + }, + [](mlir::DialectRegistry ®istry) { + registry.insert(); + registry.insert(); + }); +} diff --git a/lib/Dialect/BLIF/Export/CMakeLists.txt b/lib/Dialect/BLIF/Export/CMakeLists.txt new file mode 100644 index 000000000000..eb340d1626a5 --- /dev/null +++ b/lib/Dialect/BLIF/Export/CMakeLists.txt @@ -0,0 +1,7 @@ +add_circt_translation_library(CIRCTExportBLIF + BLIFEmitter.cpp + + LINK_LIBS PUBLIC + CIRCTBLIF + MLIRTranslateLib +)