CIRCT  19.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 
16 #include "circt/Dialect/HW/HWOps.h"
18 #include "circt/Support/LLVM.h"
19 
20 #include "mlir/Transforms/DialectConversion.h"
21 
22 using namespace circt;
23 using namespace circt::esi;
24 using namespace circt::esi::detail;
25 using namespace circt::hw;
26 
27 namespace {
28 /// Lower `ChannelBufferOp`s, breaking out the various options. For now, just
29 /// replace with the specified number of pipeline stages (since that's the only
30 /// option).
31 struct ChannelBufferLowering : public OpConversionPattern<ChannelBufferOp> {
32 public:
33  using OpConversionPattern::OpConversionPattern;
34 
35  LogicalResult
36  matchAndRewrite(ChannelBufferOp buffer, OpAdaptor adaptor,
37  ConversionPatternRewriter &rewriter) const final;
38 };
39 } // anonymous namespace
40 
41 LogicalResult ChannelBufferLowering::matchAndRewrite(
42  ChannelBufferOp buffer, OpAdaptor adaptor,
43  ConversionPatternRewriter &rewriter) const {
44  auto loc = buffer.getLoc();
45 
46  auto type = buffer.getType();
47 
48  // Expand 'abstract' buffer into 'physical' stages.
49  auto stages = buffer.getStagesAttr();
50  uint64_t numStages = 1;
51  if (stages) {
52  // Guaranteed positive by the parser.
53  numStages = stages.getValue().getLimitedValue();
54  }
55  Value input = buffer.getInput();
56  StringAttr bufferName = buffer.getNameAttr();
57  for (uint64_t i = 0; i < numStages; ++i) {
58  // Create the stages, connecting them up as we build.
59  auto stage = rewriter.create<PipelineStageOp>(loc, type, buffer.getClk(),
60  buffer.getRst(), input);
61  if (bufferName) {
62  SmallString<64> stageName(
63  {bufferName.getValue(), "_stage", std::to_string(i)});
64  stage->setAttr("name", StringAttr::get(rewriter.getContext(), stageName));
65  }
66  input = stage;
67  }
68 
69  // Replace the buffer.
70  rewriter.replaceOp(buffer, input);
71  return success();
72 }
73 
74 namespace {
75 /// Lower pure modules into hw.modules.
76 struct PureModuleLowering : public OpConversionPattern<ESIPureModuleOp> {
77 public:
78  using OpConversionPattern::OpConversionPattern;
79 
80  LogicalResult
81  matchAndRewrite(ESIPureModuleOp pureMod, OpAdaptor adaptor,
82  ConversionPatternRewriter &rewriter) const final;
83 };
84 } // anonymous namespace
85 
86 LogicalResult
87 PureModuleLowering::matchAndRewrite(ESIPureModuleOp pureMod, OpAdaptor adaptor,
88  ConversionPatternRewriter &rewriter) const {
89  auto loc = pureMod.getLoc();
90  Block *body = &pureMod.getBody().front();
91 
92  // Track existing names (so we can de-dup) and get op result when we want to
93  // replace it with the block args.
94  DenseMap<StringAttr, ESIPureModuleInputOp> inputPortNames;
95  // Build the port list for `hw.module` construction.
96  SmallVector<hw::PortInfo> ports;
97  // List the input and output ops.
98  SmallVector<ESIPureModuleInputOp> inputs;
99  SmallVector<ESIPureModuleOutputOp> outputs;
100  SmallVector<Attribute> params;
101 
102  for (Operation &op : llvm::make_early_inc_range(body->getOperations())) {
103  if (auto port = dyn_cast<ESIPureModuleInputOp>(op)) {
104  // If we already have an input port of the same name, replace the result
105  // value with the previous one. Checking that the types match is done in
106  // the pure module verifier.
107  auto existingPort = inputPortNames.find(port.getNameAttr());
108  if (existingPort != inputPortNames.end()) {
109  rewriter.replaceAllUsesWith(port.getResult(),
110  existingPort->getSecond().getResult());
111  rewriter.eraseOp(port);
112  continue;
113  }
114  // Normal port construction.
115  ports.push_back(
116  hw::PortInfo{{port.getNameAttr(), port.getResult().getType(),
118  inputs.size(),
119  {},
120  port.getLoc()});
121  inputs.push_back(port);
122  } else if (auto port = dyn_cast<ESIPureModuleOutputOp>(op)) {
123  ports.push_back(
124  hw::PortInfo{{port.getNameAttr(), port.getValue().getType(),
126  outputs.size(),
127  {},
128  port.getLoc()});
129  outputs.push_back(port);
130  } else if (auto param = dyn_cast<ESIPureModuleParamOp>(op)) {
131  params.push_back(
132  ParamDeclAttr::get(param.getNameAttr(), param.getType()));
133  rewriter.eraseOp(param);
134  }
135  }
136 
137  // Create the replacement `hw.module`.
138  auto hwMod = rewriter.create<hw::HWModuleOp>(
139  loc, pureMod.getNameAttr(), ports, ArrayAttr::get(getContext(), params));
140  hwMod->setDialectAttrs(pureMod->getDialectAttrs());
141  rewriter.eraseBlock(hwMod.getBodyBlock());
142  rewriter.inlineRegionBefore(*body->getParent(), hwMod.getBodyRegion(),
143  hwMod.getBodyRegion().end());
144  body = hwMod.getBodyBlock();
145 
146  // Re-wire the inputs and erase them.
147  for (auto input : inputs) {
148  BlockArgument newArg;
149  rewriter.modifyOpInPlace(hwMod, [&]() {
150  newArg = body->addArgument(input.getResult().getType(), input.getLoc());
151  });
152  rewriter.replaceAllUsesWith(input.getResult(), newArg);
153  rewriter.eraseOp(input);
154  }
155 
156  // Assemble the output values.
157  SmallVector<Value> hwOutputOperands;
158  for (auto output : outputs) {
159  hwOutputOperands.push_back(output.getValue());
160  rewriter.eraseOp(output);
161  }
162  rewriter.setInsertionPointToEnd(body);
163  rewriter.create<hw::OutputOp>(pureMod.getLoc(), hwOutputOperands);
164 
165  // Erase the original op.
166  rewriter.eraseOp(pureMod);
167  return success();
168 }
169 
170 namespace {
171 /// Run all the physical lowerings.
172 struct ESIToPhysicalPass : public LowerESIToPhysicalBase<ESIToPhysicalPass> {
173  void runOnOperation() override;
174 };
175 } // anonymous namespace
176 
177 void ESIToPhysicalPass::runOnOperation() {
178  // Set up a conversion and give it a set of laws.
179  ConversionTarget target(getContext());
180  target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
181  target.addIllegalOp<ChannelBufferOp>();
182  target.addIllegalOp<ESIPureModuleOp>();
183 
184  // Add all the conversion patterns.
185  RewritePatternSet patterns(&getContext());
186  patterns.insert<ChannelBufferLowering>(&getContext());
187  patterns.insert<PureModuleLowering>(&getContext());
188 
189  // Run the conversion.
190  if (failed(
191  applyPartialConversion(getOperation(), target, std::move(patterns))))
192  signalPassFailure();
193 }
194 
195 std::unique_ptr<OperationPass<ModuleOp>>
197  return std::make_unique<ESIToPhysicalPass>();
198 }
@ Input
Definition: HW.h:35
@ Output
Definition: HW.h:35
llvm::SmallVector< StringAttr > inputs
llvm::SmallVector< StringAttr > outputs
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
std::unique_ptr< OperationPass< ModuleOp > > createESIPhysicalLoweringPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21