CIRCT 24.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. Changes in data delay are made after the
53/// stages.
54LogicalResult ChannelBufferLowering::matchAndRewrite(
55 ChannelBufferOp buffer, OpAdaptor adaptor,
56 ConversionPatternRewriter &rewriter) const {
57 auto loc = buffer.getLoc();
58
59 ChannelType inputType = buffer.getInput().getType();
60 Value stageInput = buffer.getInput();
61 // The stages carry the input data delay.
62 auto stageType =
63 ChannelType::get(rewriter.getContext(), inputType.getInner(),
64 ChannelSignaling::ValidReady, inputType.getDataDelay());
65
66 // FIFO signaling must be converted to ValidReady.
67 if (inputType.getSignaling() == ChannelSignaling::FIFO) {
68 BackedgeBuilder bb(rewriter, loc);
69 Backedge rdEn = bb.get(rewriter.getI1Type());
70 Backedge valid = bb.get(rewriter.getI1Type());
71
72 auto unwrap = UnwrapFIFOOp::create(rewriter, loc, stageInput, rdEn);
73 auto wrap =
74 WrapValidReadyOp::create(rewriter, loc, stageType, rewriter.getI1Type(),
75 unwrap.getData(), valid);
76 stageInput = wrap.getChanOutput();
77
78 // rdEn = valid && ready
79 rdEn.setValue(comb::AndOp::create(rewriter, loc, wrap.getReady(), valid));
80 // valid = !empty
81 valid.setValue(comb::XorOp::create(
82 rewriter, loc, unwrap.getEmpty(),
83 hw::ConstantOp::create(rewriter, loc, rewriter.getBoolAttr(true))));
84 }
85
86 // ValidOnly signaling must be converted to ValidReady (always ready).
87 if (inputType.getSignaling() == ChannelSignaling::ValidOnly) {
88 auto unwrap = UnwrapValidOnlyOp::create(rewriter, loc, stageInput);
89 auto wrap =
90 WrapValidReadyOp::create(rewriter, loc, stageType, rewriter.getI1Type(),
91 unwrap.getRawOutput(), unwrap.getValid());
92 stageInput = wrap.getChanOutput();
93 }
94
95 // Expand 'abstract' buffer into 'physical' stages.
96 auto stages = buffer.getStagesAttr();
97 uint64_t numStages = 1;
98 if (stages)
99 // Guaranteed positive by the parser.
100 numStages = stages.getValue().getLimitedValue();
101 StringAttr bufferName = buffer.getNameAttr();
102
103 for (uint64_t i = 0; i < numStages; ++i) {
104 // Create the stages, connecting them up as we build.
105 auto stage = PipelineStageOp::create(rewriter, loc, buffer.getClk(),
106 buffer.getRst(), stageInput);
107 if (bufferName) {
108 SmallString<64> stageName(
109 {bufferName.getValue(), "_stage", std::to_string(i)});
110 stage->setAttr("name", StringAttr::get(rewriter.getContext(), stageName));
111 }
112 stageInput = stage;
113 }
114
115 ChannelType outputType = buffer.getOutput().getType();
116 Value output = stageInput;
117 uint64_t inDelay = inputType.getDataDelay();
118 uint64_t outDelay = outputType.getDataDelay();
119 auto i1 = rewriter.getI1Type();
120 auto regName = [&](StringRef suffix, uint64_t i) -> std::string {
121 std::string name = (suffix + Twine(i)).str();
122 if (bufferName)
123 name = (bufferName.getValue() + "_" + name).str();
124 return name;
125 };
126
127 if (outDelay > inDelay) {
128 // Delay the data so that it arrives 'outDelay' cycles after the transfer.
129 BackedgeBuilder bb(rewriter, loc);
130 Backedge ready = bb.get(i1);
131 auto unwrap = UnwrapValidReadyOp::create(rewriter, loc, output, ready);
132 Value data = unwrap.getRawOutput();
133 // TODO: if outDelay >>> inDelay it might make sense to use a FIFO with
134 // rd_latency.
135 for (uint64_t i = 0; i < outDelay - inDelay; ++i)
136 data = seq::CompRegOp::create(rewriter, loc, data, buffer.getClk(),
137 regName("data_dly", i), InnerSymAttr());
138 auto wrap = WrapValidReadyOp::create(rewriter, loc, data, unwrap.getValid(),
139 outDelay);
140 ready.setValue(wrap.getReady());
141 output = wrap.getChanOutput();
142 } else if (outDelay < inDelay &&
143 outputType.getSignaling() == ChannelSignaling::ValidOnly) {
144 // With no backpressure, tokens never wait. Delay the valid so that the
145 // data arrives 'outDelay' cycles after it.
146 auto alwaysReady = hw::ConstantOp::create(rewriter, loc, i1, 1);
147 auto unwrap =
148 UnwrapValidReadyOp::create(rewriter, loc, output, alwaysReady);
149 Value valid = unwrap.getValid();
150 Value false_ = hw::ConstantOp::create(rewriter, loc, i1, 0);
151 for (uint64_t i = 0; i < inDelay - outDelay; ++i)
152 valid = seq::CompRegOp::create(rewriter, loc, valid, buffer.getClk(),
153 buffer.getRst(), false_,
154 regName("valid_dly", i), InnerSymAttr());
155 output = WrapValidOnlyOp::create(rewriter, loc, unwrap.getRawOutput(),
156 valid, outDelay)
157 .getChanOutput();
158 } else if (outDelay < inDelay) {
159 // Tokens must wait until their data arrives, so hold them in a FIFO. Its
160 // input reserves room for 'inDelay' writes in flight, so it needs that many
161 // extra entries to sustain one transfer per cycle after an output stall.
162 uint64_t depth = 2 * inDelay + std::max<uint64_t>(outDelay, 1) + 1;
163 output = FIFOOp::create(rewriter, loc, outputType, buffer.getClk(),
164 buffer.getRst(), output,
165 rewriter.getI64IntegerAttr(depth));
166 }
167
168 // If the output is FIFO, we need to wrap it back into a FIFO from ValidReady.
169 if (output.getType() != outputType &&
170 outputType.getSignaling() == ChannelSignaling::FIFO) {
171 BackedgeBuilder bb(rewriter, loc);
172 Backedge ready = bb.get(rewriter.getI1Type());
173 Backedge empty = bb.get(rewriter.getI1Type());
174
175 auto unwrap = UnwrapValidReadyOp::create(rewriter, loc, output, ready);
176 auto wrap = WrapFIFOOp::create(rewriter, loc,
177 TypeRange{outputType, rewriter.getI1Type()},
178 unwrap.getRawOutput(), empty);
179
180 ready.setValue(wrap.getRden());
181 empty.setValue(comb::XorOp::create(
182 rewriter, loc, unwrap.getValid(),
183 hw::ConstantOp::create(rewriter, loc, rewriter.getBoolAttr(true))));
184 output = wrap.getChanOutput();
185 }
186
187 // If the output is ValidOnly, convert back from ValidReady.
188 if (output.getType() != outputType &&
189 outputType.getSignaling() == ChannelSignaling::ValidOnly) {
190 // Always ready since ValidOnly has no backpressure.
191 auto alwaysReady =
192 hw::ConstantOp::create(rewriter, loc, rewriter.getI1Type(), 1);
193 auto unwrap =
194 UnwrapValidReadyOp::create(rewriter, loc, output, alwaysReady);
195 auto wrap =
196 WrapValidOnlyOp::create(rewriter, loc, unwrap.getRawOutput(),
197 unwrap.getValid(), outputType.getDataDelay());
198 output = wrap.getChanOutput();
199 }
200
201 // Replace the buffer.
202 rewriter.replaceOp(buffer, output);
203 return success();
204}
205
206namespace {
207/// Lower `ChannelBufferOp`s, breaking out the various options. For now, just
208/// replace with the specified number of pipeline stages (since that's the only
209/// option).
210struct FIFOLowering : public OpConversionPattern<FIFOOp> {
211public:
212 using OpConversionPattern::OpConversionPattern;
213
214 LogicalResult
215 matchAndRewrite(FIFOOp, OpAdaptor adaptor,
216 ConversionPatternRewriter &rewriter) const final;
217};
218} // anonymous namespace
219
220LogicalResult
221FIFOLowering::matchAndRewrite(FIFOOp op, OpAdaptor adaptor,
222 ConversionPatternRewriter &rewriter) const {
223 auto loc = op.getLoc();
224 auto outputType = op.getType();
225 BackedgeBuilder bb(rewriter, loc);
226 auto i1 = rewriter.getI1Type();
227 auto c1 = hw::ConstantOp::create(rewriter, loc, rewriter.getI1Type(),
228 rewriter.getBoolAttr(true));
229 mlir::TypedValue<ChannelType> chanInput = op.getInput();
230 uint64_t inputDelay = chanInput.getType().getDataDelay();
231
232 Backedge inputEn = bb.get(i1);
233 Value rawData;
234 Value dataNotAvailable;
235 if (chanInput.getType().getSignaling() == ChannelSignaling::ValidReady) {
236 auto unwrapValidReady =
237 UnwrapValidReadyOp::create(rewriter, loc, chanInput, inputEn);
238 rawData = unwrapValidReady.getRawOutput();
239 dataNotAvailable =
240 comb::createOrFoldNot(rewriter, loc, unwrapValidReady.getValid(),
241 /*twoState=*/true);
242 dataNotAvailable.getDefiningOp()->setAttr(
243 "sv.namehint", rewriter.getStringAttr("dataNotAvailable"));
244 } else if (chanInput.getType().getSignaling() == ChannelSignaling::FIFO) {
245 auto unwrapPull = UnwrapFIFOOp::create(rewriter, loc, chanInput, inputEn);
246 rawData = unwrapPull.getData();
247 dataNotAvailable = unwrapPull.getEmpty();
248 } else if (chanInput.getType().getSignaling() ==
249 ChannelSignaling::ValidOnly) {
250 auto unwrapVO = UnwrapValidOnlyOp::create(rewriter, loc, chanInput);
251 rawData = unwrapVO.getRawOutput();
252 dataNotAvailable = comb::createOrFoldNot(rewriter, loc, unwrapVO.getValid(),
253 /*twoState=*/true);
254 dataNotAvailable.getDefiningOp()->setAttr(
255 "sv.namehint", rewriter.getStringAttr("dataNotAvailable"));
256 } else {
257 return rewriter.notifyMatchFailure(
258 op, "only supports ValidReady, FIFO, and ValidOnly signaling");
259 }
260
261 // With an input data delay of N, the data for an accepted token arrives N
262 // cycles later, so delay the write to match. Since up to N writes may be in
263 // flight, only accept input while more than N slots are free. A ValidOnly
264 // sender can't be stalled though, so instead every token is written. If the
265 // FIFO is full, it overflows, which the `seq.fifo` assertions catch in
266 // simulation.
267 bool validOnlyInput =
268 chanInput.getType().getSignaling() == ChannelSignaling::ValidOnly;
269 bool reserveSlots = inputDelay > 0 && !validOnlyInput;
270 Backedge wrEn = bb.get(i1);
271 Type almostFullType;
272 IntegerAttr almostFullThreshold;
273 if (reserveSlots) {
274 almostFullType = i1;
275 almostFullThreshold =
276 rewriter.getI64IntegerAttr(op.getDepth() - inputDelay);
277 }
278
279 Backedge outputRdEn = bb.get(i1);
280 auto seqFifo = seq::FIFOOp::create(
281 rewriter, loc, outputType.getInner(), i1, i1, almostFullType, Type(),
282 rawData, outputRdEn, wrEn, op.getClk(), op.getRst(), op.getDepthAttr(),
283 rewriter.getI64IntegerAttr(outputType.getDataDelay()),
284 almostFullThreshold, IntegerAttr());
285 auto inputNotEmpty = comb::XorOp::create(rewriter, loc, dataNotAvailable, c1);
286 inputNotEmpty->setAttr("sv.namehint",
287 rewriter.getStringAttr("inputNotEmpty"));
288 auto seqFifoNotFull = comb::XorOp::create(
289 rewriter, loc, reserveSlots ? seqFifo.getAlmostFull() : seqFifo.getFull(),
290 c1);
291 seqFifoNotFull->setAttr("sv.namehint",
292 rewriter.getStringAttr("seqFifoNotFull"));
293 inputEn.setValue(
294 comb::AndOp::create(rewriter, loc, inputNotEmpty, seqFifoNotFull));
295 static_cast<Value>(inputEn).getDefiningOp()->setAttr(
296 "sv.namehint", rewriter.getStringAttr("inputEn"));
297
298 Value write = validOnlyInput ? Value(inputNotEmpty) : Value(inputEn);
299 if (inputDelay > 0) {
300 Value false_ = hw::ConstantOp::create(rewriter, loc, i1, 0);
301 for (uint64_t i = 0; i < inputDelay; ++i)
303 rewriter, loc, write, op.getClk(), op.getRst(), false_,
304 (validOnlyInput ? "valid_dly" : "inputEn_dly") + Twine(i),
305 InnerSymAttr());
306 }
307 wrEn.setValue(write);
308
309 Value output;
310 if (outputType.getSignaling() == ChannelSignaling::ValidReady) {
311 auto wrap = WrapValidReadyOp::create(
312 rewriter, loc, mlir::TypeRange{outputType, i1}, seqFifo.getOutput(),
313 comb::createOrFoldNot(rewriter, loc, seqFifo.getEmpty(),
314 /*twoState=*/true));
315 output = wrap.getChanOutput();
316 outputRdEn.setValue(
317 comb::AndOp::create(rewriter, loc, wrap.getValid(), wrap.getReady()));
318 static_cast<Value>(outputRdEn)
319 .getDefiningOp()
320 ->setAttr("sv.namehint", rewriter.getStringAttr("outputRdEn"));
321 } else if (outputType.getSignaling() == ChannelSignaling::FIFO) {
322 auto wrap =
323 WrapFIFOOp::create(rewriter, loc, mlir::TypeRange{outputType, i1},
324 seqFifo.getOutput(), seqFifo.getEmpty());
325 output = wrap.getChanOutput();
326 outputRdEn.setValue(wrap.getRden());
327 } else if (outputType.getSignaling() == ChannelSignaling::ValidOnly) {
328 // ValidOnly output: wrap with valid signal, always read from FIFO when
329 // data is available (no backpressure).
330 auto notEmpty = comb::createOrFoldNot(rewriter, loc, seqFifo.getEmpty(),
331 /*twoState=*/true);
332 auto wrap = WrapValidOnlyOp::create(rewriter, loc, seqFifo.getOutput(),
333 notEmpty, outputType.getDataDelay());
334 output = wrap.getChanOutput();
335 // Always read when not empty since there's no backpressure.
336 outputRdEn.setValue(notEmpty);
337 } else {
338 return rewriter.notifyMatchFailure(
339 op, "only supports ValidReady, FIFO, and ValidOnly");
340 }
341
342 rewriter.replaceOp(op, output);
343 return success();
344}
345
346namespace {
347/// Lower pure modules into hw.modules.
348struct PureModuleLowering : public OpConversionPattern<ESIPureModuleOp> {
349public:
350 using OpConversionPattern::OpConversionPattern;
351
352 LogicalResult
353 matchAndRewrite(ESIPureModuleOp pureMod, OpAdaptor adaptor,
354 ConversionPatternRewriter &rewriter) const final;
355};
356} // anonymous namespace
357
358LogicalResult
359PureModuleLowering::matchAndRewrite(ESIPureModuleOp pureMod, OpAdaptor adaptor,
360 ConversionPatternRewriter &rewriter) const {
361 auto loc = pureMod.getLoc();
362 Block *body = &pureMod.getBody().front();
363
364 // Track existing names (so we can de-dup) and get op result when we want to
365 // replace it with the block args.
366 DenseMap<StringAttr, ESIPureModuleInputOp> inputPortNames;
367 // Build the port list for `hw.module` construction.
368 SmallVector<hw::PortInfo> ports;
369 // List the input and output ops.
370 SmallVector<ESIPureModuleInputOp> inputs;
371 SmallVector<ESIPureModuleOutputOp> outputs;
372 SmallVector<Attribute> params;
373
374 for (Operation &op : llvm::make_early_inc_range(body->getOperations())) {
375 if (auto port = dyn_cast<ESIPureModuleInputOp>(op)) {
376 // If we already have an input port of the same name, replace the result
377 // value with the previous one. Checking that the types match is done in
378 // the pure module verifier.
379 auto existingPort = inputPortNames.find(port.getNameAttr());
380 if (existingPort != inputPortNames.end()) {
381 rewriter.replaceOp(port, existingPort->getSecond().getResult());
382 continue;
383 }
384 // Normal port construction.
385 ports.push_back(
386 hw::PortInfo{{port.getNameAttr(), port.getResult().getType(),
387 hw::ModulePort::Direction::Input},
388 inputs.size(),
389 {},
390 port.getLoc()});
391 inputs.push_back(port);
392 } else if (auto port = dyn_cast<ESIPureModuleOutputOp>(op)) {
393 ports.push_back(
394 hw::PortInfo{{port.getNameAttr(), port.getValue().getType(),
395 hw::ModulePort::Direction::Output},
396 outputs.size(),
397 {},
398 port.getLoc()});
399 outputs.push_back(port);
400 } else if (auto param = dyn_cast<ESIPureModuleParamOp>(op)) {
401 params.push_back(
402 ParamDeclAttr::get(param.getNameAttr(), param.getType()));
403 rewriter.eraseOp(param);
404 }
405 }
406
407 // Create the replacement `hw.module`.
408 auto hwMod =
409 hw::HWModuleOp::create(rewriter, loc, pureMod.getNameAttr(), ports,
410 ArrayAttr::get(getContext(), params));
411 hwMod->setDialectAttrs(pureMod->getDialectAttrs());
412 rewriter.eraseBlock(hwMod.getBodyBlock());
413 rewriter.inlineRegionBefore(*body->getParent(), hwMod.getBodyRegion(),
414 hwMod.getBodyRegion().end());
415 body = hwMod.getBodyBlock();
416
417 // Re-wire the inputs and erase them.
418 for (auto input : inputs) {
419 BlockArgument newArg;
420 rewriter.modifyOpInPlace(hwMod, [&]() {
421 newArg = body->addArgument(input.getResult().getType(), input.getLoc());
422 });
423 rewriter.replaceOp(input, newArg);
424 }
425
426 // Assemble the output values.
427 SmallVector<Value> hwOutputOperands;
428 for (auto output : outputs) {
429 hwOutputOperands.push_back(output.getValue());
430 rewriter.eraseOp(output);
431 }
432 rewriter.setInsertionPointToEnd(body);
433 hw::OutputOp::create(rewriter, pureMod.getLoc(), hwOutputOperands);
434
435 // Erase the original op.
436 rewriter.eraseOp(pureMod);
437 return success();
438}
439
440namespace {
441/// Run all the physical lowerings.
442struct ESIToPhysicalPass
443 : public circt::esi::impl::LowerESIToPhysicalBase<ESIToPhysicalPass> {
444 void runOnOperation() override;
445};
446} // anonymous namespace
447
448void ESIToPhysicalPass::runOnOperation() {
449 // Set up a conversion and give it a set of laws.
450 ConversionTarget target(getContext());
451 target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
452 target.addIllegalOp<ChannelBufferOp, ESIPureModuleOp, FIFOOp>();
453
454 // Add all the conversion patterns.
455 RewritePatternSet patterns(&getContext());
456 patterns.insert<ChannelBufferLowering, PureModuleLowering, FIFOLowering>(
457 &getContext());
458
459 // Run the conversion.
460 if (failed(
461 applyPartialConversion(getOperation(), target, std::move(patterns))))
462 signalPassFailure();
463}
464
465std::unique_ptr<OperationPass<ModuleOp>>
467 return std::make_unique<ESIToPhysicalPass>();
468}
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))
return success()
static EvaluatorValuePtr unwrap(OMEvaluatorValue c)
Definition OM.cpp:111
static InstancePath empty
void setValue(mlir::Value)
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
std::unique_ptr< OperationPass< ModuleOp > > createESIPhysicalLoweringPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
write(addr, data)
Definition xrt_cosim.py:30