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