CIRCT 24.0.0git
Loading...
Searching...
No Matches
FlattenMemory.cpp
Go to the documentation of this file.
1//===- FlattenMemroy.cpp - Flatten Memory Pass ----------------------------===//
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 FlattenMemory pass.
10//
11//===----------------------------------------------------------------------===//
12
18#include "mlir/Pass/Pass.h"
19#include "llvm/Support/Debug.h"
20#include <numeric>
21
22#define DEBUG_TYPE "lower-memory"
23
24namespace circt {
25namespace firrtl {
26#define GEN_PASS_DEF_FLATTENMEMORY
27#include "circt/Dialect/FIRRTL/Passes.h.inc"
28} // namespace firrtl
29} // namespace circt
30
31using namespace circt;
32using namespace firrtl;
33
34namespace {
35struct FlattenMemoryPass
36 : public circt::firrtl::impl::FlattenMemoryBase<FlattenMemoryPass> {
37
38 /// Returns true if the the memory has annotations on a subfield of any of the
39 /// ports.
40 static bool hasSubAnno(MemOp op) {
41 for (size_t portIdx = 0, e = op.getNumResults(); portIdx < e; ++portIdx)
42 for (auto attr : op.getPortAnnotation(portIdx))
43 if (cast<DictionaryAttr>(attr).get("circt.fieldID"))
44 return true;
45 return false;
46 };
47
48 /// This pass converts data types of memories to a flat UInt, and inserts
49 /// appropriate bitcasts to access the data.
50 void runOnOperation() override {
51 LLVM_DEBUG(llvm::dbgs() << "\n Running lower memory on module:"
52 << getOperation().getName());
53 SmallVector<Operation *> opsToErase;
54 getOperation().getBodyBlock()->walk([&](MemOp memOp) {
55 LLVM_DEBUG(llvm::dbgs() << "\n Memory:" << memOp);
56
57 // Nothing to do if the memory already has UInt type
58 if (type_isa<UIntType>(memOp.getDataType()))
59 return;
60
61 // Cannot flatten a memory if it has debug ports, because debug port
62 // implies a memtap and we cannot transform the datatype for a memory that
63 // is tapped.
64 for (auto res : memOp.getResults())
65 if (isa<RefType>(res.getType()))
66 return;
67
68 // If subannotations present on aggregate fields, we cannot flatten the
69 // memory. It must be split into one memory per aggregate field.
70 // Do not overwrite the pass flag!
71 if (hasSubAnno(memOp))
72 return;
73
74 // The vector of leaf elements type after flattening the data. If any of
75 // the datatypes cannot be flattened, then we cannot flatten the memory.
76 SmallVector<FIRRTLBaseType> flatMemType;
77 if (!flattenType(memOp.getDataType(), flatMemType))
78 return;
79
80 // Calculate the width of the memory data type, and the width of
81 // each individual aggregate leaf elements.
82 size_t memFlatWidth = 0;
83 SmallVector<int32_t> memWidths;
84 for (auto f : flatMemType) {
85 LLVM_DEBUG(llvm::dbgs() << "\n field type:" << f);
86 auto w = f.getBitWidthOrSentinel();
87 memWidths.push_back(w);
88 memFlatWidth += w;
89 }
90 // If all the widths are zero, ignore the memory.
91 if (!memFlatWidth)
92 return;
93
94 // Calculate the mask granularity of this memory, which is how many bits
95 // of the data each mask bit controls. This is the greatest common
96 // denominator of the widths of the flattened data types.
97 auto maskGran = memWidths.front();
98 for (auto w : ArrayRef(memWidths).drop_front())
99 maskGran = std::gcd(maskGran, w);
100
101 // Total mask bitwidth after flattening.
102 uint32_t totalmaskWidths = 0;
103 // How many mask bits each field type requires.
104 SmallVector<unsigned> maskWidths;
105 for (auto w : memWidths) {
106 // How many mask bits required for each flattened field.
107 auto mWidth = w / maskGran;
108 maskWidths.push_back(mWidth);
109 totalmaskWidths += mWidth;
110 }
111
112 // Now create a new memory of type flattened data.
113 // ----------------------------------------------
114 SmallVector<Type, 8> ports;
115 SmallVector<Attribute, 8> portNames;
116
117 auto *context = memOp.getContext();
118 ImplicitLocOpBuilder builder(memOp.getLoc(), memOp);
119 // Create a new memory data type of unsigned and computed width.
120 auto flatType = UIntType::get(context, memFlatWidth);
121 for (auto port : memOp.getPorts()) {
122 ports.push_back(MemOp::getTypeForPort(memOp.getDepth(), flatType,
123 port.second, totalmaskWidths));
124 portNames.push_back(port.first);
125 }
126
127 // Create the new flattened memory.
128 auto flatMem = MemOp::create(
129 builder, ports, memOp.getReadLatency(), memOp.getWriteLatency(),
130 memOp.getDepth(), memOp.getRuw(), builder.getArrayAttr(portNames),
131 memOp.getNameAttr(), memOp.getNameKind(), memOp.getAnnotations(),
132 memOp.getPortAnnotations(), memOp.getInnerSymAttr(),
133 memOp.getInitAttr(), memOp.getPrefixAttr());
134
135 // Hook up the new memory to the wires the old memory was replaced with.
136 for (size_t index = 0, rend = memOp.getNumResults(); index < rend;
137 ++index) {
138
139 // Create a wire with the original type, and replace all uses of the old
140 // memory with the wire. We will be reconstructing the original type
141 // in the wire from the bitvector of the flattened memory.
142 auto result = memOp.getResult(index);
143 auto wire =
144 WireOp::create(
145 builder, result.getType(),
146 (memOp.getName() + "_" + memOp.getPortName(index)).str())
147 .getResult();
148 result.replaceAllUsesWith(wire);
149 result = wire;
150 auto newResult = flatMem.getResult(index);
151 auto rType = type_cast<BundleType>(result.getType());
152 for (size_t fieldIndex = 0, fend = rType.getNumElements();
153 fieldIndex != fend; ++fieldIndex) {
154 auto name = rType.getElement(fieldIndex).name;
155 auto oldField = SubfieldOp::create(builder, result, fieldIndex);
156 FIRRTLBaseValue newField =
157 SubfieldOp::create(builder, newResult, fieldIndex);
158 // data and mask depend on the memory type which was split. They can
159 // also go both directions, depending on the port direction.
160 if (!(name == "data" || name == "mask" || name == "wdata" ||
161 name == "wmask" || name == "rdata")) {
162 emitConnect(builder, newField, oldField);
163 continue;
164 }
165 Value realOldField = oldField;
166 if (rType.getElement(fieldIndex).isFlip) {
167 // Cast the memory read data from flat type to aggregate.
168 auto castField =
169 builder.createOrFold<BitCastOp>(oldField.getType(), newField);
170 // Write the aggregate read data.
171 emitConnect(builder, realOldField, castField);
172 } else {
173 // Cast the input aggregate write data to flat type.
174 auto newFieldType = newField.getType();
175 auto oldFieldBitWidth = getBitWidth(oldField.getType());
176 // Following condition is true, if a data field is 0 bits. Then
177 // newFieldType is of smaller bits than old.
178 if (getBitWidth(newFieldType) != *oldFieldBitWidth)
179 newFieldType = UIntType::get(context, *oldFieldBitWidth);
180 realOldField = BitCastOp::create(builder, newFieldType, oldField);
181 // Mask bits require special handling, since some of the mask bits
182 // need to be repeated, direct bitcasting wouldn't work. Depending
183 // on the mask granularity, some mask bits will be repeated.
184 if ((name == "mask" || name == "wmask") &&
185 (maskWidths.size() != totalmaskWidths)) {
186 Value catMasks;
187 for (const auto &m : llvm::enumerate(llvm::reverse(maskWidths))) {
188 // Get the mask bit.
189 auto mBit = builder.createOrFold<BitsPrimOp>(
190 realOldField, m.index(), m.index());
191 // Check how many times the mask bit needs to be prepend.
192 for (size_t repeat = 0; repeat < m.value(); repeat++)
193 if ((m.index() == 0 && repeat == 0) || !catMasks)
194 catMasks = mBit;
195 else
196 catMasks = builder.createOrFold<CatPrimOp>(
197 ValueRange{mBit, catMasks});
198 }
199 realOldField = catMasks;
200 }
201 // Now set the mask or write data.
202 // Ensure that the types match.
203 emitConnect(builder, newField,
204 builder.createOrFold<BitCastOp>(newField.getType(),
205 realOldField));
206 }
207 }
208 }
209 ++numFlattenedMems;
210 memOp.erase();
211 return;
212 });
213 }
214
215private:
216 // Convert a type into a flat list of fields. This is used to
217 // flatten the aggregate memory datatype. Recursively populate the results
218 // with each ground type field.
219 static bool flattenType(FIRRTLType type,
220 SmallVectorImpl<FIRRTLBaseType> &results) {
221 std::function<bool(FIRRTLType)> flatten = [&](FIRRTLType type) -> bool {
222 return FIRRTLTypeSwitch<FIRRTLType, bool>(type)
223 .Case<BundleType>([&](auto bundle) {
224 for (auto &elt : bundle)
225 if (!flatten(elt.type))
226 return false;
227 return true;
228 })
229 .Case<FVectorType>([&](auto vector) {
230 for (size_t i = 0, e = vector.getNumElements(); i != e; ++i)
231 if (!flatten(vector.getElementType()))
232 return false;
233 return true;
234 })
235 .Case<IntType>([&](IntType type) {
236 results.push_back(type);
237 return type.getWidth().has_value();
238 })
239 .Case<FEnumType>([&](FEnumType type) {
240 results.emplace_back(type);
241 return true;
242 })
243 .Default([&](auto) { return false; });
244 };
245 return flatten(type);
246 }
247
248 Value getSubWhatever(ImplicitLocOpBuilder *builder, Value val, size_t index) {
249 if (BundleType bundle = type_dyn_cast<BundleType>(val.getType()))
250 return SubfieldOp::create(*builder, val, index);
251 if (FVectorType fvector = type_dyn_cast<FVectorType>(val.getType()))
252 return SubindexOp::create(*builder, val, index);
253
254 llvm_unreachable("Unknown aggregate type");
255 return nullptr;
256 }
257};
258} // end anonymous namespace
static std::unique_ptr< Context > context
std::optional< int32_t > getWidth() const
Return an optional containing the width, if the width is known (or empty if width is unknown).
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:56
BaseTy type_cast(Type type)
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs, bool warnOnTruncation=false)
Emit a connect between two values.
mlir::TypedValue< FIRRTLBaseType > FIRRTLBaseValue
BaseTy type_dyn_cast(Type type)
bool type_isa(Type type)
std::optional< int64_t > getBitWidth(FIRRTLBaseType type, bool ignoreFlip=false)
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.