CIRCT  19.0.0git
HWToLLHD.cpp
Go to the documentation of this file.
1 //===- HWToLLHD.cpp - HW to LLHD Conversion Pass --------------------------===//
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 // This is the main HW to LLHD Conversion Pass Implementation.
10 //
11 //===----------------------------------------------------------------------===//
12 
14 #include "../PassDetail.h"
18 #include "circt/Dialect/HW/HWOps.h"
21 #include "mlir/IR/BuiltinTypes.h"
22 #include "mlir/Pass/Pass.h"
23 #include "mlir/Transforms/DialectConversion.h"
24 
25 using namespace circt;
26 using namespace llhd;
27 using namespace hw;
28 using namespace comb;
29 
30 //===----------------------------------------------------------------------===//
31 // Convert structure operations
32 //===----------------------------------------------------------------------===//
33 
34 namespace {
35 
36 /// This works on each HW module, creates corresponding entities, moves the
37 /// bodies of the modules into the entities, and converts the bodies.
38 struct ConvertHWModule : public OpConversionPattern<HWModuleOp> {
39  using OpConversionPattern::OpConversionPattern;
40 
41  LogicalResult
42  matchAndRewrite(HWModuleOp module, OpAdaptor adaptor,
43  ConversionPatternRewriter &rewriter) const override {
44  // Collect the HW module's port types.
45  unsigned numInputs = module.getNumInputPorts();
46  auto moduleInputs = module.getInputTypes();
47  auto moduleOutputs = module.getOutputTypes();
48 
49  // LLHD entities port types are all expressed as block arguments to the op,
50  // so collect all of the types in the expected order (inputs then outputs).
51  SmallVector<Type, 4> entityTypes;
52  entityTypes.append(moduleInputs.begin(), moduleInputs.end());
53  entityTypes.append(moduleOutputs.begin(), moduleOutputs.end());
54 
55  // Ensure the input and output types have all been converted already. This
56  // is handled separately by the upstream FunctionLikeTypeConversionPattern.
57  if (!llvm::all_of(entityTypes,
58  [](Type type) { return type.isa<SigType>(); }))
59  return rewriter.notifyMatchFailure(module, "Not all ports had SigType");
60 
61  // Create the entity. Note that LLHD does not support parameterized
62  // entities, so this conversion does not support parameterized modules.
63  auto entityType = rewriter.getFunctionType(entityTypes, {});
64  auto entity = rewriter.create<EntityOp>(module.getLoc(), entityType,
65  numInputs, /*argAttrs=*/ArrayAttr(),
66  /*resAttrs=*/ArrayAttr());
67 
68  // Inline the HW module body into the entity body.
69  Region &entityBodyRegion = entity.getBodyRegion();
70  rewriter.inlineRegionBefore(module.getBodyRegion(), entityBodyRegion,
71  entityBodyRegion.end());
72 
73  // Set the entity name attributes. Add block arguments for each output,
74  // since LLHD entity outputs are still block arguments to the op.
75  rewriter.modifyOpInPlace(entity, [&] {
76  entity.setName(module.getName());
77  entityBodyRegion.addArguments(
78  moduleOutputs, SmallVector<Location, 4>(moduleOutputs.size(),
79  rewriter.getUnknownLoc()));
80  });
81 
82  // Erase the HW module.
83  rewriter.eraseOp(module);
84 
85  return success();
86  }
87 };
88 
89 /// This works on each output op, creating ops to drive the appropriate results.
90 struct ConvertOutput : public OpConversionPattern<hw::OutputOp> {
91  using OpConversionPattern::OpConversionPattern;
92 
93  LogicalResult
94  matchAndRewrite(hw::OutputOp output, OpAdaptor adaptor,
95  ConversionPatternRewriter &rewriter) const override {
96  // Get the number of inputs in the entity to offset into the block args.
97  auto entity = output->getParentOfType<EntityOp>();
98  if (!entity)
99  return rewriter.notifyMatchFailure(output, "parent was not an EntityOp");
100  size_t numInputs = entity.getIns();
101 
102  // Drive the results from the mapped operands.
103  Value delta;
104  for (size_t i = 0, e = adaptor.getOperands().size(); i != e; ++i) {
105  // Get the source and destination signals.
106  auto src = adaptor.getOperands()[i];
107  auto dest = entity.getArgument(numInputs + i);
108  if (!src || !dest)
109  return rewriter.notifyMatchFailure(
110  output, "output operand must map to result block arg");
111 
112  // Look through probes on the source side and use the signal directly.
113  if (auto prb = src.getDefiningOp<PrbOp>())
114  src = prb.getSignal();
115 
116  // No work needed if they already are the same.
117  if (src == dest)
118  continue;
119 
120  // If the source has a signal type, connect it.
121  if (auto sigTy = src.getType().dyn_cast<SigType>()) {
122  rewriter.create<llhd::ConnectOp>(output.getLoc(), dest, src);
123  continue;
124  }
125 
126  // Otherwise, drive the destination block argument value.
127  if (!delta) {
128  delta = rewriter.create<ConstantTimeOp>(output.getLoc(), 0, "ns", 1, 0);
129  }
130  rewriter.create<DrvOp>(output.getLoc(), dest, src, delta, Value());
131  }
132 
133  // Remove the output op.
134  rewriter.eraseOp(output);
135 
136  return success();
137  }
138 };
139 
140 /// This works on each instance op, converting them to the LLHD dialect. If the
141 /// HW instance ops were defined in terms of the CallableOpInterface, we could
142 /// generalize this in terms of the upstream pattern to rewrite call ops' types.
143 struct ConvertInstance : public OpConversionPattern<InstanceOp> {
144  using OpConversionPattern::OpConversionPattern;
145 
146  LogicalResult
147  matchAndRewrite(InstanceOp instance, OpAdaptor adaptor,
148  ConversionPatternRewriter &rewriter) const override {
149  Value delta;
150 
151  // Materialize signals for instance arguments that are of non-signal type.
152  SmallVector<Value, 4> arguments;
153  unsigned argIdx = 0;
154  for (auto arg : adaptor.getOperands()) {
155  // Connect signals directly.
156  auto argType = arg.getType();
157  if (argType.isa<SigType>()) {
158  arguments.push_back(arg);
159  continue;
160  }
161 
162  // Look through probes and use the signal directly.
163  if (auto prb = arg.getDefiningOp<PrbOp>()) {
164  arguments.push_back(prb.getSignal());
165  continue;
166  }
167 
168  // Otherwise materialize a signal.
169  // TODO: This should be a `llhd.buffer` operation. Ultimately we would
170  // want this to be done by the TypeConverter in a materialization
171  // callback. That requires adding the mentioned operation and fleshing out
172  // the semantics of a zero-delay drive in the simulation. Once this is
173  // done, the materializer can insert buffers with no delay and have them
174  // collected in a canonicalization later where appropriate.
175  // See github.com/llvm/circt/pull/988 for a discussion.
176  if (!argType.isa<IntegerType>())
177  return rewriter.notifyMatchFailure(instance, [&](Diagnostic &diag) {
178  diag << "argument type " << argType << " is not supported";
179  });
180 
181  auto init = rewriter.create<ConstantOp>(arg.getLoc(), argType, 0);
182  SmallString<8> sigName(instance.getInstanceName());
183  sigName += "_arg_";
184  sigName += std::to_string(argIdx++);
185  auto sig = rewriter.createOrFold<SigOp>(
186  arg.getLoc(), SigType::get(argType), sigName, init);
187  if (!delta) {
188  delta = rewriter.create<ConstantTimeOp>(arg.getLoc(), 0, "ns", 1, 0);
189  }
190  rewriter.create<DrvOp>(arg.getLoc(), sig, arg, delta, Value());
191  arguments.push_back(sig);
192  }
193 
194  // HW instances model output ports as SSA results produced by the op. LLHD
195  // instances model output ports as arguments to the op, so we need to find
196  // or create SSA values. For each output port in the HW instance, try to
197  // find a signal that can be used directly, or else create a new signal.
198  SmallVector<Value, 4> resultSigs;
199  SmallVector<Value, 4> resultValues;
200  for (auto result : instance.getResults()) {
201  auto resultType = result.getType();
202  if (!resultType.isa<IntegerType>())
203  return rewriter.notifyMatchFailure(instance, [&](Diagnostic &diag) {
204  diag << "result type " << resultType << " is not supported";
205  });
206 
207  Location loc = result.getLoc();
208 
209  // Since we need to have a signal for this result, see if an OutputOp maps
210  // it to an output signal of our parent module. In that case we can just
211  // use that signal.
212  Value sig;
213  for (auto &use : result.getUses()) {
214  if (isa<hw::OutputOp>(use.getOwner())) {
215  auto entity = instance->getParentOfType<EntityOp>();
216  if (!entity)
217  continue;
218  sig = entity.getArgument(entity.getIns() + use.getOperandNumber());
219  break;
220  }
221  }
222 
223  // Otherwise materialize a signal.
224  // TODO: This should be a `llhd.buffer` operation. Ultimately we would
225  // want this to be done by the TypeConverter in a materialization
226  // callback. That requires adding the mentioned operation and fleshing out
227  // the semantics of a zero-delay drive in the simulation. Once this is
228  // done, the materializer can insert buffers with no delay and have them
229  // collected in a canonicalization later where appropriate.
230  // See github.com/llvm/circt/pull/988 for a discussion.
231  if (!sig) {
232  auto init = rewriter.create<ConstantOp>(loc, resultType, 0);
233  SmallString<8> sigName(instance.getInstanceName());
234  sigName += "_result_";
235  sigName += std::to_string(result.getResultNumber());
236  sig = rewriter.createOrFold<SigOp>(loc, SigType::get(resultType),
237  sigName, init);
238  }
239 
240  // Make OutputOps directly refer to this signal, which allows them to use
241  // a ConnectOp rather than a PrbOp+DrvOp combo.
242  for (auto &use : llvm::make_early_inc_range(result.getUses())) {
243  if (isa<hw::OutputOp>(use.getOwner())) {
244  rewriter.modifyOpInPlace(use.getOwner(), [&]() { use.set(sig); });
245  }
246  }
247 
248  // Probe the value of the signal such that we end up having a replacement
249  // for the InstanceOp results later on.
250  auto prb = rewriter.create<PrbOp>(loc, resultType, sig);
251  resultSigs.push_back(sig);
252  resultValues.push_back(prb);
253  }
254 
255  // Create the LLHD instance from the operands and results. Then mark the
256  // original instance for replacement with the new values probed from the
257  // signals attached to the LLHD instance.
258  rewriter.create<InstOp>(instance.getLoc(), instance.getInstanceName(),
259  instance.getModuleName(), arguments, resultSigs);
260  rewriter.replaceOp(instance, resultValues);
261 
262  return success();
263  }
264 };
265 
266 } // namespace
267 
268 //===----------------------------------------------------------------------===//
269 // HW to LLHD Conversion Pass
270 //===----------------------------------------------------------------------===//
271 
272 namespace {
273 struct HWToLLHDPass : public ConvertHWToLLHDBase<HWToLLHDPass> {
274  void runOnOperation() override;
275 };
276 
277 /// A helper type converter class that automatically populates the relevant
278 /// materializations and type conversions for converting HW to LLHD.
279 struct HWToLLHDTypeConverter : public TypeConverter {
280  HWToLLHDTypeConverter();
281 };
282 } // namespace
283 
284 /// Create a HW to LLHD conversion pass.
285 std::unique_ptr<OperationPass<ModuleOp>> circt::createConvertHWToLLHDPass() {
286  return std::make_unique<HWToLLHDPass>();
287 }
288 
289 /// This is the main entrypoint for the HW to LLHD conversion pass.
290 void HWToLLHDPass::runOnOperation() {
291  MLIRContext &context = getContext();
292  ModuleOp module = getOperation();
293 
294  // Mark the HW structure ops as illegal such that they get rewritten.
295  ConversionTarget target(context);
296  target.addLegalDialect<LLHDDialect>();
297  target.addLegalDialect<CombDialect>();
298  target.addLegalOp<ConstantOp>();
299  target.addIllegalOp<HWModuleOp>();
300  target.addIllegalOp<hw::OutputOp>();
301  target.addIllegalOp<InstanceOp>();
302 
303  // Rewrite `hw.module`, `hw.output`, and `hw.instance`.
304  HWToLLHDTypeConverter typeConverter;
305  RewritePatternSet patterns(&context);
306  populateHWModuleLikeTypeConversionPattern(HWModuleOp::getOperationName(),
307  patterns, typeConverter);
308  patterns.add<ConvertHWModule>(&context);
309  patterns.add<ConvertInstance>(&context);
310  patterns.add<ConvertOutput>(&context);
311  if (failed(applyPartialConversion(module, target, std::move(patterns))))
312  signalPassFailure();
313 }
314 
315 //===----------------------------------------------------------------------===//
316 // TypeConverter conversions and materializations
317 //===----------------------------------------------------------------------===//
318 
319 HWToLLHDTypeConverter::HWToLLHDTypeConverter() {
320  // Convert any type by just wrapping it in `SigType`.
321  addConversion([](Type type) { return SigType::get(type); });
322 
323  // Mark `SigType` legal by converting it to itself.
324  addConversion([](SigType type) { return type; });
325 
326  // Materialze probes when arguments are converted from any type to `SigType`.
327  addSourceMaterialization(
328  [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
329  assert(values.size() == 1);
330  auto op = builder.create<PrbOp>(loc, type, values[0]);
331  return op.getResult();
332  });
333 }
assert(baseType &&"element must be base type")
Builder builder
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
void populateHWModuleLikeTypeConversionPattern(StringRef moduleLikeOpName, RewritePatternSet &patterns, TypeConverter &converter)
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createConvertHWToLLHDPass()
Create a HW to LLHD conversion pass.
Definition: HWToLLHD.cpp:285
Definition: comb.py:1
Definition: hw.py:1