22#include "mlir/Dialect/Arith/IR/Arith.h"
23#include "mlir/IR/PatternMatch.h"
24#include "mlir/Pass/Pass.h"
25#include "mlir/Rewrite/FrozenRewritePatternSet.h"
26#include "mlir/Transforms/DialectConversion.h"
27#include "llvm/Support/Debug.h"
31#define GEN_PASS_DEF_HANDSHAKELOWEREXTMEMTOHW
32#include "circt/Dialect/Handshake/HandshakePasses.h.inc"
40using NamedType = std::pair<StringAttr, Type>;
41struct HandshakeMemType {
42 llvm::SmallVector<NamedType> inputTypes, outputTypes;
43 MemRefType memRefType;
44 unsigned loadPorts, storePorts;
51 static LoadName
get(MLIRContext *ctx,
unsigned idx) {
52 return {StringAttr::get(ctx,
"ld" + std::to_string(idx) +
".data"),
53 StringAttr::get(ctx,
"ld" + std::to_string(idx) +
".addr")};
61 static StoreNames
get(MLIRContext *ctx,
unsigned idx) {
62 return {StringAttr::get(ctx,
"st" + std::to_string(idx) +
".done"),
63 StringAttr::get(ctx,
"st" + std::to_string(idx))};
70 assert(isa<IndexType>(t) &&
"Expected index type");
71 auto shape = memRef.getShape();
72 assert(shape.size() == 1 &&
"Expected 1D memref");
73 unsigned addrWidth = llvm::Log2_64_Ceil(shape[0]);
74 return IntegerType::get(t.getContext(), addrWidth);
78 auto *ctx = v.getContext();
79 assert(isa<mlir::MemRefType>(v.getType()) &&
"Value is not a memref type");
80 auto extmemOp = cast<handshake::ExternalMemoryOp>(*v.getUsers().begin());
81 HandshakeMemType memType;
82 llvm::SmallVector<hw::detail::FieldInfo> inFields, outFields;
85 memType.memRefType = cast<MemRefType>(v.getType());
86 memType.loadPorts = extmemOp.getLdCount();
87 memType.storePorts = extmemOp.getStCount();
90 for (
auto [i, ldif] : llvm::enumerate(extmemOp.getLoadPorts())) {
91 auto names = LoadName::get(ctx, i);
92 memType.inputTypes.push_back({names.dataIn, ldif.dataOut.getType()});
93 memType.outputTypes.push_back(
99 for (
auto [i, stif] : llvm::enumerate(extmemOp.getStorePorts())) {
100 auto names = StoreNames::get(ctx, i);
103 llvm::SmallVector<hw::StructType::FieldInfo> storeOutFields;
104 storeOutFields.push_back(
105 {StringAttr::get(ctx,
"address"),
107 storeOutFields.push_back(
108 {StringAttr::get(ctx,
"data"), stif.dataIn.getType()});
109 auto inType = hw::StructType::get(ctx, storeOutFields);
110 memType.outputTypes.push_back({names.out, inType});
111 memType.inputTypes.push_back({names.doneIn, stif.doneOut.getType()});
118struct HandshakeLowerExtmemToHWPass
119 :
public circt::handshake::impl::HandshakeLowerExtmemToHWBase<
120 HandshakeLowerExtmemToHWPass> {
122 HandshakeLowerExtmemToHWPass(std::optional<bool> createESIWrapper) {
123 if (createESIWrapper)
124 this->createESIWrapper = *createESIWrapper;
127 void runOnOperation()
override {
128 auto op = getOperation();
129 for (
auto func : op.getOps<handshake::FuncOp>()) {
130 if (failed(lowerExtmemToHW(func))) {
137 LogicalResult lowerExtmemToHW(handshake::FuncOp func);
139 wrapESI(handshake::FuncOp func, hw::ModulePortInfo origPorts,
140 const std::map<unsigned, HandshakeMemType> &argReplacements);
143LogicalResult HandshakeLowerExtmemToHWPass::wrapESI(
145 const std::map<unsigned, HandshakeMemType> &argReplacements) {
146 auto *ctx = func.getContext();
148 auto loc = func.getLoc();
152 b.setInsertionPoint(func);
153 auto newPortInfo = handshake::getPortInfoForOpTypes(
154 func, func.getArgumentTypes(), func.getResultTypes());
155 auto extMod = hw::HWModuleExternOp::create(
156 b, loc, StringAttr::get(ctx,
"__" + func.getName() +
"_hw"), newPortInfo);
160 func->setAttr(kPredeclarationAttr,
161 FlatSymbolRefAttr::get(ctx, extMod.getName()));
165 auto wrapperModPortInfo = origPorts;
166 llvm::SmallVector<unsigned> argReplacementsIdxs;
167 llvm::transform(argReplacements, std::back_inserter(argReplacementsIdxs),
168 [](
auto &pair) {
return pair.first; });
169 for (
auto i : llvm::reverse(argReplacementsIdxs))
171 auto wrapperMod = hw::HWModuleOp::create(
172 b, loc, StringAttr::get(ctx, func.getName() +
"_esi_wrapper"),
174 Value
clk = wrapperMod.getBodyBlock()->getArgument(
175 wrapperMod.getBodyBlock()->getNumArguments() - 2);
176 Value rst = wrapperMod.getBodyBlock()->getArgument(
177 wrapperMod.getBodyBlock()->getNumArguments() - 1);
178 SmallVector<Value> clkRes = {
clk, rst};
180 b.setInsertionPointToStart(wrapperMod.getBodyBlock());
181 BackedgeBuilder bb(b, loc);
185 llvm::SmallVector<Backedge> backedges;
186 for (
auto resType : extMod.getOutputTypes())
187 backedges.push_back(bb.get(resType));
195 llvm::SmallVector<llvm::SmallVector<Value>> instanceArgsForMem;
197 for (
auto [i, memType] : argReplacements) {
199 b.setInsertionPoint(wrapperMod);
202 auto origPortInfo = origPorts.
atInput(i);
203 auto memrefShape = memType.memRefType.getShape();
204 auto dataType = memType.memRefType.getElementType();
205 assert(memrefShape.size() == 1 &&
"Only 1D memrefs are supported");
206 unsigned memrefSize = memrefShape[0];
207 auto memServiceDecl = esi::RandomAccessMemoryDeclOp::create(
208 b, loc, origPortInfo.name, {},
209 TypeAttr::get(dataType), b.getI64IntegerAttr(memrefSize));
210 esi::ServicePortInfo writePortInfo = memServiceDecl.writePortInfo();
211 esi::ServicePortInfo readPortInfo = memServiceDecl.readPortInfo();
213 SmallVector<Value> instanceArgsFromThisMem;
217 b.setInsertionPointToStart(wrapperMod.getBodyBlock());
220 for (
unsigned i = 0; i < memType.loadPorts; ++i) {
221 auto req = esi::RequestConnectionOp::create(
222 b, loc, readPortInfo.
type, readPortInfo.
port,
223 esi::AppIDAttr::get(ctx, b.getStringAttr(
"load"), resIdx),
225 auto reqUnpack = esi::UnpackBundleOp::create(
226 b, loc, req.getToClient(), ValueRange{backedges[resIdx]});
227 instanceArgsFromThisMem.push_back(
228 reqUnpack.getToChannels()
229 [esi::RandomAccessMemoryDeclOp::RespDirChannelIdx]);
234 for (
unsigned i = 0; i < memType.storePorts; ++i) {
235 auto req = esi::RequestConnectionOp::create(
236 b, loc, writePortInfo.
type, writePortInfo.
port,
237 esi::AppIDAttr::get(ctx, b.getStringAttr(
"store"), resIdx),
239 auto reqUnpack = esi::UnpackBundleOp::create(
240 b, loc, req.getToClient(), ValueRange{backedges[resIdx]});
241 instanceArgsFromThisMem.push_back(
242 reqUnpack.getToChannels()
243 [esi::RandomAccessMemoryDeclOp::RespDirChannelIdx]);
247 instanceArgsForMem.emplace_back(std::move(instanceArgsFromThisMem));
252 llvm::SmallVector<Value> instanceArgs;
256 unsigned wrapperArgIdx = 0;
258 for (
unsigned i = 0, e = func.getNumArguments(); i < e; i++) {
261 if (argReplacements.count(i)) {
264 auto &memArgs = instanceArgsForMem.front();
265 instanceArgs.append(memArgs.begin(), memArgs.end());
266 instanceArgsForMem.erase(instanceArgsForMem.begin());
271 instanceArgs.push_back(
272 wrapperMod.getBodyBlock()->getArgument(wrapperArgIdx++));
278 for (; wrapperArgIdx < wrapperMod.getBodyBlock()->getNumArguments();
280 instanceArgs.push_back(
281 wrapperMod.getBodyBlock()->getArgument(wrapperArgIdx));
285 hw::InstanceOp::create(b, loc, extMod, func.getName(), instanceArgs);
288 for (
auto [res, be] : llvm::zip(instance.getResults(), backedges))
294 cast<hw::OutputOp>(wrapperMod.getBodyBlock()->getTerminator());
295 b.setInsertionPoint(outputOp);
296 hw::OutputOp::create(
297 b, outputOp.getLoc(),
298 instance.getResults().take_front(wrapperMod.getNumOutputPorts()));
308static Value truncateToMemoryWidth(Location loc, OpBuilder &b, Value v,
309 MemRefType memRefType) {
310 assert(isa<IndexType>(v.getType()) &&
"Expected an index-typed value");
311 auto addrWidth = llvm::Log2_64_Ceil(memRefType.getShape().front());
312 if (addrWidth == 0) {
315 auto ctrl = handshake::JoinOp::create(b, loc, v).getResult();
316 return handshake::ConstantOp::create(
317 b, loc, b.getIntegerType(0), b.getIntegerAttr(b.getIntegerType(0), 0),
320 return arith::IndexCastOp::create(b, loc, b.getIntegerType(addrWidth), v);
323static Value plumbLoadPort(Location loc, OpBuilder &b,
324 handshake::MemLoadInterface &ldif, Value loadData,
325 MemRefType memrefType) {
329 auto dataFork = ForkOp::create(b, loc, loadData, 2);
331 auto dataOut = dataFork.getResult()[0];
332 llvm::SmallVector<Value> joinArgs = {dataFork.getResult()[1]};
333 auto dataDone = JoinOp::create(b, loc, joinArgs);
335 ldif.
dataOut.replaceAllUsesWith(dataOut);
336 ldif.
doneOut.replaceAllUsesWith(dataDone);
340 return truncateToMemoryWidth(loc, b, ldif.
addressIn, memrefType);
343static Value plumbStorePort(Location loc, OpBuilder &b,
344 handshake::MemStoreInterface &stif, Value done,
345 Type outType, MemRefType memrefType) {
346 stif.
doneOut.replaceAllUsesWith(done);
349 llvm::SmallVector<Value> structArgs = {
350 truncateToMemoryWidth(loc, b, stif.
addressIn, memrefType), stif.
dataIn};
357static void appendToStringArrayAttr(Operation *op, StringRef attrName,
359 auto *ctx = op->getContext();
360 llvm::SmallVector<Attribute> newArr;
361 llvm::copy(op->getAttrOfType<ArrayAttr>(attrName).getValue(),
362 std::back_inserter(newArr));
363 newArr.push_back(StringAttr::get(ctx, attrVal));
364 op->setAttr(attrName, ArrayAttr::get(ctx, newArr));
367static void insertInStringArrayAttr(Operation *op, StringRef attrName,
368 StringRef attrVal,
unsigned idx) {
369 auto *ctx = op->getContext();
370 llvm::SmallVector<Attribute> newArr;
371 llvm::copy(op->getAttrOfType<ArrayAttr>(attrName).getValue(),
372 std::back_inserter(newArr));
373 newArr.insert(newArr.begin() + idx, StringAttr::get(ctx, attrVal));
374 op->setAttr(attrName, ArrayAttr::get(ctx, newArr));
377static void eraseFromArrayAttr(Operation *op, StringRef attrName,
379 auto *ctx = op->getContext();
380 llvm::SmallVector<Attribute> newArr;
381 llvm::copy(op->getAttrOfType<ArrayAttr>(attrName).getValue(),
382 std::back_inserter(newArr));
383 newArr.erase(newArr.begin() + idx);
384 op->setAttr(attrName, ArrayAttr::get(ctx, newArr));
387struct ArgTypeReplacement {
394HandshakeLowerExtmemToHWPass::lowerExtmemToHW(handshake::FuncOp func) {
397 std::map<unsigned, Value> memrefArgs;
398 for (
auto [i, arg] : llvm::enumerate(func.getArguments()))
399 if (isa<MemRefType>(arg.getType()))
402 if (memrefArgs.empty())
407 std::map<unsigned, HandshakeMemType> argReplacements;
410 auto origPortInfo = handshake::getPortInfoForOpTypes(
411 func, func.getArgumentTypes(), func.getResultTypes());
414 for (
auto it : memrefArgs) {
416 unsigned i = it.first;
417 auto arg = it.second;
418 auto loc = arg.getLoc();
420 auto extmemOp = cast<handshake::ExternalMemoryOp>(*arg.getUsers().begin());
421 b.setInsertionPoint(extmemOp);
425 MemRefType memrefType = cast<MemRefType>(arg.getType());
428 cast<handshake::ReturnOp>(func.getBody().front().getTerminator());
429 llvm::SmallVector<Value> newReturnOperands = oldReturnOp.getOperands();
430 unsigned addedInPorts = 0;
431 auto memName = func.getArgName(i);
432 auto addArgRes = [&](
unsigned id, NamedType &argType,
433 NamedType &resType) -> FailureOr<Value> {
435 unsigned newArgIdx = i + addedInPorts;
437 func.insertArgument(newArgIdx, argType.second, {}, arg.getLoc())))
439 insertInStringArrayAttr(func,
"argNames",
440 memName.str() +
"_" + argType.first.str(),
442 auto newInPort = func.getArgument(newArgIdx);
446 if (failed(func.insertResult(func.getNumResults(), resType.second, {})))
448 appendToStringArrayAttr(func,
"resNames",
449 memName.str() +
"_" + resType.first.str());
454 unsigned portIdx = 0;
455 for (
auto loadPort : extmemOp.getLoadPorts()) {
456 auto newInPort = addArgRes(loadPort.index, memIOTypes.inputTypes[portIdx],
457 memIOTypes.outputTypes[portIdx]);
458 if (failed(newInPort))
460 newReturnOperands.push_back(
461 plumbLoadPort(loc, b, loadPort, *newInPort, memrefType));
466 for (
auto storePort : extmemOp.getStorePorts()) {
468 addArgRes(storePort.index, memIOTypes.inputTypes[portIdx],
469 memIOTypes.outputTypes[portIdx]);
470 if (failed(newInPort))
472 newReturnOperands.push_back(
473 plumbStorePort(loc, b, storePort, *newInPort,
474 memIOTypes.outputTypes[portIdx].second, memrefType));
480 b.setInsertionPoint(oldReturnOp);
481 ReturnOp::create(b, arg.getLoc(), newReturnOperands);
490 if (failed(func.eraseArgument(i + addedInPorts)))
492 eraseFromArrayAttr(func,
"argNames", i + addedInPorts);
494 argReplacements[i] = memIOTypes;
497 if (createESIWrapper)
498 if (failed(wrapESI(func, origPortInfo, argReplacements)))
506std::unique_ptr<mlir::Pass>
508 std::optional<bool> createESIWrapper) {
509 return std::make_unique<HandshakeLowerExtmemToHWPass>(createESIWrapper);
assert(baseType &&"element must be base type")
static Type indexToMemAddr(Type t, MemRefType memRef)
static HandshakeMemType getMemTypeForExtmem(Value v)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
create(elements, Type result_type=None)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
std::unique_ptr< mlir::Pass > createHandshakeLowerExtmemToHWPass(std::optional< bool > createESIWrapper={})
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
This holds a decoded list of input/inout and output ports for a module or instance.
PortInfo & atInput(size_t idx)
size_t sizeOutputs() const
void eraseInput(size_t idx)