CIRCT 20.0.0git
Loading...
Searching...
No Matches
ESILowerPhysical.cpp
Go to the documentation of this file.
1//===- ESILowerPhysical.cpp - Lower ESI to physical -------------*- 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 ESI to ESI "physical level" ops conversions and pass.
10//
11//===----------------------------------------------------------------------===//
12
13#include "../PassDetails.h"
14
20#include "circt/Support/LLVM.h"
21
22#include "mlir/Transforms/DialectConversion.h"
23
24namespace circt {
25namespace esi {
26#define GEN_PASS_DEF_LOWERESITOPHYSICAL
27#include "circt/Dialect/ESI/ESIPasses.h.inc"
28} // namespace esi
29} // namespace circt
30
31using namespace circt;
32using namespace circt::esi;
33using namespace circt::esi::detail;
34using namespace circt::hw;
35
36namespace {
37/// Lower `ChannelBufferOp`s, breaking out the various options. For now, just
38/// replace with the specified number of pipeline stages (since that's the only
39/// option).
40struct ChannelBufferLowering : public OpConversionPattern<ChannelBufferOp> {
41public:
42 using OpConversionPattern::OpConversionPattern;
43
44 LogicalResult
45 matchAndRewrite(ChannelBufferOp buffer, OpAdaptor adaptor,
46 ConversionPatternRewriter &rewriter) const final;
47};
48} // anonymous namespace
49
50LogicalResult ChannelBufferLowering::matchAndRewrite(
51 ChannelBufferOp buffer, OpAdaptor adaptor,
52 ConversionPatternRewriter &rewriter) const {
53 auto loc = buffer.getLoc();
54
55 auto type = buffer.getType();
56
57 // Expand 'abstract' buffer into 'physical' stages.
58 auto stages = buffer.getStagesAttr();
59 uint64_t numStages = 1;
60 if (stages) {
61 // Guaranteed positive by the parser.
62 numStages = stages.getValue().getLimitedValue();
63 }
64 Value input = buffer.getInput();
65 StringAttr bufferName = buffer.getNameAttr();
66 for (uint64_t i = 0; i < numStages; ++i) {
67 // Create the stages, connecting them up as we build.
68 auto stage = rewriter.create<PipelineStageOp>(loc, type, buffer.getClk(),
69 buffer.getRst(), input);
70 if (bufferName) {
71 SmallString<64> stageName(
72 {bufferName.getValue(), "_stage", std::to_string(i)});
73 stage->setAttr("name", StringAttr::get(rewriter.getContext(), stageName));
74 }
75 input = stage;
76 }
77
78 // Replace the buffer.
79 rewriter.replaceOp(buffer, input);
80 return success();
81}
82
83namespace {
84/// Lower `ChannelBufferOp`s, breaking out the various options. For now, just
85/// replace with the specified number of pipeline stages (since that's the only
86/// option).
87struct FIFOLowering : public OpConversionPattern<FIFOOp> {
88public:
89 using OpConversionPattern::OpConversionPattern;
90
91 LogicalResult
92 matchAndRewrite(FIFOOp, OpAdaptor adaptor,
93 ConversionPatternRewriter &rewriter) const final;
94};
95} // anonymous namespace
96
97LogicalResult
98FIFOLowering::matchAndRewrite(FIFOOp op, OpAdaptor adaptor,
99 ConversionPatternRewriter &rewriter) const {
100 auto loc = op.getLoc();
101 auto outputType = op.getType();
102 BackedgeBuilder bb(rewriter, loc);
103 auto i1 = rewriter.getI1Type();
104 auto c1 = rewriter.create<hw::ConstantOp>(loc, rewriter.getI1Type(),
105 rewriter.getBoolAttr(true));
106 mlir::TypedValue<ChannelType> chanInput = op.getInput();
107 if (chanInput.getType().getDataDelay() != 0)
108 return op.emitOpError(
109 "currently only supports input channels with zero data delay");
110
111 Backedge inputEn = bb.get(i1);
112 Value rawData;
113 Value dataNotAvailable;
114 if (chanInput.getType().getSignaling() == ChannelSignaling::ValidReady) {
115 auto unwrapValidReady =
116 rewriter.create<UnwrapValidReadyOp>(loc, chanInput, inputEn);
117 rawData = unwrapValidReady.getRawOutput();
118 dataNotAvailable = comb::createOrFoldNot(loc, unwrapValidReady.getValid(),
119 rewriter, /*twoState=*/true);
120 dataNotAvailable.getDefiningOp()->setAttr(
121 "sv.namehint", rewriter.getStringAttr("dataNotAvailable"));
122 } else if (chanInput.getType().getSignaling() == ChannelSignaling::FIFO) {
123 auto unwrapPull = rewriter.create<UnwrapFIFOOp>(loc, chanInput, inputEn);
124 rawData = unwrapPull.getData();
125 dataNotAvailable = unwrapPull.getEmpty();
126 } else {
127 return rewriter.notifyMatchFailure(
128 op, "only supports ValidReady and FIFO signaling");
129 }
130
131 Backedge outputRdEn = bb.get(i1);
132 auto seqFifo = rewriter.create<seq::FIFOOp>(
133 loc, outputType.getInner(), i1, i1, Type(), Type(), rawData, outputRdEn,
134 inputEn, op.getClk(), op.getRst(), op.getDepthAttr(),
135 rewriter.getI64IntegerAttr(outputType.getDataDelay()), IntegerAttr(),
136 IntegerAttr());
137 auto inputNotEmpty = rewriter.create<comb::XorOp>(loc, dataNotAvailable, c1);
138 inputNotEmpty->setAttr("sv.namehint",
139 rewriter.getStringAttr("inputNotEmpty"));
140 auto seqFifoNotFull =
141 rewriter.create<comb::XorOp>(loc, seqFifo.getFull(), c1);
142 seqFifoNotFull->setAttr("sv.namehint",
143 rewriter.getStringAttr("seqFifoNotFull"));
144 inputEn.setValue(
145 rewriter.create<comb::AndOp>(loc, inputNotEmpty, seqFifoNotFull));
146 static_cast<Value>(inputEn).getDefiningOp()->setAttr(
147 "sv.namehint", rewriter.getStringAttr("inputEn"));
148
149 Value output;
150 if (outputType.getSignaling() == ChannelSignaling::ValidReady) {
151 auto wrap = rewriter.create<WrapValidReadyOp>(
152 loc, mlir::TypeRange{outputType, i1}, seqFifo.getOutput(),
153 comb::createOrFoldNot(loc, seqFifo.getEmpty(), rewriter,
154 /*twoState=*/true));
155 output = wrap.getChanOutput();
156 outputRdEn.setValue(
157 rewriter.create<comb::AndOp>(loc, wrap.getValid(), wrap.getReady()));
158 static_cast<Value>(outputRdEn)
159 .getDefiningOp()
160 ->setAttr("sv.namehint", rewriter.getStringAttr("outputRdEn"));
161 } else if (outputType.getSignaling() == ChannelSignaling::FIFO) {
162 auto wrap =
163 rewriter.create<WrapFIFOOp>(loc, mlir::TypeRange{outputType, i1},
164 seqFifo.getOutput(), seqFifo.getEmpty());
165 output = wrap.getChanOutput();
166 outputRdEn.setValue(wrap.getRden());
167 } else {
168 return rewriter.notifyMatchFailure(op, "only supports ValidReady and FIFO");
169 }
170
171 rewriter.replaceOp(op, output);
172 return success();
173}
174
175namespace {
176/// Lower pure modules into hw.modules.
177struct PureModuleLowering : public OpConversionPattern<ESIPureModuleOp> {
178public:
179 using OpConversionPattern::OpConversionPattern;
180
181 LogicalResult
182 matchAndRewrite(ESIPureModuleOp pureMod, OpAdaptor adaptor,
183 ConversionPatternRewriter &rewriter) const final;
184};
185} // anonymous namespace
186
187LogicalResult
188PureModuleLowering::matchAndRewrite(ESIPureModuleOp pureMod, OpAdaptor adaptor,
189 ConversionPatternRewriter &rewriter) const {
190 auto loc = pureMod.getLoc();
191 Block *body = &pureMod.getBody().front();
192
193 // Track existing names (so we can de-dup) and get op result when we want to
194 // replace it with the block args.
195 DenseMap<StringAttr, ESIPureModuleInputOp> inputPortNames;
196 // Build the port list for `hw.module` construction.
197 SmallVector<hw::PortInfo> ports;
198 // List the input and output ops.
199 SmallVector<ESIPureModuleInputOp> inputs;
200 SmallVector<ESIPureModuleOutputOp> outputs;
201 SmallVector<Attribute> params;
202
203 for (Operation &op : llvm::make_early_inc_range(body->getOperations())) {
204 if (auto port = dyn_cast<ESIPureModuleInputOp>(op)) {
205 // If we already have an input port of the same name, replace the result
206 // value with the previous one. Checking that the types match is done in
207 // the pure module verifier.
208 auto existingPort = inputPortNames.find(port.getNameAttr());
209 if (existingPort != inputPortNames.end()) {
210 rewriter.replaceAllUsesWith(port.getResult(),
211 existingPort->getSecond().getResult());
212 rewriter.eraseOp(port);
213 continue;
214 }
215 // Normal port construction.
216 ports.push_back(
217 hw::PortInfo{{port.getNameAttr(), port.getResult().getType(),
218 hw::ModulePort::Direction::Input},
219 inputs.size(),
220 {},
221 port.getLoc()});
222 inputs.push_back(port);
223 } else if (auto port = dyn_cast<ESIPureModuleOutputOp>(op)) {
224 ports.push_back(
225 hw::PortInfo{{port.getNameAttr(), port.getValue().getType(),
226 hw::ModulePort::Direction::Output},
227 outputs.size(),
228 {},
229 port.getLoc()});
230 outputs.push_back(port);
231 } else if (auto param = dyn_cast<ESIPureModuleParamOp>(op)) {
232 params.push_back(
233 ParamDeclAttr::get(param.getNameAttr(), param.getType()));
234 rewriter.eraseOp(param);
235 }
236 }
237
238 // Create the replacement `hw.module`.
239 auto hwMod = rewriter.create<hw::HWModuleOp>(
240 loc, pureMod.getNameAttr(), ports, ArrayAttr::get(getContext(), params));
241 hwMod->setDialectAttrs(pureMod->getDialectAttrs());
242 rewriter.eraseBlock(hwMod.getBodyBlock());
243 rewriter.inlineRegionBefore(*body->getParent(), hwMod.getBodyRegion(),
244 hwMod.getBodyRegion().end());
245 body = hwMod.getBodyBlock();
246
247 // Re-wire the inputs and erase them.
248 for (auto input : inputs) {
249 BlockArgument newArg;
250 rewriter.modifyOpInPlace(hwMod, [&]() {
251 newArg = body->addArgument(input.getResult().getType(), input.getLoc());
252 });
253 rewriter.replaceAllUsesWith(input.getResult(), newArg);
254 rewriter.eraseOp(input);
255 }
256
257 // Assemble the output values.
258 SmallVector<Value> hwOutputOperands;
259 for (auto output : outputs) {
260 hwOutputOperands.push_back(output.getValue());
261 rewriter.eraseOp(output);
262 }
263 rewriter.setInsertionPointToEnd(body);
264 rewriter.create<hw::OutputOp>(pureMod.getLoc(), hwOutputOperands);
265
266 // Erase the original op.
267 rewriter.eraseOp(pureMod);
268 return success();
269}
270
271namespace {
272/// Run all the physical lowerings.
273struct ESIToPhysicalPass
274 : public circt::esi::impl::LowerESIToPhysicalBase<ESIToPhysicalPass> {
275 void runOnOperation() override;
276};
277} // anonymous namespace
278
279void ESIToPhysicalPass::runOnOperation() {
280 // Set up a conversion and give it a set of laws.
281 ConversionTarget target(getContext());
282 target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
283 target.addIllegalOp<ChannelBufferOp, ESIPureModuleOp, FIFOOp>();
284
285 // Add all the conversion patterns.
286 RewritePatternSet patterns(&getContext());
287 patterns.insert<ChannelBufferLowering, PureModuleLowering, FIFOLowering>(
288 &getContext());
289
290 // Run the conversion.
291 if (failed(
292 applyPartialConversion(getOperation(), target, std::move(patterns))))
293 signalPassFailure();
294}
295
296std::unique_ptr<OperationPass<ModuleOp>>
298 return std::make_unique<ESIToPhysicalPass>();
299}
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))
Instantiate one of these and use it to build typed backedges.
Backedge is a wrapper class around a Value.
std::unique_ptr< OperationPass< ModuleOp > > createESIPhysicalLoweringPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition esi.py:1
This holds the name, type, direction of a module's ports.