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