10 #include "mlir/IR/Threading.h"
11 #include "llvm/ADT/MapVector.h"
12 #include "llvm/Support/Debug.h"
14 using namespace circt;
17 using llvm::MapVector;
19 #define DEBUG_TYPE "lower-seq-firmem"
22 : context(circuit.getContext()), circuit(circuit) {
38 using ModuleMemories = SmallVector<std::pair<FirMemConfig, FirMemOp>, 0>;
39 SmallVector<ModuleMemories> memories(modules.size());
41 mlir::parallelFor(
context, 0, modules.size(), [&](
auto idx) {
44 HWModuleOp(modules[idx]).walk([&](seq::FirMemOp op) {
45 memories[idx].push_back({collectMemory(op), op});
50 MapVector<FirMemConfig, SmallVector<FirMemOp, 1>> grouped;
51 for (
auto [module, moduleMemories] : llvm::zip(modules, memories))
52 for (
auto [summary, memOp] : moduleMemories)
53 grouped[summary].push_back(memOp);
61 if (
auto wireOp = value.getDefiningOp<WireOp>()) {
62 value = wireOp.getInput();
75 cfg.
depth = op.getType().getDepth();
78 cfg.
maskBits = op.getType().getMaskWidth().value_or(1);
81 if (
auto init = op.getInitAttr()) {
87 if (
auto prefix = op.getPrefixAttr())
88 cfg.
prefix = prefix.getValue();
95 for (
auto *user : op->getUsers()) {
96 if (isa<FirMemReadOp>(user))
98 else if (isa<FirMemWriteOp>(user))
100 else if (isa<FirMemReadWriteOp>(user))
105 if (isa<FirMemWriteOp, FirMemReadWriteOp>(user)) {
108 clockValues.insert({clock, clockValues.size()}).first->second);
118 for (
auto op :
circuit.getOps<hw::HWGeneratorSchemaOp>()) {
119 if (op.getDescriptor() ==
"FIRRTL_Memory") {
125 auto builder = OpBuilder::atBlockBegin(
circuit.getBody());
126 std::array<StringRef, 14> schemaFields = {
127 "depth",
"numReadPorts",
128 "numWritePorts",
"numReadWritePorts",
129 "readLatency",
"writeLatency",
131 "readUnderWrite",
"writeUnderWrite",
132 "writeClockIDs",
"initFilename",
133 "initIsBinary",
"initIsInline"};
134 schemaOp = builder.create<hw::HWGeneratorSchemaOp>(
135 circuit.getLoc(),
"FIRRTLMem",
"FIRRTL_Memory",
136 builder.getStrArrayAttr(schemaFields));
145 ArrayRef<seq::FirMemOp> memOps) {
151 for (
auto memOp : memOps) {
152 auto parent = memOp->getParentOfType<
HWModuleOp>();
158 builder.setInsertionPoint(insertPt);
166 StringRef baseName =
"";
167 bool firstFound =
false;
168 for (
auto memOp : memOps) {
169 if (
auto memName = memOp.getName()) {
176 for (; idx < memName->size() && idx < baseName.size(); ++idx)
177 if ((*memName)[idx] != baseName[idx])
179 baseName = baseName.take_front(idx);
182 baseName = baseName.rtrim(
'_');
184 SmallString<32> nameBuffer;
186 if (!baseName.empty()) {
187 nameBuffer += baseName;
195 LLVM_DEBUG(llvm::dbgs() <<
"Creating " << name <<
" for " << mem.
depth
196 <<
" x " << mem.
dataWidth <<
" memory\n");
199 SmallVector<hw::PortInfo> ports;
211 auto addInput = [&](StringRef prefix,
size_t idx, StringRef suffix,
213 ports.push_back({{builder.getStringAttr(prefix + Twine(idx) + suffix), type,
219 size_t outputIdx = 0;
220 auto addOutput = [&](StringRef prefix,
size_t idx, StringRef suffix,
222 ports.push_back({{builder.getStringAttr(prefix + Twine(idx) + suffix), type,
228 auto addCommonPorts = [&](StringRef prefix,
size_t idx) {
229 addInput(prefix, idx,
"_addr", addrType);
230 addInput(prefix, idx,
"_en", bitType);
231 addInput(prefix, idx,
"_clk", clkType);
236 addCommonPorts(
"R", i);
237 addOutput(
"R", i,
"_data", dataType);
242 addCommonPorts(
"RW", i);
243 addInput(
"RW", i,
"_wmode", bitType);
244 addInput(
"RW", i,
"_wdata", dataType);
245 addOutput(
"RW", i,
"_rdata", dataType);
247 addInput(
"RW", i,
"_wmask", maskType);
252 addCommonPorts(
"W", i);
253 addInput(
"W", i,
"_data", dataType);
255 addInput(
"W", i,
"_mask", maskType);
260 auto genAttr = [&](StringRef name, Attribute attr) {
261 return builder.getNamedAttr(name, attr);
263 auto genAttrUI32 = [&](StringRef name, uint32_t value) {
264 return genAttr(name, builder.getUI32IntegerAttr(value));
266 NamedAttribute genAttrs[] = {
267 genAttr(
"depth", builder.getI64IntegerAttr(mem.
depth)),
275 genAttr(
"readUnderWrite",
277 genAttr(
"writeUnderWrite",
279 genAttr(
"writeClockIDs", builder.getI32ArrayAttr(mem.
writeClockIDs)),
280 genAttr(
"initFilename", builder.getStringAttr(mem.
initFilename)),
281 genAttr(
"initIsBinary", builder.getBoolAttr(mem.
initIsBinary)),
282 genAttr(
"initIsInline", builder.getBoolAttr(mem.
initIsInline))};
286 Location loc = FirMemOp(memOps.front()).getLoc();
287 if (memOps.size() > 1) {
288 SmallVector<Location> locs;
289 for (
auto memOp : memOps)
290 locs.push_back(memOp.getLoc());
295 auto genOp = builder.create<hw::HWModuleGeneratedOp>(
296 loc, schemaSymRef, name, ports, StringRef{}, ArrayAttr{}, genAttrs);
298 genOp->setAttr(
"output_file", mem.
outputFile);
307 ArrayRef<std::tuple<FirMemConfig *, HWModuleGeneratedOp, FirMemOp>> mems) {
308 LLVM_DEBUG(llvm::dbgs() <<
"Lowering " << mems.size() <<
" memories in "
309 << module.getName() <<
"\n");
311 DenseMap<unsigned, Value> constOneOps;
312 auto constOne = [&](
unsigned width = 1) {
313 auto it = constOneOps.try_emplace(
width, Value{});
315 auto builder = OpBuilder::atBlockBegin(module.getBodyBlock());
317 module.getLoc(), builder.getIntegerType(
width), 1);
319 return it.first->second;
321 auto valueOrOne = [&](Value value,
unsigned width = 1) {
322 return value ? value : constOne(
width);
325 for (
auto [config, genOp, memOp] : mems) {
326 LLVM_DEBUG(llvm::dbgs() <<
"- Lowering " << memOp.getName() <<
"\n");
327 SmallVector<Value> inputs;
328 SmallVector<Value> outputs;
330 auto addInput = [&](Value value) { inputs.push_back(value); };
331 auto addOutput = [&](Value value) { outputs.push_back(value); };
334 for (
auto *op : memOp->getUsers()) {
335 auto port = dyn_cast<FirMemReadOp>(op);
338 addInput(port.getAddress());
339 addInput(valueOrOne(port.getEnable()));
340 addInput(port.getClk());
341 addOutput(port.getData());
345 for (
auto *op : memOp->getUsers()) {
346 auto port = dyn_cast<FirMemReadWriteOp>(op);
349 addInput(port.getAddress());
350 addInput(valueOrOne(port.getEnable()));
351 addInput(port.getClk());
352 addInput(port.getMode());
353 addInput(port.getWriteData());
354 addOutput(port.getReadData());
355 if (config->maskBits > 1)
356 addInput(valueOrOne(port.getMask(), config->maskBits));
360 for (
auto *op : memOp->getUsers()) {
361 auto port = dyn_cast<FirMemWriteOp>(op);
364 addInput(port.getAddress());
365 addInput(valueOrOne(port.getEnable()));
366 addInput(port.getClk());
367 addInput(port.getData());
368 if (config->maskBits > 1)
369 addInput(valueOrOne(port.getMask(), config->maskBits));
373 StringRef memName =
"mem";
374 if (
auto name = memOp.getName(); name && !name->empty())
376 ImplicitLocOpBuilder builder(memOp.getLoc(), memOp);
377 auto instOp = builder.create<hw::InstanceOp>(
378 genOp, builder.getStringAttr(memName +
"_ext"), inputs, ArrayAttr{},
379 memOp.getInnerSymAttr());
380 for (
auto [oldOutput, newOutput] : llvm::zip(outputs, instOp.getResults()))
381 oldOutput.replaceAllUsesWith(newOutput);
384 auto defaultAttrNames = memOp.getAttributeNames();
385 for (
auto namedAttr : memOp->getAttrs())
386 if (!llvm::is_contained(defaultAttrNames, namedAttr.getName()))
387 instOp->setAttr(namedAttr.getName(), namedAttr.getValue());
390 for (
auto *user : llvm::make_early_inc_range(memOp->getUsers()))
static Value lookThroughWires(Value value)
Trace a value through wires to its original definition.
static std::vector< mlir::Value > toVector(mlir::ValueRange range)
UniqueConfigs collectMemories(ArrayRef< hw::HWModuleOp > modules)
Groups memories by their kind from the whole design.
void lowerMemoriesInModule(hw::HWModuleOp module, ArrayRef< MemoryConfig > mems)
Lowers a group of memories from the same module.
hw::HWGeneratorSchemaOp schemaOp
FirMemLowering(ModuleOp circuit)
hw::HWModuleGeneratedOp createMemoryModule(FirMemConfig &mem, ArrayRef< seq::FirMemOp > memOps)
Creates the generated module for a given configuration.
FlatSymbolRefAttr getOrCreateSchema()
Find the schema or create it if it does not exist.
DenseMap< hw::HWModuleOp, size_t > moduleIndex
llvm::MapVector< FirMemConfig, SmallVector< seq::FirMemOp, 1 > > UniqueConfigs
A vector of unique FirMemConfigs and all the FirMemOps that use it.
Namespace globalNamespace
void add(mlir::ModuleOp module)
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
The configuration of a FIR memory.
SmallVector< int32_t, 1 > writeClockIDs