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
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 auto type = cast<IntegerType>(intConstant.getType());
244 if (type.isSignlessInteger() || type.isUnsigned()) {
245 auto width = type.getWidth();
246 auto apInt = intConstant.getValue();
247 if (apInt.isNegative()) {
248 ++width;
249 apInt = apInt.zext(width);
250 }
251 type = IntegerType::get(context, width, IntegerType::Signed);
252 intConstant = IntegerAttr::get(type, apInt);
253 }
254 return FIntegerConstantOp::create(builderOM, intConstant);
255 }
256 if (auto strConstant = dyn_cast_or_null<mlir::StringAttr>(constVal))
257 return StringConstantOp::create(builderOM, strConstant);
258 return {};
259 };
260 auto nlaBuilder = OpBuilder::atBlockBegin(circtOp.getBodyBlock());
261
262 auto memPaths = instancePathCache.getAbsolutePaths(mem);
263 SmallVector<Value> memoryHierPaths;
264 SmallVector<Value> finalInstanceNames;
265 // Add metadata for memory paths that are in the design. There are two
266 // slightly different code paths here. If no instance is in the design,
267 // then just skip the memory. If some instances are in the designs and some
268 // are not, then add paths for memories in the design. For memories which
269 // are then _not_ in the design, give them unresolvable distinct attribute
270 // paths. The LowerClasses pass will later treat these as "optimized away"
271 // and create an empty path.
272 //
273 // TODO: This incongruity seems bad. Can we instead not generate metadata
274 // for any path not in the design?
275 bool inDut = instanceInfo.anyInstanceInEffectiveDesign(mem);
276 if (inDut) {
277 for (auto memPath : memPaths) {
278 {
279 igraph::InstanceOpInterface finalInst = memPath.leaf();
280 finalInstanceNames.emplace_back(StringConstantOp::create(
281 builderOM, finalInst.getInstanceNameAttr()));
282 }
283 SmallVector<Attribute> namepath;
284 bool foundDut = false;
285 // The hierpath will be created to the pre-extracted
286 // instance, thus drop the leaf instance of the path, which can be
287 // extracted in subsequent passes.
288 igraph::InstanceOpInterface preExtractedLeafInstance;
289 for (auto inst : llvm::drop_end(memPath)) {
290 if (!foundDut) {
291 if (!instanceInfo.isEffectiveDut(
292 inst->getParentOfType<FModuleOp>()))
293 continue;
294 foundDut = true;
295 }
296
297 // This path is not in the design. Do not record it.
298 if (inst->getParentOfType<LayerBlockOp>()) {
299 namepath.clear();
300 break;
301 }
302
303 namepath.emplace_back(firrtl::getInnerRefTo(
304 inst, [&](auto mod) -> hw::InnerSymbolNamespace & {
305 return getModuleNamespace(mod);
306 }));
307 preExtractedLeafInstance = inst;
308 }
309 PathOp pathRef;
310 if (!namepath.empty()) {
311 // This is a path that is in the design.
312 auto nla = hw::HierPathOp::create(
313 nlaBuilder, mem->getLoc(),
314 nlaBuilder.getStringAttr(circtNamespace.newName("memNLA")),
315 nlaBuilder.getArrayAttr(namepath));
316 nla.setVisibility(SymbolTable::Visibility::Private);
317 pathRef = createPathRef(preExtractedLeafInstance, nla, builderOM);
318 } else {
319 // This is a path _not_ in the design.
320 //
321 // TODO: This unresolvable distinct seems sketchy.
322 pathRef = createPathRef({}, {}, builderOM);
323 }
324
325 // Create the path operation.
326 memoryHierPaths.push_back(pathRef);
327 }
328 }
329 auto finalInstNamesList = ListCreateOp::create(
330 builderOM,
331 ListType::get(context, cast<PropertyType>(StringType::get(context))),
332 finalInstanceNames);
333 auto hierpaths = ListCreateOp::create(
334 builderOM,
335 ListType::get(context, cast<PropertyType>(PathType::get(context))),
336 memoryHierPaths);
337 SmallVector<Value> memFields;
338
339 auto object = ObjectOp::create(builderOM, memorySchemaClass, mem.getName());
340 SmallVector<Value> extraPortsList;
341 ClassType extraPortsType;
342 for (auto attr : mem.getExtraPortsAttr()) {
343
344 auto port = cast<DictionaryAttr>(attr);
345 auto portName = createConstField(port.getAs<StringAttr>("name"));
346 auto direction = createConstField(port.getAs<StringAttr>("direction"));
347 auto width = createConstField(port.getAs<IntegerAttr>("width"));
348 auto extraPortsObj =
349 ObjectOp::create(builderOM, extraPortsClass, "extraPorts");
350 extraPortsType = extraPortsObj.getType();
351 auto inPort = ObjectSubfieldOp::create(builderOM, extraPortsObj, 0);
352 PropAssignOp::create(builderOM, inPort, portName);
353 inPort = ObjectSubfieldOp::create(builderOM, extraPortsObj, 2);
354 PropAssignOp::create(builderOM, inPort, direction);
355 inPort = ObjectSubfieldOp::create(builderOM, extraPortsObj, 4);
356 PropAssignOp::create(builderOM, inPort, width);
357 extraPortsList.push_back(extraPortsObj);
358 }
359 auto extraPorts = ListCreateOp::create(
360 builderOM, memorySchemaClass.getPortType(24), extraPortsList);
361 for (auto field : llvm::enumerate(memoryParamNames)) {
362 auto propVal = createConstField(
363 llvm::StringSwitch<TypedAttr>(field.value())
364 .Case("name", builderOM.getStringAttr(mem.getName()))
365 .Case("depth", mem.getDepthAttr())
366 .Case("width", mem.getDataWidthAttr())
367 .Case("maskBits", mem.getMaskBitsAttr())
368 .Case("readPorts", mem.getNumReadPortsAttr())
369 .Case("writePorts", mem.getNumWritePortsAttr())
370 .Case("readwritePorts", mem.getNumReadWritePortsAttr())
371 .Case("readLatency", mem.getReadLatencyAttr())
372 .Case("writeLatency", mem.getWriteLatencyAttr())
373 .Case("hierarchy", {})
374 .Case("inDut", BoolAttr::get(context, inDut))
375 .Case("extraPorts", {})
376 .Case("preExtInstName", {})
377 .Case("ruwBehavior", builderOM.getStringAttr(
378 stringifyRUWBehavior(mem.getRuw()))));
379 if (!propVal) {
380 if (field.value() == "hierarchy")
381 propVal = hierpaths;
382 else if (field.value() == "preExtInstName")
383 propVal = finalInstNamesList;
384 else
385 propVal = extraPorts;
386 }
387
388 // The memory schema is a simple class, with input tied to output. The
389 // arguments are ordered such that, port index i is the input that is tied
390 // to i+1 which is the output.
391 // The following `2*index` translates the index to the memory schema input
392 // port number.
393 auto inPort =
394 ObjectSubfieldOp::create(builderOM, object, 2 * field.index());
395 PropAssignOp::create(builderOM, inPort, propVal);
396 }
397 auto portIndex = memoryMetadataClass.getNumPorts();
398 SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
399 {portIndex, PortInfo(builderOM.getStringAttr(mem.getName() + "_field"),
400 object.getType(), Direction::Out)}};
401 memoryMetadataClass.insertPorts(newPorts);
402 auto blockarg = memoryMetadataClass.getBodyBlock()->addArgument(
403 object.getType(), mem->getLoc());
404 PropAssignOp::create(builderOM, blockarg, object);
405 }
406
407 ObjectOp instantiateSifiveMetadata(FModuleOp topMod) {
408 if (!blackBoxMetadataClass && !memoryMetadataClass &&
409 !retimeModulesMetadataClass && !instanceInfo.hasDut())
410 return {};
411 auto builder = mlir::ImplicitLocOpBuilder::atBlockEnd(
412 mlir::UnknownLoc::get(circtOp->getContext()), circtOp.getBodyBlock());
413 SmallVector<PortInfo> mports;
414 auto sifiveMetadataClass = ClassOp::create(
415 builder, builder.getStringAttr("SiFive_Metadata"), mports);
416 builder.setInsertionPointToStart(sifiveMetadataClass.getBodyBlock());
417
418 auto addPort = [&](Value obj, StringRef fieldName) {
419 auto portIndex = sifiveMetadataClass.getNumPorts();
420 SmallVector<std::pair<unsigned, PortInfo>> newPorts = {
421 {portIndex, PortInfo(builder.getStringAttr(fieldName + "_field_" +
422 Twine(portIndex)),
423 obj.getType(), Direction::Out)}};
424 sifiveMetadataClass.insertPorts(newPorts);
425 auto blockarg = sifiveMetadataClass.getBodyBlock()->addArgument(
426 obj.getType(), topMod->getLoc());
427 PropAssignOp::create(builder, blockarg, obj);
428 };
429 if (blackBoxMetadataClass)
430 addPort(ObjectOp::create(builder, blackBoxMetadataClass,
431 builder.getStringAttr("blackbox_metadata")),
432 "blackbox");
433
434 if (memoryMetadataClass)
435 addPort(ObjectOp::create(builder, memoryMetadataClass,
436 builder.getStringAttr("memory_metadata")),
437 "memory");
438
439 if (retimeModulesMetadataClass)
440 addPort(
441 ObjectOp::create(builder, retimeModulesMetadataClass,
442 builder.getStringAttr("retime_modules_metadata")),
443 "retime");
444
445 if (instanceInfo.hasDut()) {
446 auto dutMod = instanceInfo.getDut();
447
448 // This can handle multiple DUTs or multiple paths to a DUT.
449 // Create a list of paths to the DUTs.
450 SmallVector<Value, 2> pathOpsToDut;
451
452 auto dutPaths = instancePathCache.getAbsolutePaths(dutMod);
453 // For each path to the DUT.
454 for (auto dutPath : dutPaths) {
455 SmallVector<Attribute> namepath;
456 // Construct the list of inner refs to the instances in the path.
457 for (auto inst : dutPath)
458 namepath.emplace_back(firrtl::getInnerRefTo(
459 inst, [&](auto mod) -> hw::InnerSymbolNamespace & {
460 return getModuleNamespace(mod);
461 }));
462 if (namepath.empty())
463 continue;
464 // The path op will refer to the leaf instance in the path (and not the
465 // actual DUT module!!).
466 auto leafInst = dutPath.leaf();
467 auto nlaBuilder = OpBuilder::atBlockBegin(circtOp.getBodyBlock());
468 auto nla = hw::HierPathOp::create(
469 nlaBuilder, dutMod->getLoc(),
470 nlaBuilder.getStringAttr(circtNamespace.newName("dutNLA")),
471 nlaBuilder.getArrayAttr(namepath));
472 nla.setVisibility(SymbolTable::Visibility::Private);
473 // Create the path ref op and record it.
474 pathOpsToDut.emplace_back(createPathRef(leafInst, nla, builder));
475 }
476 // Create the list of paths op and add it as a field of the class.
477 auto pathList = ListCreateOp::create(
478 builder,
479 ListType::get(context, cast<PropertyType>(PathType::get(context))),
480 pathOpsToDut);
481 addPort(pathList, "dutModulePath");
482 }
483
484 builder.setInsertionPointToEnd(topMod.getBodyBlock());
485 return ObjectOp::create(builder, sifiveMetadataClass,
486 builder.getStringAttr("sifive_metadata"));
487 }
488
489 /// Get the cached namespace for a module.
490 hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
491 return moduleNamespaces.try_emplace(module, module).first->second;
492 }
493 MLIRContext *context;
494 CircuitOp circtOp;
495 CircuitNamespace circtNamespace;
496 InstancePathCache instancePathCache;
497 InstanceInfo &instanceInfo;
498 /// Cached module namespaces.
499 DenseMap<Operation *, hw::InnerSymbolNamespace> &moduleNamespaces;
500 ClassOp memorySchemaClass, extraPortsClass;
501 ClassOp memoryMetadataClass;
502 ClassOp retimeModulesMetadataClass, retimeModulesSchemaClass;
503 ClassOp blackBoxModulesSchemaClass, blackBoxMetadataClass;
504 StringRef memoryParamNames[14] = {
505 "name", "depth", "width", "maskBits",
506 "readPorts", "writePorts", "readwritePorts", "writeLatency",
507 "readLatency", "ruwBehavior", "hierarchy", "inDut",
508 "extraPorts", "preExtInstName"};
509 StringRef retimeModulesParamNames[1] = {"moduleName"};
510 StringRef blackBoxModulesParamNames[3] = {"moduleName", "inDut", "libraries"};
511 llvm::SmallDenseSet<StringRef> blackboxModules;
512};
513
514class CreateSiFiveMetadataPass
515 : public circt::firrtl::impl::CreateSiFiveMetadataBase<
516 CreateSiFiveMetadataPass> {
517 using Base::Base;
518
519 LogicalResult emitRetimeModulesMetadata(ObjectModelIR &omir);
520 LogicalResult emitSitestBlackboxMetadata(ObjectModelIR &omir);
521 LogicalResult emitMemoryMetadata(ObjectModelIR &omir);
522 void runOnOperation() override;
523
524 /// Get the cached namespace for a module.
525 hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
526 return moduleNamespaces.try_emplace(module, module).first->second;
527 }
528 /// Cached module namespaces.
529 DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
530 CircuitOp circuitOp;
531 // Precomputed instanceinfo analysis
532 InstanceInfo *instanceInfo;
533
534public:
535 CreateSiFiveMetadataPass(bool replSeqMem, StringRef replSeqMemFile) {
536 this->replSeqMem = replSeqMem;
537 this->replSeqMemFile = replSeqMemFile.str();
538 }
539};
540} // namespace
541
542/// This function collects all the firrtl.mem ops and creates a verbatim op with
543/// the relevant memory attributes.
544LogicalResult
545CreateSiFiveMetadataPass::emitMemoryMetadata(ObjectModelIR &omir) {
546 if (!replSeqMem)
547 return success();
548
550 auto addSymbolToVerbatimOp =
551 [&](Operation *op,
552 llvm::SmallVectorImpl<Attribute> &symbols) -> SmallString<8> {
553 Attribute symbol;
554 if (auto module = dyn_cast<FModuleLike>(op))
555 symbol = FlatSymbolRefAttr::get(module);
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 // Get the absolute path for the parent memory, to create the
655 // hierarchy names.
656 auto paths = omir.instancePathCache.getAbsolutePaths(mem);
657 for (auto p : paths) {
658 if (p.empty())
659 continue;
660
661 // Only include the memory paths that are in the design. This means
662 // that the path has to both include the design and not be under a
663 // layer.
664 auto dutMod = instanceInfo->getEffectiveDut();
665 bool inDut = false, underLayer = false;
666 for (auto inst : p) {
667 auto parent = inst->getParentOfType<FModuleOp>();
668 inDut |= parent == dutMod;
669 if (inst->getParentOfType<LayerBlockOp>())
670 underLayer = true;
671 }
672 if (!inDut || underLayer)
673 continue;
674
675 auto top = p.top();
676 std::string hierName =
677 addSymbolToVerbatimOp(top->getParentOfType<FModuleOp>(),
678 jsonSymbols)
679 .c_str();
680 auto finalInst = p.leaf();
681 for (auto inst : llvm::drop_end(p)) {
682 auto parentModule = inst->getParentOfType<FModuleOp>();
683 if (instanceInfo->getDut() == parentModule)
684 hierName =
685 addSymbolToVerbatimOp(parentModule, jsonSymbols).c_str();
686
687 hierName = hierName + "." +
688 addSymbolToVerbatimOp(inst, jsonSymbols).c_str();
689 }
690 hierName += ("." + finalInst.getInstanceName()).str();
691
692 jsonStream.value(hierName);
693 }
694 });
695 });
696 };
697
698 std::string dutJsonBuffer;
699 llvm::raw_string_ostream dutOs(dutJsonBuffer);
700 llvm::json::OStream dutJson(dutOs, 2);
701 SmallVector<Attribute, 8> seqMemSymbols;
702 SmallVector<Attribute, 8> jsonSymbols;
703
704 std::string seqMemConfStr;
705 dutJson.array([&] {
706 for (auto mem : circuitOp.getOps<FMemModuleOp>())
707 createMemMetadata(mem, dutJson, seqMemConfStr, jsonSymbols,
708 seqMemSymbols);
709 });
710
711 auto *context = &getContext();
712 auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
713 circuitOp.getBodyBlock());
714 AnnotationSet annos(circuitOp);
715 auto dirAnno = annos.getAnnotation(metadataDirAnnoClass);
716 StringRef metadataDir = "metadata";
717 if (dirAnno)
718 if (auto dir = dirAnno.getMember<StringAttr>("dirname"))
719 metadataDir = dir.getValue();
720
721 // Use unknown loc to avoid printing the location in the metadata files.
722 {
723 SmallString<128> seqMemsJsonPath(metadataDir);
724 llvm::sys::path::append(seqMemsJsonPath, "seq_mems.json");
725 emit::FileOp::create(builder, seqMemsJsonPath, [&] {
726 sv::VerbatimOp::create(builder, dutJsonBuffer, ValueRange{},
727 builder.getArrayAttr(jsonSymbols));
728 });
729 }
730
731 {
732 if (replSeqMemFile.empty()) {
733 emitError(circuitOp->getLoc())
734 << "metadata emission failed, the option "
735 "`-repl-seq-mem-file=<filename>` is mandatory for specifying a "
736 "valid seq mem metadata file";
737 return failure();
738 }
739
740 emit::FileOp::create(builder, replSeqMemFile, [&] {
741 sv::VerbatimOp::create(builder, seqMemConfStr, ValueRange{},
742 builder.getArrayAttr(seqMemSymbols));
743 });
744 }
745
746 return success();
747}
748
749/// This will search for a target annotation and remove it from the operation.
750/// If the annotation has a filename, it will be returned in the output
751/// argument. If the annotation is missing the filename member, or if more than
752/// one matching annotation is attached, it will print an error and return
753/// failure.
754static LogicalResult removeAnnotationWithFilename(Operation *op,
755 StringRef annoClass,
756 StringRef &filename) {
757 filename = "";
758 bool error = false;
760 // If there was a previous error or its not a match, continue.
761 if (error || !anno.isClass(annoClass))
762 return false;
763
764 // If we have already found a matching annotation, error.
765 if (!filename.empty()) {
766 op->emitError("more than one ") << annoClass << " annotation attached";
767 error = true;
768 return false;
769 }
770
771 // Get the filename from the annotation.
772 auto filenameAttr = anno.getMember<StringAttr>("filename");
773 if (!filenameAttr) {
774 op->emitError(annoClass) << " requires a filename";
775 error = true;
776 return false;
777 }
778
779 // Require a non-empty filename.
780 filename = filenameAttr.getValue();
781 if (filename.empty()) {
782 op->emitError(annoClass) << " requires a non-empty filename";
783 error = true;
784 return false;
785 }
786
787 return true;
788 });
789
790 // If there was a problem above, return failure.
791 return failure(error);
792}
793
794/// This function collects the name of each module annotated and prints them
795/// all as a JSON array.
796LogicalResult
797CreateSiFiveMetadataPass::emitRetimeModulesMetadata(ObjectModelIR &omir) {
798
799 auto *context = &getContext();
800
801 // Get the filename, removing the annotation from the circuit.
802 StringRef filename;
803 if (failed(removeAnnotationWithFilename(circuitOp, retimeModulesAnnoClass,
804 filename)))
805 return failure();
806
807 if (filename.empty())
808 return success();
809
810 // Create a string buffer for the json data.
811 std::string buffer;
812 llvm::raw_string_ostream os(buffer);
813 llvm::json::OStream j(os, 2);
814
815 // The output is a json array with each element a module name.
816 unsigned index = 0;
817 SmallVector<Attribute> symbols;
818 SmallString<3> placeholder;
819 j.array([&] {
820 for (auto module : circuitOp.getBodyBlock()->getOps<FModuleLike>()) {
821 // The annotation has no supplemental information, just remove it.
822 if (!AnnotationSet::removeAnnotations(module, retimeModuleAnnoClass) ||
823 !instanceInfo->anyInstanceInEffectiveDesign(module))
824 continue;
825
826 // We use symbol substitution to make sure we output the correct thing
827 // when the module goes through renaming.
828 j.value(("{{" + Twine(index++) + "}}").str());
829 symbols.push_back(SymbolRefAttr::get(module.getModuleNameAttr()));
830 omir.addRetimeModule(module);
831 }
832 });
833
834 // Put the retime information in a verbatim operation.
835 auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
836 circuitOp.getBodyBlock());
837 emit::FileOp::create(builder, filename, [&] {
838 sv::VerbatimOp::create(builder, builder.getStringAttr(buffer), ValueRange{},
839 builder.getArrayAttr(symbols));
840 });
841 return success();
842}
843
844/// This function finds all external modules which will need to be generated for
845/// the test harness to run.
846LogicalResult
847CreateSiFiveMetadataPass::emitSitestBlackboxMetadata(ObjectModelIR &omir) {
848
849 // Any extmodule with these annotations should be excluded from the blackbox
850 // list if it doesn't declare any additional libraries.
851 std::array<StringRef, 3> blackListedAnnos = {
852 blackBoxAnnoClass,
853 blackBoxInlineAnnoClass,
854 blackBoxPathAnnoClass,
855 };
856
857 auto *context = &getContext();
858
859 // Get the filenames from the annotations.
860 StringRef dutFilename, testFilename;
861 if (failed(removeAnnotationWithFilename(circuitOp, sitestBlackBoxAnnoClass,
862 dutFilename)) ||
864 circuitOp, sitestTestHarnessBlackBoxAnnoClass, testFilename)))
865 return failure();
866
867 // If we don't have either annotation, no need to run this pass.
868 if (dutFilename.empty() && testFilename.empty())
869 return success();
870
871 // Find all extmodules in the circuit. Check if they are black-listed from
872 // being included in the list. If they are not, separate them into two
873 // groups depending on if theyre in the DUT or the test harness.
874 SmallVector<StringRef> dutLibs;
875 SmallVector<StringRef> testLibs;
876
877 for (auto extModule : circuitOp.getBodyBlock()->getOps<FExtModuleOp>()) {
878 SmallVector<StringRef> libs;
879
880 // If the module doesn't have a defname, then we can't record it properly.
881 // Just skip it.
882 if (!extModule.getDefname())
883 continue;
884
885 AnnotationSet annos(extModule);
886
887 bool isBlacklistedBlackbox =
888 llvm::any_of(blackListedAnnos, [&](auto blackListedAnno) {
889 return annos.hasAnnotation(blackListedAnno);
890 });
891
892 if (!isBlacklistedBlackbox)
893 libs.push_back(*extModule.getDefname());
894
895 if (auto libsAnno = annos.getAnnotation(sitestBlackBoxLibrariesAnnoClass)) {
896 if (auto libsAttr = libsAnno.getMember<ArrayAttr>("libraries")) {
897 for (auto lib : libsAttr) {
898 if (auto libStr = dyn_cast<StringAttr>(lib)) {
899 libs.push_back(libStr.getValue());
900 }
901 }
902 }
903 }
904
905 if (libs.empty())
906 continue;
907
908 bool inDut = false;
909 if (instanceInfo->anyInstanceInEffectiveDesign(extModule)) {
910 inDut = true;
911 for (StringRef lib : libs)
912 dutLibs.push_back(lib);
913 } else
914 for (StringRef lib : libs)
915 testLibs.push_back(lib);
916
917 omir.addBlackBoxModule(extModule, inDut, libs);
918 }
919
920 // This is a helper to create the verbatim output operation.
921 auto createOutput = [&](SmallVectorImpl<StringRef> &names,
922 StringRef filename) {
923 if (filename.empty())
924 return;
925
926 // Sort and remove duplicates.
927 std::sort(names.begin(), names.end());
928 names.erase(std::unique(names.begin(), names.end()), names.end());
929
930 // The output is a json array with each element a module name. The
931 // defname of a module can't change so we can output them verbatim.
932 std::string buffer;
933 llvm::raw_string_ostream os(buffer);
934 llvm::json::OStream j(os, 2);
935 j.array([&] {
936 for (auto &name : names)
937 j.value(name);
938 });
939
940 // Put the information in a verbatim operation.
941 auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
942 circuitOp.getBodyBlock());
943
944 emit::FileOp::create(builder, filename, [&] {
945 emit::VerbatimOp::create(builder, StringAttr::get(context, buffer));
946 });
947 };
948
949 createOutput(testLibs, testFilename);
950 createOutput(dutLibs, dutFilename);
951
952 return success();
953}
954
955void CreateSiFiveMetadataPass::runOnOperation() {
956 auto circuits = getOperation().getOps<CircuitOp>();
957 if (circuits.empty())
958 return;
959
960 circuitOp = *circuits.begin();
961
962 if (!llvm::hasSingleElement(circuits)) {
963 mlir::emitError(circuitOp.getLoc(),
964 "cannot process multiple circuit operations")
965 .attachNote((*std::next(circuits.begin())).getLoc())
966 << "second circuit here";
967 return signalPassFailure();
968 }
969
970 auto &instanceGraph = getAnalysis<InstanceGraph>();
971 instanceInfo = &getAnalysis<InstanceInfo>();
972 ObjectModelIR omir(circuitOp, instanceGraph, *instanceInfo, moduleNamespaces);
973
974 if (failed(emitRetimeModulesMetadata(omir)) ||
975 failed(emitSitestBlackboxMetadata(omir)) ||
976 failed(emitMemoryMetadata(omir)))
977 return signalPassFailure();
978 auto *node = instanceGraph.getTopLevelNode();
979 if (FModuleOp topMod = dyn_cast<FModuleOp>(*node->getModule()))
980 if (auto objectOp = omir.instantiateSifiveMetadata(topMod)) {
981 auto portIndex = topMod.getNumPorts();
982 SmallVector<std::pair<unsigned, PortInfo>> ports = {
983 {portIndex,
984 PortInfo(StringAttr::get(objectOp->getContext(), "metadataObj"),
985 AnyRefType::get(objectOp->getContext()), Direction::Out)}};
986 topMod.insertPorts(ports);
987 auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd(
988 topMod->getLoc(), topMod.getBodyBlock());
989 auto objectCast = ObjectAnyRefCastOp::create(builderOM, objectOp);
990 PropAssignOp::create(builderOM, topMod.getArgument(portIndex),
991 objectCast);
992 }
993
994 // This pass modifies the hierarchy, InstanceGraph is not preserved.
995
996 // Clear pass-global state as required by MLIR pass infrastructure.
997 circuitOp = {};
998 instanceInfo = {};
999}
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.
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.