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