CIRCT  18.0.0git
ExportQuartusTcl.cpp
Go to the documentation of this file.
1 //===- ExportQuartusTcl.cpp - Emit Quartus-flavored Tcl -------------------===//
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 // Write out Tcl with the appropriate API calls for Quartus.
10 //
11 //===----------------------------------------------------------------------===//
12 
14 #include "circt/Dialect/HW/HWOps.h"
21 #include "circt/Dialect/SV/SVOps.h"
23 #include "circt/Support/LLVM.h"
24 #include "mlir/IR/Builders.h"
25 #include "mlir/Tools/mlir-translate/Translation.h"
26 #include "llvm/ADT/StringSet.h"
27 #include "llvm/ADT/TypeSwitch.h"
28 #include "llvm/Support/raw_ostream.h"
29 
30 using namespace circt;
31 using namespace hw;
32 using namespace msft;
33 
34 TclEmitter::TclEmitter(mlir::ModuleOp topLevel)
35  : topLevel(topLevel), populated(false) {}
36 
37 LogicalResult TclEmitter::populate() {
38  if (populated)
39  return success();
40 
41  // Populated the symbol cache.
42  for (auto symOp : topLevel.getOps<mlir::SymbolOpInterface>())
43  if (auto name = symOp.getNameAttr())
44  topLevelSymbols.addDefinition(name, symOp);
46  populated = true;
47 
48  // Bin any operations we may need to emit based on the root module in the
49  // instance hierarchy path and the potential instance name.
50 
51  // Look in InstanceHierarchyOps to get the instance named ones.
52  for (auto hier : topLevel.getOps<InstanceHierarchyOp>()) {
53  Operation *mod = topLevelSymbols.getDefinition(hier.getTopModuleRefAttr());
54  auto &tclOps = tclOpsForModInstance[mod][hier.getInstNameAttr()];
55  for (auto tclOp : hier.getOps<DynInstDataOpInterface>()) {
56  assert(tclOp.getTopModule(topLevelSymbols) == mod &&
57  "Referenced mod does does not match");
58  tclOps.push_back(tclOp);
59  }
60  }
61 
62  // Locations at the global scope are assumed to refer to the module without an
63  // instance.
64  for (auto tclOp : topLevel.getOps<DynInstDataOpInterface>()) {
65  Operation *mod = tclOp.getTopModule(topLevelSymbols);
66  assert(mod && "Must be able to resolve top module");
67  tclOpsForModInstance[mod][{}].push_back(tclOp);
68  }
69 
70  return success();
71 }
72 
73 Operation *TclEmitter::getDefinition(FlatSymbolRefAttr sym) {
74  if (failed(populate()))
75  return nullptr;
76  return topLevelSymbols.getDefinition(sym);
77 }
78 
79 // TODO: Currently assumes Stratix 10 and QuartusPro. Make more general.
80 namespace {
81 /// Utility struct to assist in output and track other relevent state which are
82 /// not specific to the entity hierarchy (global WRT to the entity hierarchy).
83 struct TclOutputState {
84  TclOutputState(TclEmitter &emitter, llvm::raw_ostream &os)
85  : os(os), emitter(emitter) {}
86 
87  llvm::raw_ostream &os;
88  llvm::raw_ostream &indent() {
89  os.indent(2);
90  return os;
91  };
92 
93  TclEmitter &emitter;
94  SmallVector<Attribute> symbolRefs;
95 
96  void emit(PhysLocationAttr);
97  LogicalResult emitLocationAssignment(UnaryDynInstDataOpInterface refOp,
98  PhysLocationAttr,
99  std::optional<StringRef> subpath);
100 
101  LogicalResult emit(PDPhysRegionOp region);
102  LogicalResult emit(PDPhysLocationOp loc);
103  LogicalResult emit(PDRegPhysLocationOp);
104  LogicalResult emit(DynamicInstanceVerbatimAttrOp attr);
105  LogicalResult emit(PDMulticycleOp op);
106 
107  void emitPath(hw::HierPathOp ref, std::optional<StringRef> subpath);
108  void emitInnerRefPart(hw::InnerRefAttr innerRef);
109 
110  /// Get the HierPathOp to which the given operation is pointing. Add it to
111  /// the set of used global refs.
112  HierPathOp getRefOp(UnaryDynInstDataOpInterface op) {
113  return getRefOp(op.getLoc(), op.getPathSym());
114  }
115 
116  /// Get the HierPathOp to which a given value is pointing. Add it to the
117  /// set of used global refs.
118  HierPathOp getRefOp(Location loc, FlatSymbolRefAttr pathSym) {
119  auto ref = dyn_cast_or_null<hw::HierPathOp>(emitter.getDefinition(pathSym));
120  if (ref)
121  emitter.usedRef(ref);
122  else
123  emitError(loc, "could not find hw.hierpath named ") << pathSym;
124  return ref;
125  }
126 };
127 } // anonymous namespace
128 
129 void TclOutputState::emitInnerRefPart(hw::InnerRefAttr innerRef) {
130  // We append new symbolRefs to the state, so s.symbolRefs.size() is the
131  // index of the InnerRefAttr we are about to add.
132  os << "{{" << symbolRefs.size() << "}}";
133 
134  // Append a new inner reference for the template above.
135  symbolRefs.push_back(innerRef);
136 }
137 
138 void TclOutputState::emitPath(hw::HierPathOp ref,
139  std::optional<StringRef> subpath) {
140  os << "{{" << symbolRefs.size() << ":|}}";
141  symbolRefs.push_back(FlatSymbolRefAttr::get(ref));
142  if (subpath)
143  os << subpath;
144 }
145 
146 void TclOutputState::emit(PhysLocationAttr pla) {
147  // Different devices have different 'number' letters (the 'N' in 'N0'). M20Ks
148  // and DSPs happen to have the same one, probably because they never co-exist
149  // at the same location.
150  char numCharacter;
151  switch (pla.getPrimitiveType().getValue()) {
152  case PrimitiveType::M20K:
153  os << "M20K";
154  numCharacter = 'N';
155  break;
156  case PrimitiveType::DSP:
157  os << "MPDSP";
158  numCharacter = 'N';
159  break;
160  case PrimitiveType::FF:
161  os << "FF";
162  numCharacter = 'N';
163  break;
164  }
165 
166  // Write out the rest of the location info.
167  os << "_X" << pla.getX() << "_Y" << pla.getY() << "_" << numCharacter
168  << pla.getNum();
169 }
170 
171 /// Emit tcl in the form of:
172 /// "set_location_assignment MPDSP_X34_Y285_N0 -to
173 /// $parent|fooInst|entityName(subpath)"
174 LogicalResult
175 TclOutputState::emitLocationAssignment(UnaryDynInstDataOpInterface refOp,
176  PhysLocationAttr loc,
177  std::optional<StringRef> subpath) {
178  indent() << "set_location_assignment ";
179  emit(loc);
180 
181  // To which entity does this apply?
182  os << " -to $parent|";
183  emitPath(getRefOp(refOp), subpath);
184 
185  return success();
186 }
187 
188 LogicalResult TclOutputState::emit(PDPhysLocationOp loc) {
189  if (failed(emitLocationAssignment(loc, loc.getLoc(), loc.getSubPath())))
190  return failure();
191  os << '\n';
192  return success();
193 }
194 
195 LogicalResult TclOutputState::emit(PDRegPhysLocationOp locs) {
196  ArrayRef<PhysLocationAttr> locArr = locs.getLocs().getLocs();
197  for (size_t i = 0, e = locArr.size(); i < e; ++i) {
198  PhysLocationAttr pla = locArr[i];
199  if (!pla)
200  continue;
201  if (failed(emitLocationAssignment(locs, pla, {})))
202  return failure();
203  os << "[" << i << "]\n";
204  }
205  return success();
206 }
207 
208 LogicalResult TclOutputState::emit(PDMulticycleOp op) {
209  indent() << "set_multicycle_path ";
210  os << "-hold 1 ";
211  os << "-setup " << op.getCycles() << " ";
212  os << "-from [get_registers {$parent|";
213  emitPath(getRefOp(op.getLoc(), op.getSourceAttr()), std::nullopt);
214  os << "}] ";
215  os << "-to [get_registers {$parent|";
216  emitPath(getRefOp(op.getLoc(), op.getDestAttr()), std::nullopt);
217  os << "}]\n";
218  return success();
219 }
220 
221 /// Emit tcl in the form of:
222 /// "set_global_assignment -name NAME VALUE -to $parent|fooInst|entityName"
223 LogicalResult TclOutputState::emit(DynamicInstanceVerbatimAttrOp attr) {
224  HierPathOp ref = getRefOp(attr);
225  indent() << "set_instance_assignment -name " << attr.getName() << " "
226  << attr.getValue();
227 
228  // To which entity does this apply?
229  os << " -to $parent|";
230  emitPath(ref, attr.getSubPath());
231  os << '\n';
232  return success();
233 }
234 
235 /// Emit tcl in the form of:
236 /// set_instance_assignment -name PLACE_REGION "X1 Y1 X20 Y20" -to $parent|a|b|c
237 /// set_instance_assignment -name RESERVE_PLACE_REGION OFF -to $parent|a|b|c
238 /// set_instance_assignment -name CORE_ONLY_PLACE_REGION ON -to $parent|a|b|c
239 /// set_instance_assignment -name REGION_NAME test_region -to $parent|a|b|c
240 LogicalResult TclOutputState::emit(PDPhysRegionOp region) {
241  HierPathOp ref = getRefOp(region);
242 
243  auto physicalRegion = dyn_cast_or_null<DeclPhysicalRegionOp>(
244  emitter.getDefinition(region.getPhysRegionRefAttr()));
245  if (!physicalRegion)
246  return region.emitOpError(
247  "could not find physical region declaration named ")
248  << region.getPhysRegionRefAttr();
249 
250  // PLACE_REGION directive.
251  indent() << "set_instance_assignment -name PLACE_REGION \"";
252  auto physicalBounds =
253  physicalRegion.getBounds().getAsRange<PhysicalBoundsAttr>();
254  llvm::interleave(
255  physicalBounds, os,
256  [&](PhysicalBoundsAttr bounds) {
257  os << 'X' << bounds.getXMin() << ' ';
258  os << 'Y' << bounds.getYMin() << ' ';
259  os << 'X' << bounds.getXMax() << ' ';
260  os << 'Y' << bounds.getYMax();
261  },
262  ";");
263  os << '"';
264 
265  os << " -to $parent|";
266  emitPath(ref, region.getSubPath());
267  os << '\n';
268 
269  // RESERVE_PLACE_REGION directive.
270  indent() << "set_instance_assignment -name RESERVE_PLACE_REGION OFF";
271  os << " -to $parent|";
272  emitPath(ref, region.getSubPath());
273  os << '\n';
274 
275  // CORE_ONLY_PLACE_REGION directive.
276  indent() << "set_instance_assignment -name CORE_ONLY_PLACE_REGION ON";
277  os << " -to $parent|";
278  emitPath(ref, region.getSubPath());
279  os << '\n';
280 
281  // REGION_NAME directive.
282  indent() << "set_instance_assignment -name REGION_NAME ";
283  os << physicalRegion.getName();
284  os << " -to $parent|";
285  emitPath(ref, region.getSubPath());
286  os << '\n';
287  return success();
288 }
289 
290 /// Write out all the relevant tcl commands. Create one 'proc' per module which
291 /// takes the parent entity name since we don't assume that the created module
292 /// is the top level for the entire design.
293 LogicalResult TclEmitter::emit(Operation *hwMod, StringRef outputFile) {
294  if (failed(populate()))
295  return failure();
296 
297  // Build up the output Tcl, tracking symbol references in state.
298  std::string s;
299  llvm::raw_string_ostream os(s);
300  TclOutputState state(*this, os);
301 
302  // Iterate through all the "instances" for 'hwMod' and produce a tcl proc for
303  // each one.
304  for (const auto &tclOpsForInstancesKV : tclOpsForModInstance[hwMod]) {
305  StringAttr instName = tclOpsForInstancesKV.first;
306  os << "proc {{" << state.symbolRefs.size() << "}}";
307  if (instName)
308  os << '_' << instName.getValue();
309  os << "_config { parent } {\n";
310  state.symbolRefs.push_back(SymbolRefAttr::get(hwMod));
311 
312  // Loop through the ops relevant to the specified root module "instance".
313  LogicalResult ret = success();
314  const auto &tclOpsForMod = tclOpsForInstancesKV.second;
315  for (Operation *tclOp : tclOpsForMod) {
316  LogicalResult rc =
317  TypeSwitch<Operation *, LogicalResult>(tclOp)
318  .Case([&](PDPhysLocationOp op) { return state.emit(op); })
319  .Case([&](PDRegPhysLocationOp op) { return state.emit(op); })
320  .Case([&](PDPhysRegionOp op) { return state.emit(op); })
321  .Case([&](PDMulticycleOp op) { return state.emit(op); })
322  .Case([&](DynamicInstanceVerbatimAttrOp op) {
323  return state.emit(op);
324  })
325  .Default([](Operation *op) {
326  return op->emitOpError("could not determine how to output tcl");
327  });
328  if (failed(rc))
329  ret = failure();
330  }
331  os << "}\n\n";
332  }
333 
334  // Create a verbatim op containing the Tcl and symbol references.
335  OpBuilder builder = OpBuilder::atBlockEnd(hwMod->getBlock());
336  auto verbatim = builder.create<sv::VerbatimOp>(
337  builder.getUnknownLoc(), os.str(), ValueRange{},
338  builder.getArrayAttr(state.symbolRefs));
339 
340  // When requested, give the verbatim op an output file.
341  if (!outputFile.empty()) {
342  auto outputFileAttr =
343  OutputFileAttr::getFromFilename(builder.getContext(), outputFile);
344  verbatim->setAttr("output_file", outputFileAttr);
345  }
346 
347  return success();
348 }
assert(baseType &&"element must be base type")
Builder builder
mlir::Operation * getDefinition(mlir::Attribute attr) const override
Lookup a definition for 'symbol' in the cache.
Definition: HWSymCache.h:56
void freeze()
Mark the cache as frozen, which allows it to be shared across threads.
Definition: HWSymCache.h:75
void addDefinition(mlir::StringAttr modSymbol, mlir::StringAttr name, mlir::Operation *op, size_t port=~0ULL)
Definition: HWSymCache.h:43
Instantiate for all Tcl emissions.
Definition: ExportTcl.h:29
Operation * getDefinition(FlatSymbolRefAttr)
hw::HWSymbolCache topLevelSymbols
Definition: ExportTcl.h:42
mlir::ModuleOp topLevel
Definition: ExportTcl.h:39
DenseMap< Operation *, llvm::MapVector< StringAttr, SmallVector< DynInstDataOpInterface, 0 > > > tclOpsForModInstance
Map Module operations to their top-level "instance" names.
Definition: ExportTcl.h:48
LogicalResult emit(Operation *hwMod, StringRef outputFile)
Write out all the relevant tcl commands.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
Definition: hw.py:1
Definition: msft.py:1