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