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