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