CIRCT  20.0.0git
HWToSystemC.cpp
Go to the documentation of this file.
1 //===- HWToSystemC.cpp - HW To SystemC 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 SystemC Conversion Pass Implementation.
10 //
11 //===----------------------------------------------------------------------===//
12 
15 #include "circt/Dialect/HW/HWOps.h"
17 #include "mlir/Dialect/EmitC/IR/EmitC.h"
18 #include "mlir/IR/BuiltinDialect.h"
19 #include "mlir/Pass/Pass.h"
20 #include "mlir/Transforms/DialectConversion.h"
21 #include "llvm/ADT/TypeSwitch.h"
22 
23 namespace circt {
24 #define GEN_PASS_DEF_CONVERTHWTOSYSTEMC
25 #include "circt/Conversion/Passes.h.inc"
26 } // namespace circt
27 
28 using namespace mlir;
29 using namespace circt;
30 using namespace hw;
31 using namespace systemc;
32 
33 //===----------------------------------------------------------------------===//
34 // Operation Conversion Patterns
35 //===----------------------------------------------------------------------===//
36 
37 namespace {
38 
39 /// This works on each HW module, creates corresponding SystemC modules, moves
40 /// the body of the module into the new SystemC module by splitting up the body
41 /// into field declarations, initializations done in a newly added systemc.ctor,
42 /// and internal methods to be registered in the constructor.
43 struct ConvertHWModule : public OpConversionPattern<HWModuleOp> {
44  using OpConversionPattern::OpConversionPattern;
45 
46  LogicalResult
47  matchAndRewrite(HWModuleOp module, OpAdaptor adaptor,
48  ConversionPatternRewriter &rewriter) const override {
49  // Parameterized modules are supported yet.
50  if (!module.getParameters().empty())
51  return emitError(module->getLoc(), "module parameters not supported yet");
52 
53  auto ports = module.getPortList();
54  if (llvm::any_of(ports, [](auto &port) { return port.isInOut(); }))
55  return emitError(module->getLoc(), "inout arguments not supported yet");
56 
57  // Create the SystemC module.
58  for (size_t i = 0; i < ports.size(); ++i)
59  ports[i].type = typeConverter->convertType(ports[i].type);
60 
61  auto scModule = rewriter.create<SCModuleOp>(module.getLoc(),
62  module.getNameAttr(), ports);
63  auto *outputOp = module.getBodyBlock()->getTerminator();
64  scModule.setVisibility(module.getVisibility());
65 
66  auto portAttrs = module.getAllPortAttrs();
67  if (!portAttrs.empty())
68  scModule.setAllArgAttrs(portAttrs);
69 
70  // Create a systemc.func operation inside the module after the ctor.
71  // TODO: implement logic to extract a better name and properly unique it.
72  rewriter.setInsertionPointToStart(scModule.getBodyBlock());
73  auto scFunc = rewriter.create<SCFuncOp>(
74  module.getLoc(), rewriter.getStringAttr("innerLogic"));
75 
76  // Inline the HW module body into the systemc.func body.
77  // TODO: do some dominance analysis to detect use-before-def and cycles in
78  // the use chain, which are allowed in graph regions but not in SSACFG
79  // regions, and when possible fix them.
80  scFunc.getBodyBlock()->erase();
81  Region &scFuncBody = scFunc.getBody();
82  rewriter.inlineRegionBefore(module.getBody(), scFuncBody, scFuncBody.end());
83 
84  // Register the systemc.func inside the systemc.ctor
85  rewriter.setInsertionPointToStart(
86  scModule.getOrCreateCtor().getBodyBlock());
87  rewriter.create<MethodOp>(scModule.getLoc(), scFunc.getHandle());
88 
89  // Register the sensitivities of above SC_METHOD registration.
90  SmallVector<Value> sensitivityValues(
91  llvm::make_filter_range(scModule.getArguments(), [](BlockArgument arg) {
92  return !isa<OutputType>(arg.getType());
93  }));
94  if (!sensitivityValues.empty())
95  rewriter.create<SensitiveOp>(scModule.getLoc(), sensitivityValues);
96 
97  // Move the block arguments of the systemc.func (that we got from the
98  // hw.module) to the systemc.module
99  rewriter.setInsertionPointToStart(scFunc.getBodyBlock());
100  auto portsLocal = module.getPortList();
101  for (size_t i = 0, e = scFunc.getRegion().getNumArguments(); i < e; ++i) {
102  auto inputRead =
103  rewriter
104  .create<SignalReadOp>(scFunc.getLoc(), scModule.getArgument(i))
105  .getResult();
106  auto converted = typeConverter->materializeSourceConversion(
107  rewriter, scModule.getLoc(), portsLocal[i].type, inputRead);
108  scFuncBody.getArgument(0).replaceAllUsesWith(converted);
109  scFuncBody.eraseArgument(0);
110  }
111 
112  // Erase the HW module.
113  rewriter.eraseOp(module);
114 
115  SmallVector<Value> outPorts;
116  for (auto val : scModule.getArguments()) {
117  if (isa<OutputType>(val.getType()))
118  outPorts.push_back(val);
119  }
120 
121  rewriter.setInsertionPoint(outputOp);
122  for (auto args : llvm::zip(outPorts, outputOp->getOperands())) {
123  Value portValue = std::get<0>(args);
124  auto converted = typeConverter->materializeTargetConversion(
125  rewriter, scModule.getLoc(), getSignalBaseType(portValue.getType()),
126  std::get<1>(args));
127  rewriter.create<SignalWriteOp>(outputOp->getLoc(), portValue, converted);
128  }
129 
130  // Erase the HW OutputOp.
131  outputOp->dropAllReferences();
132  rewriter.eraseOp(outputOp);
133 
134  return success();
135  }
136 };
137 
138 /// Convert hw.instance operations to systemc.instance.decl and a
139 /// systemc.instance.bind_port operation for each port in the constructor. Also
140 /// insert the necessary intermediate signals and write or read their state in
141 /// the update function accordingly.
142 class ConvertInstance : public OpConversionPattern<InstanceOp> {
143  using OpConversionPattern::OpConversionPattern;
144 
145 private:
146  template <typename PortTy>
147  LogicalResult
148  collectPortInfo(ValueRange ports, ArrayAttr portNames,
149  SmallVector<systemc::ModuleType::PortInfo> &portInfo) const {
150  for (auto inPort : llvm::zip(ports, portNames)) {
151  Type ty = std::get<0>(inPort).getType();
152  systemc::ModuleType::PortInfo info;
153 
154  if (isa<hw::InOutType>(ty))
155  return failure();
156 
157  info.type = typeConverter->convertType(PortTy::get(ty));
158  info.name = cast<StringAttr>(std::get<1>(inPort));
159  portInfo.push_back(info);
160  }
161 
162  return success();
163  }
164 
165 public:
166  LogicalResult
167  matchAndRewrite(InstanceOp instanceOp, OpAdaptor adaptor,
168  ConversionPatternRewriter &rewriter) const override {
169  // Make sure the parent is already converted such that we already have a
170  // constructor and update function to insert operations into.
171  auto scModule = instanceOp->getParentOfType<SCModuleOp>();
172  if (!scModule)
173  return rewriter.notifyMatchFailure(instanceOp,
174  "parent was not an SCModuleOp");
175 
176  // Get the builders for the different places to insert operations.
177  auto ctor = scModule.getOrCreateCtor();
178  OpBuilder stateBuilder(ctor);
179  OpBuilder initBuilder = OpBuilder::atBlockEnd(ctor.getBodyBlock());
180 
181  // Collect the port types and names of the instantiated module and convert
182  // them to appropriate systemc types.
183  SmallVector<systemc::ModuleType::PortInfo> portInfo;
184  if (failed(collectPortInfo<InputType>(adaptor.getInputs(),
185  adaptor.getArgNames(), portInfo)) ||
186  failed(collectPortInfo<OutputType>(instanceOp->getResults(),
187  adaptor.getResultNames(), portInfo)))
188  return instanceOp->emitOpError("inout ports not supported");
189 
190  Location loc = instanceOp->getLoc();
191  auto instanceName = instanceOp.getInstanceNameAttr();
192  auto instModuleName = instanceOp.getModuleNameAttr();
193 
194  // Declare the instance.
195  auto instDecl = stateBuilder.create<InstanceDeclOp>(
196  loc, instanceName, instModuleName, portInfo);
197 
198  // Bind the input ports.
199  for (size_t i = 0, numInputs = adaptor.getInputs().size(); i < numInputs;
200  ++i) {
201  Value input = adaptor.getInputs()[i];
202  auto portId = rewriter.getIndexAttr(i);
203  StringAttr signalName = rewriter.getStringAttr(
204  instanceName.getValue() + "_" + portInfo[i].name.getValue());
205 
206  if (auto readOp = input.getDefiningOp<SignalReadOp>()) {
207  // Use the read channel directly without adding an
208  // intermediate signal.
209  initBuilder.create<BindPortOp>(loc, instDecl, portId,
210  readOp.getInput());
211  continue;
212  }
213 
214  // Otherwise, create an intermediate signal to bind the instance port to.
215  Type sigType = SignalType::get(getSignalBaseType(portInfo[i].type));
216  Value channel = stateBuilder.create<SignalOp>(loc, sigType, signalName);
217  initBuilder.create<BindPortOp>(loc, instDecl, portId, channel);
218  rewriter.create<SignalWriteOp>(loc, channel, input);
219  }
220 
221  // Bind the output ports.
222  for (size_t i = 0, numOutputs = instanceOp->getNumResults(); i < numOutputs;
223  ++i) {
224  size_t numInputs = adaptor.getInputs().size();
225  Value output = instanceOp->getResult(i);
226  auto portId = rewriter.getIndexAttr(i + numInputs);
227  StringAttr signalName =
228  rewriter.getStringAttr(instanceName.getValue() + "_" +
229  portInfo[i + numInputs].name.getValue());
230 
231  if (output.hasOneUse()) {
232  if (auto writeOp = dyn_cast<SignalWriteOp>(*output.user_begin())) {
233  // Use the channel written to directly. When there are multiple
234  // channels this value is written to or it is used somewhere else, we
235  // cannot shortcut it and have to insert an intermediate value because
236  // we cannot insert multiple bind statements for one submodule port.
237  // It is also necessary to bind it to an intermediate signal when it
238  // has no uses as every port has to be bound to a channel.
239  initBuilder.create<BindPortOp>(loc, instDecl, portId,
240  writeOp.getDest());
241  writeOp->erase();
242  continue;
243  }
244  }
245 
246  // Otherwise, create an intermediate signal.
247  Type sigType =
248  SignalType::get(getSignalBaseType(portInfo[i + numInputs].type));
249  Value channel = stateBuilder.create<SignalOp>(loc, sigType, signalName);
250  initBuilder.create<BindPortOp>(loc, instDecl, portId, channel);
251  auto instOut = rewriter.create<SignalReadOp>(loc, channel);
252  output.replaceAllUsesWith(instOut);
253  }
254 
255  rewriter.eraseOp(instanceOp);
256  return success();
257  }
258 };
259 
260 } // namespace
261 
262 //===----------------------------------------------------------------------===//
263 // Conversion Infrastructure
264 //===----------------------------------------------------------------------===//
265 
266 static void populateLegality(ConversionTarget &target) {
267  target.addIllegalDialect<HWDialect>();
268  target.addLegalDialect<mlir::BuiltinDialect>();
269  target.addLegalDialect<systemc::SystemCDialect>();
270  target.addLegalDialect<comb::CombDialect>();
271  target.addLegalDialect<emitc::EmitCDialect>();
272  target.addLegalOp<hw::ConstantOp>();
273 }
274 
275 static void populateOpConversion(RewritePatternSet &patterns,
276  TypeConverter &typeConverter) {
277  patterns.add<ConvertHWModule, ConvertInstance>(typeConverter,
278  patterns.getContext());
279 }
280 
281 static void populateTypeConversion(TypeConverter &converter) {
282  converter.addConversion([](Type type) { return type; });
283  converter.addConversion([&](SignalType type) {
284  return SignalType::get(converter.convertType(type.getBaseType()));
285  });
286  converter.addConversion([&](InputType type) {
287  return InputType::get(converter.convertType(type.getBaseType()));
288  });
289  converter.addConversion([&](systemc::InOutType type) {
290  return systemc::InOutType::get(converter.convertType(type.getBaseType()));
291  });
292  converter.addConversion([&](OutputType type) {
293  return OutputType::get(converter.convertType(type.getBaseType()));
294  });
295  converter.addConversion([](IntegerType type) -> Type {
296  auto bw = type.getIntOrFloatBitWidth();
297  if (bw == 1)
298  return type;
299 
300  if (bw <= 64) {
301  if (type.isSigned())
302  return systemc::IntType::get(type.getContext(), bw);
303 
304  return UIntType::get(type.getContext(), bw);
305  }
306 
307  if (bw <= 512) {
308  if (type.isSigned())
309  return BigIntType::get(type.getContext(), bw);
310 
311  return BigUIntType::get(type.getContext(), bw);
312  }
313 
314  return BitVectorType::get(type.getContext(), bw);
315  });
316 
317  converter.addSourceMaterialization(
318  [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
319  assert(values.size() == 1);
320  auto op = builder.create<ConvertOp>(loc, type, values[0]);
321  return op.getResult();
322  });
323 
324  converter.addTargetMaterialization(
325  [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
326  assert(values.size() == 1);
327  auto op = builder.create<ConvertOp>(loc, type, values[0]);
328  return op.getResult();
329  });
330 }
331 
332 //===----------------------------------------------------------------------===//
333 // HW to SystemC Conversion Pass
334 //===----------------------------------------------------------------------===//
335 
336 namespace {
337 struct HWToSystemCPass
338  : public circt::impl::ConvertHWToSystemCBase<HWToSystemCPass> {
339  void runOnOperation() override;
340 };
341 } // namespace
342 
343 /// Create a HW to SystemC dialects conversion pass.
344 std::unique_ptr<OperationPass<ModuleOp>> circt::createConvertHWToSystemCPass() {
345  return std::make_unique<HWToSystemCPass>();
346 }
347 
348 /// This is the main entrypoint for the HW to SystemC conversion pass.
349 void HWToSystemCPass::runOnOperation() {
350  MLIRContext &context = getContext();
351  ModuleOp module = getOperation();
352 
353  // Create the include operation here to have exactly one 'systemc' include at
354  // the top instead of one per module.
355  OpBuilder builder(module.getRegion());
356  builder.create<emitc::IncludeOp>(module->getLoc(), "systemc.h", true);
357 
358  ConversionTarget target(context);
359  TypeConverter typeConverter;
360  RewritePatternSet patterns(&context);
361  populateLegality(target);
362  populateTypeConversion(typeConverter);
363  populateOpConversion(patterns, typeConverter);
364 
365  if (failed(applyFullConversion(module, target, std::move(patterns))))
366  signalPassFailure();
367 }
assert(baseType &&"element must be base type")
static void populateLegality(ConversionTarget &target)
static void populateOpConversion(RewritePatternSet &patterns, TypeConverter &typeConverter)
static void populateTypeConversion(TypeConverter &converter)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
circt::hw::InOutType InOutType
Definition: SVTypes.h:25
Type getSignalBaseType(Type type)
Get the type wrapped by a signal or port (in, inout, out) type.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createConvertHWToSystemCPass()
Create a HW to SystemC dialects conversion pass.
Definition: hw.py:1