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 using InferMemoriesBase::InferMemoriesBase;
37 void runOnOperation()
override;
39 SmallVector<Operation *> opsToDelete;
40 SmallPtrSet<StringAttr, 2> schemaNames;
41 DenseMap<StringAttr, DictionaryAttr> memoryParams;
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);
110 auto wordType = builder.getIntegerType(width);
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 (tapMemories && !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);
144 builder.create<arc::TapOp>(value, prefixedName);
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");
219 auto c1_i1 = builder.create<
hw::ConstantOp>(builder.getI1Type(), 1);
221 Value readEnable = builder.create<
comb::AndOp>(enable, notWriteMode);
224 Value readAddress = applyLatency(clock, address, readPreLatency);
225 readEnable = applyLatency(clock, readEnable, readPreLatency);
230 builder.create<MemoryReadPortOp>(wordType, memOp, readAddress);
235 unsigned maskWidth = cast<IntegerType>(writeMask.getType()).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);
252 auto writeEnable = builder.create<
comb::AndOp>(enable, writeMode);
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 = cast<IntegerType>(
mask.getType()).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());
297 SmallVector<Value> inputs({address,
data});
299 inputs.push_back(enable);
301 inputs.push_back(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 return std::make_unique<InferMemoriesPass>(options);
std::map< std::string, WriteChannelPort & > writePorts
static std::vector< mlir::Value > toVector(mlir::ValueRange range)
A namespace that is used to store existing names and generate new names in some scope within the IR.
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.
Default symbol cache implementation; stores associations between names (StringAttr's) to mlir::Operat...
def create(data_type, value)
std::unique_ptr< mlir::Pass > createInferMemoriesPass(const InferMemoriesOptions &options={})
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.