CIRCT 24.0.0git
Loading...
Searching...
No Matches
SeqToSV.cpp
Go to the documentation of this file.
1//===- LowerSeqToSV.cpp - Seq to SV lowering ------------------------------===//
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 transform translate Seq ops to SV.
10//
11//===----------------------------------------------------------------------===//
12
14#include "FirMemLowering.h"
15#include "FirRegLowering.h"
25#include "mlir/IR/Builders.h"
26#include "mlir/IR/ImplicitLocOpBuilder.h"
27#include "mlir/IR/Threading.h"
28#include "mlir/Pass/Pass.h"
29#include "mlir/Transforms/DialectConversion.h"
30
31#define DEBUG_TYPE "lower-seq-to-sv"
32
33using namespace circt;
34using namespace seq;
35using hw::HWModuleOp;
36using llvm::MapVector;
37
38namespace circt {
39#define GEN_PASS_DEF_LOWERSEQTOSV
40#include "circt/Conversion/Passes.h.inc"
41
42struct SeqToSVPass : public impl::LowerSeqToSVBase<SeqToSVPass> {
43
44 void runOnOperation() override;
45
46 using LowerSeqToSVBase<SeqToSVPass>::lowerToAlwaysFF;
47 using LowerSeqToSVBase<SeqToSVPass>::disableRegRandomization;
48 using LowerSeqToSVBase<SeqToSVPass>::emitSeparateAlwaysBlocks;
49 using LowerSeqToSVBase<SeqToSVPass>::emitPresetAsInlineInit;
50 using LowerSeqToSVBase<SeqToSVPass>::LowerSeqToSVBase;
51 using LowerSeqToSVBase<SeqToSVPass>::numSubaccessRestored;
52};
53} // namespace circt
54
55namespace {
56struct ModuleLoweringState {
57 ModuleLoweringState(HWModuleOp module)
58 : immutableValueLowering(module), module(module) {}
59
60 struct ImmutableValueLowering {
61 ImmutableValueLowering(hw::HWModuleOp module) : module(module) {}
62
63 // Lower initial ops.
64 LogicalResult lower();
65 LogicalResult lower(seq::InitialOp initialOp);
66
67 Value
68 lookupImmutableValue(mlir::TypedValue<seq::ImmutableType> immut) const {
69 return mapping.lookup(immut);
70 }
71
72 sv::InitialOp getSVInitial() const { return svInitialOp; }
73
74 private:
75 sv::InitialOp svInitialOp = {};
76 // A mapping from a dummy immutable value to the actual initial value
77 // defined in SV initial op.
79
80 hw::HWModuleOp module;
81 } immutableValueLowering;
82
83 struct FragmentInfo {
84 bool needsRegFragment = false;
85 } fragment;
86
87 HWModuleOp module;
88};
89
90LogicalResult ModuleLoweringState::ImmutableValueLowering::lower() {
91 auto result = mergeInitialOps(module.getBodyBlock());
92 if (failed(result))
93 return failure();
94
95 auto initialOp = *result;
96 if (!initialOp)
97 return success();
98
99 return lower(initialOp);
100}
101
102LogicalResult
103ModuleLoweringState::ImmutableValueLowering::lower(seq::InitialOp initialOp) {
104 OpBuilder builder = OpBuilder::atBlockBegin(module.getBodyBlock());
105 if (!svInitialOp)
106 svInitialOp = sv::InitialOp::create(builder, initialOp->getLoc());
107 // Initial ops are merged to single one and must not have operands.
108 assert(initialOp.getNumOperands() == 0 &&
109 "initial op should have no operands");
110
111 auto loc = initialOp.getLoc();
112 llvm::SmallVector<Value> results;
113
114 auto yieldOp = cast<seq::YieldOp>(initialOp.getBodyBlock()->getTerminator());
115
116 for (auto [result, operand] :
117 llvm::zip(initialOp.getResults(), yieldOp->getOperands())) {
118 auto placeholder =
119 mlir::UnrealizedConversionCastOp::create(
120 builder, loc, ArrayRef<Type>{result.getType()}, ArrayRef<Value>{})
121 ->getResult(0);
122 result.replaceAllUsesWith(placeholder);
123 mapping.insert(
124 {cast<mlir::TypedValue<seq ::ImmutableType>>(placeholder), operand});
125 }
126
127 svInitialOp.getBodyBlock()->getOperations().splice(
128 svInitialOp.end(), initialOp.getBodyBlock()->getOperations());
129
130 assert(initialOp->use_empty());
131 initialOp.erase();
132 yieldOp->erase();
133 return success();
134}
135
136/// Lower CompRegOp to `sv.reg` and `sv.alwaysff`. Use a posedge clock and
137/// synchronous reset.
138template <typename OpTy>
139class CompRegLower : public OpConversionPattern<OpTy> {
140public:
141 CompRegLower(
142 TypeConverter &typeConverter, MLIRContext *context, bool lowerToAlwaysFF,
143 const MapVector<StringAttr, ModuleLoweringState> &moduleLoweringStates)
144 : OpConversionPattern<OpTy>(typeConverter, context),
145 lowerToAlwaysFF(lowerToAlwaysFF),
146 moduleLoweringStates(moduleLoweringStates) {}
147
148 using OpAdaptor = typename OpConversionPattern<OpTy>::OpAdaptor;
149
150 LogicalResult
151 matchAndRewrite(OpTy reg, OpAdaptor adaptor,
152 ConversionPatternRewriter &rewriter) const final {
153 Location loc = reg.getLoc();
154
155 auto regTy =
156 ConversionPattern::getTypeConverter()->convertType(reg.getType());
157 auto svReg = sv::RegOp::create(rewriter, loc, regTy, reg.getNameAttr(),
158 reg.getInnerSymAttr());
159
160 svReg->setDialectAttrs(reg->getDialectAttrs());
161
163
164 auto regVal = sv::ReadInOutOp::create(rewriter, loc, svReg);
165
166 auto assignValue = [&] {
167 createAssign(rewriter, reg.getLoc(), svReg, reg);
168 };
169 auto assignReset = [&] {
170 sv::PAssignOp::create(rewriter, loc, svReg, adaptor.getResetValue());
171 };
172
173 // Registers written in an `always_ff` process may not have any assignments
174 // outside of that process.
175 // For some tools this also prohibits inititalization.
176 bool mayLowerToAlwaysFF = lowerToAlwaysFF && !reg.getInitialValue();
177
178 if (adaptor.getReset() && adaptor.getResetValue()) {
179 if (mayLowerToAlwaysFF) {
180 sv::AlwaysFFOp::create(rewriter, loc, sv::EventControl::AtPosEdge,
181 adaptor.getClk(), sv::ResetType::SyncReset,
182 sv::EventControl::AtPosEdge, adaptor.getReset(),
183 assignValue, assignReset);
184 } else {
185 sv::AlwaysOp::create(
186 rewriter, loc, sv::EventControl::AtPosEdge, adaptor.getClk(), [&] {
187 sv::IfOp::create(rewriter, loc, adaptor.getReset(), assignReset,
188 assignValue);
189 });
190 }
191 } else {
192 if (mayLowerToAlwaysFF) {
193 sv::AlwaysFFOp::create(rewriter, loc, sv::EventControl::AtPosEdge,
194 adaptor.getClk(), assignValue);
195 } else {
196 sv::AlwaysOp::create(rewriter, loc, sv::EventControl::AtPosEdge,
197 adaptor.getClk(), assignValue);
198 }
199 }
200
201 // Lower initial values.
202 if (auto init = reg.getInitialValue()) {
203 auto module = reg->template getParentOfType<hw::HWModuleOp>();
204 const auto &initial =
205 moduleLoweringStates.find(module.getModuleNameAttr())
206 ->second.immutableValueLowering;
207
208 Value initialValue = initial.lookupImmutableValue(init);
209
210 if (auto op = initialValue.getDefiningOp();
211 op && op->hasTrait<mlir::OpTrait::ConstantLike>()) {
212 auto clonedConstant = rewriter.clone(*op);
213 rewriter.moveOpBefore(clonedConstant, svReg);
214 svReg.getInitMutable().assign(clonedConstant->getResult(0));
215 } else {
216 OpBuilder::InsertionGuard guard(rewriter);
217 auto in = initial.getSVInitial();
218 rewriter.setInsertionPointToEnd(in.getBodyBlock());
219 sv::BPAssignOp::create(rewriter, reg->getLoc(), svReg, initialValue);
220 }
221 }
222
223 rewriter.replaceOp(reg, regVal);
224 return success();
225 }
226
227 // Helper to create an assignment based on the register type.
228 void createAssign(ConversionPatternRewriter &rewriter, Location loc,
229 sv::RegOp svReg, OpAdaptor reg) const;
230
231private:
232 bool lowerToAlwaysFF;
233 const MapVector<StringAttr, ModuleLoweringState> &moduleLoweringStates;
234};
235
236/// Create the assign.
237template <>
238void CompRegLower<CompRegOp>::createAssign(ConversionPatternRewriter &rewriter,
239 Location loc, sv::RegOp svReg,
240 OpAdaptor reg) const {
241 sv::PAssignOp::create(rewriter, loc, svReg, reg.getInput());
242}
243/// Create the assign inside of an if block.
244template <>
245void CompRegLower<CompRegClockEnabledOp>::createAssign(
246 ConversionPatternRewriter &rewriter, Location loc, sv::RegOp svReg,
247 OpAdaptor reg) const {
248 sv::IfOp::create(rewriter, loc, reg.getClockEnable(), [&]() {
249 sv::PAssignOp::create(rewriter, loc, svReg, reg.getInput());
250 });
251}
252
253/// Lower FromImmutable to `sv.reg` and `sv.initial`.
254class FromImmutableLowering : public OpConversionPattern<FromImmutableOp> {
255public:
256 FromImmutableLowering(
257 TypeConverter &typeConverter, MLIRContext *context,
258 const MapVector<StringAttr, ModuleLoweringState> &moduleLoweringStates)
259 : OpConversionPattern<FromImmutableOp>(typeConverter, context),
260 moduleLoweringStates(moduleLoweringStates) {}
261
262 using OpAdaptor = typename OpConversionPattern<FromImmutableOp>::OpAdaptor;
263
264 LogicalResult
265 matchAndRewrite(FromImmutableOp fromImmutableOp, OpAdaptor adaptor,
266 ConversionPatternRewriter &rewriter) const final {
267 Location loc = fromImmutableOp.getLoc();
268
269 auto regTy = ConversionPattern::getTypeConverter()->convertType(
270 fromImmutableOp.getType());
271 auto svReg = sv::RegOp::create(rewriter, loc, regTy);
272
273 auto regVal = sv::ReadInOutOp::create(rewriter, loc, svReg);
274
275 // Lower initial values.
276 auto module = fromImmutableOp->template getParentOfType<hw::HWModuleOp>();
277 const auto &initial = moduleLoweringStates.find(module.getModuleNameAttr())
278 ->second.immutableValueLowering;
279
280 Value initialValue =
281 initial.lookupImmutableValue(fromImmutableOp.getInput());
282
283 OpBuilder::InsertionGuard guard(rewriter);
284 auto in = initial.getSVInitial();
285 rewriter.setInsertionPointToEnd(in.getBodyBlock());
286 sv::BPAssignOp::create(rewriter, fromImmutableOp->getLoc(), svReg,
287 initialValue);
288
289 rewriter.replaceOp(fromImmutableOp, regVal);
290 return success();
291 }
292
293private:
294 const MapVector<StringAttr, ModuleLoweringState> &moduleLoweringStates;
295};
296// Lower seq.clock_gate to a fairly standard clock gate implementation.
297//
298class ClockGateLowering : public OpConversionPattern<ClockGateOp> {
299public:
301 using OpAdaptor = typename OpConversionPattern<ClockGateOp>::OpAdaptor;
302 LogicalResult
303 matchAndRewrite(ClockGateOp clockGate, OpAdaptor adaptor,
304 ConversionPatternRewriter &rewriter) const final {
305 auto loc = clockGate.getLoc();
306 Value clk = adaptor.getInput();
307
308 // enable in
309 Value enable = adaptor.getEnable();
310 if (auto te = adaptor.getTestEnable())
311 enable = comb::OrOp::create(rewriter, loc, enable, te);
312
313 // Enable latch.
314 Value enableLatch =
315 sv::RegOp::create(rewriter, loc, rewriter.getI1Type(),
316 rewriter.getStringAttr("cg_en_latch"));
317
318 // Latch the enable signal using an always @* block.
319 sv::AlwaysOp::create(
320 rewriter, loc, llvm::SmallVector<sv::EventControl>{},
321 llvm::SmallVector<Value>{}, [&]() {
322 sv::IfOp::create(
323 rewriter, loc, comb::createOrFoldNot(rewriter, loc, clk), [&]() {
324 sv::PAssignOp::create(rewriter, loc, enableLatch, enable);
325 });
326 });
327
328 // Create the gated clock signal.
329 rewriter.replaceOpWithNewOp<comb::AndOp>(
330 clockGate, clk, sv::ReadInOutOp::create(rewriter, loc, enableLatch));
331 return success();
332 }
333};
334
335// Lower seq.clock_inv to a regular inverter.
336//
337class ClockInverterLowering : public OpConversionPattern<ClockInverterOp> {
338public:
339 using OpConversionPattern<ClockInverterOp>::OpConversionPattern;
340
341 LogicalResult
342 matchAndRewrite(ClockInverterOp op, OpAdaptor adaptor,
343 ConversionPatternRewriter &rewriter) const final {
344 auto loc = op.getLoc();
345 Value clk = adaptor.getInput();
346
347 StringAttr name = op->getAttrOfType<StringAttr>("sv.namehint");
348 Value one = hw::ConstantOp::create(rewriter, loc, APInt(1, 1));
349 auto newOp = rewriter.replaceOpWithNewOp<comb::XorOp>(op, clk, one);
350 if (name)
351 rewriter.modifyOpInPlace(newOp,
352 [&] { newOp->setAttr("sv.namehint", name); });
353 return success();
354 }
355};
356
357// Lower seq.clock_mux to a `comb.mux` op
358//
359class ClockMuxLowering : public OpConversionPattern<ClockMuxOp> {
360public:
362 using OpConversionPattern<ClockMuxOp>::OpAdaptor;
363
364 LogicalResult
365 matchAndRewrite(ClockMuxOp clockMux, OpAdaptor adaptor,
366 ConversionPatternRewriter &rewriter) const final {
367 rewriter.replaceOpWithNewOp<comb::MuxOp>(clockMux, adaptor.getCond(),
368 adaptor.getTrueClock(),
369 adaptor.getFalseClock(), true);
370 return success();
371 }
372};
373
374/// Map `seq.clock` to `i1`.
375struct SeqToSVTypeConverter : public TypeConverter {
376 SeqToSVTypeConverter() {
377 addConversion([&](Type type) { return type; });
378 addConversion([&](seq::ImmutableType type) { return type.getInnerType(); });
379 addConversion([&](seq::ClockType type) {
380 return IntegerType::get(type.getContext(), 1);
381 });
382 addConversion([&](hw::StructType structTy) {
383 bool changed = false;
384
385 SmallVector<hw::StructType::FieldInfo> newFields;
386 for (auto field : structTy.getElements()) {
387 auto &newField = newFields.emplace_back();
388 newField.name = field.name;
389 newField.type = convertType(field.type);
390 if (field.type != newField.type)
391 changed = true;
392 }
393
394 if (!changed)
395 return structTy;
396
397 return hw::StructType::get(structTy.getContext(), newFields);
398 });
399 addConversion([&](hw::ArrayType arrayTy) {
400 auto elementTy = arrayTy.getElementType();
401 auto newElementTy = convertType(elementTy);
402 if (elementTy != newElementTy)
403 return hw::ArrayType::get(newElementTy, arrayTy.getNumElements());
404 return arrayTy;
405 });
406
407 addTargetMaterialization([&](mlir::OpBuilder &builder,
408 mlir::Type resultType, mlir::ValueRange inputs,
409 mlir::Location loc) -> mlir::Value {
410 if (inputs.size() != 1)
411 return Value();
412 return mlir::UnrealizedConversionCastOp::create(builder, loc, resultType,
413 inputs[0])
414 ->getResult(0);
415 });
416
417 addSourceMaterialization([&](mlir::OpBuilder &builder,
418 mlir::Type resultType, mlir::ValueRange inputs,
419 mlir::Location loc) -> mlir::Value {
420 if (inputs.size() != 1)
421 return Value();
422 return mlir::UnrealizedConversionCastOp::create(builder, loc, resultType,
423 inputs[0])
424 ->getResult(0);
425 });
426 }
427};
428
429/// Eliminate no-op clock casts.
430template <typename T>
431class ClockCastLowering : public OpConversionPattern<T> {
432public:
434
435 LogicalResult
436 matchAndRewrite(T op, typename T::Adaptor adaptor,
437 ConversionPatternRewriter &rewriter) const final {
438 // If the cast had a better name than its input, propagate it.
439 if (Operation *inputOp = adaptor.getInput().getDefiningOp())
440 if (!isa<mlir::UnrealizedConversionCastOp>(inputOp))
441 if (auto name = chooseName(op, inputOp))
442 rewriter.modifyOpInPlace(
443 inputOp, [&] { inputOp->setAttr("sv.namehint", name); });
444
445 rewriter.replaceOp(op, adaptor.getInput());
446 return success();
447 }
448};
449
450// Lower seq.const_clock to `hw.constant`
451//
452class ClockConstLowering : public OpConversionPattern<ConstClockOp> {
453public:
455 using OpConversionPattern<ConstClockOp>::OpAdaptor;
456
457 LogicalResult
458 matchAndRewrite(ConstClockOp clockConst, OpAdaptor adaptor,
459 ConversionPatternRewriter &rewriter) const final {
460 rewriter.replaceOpWithNewOp<hw::ConstantOp>(
461 clockConst, APInt(1, clockConst.getValue() == ClockConst::High));
462 return success();
463 }
464};
465
466class AggregateConstantPattern
467 : public OpConversionPattern<hw::AggregateConstantOp> {
468public:
469 using OpConversionPattern<hw::AggregateConstantOp>::OpConversionPattern;
470 using OpConversionPattern<hw::AggregateConstantOp>::OpAdaptor;
471
472 LogicalResult
473 matchAndRewrite(hw::AggregateConstantOp aggregateConstant, OpAdaptor adaptor,
474 ConversionPatternRewriter &rewriter) const final {
475 auto newType = typeConverter->convertType(aggregateConstant.getType());
476 auto newAttr = aggregateConstant.getFieldsAttr().replace(
477 [](seq::ClockConstAttr clockConst) {
478 return mlir::IntegerAttr::get(
479 mlir::IntegerType::get(clockConst.getContext(), 1),
480 APInt(1, clockConst.getValue() == ClockConst::High));
481 });
482 rewriter.replaceOpWithNewOp<hw::AggregateConstantOp>(
483 aggregateConstant, newType, cast<ArrayAttr>(newAttr));
484 return success();
485 }
486};
487
488/// Lower `seq.clock_div` to a behavioural clock divider
489///
490class ClockDividerLowering : public OpConversionPattern<ClockDividerOp> {
491public:
492 using OpConversionPattern<ClockDividerOp>::OpConversionPattern;
493 using OpConversionPattern<ClockDividerOp>::OpAdaptor;
494
495 LogicalResult
496 matchAndRewrite(ClockDividerOp clockDiv, OpAdaptor adaptor,
497 ConversionPatternRewriter &rewriter) const final {
498 Location loc = clockDiv.getLoc();
499
500 Value one;
501 if (clockDiv.getPow2()) {
502 one = hw::ConstantOp::create(rewriter, loc, APInt(1, 1));
503 }
504
505 Value output = clockDiv.getInput();
506
507 SmallVector<Value> regs;
508 for (unsigned i = 0; i < clockDiv.getPow2(); ++i) {
509 Value reg = sv::RegOp::create(
510 rewriter, loc, rewriter.getI1Type(),
511 rewriter.getStringAttr("clock_out_" + std::to_string(i)));
512 regs.push_back(reg);
513
514 sv::AlwaysOp::create(
515 rewriter, loc, sv::EventControl::AtPosEdge, output, [&] {
516 Value outputVal = sv::ReadInOutOp::create(rewriter, loc, reg);
517 Value inverted = comb::XorOp::create(rewriter, loc, outputVal, one);
518 sv::BPAssignOp::create(rewriter, loc, reg, inverted);
519 });
520
521 output = sv::ReadInOutOp::create(rewriter, loc, reg);
522 }
523
524 if (!regs.empty()) {
525 Value zero = hw::ConstantOp::create(rewriter, loc, APInt(1, 0));
526 sv::InitialOp::create(rewriter, loc, [&] {
527 for (Value reg : regs) {
528 sv::BPAssignOp::create(rewriter, loc, reg, zero);
529 }
530 });
531 }
532
533 rewriter.replaceOp(clockDiv, output);
534 return success();
535 }
536};
537
538} // namespace
539
540// NOLINTBEGIN(misc-no-recursion)
541static bool isLegalType(Type ty) {
542 if (hw::type_isa<ClockType>(ty))
543 return false;
544
545 if (auto arrayTy = hw::type_dyn_cast<hw::ArrayType>(ty))
546 return isLegalType(arrayTy.getElementType());
547
548 if (auto structTy = hw::type_dyn_cast<hw::StructType>(ty)) {
549 for (auto field : structTy.getElements())
550 if (!isLegalType(field.type))
551 return false;
552 return true;
553 }
554
555 return true;
556}
557// NOLINTEND(misc-no-recursion)
558
559static bool isLegalOp(Operation *op) {
560 if (auto module = dyn_cast<hw::HWModuleLike>(op)) {
561 for (auto port : module.getHWModuleType().getPorts())
562 if (!isLegalType(port.type))
563 return false;
564 return true;
565 }
566
567 if (auto hwAggregateConstantOp = dyn_cast<hw::AggregateConstantOp>(op)) {
568 bool foundClockAttr = false;
569 hwAggregateConstantOp.getFieldsAttr().walk(
570 [&](seq::ClockConstAttr attr) { foundClockAttr = true; });
571 if (foundClockAttr)
572 return false;
573 }
574
575 bool allOperandsLowered = llvm::all_of(
576 op->getOperands(), [](auto op) { return isLegalType(op.getType()); });
577 bool allResultsLowered = llvm::all_of(op->getResults(), [](auto result) {
578 return isLegalType(result.getType());
579 });
580 return allOperandsLowered && allResultsLowered;
581}
582
584 auto circuit = getOperation();
585 MLIRContext *context = &getContext();
586
587 auto modules = llvm::to_vector(circuit.getOps<HWModuleOp>());
588
589 FirMemLowering memLowering(circuit);
590
591 // Identify memories and group them by module.
592 auto uniqueMems = memLowering.collectMemories(modules);
594 SmallVector<HWModuleGeneratedOp> generatedModules;
595 for (auto &[config, memOps] : uniqueMems) {
596 // Create the `HWModuleGeneratedOp`s for each unique configuration.
597 auto genOp = memLowering.createMemoryModule(config, memOps);
598 generatedModules.push_back(genOp);
599
600 // Group memories by their parent module for parallelism.
601 for (auto memOp : memOps) {
602 auto parent = memOp->getParentOfType<HWModuleOp>();
603 memsByModule[parent].emplace_back(&config, genOp, memOp);
604 }
605 }
606
607 // Any register that is "buried" inside an ifdef, will need a hierpath for
608 // building the initialization/randomization IR. Do that here. This path
609 // table will be used by the per-module FirRegLowering routine. This is done
610 // single-threaded to ensure symbol names are deterministic.
611 auto pathTable = FirRegLowering::createPaths(circuit);
612
613 // Lower memories and registers in modules in parallel.
614 std::atomic<bool> needsRegRandomization = false;
615 std::atomic<bool> needsMemRandomization = false;
616
618 for (auto module : circuit.getOps<HWModuleOp>())
619 moduleLoweringStates.try_emplace(module.getModuleNameAttr(),
620 ModuleLoweringState(module));
621
622 auto result = mlir::failableParallelForEach(
623 &getContext(), moduleLoweringStates, [&](auto &moduleAndState) {
624 auto &state = moduleAndState.second;
625 auto module = state.module;
626 SeqToSVTypeConverter typeConverter;
627 FirRegLowering regLowering(
628 typeConverter, module, pathTable, disableRegRandomization,
629 emitSeparateAlwaysBlocks, emitPresetAsInlineInit);
630 regLowering.lower();
631 if (regLowering.needsRegRandomization()) {
632 if (!disableRegRandomization) {
633 state.fragment.needsRegFragment = true;
634 }
635 needsRegRandomization = true;
636 }
637 numSubaccessRestored += regLowering.numSubaccessRestored;
638
639 if (auto *it = memsByModule.find(module); it != memsByModule.end()) {
640 memLowering.lowerMemoriesInModule(module, it->second);
641 // Generated memories need register randomization since `HWMemSimImpl`
642 // may add registers.
643 needsMemRandomization = true;
644 needsRegRandomization = true;
645 }
646 return state.immutableValueLowering.lower();
647 });
648
649 if (failed(result))
650 return signalPassFailure();
651
652 auto randomInitFragmentName =
653 FlatSymbolRefAttr::get(context, "RANDOM_INIT_FRAGMENT");
654 auto randomInitRegFragmentName =
655 FlatSymbolRefAttr::get(context, "RANDOM_INIT_REG_FRAGMENT");
656 auto randomInitMemFragmentName =
657 FlatSymbolRefAttr::get(context, "RANDOM_INIT_MEM_FRAGMENT");
658
659 for (auto &[_, state] : moduleLoweringStates) {
660 const auto &info = state.fragment;
661 // Do not add fragments if not needed.
662 if (!info.needsRegFragment) {
663 continue;
664 }
665
666 SmallVector<Attribute> fragmentAttrs;
667 auto module = state.module;
668 if (auto others =
669 module->getAttrOfType<ArrayAttr>(emit::getFragmentsAttrName()))
670 fragmentAttrs = llvm::to_vector(others);
671
672 if (info.needsRegFragment) {
673 fragmentAttrs.push_back(randomInitRegFragmentName);
674 fragmentAttrs.push_back(randomInitFragmentName);
675 }
676
677 module->setAttr(emit::getFragmentsAttrName(),
678 ArrayAttr::get(context, fragmentAttrs));
679 }
680
681 // Set fragments for generated modules.
682 SmallVector<Attribute> genModFragments;
683 if (!disableRegRandomization)
684 genModFragments.push_back(randomInitRegFragmentName);
685 if (!disableMemRandomization)
686 genModFragments.push_back(randomInitMemFragmentName);
687 if (!genModFragments.empty()) {
688 genModFragments.push_back(randomInitFragmentName);
689 auto fragmentAttr = ArrayAttr::get(context, genModFragments);
690 for (auto genOp : generatedModules)
691 genOp->setAttr(emit::getFragmentsAttrName(), fragmentAttr);
692 }
693
694 // Mark all ops which can have clock types as illegal.
695 SeqToSVTypeConverter typeConverter;
696 ConversionTarget target(*context);
697 target.addIllegalDialect<SeqDialect>();
698 target.markUnknownOpDynamicallyLegal(isLegalOp);
699
700 RewritePatternSet patterns(context);
701 patterns.add<CompRegLower<CompRegOp>>(typeConverter, context, lowerToAlwaysFF,
702 moduleLoweringStates);
703 patterns.add<CompRegLower<CompRegClockEnabledOp>>(
704 typeConverter, context, lowerToAlwaysFF, moduleLoweringStates);
705 patterns.add<FromImmutableLowering>(typeConverter, context,
706 moduleLoweringStates);
707 patterns.add<ClockCastLowering<seq::FromClockOp>>(typeConverter, context);
708 patterns.add<ClockCastLowering<seq::ToClockOp>>(typeConverter, context);
709 patterns.add<ClockGateLowering>(typeConverter, context);
710 patterns.add<ClockInverterLowering>(typeConverter, context);
711 patterns.add<ClockMuxLowering>(typeConverter, context);
712 patterns.add<ClockDividerLowering>(typeConverter, context);
713 patterns.add<ClockConstLowering>(typeConverter, context);
714 patterns.add<TypeConversionPattern>(typeConverter, context);
715 patterns.add<AggregateConstantPattern>(typeConverter, context);
716
717 if (failed(applyPartialConversion(circuit, target, std::move(patterns))))
718 signalPassFailure();
719
720 auto loc = UnknownLoc::get(context);
721 auto b = ImplicitLocOpBuilder::atBlockBegin(loc, circuit.getBody());
722 if (needsRegRandomization || needsMemRandomization) {
723 sv::MacroDeclOp::create(b, "ENABLE_INITIAL_REG_");
724 sv::MacroDeclOp::create(b, "ENABLE_INITIAL_MEM_");
725 if (needsRegRandomization) {
726 sv::MacroDeclOp::create(b, "FIRRTL_BEFORE_INITIAL");
727 sv::MacroDeclOp::create(b, "FIRRTL_AFTER_INITIAL");
728 }
729 if (needsMemRandomization)
730 sv::MacroDeclOp::create(b, "RANDOMIZE_MEM_INIT");
731 sv::MacroDeclOp::create(b, "RANDOMIZE_REG_INIT");
732 sv::MacroDeclOp::create(b, "RANDOMIZE");
733 sv::MacroDeclOp::create(b, "RANDOMIZE_DELAY");
734 sv::MacroDeclOp::create(b, "RANDOM");
735 sv::MacroDeclOp::create(b, "INIT_RANDOM");
736 sv::MacroDeclOp::create(b, "INIT_RANDOM_PROLOG_");
737 }
738
739 bool hasRegRandomization = needsRegRandomization && !disableRegRandomization;
740 bool hasMemRandomization = needsMemRandomization && !disableMemRandomization;
741 if (!hasRegRandomization && !hasMemRandomization)
742 return;
743
744 // Build macros for FIRRTL-style register and memory initialization.
745 // Insert them at the start of the module, after any other verbatims.
746 for (Operation &op : *circuit.getBody()) {
747 if (!isa<sv::VerbatimOp, sv::IfDefOp>(&op)) {
748 b.setInsertionPoint(&op);
749 break;
750 }
751 }
752
753 // Create SYNTHESIS/VERILATOR macros if other passes have not done so already.
754 {
755 StringSet<> symbols;
756 for (auto sym : circuit.getOps<sv::MacroDeclOp>())
757 symbols.insert(sym.getName());
758 if (!symbols.count("SYNTHESIS"))
759 sv::MacroDeclOp::create(b, "SYNTHESIS");
760 if (!symbols.count("VERILATOR"))
761 sv::MacroDeclOp::create(b, "VERILATOR");
762 }
763
764 // TODO: We could have an operation for macros and uses of them, and
765 // even turn them into symbols so we can DCE unused macro definitions.
766 auto emitGuardedDefine = [&](StringRef guard, StringRef defName,
767 StringRef defineTrue = "",
768 StringRef defineFalse = StringRef()) {
769 if (!defineFalse.data()) {
770 assert(defineTrue.data() && "didn't define anything");
771 sv::IfDefOp::create(
772 b, guard, [&]() { sv::MacroDefOp::create(b, defName, defineTrue); });
773 } else {
774 sv::IfDefOp::create(
775 b, guard,
776 [&]() {
777 if (defineTrue.data())
778 sv::MacroDefOp::create(b, defName, defineTrue);
779 },
780 [&]() { sv::MacroDefOp::create(b, defName, defineFalse); });
781 }
782 };
783
784 // Helper function to emit #ifndef guard.
785 auto emitGuard = [&](const char *guard, llvm::function_ref<void(void)> body) {
786 sv::IfDefOp::create(
787 b, guard, []() {}, body);
788 };
789
790 emit::FragmentOp::create(b, randomInitFragmentName.getAttr(), [&] {
791 sv::VerbatimOp::create(b,
792 "// Standard header to adapt well known macros for "
793 "register randomization.");
794
795 sv::VerbatimOp::create(
796 b, "\n// RANDOM may be set to an expression that produces a 32-bit "
797 "random unsigned value.");
798 emitGuardedDefine("RANDOM", "RANDOM", StringRef(), "$random");
799
800 sv::VerbatimOp::create(
801 b, "\n// Users can define INIT_RANDOM as general code that gets "
802 "injected "
803 "into the\n// initializer block for modules with registers.");
804 emitGuardedDefine("INIT_RANDOM", "INIT_RANDOM", StringRef(), "");
805
806 sv::VerbatimOp::create(
807 b, "\n// If using random initialization, you can also define "
808 "RANDOMIZE_DELAY to\n// customize the delay used, otherwise 0.002 "
809 "is used.");
810 emitGuardedDefine("RANDOMIZE_DELAY", "RANDOMIZE_DELAY", StringRef(),
811 "0.002");
812
813 sv::VerbatimOp::create(
814 b, "\n// Define INIT_RANDOM_PROLOG_ for use in our modules below.");
815 emitGuard("INIT_RANDOM_PROLOG_", [&]() {
816 sv::IfDefOp::create(
817 b, "RANDOMIZE",
818 [&]() {
819 emitGuardedDefine("VERILATOR", "INIT_RANDOM_PROLOG_",
820 "`INIT_RANDOM",
821 "`INIT_RANDOM #`RANDOMIZE_DELAY begin end");
822 },
823 [&]() { sv::MacroDefOp::create(b, "INIT_RANDOM_PROLOG_", ""); });
824 });
825 });
826
827 if (hasMemRandomization) {
828 emit::FragmentOp::create(b, randomInitMemFragmentName.getAttr(), [&] {
829 sv::VerbatimOp::create(b, "\n// Include rmemory initializers in init "
830 "blocks unless synthesis is set");
831 emitGuard("RANDOMIZE", [&]() {
832 emitGuardedDefine("RANDOMIZE_MEM_INIT", "RANDOMIZE");
833 });
834 emitGuard("SYNTHESIS", [&] {
835 emitGuardedDefine("ENABLE_INITIAL_MEM_", "ENABLE_INITIAL_MEM_",
836 StringRef(), "");
837 });
838 sv::VerbatimOp::create(b, "");
839 });
840 }
841
842 if (hasRegRandomization) {
843 emit::FragmentOp::create(b, randomInitRegFragmentName.getAttr(), [&] {
844 sv::VerbatimOp::create(b, "\n// Include register initializers in init "
845 "blocks unless synthesis is set");
846 emitGuard("RANDOMIZE", [&]() {
847 emitGuardedDefine("RANDOMIZE_REG_INIT", "RANDOMIZE");
848 });
849 emitGuard("SYNTHESIS", [&] {
850 emitGuardedDefine("ENABLE_INITIAL_REG_", "ENABLE_INITIAL_REG_",
851 StringRef(), "");
852 });
853 sv::VerbatimOp::create(b, "");
854 });
855 }
856}
857
858std::unique_ptr<Pass>
859circt::createLowerSeqToSVPass(const LowerSeqToSVOptions &options) {
860 return std::make_unique<SeqToSVPass>(options);
861}
assert(baseType &&"element must be base type")
static bool isLegalOp(Operation *op)
Returns true if the given op is considered as legal - i.e.
Definition DCToHW.cpp:844
static std::unique_ptr< Context > context
static FIRRTLBaseType convertType(FIRRTLBaseType type)
Returns null type if no conversion is needed.
Definition DropConst.cpp:32
static bool isLegalType(Type ty)
Definition SeqToSV.cpp:541
static bool isLegalOp(Operation *op)
Definition SeqToSV.cpp:559
FIR memory lowering helper.
UniqueConfigs collectMemories(ArrayRef< hw::HWModuleOp > modules)
Groups memories by their kind from the whole design.
void lowerMemoriesInModule(hw::HWModuleOp module, ArrayRef< MemoryConfig > mems)
Lowers a group of memories from the same module.
hw::HWModuleGeneratedOp createMemoryModule(FirMemConfig &mem, ArrayRef< seq::FirMemOp > memOps)
Creates the generated module for a given configuration.
Lower FirRegOp to sv.reg and sv.always.
bool needsRegRandomization() const
static PathTable createPaths(mlir::ModuleOp top)
When a register is buried under an ifdef op, the initialization code at the footer of the HW module w...
create(data_type, value)
Definition hw.py:433
create(value)
Definition sv.py:108
Definition sv.py:70
StringRef getFragmentsAttrName()
Return the name of the fragments array attribute.
Definition EmitOps.h:30
FailureOr< seq::InitialOp > mergeInitialOps(Block *block)
Definition SeqOps.cpp:1220
mlir::ArrayAttr getSVAttributes(mlir::Operation *op)
Return all the SV attributes of an operation, or null if there are none.
void setSVAttributes(mlir::Operation *op, mlir::ArrayAttr attrs)
Set the SV attributes of an operation.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createLowerSeqToSVPass(const LowerSeqToSVOptions &options={})
Definition SeqToSV.cpp:859
StringRef chooseName(StringRef a, StringRef b)
Choose a good name for an item from two options.
Definition Naming.cpp:47
Definition seq.py:1
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21
void runOnOperation() override
Definition SeqToSV.cpp:583
Generic pattern which replaces an operation by one of the same operation name, but with converted att...