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