Loading [MathJax]/extensions/tex2jax.js
CIRCT 22.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
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
23namespace circt {
24#define GEN_PASS_DEF_CONVERTHWTOSYSTEMC
25#include "circt/Conversion/Passes.h.inc"
26} // namespace circt
27
28using namespace mlir;
29using namespace circt;
30using namespace hw;
31using namespace systemc;
32
33//===----------------------------------------------------------------------===//
34// Operation Conversion Patterns
35//===----------------------------------------------------------------------===//
36
37namespace {
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.
43struct 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 = SCModuleOp::create(rewriter, 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 = SCFuncOp::create(rewriter, module.getLoc(),
74 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 MethodOp::create(rewriter, 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 SensitiveOp::create(rewriter, 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 = SignalReadOp::create(rewriter, scFunc.getLoc(),
103 scModule.getArgument(i))
104 .getResult();
105 auto converted = typeConverter->materializeSourceConversion(
106 rewriter, scModule.getLoc(), portsLocal[i].type, inputRead);
107 scFuncBody.getArgument(0).replaceAllUsesWith(converted);
108 scFuncBody.eraseArgument(0);
109 }
110
111 // Erase the HW module.
112 rewriter.eraseOp(module);
113
114 SmallVector<Value> outPorts;
115 for (auto val : scModule.getArguments()) {
116 if (isa<OutputType>(val.getType()))
117 outPorts.push_back(val);
118 }
119
120 rewriter.setInsertionPoint(outputOp);
121 for (auto args : llvm::zip(outPorts, outputOp->getOperands())) {
122 Value portValue = std::get<0>(args);
123 auto converted = typeConverter->materializeTargetConversion(
124 rewriter, scModule.getLoc(), getSignalBaseType(portValue.getType()),
125 std::get<1>(args));
126 SignalWriteOp::create(rewriter, outputOp->getLoc(), portValue, converted);
127 }
128
129 // Erase the HW OutputOp.
130 outputOp->dropAllReferences();
131 rewriter.eraseOp(outputOp);
132
133 return success();
134 }
135};
136
137/// Convert hw.instance operations to systemc.instance.decl and a
138/// systemc.instance.bind_port operation for each port in the constructor. Also
139/// insert the necessary intermediate signals and write or read their state in
140/// the update function accordingly.
141class ConvertInstance : public OpConversionPattern<InstanceOp> {
142 using OpConversionPattern::OpConversionPattern;
143
144private:
145 template <typename PortTy>
146 LogicalResult
147 collectPortInfo(ValueRange ports, ArrayAttr portNames,
148 SmallVector<systemc::ModuleType::PortInfo> &portInfo) const {
149 for (auto inPort : llvm::zip(ports, portNames)) {
150 Type ty = std::get<0>(inPort).getType();
151 systemc::ModuleType::PortInfo info;
152
153 if (isa<hw::InOutType>(ty))
154 return failure();
155
156 info.type = typeConverter->convertType(PortTy::get(ty));
157 info.name = cast<StringAttr>(std::get<1>(inPort));
158 portInfo.push_back(info);
159 }
160
161 return success();
162 }
163
164public:
165 LogicalResult
166 matchAndRewrite(InstanceOp instanceOp, OpAdaptor adaptor,
167 ConversionPatternRewriter &rewriter) const override {
168 // Make sure the parent is already converted such that we already have a
169 // constructor and update function to insert operations into.
170 auto scModule = instanceOp->getParentOfType<SCModuleOp>();
171 if (!scModule)
172 return rewriter.notifyMatchFailure(instanceOp,
173 "parent was not an SCModuleOp");
174
175 // Get the builders for the different places to insert operations.
176 auto ctor = scModule.getOrCreateCtor();
177 OpBuilder stateBuilder(ctor);
178 OpBuilder initBuilder = OpBuilder::atBlockEnd(ctor.getBodyBlock());
179
180 // Collect the port types and names of the instantiated module and convert
181 // them to appropriate systemc types.
182 SmallVector<systemc::ModuleType::PortInfo> portInfo;
183 if (failed(collectPortInfo<InputType>(adaptor.getInputs(),
184 adaptor.getArgNames(), portInfo)) ||
185 failed(collectPortInfo<OutputType>(instanceOp->getResults(),
186 adaptor.getResultNames(), portInfo)))
187 return instanceOp->emitOpError("inout ports not supported");
188
189 Location loc = instanceOp->getLoc();
190 auto instanceName = instanceOp.getInstanceNameAttr();
191 auto instModuleName = instanceOp.getModuleNameAttr();
192
193 // Declare the instance.
194 auto instDecl = InstanceDeclOp::create(stateBuilder, loc, instanceName,
195 instModuleName, portInfo);
196
197 // Bind the input ports.
198 for (size_t i = 0, numInputs = adaptor.getInputs().size(); i < numInputs;
199 ++i) {
200 Value input = adaptor.getInputs()[i];
201 auto portId = rewriter.getIndexAttr(i);
202 StringAttr signalName = rewriter.getStringAttr(
203 instanceName.getValue() + "_" + portInfo[i].name.getValue());
204
205 if (auto readOp = input.getDefiningOp<SignalReadOp>()) {
206 // Use the read channel directly without adding an
207 // intermediate signal.
208 BindPortOp::create(initBuilder, loc, instDecl, portId,
209 readOp.getInput());
210 continue;
211 }
212
213 // Otherwise, create an intermediate signal to bind the instance port to.
214 Type sigType = SignalType::get(getSignalBaseType(portInfo[i].type));
215 Value channel = SignalOp::create(stateBuilder, loc, sigType, signalName);
216 BindPortOp::create(initBuilder, loc, instDecl, portId, channel);
217 SignalWriteOp::create(rewriter, loc, channel, input);
218 }
219
220 // Bind the output ports.
221 for (size_t i = 0, numOutputs = instanceOp->getNumResults(); i < numOutputs;
222 ++i) {
223 size_t numInputs = adaptor.getInputs().size();
224 Value output = instanceOp->getResult(i);
225 auto portId = rewriter.getIndexAttr(i + numInputs);
226 StringAttr signalName =
227 rewriter.getStringAttr(instanceName.getValue() + "_" +
228 portInfo[i + numInputs].name.getValue());
229
230 if (output.hasOneUse()) {
231 if (auto writeOp = dyn_cast<SignalWriteOp>(*output.user_begin())) {
232 // Use the channel written to directly. When there are multiple
233 // channels this value is written to or it is used somewhere else, we
234 // cannot shortcut it and have to insert an intermediate value because
235 // we cannot insert multiple bind statements for one submodule port.
236 // It is also necessary to bind it to an intermediate signal when it
237 // has no uses as every port has to be bound to a channel.
238 BindPortOp::create(initBuilder, loc, instDecl, portId,
239 writeOp.getDest());
240 writeOp->erase();
241 continue;
242 }
243 }
244
245 // Otherwise, create an intermediate signal.
246 Type sigType =
247 SignalType::get(getSignalBaseType(portInfo[i + numInputs].type));
248 Value channel = SignalOp::create(stateBuilder, loc, sigType, signalName);
249 BindPortOp::create(initBuilder, loc, instDecl, portId, channel);
250 auto instOut = SignalReadOp::create(rewriter, loc, channel);
251 output.replaceAllUsesWith(instOut);
252 }
253
254 rewriter.eraseOp(instanceOp);
255 return success();
256 }
257};
258
259} // namespace
260
261//===----------------------------------------------------------------------===//
262// Conversion Infrastructure
263//===----------------------------------------------------------------------===//
264
265static void populateLegality(ConversionTarget &target) {
266 target.addIllegalDialect<HWDialect>();
267 target.addLegalDialect<mlir::BuiltinDialect>();
268 target.addLegalDialect<systemc::SystemCDialect>();
269 target.addLegalDialect<comb::CombDialect>();
270 target.addLegalDialect<emitc::EmitCDialect>();
271 target.addLegalOp<hw::ConstantOp>();
272}
273
274static void populateOpConversion(RewritePatternSet &patterns,
275 TypeConverter &typeConverter) {
276 patterns.add<ConvertHWModule, ConvertInstance>(typeConverter,
277 patterns.getContext());
278}
279
280static void populateTypeConversion(TypeConverter &converter) {
281 converter.addConversion([](Type type) { return type; });
282 converter.addConversion([&](SignalType type) {
283 return SignalType::get(converter.convertType(type.getBaseType()));
284 });
285 converter.addConversion([&](InputType type) {
286 return InputType::get(converter.convertType(type.getBaseType()));
287 });
288 converter.addConversion([&](systemc::InOutType type) {
289 return systemc::InOutType::get(converter.convertType(type.getBaseType()));
290 });
291 converter.addConversion([&](OutputType type) {
292 return OutputType::get(converter.convertType(type.getBaseType()));
293 });
294 converter.addConversion([](IntegerType type) -> Type {
295 auto bw = type.getIntOrFloatBitWidth();
296 if (bw == 1)
297 return type;
298
299 if (bw <= 64) {
300 if (type.isSigned())
301 return systemc::IntType::get(type.getContext(), bw);
302
303 return UIntType::get(type.getContext(), bw);
304 }
305
306 if (bw <= 512) {
307 if (type.isSigned())
308 return BigIntType::get(type.getContext(), bw);
309
310 return BigUIntType::get(type.getContext(), bw);
311 }
312
313 return BitVectorType::get(type.getContext(), bw);
314 });
315
316 converter.addSourceMaterialization(
317 [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
318 assert(values.size() == 1);
319 auto op = ConvertOp::create(builder, loc, type, values[0]);
320 return op.getResult();
321 });
322
323 converter.addTargetMaterialization(
324 [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
325 assert(values.size() == 1);
326 auto op = ConvertOp::create(builder, loc, type, values[0]);
327 return op.getResult();
328 });
329}
330
331//===----------------------------------------------------------------------===//
332// HW to SystemC Conversion Pass
333//===----------------------------------------------------------------------===//
334
335namespace {
336struct HWToSystemCPass
337 : public circt::impl::ConvertHWToSystemCBase<HWToSystemCPass> {
338 void runOnOperation() override;
339};
340} // namespace
341
342/// Create a HW to SystemC dialects conversion pass.
343std::unique_ptr<OperationPass<ModuleOp>> circt::createConvertHWToSystemCPass() {
344 return std::make_unique<HWToSystemCPass>();
345}
346
347/// This is the main entrypoint for the HW to SystemC conversion pass.
348void HWToSystemCPass::runOnOperation() {
349 MLIRContext &context = getContext();
350 ModuleOp module = getOperation();
351
352 // Create the include operation here to have exactly one 'systemc' include at
353 // the top instead of one per module.
354 OpBuilder builder(module.getRegion());
355 emitc::IncludeOp::create(builder, module->getLoc(), "systemc.h", true);
356
357 ConversionTarget target(context);
358 TypeConverter typeConverter;
359 RewritePatternSet patterns(&context);
360 populateLegality(target);
361 populateTypeConversion(typeConverter);
362 populateOpConversion(patterns, typeConverter);
363
364 if (failed(applyFullConversion(module, target, std::move(patterns))))
365 signalPassFailure();
366}
assert(baseType &&"element must be base type")
static void populateLegality(ConversionTarget &target)
static void populateOpConversion(RewritePatternSet &patterns, TypeConverter &typeConverter)
static void populateTypeConversion(TypeConverter &converter)
static BigIntType get(MLIRContext *context, unsigned width)
static BigUIntType get(MLIRContext *context, unsigned width)
static BitVectorType get(MLIRContext *context, unsigned width)
static IntType get(MLIRContext *context, unsigned width)
void info(Twine message)
Definition LSPUtils.cpp:20
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.
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createConvertHWToSystemCPass()
Create a HW to SystemC dialects conversion pass.
Definition hw.py:1