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