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