10 #include "mlir/IR/Threading.h"
11 #include "llvm/Support/Debug.h"
13 using namespace circt;
16 using llvm::MapVector;
18 #define DEBUG_TYPE "lower-seq-firmem"
21 : context(circuit.getContext()), circuit(circuit) {
37 using ModuleMemories = SmallVector<std::pair<FirMemConfig, FirMemOp>, 0>;
38 SmallVector<ModuleMemories> memories(modules.size());
40 mlir::parallelFor(
context, 0, modules.size(), [&](
auto idx) {
43 HWModuleOp(modules[idx]).walk([&](seq::FirMemOp op) {
44 memories[idx].push_back({collectMemory(op), op});
49 MapVector<FirMemConfig, SmallVector<FirMemOp, 1>> grouped;
50 for (
auto [module, moduleMemories] : llvm::zip(modules, memories))
51 for (
auto [summary, memOp] : moduleMemories)
52 grouped[summary].push_back(memOp);
60 if (
auto wireOp =
value.getDefiningOp<WireOp>()) {
61 value = wireOp.getInput();
74 cfg.
depth = op.getType().getDepth();
77 cfg.
maskBits = op.getType().getMaskWidth().value_or(1);
80 if (
auto init = op.getInitAttr()) {
86 if (
auto prefix = op.getPrefixAttr())
87 cfg.
prefix = prefix.getValue();
94 for (
auto *user : op->getUsers()) {
95 if (isa<FirMemReadOp>(user))
97 else if (isa<FirMemWriteOp>(user))
99 else if (isa<FirMemReadWriteOp>(user))
104 if (isa<FirMemWriteOp, FirMemReadWriteOp>(user)) {
107 clockValues.insert({clock, clockValues.size()}).first->second);
117 for (
auto op :
circuit.getOps<hw::HWGeneratorSchemaOp>()) {
118 if (op.getDescriptor() ==
"FIRRTL_Memory") {
125 std::array<StringRef, 14> schemaFields = {
126 "depth",
"numReadPorts",
127 "numWritePorts",
"numReadWritePorts",
128 "readLatency",
"writeLatency",
130 "readUnderWrite",
"writeUnderWrite",
131 "writeClockIDs",
"initFilename",
132 "initIsBinary",
"initIsInline"};
134 circuit.getLoc(),
"FIRRTLMem",
"FIRRTL_Memory",
135 builder.getStrArrayAttr(schemaFields));
144 ArrayRef<seq::FirMemOp> memOps) {
150 for (
auto memOp : memOps) {
151 auto parent = memOp->getParentOfType<
HWModuleOp>();
157 builder.setInsertionPoint(insertPt);
165 StringRef baseName =
"";
166 bool firstFound =
false;
167 for (
auto memOp : memOps) {
168 if (
auto memName = memOp.getName()) {
175 for (; idx < memName->size() && idx < baseName.size(); ++idx)
176 if ((*memName)[idx] != baseName[idx])
178 baseName = baseName.take_front(idx);
181 baseName = baseName.rtrim(
'_');
183 SmallString<32> nameBuffer;
185 if (!baseName.empty()) {
186 nameBuffer += baseName;
195 <<
" x " << mem.
dataWidth <<
" memory\n");
198 SmallVector<hw::PortInfo> ports;
210 auto addInput = [&](StringRef prefix,
size_t idx, StringRef suffix,
212 ports.push_back({{
builder.getStringAttr(prefix + Twine(idx) + suffix), type,
218 size_t outputIdx = 0;
219 auto addOutput = [&](StringRef prefix,
size_t idx, StringRef suffix,
221 ports.push_back({{
builder.getStringAttr(prefix + Twine(idx) + suffix), type,
227 auto addCommonPorts = [&](StringRef prefix,
size_t idx) {
228 addInput(prefix, idx,
"_addr", addrType);
229 addInput(prefix, idx,
"_en", bitType);
230 addInput(prefix, idx,
"_clk", clkType);
235 addCommonPorts(
"R", i);
236 addOutput(
"R", i,
"_data", dataType);
241 addCommonPorts(
"RW", i);
242 addInput(
"RW", i,
"_wmode", bitType);
243 addInput(
"RW", i,
"_wdata", dataType);
244 addOutput(
"RW", i,
"_rdata", dataType);
246 addInput(
"RW", i,
"_wmask", maskType);
251 addCommonPorts(
"W", i);
252 addInput(
"W", i,
"_data", dataType);
254 addInput(
"W", i,
"_mask", maskType);
259 auto genAttr = [&](StringRef name, Attribute attr) {
260 return builder.getNamedAttr(name, attr);
262 auto genAttrUI32 = [&](StringRef name, uint32_t
value) {
263 return genAttr(name,
builder.getUI32IntegerAttr(
value));
265 NamedAttribute genAttrs[] = {
274 genAttr(
"readUnderWrite",
276 genAttr(
"writeUnderWrite",
285 Location loc = FirMemOp(memOps.front()).getLoc();
286 if (memOps.size() > 1) {
287 SmallVector<Location> locs;
288 for (
auto memOp : memOps)
289 locs.push_back(memOp.getLoc());
294 auto genOp =
builder.create<hw::HWModuleGeneratedOp>(
295 loc, schemaSymRef, name, ports, StringRef{}, ArrayAttr{}, genAttrs);
297 genOp->setAttr(
"output_file", mem.
outputFile);
306 ArrayRef<std::tuple<FirMemConfig *, HWModuleGeneratedOp, FirMemOp>> mems) {
307 LLVM_DEBUG(
llvm::dbgs() <<
"Lowering " << mems.size() <<
" memories in "
308 << module.getName() <<
"\n");
311 auto constOne = [&](
unsigned width = 1) {
313 auto builder = OpBuilder::atBlockBegin(module.getBodyBlock());
319 auto valueOrOne = [&](Value
value,
unsigned width = 1) {
323 for (
auto [config, genOp, memOp] : mems) {
324 LLVM_DEBUG(
llvm::dbgs() <<
"- Lowering " << memOp.getName() <<
"\n");
325 SmallVector<Value>
inputs;
332 for (
auto *op : memOp->getUsers()) {
333 auto port = dyn_cast<FirMemReadOp>(op);
336 addInput(port.getAddress());
337 addInput(valueOrOne(port.getEnable()));
338 addInput(port.getClk());
339 addOutput(port.getData());
343 for (
auto *op : memOp->getUsers()) {
344 auto port = dyn_cast<FirMemReadWriteOp>(op);
347 addInput(port.getAddress());
348 addInput(valueOrOne(port.getEnable()));
349 addInput(port.getClk());
350 addInput(port.getMode());
351 addInput(port.getWriteData());
352 addOutput(port.getReadData());
353 if (config->maskBits > 1)
354 addInput(valueOrOne(port.getMask(), config->maskBits));
358 for (
auto *op : memOp->getUsers()) {
359 auto port = dyn_cast<FirMemWriteOp>(op);
362 addInput(port.getAddress());
363 addInput(valueOrOne(port.getEnable()));
364 addInput(port.getClk());
365 addInput(port.getData());
366 if (config->maskBits > 1)
367 addInput(valueOrOne(port.getMask(), config->maskBits));
371 StringRef memName =
"mem";
372 if (
auto name = memOp.getName(); name && !name->empty())
374 ImplicitLocOpBuilder
builder(memOp.getLoc(), memOp);
375 auto instOp =
builder.create<hw::InstanceOp>(
376 genOp,
builder.getStringAttr(memName +
"_ext"),
inputs, ArrayAttr{},
377 memOp.getInnerSymAttr());
378 for (
auto [oldOutput, newOutput] : llvm::zip(
outputs, instOp.getResults()))
379 oldOutput.replaceAllUsesWith(newOutput);
382 auto defaultAttrNames = memOp.getAttributeNames();
383 for (
auto namedAttr : memOp->getAttrs())
384 if (!llvm::is_contained(defaultAttrNames, namedAttr.getName()))
385 instOp->setAttr(namedAttr.getName(), namedAttr.getValue());
388 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)
llvm::SmallVector< StringAttr > inputs
llvm::SmallVector< StringAttr > outputs
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(SymbolCache &symCache)
SymbolCache initializer; initialize from every key that is convertible to a StringAttr in the SymbolC...
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.
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
mlir::raw_indented_ostream & dbgs()
The configuration of a FIR memory.
SmallVector< int32_t, 1 > writeClockIDs