mirror of https://github.com/llvm/circt.git
[ESI] Wrap modules exposing latency insensitive signals to expose ESI channels (#828)
Add two functions which are intended to be exposed over an API: - A function to heuristically locate signal port triplets with ready valid on an RTL module. - A function which takes an RTL module and a list of those port triplets and build a 'shell' around that module which 'uplifts' the port triplets to ESI channels. Adds an 'esi-tester' binary to execute these two functions.
This commit is contained in:
parent
4fdba7023e
commit
31d19839f0
|
@ -19,8 +19,11 @@
|
|||
#ifndef CIRCT_DIALECT_ESI_ESIDIALECT_H
|
||||
#define CIRCT_DIALECT_ESI_ESIDIALECT_H
|
||||
|
||||
#include "circt/Dialect/RTL/RTLOps.h"
|
||||
#include "circt/Support/LLVM.h"
|
||||
#include "mlir/IR/BuiltinAttributes.h"
|
||||
#include "mlir/IR/Dialect.h"
|
||||
|
||||
namespace circt {
|
||||
namespace esi {
|
||||
|
||||
|
@ -46,6 +49,23 @@ private:
|
|||
void registerESIPasses();
|
||||
void registerESITranslations();
|
||||
|
||||
/// A triple of signals which represent a latency insensitive interface with
|
||||
/// valid/ready semantics.
|
||||
struct ESIPortValidReadyMapping {
|
||||
rtl::ModulePortInfo data, valid, ready;
|
||||
};
|
||||
|
||||
/// Find all the port triples on a module which fit the
|
||||
/// <name>/<name>_valid/<name>_ready pattern. Ready must be the opposite
|
||||
/// direction of the other two.
|
||||
void findValidReadySignals(Operation *modOp,
|
||||
SmallVectorImpl<ESIPortValidReadyMapping> &names);
|
||||
|
||||
/// Build an ESI module wrapper, converting the wires with latency-insensitive
|
||||
/// semantics to ESI channels and passing through the rest.
|
||||
Operation *buildESIWrapper(OpBuilder &b, Operation *mod,
|
||||
ArrayRef<ESIPortValidReadyMapping> esiPortNames);
|
||||
|
||||
} // namespace esi
|
||||
} // namespace circt
|
||||
|
||||
|
|
|
@ -13,12 +13,19 @@
|
|||
#include "circt/Dialect/ESI/ESIDialect.h"
|
||||
#include "circt/Dialect/ESI/ESIOps.h"
|
||||
#include "circt/Dialect/ESI/ESITypes.h"
|
||||
#include "circt/Dialect/RTL/RTLOps.h"
|
||||
#include "circt/Support/BackedgeBuilder.h"
|
||||
#include "circt/Support/ImplicitLocOpBuilder.h"
|
||||
#include "circt/Support/LLVM.h"
|
||||
#include "mlir/IR/Builders.h"
|
||||
#include "mlir/IR/DialectImplementation.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "llvm/ADT/StringSet.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
|
||||
namespace circt {
|
||||
namespace esi {
|
||||
using namespace circt;
|
||||
using namespace circt::esi;
|
||||
|
||||
ESIDialect::ESIDialect(MLIRContext *context)
|
||||
: Dialect("esi", context, TypeID::get<ESIDialect>()) {
|
||||
|
@ -30,7 +37,238 @@ ESIDialect::ESIDialect(MLIRContext *context)
|
|||
#include "circt/Dialect/ESI/ESI.cpp.inc"
|
||||
>();
|
||||
}
|
||||
} // namespace esi
|
||||
} // namespace circt
|
||||
|
||||
/// Find all the port triples on a module which fit the
|
||||
/// <name>/<name>_valid/<name>_ready pattern. Ready must be the opposite
|
||||
/// direction of the other two.
|
||||
void circt::esi::findValidReadySignals(
|
||||
Operation *modOp, SmallVectorImpl<ESIPortValidReadyMapping> &names) {
|
||||
SmallVector<rtl::ModulePortInfo, 64> ports;
|
||||
rtl::getModulePortInfo(modOp, ports);
|
||||
|
||||
llvm::StringMap<rtl::ModulePortInfo> nameMap(ports.size());
|
||||
for (auto port : ports)
|
||||
nameMap[port.getName()] = port;
|
||||
|
||||
for (auto port : ports) {
|
||||
if (port.direction == rtl::PortDirection::INOUT)
|
||||
continue;
|
||||
|
||||
StringRef portDataName = port.getName();
|
||||
if (portDataName.endswith("_data")) // Detect both `foo` and `foo_data`.
|
||||
portDataName = portDataName.substr(0, portDataName.size() - 5);
|
||||
|
||||
// Try to find a corresponding 'valid' port.
|
||||
SmallString<64> portName = portDataName;
|
||||
portName.append("_valid");
|
||||
auto valid = nameMap.find(portName);
|
||||
if (valid == nameMap.end() || valid->second.direction != port.direction ||
|
||||
!valid->second.type.isSignlessInteger(1))
|
||||
continue;
|
||||
|
||||
// Try to find a corresponding 'ready' port.
|
||||
portName = portDataName;
|
||||
portName.append("_ready");
|
||||
rtl::PortDirection readyDir = port.direction == rtl::PortDirection::INPUT
|
||||
? rtl::PortDirection::OUTPUT
|
||||
: rtl::PortDirection::INPUT;
|
||||
auto ready = nameMap.find(portName);
|
||||
if (ready == nameMap.end() || ready->second.direction != readyDir ||
|
||||
!valid->second.type.isSignlessInteger(1))
|
||||
continue;
|
||||
|
||||
// Found one.
|
||||
names.push_back(ESIPortValidReadyMapping{
|
||||
.data = port, .valid = valid->second, .ready = ready->second});
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an ESI module wrapper, converting the wires with latency-insensitive
|
||||
/// semantics to ESI channels and passing through the rest.
|
||||
Operation *
|
||||
circt::esi::buildESIWrapper(OpBuilder &b, Operation *pearl,
|
||||
ArrayRef<ESIPortValidReadyMapping> portsToConvert) {
|
||||
// In order to avoid the similar sounding and looking "wrapped" and "wrapper"
|
||||
// names or the ambiguous "module", we use "pearl" for the module _being
|
||||
// wrapped_ and "shell" for the _wrapper_ modules which is being created
|
||||
// (terms typically used in latency insensitive design papers).
|
||||
|
||||
auto *ctxt = b.getContext();
|
||||
Location loc = pearl->getLoc();
|
||||
FunctionType modType = rtl::getModuleType(pearl);
|
||||
|
||||
SmallVector<rtl::ModulePortInfo, 64> pearlPorts;
|
||||
rtl::getModulePortInfo(pearl, pearlPorts);
|
||||
|
||||
// -----
|
||||
// First, build up a set of data structures to use throughout this function.
|
||||
|
||||
StringSet<> controlPorts; // Memoize the list of ready/valid ports to ignore.
|
||||
llvm::StringMap<ESIPortValidReadyMapping>
|
||||
dataPortMap; // Store a lookup table of ports to convert indexed on the
|
||||
// data port name.
|
||||
// Validate input and assemble lookup structures.
|
||||
for (const auto &esiPort : portsToConvert) {
|
||||
if (esiPort.data.direction == rtl::PortDirection::INOUT) {
|
||||
pearl->emitError("Data signal '")
|
||||
<< esiPort.data.name << "' must not be INOUT";
|
||||
return nullptr;
|
||||
}
|
||||
dataPortMap[esiPort.data.name.getValue()] = esiPort;
|
||||
|
||||
if (esiPort.valid.direction != esiPort.data.direction) {
|
||||
pearl->emitError("Valid port '")
|
||||
<< esiPort.valid.name << "' direction must match data port.";
|
||||
return nullptr;
|
||||
}
|
||||
if (esiPort.valid.type != b.getI1Type()) {
|
||||
pearl->emitError("Valid signal '")
|
||||
<< esiPort.valid.name << "' must be i1 type";
|
||||
return nullptr;
|
||||
}
|
||||
controlPorts.insert(esiPort.valid.name.getValue());
|
||||
|
||||
if (esiPort.ready.direction != (esiPort.data.isOutput()
|
||||
? rtl::PortDirection::INPUT
|
||||
: rtl::PortDirection::OUTPUT)) {
|
||||
pearl->emitError("Ready port '")
|
||||
<< esiPort.ready.name
|
||||
<< "' must be opposite direction to data signal.";
|
||||
return nullptr;
|
||||
}
|
||||
if (esiPort.ready.type != b.getI1Type()) {
|
||||
pearl->emitError("Ready signal '")
|
||||
<< esiPort.ready.name << "' must be i1 type";
|
||||
return nullptr;
|
||||
}
|
||||
controlPorts.insert(esiPort.ready.name.getValue());
|
||||
}
|
||||
|
||||
// -----
|
||||
// Second, build a list of ports for the shell module, skipping the
|
||||
// valid/ready, and converting the ESI data ports to the ESI channel port
|
||||
// type. Store some bookkeeping information.
|
||||
|
||||
SmallVector<rtl::ModulePortInfo, 64> shellPorts;
|
||||
// Map the shell operand to the pearl port.
|
||||
SmallVector<rtl::ModulePortInfo, 64> inputPortMap;
|
||||
// Map the shell result to the pearl port.
|
||||
SmallVector<rtl::ModulePortInfo, 64> outputPortMap;
|
||||
|
||||
for (const auto port : pearlPorts) {
|
||||
if (controlPorts.contains(port.name.getValue()))
|
||||
continue;
|
||||
|
||||
rtl::ModulePortInfo newPort = port;
|
||||
if (dataPortMap.find(port.name.getValue()) != dataPortMap.end())
|
||||
newPort.type = esi::ChannelPort::get(ctxt, port.type);
|
||||
|
||||
if (port.isOutput()) {
|
||||
newPort.argNum = outputPortMap.size();
|
||||
outputPortMap.push_back(port);
|
||||
} else {
|
||||
newPort.argNum = inputPortMap.size();
|
||||
inputPortMap.push_back(port);
|
||||
}
|
||||
shellPorts.push_back(newPort);
|
||||
}
|
||||
|
||||
// -----
|
||||
// Third, create the shell module and also some builders for the inside.
|
||||
|
||||
SmallString<64> shellNameBuf;
|
||||
StringAttr shellName = b.getStringAttr(
|
||||
(SymbolTable::getSymbolName(pearl) + "_esi").toStringRef(shellNameBuf));
|
||||
auto shell = b.create<rtl::RTLModuleOp>(loc, shellName, shellPorts);
|
||||
shell.getBodyBlock()->clear(); // Erase the terminator.
|
||||
auto modBuilder =
|
||||
ImplicitLocOpBuilder::atBlockBegin(loc, shell.getBodyBlock());
|
||||
BackedgeBuilder bb(modBuilder, modBuilder.getLoc());
|
||||
|
||||
// Hold the operands for `rtl.output` here.
|
||||
SmallVector<Value, 64> outputs(shell.getNumResults());
|
||||
|
||||
// -----
|
||||
// Fourth, assemble the inputs for the pearl module AND build all the ESI wrap
|
||||
// and unwrap ops for both the input and output channels.
|
||||
|
||||
SmallVector<Value, 64> pearlOperands(modType.getNumInputs());
|
||||
|
||||
// Since we build all the ESI wrap and unwrap operations before pearl
|
||||
// instantiation, we only need backedges from the pearl instance result. Index
|
||||
// the backedges by the pearl modules result number.
|
||||
llvm::DenseMap<size_t, Backedge> backedges;
|
||||
|
||||
// Go through the shell input ports, either tunneling them through or
|
||||
// unwrapping the ESI channels. We'll need backedges for the ready signals
|
||||
// since they are results from the upcoming pearl instance.
|
||||
for (const auto port : shellPorts) {
|
||||
if (port.isOutput())
|
||||
continue;
|
||||
|
||||
Value arg = shell.getArgument(port.argNum);
|
||||
auto esiPort = dataPortMap.find(port.name.getValue());
|
||||
if (esiPort == dataPortMap.end()) {
|
||||
// If it's just a regular port, it just gets passed through.
|
||||
size_t pearlOpNum = inputPortMap[port.argNum].argNum;
|
||||
pearlOperands[pearlOpNum] = arg;
|
||||
continue;
|
||||
}
|
||||
|
||||
Backedge ready = bb.get(modBuilder.getI1Type());
|
||||
backedges.insert(std::make_pair(esiPort->second.ready.argNum, ready));
|
||||
auto unwrap = modBuilder.create<UnwrapValidReady>(arg, ready);
|
||||
pearlOperands[esiPort->second.data.argNum] = unwrap.rawOutput();
|
||||
pearlOperands[esiPort->second.valid.argNum] = unwrap.valid();
|
||||
}
|
||||
|
||||
// Iterate through the shell output ports, identify the ESI channels, and
|
||||
// build ESI wrapper ops for signals being output from the pearl. The data and
|
||||
// valid for these wrap ops will need to be backedges.
|
||||
for (const auto port : shellPorts) {
|
||||
if (!port.isOutput())
|
||||
continue;
|
||||
auto esiPort = dataPortMap.find(port.name.getValue());
|
||||
if (esiPort == dataPortMap.end())
|
||||
continue;
|
||||
|
||||
Backedge data = bb.get(esiPort->second.data.type);
|
||||
Backedge valid = bb.get(modBuilder.getI1Type());
|
||||
auto wrap = modBuilder.create<WrapValidReady>(data, valid);
|
||||
backedges.insert(std::make_pair(esiPort->second.data.argNum, data));
|
||||
backedges.insert(std::make_pair(esiPort->second.valid.argNum, valid));
|
||||
outputs[port.argNum] = wrap.chanOutput();
|
||||
pearlOperands[esiPort->second.ready.argNum] = wrap.ready();
|
||||
}
|
||||
|
||||
// -----
|
||||
// Fifth, instantiate the pearl module.
|
||||
|
||||
auto pearlInst = modBuilder.create<rtl::InstanceOp>(
|
||||
modType.getResults(), "pearl", SymbolTable::getSymbolName(pearl),
|
||||
pearlOperands, DictionaryAttr());
|
||||
|
||||
// Hookup all the backedges.
|
||||
for (size_t i = 0, e = pearlInst.getNumResults(); i < e; ++i) {
|
||||
auto backedge = backedges.find(i);
|
||||
if (backedge != backedges.end())
|
||||
backedge->second.setValue(pearlInst.getResult(i));
|
||||
}
|
||||
|
||||
// -----
|
||||
// Finally, find all the regular outputs and either tunnel them through.
|
||||
for (const auto port : shellPorts) {
|
||||
if (!port.isOutput())
|
||||
continue;
|
||||
auto esiPort = dataPortMap.find(port.name.getValue());
|
||||
if (esiPort != dataPortMap.end())
|
||||
continue;
|
||||
size_t pearlResNum = outputPortMap[port.argNum].argNum;
|
||||
outputs[port.argNum] = pearlInst.getResult(pearlResNum);
|
||||
}
|
||||
|
||||
modBuilder.create<rtl::OutputOp>(outputs);
|
||||
return shell;
|
||||
}
|
||||
|
||||
#include "circt/Dialect/ESI/ESIAttrs.cpp.inc"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
add_subdirectory(CAPI)
|
||||
add_subdirectory(Dialect)
|
||||
|
||||
configure_lit_site_cfg(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
|
||||
|
@ -12,6 +13,7 @@ set(CIRCT_TEST_DEPENDS
|
|||
circt-capi-ir-test
|
||||
circt-opt
|
||||
circt-translate
|
||||
esi-tester
|
||||
handshake-runner
|
||||
firtool
|
||||
llhd-sim
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
##===- CMakeLists.txt - ---------------------------------------*- cmake -*-===//
|
||||
##
|
||||
## 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
|
||||
##
|
||||
##===----------------------------------------------------------------------===//
|
||||
|
||||
add_subdirectory(ESI)
|
|
@ -0,0 +1,30 @@
|
|||
##===- CMakeLists.txt - ---------------------------------------*- cmake -*-===//
|
||||
##
|
||||
## 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
|
||||
##
|
||||
##===----------------------------------------------------------------------===//
|
||||
|
||||
set(LLVM_LINK_COMPONENTS
|
||||
Support
|
||||
)
|
||||
|
||||
add_llvm_tool(esi-tester
|
||||
esi-tester.cpp
|
||||
)
|
||||
llvm_update_compile_flags(esi-tester)
|
||||
target_link_libraries(esi-tester
|
||||
PRIVATE
|
||||
CIRCTESI
|
||||
CIRCTRTL
|
||||
CIRCTSV
|
||||
|
||||
MLIRParser
|
||||
MLIRSupport
|
||||
MLIRIR
|
||||
MLIROptLib
|
||||
MLIRStandard
|
||||
MLIRTransforms
|
||||
MLIRLLVMIR
|
||||
)
|
|
@ -0,0 +1,52 @@
|
|||
//===- esi-tester.cpp - The ESI test driver -------------------------------===//
|
||||
//
|
||||
// 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 program exercises some ESI functionality which is intended to be for API
|
||||
// use only.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "circt/InitAllDialects.h"
|
||||
#include "circt/InitAllPasses.h"
|
||||
#include "mlir/Dialect/StandardOps/IR/Ops.h"
|
||||
#include "mlir/Pass/Pass.h"
|
||||
#include "mlir/Support/MlirOptMain.h"
|
||||
#include "mlir/Transforms/Passes.h"
|
||||
|
||||
using namespace circt;
|
||||
using namespace circt::esi;
|
||||
|
||||
/// This is a test pass for verifying FuncOp's eraseResult method.
|
||||
struct TestESIModWrap
|
||||
: public mlir::PassWrapper<TestESIModWrap, OperationPass<mlir::ModuleOp>> {
|
||||
void runOnOperation() override {
|
||||
auto mlirMod = getOperation();
|
||||
auto b = mlir::OpBuilder::atBlockEnd(mlirMod.getBody());
|
||||
|
||||
SmallVector<rtl::RTLModuleOp, 8> mods;
|
||||
for (Operation *mod : mlirMod.getOps<rtl::RTLModuleExternOp>()) {
|
||||
SmallVector<ESIPortValidReadyMapping, 32> liPorts;
|
||||
findValidReadySignals(mod, liPorts);
|
||||
if (!liPorts.empty())
|
||||
if (!buildESIWrapper(b, mod, liPorts))
|
||||
signalPassFailure();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
mlir::DialectRegistry registry;
|
||||
registry.insert<comb::CombDialect, esi::ESIDialect, rtl::RTLDialect>();
|
||||
|
||||
mlir::PassRegistration<TestESIModWrap>(
|
||||
"test-mod-wrap", "Test the ESI find and wrap functionality");
|
||||
|
||||
return mlir::failed(
|
||||
mlir::MlirOptMain(argc, argv, "CIRCT modular optimizer driver", registry,
|
||||
/*preloadDialectsInContext=*/true));
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// RUN: esi-tester %s --test-mod-wrap | FileCheck %s
|
||||
|
||||
rtl.module.extern @OutputChannel(%clk: i1, %bar_ready: i1) -> (%bar: i42, %bar_valid: i1)
|
||||
|
||||
// CHECK-LABEL: rtl.module @OutputChannel_esi(%clk: i1) -> (%bar: !esi.channel<i42>) {
|
||||
// CHECK: %chanOutput, %ready = esi.wrap.vr %pearl.bar, %pearl.bar_valid : i42
|
||||
// CHECK: %pearl.bar, %pearl.bar_valid = rtl.instance "pearl" @OutputChannel(%clk, %ready) : (i1, i1) -> (i42, i1)
|
||||
// CHECK: rtl.output %chanOutput : !esi.channel<i42>
|
||||
|
||||
rtl.module.extern @InputChannel(%clk: i1, %foo_data: i23, %foo_valid: i1) -> (%foo_ready: i1, %rawOut: i99)
|
||||
|
||||
// CHECK-LABEL: rtl.module @InputChannel_esi(%clk: i1, %foo_data: !esi.channel<i23>) -> (%rawOut: i99) {
|
||||
// CHECK: %rawOutput, %valid = esi.unwrap.vr %foo_data, %pearl.foo_ready : i23
|
||||
// CHECK: %pearl.foo_ready, %pearl.rawOut = rtl.instance "pearl" @InputChannel(%clk, %rawOutput, %valid) : (i1, i23, i1) -> (i1, i99)
|
||||
// CHECK: rtl.output %pearl.rawOut : i99
|
||||
|
||||
rtl.module.extern @Mixed(%clk: i1, %foo: i23, %foo_valid: i1, %bar_ready: i1) ->
|
||||
(%bar: i42, %bar_valid: i1, %foo_ready: i1, %rawOut: i99)
|
||||
|
||||
// CHECK-LABEL: rtl.module @Mixed_esi(%clk: i1, %foo: !esi.channel<i23>) -> (%bar: !esi.channel<i42>, %rawOut: i99) {
|
||||
// CHECK: %rawOutput, %valid = esi.unwrap.vr %foo, %pearl.foo_ready : i23
|
||||
// CHECK: %chanOutput, %ready = esi.wrap.vr %pearl.bar, %pearl.bar_valid : i42
|
||||
// CHECK: %pearl.bar, %pearl.bar_valid, %pearl.foo_ready, %pearl.rawOut = rtl.instance "pearl" @Mixed(%clk, %rawOutput, %valid, %ready) : (i1, i23, i1, i1) -> (i42, i1, i1, i99)
|
||||
// CHECK: rtl.output %chanOutput, %pearl.rawOut : !esi.channel<i42>, i99
|
|
@ -61,6 +61,7 @@ tools = [
|
|||
'circt-opt',
|
||||
'circt-translate',
|
||||
'circt-capi-ir-test',
|
||||
'esi-tester',
|
||||
'llhd-sim'
|
||||
]
|
||||
|
||||
|
|
Loading…
Reference in New Issue