CIRCT  19.0.0git
CreateSiFiveMetadata.cpp
Go to the documentation of this file.
1 //===- CreateSiFiveMetadata.cpp - Create various metadata -------*- C++ -*-===//
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 file defines the CreateSiFiveMetadata pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "PassDetails.h"
24 #include "circt/Dialect/SV/SVOps.h"
25 #include "mlir/IR/ImplicitLocOpBuilder.h"
26 #include "mlir/IR/Location.h"
27 #include "llvm/ADT/DepthFirstIterator.h"
28 #include "llvm/ADT/STLExtras.h"
29 #include "llvm/ADT/StringSwitch.h"
30 #include "llvm/Support/JSON.h"
31 #include "llvm/Support/Path.h"
32 
33 using namespace circt;
34 using namespace firrtl;
35 
36 namespace {
37 
38 struct ObjectModelIR {
39  ObjectModelIR(
40  CircuitOp circtOp, FModuleOp dutMod, InstanceGraph &instanceGraph,
41  DenseMap<Operation *, hw::InnerSymbolNamespace> &moduleNamespaces)
42  : circtOp(circtOp), dutMod(dutMod),
43  circtNamespace(CircuitNamespace(circtOp)),
44  instancePathCache(InstancePathCache(instanceGraph)),
45  moduleNamespaces(moduleNamespaces) {}
46 
47  // Add the tracker annotation to the op and get a PathOp to the op.
48  PathOp createPathRef(Operation *op, hw::HierPathOp nla,
49  mlir::ImplicitLocOpBuilder &builderOM) {
50  auto *context = op->getContext();
51 
52  NamedAttrList fields;
53  auto id = DistinctAttr::create(UnitAttr::get(context));
54  fields.append("id", id);
55  fields.append("class", StringAttr::get(context, "circt.tracker"));
56  if (nla)
57  fields.append("circt.nonlocal", mlir::FlatSymbolRefAttr::get(nla));
58  AnnotationSet annos(op);
59  annos.addAnnotations(DictionaryAttr::get(context, fields));
60  annos.applyToOperation(op);
61  TargetKind kind = TargetKind::Reference;
62  if (isa<InstanceOp, FModuleLike>(op))
63  kind = TargetKind::Instance;
64 
65  // Create the path operation.
66  return builderOM.create<PathOp>(kind, id);
67  }
68 
69  // Create a ClassOp, with the specified fieldNames and fieldTypes as ports.
70  // The output property is set from the input property port.
71  ClassOp buildSimpleClassOp(OpBuilder &odsBuilder, Location loc, Twine name,
72  ArrayRef<StringRef> fieldNames,
73  ArrayRef<Type> fieldTypes) {
74  SmallVector<PortInfo, 10> ports;
75  for (auto [fieldName, fieldType] : llvm::zip(fieldNames, fieldTypes)) {
76  ports.emplace_back(odsBuilder.getStringAttr(fieldName + "_in"), fieldType,
78  ports.emplace_back(odsBuilder.getStringAttr(fieldName), fieldType,
80  }
81 
82  ClassOp classOp =
83  odsBuilder.create<ClassOp>(loc, odsBuilder.getStringAttr(name), ports);
84  Block *body = classOp.getBodyBlock();
85  auto prevLoc = odsBuilder.saveInsertionPoint();
86  odsBuilder.setInsertionPointToEnd(body);
87  auto args = body->getArguments();
88  for (unsigned i = 0, e = ports.size(); i != e; i += 2)
89  odsBuilder.create<PropAssignOp>(loc, args[i + 1], args[i]);
90 
91  odsBuilder.restoreInsertionPoint(prevLoc);
92 
93  return classOp;
94  }
95 
96  void createMemorySchema() {
97  auto *context = circtOp.getContext();
98 
99  auto unknownLoc = mlir::UnknownLoc::get(context);
100  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
101  unknownLoc, circtOp.getBodyBlock());
102 
103  // Add all the properties of a memory as fields of the class.
104  // The types must match exactly with the FMemModuleOp attribute type.
105 
106  mlir::Type extraPortsType[] = {
107  StringType::get(context), // name
108  StringType::get(context), // direction
109  FIntegerType::get(context) // Width
110  };
111  StringRef extraPortFields[3] = {"name", "direction", "width"};
112 
113  extraPortsClass =
114  buildSimpleClassOp(builderOM, unknownLoc, "ExtraPortsMemorySchema",
115  extraPortFields, extraPortsType);
116 
117  mlir::Type classFieldTypes[12] = {
118  StringType::get(context),
119  FIntegerType::get(context),
120  FIntegerType::get(context),
121  FIntegerType::get(context),
122  FIntegerType::get(context),
123  FIntegerType::get(context),
124  FIntegerType::get(context),
125  FIntegerType::get(context),
126  FIntegerType::get(context),
127  ListType::get(context, cast<PropertyType>(PathType::get(context))),
128  BoolType::get(context),
129  ListType::get(context,
130  cast<PropertyType>(detail::getInstanceTypeForClassLike(
131  extraPortsClass)))};
132 
133  memorySchemaClass =
134  buildSimpleClassOp(builderOM, unknownLoc, "MemorySchema",
135  memoryParamNames, classFieldTypes);
136 
137  // Now create the class that will instantiate metadata class with all the
138  // memories of the circt.
139  SmallVector<PortInfo> mports;
140  memoryMetadataClass = builderOM.create<ClassOp>(
141  builderOM.getStringAttr("MemoryMetadata"), mports);
142  }
143 
144  void createRetimeModulesSchema() {
145  auto *context = circtOp.getContext();
146  auto unknownLoc = mlir::UnknownLoc::get(context);
147  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
148  unknownLoc, circtOp.getBodyBlock());
149  Type classFieldTypes[] = {StringType::get(context)};
150  retimeModulesSchemaClass =
151  buildSimpleClassOp(builderOM, unknownLoc, "RetimeModulesSchema",
152  retimeModulesParamNames, classFieldTypes);
153 
154  SmallVector<PortInfo> mports;
155  retimeModulesMetadataClass = builderOM.create<ClassOp>(
156  builderOM.getStringAttr("RetimeModulesMetadata"), mports);
157  }
158 
159  void addRetimeModule(FModuleLike module) {
160  if (!retimeModulesSchemaClass)
161  createRetimeModulesSchema();
162  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
163  module->getLoc(), retimeModulesMetadataClass.getBodyBlock());
164 
165  // Create the path operation.
166  auto modEntry =
167  builderOM.create<StringConstantOp>(module.getModuleNameAttr());
168  auto object = builderOM.create<ObjectOp>(retimeModulesSchemaClass,
169  module.getModuleNameAttr());
170 
171  auto inPort = builderOM.create<ObjectSubfieldOp>(object, 0);
172  builderOM.create<PropAssignOp>(inPort, modEntry);
173  auto portIndex = retimeModulesMetadataClass.getNumPorts();
174  SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
175  {portIndex,
176  PortInfo(builderOM.getStringAttr(module.getName() + "_field"),
177  object.getType(), Direction::Out)}};
178  retimeModulesMetadataClass.insertPorts(newPorts);
179  auto blockarg = retimeModulesMetadataClass.getBodyBlock()->addArgument(
180  object.getType(), module->getLoc());
181  builderOM.create<PropAssignOp>(blockarg, object);
182  }
183 
184  void addBlackBoxModulesSchema() {
185  auto *context = circtOp.getContext();
186  auto unknownLoc = mlir::UnknownLoc::get(context);
187  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
188  unknownLoc, circtOp.getBodyBlock());
189  Type classFieldTypes[] = {StringType::get(context)};
190  blackBoxModulesSchemaClass =
191  buildSimpleClassOp(builderOM, unknownLoc, "SitestBlackBoxModulesSchema",
192  blackBoxModulesParamNames, classFieldTypes);
193  SmallVector<PortInfo> mports;
194  blackBoxMetadataClass = builderOM.create<ClassOp>(
195  builderOM.getStringAttr("SitestBlackBoxMetadata"), mports);
196  }
197 
198  void addBlackBoxModule(FExtModuleOp module) {
199  if (!blackBoxModulesSchemaClass)
200  addBlackBoxModulesSchema();
201  StringRef defName = *module.getDefname();
202  if (!blackboxModules.insert(defName).second)
203  return;
204  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
205  module.getLoc(), blackBoxMetadataClass.getBodyBlock());
206  auto modEntry = builderOM.create<StringConstantOp>(module.getDefnameAttr());
207  auto object = builderOM.create<ObjectOp>(blackBoxModulesSchemaClass,
208  module.getModuleNameAttr());
209 
210  auto inPort = builderOM.create<ObjectSubfieldOp>(object, 0);
211  builderOM.create<PropAssignOp>(inPort, modEntry);
212  auto portIndex = blackBoxMetadataClass.getNumPorts();
213  SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
214  {portIndex,
215  PortInfo(builderOM.getStringAttr(module.getName() + "_field"),
216  object.getType(), Direction::Out)}};
217  blackBoxMetadataClass.insertPorts(newPorts);
218  auto blockarg = blackBoxMetadataClass.getBodyBlock()->addArgument(
219  object.getType(), module->getLoc());
220  builderOM.create<PropAssignOp>(blockarg, object);
221  }
222 
223  void addMemory(FMemModuleOp mem, bool inDut) {
224  if (!memorySchemaClass)
225  createMemorySchema();
226  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
227  mem.getLoc(), memoryMetadataClass.getBodyBlock());
228  auto *context = builderOM.getContext();
229  auto createConstField = [&](Attribute constVal) -> Value {
230  if (auto boolConstant = dyn_cast_or_null<mlir::BoolAttr>(constVal))
231  return builderOM.create<BoolConstantOp>(boolConstant);
232  if (auto intConstant = dyn_cast_or_null<mlir::IntegerAttr>(constVal))
233  return builderOM.create<FIntegerConstantOp>(intConstant);
234  if (auto strConstant = dyn_cast_or_null<mlir::StringAttr>(constVal))
235  return builderOM.create<StringConstantOp>(strConstant);
236  return {};
237  };
238  auto nlaBuilder = OpBuilder::atBlockBegin(circtOp.getBodyBlock());
239 
240  auto memPaths = instancePathCache.getAbsolutePaths(mem);
241  SmallVector<Value> memoryHierPaths;
242  for (auto memPath : memPaths) {
243  Operation *finalInst = memPath.leaf();
244  SmallVector<Attribute> namepath;
245  bool foundDut = dutMod == nullptr;
246  for (auto inst : memPath) {
247  if (!foundDut)
248  if (inst->getParentOfType<FModuleOp>() == dutMod)
249  foundDut = true;
250  if (!foundDut)
251  continue;
252 
253  namepath.emplace_back(firrtl::getInnerRefTo(
254  inst, [&](auto mod) -> hw::InnerSymbolNamespace & {
255  return getModuleNamespace(mod);
256  }));
257  }
258  if (namepath.empty())
259  continue;
260  auto nla = nlaBuilder.create<hw::HierPathOp>(
261  mem->getLoc(),
262  nlaBuilder.getStringAttr(circtNamespace.newName("memNLA")),
263  nlaBuilder.getArrayAttr(namepath));
264 
265  // Create the path operation.
266  memoryHierPaths.emplace_back(createPathRef(finalInst, nla, builderOM));
267  }
268  auto hierpaths = builderOM.create<ListCreateOp>(
269  ListType::get(context, cast<PropertyType>(PathType::get(context))),
270  memoryHierPaths);
271  SmallVector<Value> memFields;
272 
273  auto object = builderOM.create<ObjectOp>(memorySchemaClass, mem.getName());
274  SmallVector<Value> extraPortsList;
275  ClassType extraPortsType;
276  for (auto attr : mem.getExtraPortsAttr()) {
277 
278  auto port = cast<DictionaryAttr>(attr);
279  auto portName = createConstField(port.getAs<StringAttr>("name"));
280  auto direction = createConstField(port.getAs<StringAttr>("direction"));
281  auto width = createConstField(port.getAs<IntegerAttr>("width"));
282  auto extraPortsObj =
283  builderOM.create<ObjectOp>(extraPortsClass, "extraPorts");
284  extraPortsType = extraPortsObj.getType();
285  auto inPort = builderOM.create<ObjectSubfieldOp>(extraPortsObj, 0);
286  builderOM.create<PropAssignOp>(inPort, portName);
287  inPort = builderOM.create<ObjectSubfieldOp>(extraPortsObj, 2);
288  builderOM.create<PropAssignOp>(inPort, direction);
289  inPort = builderOM.create<ObjectSubfieldOp>(extraPortsObj, 4);
290  builderOM.create<PropAssignOp>(inPort, width);
291  extraPortsList.push_back(extraPortsObj);
292  }
293  auto extraPorts = builderOM.create<ListCreateOp>(
294  memorySchemaClass.getPortType(22), extraPortsList);
295  for (auto field : llvm::enumerate(memoryParamNames)) {
296  auto propVal = createConstField(
297  llvm::StringSwitch<TypedAttr>(field.value())
298  .Case("name", builderOM.getStringAttr(mem.getName()))
299  .Case("depth", mem.getDepthAttr())
300  .Case("width", mem.getDataWidthAttr())
301  .Case("maskBits", mem.getMaskBitsAttr())
302  .Case("readPorts", mem.getNumReadPortsAttr())
303  .Case("writePorts", mem.getNumWritePortsAttr())
304  .Case("readwritePorts", mem.getNumReadWritePortsAttr())
305  .Case("readLatency", mem.getReadLatencyAttr())
306  .Case("writeLatency", mem.getWriteLatencyAttr())
307  .Case("hierarchy", {})
308  .Case("inDut", BoolAttr::get(context, inDut))
309  .Case("extraPorts", {}));
310  if (!propVal) {
311  if (field.value().equals("hierarchy"))
312  propVal = hierpaths;
313  else
314  propVal = extraPorts;
315  }
316 
317  // The memory schema is a simple class, with input tied to output. The
318  // arguments are ordered such that, port index i is the input that is tied
319  // to i+1 which is the output.
320  // The following `2*index` translates the index to the memory schema input
321  // port number.
322  auto inPort =
323  builderOM.create<ObjectSubfieldOp>(object, 2 * field.index());
324  builderOM.create<PropAssignOp>(inPort, propVal);
325  }
326  auto portIndex = memoryMetadataClass.getNumPorts();
327  SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
328  {portIndex, PortInfo(builderOM.getStringAttr(mem.getName() + "_field"),
329  object.getType(), Direction::Out)}};
330  memoryMetadataClass.insertPorts(newPorts);
331  auto blockarg = memoryMetadataClass.getBodyBlock()->addArgument(
332  object.getType(), mem->getLoc());
333  builderOM.create<PropAssignOp>(blockarg, object);
334  }
335 
336  ObjectOp instantiateSifiveMetadata(FModuleOp topMod) {
337  if (!blackBoxMetadataClass && !memoryMetadataClass &&
338  !retimeModulesMetadataClass)
339  return {};
340  auto builder = mlir::ImplicitLocOpBuilder::atBlockEnd(
341  mlir::UnknownLoc::get(circtOp->getContext()), circtOp.getBodyBlock());
342  SmallVector<PortInfo> mports;
343  auto sifiveMetadataClass = builder.create<ClassOp>(
344  builder.getStringAttr("SiFive_Metadata"), mports);
345  builder.setInsertionPointToStart(sifiveMetadataClass.getBodyBlock());
346 
347  auto addPort = [&](Value obj, StringRef fieldName) {
348  auto portIndex = sifiveMetadataClass.getNumPorts();
349  SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
350  {portIndex, PortInfo(builder.getStringAttr(fieldName + "_field_" +
351  Twine(portIndex)),
352  obj.getType(), Direction::Out)}};
353  sifiveMetadataClass.insertPorts(newPorts);
354  auto blockarg = sifiveMetadataClass.getBodyBlock()->addArgument(
355  obj.getType(), topMod->getLoc());
356  builder.create<PropAssignOp>(blockarg, obj);
357  };
358  if (blackBoxMetadataClass)
359  addPort(
360  builder.create<ObjectOp>(blackBoxMetadataClass,
361  builder.getStringAttr("blackbox_metadata")),
362  "blackbox");
363 
364  if (memoryMetadataClass)
365  addPort(
366  builder.create<ObjectOp>(memoryMetadataClass,
367  builder.getStringAttr("memory_metadata")),
368  "memory");
369 
370  if (retimeModulesMetadataClass)
371  addPort(builder.create<ObjectOp>(
372  retimeModulesMetadataClass,
373  builder.getStringAttr("retime_modules_metadata")),
374  "retime");
375 
376  builder.setInsertionPointToEnd(topMod.getBodyBlock());
377  return builder.create<ObjectOp>(sifiveMetadataClass,
378  builder.getStringAttr("sifive_metadata"));
379  }
380 
381  /// Get the cached namespace for a module.
382  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
383  return moduleNamespaces.try_emplace(module, module).first->second;
384  }
385  CircuitOp circtOp;
386  FModuleOp dutMod;
387  CircuitNamespace circtNamespace;
388  InstancePathCache instancePathCache;
389  /// Cached module namespaces.
390  DenseMap<Operation *, hw::InnerSymbolNamespace> &moduleNamespaces;
391  ClassOp memorySchemaClass, extraPortsClass;
392  ClassOp memoryMetadataClass;
393  ClassOp retimeModulesMetadataClass, retimeModulesSchemaClass;
394  ClassOp blackBoxModulesSchemaClass, blackBoxMetadataClass;
395  StringRef memoryParamNames[12] = {
396  "name", "depth", "width", "maskBits",
397  "readPorts", "writePorts", "readwritePorts", "writeLatency",
398  "readLatency", "hierarchy", "inDut", "extraPorts"};
399  StringRef retimeModulesParamNames[1] = {"moduleName"};
400  StringRef blackBoxModulesParamNames[1] = {"moduleName"};
401  llvm::SmallDenseSet<StringRef> blackboxModules;
402 }; // namespace
403 
404 class CreateSiFiveMetadataPass
405  : public CreateSiFiveMetadataBase<CreateSiFiveMetadataPass> {
406  LogicalResult emitRetimeModulesMetadata(ObjectModelIR &omir);
407  LogicalResult emitSitestBlackboxMetadata(ObjectModelIR &omir);
408  LogicalResult emitMemoryMetadata(ObjectModelIR &omir);
409  void runOnOperation() override;
410 
411  /// Get the cached namespace for a module.
412  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
413  return moduleNamespaces.try_emplace(module, module).first->second;
414  }
415  // The set of all modules underneath the design under test module.
416  DenseSet<Operation *> dutModuleSet;
417  /// Cached module namespaces.
418  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
419  // The design under test module.
420  FModuleOp dutMod;
421  CircuitOp circuitOp;
422 
423 public:
424  CreateSiFiveMetadataPass(bool replSeqMem, StringRef replSeqMemFile) {
425  this->replSeqMem = replSeqMem;
426  this->replSeqMemFile = replSeqMemFile.str();
427  }
428 };
429 } // end anonymous namespace
430 
431 /// This function collects all the firrtl.mem ops and creates a verbatim op with
432 /// the relevant memory attributes.
433 LogicalResult
434 CreateSiFiveMetadataPass::emitMemoryMetadata(ObjectModelIR &omir) {
435  if (!replSeqMem)
436  return success();
437 
438  // Everything goes in the DUT if (1) there is no DUT specified or (2) if the
439  // DUT is the top module.
440  bool everythingInDUT =
441  !dutMod ||
442  omir.instancePathCache.instanceGraph.getTopLevelNode()->getModule() ==
443  dutMod;
445  auto addSymbolToVerbatimOp =
446  [&](Operation *op,
447  llvm::SmallVectorImpl<Attribute> &symbols) -> SmallString<8> {
448  Attribute symbol;
449  if (auto module = dyn_cast<FModuleLike>(op))
450  symbol = FlatSymbolRefAttr::get(module);
451  else
452  symbol = firrtl::getInnerRefTo(
453  op, [&](auto mod) -> hw::InnerSymbolNamespace & {
454  return getModuleNamespace(mod);
455  });
456 
457  auto [it, inserted] = symbolIndices.try_emplace(symbol, symbols.size());
458  if (inserted)
459  symbols.push_back(symbol);
460 
461  SmallString<8> str;
462  ("{{" + Twine(it->second) + "}}").toVector(str);
463  return str;
464  };
465  // This lambda, writes to the given Json stream all the relevant memory
466  // attributes. Also adds the memory attrbutes to the string for creating the
467  // memmory conf file.
468  auto createMemMetadata = [&](FMemModuleOp mem,
469  llvm::json::OStream &jsonStream,
470  std::string &seqMemConfStr,
471  SmallVectorImpl<Attribute> &jsonSymbols,
472  SmallVectorImpl<Attribute> &seqMemSymbols) {
473  bool inDut = everythingInDUT || dutModuleSet.contains(mem);
474  omir.addMemory(mem, inDut);
475  // Get the memory data width.
476  auto width = mem.getDataWidth();
477  // Metadata needs to be printed for memories which are candidates for
478  // macro replacement. The requirements for macro replacement::
479  // 1. read latency and write latency of one.
480  // 2. undefined read-under-write behavior.
481  if (mem.getReadLatency() != 1 || mem.getWriteLatency() != 1 || width <= 0)
482  return;
483  auto memExtSym = FlatSymbolRefAttr::get(SymbolTable::getSymbolName(mem));
484  auto symId = seqMemSymbols.size();
485  seqMemSymbols.push_back(memExtSym);
486  // Compute the mask granularity.
487  auto isMasked = mem.isMasked();
488  auto maskGran = width;
489  if (isMasked)
490  maskGran /= mem.getMaskBits();
491  // Now create the config string for the memory.
492  std::string portStr;
493  for (uint32_t i = 0; i < mem.getNumWritePorts(); ++i) {
494  if (!portStr.empty())
495  portStr += ",";
496  portStr += isMasked ? "mwrite" : "write";
497  }
498  for (uint32_t i = 0; i < mem.getNumReadPorts(); ++i) {
499  if (!portStr.empty())
500  portStr += ",";
501  portStr += "read";
502  }
503  for (uint32_t i = 0; i < mem.getNumReadWritePorts(); ++i) {
504  if (!portStr.empty())
505  portStr += ",";
506  portStr += isMasked ? "mrw" : "rw";
507  }
508 
509  auto maskGranStr =
510  !isMasked ? "" : " mask_gran " + std::to_string(maskGran);
511  seqMemConfStr = (StringRef(seqMemConfStr) + "name {{" + Twine(symId) +
512  "}} depth " + Twine(mem.getDepth()) + " width " +
513  Twine(width) + " ports " + portStr + maskGranStr + "\n")
514  .str();
515 
516  // Do not emit any JSON for memories which are not in the DUT.
517  if (!everythingInDUT && !dutModuleSet.contains(mem))
518  return;
519  // This adds a Json array element entry corresponding to this memory.
520  jsonStream.object([&] {
521  jsonStream.attribute("module_name",
522  addSymbolToVerbatimOp(mem, jsonSymbols));
523  jsonStream.attribute("depth", (int64_t)mem.getDepth());
524  jsonStream.attribute("width", (int64_t)width);
525  jsonStream.attribute("masked", isMasked);
526  jsonStream.attribute("read", mem.getNumReadPorts());
527  jsonStream.attribute("write", mem.getNumWritePorts());
528  jsonStream.attribute("readwrite", mem.getNumReadWritePorts());
529  if (isMasked)
530  jsonStream.attribute("mask_granularity", (int64_t)maskGran);
531  jsonStream.attributeArray("extra_ports", [&] {
532  for (auto attr : mem.getExtraPorts()) {
533  jsonStream.object([&] {
534  auto port = cast<DictionaryAttr>(attr);
535  auto name = port.getAs<StringAttr>("name").getValue();
536  jsonStream.attribute("name", name);
537  auto direction = port.getAs<StringAttr>("direction").getValue();
538  jsonStream.attribute("direction", direction);
539  auto width = port.getAs<IntegerAttr>("width").getUInt();
540  jsonStream.attribute("width", width);
541  });
542  }
543  });
544  // Record all the hierarchy names.
545  SmallVector<std::string> hierNames;
546  jsonStream.attributeArray("hierarchy", [&] {
547  // Get the absolute path for the parent memory, to create the
548  // hierarchy names.
549  auto paths = omir.instancePathCache.getAbsolutePaths(mem);
550  for (auto p : paths) {
551  if (p.empty())
552  continue;
553 
554  auto top = p.top();
555  std::string hierName =
556  addSymbolToVerbatimOp(top->getParentOfType<FModuleOp>(),
557  jsonSymbols)
558  .c_str();
559  auto finalInst = p.leaf();
560  for (auto inst : llvm::drop_end(p)) {
561  auto parentModule = inst->getParentOfType<FModuleOp>();
562  if (dutMod == parentModule)
563  hierName =
564  addSymbolToVerbatimOp(parentModule, jsonSymbols).c_str();
565 
566  hierName = hierName + "." +
567  addSymbolToVerbatimOp(inst, jsonSymbols).c_str();
568  }
569  hierName += ("." + finalInst.getInstanceName()).str();
570 
571  hierNames.push_back(hierName);
572  // Only include the memory path if it is under the DUT or we are in a
573  // situation where everything is deemed to be "in the DUT", i.e., when
574  // the DUT is the top module or when no DUT is specified.
575  if (everythingInDUT ||
576  llvm::any_of(p, [&](circt::igraph::InstanceOpInterface inst) {
577  return llvm::all_of(inst.getReferencedModuleNamesAttr(),
578  [&](Attribute attr) {
579  return attr == dutMod.getNameAttr();
580  });
581  }))
582  jsonStream.value(hierName);
583  }
584  });
585  });
586  };
587 
588  std::string dutJsonBuffer;
589  llvm::raw_string_ostream dutOs(dutJsonBuffer);
590  llvm::json::OStream dutJson(dutOs, 2);
591  SmallVector<Attribute, 8> seqMemSymbols;
592  SmallVector<Attribute, 8> jsonSymbols;
593 
594  std::string seqMemConfStr;
595  dutJson.array([&] {
596  for (auto mem : circuitOp.getOps<FMemModuleOp>())
597  createMemMetadata(mem, dutJson, seqMemConfStr, jsonSymbols,
598  seqMemSymbols);
599  });
600 
601  auto *context = &getContext();
602  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
603  circuitOp.getBodyBlock());
604  AnnotationSet annos(circuitOp);
605  auto dirAnno = annos.getAnnotation(metadataDirectoryAttrName);
606  StringRef metadataDir = "metadata";
607  if (dirAnno)
608  if (auto dir = dirAnno.getMember<StringAttr>("dirname"))
609  metadataDir = dir.getValue();
610 
611  // Use unknown loc to avoid printing the location in the metadata files.
612  {
613  SmallString<128> seqMemsJsonPath(metadataDir);
614  llvm::sys::path::append(seqMemsJsonPath, "seq_mems.json");
615  builder.create<emit::FileOp>(seqMemsJsonPath, [&] {
616  builder.create<sv::VerbatimOp>(dutJsonBuffer, ValueRange{},
617  builder.getArrayAttr(jsonSymbols));
618  });
619  }
620 
621  {
622  if (replSeqMemFile.empty()) {
623  emitError(circuitOp->getLoc())
624  << "metadata emission failed, the option "
625  "`-repl-seq-mem-file=<filename>` is mandatory for specifying a "
626  "valid seq mem metadata file";
627  return failure();
628  }
629 
630  builder.create<emit::FileOp>(replSeqMemFile, [&] {
631  builder.create<sv::VerbatimOp>(seqMemConfStr, ValueRange{},
632  builder.getArrayAttr(seqMemSymbols));
633  });
634  }
635 
636  return success();
637 }
638 
639 /// This will search for a target annotation and remove it from the operation.
640 /// If the annotation has a filename, it will be returned in the output
641 /// argument. If the annotation is missing the filename member, or if more than
642 /// one matching annotation is attached, it will print an error and return
643 /// failure.
644 static LogicalResult removeAnnotationWithFilename(Operation *op,
645  StringRef annoClass,
646  StringRef &filename) {
647  filename = "";
648  bool error = false;
649  AnnotationSet::removeAnnotations(op, [&](Annotation anno) {
650  // If there was a previous error or its not a match, continue.
651  if (error || !anno.isClass(annoClass))
652  return false;
653 
654  // If we have already found a matching annotation, error.
655  if (!filename.empty()) {
656  op->emitError("more than one ") << annoClass << " annotation attached";
657  error = true;
658  return false;
659  }
660 
661  // Get the filename from the annotation.
662  auto filenameAttr = anno.getMember<StringAttr>("filename");
663  if (!filenameAttr) {
664  op->emitError(annoClass) << " requires a filename";
665  error = true;
666  return false;
667  }
668 
669  // Require a non-empty filename.
670  filename = filenameAttr.getValue();
671  if (filename.empty()) {
672  op->emitError(annoClass) << " requires a non-empty filename";
673  error = true;
674  return false;
675  }
676 
677  return true;
678  });
679 
680  // If there was a problem above, return failure.
681  return failure(error);
682 }
683 
684 /// This function collects the name of each module annotated and prints them
685 /// all as a JSON array.
686 LogicalResult
687 CreateSiFiveMetadataPass::emitRetimeModulesMetadata(ObjectModelIR &omir) {
688 
689  auto *context = &getContext();
690 
691  // Get the filename, removing the annotation from the circuit.
692  StringRef filename;
694  filename)))
695  return failure();
696 
697  if (filename.empty())
698  return success();
699 
700  // Create a string buffer for the json data.
701  std::string buffer;
702  llvm::raw_string_ostream os(buffer);
703  llvm::json::OStream j(os, 2);
704 
705  // The output is a json array with each element a module name.
706  unsigned index = 0;
707  SmallVector<Attribute> symbols;
708  SmallString<3> placeholder;
709  j.array([&] {
710  for (auto module : circuitOp.getBodyBlock()->getOps<FModuleLike>()) {
711  // The annotation has no supplemental information, just remove it.
712  if (!AnnotationSet::removeAnnotations(module, retimeModuleAnnoClass))
713  continue;
714 
715  // We use symbol substitution to make sure we output the correct thing
716  // when the module goes through renaming.
717  j.value(("{{" + Twine(index++) + "}}").str());
718  symbols.push_back(SymbolRefAttr::get(module.getModuleNameAttr()));
719  omir.addRetimeModule(module);
720  }
721  });
722 
723  // Put the retime information in a verbatim operation.
724  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
725  circuitOp.getBodyBlock());
726  builder.create<emit::FileOp>(filename, [&] {
727  builder.create<sv::VerbatimOp>(builder.getStringAttr(buffer), ValueRange{},
728  builder.getArrayAttr(symbols));
729  });
730  return success();
731 }
732 
733 /// This function finds all external modules which will need to be generated for
734 /// the test harness to run.
735 LogicalResult
736 CreateSiFiveMetadataPass::emitSitestBlackboxMetadata(ObjectModelIR &omir) {
737 
738  // Any extmodule with these annotations should be excluded from the blackbox
739  // list.
740  std::array<StringRef, 6> blackListedAnnos = {
743 
744  auto *context = &getContext();
745 
746  // Get the filenames from the annotations.
747  StringRef dutFilename, testFilename;
749  dutFilename)) ||
751  circuitOp, sitestTestHarnessBlackBoxAnnoClass, testFilename)))
752  return failure();
753 
754  // If we don't have either annotation, no need to run this pass.
755  if (dutFilename.empty() && testFilename.empty())
756  return success();
757 
758  // Find all extmodules in the circuit. Check if they are black-listed from
759  // being included in the list. If they are not, separate them into two
760  // groups depending on if theyre in the DUT or the test harness.
761  SmallVector<StringRef> dutModules;
762  SmallVector<StringRef> testModules;
763  for (auto extModule : circuitOp.getBodyBlock()->getOps<FExtModuleOp>()) {
764  // If the module doesn't have a defname, then we can't record it properly.
765  // Just skip it.
766  if (!extModule.getDefname())
767  continue;
768 
769  // If its a generated blackbox, skip it.
770  AnnotationSet annos(extModule);
771  if (llvm::any_of(blackListedAnnos, [&](auto blackListedAnno) {
772  return annos.hasAnnotation(blackListedAnno);
773  }))
774  continue;
775 
776  // Record the defname of the module.
777  if (!dutMod || dutModuleSet.contains(extModule)) {
778  dutModules.push_back(*extModule.getDefname());
779  } else {
780  testModules.push_back(*extModule.getDefname());
781  }
782  omir.addBlackBoxModule(extModule);
783  }
784 
785  // This is a helper to create the verbatim output operation.
786  auto createOutput = [&](SmallVectorImpl<StringRef> &names,
787  StringRef filename) {
788  if (filename.empty())
789  return;
790 
791  // Sort and remove duplicates.
792  std::sort(names.begin(), names.end());
793  names.erase(std::unique(names.begin(), names.end()), names.end());
794 
795  // The output is a json array with each element a module name. The
796  // defname of a module can't change so we can output them verbatim.
797  std::string buffer;
798  llvm::raw_string_ostream os(buffer);
799  llvm::json::OStream j(os, 2);
800  j.array([&] {
801  for (auto &name : names)
802  j.value(name);
803  });
804 
805  // Put the information in a verbatim operation.
806  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
807  circuitOp.getBodyBlock());
808 
809  builder.create<emit::FileOp>(filename, [&] {
810  builder.create<emit::VerbatimOp>(StringAttr::get(context, buffer));
811  });
812  };
813 
814  createOutput(testModules, testFilename);
815  createOutput(dutModules, dutFilename);
816 
817  return success();
818 }
819 
820 void CreateSiFiveMetadataPass::runOnOperation() {
821 
822  auto moduleOp = getOperation();
823  auto circuits = moduleOp.getOps<CircuitOp>();
824  if (circuits.empty())
825  return;
826  auto cIter = circuits.begin();
827  circuitOp = *cIter++;
828 
829  assert(cIter == circuits.end() &&
830  "cannot handle more than one CircuitOp in a mlir::ModuleOp");
831 
832  auto *body = circuitOp.getBodyBlock();
833  // Find the device under test and create a set of all modules underneath it.
834  auto it = llvm::find_if(*body, [&](Operation &op) -> bool {
836  });
837  auto &instanceGraph = getAnalysis<InstanceGraph>();
838  if (it != body->end()) {
839  dutMod = dyn_cast<FModuleOp>(*it);
840  auto *node = instanceGraph.lookup(cast<igraph::ModuleOpInterface>(*it));
841  llvm::for_each(llvm::depth_first(node),
842  [&](igraph::InstanceGraphNode *node) {
843  dutModuleSet.insert(node->getModule());
844  });
845  }
846  ObjectModelIR omir(circuitOp, dutMod, instanceGraph, moduleNamespaces);
847 
848  if (failed(emitRetimeModulesMetadata(omir)) ||
849  failed(emitSitestBlackboxMetadata(omir)) ||
850  failed(emitMemoryMetadata(omir)))
851  return signalPassFailure();
852  auto *node = instanceGraph.getTopLevelNode();
853  if (FModuleOp topMod = dyn_cast<FModuleOp>(*node->getModule()))
854  if (auto objectOp = omir.instantiateSifiveMetadata(topMod)) {
855  auto portIndex = topMod.getNumPorts();
856  SmallVector<std::pair<unsigned, PortInfo>> ports = {
857  {portIndex,
858  PortInfo(StringAttr::get(objectOp->getContext(), "metadataObj"),
859  objectOp.getType(), Direction::Out)}};
860  topMod.insertPorts(ports);
861  auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
862  topMod->getLoc(), topMod.getBodyBlock());
863  builderOM.create<PropAssignOp>(topMod.getArgument(portIndex), objectOp);
864  }
865 
866  // This pass modifies the hierarchy, InstanceGraph is not preserved.
867 
868  // Clear pass-global state as required by MLIR pass infrastructure.
869  dutMod = {};
870  circuitOp = {};
871  dutModuleSet.empty();
872 }
873 
874 std::unique_ptr<mlir::Pass>
876  StringRef replSeqMemFile) {
877  return std::make_unique<CreateSiFiveMetadataPass>(replSeqMem, replSeqMemFile);
878 }
assert(baseType &&"element must be base type")
static LogicalResult removeAnnotationWithFilename(Operation *op, StringRef annoClass, StringRef &filename)
This will search for a target annotation and remove it from the operation.
int32_t width
Definition: FIRRTL.cpp:36
static std::vector< mlir::Value > toVector(mlir::ValueRange range)
Builder builder
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool hasAnnotation(StringRef className) const
Return true if we have an annotation with the specified class name.
This class provides a read-only projection of an annotation.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
This graph tracks modules and where they are instantiated.
This is a Node in the InstanceGraph.
auto getModule()
Get the module that this node is tracking.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
ClassType getInstanceTypeForClassLike(ClassLike classOp)
Definition: FIRRTLOps.cpp:1776
constexpr const char * blackBoxAnnoClass
constexpr const char * sitestBlackBoxAnnoClass
constexpr const char * metadataDirectoryAttrName
constexpr const char * sitestTestHarnessBlackBoxAnnoClass
constexpr const char * dutAnnoClass
std::unique_ptr< mlir::Pass > createCreateSiFiveMetadataPass(bool replSeqMem=false, mlir::StringRef replSeqMemFile="")
hw::InnerRefAttr getInnerRefTo(const hw::InnerSymTarget &target, GetNamespaceCallback getNamespace)
Obtain an inner reference to the target (operation or port), adding an inner symbol as necessary.
constexpr const char * dataTapsBlackboxClass
constexpr const char * memTapBlackboxClass
constexpr const char * blackBoxPathAnnoClass
constexpr const char * retimeModulesFileAnnoClass
constexpr const char * blackBoxInlineAnnoClass
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
The namespace of a CircuitOp, generally inhabited by modules.
Definition: Namespace.h:24
This holds the name and type that describes the module's ports.
A data structure that caches and provides absolute paths to module instances in the IR.