CIRCT  18.0.0git
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 "../PassDetail.h"
15 #include "FirMemLowering.h"
16 #include "FirRegLowering.h"
20 #include "circt/Dialect/HW/HWOps.h"
23 #include "circt/Dialect/SV/SVOps.h"
25 #include "mlir/IR/Builders.h"
26 #include "mlir/IR/DialectImplementation.h"
27 #include "mlir/IR/ImplicitLocOpBuilder.h"
28 #include "mlir/IR/Threading.h"
29 #include "mlir/Pass/Pass.h"
30 #include "mlir/Transforms/DialectConversion.h"
31 #include "llvm/ADT/IntervalMap.h"
32 #include "llvm/ADT/TypeSwitch.h"
33 
34 #define DEBUG_TYPE "lower-seq-to-sv"
35 
36 using namespace circt;
37 using namespace seq;
38 using hw::HWModuleOp;
39 using llvm::MapVector;
40 
41 namespace {
42 #define GEN_PASS_DEF_LOWERSEQTOSV
43 #include "circt/Conversion/Passes.h.inc"
44 
45 struct SeqToSVPass : public impl::LowerSeqToSVBase<SeqToSVPass> {
46  void runOnOperation() override;
47  using LowerSeqToSVBase<SeqToSVPass>::lowerToAlwaysFF;
48  using LowerSeqToSVBase<SeqToSVPass>::disableRegRandomization;
49  using LowerSeqToSVBase<SeqToSVPass>::emitSeparateAlwaysBlocks;
50  using LowerSeqToSVBase<SeqToSVPass>::LowerSeqToSVBase;
51  using LowerSeqToSVBase<SeqToSVPass>::numSubaccessRestored;
52 };
53 } // anonymous namespace
54 
55 namespace {
56 /// Lower CompRegOp to `sv.reg` and `sv.alwaysff`. Use a posedge clock and
57 /// synchronous reset.
58 template <typename OpTy>
59 class CompRegLower : public OpConversionPattern<OpTy> {
60 public:
61  CompRegLower(TypeConverter &typeConverter, MLIRContext *context,
62  bool lowerToAlwaysFF)
63  : OpConversionPattern<OpTy>(typeConverter, context),
64  lowerToAlwaysFF(lowerToAlwaysFF) {}
65 
66  using OpAdaptor = typename OpConversionPattern<OpTy>::OpAdaptor;
67 
68  LogicalResult
69  matchAndRewrite(OpTy reg, OpAdaptor adaptor,
70  ConversionPatternRewriter &rewriter) const final {
71  Location loc = reg.getLoc();
72 
73  auto regTy =
74  ConversionPattern::getTypeConverter()->convertType(reg.getType());
75 
76  auto svReg = rewriter.create<sv::RegOp>(loc, regTy, reg.getNameAttr(),
77  reg.getInnerSymAttr(),
78  reg.getPowerOnValue());
79  svReg->setDialectAttrs(reg->getDialectAttrs());
80 
82 
83  auto regVal = rewriter.create<sv::ReadInOutOp>(loc, svReg);
84 
85  auto assignValue = [&] {
86  createAssign(rewriter, reg.getLoc(), svReg, reg);
87  };
88  auto assignReset = [&] {
89  rewriter.create<sv::PAssignOp>(loc, svReg, adaptor.getResetValue());
90  };
91 
92  if (adaptor.getReset() && adaptor.getResetValue()) {
93  if (lowerToAlwaysFF) {
94  rewriter.create<sv::AlwaysFFOp>(
95  loc, sv::EventControl::AtPosEdge, adaptor.getClk(),
96  ResetType::SyncReset, sv::EventControl::AtPosEdge,
97  adaptor.getReset(), assignValue, assignReset);
98  } else {
99  rewriter.create<sv::AlwaysOp>(
100  loc, sv::EventControl::AtPosEdge, adaptor.getClk(), [&] {
101  rewriter.create<sv::IfOp>(loc, adaptor.getReset(), assignReset,
102  assignValue);
103  });
104  }
105  } else {
106  if (lowerToAlwaysFF) {
107  rewriter.create<sv::AlwaysFFOp>(loc, sv::EventControl::AtPosEdge,
108  adaptor.getClk(), assignValue);
109  } else {
110  rewriter.create<sv::AlwaysOp>(loc, sv::EventControl::AtPosEdge,
111  adaptor.getClk(), assignValue);
112  }
113  }
114 
115  rewriter.replaceOp(reg, regVal);
116  return success();
117  }
118 
119  // Helper to create an assignment based on the register type.
120  void createAssign(ConversionPatternRewriter &rewriter, Location loc,
121  sv::RegOp svReg, OpAdaptor reg) const;
122 
123 private:
124  bool lowerToAlwaysFF;
125 };
126 
127 /// Create the assign.
128 template <>
129 void CompRegLower<CompRegOp>::createAssign(ConversionPatternRewriter &rewriter,
130  Location loc, sv::RegOp svReg,
131  OpAdaptor reg) const {
132  rewriter.create<sv::PAssignOp>(loc, svReg, reg.getInput());
133 }
134 /// Create the assign inside of an if block.
135 template <>
136 void CompRegLower<CompRegClockEnabledOp>::createAssign(
137  ConversionPatternRewriter &rewriter, Location loc, sv::RegOp svReg,
138  OpAdaptor reg) const {
139  rewriter.create<sv::IfOp>(loc, reg.getClockEnable(), [&]() {
140  rewriter.create<sv::PAssignOp>(loc, svReg, reg.getInput());
141  });
142 }
143 
144 // Lower seq.clock_gate to a fairly standard clock gate implementation.
145 //
146 class ClockGateLowering : public OpConversionPattern<ClockGateOp> {
147 public:
149  using OpAdaptor = typename OpConversionPattern<ClockGateOp>::OpAdaptor;
150  LogicalResult
151  matchAndRewrite(ClockGateOp clockGate, OpAdaptor adaptor,
152  ConversionPatternRewriter &rewriter) const final {
153  auto loc = clockGate.getLoc();
154  Value clk = adaptor.getInput();
155 
156  // enable in
157  Value enable = adaptor.getEnable();
158  if (auto te = adaptor.getTestEnable())
159  enable = rewriter.create<comb::OrOp>(loc, enable, te);
160 
161  // Enable latch.
162  Value enableLatch = rewriter.create<sv::RegOp>(
163  loc, rewriter.getI1Type(), rewriter.getStringAttr("cg_en_latch"));
164 
165  // Latch the enable signal using an always @* block.
166  rewriter.create<sv::AlwaysOp>(
167  loc, llvm::SmallVector<sv::EventControl>{}, llvm::SmallVector<Value>{},
168  [&]() {
169  rewriter.create<sv::IfOp>(
170  loc, comb::createOrFoldNot(loc, clk, rewriter), [&]() {
171  rewriter.create<sv::PAssignOp>(loc, enableLatch, enable);
172  });
173  });
174 
175  // Create the gated clock signal.
176  Value gclk = rewriter.create<comb::AndOp>(
177  loc, clk, rewriter.create<sv::ReadInOutOp>(loc, enableLatch));
178  clockGate.replaceAllUsesWith(gclk);
179  rewriter.eraseOp(clockGate);
180  return success();
181  }
182 };
183 
184 // Lower seq.clock_mux to a `comb.mux` op
185 //
186 class ClockMuxLowering : public OpConversionPattern<ClockMuxOp> {
187 public:
190 
191  LogicalResult
192  matchAndRewrite(ClockMuxOp clockMux, OpAdaptor adaptor,
193  ConversionPatternRewriter &rewriter) const final {
194  rewriter.replaceOpWithNewOp<comb::MuxOp>(clockMux, adaptor.getCond(),
195  adaptor.getTrueClock(),
196  adaptor.getFalseClock(), true);
197  return success();
198  }
199 };
200 
201 /// Map `seq.clock` to `i1`.
202 struct SeqToSVTypeConverter : public TypeConverter {
203  SeqToSVTypeConverter() {
204  addConversion([&](Type type) { return type; });
205  addConversion([&](seq::ClockType type) {
206  return IntegerType::get(type.getContext(), 1);
207  });
208  addConversion([&](hw::StructType structTy) {
209  bool changed = false;
210 
211  SmallVector<hw::StructType::FieldInfo> newFields;
212  for (auto field : structTy.getElements()) {
213  auto &newField = newFields.emplace_back();
214  newField.name = field.name;
215  newField.type = convertType(field.type);
216  if (field.type != newField.type)
217  changed = true;
218  }
219 
220  if (!changed)
221  return structTy;
222 
223  return hw::StructType::get(structTy.getContext(), newFields);
224  });
225  addConversion([&](hw::ArrayType arrayTy) {
226  auto elementTy = arrayTy.getElementType();
227  auto newElementTy = convertType(elementTy);
228  if (elementTy != newElementTy)
229  return hw::ArrayType::get(newElementTy, arrayTy.getNumElements());
230  return arrayTy;
231  });
232 
233  addTargetMaterialization(
234  [&](mlir::OpBuilder &builder, mlir::Type resultType,
235  mlir::ValueRange inputs,
236  mlir::Location loc) -> std::optional<mlir::Value> {
237  if (inputs.size() != 1)
238  return std::nullopt;
239  return inputs[0];
240  });
241 
242  addSourceMaterialization(
243  [&](mlir::OpBuilder &builder, mlir::Type resultType,
244  mlir::ValueRange inputs,
245  mlir::Location loc) -> std::optional<mlir::Value> {
246  if (inputs.size() != 1)
247  return std::nullopt;
248  return inputs[0];
249  });
250  }
251 };
252 
253 /// Eliminate no-op clock casts.
254 template <typename T>
255 class ClockCastLowering : public OpConversionPattern<T> {
256 public:
258 
259  LogicalResult
260  matchAndRewrite(T op, typename T::Adaptor adaptor,
261  ConversionPatternRewriter &rewriter) const final {
262  rewriter.replaceOp(op, adaptor.getInput());
263  return success();
264  }
265 };
266 
267 // Lower seq.const_clock to `hw.constant`
268 //
269 class ClockConstLowering : public OpConversionPattern<ConstClockOp> {
270 public:
273 
274  LogicalResult
275  matchAndRewrite(ConstClockOp clockConst, OpAdaptor adaptor,
276  ConversionPatternRewriter &rewriter) const final {
277  rewriter.replaceOpWithNewOp<hw::ConstantOp>(
278  clockConst, APInt(1, clockConst.getValue() == ClockConst::High));
279  return success();
280  }
281 };
282 
283 /// Lower `seq.clock_div` to a behavioural clock divider
284 ///
285 class ClockDividerLowering : public OpConversionPattern<ClockDivider> {
286 public:
289 
290  LogicalResult
291  matchAndRewrite(ClockDivider clockDiv, OpAdaptor adaptor,
292  ConversionPatternRewriter &rewriter) const final {
293  Location loc = clockDiv.getLoc();
294 
295  Value one;
296  if (clockDiv.getPow2()) {
297  one = rewriter.create<hw::ConstantOp>(loc, APInt(1, 1));
298  }
299 
300  Value output = clockDiv.getClockIn();
301 
302  SmallVector<Value> regs;
303  for (unsigned i = 0; i < clockDiv.getPow2(); ++i) {
304  Value reg = rewriter.create<sv::RegOp>(
305  loc, rewriter.getI1Type(),
306  rewriter.getStringAttr("clock_out_" + std::to_string(i)));
307  regs.push_back(reg);
308 
309  rewriter.create<sv::AlwaysOp>(
310  loc, sv::EventControl::AtPosEdge, output, [&] {
311  Value outputVal = rewriter.create<sv::ReadInOutOp>(loc, reg);
312  Value inverted = rewriter.create<comb::XorOp>(loc, outputVal, one);
313  rewriter.create<sv::BPAssignOp>(loc, reg, inverted);
314  });
315 
316  output = rewriter.create<sv::ReadInOutOp>(loc, reg);
317  }
318 
319  if (!regs.empty()) {
320  Value zero = rewriter.create<hw::ConstantOp>(loc, APInt(1, 0));
321  rewriter.create<sv::InitialOp>(loc, [&] {
322  for (Value reg : regs) {
323  rewriter.create<sv::BPAssignOp>(loc, reg, zero);
324  }
325  });
326  }
327 
328  rewriter.replaceOp(clockDiv, output);
329  return success();
330  }
331 };
332 
333 } // namespace
334 
335 // NOLINTBEGIN(misc-no-recursion)
336 static bool isLegalType(Type ty) {
337  if (hw::type_isa<ClockType>(ty))
338  return false;
339 
340  if (auto arrayTy = hw::type_dyn_cast<hw::ArrayType>(ty))
341  return isLegalType(arrayTy.getElementType());
342 
343  if (auto structTy = hw::type_dyn_cast<hw::StructType>(ty)) {
344  for (auto field : structTy.getElements())
345  if (!isLegalType(field.type))
346  return false;
347  return true;
348  }
349 
350  return true;
351 }
352 // NOLINTEND(misc-no-recursion)
353 
354 static bool isLegalOp(Operation *op) {
355  if (auto module = dyn_cast<hw::HWModuleLike>(op)) {
356  return llvm::all_of(module.getPortList(), [](hw::PortInfo port) {
357  return isLegalType(port.type);
358  });
359  }
360  bool allOperandsLowered = llvm::all_of(
361  op->getOperands(), [](auto op) { return isLegalType(op.getType()); });
362  bool allResultsLowered = llvm::all_of(op->getResults(), [](auto result) {
363  return isLegalType(result.getType());
364  });
365  return allOperandsLowered && allResultsLowered;
366 }
367 
368 void SeqToSVPass::runOnOperation() {
369  auto circuit = getOperation();
370  MLIRContext *context = &getContext();
371 
372  auto modules = llvm::to_vector(circuit.getOps<HWModuleOp>());
373 
374  FirMemLowering memLowering(circuit);
375 
376  // Identify memories and group them by module.
377  auto uniqueMems = memLowering.collectMemories(modules);
378  MapVector<HWModuleOp, SmallVector<FirMemLowering::MemoryConfig>> memsByModule;
379  for (auto &[config, memOps] : uniqueMems) {
380  // Create the `HWModuleGeneratedOp`s for each unique configuration.
381  auto genOp = memLowering.createMemoryModule(config, memOps);
382 
383  // Group memories by their parent module for parallelism.
384  for (auto memOp : memOps) {
385  auto parent = memOp->getParentOfType<HWModuleOp>();
386  memsByModule[parent].emplace_back(&config, genOp, memOp);
387  }
388  }
389 
390  // Lower memories and registers in modules in parallel.
391  mlir::parallelForEach(&getContext(), modules, [&](HWModuleOp module) {
392  SeqToSVTypeConverter typeConverter;
393  FirRegLowering regLowering(typeConverter, module, disableRegRandomization,
394  emitSeparateAlwaysBlocks);
395  regLowering.lower();
396  numSubaccessRestored += regLowering.numSubaccessRestored;
397 
398  if (auto *it = memsByModule.find(module); it != memsByModule.end())
399  memLowering.lowerMemoriesInModule(module, it->second);
400  });
401 
402  // Mark all ops which can have clock types as illegal.
403  SeqToSVTypeConverter typeConverter;
404  ConversionTarget target(*context);
405  target.addIllegalDialect<SeqDialect>();
406  target.markUnknownOpDynamicallyLegal(isLegalOp);
407 
408  RewritePatternSet patterns(context);
409  patterns.add<CompRegLower<CompRegOp>>(typeConverter, context,
410  lowerToAlwaysFF);
411  patterns.add<CompRegLower<CompRegClockEnabledOp>>(typeConverter, context,
412  lowerToAlwaysFF);
413  patterns.add<ClockCastLowering<seq::FromClockOp>>(typeConverter, context);
414  patterns.add<ClockCastLowering<seq::ToClockOp>>(typeConverter, context);
415  patterns.add<ClockGateLowering>(typeConverter, context);
416  patterns.add<ClockMuxLowering>(typeConverter, context);
417  patterns.add<ClockDividerLowering>(typeConverter, context);
418  patterns.add<ClockConstLowering>(typeConverter, context);
419  patterns.add<TypeConversionPattern>(typeConverter, context);
420 
421  if (failed(applyPartialConversion(circuit, target, std::move(patterns))))
422  signalPassFailure();
423 }
424 
425 std::unique_ptr<Pass>
426 circt::createLowerSeqToSVPass(const LowerSeqToSVOptions &options) {
427  return std::make_unique<SeqToSVPass>(options);
428 }
static FIRRTLBaseType convertType(FIRRTLBaseType type)
Returns null type if no conversion is needed.
Definition: DropConst.cpp:25
llvm::SmallVector< StringAttr > inputs
Builder builder
static bool isLegalType(Type ty)
Definition: SeqToSV.cpp:336
static bool isLegalOp(Operation *op)
Definition: SeqToSV.cpp:354
FIR memory lowering helper.
Lower FirRegOp to sv.reg and sv.always.
def create(data_type, value)
Definition: hw.py:397
def create(value)
Definition: sv.py:106
Definition: sv.py:68
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
Value createOrFoldNot(Location loc, Value value, OpBuilder &builder, bool twoState=false)
Create a `‘Not’' gate on a value.
Definition: CombOps.cpp:48
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.
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createLowerSeqToSVPass(const LowerSeqToSVOptions &options={})
Definition: SeqToSV.cpp:426
Definition: seq.py:1
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition: seq.py:20
Generic pattern which replaces an operation by one of the same operation name, but with converted att...