CIRCT  20.0.0git
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 
17 #include "circt/Dialect/HW/HWOps.h"
20 #include "circt/Support/LLVM.h"
21 
22 #include "mlir/Transforms/DialectConversion.h"
23 
24 namespace circt {
25 namespace esi {
26 #define GEN_PASS_DEF_LOWERESITOPHYSICAL
27 #include "circt/Dialect/ESI/ESIPasses.h.inc"
28 } // namespace esi
29 } // namespace circt
30 
31 using namespace circt;
32 using namespace circt::esi;
33 using namespace circt::esi::detail;
34 using namespace circt::hw;
35 
36 namespace {
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).
40 struct ChannelBufferLowering : public OpConversionPattern<ChannelBufferOp> {
41 public:
42  using OpConversionPattern::OpConversionPattern;
43 
44  LogicalResult
45  matchAndRewrite(ChannelBufferOp buffer, OpAdaptor adaptor,
46  ConversionPatternRewriter &rewriter) const final;
47 };
48 } // anonymous namespace
49 
50 LogicalResult 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 
83 namespace {
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).
87 struct FIFOLowering : public OpConversionPattern<FIFOOp> {
88 public:
89  using OpConversionPattern::OpConversionPattern;
90 
91  LogicalResult
92  matchAndRewrite(FIFOOp, OpAdaptor adaptor,
93  ConversionPatternRewriter &rewriter) const final;
94 };
95 } // anonymous namespace
96 
97 LogicalResult
98 FIFOLowering::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 
175 namespace {
176 /// Lower pure modules into hw.modules.
177 struct PureModuleLowering : public OpConversionPattern<ESIPureModuleOp> {
178 public:
179  using OpConversionPattern::OpConversionPattern;
180 
181  LogicalResult
182  matchAndRewrite(ESIPureModuleOp pureMod, OpAdaptor adaptor,
183  ConversionPatternRewriter &rewriter) const final;
184 };
185 } // anonymous namespace
186 
187 LogicalResult
188 PureModuleLowering::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(),
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(),
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 
271 namespace {
272 /// Run all the physical lowerings.
273 struct ESIToPhysicalPass
274  : public circt::esi::impl::LowerESIToPhysicalBase<ESIToPhysicalPass> {
275  void runOnOperation() override;
276 };
277 } // anonymous namespace
278 
279 void 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 
296 std::unique_ptr<OperationPass<ModuleOp>>
298  return std::make_unique<ESIToPhysicalPass>();
299 }
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))
@ Input
Definition: HW.h:35
@ Output
Definition: HW.h:35
Instantiate one of these and use it to build typed backedges.
Backedge is a wrapper class around a Value.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
Value createOrFoldNot(Location loc, Value value, OpBuilder &builder, bool twoState=false)
Create a `‘Not’' gate on a value.
Definition: CombOps.cpp:48
std::unique_ptr< OperationPass< ModuleOp > > createESIPhysicalLoweringPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
Definition: esi.py:1