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