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