|
| 1 | +//===- SVMaskNonSynthesizable.cpp - Mask non-synthesizable SV ops ---------===// |
| 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 hides a fixed set of `sv` operations that are not generally |
| 10 | +// synthesizable (concurrent and property assertions) from the SystemVerilog |
| 11 | +// output. It supports two masking modes: |
| 12 | +// |
| 13 | +// * `delete`: erase the matched ops. |
| 14 | +// * `ifdef` : wrap each matched op individually in an |
| 15 | +// `sv.ifdef`/`sv.ifdef.procedural` whose else region holds the |
| 16 | +// moved op, plus a single `sv.macro.decl @SYNTHESIS` at the top |
| 17 | +// of the module if absent. |
| 18 | +// |
| 19 | +//===----------------------------------------------------------------------===// |
| 20 | + |
| 21 | +#include "circt/Dialect/HW/HWOps.h" |
| 22 | +#include "circt/Dialect/SV/SVAttributes.h" |
| 23 | +#include "circt/Dialect/SV/SVOps.h" |
| 24 | +#include "circt/Dialect/SV/SVPasses.h" |
| 25 | +#include "circt/Support/Namespace.h" |
| 26 | +#include "circt/Support/ProceduralRegionTrait.h" |
| 27 | +#include "circt/Support/Utils.h" |
| 28 | +#include "mlir/IR/Builders.h" |
| 29 | +#include "mlir/Pass/Pass.h" |
| 30 | +#include "llvm/ADT/TypeSwitch.h" |
| 31 | + |
| 32 | +namespace circt { |
| 33 | +namespace sv { |
| 34 | +#define GEN_PASS_DEF_SVMASKNONSYNTHESIZABLE |
| 35 | +#include "circt/Dialect/SV/SVPasses.h.inc" |
| 36 | +} // namespace sv |
| 37 | +} // namespace circt |
| 38 | + |
| 39 | +using namespace circt; |
| 40 | +using namespace circt::sv; |
| 41 | + |
| 42 | +//===----------------------------------------------------------------------===// |
| 43 | +// Helpers |
| 44 | +//===----------------------------------------------------------------------===// |
| 45 | + |
| 46 | +/// Returns true if `op` is one of the SV ops we mask. |
| 47 | +static bool isMaskedOp(Operation *op) { |
| 48 | + return isa<sv::AssertConcurrentOp, sv::AssumeConcurrentOp, |
| 49 | + sv::CoverConcurrentOp, sv::AssertPropertyOp, sv::AssumePropertyOp, |
| 50 | + sv::CoverPropertyOp>(op); |
| 51 | +} |
| 52 | + |
| 53 | +/// If `op` is an `sv.ifdef`/`sv.ifdef.procedural` whose macro matches |
| 54 | +/// `expectedMacro`, returns its else region; otherwise returns nullptr. Used |
| 55 | +/// to identify the region that need not be re-wrapped in `ifdef` mode. |
| 56 | +static Region *getMatchingIfDefElseRegion(Operation *op, |
| 57 | + StringRef expectedMacro) { |
| 58 | + return llvm::TypeSwitch<Operation *, Region *>(op) |
| 59 | + .Case<sv::IfDefOp, sv::IfDefProceduralOp>([&](auto ifdef) -> Region * { |
| 60 | + if (ifdef.getCond().getName() != expectedMacro) |
| 61 | + return nullptr; |
| 62 | + return &ifdef.getElseRegion(); |
| 63 | + }) |
| 64 | + .Default(static_cast<Region *>(nullptr)); |
| 65 | +} |
| 66 | + |
| 67 | +/// Resolve the symbol name to use for the `sv.macro.decl` referenced by |
| 68 | +/// `ifdef` mode's `sv.ifdef` ops. Returns the existing decl's sym_name if a |
| 69 | +/// `sv.macro.decl` whose Verilog identifier matches `verilogName` already |
| 70 | +/// exists at top level; otherwise returns a fresh sym_name that does not |
| 71 | +/// collide with any existing top-level symbol (which may differ from |
| 72 | +/// `verilogName` if a non-`sv.macro.decl` symbol of that name is present). |
| 73 | +/// `created` is set to true iff the returned name belongs to a decl that |
| 74 | +/// the caller still needs to materialize. |
| 75 | +static StringAttr resolveMacroSymName(mlir::ModuleOp moduleOp, |
| 76 | + StringRef verilogName, bool &created) { |
| 77 | + for (auto decl : moduleOp.getOps<sv::MacroDeclOp>()) { |
| 78 | + if (decl.getMacroIdentifier() == verilogName) { |
| 79 | + created = false; |
| 80 | + return decl.getSymNameAttr(); |
| 81 | + } |
| 82 | + } |
| 83 | + Namespace ns; |
| 84 | + ns.add(moduleOp); |
| 85 | + StringRef name = ns.newName(verilogName); |
| 86 | + created = true; |
| 87 | + return StringAttr::get(moduleOp.getContext(), name); |
| 88 | +} |
| 89 | + |
| 90 | +/// `delete` mode: walk the block and erase every masked op. |
| 91 | +static void processBlockDelete(Block &block) { |
| 92 | + block.walk([&](Operation *op) { |
| 93 | + if (isMaskedOp(op)) |
| 94 | + op->erase(); |
| 95 | + }); |
| 96 | +} |
| 97 | + |
| 98 | +/// Wrap a single masked op in its own `sv.ifdef`/`sv.ifdef.procedural`. Picks |
| 99 | +/// the procedural variant based on the enclosing region. |
| 100 | +static void maskOpByIfdef(Operation *op, StringRef macro) { |
| 101 | + OpBuilder builder(op); |
| 102 | + Location loc = op->getLoc(); |
| 103 | + |
| 104 | + // Passing a non-null `elseCtor` is what triggers the builder to create an |
| 105 | + // else block. |
| 106 | + Block *elseBlock; |
| 107 | + if (isInProceduralRegion(op)) |
| 108 | + elseBlock = |
| 109 | + sv::IfDefProceduralOp::create(builder, loc, macro, |
| 110 | + /*thenCtor=*/{}, /*elseCtor=*/[]() {}) |
| 111 | + .getElseBlock(); |
| 112 | + else |
| 113 | + elseBlock = sv::IfDefOp::create(builder, loc, macro, /*thenCtor=*/{}, |
| 114 | + /*elseCtor=*/[]() {}) |
| 115 | + .getElseBlock(); |
| 116 | + |
| 117 | + // Move the op into the else block. |
| 118 | + op->moveBefore(elseBlock, elseBlock->end()); |
| 119 | +} |
| 120 | + |
| 121 | +/// `ifdef` mode: wrap each masked op in its own |
| 122 | +/// `sv.ifdef`/`sv.ifdef.procedural`. Recurse into nested regions of non-masked |
| 123 | +/// ops, but skip recursion into the else region of a matching |
| 124 | +/// `sv.ifdef @<macro>` -- those ops are already guarded. |
| 125 | +static bool processBlockIfdef(Block &block, StringRef macro) { |
| 126 | + bool changed = false; |
| 127 | + // `make_early_inc_range` lets us move the current op out of `block` inside |
| 128 | + // the loop body without invalidating the iterator. |
| 129 | + for (Operation &op : llvm::make_early_inc_range(block)) { |
| 130 | + if (isMaskedOp(&op)) { |
| 131 | + maskOpByIfdef(&op, macro); |
| 132 | + changed = true; |
| 133 | + continue; |
| 134 | + } |
| 135 | + Region *guardedElseRegion = getMatchingIfDefElseRegion(&op, macro); |
| 136 | + for (Region ®ion : op.getRegions()) { |
| 137 | + if (®ion == guardedElseRegion) |
| 138 | + continue; |
| 139 | + for (Block &nested : region) |
| 140 | + changed |= processBlockIfdef(nested, macro); |
| 141 | + } |
| 142 | + } |
| 143 | + return changed; |
| 144 | +} |
| 145 | + |
| 146 | +//===----------------------------------------------------------------------===// |
| 147 | +// Pass |
| 148 | +//===----------------------------------------------------------------------===// |
| 149 | + |
| 150 | +namespace { |
| 151 | +struct SVMaskNonSynthesizablePass |
| 152 | + : public circt::sv::impl::SVMaskNonSynthesizableBase< |
| 153 | + SVMaskNonSynthesizablePass> { |
| 154 | + using Base::Base; |
| 155 | + void runOnOperation() override; |
| 156 | +}; |
| 157 | +} // namespace |
| 158 | + |
| 159 | +void SVMaskNonSynthesizablePass::runOnOperation() { |
| 160 | + mlir::ModuleOp moduleOp = getOperation(); |
| 161 | + |
| 162 | + // For `ifdef` mode, resolve the symbol name to use for the macro decl |
| 163 | + // up front. |
| 164 | + StringAttr macroSymName; |
| 165 | + bool macroDeclNeedsCreation = false; |
| 166 | + if (mode == MaskNonSynthesizableMode::Ifdef) |
| 167 | + macroSymName = resolveMacroSymName(moduleOp, macro, macroDeclNeedsCreation); |
| 168 | + |
| 169 | + StringRef passDownMacro = |
| 170 | + macroSymName ? macroSymName.getValue() : StringRef(); |
| 171 | + // Each `hw.module`'s body is independent: `processBlock*` only mutates ops |
| 172 | + // inside the module it's called on. |
| 173 | + SmallVector<hw::HWModuleOp> hwModules(moduleOp.getOps<hw::HWModuleOp>()); |
| 174 | + unsigned numChanged = |
| 175 | + transformReduce(&getContext(), hwModules, /*init=*/0u, std::plus<>(), |
| 176 | + [&](hw::HWModuleOp hwModule) -> unsigned { |
| 177 | + Block &body = *hwModule.getBodyBlock(); |
| 178 | + switch (mode) { |
| 179 | + case MaskNonSynthesizableMode::Delete: |
| 180 | + processBlockDelete(body); |
| 181 | + return 0; |
| 182 | + case MaskNonSynthesizableMode::Ifdef: |
| 183 | + return processBlockIfdef(body, passDownMacro) ? 1 : 0; |
| 184 | + } |
| 185 | + llvm_unreachable("all modes handled"); |
| 186 | + }); |
| 187 | + |
| 188 | + if (mode == MaskNonSynthesizableMode::Ifdef && numChanged > 0 && |
| 189 | + macroDeclNeedsCreation) { |
| 190 | + auto builder = OpBuilder::atBlockBegin(moduleOp.getBody()); |
| 191 | + sv::MacroDeclOp::create(builder, moduleOp.getLoc(), macroSymName, |
| 192 | + /*args=*/ArrayAttr{}, builder.getStringAttr(macro)); |
| 193 | + } |
| 194 | +} |
0 commit comments