CIRCT 20.0.0git
Loading...
Searching...
No Matches
SimToSV.cpp
Go to the documentation of this file.
1//===- LowerSimToSV.cpp - Sim to SV lowering ------------------------------===//
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 transform translates Sim ops to SV.
10//
11//===----------------------------------------------------------------------===//
12
22#include "mlir/IR/Builders.h"
23#include "mlir/IR/DialectImplementation.h"
24#include "mlir/IR/ImplicitLocOpBuilder.h"
25#include "mlir/IR/Threading.h"
26#include "mlir/Pass/Pass.h"
27#include "mlir/Transforms/DialectConversion.h"
28
29#define DEBUG_TYPE "lower-sim-to-sv"
30
31namespace circt {
32#define GEN_PASS_DEF_LOWERSIMTOSV
33#include "circt/Conversion/Passes.h.inc"
34} // namespace circt
35
36using namespace circt;
37using namespace sim;
38
39namespace {
40
41struct SimConversionState {
42 hw::HWModuleOp module;
43 bool usedSynthesisMacro = false;
44 SetVector<StringAttr> dpiCallees;
45};
46
47template <typename T>
48struct SimConversionPattern : public OpConversionPattern<T> {
49 explicit SimConversionPattern(MLIRContext *context, SimConversionState &state)
50 : OpConversionPattern<T>(context), state(state) {}
51
52 SimConversionState &state;
53};
54
55} // namespace
56
57// Lower `sim.plusargs.test` to a standard SV implementation.
58//
59class PlusArgsTestLowering : public SimConversionPattern<PlusArgsTestOp> {
60public:
61 using SimConversionPattern<PlusArgsTestOp>::SimConversionPattern;
62
63 LogicalResult
64 matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor,
65 ConversionPatternRewriter &rewriter) const final {
66 auto loc = op.getLoc();
67 auto resultType = rewriter.getIntegerType(1);
68 auto str = rewriter.create<sv::ConstantStrOp>(loc, op.getFormatString());
69 auto reg = rewriter.create<sv::RegOp>(loc, resultType,
70 rewriter.getStringAttr("_pargs"));
71 rewriter.create<sv::InitialOp>(loc, [&] {
72 auto call = rewriter.create<sv::SystemFunctionOp>(
73 loc, resultType, "test$plusargs", ArrayRef<Value>{str});
74 rewriter.create<sv::BPAssignOp>(loc, reg, call);
75 });
76
77 rewriter.replaceOpWithNewOp<sv::ReadInOutOp>(op, reg);
78 return success();
79 }
80};
81
82// Lower `sim.plusargs.value` to a standard SV implementation.
83//
84class PlusArgsValueLowering : public SimConversionPattern<PlusArgsValueOp> {
85public:
86 using SimConversionPattern<PlusArgsValueOp>::SimConversionPattern;
87
88 LogicalResult
89 matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor,
90 ConversionPatternRewriter &rewriter) const final {
91 auto loc = op.getLoc();
92
93 auto i1ty = rewriter.getIntegerType(1);
94 auto type = op.getResult().getType();
95
96 auto regv = rewriter.create<sv::RegOp>(loc, type,
97 rewriter.getStringAttr("_pargs_v_"));
98 auto regf = rewriter.create<sv::RegOp>(loc, i1ty,
99 rewriter.getStringAttr("_pargs_f"));
100
101 state.usedSynthesisMacro = true;
102 rewriter.create<sv::IfDefOp>(
103 loc, "SYNTHESIS",
104 [&]() {
105 auto cstFalse = rewriter.create<hw::ConstantOp>(loc, APInt(1, 0));
106 auto cstZ = rewriter.create<sv::ConstantZOp>(loc, type);
107 auto assignZ = rewriter.create<sv::AssignOp>(loc, regv, cstZ);
109 assignZ,
110 sv::SVAttributeAttr::get(
111 rewriter.getContext(),
112 "This dummy assignment exists to avoid undriven lint "
113 "warnings (e.g., Verilator UNDRIVEN).",
114 /*emitAsComment=*/true));
115 rewriter.create<sv::AssignOp>(loc, regf, cstFalse);
116 },
117 [&]() {
118 rewriter.create<sv::InitialOp>(loc, [&] {
119 auto zero32 = rewriter.create<hw::ConstantOp>(loc, APInt(32, 0));
120 auto tmpResultType = rewriter.getIntegerType(32);
121 auto str =
122 rewriter.create<sv::ConstantStrOp>(loc, op.getFormatString());
123 auto call = rewriter.create<sv::SystemFunctionOp>(
124 loc, tmpResultType, "value$plusargs",
125 ArrayRef<Value>{str, regv});
126 auto test = rewriter.create<comb::ICmpOp>(
127 loc, comb::ICmpPredicate::ne, call, zero32, true);
128 rewriter.create<sv::BPAssignOp>(loc, regf, test);
129 });
130 });
131
132 auto readf = rewriter.create<sv::ReadInOutOp>(loc, regf);
133 auto readv = rewriter.create<sv::ReadInOutOp>(loc, regv);
134 rewriter.replaceOp(op, {readf, readv});
135 return success();
136 }
137};
138
139template <typename FromOp, typename ToOp>
140class SimulatorStopLowering : public SimConversionPattern<FromOp> {
141public:
142 using SimConversionPattern<FromOp>::SimConversionPattern;
143
144 LogicalResult
145 matchAndRewrite(FromOp op, typename FromOp::Adaptor adaptor,
146 ConversionPatternRewriter &rewriter) const final {
147 auto loc = op.getLoc();
148
149 Value clockCast = rewriter.create<seq::FromClockOp>(loc, adaptor.getClk());
150
151 this->state.usedSynthesisMacro = true;
152 rewriter.create<sv::IfDefOp>(
153 loc, "SYNTHESIS", [&] {},
154 [&] {
155 rewriter.create<sv::AlwaysOp>(
156 loc, sv::EventControl::AtPosEdge, clockCast, [&] {
157 rewriter.create<sv::IfOp>(loc, adaptor.getCond(),
158 [&] { rewriter.create<ToOp>(loc); });
159 });
160 });
161
162 rewriter.eraseOp(op);
163
164 return success();
165 }
166};
167
168class DPICallLowering : public SimConversionPattern<DPICallOp> {
169public:
170 using SimConversionPattern<DPICallOp>::SimConversionPattern;
171
172 LogicalResult
173 matchAndRewrite(DPICallOp op, OpAdaptor adaptor,
174 ConversionPatternRewriter &rewriter) const final {
175 auto loc = op.getLoc();
176 // Record the callee.
177 state.dpiCallees.insert(op.getCalleeAttr().getAttr());
178
179 bool isClockedCall = !!op.getClock();
180 bool hasEnable = !!op.getEnable();
181
182 SmallVector<sv::RegOp> temporaries;
183 SmallVector<Value> reads;
184 for (auto [type, result] :
185 llvm::zip(op.getResultTypes(), op.getResults())) {
186 temporaries.push_back(rewriter.create<sv::RegOp>(op.getLoc(), type));
187 reads.push_back(
188 rewriter.create<sv::ReadInOutOp>(op.getLoc(), temporaries.back()));
189 }
190
191 auto emitCall = [&]() {
192 auto call = rewriter.create<sv::FuncCallProceduralOp>(
193 op.getLoc(), op.getResultTypes(), op.getCalleeAttr(),
194 adaptor.getInputs());
195 for (auto [lhs, rhs] : llvm::zip(temporaries, call.getResults())) {
196 if (isClockedCall)
197 rewriter.create<sv::PAssignOp>(op.getLoc(), lhs, rhs);
198 else
199 rewriter.create<sv::BPAssignOp>(op.getLoc(), lhs, rhs);
200 }
201 };
202 if (isClockedCall) {
203 Value clockCast =
204 rewriter.create<seq::FromClockOp>(loc, adaptor.getClock());
205 rewriter.create<sv::AlwaysOp>(
206 loc, ArrayRef<sv::EventControl>{sv::EventControl::AtPosEdge},
207 ArrayRef<Value>{clockCast}, [&]() {
208 if (!hasEnable)
209 return emitCall();
210 rewriter.create<sv::IfOp>(op.getLoc(), adaptor.getEnable(),
211 emitCall);
212 });
213 } else {
214 // Unclocked call is lowered into always_comb.
215 // TODO: If there is a return value and no output argument, use an
216 // unclocked call op.
217 rewriter.create<sv::AlwaysCombOp>(loc, [&]() {
218 if (!hasEnable)
219 return emitCall();
220 auto assignXToResults = [&] {
221 for (auto lhs : temporaries) {
222 auto xValue = rewriter.create<sv::ConstantXOp>(
223 op.getLoc(), lhs.getType().getElementType());
224 rewriter.create<sv::BPAssignOp>(op.getLoc(), lhs, xValue);
225 }
226 };
227 rewriter.create<sv::IfOp>(op.getLoc(), adaptor.getEnable(), emitCall,
228 assignXToResults);
229 });
230 }
231
232 rewriter.replaceOp(op, reads);
233 return success();
234 }
235};
236
237// A helper struct to lower DPI function/call.
239 llvm::DenseMap<StringAttr, StringAttr> symbolToFragment;
241 LowerDPIFunc(mlir::ModuleOp module) { nameSpace.add(module); }
242 void lower(sim::DPIFuncOp func);
243 void addFragments(hw::HWModuleOp module,
244 ArrayRef<StringAttr> dpiCallees) const;
245};
246
247void LowerDPIFunc::lower(sim::DPIFuncOp func) {
248 ImplicitLocOpBuilder builder(func.getLoc(), func);
249 ArrayAttr inputLocsAttr, outputLocsAttr;
250 if (func.getArgumentLocs()) {
251 SmallVector<Attribute> inputLocs, outputLocs;
252 for (auto [port, loc] :
253 llvm::zip(func.getModuleType().getPorts(),
254 func.getArgumentLocsAttr().getAsRange<LocationAttr>())) {
255 (port.dir == hw::ModulePort::Output ? outputLocs : inputLocs)
256 .push_back(loc);
257 }
258 inputLocsAttr = builder.getArrayAttr(inputLocs);
259 outputLocsAttr = builder.getArrayAttr(outputLocs);
260 }
261
262 auto svFuncDecl =
263 builder.create<sv::FuncOp>(func.getSymNameAttr(), func.getModuleType(),
264 func.getPerArgumentAttrsAttr(), inputLocsAttr,
265 outputLocsAttr, func.getVerilogNameAttr());
266 // DPI function is a declaration so it must be a private function.
267 svFuncDecl.setPrivate();
268 auto name = builder.getStringAttr(nameSpace.newName(
269 func.getSymNameAttr().getValue(), "dpi_import_fragument"));
270
271 // Add include guards to avoid duplicate declarations. See Issue 7458.
272 auto macroDecl = builder.create<sv::MacroDeclOp>(nameSpace.newName(
273 "__CIRCT_DPI_IMPORT", func.getSymNameAttr().getValue().upper()));
274 builder.create<emit::FragmentOp>(name, [&]() {
275 builder.create<sv::IfDefOp>(
276 macroDecl.getSymNameAttr(), []() {},
277 [&]() {
278 builder.create<sv::FuncDPIImportOp>(func.getSymNameAttr(),
279 StringAttr());
280 builder.create<sv::MacroDefOp>(macroDecl.getSymNameAttr(), "");
281 });
282 });
283
284 symbolToFragment.insert({func.getSymNameAttr(), name});
285 func.erase();
286}
287
289 ArrayRef<StringAttr> dpiCallees) const {
290 llvm::SetVector<Attribute> fragments;
291 // Add existing emit fragments.
292 if (auto exstingFragments =
293 module->getAttrOfType<ArrayAttr>(emit::getFragmentsAttrName()))
294 for (auto fragment : exstingFragments.getAsRange<FlatSymbolRefAttr>())
295 fragments.insert(fragment);
296 for (auto callee : dpiCallees) {
297 auto attr = symbolToFragment.at(callee);
298 fragments.insert(FlatSymbolRefAttr::get(attr));
299 }
300 if (!fragments.empty())
301 module->setAttr(
302 emit::getFragmentsAttrName(),
303 ArrayAttr::get(module.getContext(), fragments.takeVector()));
304}
305
306namespace {
307struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
308 void runOnOperation() override {
309 auto circuit = getOperation();
310 MLIRContext *context = &getContext();
311 LowerDPIFunc lowerDPIFunc(circuit);
312
313 // Lower DPI functions.
314 for (auto func :
315 llvm::make_early_inc_range(circuit.getOps<sim::DPIFuncOp>()))
316 lowerDPIFunc.lower(func);
317
318 std::atomic<bool> usedSynthesisMacro = false;
319 auto lowerModule = [&](hw::HWModuleOp module) {
320 SimConversionState state;
321 ConversionTarget target(*context);
322 target.addIllegalDialect<SimDialect>();
323 target.addLegalDialect<sv::SVDialect>();
324 target.addLegalDialect<hw::HWDialect>();
325 target.addLegalDialect<seq::SeqDialect>();
326 target.addLegalDialect<comb::CombDialect>();
327
328 RewritePatternSet patterns(context);
329 patterns.add<PlusArgsTestLowering>(context, state);
330 patterns.add<PlusArgsValueLowering>(context, state);
332 state);
334 state);
335 patterns.add<DPICallLowering>(context, state);
336 auto result = applyPartialConversion(module, target, std::move(patterns));
337
338 if (failed(result))
339 return result;
340
341 // Set the emit fragment.
342 lowerDPIFunc.addFragments(module, state.dpiCallees.takeVector());
343
344 if (state.usedSynthesisMacro)
345 usedSynthesisMacro = true;
346 return result;
347 };
348
349 if (failed(mlir::failableParallelForEach(
350 context, circuit.getOps<hw::HWModuleOp>(), lowerModule)))
351 return signalPassFailure();
352
353 if (usedSynthesisMacro) {
354 Operation *op = circuit.lookupSymbol("SYNTHESIS");
355 if (op) {
356 if (!isa<sv::MacroDeclOp>(op)) {
357 op->emitOpError("should be a macro declaration");
358 return signalPassFailure();
359 }
360 } else {
361 auto builder = ImplicitLocOpBuilder::atBlockBegin(
362 UnknownLoc::get(context), circuit.getBody());
363 builder.create<sv::MacroDeclOp>("SYNTHESIS");
364 }
365 }
366 }
367};
368} // anonymous namespace
369
370std::unique_ptr<Pass> circt::createLowerSimToSVPass() {
371 return std::make_unique<SimToSVPass>();
372}
LogicalResult matchAndRewrite(DPICallOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:173
LogicalResult matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:64
LogicalResult matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:89
LogicalResult matchAndRewrite(FromOp op, typename FromOp::Adaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:145
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
void add(mlir::ModuleOp module)
Definition Namespace.h:48
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:85
create(data_type, value)
Definition hw.py:433
create(dest, src)
Definition sv.py:98
create(value)
Definition sv.py:106
Definition sv.py:68
void setSVAttributes(mlir::Operation *op, mlir::ArrayAttr attrs)
Set the SV attributes of an operation.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createLowerSimToSVPass()
Definition SimToSV.cpp:370
circt::Namespace nameSpace
Definition SimToSV.cpp:240
void lower(sim::DPIFuncOp func)
Definition SimToSV.cpp:247
void addFragments(hw::HWModuleOp module, ArrayRef< StringAttr > dpiCallees) const
Definition SimToSV.cpp:288
llvm::DenseMap< StringAttr, StringAttr > symbolToFragment
Definition SimToSV.cpp:239
LowerDPIFunc(mlir::ModuleOp module)
Definition SimToSV.cpp:241