CIRCT 24.0.0git
Loading...
Searching...
No Matches
ESILowerToHW.cpp
Go to the documentation of this file.
1//===- ESILowerToHW.cpp - Lower ESI to HW -----------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Lower to HW/SV conversions and pass.
10//
11//===----------------------------------------------------------------------===//
12
13#include "../PassDetails.h"
14
21#include "circt/Support/LLVM.h"
23
24#include "mlir/Transforms/DialectConversion.h"
25#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
26
27#include "llvm/ADT/StringExtras.h"
28#include "llvm/ADT/TypeSwitch.h"
29#include "llvm/Support/JSON.h"
30
31namespace circt {
32namespace esi {
33#define GEN_PASS_DEF_LOWERESITOHW
34#include "circt/Dialect/ESI/ESIPasses.h.inc"
35} // namespace esi
36} // namespace circt
37
38using namespace circt;
39using namespace circt::esi;
40using namespace circt::esi::detail;
41using namespace circt::hw;
42using namespace circt::sv;
43
44namespace {
45/// Lower PipelineStageOp ops to an HW implementation. Unwrap and re-wrap
46/// appropriately. Another conversion will take care merging the resulting
47/// adjacent wrap/unwrap ops.
48struct PipelineStageLowering : public OpConversionPattern<PipelineStageOp> {
49public:
50 PipelineStageLowering(ESIHWBuilder &builder, MLIRContext *ctxt)
51 : OpConversionPattern(ctxt), builder(builder) {}
52 using OpConversionPattern::OpConversionPattern;
53
54 LogicalResult
55 matchAndRewrite(PipelineStageOp stage, OpAdaptor adaptor,
56 ConversionPatternRewriter &rewriter) const final;
57
58private:
59 ESIHWBuilder &builder;
60};
61} // anonymous namespace
62
63LogicalResult PipelineStageLowering::matchAndRewrite(
64 PipelineStageOp stage, OpAdaptor adaptor,
65 ConversionPatternRewriter &rewriter) const {
66 auto loc = stage.getLoc();
67 auto chPort = dyn_cast<ChannelType>(stage.getInput().getType());
68 if (!chPort)
69 return rewriter.notifyMatchFailure(stage, "stage had wrong type");
70 Operation *symTable = stage->getParentWithTrait<OpTrait::SymbolTable>();
71 auto stageModule = builder.declareStage(symTable, stage);
72
73 size_t width = circt::hw::getBitWidth(chPort.getInner());
74
75 ArrayAttr stageParams = builder.getStageParameterList(
76 rewriter.getUI32IntegerAttr(width),
77 rewriter.getUI32IntegerAttr(chPort.getDataDelay()));
78
79 // Unwrap the channel. The ready signal is a Value we haven't created yet,
80 // so create a temp value and replace it later. Give this constant an
81 // odd-looking type to make debugging easier.
82 circt::BackedgeBuilder back(rewriter, loc);
83 circt::Backedge wrapReady = back.get(rewriter.getI1Type());
84 auto unwrap =
85 UnwrapValidReadyOp::create(rewriter, loc, stage.getInput(), wrapReady);
86
87 StringRef pipeStageName = "pipelineStage";
88 if (auto name = stage->getAttrOfType<StringAttr>("name"))
89 pipeStageName = name.getValue();
90
91 // Instantiate the "ESI_PipelineStage" external module.
92 circt::Backedge stageReady = back.get(rewriter.getI1Type());
93 llvm::SmallVector<Value> operands = {stage.getClk(), stage.getRst()};
94 operands.push_back(unwrap.getRawOutput());
95 operands.push_back(unwrap.getValid());
96 operands.push_back(stageReady);
97 auto stageInst = hw::InstanceOp::create(rewriter, loc, stageModule,
98 pipeStageName, operands, stageParams);
99 auto stageInstResults = stageInst.getResults();
100
101 // Set a_ready (from the unwrap) back edge correctly to its output from
102 // stage.
103 wrapReady.setValue(stageInstResults[0]);
104 Value x, xValid;
105 x = stageInstResults[1];
106 xValid = stageInstResults[2];
107
108 // Wrap up the output of the HW stage module.
109 auto wrap = WrapValidReadyOp::create(rewriter, loc, chPort,
110 rewriter.getI1Type(), x, xValid);
111 // Set the stages x_ready backedge correctly.
112 stageReady.setValue(wrap.getReady());
113
114 rewriter.replaceOp(stage, wrap.getChanOutput());
115 return success();
116}
117
118namespace {
119struct NullSourceOpLowering : public OpConversionPattern<NullSourceOp> {
120public:
121 NullSourceOpLowering(MLIRContext *ctxt) : OpConversionPattern(ctxt) {}
122 using OpConversionPattern::OpConversionPattern;
123
124 LogicalResult
125 matchAndRewrite(NullSourceOp nullop, OpAdaptor adaptor,
126 ConversionPatternRewriter &rewriter) const final;
127};
128} // anonymous namespace
129
130LogicalResult NullSourceOpLowering::matchAndRewrite(
131 NullSourceOp nullop, OpAdaptor adaptor,
132 ConversionPatternRewriter &rewriter) const {
133 auto chanType = cast<ChannelType>(nullop.getOut().getType());
134 auto innerType = chanType.getInner();
135 Location loc = nullop.getLoc();
136 int64_t width = hw::getBitWidth(innerType);
137 if (width == -1)
138 return rewriter.notifyMatchFailure(
139 nullop, "NullOp lowering only supports hw types");
140 auto valid = hw::ConstantOp::create(rewriter, nullop.getLoc(),
141 rewriter.getI1Type(), 0);
142 auto zero =
143 hw::ConstantOp::create(rewriter, loc, rewriter.getIntegerType(width), 0);
144 auto typedZero = hw::BitcastOp::create(rewriter, loc, innerType, zero);
145
146 if (chanType.getSignaling() == ChannelSignaling::ValidOnly) {
147 auto wrap = WrapValidOnlyOp::create(rewriter, loc, typedZero, valid,
148 chanType.getDataDelay());
149 wrap->setAttr("name", rewriter.getStringAttr("nullsource"));
150 rewriter.replaceOp(nullop, {wrap.getChanOutput()});
151 } else {
152 auto wrap = WrapValidReadyOp::create(rewriter, loc, typedZero, valid,
153 chanType.getDataDelay());
154 wrap->setAttr("name", rewriter.getStringAttr("nullsource"));
155 rewriter.replaceOp(nullop, {wrap.getChanOutput()});
156 }
157 return success();
158}
159
160namespace {
161/// Eliminate snoop operations by extracting signals from wrap operations.
162/// After ESI ports lowering, channels always come from wraps and are consumed
163/// by unwraps (or have no consumers). This pattern leverages that invariant.
164struct RemoveSnoopOp : public OpConversionPattern<SnoopValidReadyOp> {
165public:
166 using OpConversionPattern::OpConversionPattern;
167
168 LogicalResult
169 matchAndRewrite(SnoopValidReadyOp op, SnoopValidReadyOpAdaptor operands,
170 ConversionPatternRewriter &rewriter) const override {
171 Operation *defOp = op.getInput().getDefiningOp();
172 if (!defOp)
173 return rewriter.notifyMatchFailure(op,
174 "snoop input is not defined by an op");
175 auto wrap = dyn_cast<WrapValidReadyOp>(defOp);
176 if (!wrap)
177 return rewriter.notifyMatchFailure(
178 defOp, "Snoop input must be a wrap.vr operation");
179
180 // Get valid and data directly from the wrap
181 Value valid = wrap.getValid();
182 Value data = wrap.getRawInput();
183
184 // For ready: check if there's an unwrap consumer
185 Value ready;
186 auto *unwrapOpOperand =
187 ChannelType::getSingleConsumer(wrap.getChanOutput());
188
189 if (unwrapOpOperand &&
190 isa<UnwrapValidReadyOp>(unwrapOpOperand->getOwner())) {
191 // There's an unwrap - use its ready signal
192 auto unwrap = cast<UnwrapValidReadyOp>(unwrapOpOperand->getOwner());
193 ready = unwrap.getReady();
194 } else {
195 // No consumer: synthesize a constant false ready signal
196 // (nothing is ready to consume this channel)
197 assert(!unwrapOpOperand &&
198 "Expected no consumer or consumer should be an unwrap");
199 ready = hw::ConstantOp::create(rewriter, op.getLoc(),
200 rewriter.getI1Type(), 0);
201 }
202
203 rewriter.replaceOp(op, {valid, ready, data});
204 return success();
205 }
206};
207} // anonymous namespace
208
209namespace {
210/// Eliminate snoop transaction operations by extracting signals from wrap
211/// operations. After ESI ports lowering, channels always come from wraps
212/// and are consumed by unwraps (or have no consumers).
213struct RemoveSnoopTransactionOp
214 : public OpConversionPattern<SnoopTransactionOp> {
215public:
216 using OpConversionPattern::OpConversionPattern;
217
218 LogicalResult
219 matchAndRewrite(SnoopTransactionOp op, SnoopTransactionOpAdaptor operands,
220 ConversionPatternRewriter &rewriter) const override {
221 Operation *defOp = op.getInput().getDefiningOp();
222 if (!defOp)
223 return rewriter.notifyMatchFailure(op,
224 "snoop input is not defined by an op");
225
226 // Handle ValidReady signaling
227 if (auto wrapVR = dyn_cast<WrapValidReadyOp>(defOp)) {
228 Value data = wrapVR.getRawInput();
229 Value valid = wrapVR.getValid();
230
231 // Find ready signal
232 Value ready;
233 auto *unwrapOpOperand =
234 ChannelType::getSingleConsumer(wrapVR.getChanOutput());
235
236 if (unwrapOpOperand &&
237 isa<UnwrapValidReadyOp>(unwrapOpOperand->getOwner())) {
238 // There's an unwrap - use its ready signal
239 auto unwrapVR = cast<UnwrapValidReadyOp>(unwrapOpOperand->getOwner());
240 ready = unwrapVR.getReady();
241 } else {
242 // No consumer: transaction never happens (valid && false)
243 assert(!unwrapOpOperand &&
244 "Expected no consumer or consumer should be an unwrap");
245 ready = hw::ConstantOp::create(rewriter, op.getLoc(),
246 rewriter.getI1Type(), 0);
247 }
248
249 // Create transaction signal as valid AND ready
250 auto transaction =
251 comb::AndOp::create(rewriter, op.getLoc(), valid, ready);
252
253 rewriter.replaceOp(op, {transaction, data});
254 return success();
255 }
256
257 // Handle FIFO signaling
258 if (auto wrapFIFO = dyn_cast<WrapFIFOOp>(defOp)) {
259 Value data = wrapFIFO.getData();
260 Value empty = wrapFIFO.getEmpty();
261
262 // Find rden signal
263 Value rden;
264 auto *unwrapOpOperand =
265 ChannelType::getSingleConsumer(wrapFIFO.getChanOutput());
266
267 if (unwrapOpOperand && isa<UnwrapFIFOOp>(unwrapOpOperand->getOwner())) {
268 // There's an unwrap - use its rden signal
269 auto unwrapFIFO = cast<UnwrapFIFOOp>(unwrapOpOperand->getOwner());
270 rden = unwrapFIFO.getRden();
271 } else {
272 // No consumer: never reading
273 assert(!unwrapOpOperand &&
274 "Expected no consumer or consumer should be an unwrap");
275 rden = hw::ConstantOp::create(rewriter, op.getLoc(),
276 rewriter.getI1Type(), 0);
277 }
278
279 // Create transaction signal as !empty AND rden
280 auto notEmpty = comb::XorOp::create(
281 rewriter, op.getLoc(), empty,
282 hw::ConstantOp::create(rewriter, op.getLoc(),
283 rewriter.getBoolAttr(true)));
284 auto transaction =
285 comb::AndOp::create(rewriter, op.getLoc(), notEmpty, rden);
286
287 rewriter.replaceOp(op, {transaction, data});
288 return success();
289 }
290
291 // Handle ValidOnly signaling
292 if (auto wrapVO = dyn_cast<WrapValidOnlyOp>(defOp)) {
293 Value data = wrapVO.getRawInput();
294 Value valid = wrapVO.getValid();
295 // For ValidOnly, transaction == valid (always ready, no backpressure).
296 rewriter.replaceOp(op, {valid, data});
297 return success();
298 }
299
300 return rewriter.notifyMatchFailure(
301 defOp,
302 "Snoop input must be a wrap.vr, wrap.fifo, or wrap.vo operation");
303 }
304};
305} // anonymous namespace
306
307namespace {
308/// Use the op canonicalizer to lower away the op. Assumes the canonicalizer
309/// deletes the op.
310template <typename Op>
311struct CanonicalizerOpLowering : public OpConversionPattern<Op> {
312public:
313 CanonicalizerOpLowering(MLIRContext *ctxt) : OpConversionPattern<Op>(ctxt) {}
314
315 LogicalResult
316 matchAndRewrite(Op op, typename Op::Adaptor adaptor,
317 ConversionPatternRewriter &rewriter) const final {
318 if (failed(Op::canonicalize(op, rewriter)))
319 return rewriter.notifyMatchFailure(op->getLoc(), "canonicalizer failed");
320 return success();
321 }
322};
323} // anonymous namespace
324
325namespace {
326struct ESItoHWPass : public circt::esi::impl::LowerESItoHWBase<ESItoHWPass> {
327 void runOnOperation() override;
328};
329} // anonymous namespace
330
331namespace {
332/// Lower `CosimEndpointOp` ops to a SystemVerilog extern module and a Capnp
333/// gasket op.
334struct CosimToHostLowering : public OpConversionPattern<CosimToHostEndpointOp> {
335public:
336 CosimToHostLowering(ESIHWBuilder &b)
337 : OpConversionPattern(b.getContext(), 1), builder(b) {}
338
339 using OpConversionPattern::OpConversionPattern;
340
341 LogicalResult
342 matchAndRewrite(CosimToHostEndpointOp, OpAdaptor adaptor,
343 ConversionPatternRewriter &rewriter) const final;
344
345private:
346 ESIHWBuilder &builder;
347};
348} // anonymous namespace
349
350LogicalResult CosimToHostLowering::matchAndRewrite(
351 CosimToHostEndpointOp ep, OpAdaptor adaptor,
352 ConversionPatternRewriter &rewriter) const {
353 auto loc = ep.getLoc();
354 auto *ctxt = rewriter.getContext();
355 circt::BackedgeBuilder bb(rewriter, loc);
356
357 Value toHost = adaptor.getToHost();
358 Type type = toHost.getType();
359 auto chanTy = dyn_cast<ChannelType>(type);
360 uint64_t width = getWidth(type);
361
362 // Set all the parameters.
363 SmallVector<Attribute, 8> params;
364 params.push_back(ParamDeclAttr::get("ENDPOINT_ID", ep.getIdAttr()));
365 params.push_back(ParamDeclAttr::get("TO_HOST_TYPE_ID", getTypeID(type)));
366 params.push_back(ParamDeclAttr::get(
367 "TO_HOST_SIZE_BITS", rewriter.getI32IntegerAttr(width > 0 ? width : 1)));
368
369 // Set up the egest route to drive the EP's toHost ports.
370 auto sendReady = bb.get(rewriter.getI1Type());
371 UnwrapValidReadyOp unwrapSend =
372 UnwrapValidReadyOp::create(rewriter, loc, toHost, sendReady);
373
374 Value rawSendData = unwrapSend.getRawOutput();
375 // For windows, we need to extract the lowered data from the window type.
376 if (chanTy)
377 if (WindowType windowType = dyn_cast_or_null<WindowType>(chanTy.getInner()))
378 rawSendData = UnwrapWindow::create(rewriter, loc, rawSendData);
379
380 Value castedSendData;
381 if (width > 0)
382 castedSendData = hw::BitcastOp::create(
383 rewriter, loc, rewriter.getIntegerType(width), rawSendData);
384 else
385 castedSendData = hw::ConstantOp::create(
386 rewriter, loc, rewriter.getIntegerType(1), rewriter.getBoolAttr(false));
387
388 // Build or get the cached Cosim Endpoint module parameterization.
389 Operation *symTable = ep->getParentWithTrait<OpTrait::SymbolTable>();
390 HWModuleExternOp endpoint =
391 builder.declareCosimEndpointToHostModule(symTable);
392
393 // Create replacement Cosim_Endpoint instance.
394 Value operands[] = {
395 adaptor.getClk(),
396 adaptor.getRst(),
397 unwrapSend.getValid(),
398 castedSendData,
399 };
400 auto cosimEpModule =
401 hw::InstanceOp::create(rewriter, loc, endpoint, ep.getIdAttr(), operands,
402 ArrayAttr::get(ctxt, params));
403 sendReady.setValue(cosimEpModule.getResult(0));
404
405 // Replace the CosimEndpointOp op.
406 rewriter.eraseOp(ep);
407
408 return success();
409}
410
411namespace {
412/// Lower `CosimEndpointOp` ops to a SystemVerilog extern module and a Capnp
413/// gasket op.
414struct CosimFromHostLowering
415 : public OpConversionPattern<CosimFromHostEndpointOp> {
416public:
417 CosimFromHostLowering(ESIHWBuilder &b)
418 : OpConversionPattern(b.getContext(), 1), builder(b) {}
419
420 using OpConversionPattern::OpConversionPattern;
421
422 LogicalResult
423 matchAndRewrite(CosimFromHostEndpointOp, OpAdaptor adaptor,
424 ConversionPatternRewriter &rewriter) const final;
425
426private:
427 ESIHWBuilder &builder;
428};
429} // anonymous namespace
430
431LogicalResult CosimFromHostLowering::matchAndRewrite(
432 CosimFromHostEndpointOp ep, OpAdaptor adaptor,
433 ConversionPatternRewriter &rewriter) const {
434 auto loc = ep.getLoc();
435 auto *ctxt = rewriter.getContext();
436 circt::BackedgeBuilder bb(rewriter, loc);
437
438 ChannelType type = ep.getFromHost().getType();
439 WindowType windowType = dyn_cast<WindowType>(type.getInner());
440 uint64_t width = getWidth(type);
441
442 // Set all the parameters.
443 SmallVector<Attribute, 8> params;
444 params.push_back(ParamDeclAttr::get("ENDPOINT_ID", ep.getIdAttr()));
445 params.push_back(ParamDeclAttr::get("FROM_HOST_TYPE_ID", getTypeID(type)));
446 params.push_back(
447 ParamDeclAttr::get("FROM_HOST_SIZE_BITS",
448 rewriter.getI32IntegerAttr(width > 0 ? width : 1)));
449
450 // Get information necessary for injest path.
451 auto recvReady = bb.get(rewriter.getI1Type());
452
453 // Build or get the cached Cosim Endpoint module parameterization.
454 Operation *symTable = ep->getParentWithTrait<OpTrait::SymbolTable>();
455 HWModuleExternOp endpoint =
456 builder.declareCosimEndpointFromHostModule(symTable);
457
458 // Create replacement Cosim_Endpoint instance.
459 Value operands[] = {adaptor.getClk(), adaptor.getRst(), recvReady};
460 auto cosimEpModule =
461 hw::InstanceOp::create(rewriter, loc, endpoint, ep.getIdAttr(), operands,
462 ArrayAttr::get(ctxt, params));
463
464 // Set up the injest path.
465 Value recvDataFromCosim = cosimEpModule.getResult(1);
466 Value recvValidFromCosim = cosimEpModule.getResult(0);
467 Value castedRecvData;
468 Type castToType = windowType ? windowType.getLoweredType() : type.getInner();
469 if (width > 0)
470 castedRecvData =
471 hw::BitcastOp::create(rewriter, loc, castToType, recvDataFromCosim);
472 else
473 castedRecvData = hw::ConstantOp::create(
474 rewriter, loc, rewriter.getIntegerType(0),
475 rewriter.getIntegerAttr(rewriter.getIntegerType(0), 0));
476 if (windowType) {
477 // For windows, we need to reconstruct the window type from the lowered
478 // data.
479 castedRecvData =
480 WrapWindow::create(rewriter, loc, windowType, castedRecvData);
481 }
482 WrapValidReadyOp wrapRecv = WrapValidReadyOp::create(
483 rewriter, loc, castedRecvData, recvValidFromCosim, type.getDataDelay());
484 recvReady.setValue(wrapRecv.getReady());
485
486 // Replace the CosimEndpointOp op.
487 rewriter.replaceOp(ep, wrapRecv.getChanOutput());
488
489 return success();
490}
491
492namespace {
493/// Lower `CompressedManifestOps` ops to a module containing an on-chip ROM.
494/// Said module has registered input and outputs, so it has two cycles latency
495/// between changing the address and the data being reflected on the output.
496struct ManifestRomLowering : public OpConversionPattern<CompressedManifestOp> {
497public:
498 using OpConversionPattern::OpConversionPattern;
499 constexpr static StringRef manifestRomName = "__ESI_Manifest_ROM";
500
501 LogicalResult
502 matchAndRewrite(CompressedManifestOp, OpAdaptor adaptor,
503 ConversionPatternRewriter &rewriter) const override;
504
505protected:
506 LogicalResult createRomModule(CompressedManifestOp op,
507 ConversionPatternRewriter &rewriter) const;
508};
509} // anonymous namespace
510
511LogicalResult ManifestRomLowering::createRomModule(
512 CompressedManifestOp op, ConversionPatternRewriter &rewriter) const {
513 Location loc = op.getLoc();
514 auto mlirModBody = op->getParentOfType<mlir::ModuleOp>();
515 rewriter.setInsertionPointToStart(mlirModBody.getBody());
516
517 // Find possible existing module (which may have been created as a dummy
518 // module) and erase it.
519 if (Operation *existingExtern = mlirModBody.lookupSymbol(manifestRomName)) {
520 if (!isa<hw::HWModuleExternOp>(existingExtern))
521 return rewriter.notifyMatchFailure(
522 op,
523 "Found " + manifestRomName + " but it wasn't an HWModuleExternOp");
524 rewriter.eraseOp(existingExtern);
525 }
526
527 // Create the real module.
528 PortInfo ports[] = {
529 {{rewriter.getStringAttr("clk"), rewriter.getType<seq::ClockType>(),
530 ModulePort::Direction::Input}},
531 {{rewriter.getStringAttr("address"), rewriter.getIntegerType(29),
532 ModulePort::Direction::Input}},
533 {{rewriter.getStringAttr("data"), rewriter.getI64Type(),
534 ModulePort::Direction::Output}},
535 };
536 auto rom = HWModuleOp::create(rewriter, loc,
537 rewriter.getStringAttr(manifestRomName), ports);
538 Block *romBody = rom.getBodyBlock();
539 rewriter.setInsertionPointToStart(romBody);
540 Value clk = romBody->getArgument(0);
541 Value inputAddress = romBody->getArgument(1);
542
543 // Manifest the compressed manifest into 64-bit words.
544 ArrayRef<uint8_t> maniBytes = op.getCompressedManifest().getData();
545 SmallVector<uint64_t> words;
546 words.push_back(maniBytes.size());
547
548 for (size_t i = 0; i < maniBytes.size() - 7; i += 8) {
549 uint64_t word = 0;
550 for (size_t b = 0; b < 8; ++b)
551 word |= static_cast<uint64_t>(maniBytes[i + b]) << (8 * b);
552 words.push_back(word);
553 }
554 size_t overHang = maniBytes.size() % 8;
555 if (overHang != 0) {
556 uint64_t word = 0;
557 for (size_t i = 0; i < overHang; ++i)
558 word |= static_cast<uint64_t>(maniBytes[maniBytes.size() - overHang + i])
559 << (i * 8);
560 words.push_back(word);
561 }
562
563 // From the words, create an the register which will hold the manifest (and
564 // hopefully synthized to a ROM).
565 SmallVector<Attribute> wordAttrs;
566 for (uint64_t word : words)
567 wordAttrs.push_back(rewriter.getI64IntegerAttr(word));
568 auto manifestConstant = hw::AggregateConstantOp::create(
569 rewriter, loc,
570 hw::UnpackedArrayType::get(rewriter.getI64Type(), words.size()),
571 rewriter.getArrayAttr(wordAttrs));
572 auto manifestReg =
573 sv::VarOp::create(rewriter, loc, manifestConstant.getType());
574 sv::VarAssignOp::create(rewriter, loc, manifestReg, manifestConstant);
575
576 // Slim down the address, register it, do the lookup, and register the output.
577 size_t addrBits = llvm::Log2_64_Ceil(words.size());
578 auto slimmedIdx =
579 comb::ExtractOp::create(rewriter, loc, inputAddress, 0, addrBits);
580 Value inputAddresReg = seq::CompRegOp::create(rewriter, loc, slimmedIdx, clk);
581 auto readIdx =
582 sv::ArrayIndexInOutOp::create(rewriter, loc, manifestReg, inputAddresReg);
583 auto readData = sv::ReadInOutOp::create(rewriter, loc, readIdx);
584 Value readDataReg = seq::CompRegOp::create(rewriter, loc, readData, clk);
585 if (auto *term = romBody->getTerminator())
586 rewriter.eraseOp(term);
587 hw::OutputOp::create(rewriter, loc, ValueRange{readDataReg});
588 return success();
589}
590
591LogicalResult ManifestRomLowering::matchAndRewrite(
592 CompressedManifestOp op, OpAdaptor adaptor,
593 ConversionPatternRewriter &rewriter) const {
594 LogicalResult ret = createRomModule(op, rewriter);
595 rewriter.eraseOp(op);
596 return ret;
597}
598
599namespace {
600/// Lower `CompressedManifestOps` ops to a SystemVerilog module which sets the
601/// Cosim manifest using a DPI support module.
602struct CosimManifestLowering : public ManifestRomLowering {
603public:
604 using ManifestRomLowering::ManifestRomLowering;
605
606 LogicalResult
607 matchAndRewrite(CompressedManifestOp, OpAdaptor adaptor,
608 ConversionPatternRewriter &rewriter) const final;
609};
610} // anonymous namespace
611
612LogicalResult CosimManifestLowering::matchAndRewrite(
613 CompressedManifestOp op, OpAdaptor adaptor,
614 ConversionPatternRewriter &rewriter) const {
615 MLIRContext *ctxt = rewriter.getContext();
616 Location loc = op.getLoc();
617
618 // Cosim can optionally include a manifest simulation, so produce it in case
619 // the Cosim BSP wants it.
620 LogicalResult ret = createRomModule(op, rewriter);
621 if (failed(ret))
622 return ret;
623
624 // Declare external module.
625 Attribute params[] = {
626 ParamDeclAttr::get("COMPRESSED_MANIFEST_SIZE", rewriter.getI32Type()),
627 ParamDeclAttr::get("ESI_VERSION", rewriter.getI32Type())};
628 PortInfo ports[] = {
629 {{rewriter.getStringAttr("compressed_manifest"),
630 rewriter.getType<hw::ArrayType>(
631 rewriter.getI8Type(),
632 ParamDeclRefAttr::get(
633 rewriter.getStringAttr("COMPRESSED_MANIFEST_SIZE"),
634 rewriter.getI32Type())),
635 ModulePort::Direction::Input},
636 0},
637 };
638 rewriter.setInsertionPointToEnd(
639 op->getParentOfType<mlir::ModuleOp>().getBody());
640 auto cosimManifestExternModule = HWModuleExternOp::create(
641 rewriter, loc, rewriter.getStringAttr("Cosim_Manifest"), ports,
642 "Cosim_Manifest", ArrayAttr::get(ctxt, params));
643
644 hw::ModulePortInfo portInfo({});
645 auto manifestMod = hw::HWModuleOp::create(
646 rewriter, loc, rewriter.getStringAttr("__ESIManifest"), portInfo,
647 [&](OpBuilder &rewriter, const hw::HWModulePortAccessor &) {
648 // Assemble the manifest data into a constant.
649 SmallVector<Attribute> bytes;
650 for (uint8_t b : op.getCompressedManifest().getData())
651 bytes.push_back(rewriter.getI8IntegerAttr(b));
652 auto manifestConstant = hw::AggregateConstantOp::create(
653 rewriter, loc,
654 hw::ArrayType::get(rewriter.getI8Type(), bytes.size()),
655 rewriter.getArrayAttr(bytes));
656 auto manifestLogic =
657 sv::VarOp::create(rewriter, loc, manifestConstant.getType());
658 sv::VarAssignOp::create(rewriter, loc, manifestLogic, manifestConstant);
659 auto manifest = sv::ReadInOutOp::create(rewriter, loc, manifestLogic);
660
661 // Then instantiate the external module.
662 hw::InstanceOp::create(
663 rewriter, loc, cosimManifestExternModule, "__manifest",
664 ArrayRef<Value>({manifest}),
665 rewriter.getArrayAttr({
666 ParamDeclAttr::get("COMPRESSED_MANIFEST_SIZE",
667 rewriter.getI32IntegerAttr(bytes.size())),
668 ParamDeclAttr::get("ESI_VERSION",
669 rewriter.getI32IntegerAttr(esiApiVersion)),
670 }));
671 });
672
673 rewriter.setInsertionPoint(op);
674 hw::InstanceOp::create(rewriter, loc, manifestMod, "__manifest",
675 ArrayRef<Value>({}));
676
677 rewriter.eraseOp(op);
678 return success();
679}
680
681/// Returns true if `type` is an ESI channel or an aggregate (e.g. array,
682/// struct) that contains a channel somewhere within it.
683static bool typeContainsChannel(Type type) {
684 return type.walk([](ChannelType) { return WalkResult::interrupt(); })
685 .wasInterrupted();
686}
687
688/// Greedily canonicalize every op whose type involves an ESI channel. This
689/// hopefully removes all channel ops: channel-typed aggregates (e.g.
690/// hw.array_create/ array_get over channels) fold away, exposing adjacent
691/// wrap/unwrap pairs that the wrap/unwrap canonicalizers then merge. A greedy
692/// rewrite is used (rather than a dialect conversion) because this requires
693/// folding, DCE, and iterating to a fixpoint, none of which the conversion
694/// driver does.
695static void canonicalizeChannelOps(Operation *top, MLIRContext *ctxt) {
696 SmallVector<Operation *> channelOps;
697 DenseSet<OperationName> seenOpNames;
698 RewritePatternSet canonPatterns(ctxt);
699 top->walk([&](Operation *op) {
700 if (!llvm::any_of(op->getResultTypes(), typeContainsChannel) &&
701 !llvm::any_of(op->getOperandTypes(), typeContainsChannel))
702 return;
703 channelOps.push_back(op);
704 if (std::optional<mlir::RegisteredOperationName> info =
705 op->getRegisteredInfo();
706 info && seenOpNames.insert(op->getName()).second)
707 info->getCanonicalizationPatterns(canonPatterns, ctxt);
708 });
709 mlir::GreedyRewriteConfig config;
710 config.setStrictness(mlir::GreedyRewriteStrictness::ExistingAndNewOps);
711 (void)mlir::applyOpPatternsGreedily(
712 channelOps, mlir::FrozenRewritePatternSet(std::move(canonPatterns)),
713 config);
714}
715
716/// Returns true if any of `types` is or contains an ESI channel.
717static bool anyTypeContainsChannel(TypeRange types) {
718 return llvm::any_of(types, typeContainsChannel);
719}
720
721/// Verify that no ESI channel ops or channel-typed values remain under `top`,
722/// which the channel lowering is responsible for removing. Returns true if all
723/// channels were removed; otherwise emits diagnostics on a handful of offenders
724/// (rather than all of them) and returns false.
725static bool verifyNoChannelsRemain(Operation *top, MLIRContext *ctxt) {
726 constexpr unsigned maxReported = 10;
727 unsigned numReported = 0;
728 auto *esiDialect = ctxt->getLoadedDialect<ESIDialect>();
729 top->walk([&](Operation *op) {
730 // Ops marked OperatesOnESITypes (e.g. the window wrap/unwrap ops)
731 // operate on ESI types and are lowered by a later pass.
732 if (op->hasTrait<OperatesOnESITypes>())
733 return WalkResult::advance();
734
735 // Other ESI ops must have been lowered away by now.
736 if (op->getDialect() == esiDialect) {
737 op->emitError("lower-esi-to-hw left behind a channel operation");
738 if (++numReported >= maxReported)
739 return WalkResult::interrupt();
740 return WalkResult::advance();
741 }
742
743 // Channel types should have been fully lowered away, even if they're buried
744 // inside aggregates. Look for any type that contains a channel anywhere
745 // within it: results, operands, and block arguments (e.g. module ports,
746 // which may not appear as an operand if they are unused).
747 bool hasChannelType = anyTypeContainsChannel(op->getResultTypes()) ||
748 anyTypeContainsChannel(op->getOperandTypes());
749 for (Region &region : op->getRegions())
750 for (Block &block : region)
751 hasChannelType |= anyTypeContainsChannel(block.getArgumentTypes());
752 if (hasChannelType) {
753 op->emitError("lower-esi-to-hw left behind a channel-typed value");
754 if (++numReported >= maxReported)
755 return WalkResult::interrupt();
756 }
757 return WalkResult::advance();
758 });
759 return numReported == 0;
760}
761
762void ESItoHWPass::runOnOperation() {
763 auto top = getOperation();
764 auto *ctxt = &getContext();
765
766 // Lower all the bundles.
767 ConversionTarget noBundlesTarget(*ctxt);
768 noBundlesTarget.markUnknownOpDynamicallyLegal(
769 [](Operation *) { return true; });
770 noBundlesTarget.addIllegalOp<PackBundleOp>();
771 noBundlesTarget.addIllegalOp<UnpackBundleOp>();
772 RewritePatternSet bundlePatterns(&getContext());
773 bundlePatterns.add<CanonicalizerOpLowering<PackBundleOp>>(&getContext());
774 bundlePatterns.add<CanonicalizerOpLowering<UnpackBundleOp>>(&getContext());
775 if (failed(applyPartialConversion(getOperation(), noBundlesTarget,
776 std::move(bundlePatterns)))) {
777 signalPassFailure();
778 return;
779 }
780
781 // Set up a conversion and give it a set of laws.
782 ConversionTarget pass1Target(*ctxt);
783 pass1Target.addLegalDialect<comb::CombDialect>();
784 pass1Target.addLegalDialect<HWDialect>();
785 pass1Target.addLegalDialect<SVDialect>();
786 pass1Target.addLegalDialect<seq::SeqDialect>();
787 pass1Target.addLegalOp<WrapValidReadyOp, UnwrapValidReadyOp, WrapFIFOOp,
788 UnwrapFIFOOp, WrapValidOnlyOp, UnwrapValidOnlyOp,
789 WrapWindow, UnwrapWindow>();
790 pass1Target.addLegalOp<SnoopTransactionOp, SnoopValidReadyOp>();
791
792 pass1Target.addIllegalOp<PipelineStageOp>();
793 pass1Target.addIllegalOp<CompressedManifestOp>();
794
795 // Add all the conversion patterns.
796 ESIHWBuilder esiBuilder(top);
797 RewritePatternSet pass1Patterns(ctxt);
798 pass1Patterns.insert<PipelineStageLowering>(esiBuilder, ctxt);
799 pass1Patterns.insert<CosimToHostLowering>(esiBuilder);
800 pass1Patterns.insert<CosimFromHostLowering>(esiBuilder);
801 pass1Patterns.insert<NullSourceOpLowering>(ctxt);
802
803 if (platform == Platform::cosim)
804 pass1Patterns.insert<CosimManifestLowering>(ctxt);
805 else if (platform == Platform::fpga)
806 pass1Patterns.insert<ManifestRomLowering>(ctxt);
807 else
808 pass1Patterns.insert<RemoveOpLowering<CompressedManifestOp>>(ctxt);
809
810 // Run the conversion.
811 if (failed(
812 applyPartialConversion(top, pass1Target, std::move(pass1Patterns)))) {
813 signalPassFailure();
814 return;
815 }
816
817 // Lower all the snoop operations.
818 ConversionTarget pass2Target(*ctxt);
819 pass2Target.addLegalDialect<comb::CombDialect>();
820 pass2Target.addLegalDialect<HWDialect>();
821 pass2Target.addLegalDialect<SVDialect>();
822 pass2Target.addIllegalOp<SnoopTransactionOp, SnoopValidReadyOp>();
823 pass2Target.addLegalOp<WrapValidReadyOp, UnwrapValidReadyOp, WrapFIFOOp,
824 UnwrapFIFOOp, WrapValidOnlyOp, UnwrapValidOnlyOp>();
825 RewritePatternSet pass2Patterns(ctxt);
826 pass2Patterns.insert<RemoveSnoopOp>(ctxt);
827 pass2Patterns.insert<RemoveSnoopTransactionOp>(ctxt);
828 if (failed(
829 applyPartialConversion(top, pass2Target, std::move(pass2Patterns)))) {
830 signalPassFailure();
831 return;
832 }
833
834 // Eliminate all channel ops (and channel-typed aggregates) via their
835 // canonicalizers, including merging the adjacent wrap/unwrap pairs.
836 canonicalizeChannelOps(top, ctxt);
837
838 // The canonicalizers above are responsible for removing every ESI op and
839 // channel-typed value. Verify that none remain.
840 if (!verifyNoChannelsRemain(top, ctxt))
841 signalPassFailure();
842}
843
844std::unique_ptr<OperationPass<ModuleOp>> circt::esi::createESItoHWPass() {
845 return std::make_unique<ESItoHWPass>();
846}
assert(baseType &&"element must be base type")
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))
static bool typeContainsChannel(Type type)
Returns true if type is an ESI channel or an aggregate (e.g.
static bool verifyNoChannelsRemain(Operation *top, MLIRContext *ctxt)
Verify that no ESI channel ops or channel-typed values remain under top, which the channel lowering i...
static bool anyTypeContainsChannel(TypeRange types)
Returns true if any of types is or contains an ESI channel.
static void canonicalizeChannelOps(Operation *top, MLIRContext *ctxt)
Greedily canonicalize every op whose type involves an ESI channel.
return success()
static EvaluatorValuePtr unwrap(OMEvaluatorValue c)
Definition OM.cpp:111
static InstancePath empty
void setValue(mlir::Value)
Trait marking an ESI op that operates on ESI types (e.g.
Definition ESIOps.h:34
hw::HWModuleExternOp declareCosimEndpointFromHostModule(Operation *symTable)
hw::HWModuleExternOp declareCosimEndpointToHostModule(Operation *symTable)
Write an 'ExternModuleOp' to use a hand-coded SystemVerilog module.
ArrayAttr getStageParameterList(Attribute widthValue, Attribute dataDelayValue)
Get the parameter list for the stage module.
Definition ESIPasses.cpp:94
hw::HWModuleExternOp declareStage(Operation *symTable, PipelineStageOp)
Write an 'ExternModuleOp' to use a hand-coded SystemVerilog module.
create(low_bit, result_type, input=None)
Definition comb.py:187
create(data_type, value)
Definition hw.py:444
create(data_type, value)
Definition hw.py:433
create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition seq.py:157
create(value)
Definition sv.py:151
create(dest, src)
Definition sv.py:143
create(data_type, name=None, sym_name=None)
Definition sv.py:125
uint64_t getWidth(Type t)
Definition ESIPasses.cpp:30
StringAttr getTypeID(Type t)
Definition ESIPasses.cpp:24
std::unique_ptr< OperationPass< ModuleOp > > createESItoHWPass()
constexpr uint64_t esiApiVersion
ESI ABI version.
Definition ESIDialect.h:32
mlir::Type innerType(mlir::Type type)
Definition ESITypes.cpp:423
int64_t getBitWidth(mlir::Type type)
Return the hardware bit width of a type.
Definition HWTypes.cpp:122
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
static constexpr char fpga[]
Definition ESIPasses.h:29
static constexpr char cosim[]
Definition ESIPasses.h:28