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