Loading [MathJax]/extensions/tex2jax.js
CIRCT 22.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
50/// Since as of now the only construct supported by the StageOp, we convert FIFO
51/// signaling to/from ValidReady signaling. This will be done opposite way when
52/// we implement FIFO-based buffers.
53LogicalResult ChannelBufferLowering::matchAndRewrite(
54 ChannelBufferOp buffer, OpAdaptor adaptor,
55 ConversionPatternRewriter &rewriter) const {
56 auto loc = buffer.getLoc();
57
58 ChannelType inputType = buffer.getInput().getType();
59 Value stageInput = buffer.getInput();
60
61 // FIFO signaling must be converted to ValidReady.
62 if (inputType.getSignaling() == ChannelSignaling::FIFO) {
63 BackedgeBuilder bb(rewriter, loc);
64 Backedge rdEn = bb.get(rewriter.getI1Type());
65 Backedge valid = bb.get(rewriter.getI1Type());
66
67 auto unwrap = rewriter.create<UnwrapFIFOOp>(loc, stageInput, rdEn);
68 auto wrap = rewriter.create<WrapValidReadyOp>(loc, unwrap.getData(), valid);
69 stageInput = wrap.getChanOutput();
70
71 // rdEn = valid && ready
72 rdEn.setValue(rewriter.create<comb::AndOp>(loc, wrap.getReady(), valid));
73 // valid = !empty
74 valid.setValue(rewriter.create<comb::XorOp>(
75 loc, unwrap.getEmpty(),
76 rewriter.create<hw::ConstantOp>(loc, rewriter.getBoolAttr(true))));
77 }
78
79 // Expand 'abstract' buffer into 'physical' stages.
80 auto stages = buffer.getStagesAttr();
81 uint64_t numStages = 1;
82 if (stages)
83 // Guaranteed positive by the parser.
84 numStages = stages.getValue().getLimitedValue();
85 StringAttr bufferName = buffer.getNameAttr();
86
87 for (uint64_t i = 0; i < numStages; ++i) {
88 // Create the stages, connecting them up as we build.
89 auto stage = rewriter.create<PipelineStageOp>(loc, buffer.getClk(),
90 buffer.getRst(), stageInput);
91 if (bufferName) {
92 SmallString<64> stageName(
93 {bufferName.getValue(), "_stage", std::to_string(i)});
94 stage->setAttr("name", StringAttr::get(rewriter.getContext(), stageName));
95 }
96 stageInput = stage;
97 }
98
99 ChannelType outputType = buffer.getOutput().getType();
100 Value output = stageInput;
101 // If the output is FIFO, we need to wrap it back into a FIFO from ValidReady.
102 if (outputType.getSignaling() == ChannelSignaling::FIFO) {
103 BackedgeBuilder bb(rewriter, loc);
104 Backedge ready = bb.get(rewriter.getI1Type());
105 Backedge empty = bb.get(rewriter.getI1Type());
106
107 auto unwrap = rewriter.create<UnwrapValidReadyOp>(loc, stageInput, ready);
108 auto wrap = rewriter.create<WrapFIFOOp>(
109 loc, TypeRange{outputType, rewriter.getI1Type()}, unwrap.getRawOutput(),
110 empty);
111
112 ready.setValue(wrap.getRden());
113 empty.setValue(rewriter.create<comb::XorOp>(
114 loc, unwrap.getValid(),
115 rewriter.create<hw::ConstantOp>(loc, rewriter.getBoolAttr(true))));
116 output = wrap.getChanOutput();
117 }
118
119 // Replace the buffer.
120 rewriter.replaceOp(buffer, output);
121 return success();
122}
123
124namespace {
125/// Lower `ChannelBufferOp`s, breaking out the various options. For now, just
126/// replace with the specified number of pipeline stages (since that's the only
127/// option).
128struct FIFOLowering : public OpConversionPattern<FIFOOp> {
129public:
130 using OpConversionPattern::OpConversionPattern;
131
132 LogicalResult
133 matchAndRewrite(FIFOOp, OpAdaptor adaptor,
134 ConversionPatternRewriter &rewriter) const final;
135};
136} // anonymous namespace
137
138LogicalResult
139FIFOLowering::matchAndRewrite(FIFOOp op, OpAdaptor adaptor,
140 ConversionPatternRewriter &rewriter) const {
141 auto loc = op.getLoc();
142 auto outputType = op.getType();
143 BackedgeBuilder bb(rewriter, loc);
144 auto i1 = rewriter.getI1Type();
145 auto c1 = rewriter.create<hw::ConstantOp>(loc, rewriter.getI1Type(),
146 rewriter.getBoolAttr(true));
147 mlir::TypedValue<ChannelType> chanInput = op.getInput();
148 if (chanInput.getType().getDataDelay() != 0)
149 return op.emitOpError(
150 "currently only supports input channels with zero data delay");
151
152 Backedge inputEn = bb.get(i1);
153 Value rawData;
154 Value dataNotAvailable;
155 if (chanInput.getType().getSignaling() == ChannelSignaling::ValidReady) {
156 auto unwrapValidReady =
157 rewriter.create<UnwrapValidReadyOp>(loc, chanInput, inputEn);
158 rawData = unwrapValidReady.getRawOutput();
159 dataNotAvailable = comb::createOrFoldNot(loc, unwrapValidReady.getValid(),
160 rewriter, /*twoState=*/true);
161 dataNotAvailable.getDefiningOp()->setAttr(
162 "sv.namehint", rewriter.getStringAttr("dataNotAvailable"));
163 } else if (chanInput.getType().getSignaling() == ChannelSignaling::FIFO) {
164 auto unwrapPull = rewriter.create<UnwrapFIFOOp>(loc, chanInput, inputEn);
165 rawData = unwrapPull.getData();
166 dataNotAvailable = unwrapPull.getEmpty();
167 } else {
168 return rewriter.notifyMatchFailure(
169 op, "only supports ValidReady and FIFO signaling");
170 }
171
172 Backedge outputRdEn = bb.get(i1);
173 auto seqFifo = rewriter.create<seq::FIFOOp>(
174 loc, outputType.getInner(), i1, i1, Type(), Type(), rawData, outputRdEn,
175 inputEn, op.getClk(), op.getRst(), op.getDepthAttr(),
176 rewriter.getI64IntegerAttr(outputType.getDataDelay()), IntegerAttr(),
177 IntegerAttr());
178 auto inputNotEmpty = rewriter.create<comb::XorOp>(loc, dataNotAvailable, c1);
179 inputNotEmpty->setAttr("sv.namehint",
180 rewriter.getStringAttr("inputNotEmpty"));
181 auto seqFifoNotFull =
182 rewriter.create<comb::XorOp>(loc, seqFifo.getFull(), c1);
183 seqFifoNotFull->setAttr("sv.namehint",
184 rewriter.getStringAttr("seqFifoNotFull"));
185 inputEn.setValue(
186 rewriter.create<comb::AndOp>(loc, inputNotEmpty, seqFifoNotFull));
187 static_cast<Value>(inputEn).getDefiningOp()->setAttr(
188 "sv.namehint", rewriter.getStringAttr("inputEn"));
189
190 Value output;
191 if (outputType.getSignaling() == ChannelSignaling::ValidReady) {
192 auto wrap = rewriter.create<WrapValidReadyOp>(
193 loc, mlir::TypeRange{outputType, i1}, seqFifo.getOutput(),
194 comb::createOrFoldNot(loc, seqFifo.getEmpty(), rewriter,
195 /*twoState=*/true));
196 output = wrap.getChanOutput();
197 outputRdEn.setValue(
198 rewriter.create<comb::AndOp>(loc, wrap.getValid(), wrap.getReady()));
199 static_cast<Value>(outputRdEn)
200 .getDefiningOp()
201 ->setAttr("sv.namehint", rewriter.getStringAttr("outputRdEn"));
202 } else if (outputType.getSignaling() == ChannelSignaling::FIFO) {
203 auto wrap =
204 rewriter.create<WrapFIFOOp>(loc, mlir::TypeRange{outputType, i1},
205 seqFifo.getOutput(), seqFifo.getEmpty());
206 output = wrap.getChanOutput();
207 outputRdEn.setValue(wrap.getRden());
208 } else {
209 return rewriter.notifyMatchFailure(op, "only supports ValidReady and FIFO");
210 }
211
212 rewriter.replaceOp(op, output);
213 return success();
214}
215
216namespace {
217/// Lower pure modules into hw.modules.
218struct PureModuleLowering : public OpConversionPattern<ESIPureModuleOp> {
219public:
220 using OpConversionPattern::OpConversionPattern;
221
222 LogicalResult
223 matchAndRewrite(ESIPureModuleOp pureMod, OpAdaptor adaptor,
224 ConversionPatternRewriter &rewriter) const final;
225};
226} // anonymous namespace
227
228LogicalResult
229PureModuleLowering::matchAndRewrite(ESIPureModuleOp pureMod, OpAdaptor adaptor,
230 ConversionPatternRewriter &rewriter) const {
231 auto loc = pureMod.getLoc();
232 Block *body = &pureMod.getBody().front();
233
234 // Track existing names (so we can de-dup) and get op result when we want to
235 // replace it with the block args.
236 DenseMap<StringAttr, ESIPureModuleInputOp> inputPortNames;
237 // Build the port list for `hw.module` construction.
238 SmallVector<hw::PortInfo> ports;
239 // List the input and output ops.
240 SmallVector<ESIPureModuleInputOp> inputs;
241 SmallVector<ESIPureModuleOutputOp> outputs;
242 SmallVector<Attribute> params;
243
244 for (Operation &op : llvm::make_early_inc_range(body->getOperations())) {
245 if (auto port = dyn_cast<ESIPureModuleInputOp>(op)) {
246 // If we already have an input port of the same name, replace the result
247 // value with the previous one. Checking that the types match is done in
248 // the pure module verifier.
249 auto existingPort = inputPortNames.find(port.getNameAttr());
250 if (existingPort != inputPortNames.end()) {
251 rewriter.replaceAllUsesWith(port.getResult(),
252 existingPort->getSecond().getResult());
253 rewriter.eraseOp(port);
254 continue;
255 }
256 // Normal port construction.
257 ports.push_back(
258 hw::PortInfo{{port.getNameAttr(), port.getResult().getType(),
259 hw::ModulePort::Direction::Input},
260 inputs.size(),
261 {},
262 port.getLoc()});
263 inputs.push_back(port);
264 } else if (auto port = dyn_cast<ESIPureModuleOutputOp>(op)) {
265 ports.push_back(
266 hw::PortInfo{{port.getNameAttr(), port.getValue().getType(),
267 hw::ModulePort::Direction::Output},
268 outputs.size(),
269 {},
270 port.getLoc()});
271 outputs.push_back(port);
272 } else if (auto param = dyn_cast<ESIPureModuleParamOp>(op)) {
273 params.push_back(
274 ParamDeclAttr::get(param.getNameAttr(), param.getType()));
275 rewriter.eraseOp(param);
276 }
277 }
278
279 // Create the replacement `hw.module`.
280 auto hwMod = rewriter.create<hw::HWModuleOp>(
281 loc, pureMod.getNameAttr(), ports, ArrayAttr::get(getContext(), params));
282 hwMod->setDialectAttrs(pureMod->getDialectAttrs());
283 rewriter.eraseBlock(hwMod.getBodyBlock());
284 rewriter.inlineRegionBefore(*body->getParent(), hwMod.getBodyRegion(),
285 hwMod.getBodyRegion().end());
286 body = hwMod.getBodyBlock();
287
288 // Re-wire the inputs and erase them.
289 for (auto input : inputs) {
290 BlockArgument newArg;
291 rewriter.modifyOpInPlace(hwMod, [&]() {
292 newArg = body->addArgument(input.getResult().getType(), input.getLoc());
293 });
294 rewriter.replaceAllUsesWith(input.getResult(), newArg);
295 rewriter.eraseOp(input);
296 }
297
298 // Assemble the output values.
299 SmallVector<Value> hwOutputOperands;
300 for (auto output : outputs) {
301 hwOutputOperands.push_back(output.getValue());
302 rewriter.eraseOp(output);
303 }
304 rewriter.setInsertionPointToEnd(body);
305 rewriter.create<hw::OutputOp>(pureMod.getLoc(), hwOutputOperands);
306
307 // Erase the original op.
308 rewriter.eraseOp(pureMod);
309 return success();
310}
311
312namespace {
313/// Run all the physical lowerings.
314struct ESIToPhysicalPass
315 : public circt::esi::impl::LowerESIToPhysicalBase<ESIToPhysicalPass> {
316 void runOnOperation() override;
317};
318} // anonymous namespace
319
320void ESIToPhysicalPass::runOnOperation() {
321 // Set up a conversion and give it a set of laws.
322 ConversionTarget target(getContext());
323 target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
324 target.addIllegalOp<ChannelBufferOp, ESIPureModuleOp, FIFOOp>();
325
326 // Add all the conversion patterns.
327 RewritePatternSet patterns(&getContext());
328 patterns.insert<ChannelBufferLowering, PureModuleLowering, FIFOLowering>(
329 &getContext());
330
331 // Run the conversion.
332 if (failed(
333 applyPartialConversion(getOperation(), target, std::move(patterns))))
334 signalPassFailure();
335}
336
337std::unique_ptr<OperationPass<ModuleOp>>
339 return std::make_unique<ESIToPhysicalPass>();
340}
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))
static InstancePath empty
static EvaluatorValuePtr unwrap(OMEvaluatorValue c)
Definition OM.cpp:111
Instantiate one of these and use it to build typed backedges.
Backedge is a wrapper class around a Value.
void setValue(mlir::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.