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