16 #include "mlir/IR/ImplicitLocOpBuilder.h"
17 #include "mlir/Pass/Pass.h"
18 #include "llvm/Support/Debug.h"
20 #define DEBUG_TYPE "arc-infer-memories"
24 #define GEN_PASS_DEF_INFERMEMORIES
25 #include "circt/Dialect/Arc/ArcPasses.h.inc"
29 using namespace circt;
33 struct InferMemoriesPass
34 :
public arc::impl::InferMemoriesBase<InferMemoriesPass> {
35 void runOnOperation()
override;
37 SmallVector<Operation *> opsToDelete;
38 SmallPtrSet<StringAttr, 2> schemaNames;
39 DenseMap<StringAttr, DictionaryAttr> memoryParams;
41 using InferMemoriesBase::tapPorts;
45 void InferMemoriesPass::runOnOperation() {
46 auto module = getOperation();
57 for (
auto schemaOp : module.getOps<hw::HWGeneratorSchemaOp>()) {
58 if (schemaOp.getDescriptor() ==
"FIRRTL_Memory") {
59 schemaNames.insert(schemaOp.getSymNameAttr());
60 opsToDelete.push_back(schemaOp);
63 LLVM_DEBUG(
llvm::dbgs() <<
"Found " << schemaNames.size() <<
" schemas\n");
66 for (
auto genOp : module.getOps<hw::HWModuleGeneratedOp>()) {
67 if (!schemaNames.contains(genOp.getGeneratorKindAttr().getAttr()))
69 memoryParams[genOp.getModuleNameAttr()] = genOp->getAttrDictionary();
70 opsToDelete.push_back(genOp);
72 LLVM_DEBUG(
llvm::dbgs() <<
"Found " << memoryParams.size()
73 <<
" memory modules\n");
76 unsigned numReplaced = 0;
77 module.walk([&](hw::InstanceOp instOp) {
78 auto it = memoryParams.find(instOp.getModuleNameAttr().getAttr());
79 if (it == memoryParams.end())
82 DictionaryAttr params = it->second;
83 auto width = params.getAs<IntegerAttr>(
"width").getValue().getZExtValue();
84 auto depth = params.getAs<IntegerAttr>(
"depth").getValue().getZExtValue();
85 auto maskGranAttr = params.getAs<IntegerAttr>(
"maskGran");
87 maskGranAttr ? maskGranAttr.getValue().getZExtValue() :
width;
88 auto maskBits =
width / maskGran;
91 params.getAs<IntegerAttr>(
"writeLatency").getValue().getZExtValue();
93 params.getAs<IntegerAttr>(
"readLatency").getValue().getZExtValue();
94 if (writeLatency != 1) {
95 instOp.emitError(
"unsupported memory write latency ") << writeLatency;
96 return signalPassFailure();
106 unsigned readPreLatency = readLatency;
107 unsigned readPostLatency = 0;
109 ImplicitLocOpBuilder
builder(instOp.getLoc(), instOp);
111 auto addressTy = dyn_cast<IntegerType>(instOp.getOperand(0).getType());
113 instOp.emitError(
"expected integer type for memory addressing, got ")
115 return signalPassFailure();
117 auto memType =
MemoryType::get(&getContext(), depth, wordType, addressTy);
118 auto memOp =
builder.create<MemoryOp>(memType);
119 if (!instOp.getInstanceName().empty())
120 memOp->setAttr(
"name", instOp.getInstanceNameAttr());
123 unsigned resultIdx = 0;
125 auto applyLatency = [&](Value clock, Value
data,
unsigned latency) {
126 for (
unsigned i = 0; i < latency; ++i)
128 data, clock,
builder.getStringAttr(
""), Value{}, Value{}, Value{},
133 SmallVector<std::tuple<Value, Value, SmallVector<Value>, bool,
bool>>
137 SmallString<64> tapPrefix(instOp.getInstanceName());
138 if (!tapPrefix.empty())
139 tapPrefix.push_back(
'/');
140 auto tapPrefixBaseLen = tapPrefix.size();
142 auto tap = [&](Value
value,
const Twine &name) {
143 auto prefixedName =
builder.getStringAttr(tapPrefix +
"_" + name);
149 params.getAs<IntegerAttr>(
"numReadPorts").getValue().getZExtValue();
150 for (
unsigned portIdx = 0; portIdx != numReadPorts; ++portIdx) {
151 auto address = instOp.getOperand(argIdx++);
152 auto enable = instOp.getOperand(argIdx++);
153 auto clock = instOp.getOperand(argIdx++);
154 auto data = instOp.getResult(resultIdx++);
156 if (address.getType() != addressTy) {
157 instOp.emitOpError(
"expected ")
158 << addressTy <<
", but got " << address.getType();
159 return signalPassFailure();
164 tapPrefix.resize(tapPrefixBaseLen);
165 (Twine(
"R") + Twine(portIdx)).
toVector(tapPrefix);
166 tap(address,
"addr");
172 address = applyLatency(clock, address, readPreLatency);
173 enable = applyLatency(clock, enable, readPreLatency);
177 Value readOp =
builder.create<MemoryReadPortOp>(wordType, memOp, address);
183 readOp = applyLatency(clock, readOp, readPostLatency);
184 data.replaceAllUsesWith(readOp);
188 auto numReadWritePorts = params.getAs<IntegerAttr>(
"numReadWritePorts")
191 for (
unsigned portIdx = 0; portIdx != numReadWritePorts; ++portIdx) {
192 auto address = instOp.getOperand(argIdx++);
193 auto enable = instOp.getOperand(argIdx++);
194 auto clock = instOp.getOperand(argIdx++);
195 auto writeMode = instOp.getOperand(argIdx++);
196 auto writeData = instOp.getOperand(argIdx++);
197 auto writeMask = maskBits > 1 ? instOp.getOperand(argIdx++) : Value{};
198 auto readData = instOp.getResult(resultIdx++);
200 if (address.getType() != addressTy) {
201 instOp.emitOpError(
"expected ")
202 << addressTy <<
", but got " << address.getType();
203 return signalPassFailure();
208 tapPrefix.resize(tapPrefixBaseLen);
209 (Twine(
"RW") + Twine(portIdx)).
toVector(tapPrefix);
210 tap(address,
"addr");
212 tap(writeMode,
"wmode");
213 tap(writeData,
"wdata");
215 tap(writeMask,
"wmask");
216 tap(readData,
"rdata");
224 Value readAddress = applyLatency(clock, address, readPreLatency);
225 readEnable = applyLatency(clock, readEnable, readPreLatency);
230 builder.create<MemoryReadPortOp>(wordType, memOp, readAddress);
235 unsigned maskWidth = writeMask.getType().cast<IntegerType>().
getWidth();
236 SmallVector<Value> toConcat;
237 for (
unsigned i = 0; i < maskWidth; ++i) {
239 Value replicated =
builder.create<comb::ReplicateOp>(bit, maskGran);
240 toConcat.push_back(replicated);
242 std::reverse(toConcat.begin(), toConcat.end());
249 readOp = applyLatency(clock, readOp, readPostLatency);
250 readData.replaceAllUsesWith(readOp);
253 SmallVector<Value>
inputs({address, writeData, writeEnable});
255 inputs.push_back(writeMask);
256 writePorts.push_back({memOp, clock,
inputs,
true, !!writeMask});
261 params.getAs<IntegerAttr>(
"numWritePorts").getValue().getZExtValue();
262 for (
unsigned portIdx = 0; portIdx != numWritePorts; ++portIdx) {
263 auto address = instOp.getOperand(argIdx++);
264 auto enable = instOp.getOperand(argIdx++);
265 auto clock = instOp.getOperand(argIdx++);
266 auto data = instOp.getOperand(argIdx++);
267 auto mask = maskBits > 1 ? instOp.getOperand(argIdx++) : Value{};
269 if (address.getType() != addressTy) {
270 instOp.emitOpError(
"expected ")
271 << addressTy <<
", but got " << address.getType();
272 return signalPassFailure();
277 tapPrefix.resize(tapPrefixBaseLen);
278 (Twine(
"W") + Twine(portIdx)).
toVector(tapPrefix);
279 tap(address,
"addr");
287 unsigned maskWidth =
mask.getType().cast<IntegerType>().
getWidth();
288 SmallVector<Value> toConcat;
289 for (
unsigned i = 0; i < maskWidth; ++i) {
291 Value replicated =
builder.create<comb::ReplicateOp>(bit, maskGran);
292 toConcat.push_back(replicated);
294 std::reverse(toConcat.begin(), toConcat.end());
302 writePorts.push_back({memOp, clock,
inputs, !!enable, !!
mask});
307 for (
auto [memOp, clock,
inputs, hasEnable, hasMask] : writePorts) {
308 auto ipSave =
builder.saveInsertionPoint();
309 TypeRange types = ValueRange(
inputs).getTypes();
310 builder.setInsertionPointToStart(module.getBody());
311 auto defOp =
builder.create<DefineOp>(
312 names.
newName(
"mem_write"),
builder.getFunctionType(types, types));
313 auto &block = defOp.getBody().emplaceBlock();
314 auto args = block.addArguments(
315 types, SmallVector<Location>(types.size(),
builder.getLoc()));
316 builder.setInsertionPointToEnd(&block);
317 builder.create<arc::OutputOp>(SmallVector<Value>(args));
318 builder.restoreInsertionPoint(ipSave);
319 builder.create<MemoryWritePortOp>(memOp, defOp.getName(),
inputs, clock,
323 opsToDelete.push_back(instOp);
325 LLVM_DEBUG(
llvm::dbgs() <<
"Inferred " << numReplaced <<
" memories\n");
327 for (
auto *op : opsToDelete)
331 std::unique_ptr<Pass>
333 auto pass = std::make_unique<InferMemoriesPass>();
335 pass->tapPorts = *tapPorts;
static std::vector< mlir::Value > toVector(mlir::ValueRange range)
llvm::SmallVector< StringAttr > inputs
A namespace that is used to store existing names and generate new names in some scope within the IR.
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.
Default symbol cache implementation; stores associations between names (StringAttr's) to mlir::Operat...
std::unique_ptr< mlir::Pass > createInferMemoriesPass(std::optional< bool > tapPorts={})
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
uint64_t getWidth(Type t)
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
mlir::raw_indented_ostream & dbgs()