CIRCT 21.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 rewriter.create<UnwrapValidReadyOp>(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 = rewriter.create<hw::InstanceOp>(
96 loc, stageModule, 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 = rewriter.create<WrapValidReadyOp>(
108 loc, chPort, 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 =
138 rewriter.create<hw::ConstantOp>(nullop.getLoc(), rewriter.getI1Type(), 0);
139 auto zero =
140 rewriter.create<hw::ConstantOp>(loc, rewriter.getIntegerType(width), 0);
141 auto typedZero = rewriter.create<hw::BitcastOp>(loc, innerType, zero);
142 auto wrap = rewriter.create<WrapValidReadyOp>(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 = rewriter.create<hw::ConstantOp>(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 in wrap-unwrap pairs.
215struct RemoveSnoopOp : public OpConversionPattern<SnoopValidReadyOp> {
216public:
217 using OpConversionPattern::OpConversionPattern;
218
219 LogicalResult
220 matchAndRewrite(SnoopValidReadyOp op, SnoopValidReadyOpAdaptor operands,
221 ConversionPatternRewriter &rewriter) const override {
222 Operation *defOp = op.getInput().getDefiningOp();
223 if (!defOp)
224 return rewriter.notifyMatchFailure(op,
225 "snoop input is not defined by an op");
226 auto wrap = dyn_cast<WrapValidReadyOp>(defOp);
227 if (!wrap)
228 return rewriter.notifyMatchFailure(
229 defOp, "This conversion only supports wrap-unwrap back-to-back. "
230 "Could not find 'wrap'.");
231 auto *unwrapOpOperand =
232 ChannelType::getSingleConsumer(wrap.getChanOutput());
233 if (!unwrapOpOperand)
234 return rewriter.notifyMatchFailure(
235 defOp, "This conversion only supports wrap-unwrap back-to-back. "
236 "Could sole consumer.");
237 auto unwrap = dyn_cast<UnwrapValidReadyOp>(unwrapOpOperand->getOwner());
238 if (!unwrap)
239 return rewriter.notifyMatchFailure(
240 defOp, "This conversion only supports wrap-unwrap back-to-back. "
241 "Could not find 'unwrap'.");
242 rewriter.replaceOp(
243 op, {wrap.getValid(), unwrap.getReady(), wrap.getRawInput()});
244 return success();
245 }
246};
247} // anonymous namespace
248
249namespace {
250/// Use the op canonicalizer to lower away the op. Assumes the canonicalizer
251/// deletes the op.
252template <typename Op>
253struct CanonicalizerOpLowering : public OpConversionPattern<Op> {
254public:
255 CanonicalizerOpLowering(MLIRContext *ctxt) : OpConversionPattern<Op>(ctxt) {}
256
257 LogicalResult
258 matchAndRewrite(Op op, typename Op::Adaptor adaptor,
259 ConversionPatternRewriter &rewriter) const final {
260 if (failed(Op::canonicalize(op, rewriter)))
261 return rewriter.notifyMatchFailure(op->getLoc(), "canonicalizer failed");
262 return success();
263 }
264};
265} // anonymous namespace
266
267namespace {
268struct ESItoHWPass : public circt::esi::impl::LowerESItoHWBase<ESItoHWPass> {
269 void runOnOperation() override;
270};
271} // anonymous namespace
272
273namespace {
274/// Lower a `wrap.iface` to `wrap.vr` by extracting the wires then feeding the
275/// new `wrap.vr`.
276struct WrapInterfaceLower : public OpConversionPattern<WrapSVInterfaceOp> {
277public:
278 using OpConversionPattern::OpConversionPattern;
279
280 LogicalResult
281 matchAndRewrite(WrapSVInterfaceOp wrap, OpAdaptor adaptor,
282 ConversionPatternRewriter &rewriter) const final;
283};
284} // anonymous namespace
285
286LogicalResult
287WrapInterfaceLower::matchAndRewrite(WrapSVInterfaceOp wrap, OpAdaptor adaptor,
288 ConversionPatternRewriter &rewriter) const {
289 auto operands = adaptor.getOperands();
290 if (operands.size() != 1)
291 return rewriter.notifyMatchFailure(wrap, [&operands](Diagnostic &d) {
292 d << "wrap.iface has 1 argument. Got " << operands.size() << "operands";
293 });
294 auto sinkModport = dyn_cast<GetModportOp>(operands[0].getDefiningOp());
295 if (!sinkModport)
296 return failure();
297 auto ifaceInstance =
298 dyn_cast<InterfaceInstanceOp>(sinkModport.getIface().getDefiningOp());
299 if (!ifaceInstance)
300 return failure();
301
302 auto loc = wrap.getLoc();
303 auto validSignal = rewriter.create<ReadInterfaceSignalOp>(
304 loc, ifaceInstance, ESIHWBuilder::validStr);
305 Value dataSignal;
306 dataSignal = rewriter.create<ReadInterfaceSignalOp>(loc, ifaceInstance,
308 auto wrapVR = rewriter.create<WrapValidReadyOp>(loc, dataSignal, validSignal);
309 rewriter.create<AssignInterfaceSignalOp>(
310 loc, ifaceInstance, ESIHWBuilder::readyStr, wrapVR.getReady());
311 rewriter.replaceOp(wrap, {wrapVR.getChanOutput()});
312 return success();
313}
314
315namespace {
316/// Lower an unwrap interface to just extract the wires and feed them into an
317/// `unwrap.vr`.
318struct UnwrapInterfaceLower : public OpConversionPattern<UnwrapSVInterfaceOp> {
319public:
320 UnwrapInterfaceLower(MLIRContext *ctxt) : OpConversionPattern(ctxt) {}
321 using OpConversionPattern::OpConversionPattern;
322
323 LogicalResult
324 matchAndRewrite(UnwrapSVInterfaceOp wrap, OpAdaptor adaptor,
325 ConversionPatternRewriter &rewriter) const final;
326};
327} // anonymous namespace
328
329LogicalResult UnwrapInterfaceLower::matchAndRewrite(
330 UnwrapSVInterfaceOp unwrap, OpAdaptor adaptor,
331 ConversionPatternRewriter &rewriter) const {
332 auto operands = adaptor.getOperands();
333 if (operands.size() != 2)
334 return rewriter.notifyMatchFailure(unwrap, [&operands](Diagnostic &d) {
335 d << "Unwrap.iface has 2 arguments. Got " << operands.size()
336 << "operands";
337 });
338
339 auto sourceModport = dyn_cast<GetModportOp>(operands[1].getDefiningOp());
340 if (!sourceModport)
341 return failure();
342 auto ifaceInstance =
343 dyn_cast<InterfaceInstanceOp>(sourceModport.getIface().getDefiningOp());
344 if (!ifaceInstance)
345 return failure();
346
347 auto loc = unwrap.getLoc();
348 auto readySignal = rewriter.create<ReadInterfaceSignalOp>(
349 loc, ifaceInstance, ESIHWBuilder::readyStr);
350 auto unwrapVR =
351 rewriter.create<UnwrapValidReadyOp>(loc, operands[0], readySignal);
352 rewriter.create<AssignInterfaceSignalOp>(
353 loc, ifaceInstance, ESIHWBuilder::validStr, unwrapVR.getValid());
354
355 rewriter.create<AssignInterfaceSignalOp>(
356 loc, ifaceInstance, ESIHWBuilder::dataStr, unwrapVR.getRawOutput());
357 rewriter.eraseOp(unwrap);
358 return success();
359}
360
361namespace {
362/// Lower `CosimEndpointOp` ops to a SystemVerilog extern module and a Capnp
363/// gasket op.
364struct CosimToHostLowering : public OpConversionPattern<CosimToHostEndpointOp> {
365public:
366 CosimToHostLowering(ESIHWBuilder &b)
367 : OpConversionPattern(b.getContext(), 1), builder(b) {}
368
369 using OpConversionPattern::OpConversionPattern;
370
371 LogicalResult
372 matchAndRewrite(CosimToHostEndpointOp, OpAdaptor adaptor,
373 ConversionPatternRewriter &rewriter) const final;
374
375private:
376 ESIHWBuilder &builder;
377};
378} // anonymous namespace
379
380LogicalResult CosimToHostLowering::matchAndRewrite(
381 CosimToHostEndpointOp ep, OpAdaptor adaptor,
382 ConversionPatternRewriter &rewriter) const {
383 auto loc = ep.getLoc();
384 auto *ctxt = rewriter.getContext();
385 circt::BackedgeBuilder bb(rewriter, loc);
386
387 Value toHost = adaptor.getToHost();
388 Type type = toHost.getType();
389 uint64_t width = getWidth(type);
390
391 // Set all the parameters.
392 SmallVector<Attribute, 8> params;
393 params.push_back(ParamDeclAttr::get("ENDPOINT_ID", ep.getIdAttr()));
394 params.push_back(ParamDeclAttr::get("TO_HOST_TYPE_ID", getTypeID(type)));
395 params.push_back(ParamDeclAttr::get(
396 "TO_HOST_SIZE_BITS", rewriter.getI32IntegerAttr(width > 0 ? width : 1)));
397
398 // Set up the egest route to drive the EP's toHost ports.
399 auto sendReady = bb.get(rewriter.getI1Type());
400 UnwrapValidReadyOp unwrapSend =
401 rewriter.create<UnwrapValidReadyOp>(loc, toHost, sendReady);
402 Value castedSendData;
403 if (width > 0)
404 castedSendData = rewriter.create<hw::BitcastOp>(
405 loc, rewriter.getIntegerType(width), unwrapSend.getRawOutput());
406 else
407 castedSendData = rewriter.create<hw::ConstantOp>(
408 loc, rewriter.getIntegerType(1), rewriter.getBoolAttr(false));
409
410 // Build or get the cached Cosim Endpoint module parameterization.
411 Operation *symTable = ep->getParentWithTrait<OpTrait::SymbolTable>();
412 HWModuleExternOp endpoint =
413 builder.declareCosimEndpointToHostModule(symTable);
414
415 // Create replacement Cosim_Endpoint instance.
416 Value operands[] = {
417 adaptor.getClk(),
418 adaptor.getRst(),
419 unwrapSend.getValid(),
420 castedSendData,
421 };
422 auto cosimEpModule = rewriter.create<hw::InstanceOp>(
423 loc, endpoint, ep.getIdAttr(), operands, ArrayAttr::get(ctxt, params));
424 sendReady.setValue(cosimEpModule.getResult(0));
425
426 // Replace the CosimEndpointOp op.
427 rewriter.eraseOp(ep);
428
429 return success();
430}
431
432namespace {
433/// Lower `CosimEndpointOp` ops to a SystemVerilog extern module and a Capnp
434/// gasket op.
435struct CosimFromHostLowering
436 : public OpConversionPattern<CosimFromHostEndpointOp> {
437public:
438 CosimFromHostLowering(ESIHWBuilder &b)
439 : OpConversionPattern(b.getContext(), 1), builder(b) {}
440
441 using OpConversionPattern::OpConversionPattern;
442
443 LogicalResult
444 matchAndRewrite(CosimFromHostEndpointOp, OpAdaptor adaptor,
445 ConversionPatternRewriter &rewriter) const final;
446
447private:
448 ESIHWBuilder &builder;
449};
450} // anonymous namespace
451
452LogicalResult CosimFromHostLowering::matchAndRewrite(
453 CosimFromHostEndpointOp ep, OpAdaptor adaptor,
454 ConversionPatternRewriter &rewriter) const {
455 auto loc = ep.getLoc();
456 auto *ctxt = rewriter.getContext();
457 circt::BackedgeBuilder bb(rewriter, loc);
458
459 ChannelType type = ep.getFromHost().getType();
460 uint64_t width = getWidth(type);
461
462 // Set all the parameters.
463 SmallVector<Attribute, 8> params;
464 params.push_back(ParamDeclAttr::get("ENDPOINT_ID", ep.getIdAttr()));
465 params.push_back(ParamDeclAttr::get("FROM_HOST_TYPE_ID", getTypeID(type)));
466 params.push_back(
467 ParamDeclAttr::get("FROM_HOST_SIZE_BITS",
468 rewriter.getI32IntegerAttr(width > 0 ? width : 1)));
469
470 // Get information necessary for injest path.
471 auto recvReady = bb.get(rewriter.getI1Type());
472
473 // Build or get the cached Cosim Endpoint module parameterization.
474 Operation *symTable = ep->getParentWithTrait<OpTrait::SymbolTable>();
475 HWModuleExternOp endpoint =
476 builder.declareCosimEndpointFromHostModule(symTable);
477
478 // Create replacement Cosim_Endpoint instance.
479 Value operands[] = {adaptor.getClk(), adaptor.getRst(), recvReady};
480 auto cosimEpModule = rewriter.create<hw::InstanceOp>(
481 loc, endpoint, ep.getIdAttr(), operands, ArrayAttr::get(ctxt, params));
482
483 // Set up the injest path.
484 Value recvDataFromCosim = cosimEpModule.getResult(1);
485 Value recvValidFromCosim = cosimEpModule.getResult(0);
486 Value castedRecvData;
487 if (width > 0)
488 castedRecvData =
489 rewriter.create<hw::BitcastOp>(loc, type.getInner(), recvDataFromCosim);
490 else
491 castedRecvData = rewriter.create<hw::ConstantOp>(
492 loc, rewriter.getIntegerType(0),
493 rewriter.getIntegerAttr(rewriter.getIntegerType(0), 0));
494 WrapValidReadyOp wrapRecv = rewriter.create<WrapValidReadyOp>(
495 loc, castedRecvData, recvValidFromCosim);
496 recvReady.setValue(wrapRecv.getReady());
497
498 // Replace the CosimEndpointOp op.
499 rewriter.replaceOp(ep, wrapRecv.getChanOutput());
500
501 return success();
502}
503
504namespace {
505/// Lower `CompressedManifestOps` ops to a module containing an on-chip ROM.
506/// Said module has registered input and outputs, so it has two cycles latency
507/// between changing the address and the data being reflected on the output.
508struct ManifestRomLowering : public OpConversionPattern<CompressedManifestOp> {
509public:
510 using OpConversionPattern::OpConversionPattern;
511 constexpr static StringRef manifestRomName = "__ESI_Manifest_ROM";
512
513 LogicalResult
514 matchAndRewrite(CompressedManifestOp, OpAdaptor adaptor,
515 ConversionPatternRewriter &rewriter) const override;
516
517protected:
518 LogicalResult createRomModule(CompressedManifestOp op,
519 ConversionPatternRewriter &rewriter) const;
520};
521} // anonymous namespace
522
523LogicalResult ManifestRomLowering::createRomModule(
524 CompressedManifestOp op, ConversionPatternRewriter &rewriter) const {
525 Location loc = op.getLoc();
526 auto mlirModBody = op->getParentOfType<mlir::ModuleOp>();
527 rewriter.setInsertionPointToStart(mlirModBody.getBody());
528
529 // Find possible existing module (which may have been created as a dummy
530 // module) and erase it.
531 if (Operation *existingExtern = mlirModBody.lookupSymbol(manifestRomName)) {
532 if (!isa<hw::HWModuleExternOp>(existingExtern))
533 return rewriter.notifyMatchFailure(
534 op,
535 "Found " + manifestRomName + " but it wasn't an HWModuleExternOp");
536 rewriter.eraseOp(existingExtern);
537 }
538
539 // Create the real module.
540 PortInfo ports[] = {
541 {{rewriter.getStringAttr("clk"), rewriter.getType<seq::ClockType>(),
542 ModulePort::Direction::Input}},
543 {{rewriter.getStringAttr("address"), rewriter.getIntegerType(29),
544 ModulePort::Direction::Input}},
545 {{rewriter.getStringAttr("data"), rewriter.getI64Type(),
546 ModulePort::Direction::Output}},
547 };
548 auto rom = rewriter.create<HWModuleOp>(
549 loc, rewriter.getStringAttr(manifestRomName), ports);
550 Block *romBody = rom.getBodyBlock();
551 rewriter.setInsertionPointToStart(romBody);
552 Value clk = romBody->getArgument(0);
553 Value inputAddress = romBody->getArgument(1);
554
555 // Manifest the compressed manifest into 64-bit words.
556 ArrayRef<uint8_t> maniBytes = op.getCompressedManifest().getData();
557 SmallVector<uint64_t> words;
558 words.push_back(maniBytes.size());
559
560 for (size_t i = 0; i < maniBytes.size() - 7; i += 8) {
561 uint64_t word = 0;
562 for (size_t b = 0; b < 8; ++b)
563 word |= static_cast<uint64_t>(maniBytes[i + b]) << (8 * b);
564 words.push_back(word);
565 }
566 size_t overHang = maniBytes.size() % 8;
567 if (overHang != 0) {
568 uint64_t word = 0;
569 for (size_t i = 0; i < overHang; ++i)
570 word |= static_cast<uint64_t>(maniBytes[maniBytes.size() - overHang + i])
571 << (i * 8);
572 words.push_back(word);
573 }
574
575 // From the words, create an the register which will hold the manifest (and
576 // hopefully synthized to a ROM).
577 SmallVector<Attribute> wordAttrs;
578 for (uint64_t word : words)
579 wordAttrs.push_back(rewriter.getI64IntegerAttr(word));
580 auto manifestConstant = rewriter.create<hw::AggregateConstantOp>(
581 loc, hw::UnpackedArrayType::get(rewriter.getI64Type(), words.size()),
582 rewriter.getArrayAttr(wordAttrs));
583 auto manifestReg =
584 rewriter.create<sv::RegOp>(loc, manifestConstant.getType());
585 rewriter.create<sv::AssignOp>(loc, manifestReg, manifestConstant);
586
587 // Slim down the address, register it, do the lookup, and register the output.
588 size_t addrBits = llvm::Log2_64_Ceil(words.size());
589 auto slimmedIdx =
590 rewriter.create<comb::ExtractOp>(loc, inputAddress, 0, addrBits);
591 Value inputAddresReg = rewriter.create<seq::CompRegOp>(loc, slimmedIdx, clk);
592 auto readIdx =
593 rewriter.create<sv::ArrayIndexInOutOp>(loc, manifestReg, inputAddresReg);
594 auto readData = rewriter.create<sv::ReadInOutOp>(loc, readIdx);
595 Value readDataReg = rewriter.create<seq::CompRegOp>(loc, readData, clk);
596 if (auto *term = romBody->getTerminator())
597 rewriter.eraseOp(term);
598 rewriter.create<hw::OutputOp>(loc, ValueRange{readDataReg});
599 return success();
600}
601
602LogicalResult ManifestRomLowering::matchAndRewrite(
603 CompressedManifestOp op, OpAdaptor adaptor,
604 ConversionPatternRewriter &rewriter) const {
605 LogicalResult ret = createRomModule(op, rewriter);
606 rewriter.eraseOp(op);
607 return ret;
608}
609
610namespace {
611/// Lower `CompressedManifestOps` ops to a SystemVerilog module which sets the
612/// Cosim manifest using a DPI support module.
613struct CosimManifestLowering : public ManifestRomLowering {
614public:
615 using ManifestRomLowering::ManifestRomLowering;
616
617 LogicalResult
618 matchAndRewrite(CompressedManifestOp, OpAdaptor adaptor,
619 ConversionPatternRewriter &rewriter) const final;
620};
621} // anonymous namespace
622
623LogicalResult CosimManifestLowering::matchAndRewrite(
624 CompressedManifestOp op, OpAdaptor adaptor,
625 ConversionPatternRewriter &rewriter) const {
626 MLIRContext *ctxt = rewriter.getContext();
627 Location loc = op.getLoc();
628
629 // Cosim can optionally include a manifest simulation, so produce it in case
630 // the Cosim BSP wants it.
631 LogicalResult ret = createRomModule(op, rewriter);
632 if (failed(ret))
633 return ret;
634
635 // Declare external module.
636 Attribute params[] = {
637 ParamDeclAttr::get("COMPRESSED_MANIFEST_SIZE", rewriter.getI32Type())};
638 PortInfo ports[] = {
639 {{rewriter.getStringAttr("compressed_manifest"),
640 rewriter.getType<hw::ArrayType>(
641 rewriter.getI8Type(),
642 ParamDeclRefAttr::get(
643 rewriter.getStringAttr("COMPRESSED_MANIFEST_SIZE"),
644 rewriter.getI32Type())),
645 ModulePort::Direction::Input},
646 0},
647 };
648 rewriter.setInsertionPointToEnd(
649 op->getParentOfType<mlir::ModuleOp>().getBody());
650 auto cosimManifestExternModule = rewriter.create<HWModuleExternOp>(
651 loc, rewriter.getStringAttr("Cosim_Manifest"), ports, "Cosim_Manifest",
652 ArrayAttr::get(ctxt, params));
653
654 hw::ModulePortInfo portInfo({});
655 auto manifestMod = rewriter.create<hw::HWModuleOp>(
656 loc, rewriter.getStringAttr("__ESIManifest"), portInfo,
657 [&](OpBuilder &rewriter, const hw::HWModulePortAccessor &) {
658 // Assemble the manifest data into a constant.
659 SmallVector<Attribute> bytes;
660 for (uint8_t b : op.getCompressedManifest().getData())
661 bytes.push_back(rewriter.getI8IntegerAttr(b));
662 auto manifestConstant = rewriter.create<hw::AggregateConstantOp>(
663 loc, hw::ArrayType::get(rewriter.getI8Type(), bytes.size()),
664 rewriter.getArrayAttr(bytes));
665 auto manifestLogic =
666 rewriter.create<sv::LogicOp>(loc, manifestConstant.getType());
667 rewriter.create<sv::AssignOp>(loc, manifestLogic, manifestConstant);
668 auto manifest = rewriter.create<sv::ReadInOutOp>(loc, manifestLogic);
669
670 // Then instantiate the external module.
671 rewriter.create<hw::InstanceOp>(
672 loc, cosimManifestExternModule, "__manifest",
673 ArrayRef<Value>({manifest}),
674 rewriter.getArrayAttr({ParamDeclAttr::get(
675 "COMPRESSED_MANIFEST_SIZE",
676 rewriter.getI32IntegerAttr(bytes.size()))}));
677 });
678
679 rewriter.setInsertionPoint(op);
680 rewriter.create<hw::InstanceOp>(loc, manifestMod, "__manifest",
681 ArrayRef<Value>({}));
682
683 rewriter.eraseOp(op);
684 return success();
685}
686void ESItoHWPass::runOnOperation() {
687 auto top = getOperation();
688 auto *ctxt = &getContext();
689
690 ConversionTarget noBundlesTarget(*ctxt);
691 noBundlesTarget.markUnknownOpDynamicallyLegal(
692 [](Operation *) { return true; });
693 noBundlesTarget.addIllegalOp<PackBundleOp>();
694 noBundlesTarget.addIllegalOp<UnpackBundleOp>();
695 RewritePatternSet bundlePatterns(&getContext());
696 bundlePatterns.add<CanonicalizerOpLowering<PackBundleOp>>(&getContext());
697 bundlePatterns.add<CanonicalizerOpLowering<UnpackBundleOp>>(&getContext());
698 if (failed(applyPartialConversion(getOperation(), noBundlesTarget,
699 std::move(bundlePatterns))))
700 signalPassFailure();
701
702 // Set up a conversion and give it a set of laws.
703 ConversionTarget pass1Target(*ctxt);
704 pass1Target.addLegalDialect<comb::CombDialect>();
705 pass1Target.addLegalDialect<HWDialect>();
706 pass1Target.addLegalDialect<SVDialect>();
707 pass1Target.addLegalDialect<seq::SeqDialect>();
708 pass1Target.addLegalOp<WrapValidReadyOp, UnwrapValidReadyOp>();
709
710 pass1Target.addIllegalOp<WrapSVInterfaceOp, UnwrapSVInterfaceOp>();
711 pass1Target.addIllegalOp<PipelineStageOp>();
712 pass1Target.addIllegalOp<CompressedManifestOp>();
713
714 // Add all the conversion patterns.
715 ESIHWBuilder esiBuilder(top);
716 RewritePatternSet pass1Patterns(ctxt);
717 pass1Patterns.insert<PipelineStageLowering>(esiBuilder, ctxt);
718 pass1Patterns.insert<WrapInterfaceLower>(ctxt);
719 pass1Patterns.insert<UnwrapInterfaceLower>(ctxt);
720 pass1Patterns.insert<CosimToHostLowering>(esiBuilder);
721 pass1Patterns.insert<CosimFromHostLowering>(esiBuilder);
722 pass1Patterns.insert<NullSourceOpLowering>(ctxt);
723
724 if (platform == Platform::cosim)
725 pass1Patterns.insert<CosimManifestLowering>(ctxt);
726 else if (platform == Platform::fpga)
727 pass1Patterns.insert<ManifestRomLowering>(ctxt);
728 else
729 pass1Patterns.insert<RemoveOpLowering<CompressedManifestOp>>(ctxt);
730
731 // Run the conversion.
732 if (failed(
733 applyPartialConversion(top, pass1Target, std::move(pass1Patterns))))
734 signalPassFailure();
735
736 ConversionTarget pass2Target(*ctxt);
737 pass2Target.addLegalDialect<comb::CombDialect>();
738 pass2Target.addLegalDialect<HWDialect>();
739 pass2Target.addLegalDialect<SVDialect>();
740 pass2Target.addIllegalDialect<ESIDialect>();
741
742 RewritePatternSet pass2Patterns(ctxt);
743 pass2Patterns.insert<CanonicalizerOpLowering<UnwrapFIFOOp>>(ctxt);
744 pass2Patterns.insert<CanonicalizerOpLowering<WrapFIFOOp>>(ctxt);
745 pass2Patterns.insert<RemoveWrapUnwrap>(ctxt);
746 pass2Patterns.insert<RemoveSnoopOp>(ctxt);
747 if (failed(
748 applyPartialConversion(top, pass2Target, std::move(pass2Patterns))))
749 signalPassFailure();
750}
751
752std::unique_ptr<OperationPass<ModuleOp>> circt::esi::createESItoHWPass() {
753 return std::make_unique<ESItoHWPass>();
754}
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))
static EvaluatorValuePtr unwrap(OMEvaluatorValue c)
Definition OM.cpp:113
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:98
create(value)
Definition sv.py:106
Definition sv.py:68
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:227
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.