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