Loading [MathJax]/extensions/tex2jax.js
CIRCT 22.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
HWLegalizeModules.cpp
Go to the documentation of this file.
1//===- HWLegalizeModulesPass.cpp - Lower unsupported IR features away -----===//
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 pass lowers away features in the SV/Comb/HW dialects that are
10// unsupported by some tools (e.g. multidimensional arrays) as specified by
11// LoweringOptions. This pass is run relatively late in the pipeline in
12// preparation for emission. Any passes run after this (e.g. PrettifyVerilog)
13// must be aware they cannot introduce new invalid constructs.
14//
15//===----------------------------------------------------------------------===//
16
22#include "mlir/IR/Builders.h"
23#include "mlir/Pass/Pass.h"
24
25namespace circt {
26namespace sv {
27#define GEN_PASS_DEF_HWLEGALIZEMODULES
28#include "circt/Dialect/SV/SVPasses.h.inc"
29} // namespace sv
30} // namespace circt
31
32using namespace circt;
33//===----------------------------------------------------------------------===//
34// HWLegalizeModulesPass
35//===----------------------------------------------------------------------===//
36
37namespace {
38struct HWLegalizeModulesPass
39 : public circt::sv::impl::HWLegalizeModulesBase<HWLegalizeModulesPass> {
40 void runOnOperation() override;
41
42private:
43 void processPostOrder(Block &block);
44 bool tryLoweringPackedArrayOp(Operation &op);
45 Value lowerLookupToCasez(Operation &op, Value input, Value index,
46 mlir::Type elementType,
47 SmallVector<Value> caseValues);
48 bool processUsers(Operation &op, Value value, ArrayRef<Value> mapping);
49 std::optional<std::pair<uint64_t, unsigned>>
50 tryExtractIndexAndBitWidth(Value value);
51
52 /// This is the current hw.module being processed.
53 hw::HWModuleOp thisHWModule;
54
55 bool anythingChanged;
56
57 /// This tells us what language features we're allowed to use in generated
58 /// Verilog.
59 LoweringOptions options;
60
61 /// This pass will be run on multiple hw.modules, this keeps track of the
62 /// contents of LoweringOptions so we don't have to reparse the
63 /// LoweringOptions for every hw.module.
64 StringAttr lastParsedOptions;
65};
66} // end anonymous namespace
67
68bool HWLegalizeModulesPass::tryLoweringPackedArrayOp(Operation &op) {
69 return TypeSwitch<Operation *, bool>(&op)
70 .Case<hw::AggregateConstantOp>([&](hw::AggregateConstantOp constOp) {
71 // Replace individual element uses (if any) with input fields.
72 SmallVector<Value> inputs;
73 OpBuilder builder(constOp);
74 for (auto field : llvm::reverse(constOp.getFields())) {
75 if (auto intAttr = dyn_cast<IntegerAttr>(field))
76 inputs.push_back(
77 hw::ConstantOp::create(builder, constOp.getLoc(), intAttr));
78 else
79 inputs.push_back(hw::AggregateConstantOp::create(
80 builder, constOp.getLoc(), constOp.getType(),
81 cast<ArrayAttr>(field)));
82 }
83 if (!processUsers(op, constOp.getResult(), inputs))
84 return false;
85
86 // Remove original op.
87 return true;
88 })
89 .Case<hw::ArrayConcatOp>([&](hw::ArrayConcatOp concatOp) {
90 // Redirect individual element uses (if any) to the input arguments.
91 SmallVector<std::pair<Value, uint64_t>> arrays;
92 for (auto array : llvm::reverse(concatOp.getInputs())) {
93 auto ty = hw::type_cast<hw::ArrayType>(array.getType());
94 arrays.emplace_back(array, ty.getNumElements());
95 }
96 for (auto *user :
97 llvm::make_early_inc_range(concatOp.getResult().getUsers())) {
98 if (TypeSwitch<Operation *, bool>(user)
99 .Case<hw::ArrayGetOp>([&](hw::ArrayGetOp getOp) {
100 if (auto indexAndBitWidth =
101 tryExtractIndexAndBitWidth(getOp.getIndex())) {
102 auto [indexValue, bitWidth] = *indexAndBitWidth;
103 // FIXME: More efficient search
104 for (const auto &[array, size] : arrays) {
105 if (indexValue >= size) {
106 indexValue -= size;
107 continue;
108 }
109 OpBuilder builder(getOp);
110 getOp.getInputMutable().set(array);
111 getOp.getIndexMutable().set(
112 builder.createOrFold<hw::ConstantOp>(
113 getOp.getLoc(), APInt(bitWidth, indexValue)));
114 return true;
115 }
116 }
117
118 return false;
119 })
120 .Default([](auto op) { return false; }))
121 continue;
122
123 op.emitError("unsupported packed array expression");
124 signalPassFailure();
125 }
126
127 // Remove the original op.
128 return true;
129 })
130 .Case<hw::ArrayCreateOp>([&](hw::ArrayCreateOp createOp) {
131 // Replace individual element uses (if any) with input arguments.
132 SmallVector<Value> inputs(llvm::reverse(createOp.getInputs()));
133 if (!processUsers(op, createOp.getResult(), inputs))
134 return false;
135
136 // Remove original op.
137 return true;
138 })
139 .Case<hw::ArrayGetOp>([&](hw::ArrayGetOp getOp) {
140 // Skip index ops with constant index.
141 auto index = getOp.getIndex();
142 if (auto *definingOp = index.getDefiningOp())
143 if (isa<hw::ConstantOp>(definingOp))
144 return false;
145
146 // Generate case value element lookups.
147 auto ty = hw::type_cast<hw::ArrayType>(getOp.getInput().getType());
148 OpBuilder builder(getOp);
149 SmallVector<Value> caseValues;
150 for (size_t i = 0, e = ty.getNumElements(); i < e; i++) {
151 auto loc = op.getLoc();
152 auto index = builder.createOrFold<hw::ConstantOp>(
153 loc, APInt(llvm::Log2_64_Ceil(e), i));
154 auto element =
155 hw::ArrayGetOp::create(builder, loc, getOp.getInput(), index);
156 caseValues.push_back(element);
157 }
158
159 // Transform array index op into casez statement.
160 auto theWire = lowerLookupToCasez(op, getOp.getInput(), index,
161 ty.getElementType(), caseValues);
162
163 // Emit the read from the wire, replace uses and clean up.
164 builder.setInsertionPoint(getOp);
165 auto readWire =
166 sv::ReadInOutOp::create(builder, getOp.getLoc(), theWire);
167 getOp.getResult().replaceAllUsesWith(readWire);
168 return true;
169 })
170 .Case<sv::ArrayIndexInOutOp>([&](sv::ArrayIndexInOutOp indexOp) {
171 // Skip index ops with constant index.
172 auto index = indexOp.getIndex();
173 if (auto *definingOp = index.getDefiningOp())
174 if (isa<hw::ConstantOp>(definingOp))
175 return false;
176
177 // Skip index ops with unpacked arrays.
178 auto inout = indexOp.getInput().getType();
179 if (hw::type_isa<hw::UnpackedArrayType>(inout.getElementType()))
180 return false;
181
182 // Generate case value element lookups.
183 auto ty = hw::type_cast<hw::ArrayType>(inout.getElementType());
184 OpBuilder builder(&op);
185 SmallVector<Value> caseValues;
186 for (size_t i = 0, e = ty.getNumElements(); i < e; i++) {
187 auto loc = op.getLoc();
188 auto index = builder.createOrFold<hw::ConstantOp>(
189 loc, APInt(llvm::Log2_64_Ceil(e), i));
190 auto element = sv::ArrayIndexInOutOp::create(
191 builder, loc, indexOp.getInput(), index);
192 auto readElement = sv::ReadInOutOp::create(builder, loc, element);
193 caseValues.push_back(readElement);
194 }
195
196 // Transform array index op into casez statement.
197 auto theWire = lowerLookupToCasez(op, indexOp.getInput(), index,
198 ty.getElementType(), caseValues);
199
200 // Replace uses and clean up.
201 indexOp.getResult().replaceAllUsesWith(theWire);
202 return true;
203 })
204 .Case<sv::PAssignOp>([&](sv::PAssignOp assignOp) {
205 // Transform array assignment into individual assignments for each array
206 // element.
207 auto inout = assignOp.getDest().getType();
208 auto ty = hw::type_dyn_cast<hw::ArrayType>(inout.getElementType());
209 if (!ty)
210 return false;
211
212 OpBuilder builder(assignOp);
213 for (size_t i = 0, e = ty.getNumElements(); i < e; i++) {
214 auto loc = op.getLoc();
215 auto index = builder.createOrFold<hw::ConstantOp>(
216 loc, APInt(llvm::Log2_64_Ceil(e), i));
217 auto dstElement = sv::ArrayIndexInOutOp::create(
218 builder, loc, assignOp.getDest(), index);
219 auto srcElement =
220 hw::ArrayGetOp::create(builder, loc, assignOp.getSrc(), index);
221 sv::PAssignOp::create(builder, loc, dstElement, srcElement);
222 }
223
224 // Remove original assignment.
225 return true;
226 })
227 .Case<sv::RegOp>([&](sv::RegOp regOp) {
228 // Transform array reg into individual regs for each array element.
229 auto ty = hw::type_dyn_cast<hw::ArrayType>(regOp.getElementType());
230 if (!ty)
231 return false;
232
233 OpBuilder builder(regOp);
234 auto name = StringAttr::get(regOp.getContext(), "name");
235 SmallVector<Value> elements;
236 for (size_t i = 0, e = ty.getNumElements(); i < e; i++) {
237 auto loc = op.getLoc();
238 auto element = sv::RegOp::create(builder, loc, ty.getElementType());
239 if (auto nameAttr = regOp->getAttrOfType<StringAttr>(name)) {
240 element.setNameAttr(
241 StringAttr::get(regOp.getContext(), nameAttr.getValue()));
242 }
243 elements.push_back(element);
244 }
245
246 // Fix users to refer to individual element regs.
247 if (!processUsers(op, regOp.getResult(), elements))
248 return false;
249
250 // Remove original reg.
251 return true;
252 })
253 .Default([&](auto op) { return false; });
254}
255
256Value HWLegalizeModulesPass::lowerLookupToCasez(Operation &op, Value input,
257 Value index,
258 mlir::Type elementType,
259 SmallVector<Value> caseValues) {
260 // Create the wire for the result of the casez in the
261 // hw.module.
262 OpBuilder builder(&op);
263 auto theWire = sv::RegOp::create(builder, op.getLoc(), elementType,
264 builder.getStringAttr("casez_tmp"));
265 builder.setInsertionPoint(&op);
266
267 auto loc = input.getLoc();
268 // A casez is a procedural operation, so if we're in a
269 // non-procedural region we need to inject an always_comb
270 // block.
271 if (!op.getParentOp()->hasTrait<sv::ProceduralRegion>()) {
272 auto alwaysComb = sv::AlwaysCombOp::create(builder, loc);
273 builder.setInsertionPointToEnd(alwaysComb.getBodyBlock());
274 }
275
276 // If we are missing elements in the array (it is non-power of
277 // two), then add a default 'X' value.
278 if (1ULL << index.getType().getIntOrFloatBitWidth() != caseValues.size()) {
279 caseValues.push_back(sv::ConstantXOp::create(builder, op.getLoc(),
280 op.getResult(0).getType()));
281 }
282
283 APInt caseValue(index.getType().getIntOrFloatBitWidth(), 0);
284 auto *context = builder.getContext();
285
286 // Create the casez itself.
287 sv::CaseOp::create(
288 builder, loc, CaseStmtType::CaseZStmt, index, caseValues.size(),
289 [&](size_t caseIdx) -> std::unique_ptr<sv::CasePattern> {
290 // Use a default pattern for the last value, even if we
291 // are complete. This avoids tools thinking they need to
292 // insert a latch due to potentially incomplete case
293 // coverage.
294 bool isDefault = caseIdx == caseValues.size() - 1;
295 Value theValue = caseValues[caseIdx];
296 std::unique_ptr<sv::CasePattern> thePattern;
297
298 if (isDefault)
299 thePattern = std::make_unique<sv::CaseDefaultPattern>(context);
300 else
301 thePattern = std::make_unique<sv::CaseBitPattern>(caseValue, context);
302 ++caseValue;
303 sv::BPAssignOp::create(builder, loc, theWire, theValue);
304 return thePattern;
305 });
306
307 return theWire;
308}
309
310bool HWLegalizeModulesPass::processUsers(Operation &op, Value value,
311 ArrayRef<Value> mapping) {
312 for (auto *user : llvm::make_early_inc_range(value.getUsers())) {
313 if (TypeSwitch<Operation *, bool>(user)
314 .Case<hw::ArrayGetOp>([&](hw::ArrayGetOp getOp) {
315 if (auto indexAndBitWidth =
316 tryExtractIndexAndBitWidth(getOp.getIndex())) {
317 getOp.replaceAllUsesWith(mapping[indexAndBitWidth->first]);
318 return true;
319 }
320
321 return false;
322 })
323 .Case<sv::ArrayIndexInOutOp>([&](sv::ArrayIndexInOutOp indexOp) {
324 if (auto indexAndBitWidth =
325 tryExtractIndexAndBitWidth(indexOp.getIndex())) {
326 indexOp.replaceAllUsesWith(mapping[indexAndBitWidth->first]);
327 return true;
328 }
329
330 return false;
331 })
332 .Default([](auto op) { return false; })) {
333 user->erase();
334 continue;
335 }
336
337 user->emitError("unsupported packed array expression");
338 signalPassFailure();
339 return false;
340 }
341
342 return true;
343}
344
345std::optional<std::pair<uint64_t, unsigned>>
346HWLegalizeModulesPass::tryExtractIndexAndBitWidth(Value value) {
347 if (auto constantOp = dyn_cast<hw::ConstantOp>(value.getDefiningOp())) {
348 auto index = constantOp.getValue();
349 return std::make_optional(
350 std::make_pair(index.getZExtValue(), index.getBitWidth()));
351 }
352 return std::nullopt;
353}
354
355void HWLegalizeModulesPass::processPostOrder(Block &body) {
356 if (body.empty())
357 return;
358
359 // Walk the block bottom-up, processing the region tree inside out.
360 Block::iterator it = std::prev(body.end());
361 while (it != body.end()) {
362 auto &op = *it;
363
364 // Advance the iterator, using the end iterator as a sentinel that we're at
365 // the top of the block.
366 if (it == body.begin())
367 it = body.end();
368 else
369 --it;
370
371 if (op.getNumRegions()) {
372 for (auto &region : op.getRegions())
373 for (auto &regionBlock : region.getBlocks())
374 processPostOrder(regionBlock);
375 }
376
377 if (options.disallowPackedArrays) {
378 // Try supported packed array op lowering.
379 if (tryLoweringPackedArrayOp(op)) {
380 it = --Block::iterator(op);
381 op.erase();
382 anythingChanged = true;
383 continue;
384 }
385
386 // Otherwise, if the IR produces a packed array and we aren't allowing
387 // multi-dimensional arrays, reject the IR as invalid.
388 for (auto value : op.getResults()) {
389 if (isa<hw::ArrayType>(value.getType())) {
390 op.emitError("unsupported packed array expression");
391 signalPassFailure();
392 }
393 }
394 }
395 }
396}
397
398void HWLegalizeModulesPass::runOnOperation() {
399 thisHWModule = getOperation();
400
401 // Parse the lowering options if necessary.
402 auto optionsAttr = LoweringOptions::getAttributeFrom(
403 cast<ModuleOp>(thisHWModule->getParentOp()));
404 if (optionsAttr != lastParsedOptions) {
405 if (optionsAttr)
406 options = LoweringOptions(optionsAttr.getValue(), [&](Twine error) {
407 thisHWModule.emitError(error);
408 });
409 else
410 options = LoweringOptions();
411 lastParsedOptions = optionsAttr;
412 }
413
414 // Keeps track if anything changed during this pass, used to determine if
415 // the analyses were preserved.
416 anythingChanged = false;
417
418 // Walk the operations in post-order, transforming any that are interesting.
419 processPostOrder(*thisHWModule.getBodyBlock());
420
421 // If we did not change anything in the IR mark all analysis as preserved.
422 if (!anythingChanged)
423 markAllAnalysesPreserved();
424}
425
427 return std::make_unique<HWLegalizeModulesPass>();
428}
MlirType elementType
Definition CHIRRTL.cpp:29
Signals that an operations regions are procedural.
Definition SVOps.h:160
create(array_value, idx)
Definition hw.py:450
create(data_type, value)
Definition hw.py:433
create(value)
Definition sv.py:106
Definition sv.py:68
std::unique_ptr< mlir::Pass > createHWLegalizeModulesPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition sv.py:1
Options which control the emission from CIRCT to Verilog.
static mlir::StringAttr getAttributeFrom(mlir::ModuleOp module)
Return the value of the circt.loweringOptions in the specified module if present, or a null attribute...