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