CIRCT  19.0.0git
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 
16 #include "circt/Dialect/HW/HWOps.h"
17 #include "circt/Dialect/SV/SVOps.h"
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 
31 namespace circt {
32 #define GEN_PASS_DEF_LOWERSIMTOSV
33 #include "circt/Conversion/Passes.h.inc"
34 } // namespace circt
35 
36 using namespace circt;
37 using namespace sim;
38 
39 namespace {
40 
41 struct SimConversionState {
42  hw::HWModuleOp module;
43  bool usedSynthesisMacro = false;
44  SetVector<StringAttr> dpiCallees;
45 };
46 
47 template <typename T>
48 struct 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 //
59 class PlusArgsTestLowering : public SimConversionPattern<PlusArgsTestOp> {
60 public:
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 //
84 class PlusArgsValueLowering : public SimConversionPattern<PlusArgsValueOp> {
85 public:
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,
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 
139 template <typename FromOp, typename ToOp>
140 class SimulatorStopLowering : public SimConversionPattern<FromOp> {
141 public:
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 
168 class DPICallLowering : public SimConversionPattern<DPICallOp> {
169 public:
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.
238 struct LowerDPIFunc {
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 
247 void 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  builder.create<emit::FragmentOp>(name, [&]() {
272  builder.create<sv::FuncDPIImportOp>(func.getSymNameAttr(), StringAttr());
273  });
274 
275  symbolToFragment.insert({func.getSymNameAttr(), name});
276  func.erase();
277 }
278 
280  ArrayRef<StringAttr> dpiCallees) const {
281  llvm::SetVector<Attribute> fragments;
282  // Add existing emit fragments.
283  if (auto exstingFragments =
284  module->getAttrOfType<ArrayAttr>(emit::getFragmentsAttrName()))
285  for (auto fragment : exstingFragments.getAsRange<FlatSymbolRefAttr>())
286  fragments.insert(fragment);
287  for (auto callee : dpiCallees) {
288  auto attr = symbolToFragment.at(callee);
289  fragments.insert(FlatSymbolRefAttr::get(attr));
290  }
291  if (!fragments.empty())
292  module->setAttr(
294  ArrayAttr::get(module.getContext(), fragments.takeVector()));
295 }
296 
297 namespace {
298 struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
299  void runOnOperation() override {
300  auto circuit = getOperation();
301  MLIRContext *context = &getContext();
302  LowerDPIFunc lowerDPIFunc(circuit);
303 
304  // Lower DPI functions.
305  for (auto func :
306  llvm::make_early_inc_range(circuit.getOps<sim::DPIFuncOp>()))
307  lowerDPIFunc.lower(func);
308 
309  std::atomic<bool> usedSynthesisMacro = false;
310  auto lowerModule = [&](hw::HWModuleOp module) {
311  SimConversionState state;
312  ConversionTarget target(*context);
313  target.addIllegalDialect<SimDialect>();
314  target.addLegalDialect<sv::SVDialect>();
315  target.addLegalDialect<hw::HWDialect>();
316  target.addLegalDialect<seq::SeqDialect>();
317  target.addLegalDialect<comb::CombDialect>();
318 
319  RewritePatternSet patterns(context);
320  patterns.add<PlusArgsTestLowering>(context, state);
321  patterns.add<PlusArgsValueLowering>(context, state);
323  state);
325  state);
326  patterns.add<DPICallLowering>(context, state);
327  auto result = applyPartialConversion(module, target, std::move(patterns));
328 
329  if (failed(result))
330  return result;
331 
332  // Set the emit fragment.
333  lowerDPIFunc.addFragments(module, state.dpiCallees.takeVector());
334 
335  if (state.usedSynthesisMacro)
336  usedSynthesisMacro = true;
337  return result;
338  };
339 
340  if (failed(mlir::failableParallelForEach(
341  context, circuit.getOps<hw::HWModuleOp>(), lowerModule)))
342  return signalPassFailure();
343 
344  if (usedSynthesisMacro) {
345  Operation *op = circuit.lookupSymbol("SYNTHESIS");
346  if (op) {
347  if (!isa<sv::MacroDeclOp>(op)) {
348  op->emitOpError("should be a macro declaration");
349  return signalPassFailure();
350  }
351  } else {
352  auto builder = ImplicitLocOpBuilder::atBlockBegin(
353  UnknownLoc::get(context), circuit.getBody());
354  builder.create<sv::MacroDeclOp>("SYNTHESIS");
355  }
356  }
357  }
358 };
359 } // anonymous namespace
360 
361 std::unique_ptr<Pass> circt::createLowerSimToSVPass() {
362  return std::make_unique<SimToSVPass>();
363 }
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:46
def create(data_type, value)
Definition: hw.py:393
def create(dest, src)
Definition: sv.py:98
Definition: sv.py:15
def create(value)
Definition: sv.py:106
Definition: sv.py:68
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
StringRef getFragmentsAttrName()
Return the name of the fragments array attribute.
Definition: EmitOps.h:32
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.
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createLowerSimToSVPass()
Definition: SimToSV.cpp:361
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition: seq.py:20
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:279
llvm::DenseMap< StringAttr, StringAttr > symbolToFragment
Definition: SimToSV.cpp:239
LowerDPIFunc(mlir::ModuleOp module)
Definition: SimToSV.cpp:241