CIRCT 22.0.0git
Loading...
Searching...
No Matches
LowerArcToLLVM.cpp
Go to the documentation of this file.
1//===- LowerArcToLLVM.cpp -------------------------------------------------===//
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
19#include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h"
20#include "mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h"
21#include "mlir/Conversion/FuncToLLVM/ConvertFuncToLLVM.h"
22#include "mlir/Conversion/IndexToLLVM/IndexToLLVM.h"
23#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
24#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
25#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
26#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
27#include "mlir/Dialect/Func/IR/FuncOps.h"
28#include "mlir/Dialect/Index/IR/IndexOps.h"
29#include "mlir/Dialect/LLVMIR/FunctionCallUtils.h"
30#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
31#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
32#include "mlir/Dialect/SCF/IR/SCF.h"
33#include "mlir/IR/BuiltinDialect.h"
34#include "mlir/Pass/Pass.h"
35#include "mlir/Transforms/DialectConversion.h"
36#include "llvm/Support/Debug.h"
37#include "llvm/Support/FormatVariadic.h"
38
39#define DEBUG_TYPE "lower-arc-to-llvm"
40
41namespace circt {
42#define GEN_PASS_DEF_LOWERARCTOLLVM
43#include "circt/Conversion/Passes.h.inc"
44} // namespace circt
45
46using namespace mlir;
47using namespace circt;
48using namespace arc;
49using namespace hw;
50
51//===----------------------------------------------------------------------===//
52// Lowering Patterns
53//===----------------------------------------------------------------------===//
54
55static llvm::Twine evalSymbolFromModelName(StringRef modelName) {
56 return modelName + "_eval";
57}
58
59namespace {
60
61struct ModelOpLowering : public OpConversionPattern<arc::ModelOp> {
62 using OpConversionPattern::OpConversionPattern;
63 LogicalResult
64 matchAndRewrite(arc::ModelOp op, OpAdaptor adaptor,
65 ConversionPatternRewriter &rewriter) const final {
66 {
67 IRRewriter::InsertionGuard guard(rewriter);
68 rewriter.setInsertionPointToEnd(&op.getBodyBlock());
69 func::ReturnOp::create(rewriter, op.getLoc());
70 }
71 auto funcName =
72 rewriter.getStringAttr(evalSymbolFromModelName(op.getName()));
73 auto funcType =
74 rewriter.getFunctionType(op.getBody().getArgumentTypes(), {});
75 auto func =
76 mlir::func::FuncOp::create(rewriter, op.getLoc(), funcName, funcType);
77 rewriter.inlineRegionBefore(op.getRegion(), func.getBody(), func.end());
78 rewriter.eraseOp(op);
79 return success();
80 }
81};
82
83struct AllocStorageOpLowering
84 : public OpConversionPattern<arc::AllocStorageOp> {
85 using OpConversionPattern::OpConversionPattern;
86 LogicalResult
87 matchAndRewrite(arc::AllocStorageOp op, OpAdaptor adaptor,
88 ConversionPatternRewriter &rewriter) const final {
89 auto type = typeConverter->convertType(op.getType());
90 if (!op.getOffset().has_value())
91 return failure();
92 rewriter.replaceOpWithNewOp<LLVM::GEPOp>(op, type, rewriter.getI8Type(),
93 adaptor.getInput(),
94 LLVM::GEPArg(*op.getOffset()));
95 return success();
96 }
97};
98
99template <class ConcreteOp>
100struct AllocStateLikeOpLowering : public OpConversionPattern<ConcreteOp> {
102 using OpConversionPattern<ConcreteOp>::typeConverter;
103 using OpAdaptor = typename ConcreteOp::Adaptor;
104
105 LogicalResult
106 matchAndRewrite(ConcreteOp op, OpAdaptor adaptor,
107 ConversionPatternRewriter &rewriter) const final {
108 // Get a pointer to the correct offset in the storage.
109 auto offsetAttr = op->template getAttrOfType<IntegerAttr>("offset");
110 if (!offsetAttr)
111 return failure();
112 Value ptr = LLVM::GEPOp::create(
113 rewriter, op->getLoc(), adaptor.getStorage().getType(),
114 rewriter.getI8Type(), adaptor.getStorage(),
115 LLVM::GEPArg(offsetAttr.getValue().getZExtValue()));
116 rewriter.replaceOp(op, ptr);
117 return success();
118 }
119};
120
121struct StateReadOpLowering : public OpConversionPattern<arc::StateReadOp> {
122 using OpConversionPattern::OpConversionPattern;
123 LogicalResult
124 matchAndRewrite(arc::StateReadOp op, OpAdaptor adaptor,
125 ConversionPatternRewriter &rewriter) const final {
126 auto type = typeConverter->convertType(op.getType());
127 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, type, adaptor.getState());
128 return success();
129 }
130};
131
132struct StateWriteOpLowering : public OpConversionPattern<arc::StateWriteOp> {
133 using OpConversionPattern::OpConversionPattern;
134 LogicalResult
135 matchAndRewrite(arc::StateWriteOp op, OpAdaptor adaptor,
136 ConversionPatternRewriter &rewriter) const final {
137 if (adaptor.getCondition()) {
138 rewriter.replaceOpWithNewOp<scf::IfOp>(
139 op, adaptor.getCondition(), [&](auto &builder, auto loc) {
140 LLVM::StoreOp::create(builder, loc, adaptor.getValue(),
141 adaptor.getState());
142 scf::YieldOp::create(builder, loc);
143 });
144 } else {
145 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(),
146 adaptor.getState());
147 }
148 return success();
149 }
150};
151
152struct AllocMemoryOpLowering : public OpConversionPattern<arc::AllocMemoryOp> {
153 using OpConversionPattern::OpConversionPattern;
154 LogicalResult
155 matchAndRewrite(arc::AllocMemoryOp op, OpAdaptor adaptor,
156 ConversionPatternRewriter &rewriter) const final {
157 auto offsetAttr = op->getAttrOfType<IntegerAttr>("offset");
158 if (!offsetAttr)
159 return failure();
160 Value ptr = LLVM::GEPOp::create(
161 rewriter, op.getLoc(), adaptor.getStorage().getType(),
162 rewriter.getI8Type(), adaptor.getStorage(),
163 LLVM::GEPArg(offsetAttr.getValue().getZExtValue()));
164
165 rewriter.replaceOp(op, ptr);
166 return success();
167 }
168};
169
170struct StorageGetOpLowering : public OpConversionPattern<arc::StorageGetOp> {
171 using OpConversionPattern::OpConversionPattern;
172 LogicalResult
173 matchAndRewrite(arc::StorageGetOp op, OpAdaptor adaptor,
174 ConversionPatternRewriter &rewriter) const final {
175 Value offset = LLVM::ConstantOp::create(
176 rewriter, op.getLoc(), rewriter.getI32Type(), op.getOffsetAttr());
177 Value ptr = LLVM::GEPOp::create(
178 rewriter, op.getLoc(), adaptor.getStorage().getType(),
179 rewriter.getI8Type(), adaptor.getStorage(), offset);
180 rewriter.replaceOp(op, ptr);
181 return success();
182 }
183};
184
185struct MemoryAccess {
186 Value ptr;
187 Value withinBounds;
188};
189
190static MemoryAccess prepareMemoryAccess(Location loc, Value memory,
191 Value address, MemoryType type,
192 ConversionPatternRewriter &rewriter) {
193 auto zextAddrType = rewriter.getIntegerType(
194 cast<IntegerType>(address.getType()).getWidth() + 1);
195 Value addr = LLVM::ZExtOp::create(rewriter, loc, zextAddrType, address);
196 Value addrLimit =
197 LLVM::ConstantOp::create(rewriter, loc, zextAddrType,
198 rewriter.getI32IntegerAttr(type.getNumWords()));
199 Value withinBounds = LLVM::ICmpOp::create(
200 rewriter, loc, LLVM::ICmpPredicate::ult, addr, addrLimit);
201 Value ptr = LLVM::GEPOp::create(
202 rewriter, loc, LLVM::LLVMPointerType::get(memory.getContext()),
203 rewriter.getIntegerType(type.getStride() * 8), memory, ValueRange{addr});
204 return {ptr, withinBounds};
205}
206
207struct MemoryReadOpLowering : public OpConversionPattern<arc::MemoryReadOp> {
208 using OpConversionPattern::OpConversionPattern;
209 LogicalResult
210 matchAndRewrite(arc::MemoryReadOp op, OpAdaptor adaptor,
211 ConversionPatternRewriter &rewriter) const final {
212 auto type = typeConverter->convertType(op.getType());
213 auto memoryType = cast<MemoryType>(op.getMemory().getType());
214 auto access =
215 prepareMemoryAccess(op.getLoc(), adaptor.getMemory(),
216 adaptor.getAddress(), memoryType, rewriter);
217
218 // Only attempt to read the memory if the address is within bounds,
219 // otherwise produce a zero value.
220 rewriter.replaceOpWithNewOp<scf::IfOp>(
221 op, access.withinBounds,
222 [&](auto &builder, auto loc) {
223 Value loadOp = LLVM::LoadOp::create(
224 builder, loc, memoryType.getWordType(), access.ptr);
225 scf::YieldOp::create(builder, loc, loadOp);
226 },
227 [&](auto &builder, auto loc) {
228 Value zeroValue = LLVM::ConstantOp::create(
229 builder, loc, type, builder.getI64IntegerAttr(0));
230 scf::YieldOp::create(builder, loc, zeroValue);
231 });
232 return success();
233 }
234};
235
236struct MemoryWriteOpLowering : public OpConversionPattern<arc::MemoryWriteOp> {
237 using OpConversionPattern::OpConversionPattern;
238 LogicalResult
239 matchAndRewrite(arc::MemoryWriteOp op, OpAdaptor adaptor,
240 ConversionPatternRewriter &rewriter) const final {
241 auto access = prepareMemoryAccess(
242 op.getLoc(), adaptor.getMemory(), adaptor.getAddress(),
243 cast<MemoryType>(op.getMemory().getType()), rewriter);
244 auto enable = access.withinBounds;
245 if (adaptor.getEnable())
246 enable = LLVM::AndOp::create(rewriter, op.getLoc(), adaptor.getEnable(),
247 enable);
248
249 // Only attempt to write the memory if the address is within bounds.
250 rewriter.replaceOpWithNewOp<scf::IfOp>(
251 op, enable, [&](auto &builder, auto loc) {
252 LLVM::StoreOp::create(builder, loc, adaptor.getData(), access.ptr);
253 scf::YieldOp::create(builder, loc);
254 });
255 return success();
256 }
257};
258
259/// A dummy lowering for clock gates to an AND gate.
260struct ClockGateOpLowering : public OpConversionPattern<seq::ClockGateOp> {
261 using OpConversionPattern::OpConversionPattern;
262 LogicalResult
263 matchAndRewrite(seq::ClockGateOp op, OpAdaptor adaptor,
264 ConversionPatternRewriter &rewriter) const final {
265 rewriter.replaceOpWithNewOp<LLVM::AndOp>(op, adaptor.getInput(),
266 adaptor.getEnable());
267 return success();
268 }
269};
270
271/// Lower 'seq.clock_inv x' to 'llvm.xor x true'
272struct ClockInvOpLowering : public OpConversionPattern<seq::ClockInverterOp> {
273 using OpConversionPattern::OpConversionPattern;
274 LogicalResult
275 matchAndRewrite(seq::ClockInverterOp op, OpAdaptor adaptor,
276 ConversionPatternRewriter &rewriter) const final {
277 auto constTrue = LLVM::ConstantOp::create(rewriter, op->getLoc(),
278 rewriter.getI1Type(), 1);
279 rewriter.replaceOpWithNewOp<LLVM::XOrOp>(op, adaptor.getInput(), constTrue);
280 return success();
281 }
282};
283
284struct ZeroCountOpLowering : public OpConversionPattern<arc::ZeroCountOp> {
285 using OpConversionPattern::OpConversionPattern;
286 LogicalResult
287 matchAndRewrite(arc::ZeroCountOp op, OpAdaptor adaptor,
288 ConversionPatternRewriter &rewriter) const override {
289 // Use poison when input is zero.
290 IntegerAttr isZeroPoison = rewriter.getBoolAttr(true);
291
292 if (op.getPredicate() == arc::ZeroCountPredicate::leading) {
293 rewriter.replaceOpWithNewOp<LLVM::CountLeadingZerosOp>(
294 op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison);
295 return success();
296 }
297
298 rewriter.replaceOpWithNewOp<LLVM::CountTrailingZerosOp>(
299 op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison);
300 return success();
301 }
302};
303
304struct SeqConstClockLowering : public OpConversionPattern<seq::ConstClockOp> {
305 using OpConversionPattern::OpConversionPattern;
306 LogicalResult
307 matchAndRewrite(seq::ConstClockOp op, OpAdaptor adaptor,
308 ConversionPatternRewriter &rewriter) const override {
309 rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(
310 op, rewriter.getI1Type(), static_cast<int64_t>(op.getValue()));
311 return success();
312 }
313};
314
315template <typename OpTy>
316struct ReplaceOpWithInputPattern : public OpConversionPattern<OpTy> {
318 using OpAdaptor = typename OpTy::Adaptor;
319 LogicalResult
320 matchAndRewrite(OpTy op, OpAdaptor adaptor,
321 ConversionPatternRewriter &rewriter) const override {
322 rewriter.replaceOp(op, adaptor.getInput());
323 return success();
324 }
325};
326
327} // namespace
328
329//===----------------------------------------------------------------------===//
330// Simulation Orchestration Lowering Patterns
331//===----------------------------------------------------------------------===//
332
333namespace {
334
335struct ModelInfoMap {
336 size_t numStateBytes;
337 llvm::DenseMap<StringRef, StateInfo> states;
338 mlir::FlatSymbolRefAttr initialFnSymbol;
339 mlir::FlatSymbolRefAttr finalFnSymbol;
340};
341
342template <typename OpTy>
343struct ModelAwarePattern : public OpConversionPattern<OpTy> {
344 ModelAwarePattern(const TypeConverter &typeConverter, MLIRContext *context,
345 llvm::DenseMap<StringRef, ModelInfoMap> &modelInfo)
346 : OpConversionPattern<OpTy>(typeConverter, context),
347 modelInfo(modelInfo) {}
348
349protected:
350 Value createPtrToPortState(ConversionPatternRewriter &rewriter, Location loc,
351 Value state, const StateInfo &port) const {
352 MLIRContext *ctx = rewriter.getContext();
353 return LLVM::GEPOp::create(rewriter, loc, LLVM::LLVMPointerType::get(ctx),
354 IntegerType::get(ctx, 8), state,
355 LLVM::GEPArg(port.offset));
356 }
357
358 llvm::DenseMap<StringRef, ModelInfoMap> &modelInfo;
359};
360
361/// Lowers SimInstantiateOp to a malloc and memset call. This pattern will
362/// mutate the global module.
363struct SimInstantiateOpLowering
364 : public ModelAwarePattern<arc::SimInstantiateOp> {
365 using ModelAwarePattern::ModelAwarePattern;
366
367 LogicalResult
368 matchAndRewrite(arc::SimInstantiateOp op, OpAdaptor adaptor,
369 ConversionPatternRewriter &rewriter) const final {
370 auto modelIt = modelInfo.find(
371 cast<SimModelInstanceType>(op.getBody().getArgument(0).getType())
372 .getModel()
373 .getValue());
374 ModelInfoMap &model = modelIt->second;
375
376 ModuleOp moduleOp = op->getParentOfType<ModuleOp>();
377 if (!moduleOp)
378 return failure();
379
380 ConversionPatternRewriter::InsertionGuard guard(rewriter);
381
382 // FIXME: like the rest of MLIR, this assumes sizeof(intptr_t) ==
383 // sizeof(size_t) on the target architecture.
384 Type convertedIndex = typeConverter->convertType(rewriter.getIndexType());
385
386 FailureOr<LLVM::LLVMFuncOp> mallocFunc =
387 LLVM::lookupOrCreateMallocFn(rewriter, moduleOp, convertedIndex);
388 if (failed(mallocFunc))
389 return mallocFunc;
390
391 FailureOr<LLVM::LLVMFuncOp> freeFunc =
392 LLVM::lookupOrCreateFreeFn(rewriter, moduleOp);
393 if (failed(freeFunc))
394 return freeFunc;
395
396 Location loc = op.getLoc();
397 Value numStateBytes = LLVM::ConstantOp::create(
398 rewriter, loc, convertedIndex, model.numStateBytes);
399 Value allocated = LLVM::CallOp::create(rewriter, loc, mallocFunc.value(),
400 ValueRange{numStateBytes})
401 .getResult();
402 Value zero =
403 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI8Type(), 0);
404 LLVM::MemsetOp::create(rewriter, loc, allocated, zero, numStateBytes,
405 false);
406
407 // Call the model's 'initial' function if present.
408 if (model.initialFnSymbol) {
409 auto initialFnType = LLVM::LLVMFunctionType::get(
410 LLVM::LLVMVoidType::get(op.getContext()),
411 {LLVM::LLVMPointerType::get(op.getContext())});
412 LLVM::CallOp::create(rewriter, loc, initialFnType, model.initialFnSymbol,
413 ValueRange{allocated});
414 }
415
416 // Execute the body.
417 rewriter.inlineBlockBefore(&adaptor.getBody().getBlocks().front(), op,
418 {allocated});
419
420 // Call the model's 'final' function if present.
421 if (model.finalFnSymbol) {
422 auto finalFnType = LLVM::LLVMFunctionType::get(
423 LLVM::LLVMVoidType::get(op.getContext()),
424 {LLVM::LLVMPointerType::get(op.getContext())});
425 LLVM::CallOp::create(rewriter, loc, finalFnType, model.finalFnSymbol,
426 ValueRange{allocated});
427 }
428
429 LLVM::CallOp::create(rewriter, loc, freeFunc.value(),
430 ValueRange{allocated});
431 rewriter.eraseOp(op);
432
433 return success();
434 }
435};
436
437struct SimSetInputOpLowering : public ModelAwarePattern<arc::SimSetInputOp> {
438 using ModelAwarePattern::ModelAwarePattern;
439
440 LogicalResult
441 matchAndRewrite(arc::SimSetInputOp op, OpAdaptor adaptor,
442 ConversionPatternRewriter &rewriter) const final {
443 auto modelIt =
444 modelInfo.find(cast<SimModelInstanceType>(op.getInstance().getType())
445 .getModel()
446 .getValue());
447 ModelInfoMap &model = modelIt->second;
448
449 auto portIt = model.states.find(op.getInput());
450 if (portIt == model.states.end()) {
451 // If the port is not found in the state, it means the model does not
452 // actually use it. Thus this operation is a no-op.
453 rewriter.eraseOp(op);
454 return success();
455 }
456
457 StateInfo &port = portIt->second;
458 Value statePtr = createPtrToPortState(rewriter, op.getLoc(),
459 adaptor.getInstance(), port);
460 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(),
461 statePtr);
462
463 return success();
464 }
465};
466
467struct SimGetPortOpLowering : public ModelAwarePattern<arc::SimGetPortOp> {
468 using ModelAwarePattern::ModelAwarePattern;
469
470 LogicalResult
471 matchAndRewrite(arc::SimGetPortOp op, OpAdaptor adaptor,
472 ConversionPatternRewriter &rewriter) const final {
473 auto modelIt =
474 modelInfo.find(cast<SimModelInstanceType>(op.getInstance().getType())
475 .getModel()
476 .getValue());
477 ModelInfoMap &model = modelIt->second;
478
479 auto type = typeConverter->convertType(op.getValue().getType());
480 if (!type)
481 return failure();
482 auto portIt = model.states.find(op.getPort());
483 if (portIt == model.states.end()) {
484 // If the port is not found in the state, it means the model does not
485 // actually set it. Thus this operation returns 0.
486 rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(op, type, 0);
487 return success();
488 }
489
490 StateInfo &port = portIt->second;
491 Value statePtr = createPtrToPortState(rewriter, op.getLoc(),
492 adaptor.getInstance(), port);
493 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, type, statePtr);
494
495 return success();
496 }
497};
498
499struct SimStepOpLowering : public ModelAwarePattern<arc::SimStepOp> {
500 using ModelAwarePattern::ModelAwarePattern;
501
502 LogicalResult
503 matchAndRewrite(arc::SimStepOp op, OpAdaptor adaptor,
504 ConversionPatternRewriter &rewriter) const final {
505 StringRef modelName = cast<SimModelInstanceType>(op.getInstance().getType())
506 .getModel()
507 .getValue();
508
509 StringAttr evalFunc =
510 rewriter.getStringAttr(evalSymbolFromModelName(modelName));
511 rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, mlir::TypeRange(), evalFunc,
512 adaptor.getInstance());
513
514 return success();
515 }
516};
517
518/// Lowers SimEmitValueOp to a printf call. The integer will be printed in its
519/// entirety if it is of size up to size_t, and explicitly truncated otherwise.
520/// This pattern will mutate the global module.
521struct SimEmitValueOpLowering
522 : public OpConversionPattern<arc::SimEmitValueOp> {
523 using OpConversionPattern::OpConversionPattern;
524
525 LogicalResult
526 matchAndRewrite(arc::SimEmitValueOp op, OpAdaptor adaptor,
527 ConversionPatternRewriter &rewriter) const final {
528 auto valueType = dyn_cast<IntegerType>(adaptor.getValue().getType());
529 if (!valueType)
530 return failure();
531
532 Location loc = op.getLoc();
533
534 ModuleOp moduleOp = op->getParentOfType<ModuleOp>();
535 if (!moduleOp)
536 return failure();
537
538 SmallVector<Value> printfVariadicArgs;
539 SmallString<16> printfFormatStr;
540 int remainingBits = valueType.getWidth();
541 Value value = adaptor.getValue();
542
543 // Assumes the target platform uses 64bit for long long ints (%llx
544 // formatter).
545 constexpr llvm::StringRef intFormatter = "llx";
546 auto intType = IntegerType::get(getContext(), 64);
547 Value shiftValue = LLVM::ConstantOp::create(
548 rewriter, loc, rewriter.getIntegerAttr(valueType, intType.getWidth()));
549
550 if (valueType.getWidth() < intType.getWidth()) {
551 int width = llvm::divideCeil(valueType.getWidth(), 4);
552 printfFormatStr = llvm::formatv("%0{0}{1}", width, intFormatter);
553 printfVariadicArgs.push_back(
554 LLVM::ZExtOp::create(rewriter, loc, intType, value));
555 } else {
556 // Process the value in 64 bit chunks, starting from the least significant
557 // bits. Since we append chunks in low-to-high order, we reverse the
558 // vector to print them in the correct high-to-low order.
559 int otherChunkWidth = intType.getWidth() / 4;
560 int firstChunkWidth =
561 llvm::divideCeil(valueType.getWidth() % intType.getWidth(), 4);
562 if (firstChunkWidth == 0) { // print the full 64-bit hex or a subset.
563 firstChunkWidth = otherChunkWidth;
564 }
565
566 std::string firstChunkFormat =
567 llvm::formatv("%0{0}{1}", firstChunkWidth, intFormatter);
568 std::string otherChunkFormat =
569 llvm::formatv("%0{0}{1}", otherChunkWidth, intFormatter);
570
571 for (int i = 0; remainingBits > 0; ++i) {
572 // Append 64-bit chunks to the printf arguments, in low-to-high
573 // order. The integer is printed in hex format with zero padding.
574 printfVariadicArgs.push_back(
575 LLVM::TruncOp::create(rewriter, loc, intType, value));
576
577 // Zero-padded format specifier for fixed width, e.g. %01llx for 4 bits.
578 printfFormatStr.append(i == 0 ? firstChunkFormat : otherChunkFormat);
579
580 value =
581 LLVM::LShrOp::create(rewriter, loc, value, shiftValue).getResult();
582 remainingBits -= intType.getWidth();
583 }
584 }
585
586 // Lookup of create printf function symbol.
587 auto printfFunc = LLVM::lookupOrCreateFn(
588 rewriter, moduleOp, "printf", LLVM::LLVMPointerType::get(getContext()),
589 LLVM::LLVMVoidType::get(getContext()), true);
590 if (failed(printfFunc))
591 return printfFunc;
592
593 // Insert the format string if not already available.
594 SmallString<16> formatStrName{"_arc_sim_emit_"};
595 formatStrName.append(adaptor.getValueName());
596 LLVM::GlobalOp formatStrGlobal;
597 if (!(formatStrGlobal =
598 moduleOp.lookupSymbol<LLVM::GlobalOp>(formatStrName))) {
599 ConversionPatternRewriter::InsertionGuard insertGuard(rewriter);
600
601 SmallString<16> formatStr = adaptor.getValueName();
602 formatStr.append(" = ");
603 formatStr.append(printfFormatStr);
604 formatStr.append("\n");
605 SmallVector<char> formatStrVec{formatStr.begin(), formatStr.end()};
606 formatStrVec.push_back(0);
607
608 rewriter.setInsertionPointToStart(moduleOp.getBody());
609 auto globalType =
610 LLVM::LLVMArrayType::get(rewriter.getI8Type(), formatStrVec.size());
611 formatStrGlobal = LLVM::GlobalOp::create(
612 rewriter, loc, globalType, /*isConstant=*/true,
613 LLVM::Linkage::Internal,
614 /*name=*/formatStrName, rewriter.getStringAttr(formatStrVec),
615 /*alignment=*/0);
616 }
617
618 Value formatStrGlobalPtr =
619 LLVM::AddressOfOp::create(rewriter, loc, formatStrGlobal);
620
621 // Add the format string to the end, and reverse the vector to print them in
622 // the correct high-to-low order with the format string at the beginning.
623 printfVariadicArgs.push_back(formatStrGlobalPtr);
624 std::reverse(printfVariadicArgs.begin(), printfVariadicArgs.end());
625
626 rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, printfFunc.value(),
627 printfVariadicArgs);
628
629 return success();
630 }
631};
632
633} // namespace
634
635static LogicalResult convert(arc::ExecuteOp op, arc::ExecuteOp::Adaptor adaptor,
636 ConversionPatternRewriter &rewriter,
637 const TypeConverter &converter) {
638 // Convert the argument types in the body blocks.
639 if (failed(rewriter.convertRegionTypes(&op.getBody(), converter)))
640 return failure();
641
642 // Split the block at the current insertion point such that we can branch into
643 // the `arc.execute` body region, and have `arc.output` branch back to the
644 // point after the `arc.execute`.
645 auto *blockBefore = rewriter.getInsertionBlock();
646 auto *blockAfter =
647 rewriter.splitBlock(blockBefore, rewriter.getInsertionPoint());
648
649 // Branch to the entry block.
650 rewriter.setInsertionPointToEnd(blockBefore);
651 mlir::cf::BranchOp::create(rewriter, op.getLoc(), &op.getBody().front(),
652 adaptor.getInputs());
653
654 // Make all `arc.output` terminators branch to the block after the
655 // `arc.execute` op.
656 for (auto &block : op.getBody()) {
657 auto outputOp = dyn_cast<arc::OutputOp>(block.getTerminator());
658 if (!outputOp)
659 continue;
660 rewriter.setInsertionPointToEnd(&block);
661 rewriter.replaceOpWithNewOp<mlir::cf::BranchOp>(outputOp, blockAfter,
662 outputOp.getOperands());
663 }
664
665 // Inline the body region between the before and after blocks.
666 rewriter.inlineRegionBefore(op.getBody(), blockAfter);
667
668 // Add arguments to the block after the `arc.execute`, replace the op's
669 // results with the arguments, then perform block signature conversion.
670 SmallVector<Value> args;
671 args.reserve(op.getNumResults());
672 for (auto result : op.getResults())
673 args.push_back(blockAfter->addArgument(result.getType(), result.getLoc()));
674 rewriter.replaceOp(op, args);
675 auto conversion = converter.convertBlockSignature(blockAfter);
676 if (!conversion)
677 return failure();
678 rewriter.applySignatureConversion(blockAfter, *conversion, &converter);
679 return success();
680}
681
682//===----------------------------------------------------------------------===//
683// Pass Implementation
684//===----------------------------------------------------------------------===//
685
686namespace {
687struct LowerArcToLLVMPass
688 : public circt::impl::LowerArcToLLVMBase<LowerArcToLLVMPass> {
689 void runOnOperation() override;
690};
691} // namespace
692
693void LowerArcToLLVMPass::runOnOperation() {
694 // Replace any `i0` values with an `hw.constant 0 : i0` to avoid later issues
695 // in LLVM conversion.
696 {
697 DenseMap<Region *, hw::ConstantOp> zeros;
698 getOperation().walk([&](Operation *op) {
699 if (op->hasTrait<OpTrait::ConstantLike>())
700 return;
701 for (auto result : op->getResults()) {
702 auto type = dyn_cast<IntegerType>(result.getType());
703 if (!type || type.getWidth() != 0)
704 continue;
705 auto *region = op->getParentRegion();
706 auto &zero = zeros[region];
707 if (!zero) {
708 auto builder = OpBuilder::atBlockBegin(&region->front());
709 zero = hw::ConstantOp::create(builder, result.getLoc(),
710 APInt::getZero(0));
711 }
712 result.replaceAllUsesWith(zero);
713 }
714 });
715 }
716
717 // Collect the symbols in the root op such that the HW-to-LLVM lowering can
718 // create LLVM globals with non-colliding names.
719 Namespace globals;
720 SymbolCache cache;
721 cache.addDefinitions(getOperation());
722 globals.add(cache);
723
724 // Setup the conversion target. Explicitly mark `scf.yield` legal since it
725 // does not have a conversion itself, which would cause it to fail
726 // legalization and for the conversion to abort. (It relies on its parent op's
727 // conversion to remove it.)
728 LLVMConversionTarget target(getContext());
729 target.addLegalOp<mlir::ModuleOp>();
730 target.addLegalOp<scf::YieldOp>(); // quirk of SCF dialect conversion
731
732 // Setup the arc dialect type conversion.
733 LLVMTypeConverter converter(&getContext());
734 converter.addConversion([&](seq::ClockType type) {
735 return IntegerType::get(type.getContext(), 1);
736 });
737 converter.addConversion([&](StorageType type) {
738 return LLVM::LLVMPointerType::get(type.getContext());
739 });
740 converter.addConversion([&](MemoryType type) {
741 return LLVM::LLVMPointerType::get(type.getContext());
742 });
743 converter.addConversion([&](StateType type) {
744 return LLVM::LLVMPointerType::get(type.getContext());
745 });
746 converter.addConversion([&](SimModelInstanceType type) {
747 return LLVM::LLVMPointerType::get(type.getContext());
748 });
749
750 // Setup the conversion patterns.
751 ConversionPatternSet patterns(&getContext(), converter);
752
753 // MLIR patterns.
754 populateSCFToControlFlowConversionPatterns(patterns);
755 populateFuncToLLVMConversionPatterns(converter, patterns);
756 cf::populateControlFlowToLLVMConversionPatterns(converter, patterns);
757 arith::populateArithToLLVMConversionPatterns(converter, patterns);
758 index::populateIndexToLLVMConversionPatterns(converter, patterns);
759 populateAnyFunctionOpInterfaceTypeConversionPattern(patterns, converter);
760
761 // CIRCT patterns.
762 DenseMap<std::pair<Type, ArrayAttr>, LLVM::GlobalOp> constAggregateGlobalsMap;
764 std::optional<HWToLLVMArraySpillCache> spillCacheOpt =
766 {
767 OpBuilder spillBuilder(getOperation());
768 spillCacheOpt->spillNonHWOps(spillBuilder, converter, getOperation());
769 }
771 constAggregateGlobalsMap, spillCacheOpt);
772
775
776 // Arc patterns.
777 // clang-format off
778 patterns.add<
779 AllocMemoryOpLowering,
780 AllocStateLikeOpLowering<arc::AllocStateOp>,
781 AllocStateLikeOpLowering<arc::RootInputOp>,
782 AllocStateLikeOpLowering<arc::RootOutputOp>,
783 AllocStorageOpLowering,
784 ClockGateOpLowering,
785 ClockInvOpLowering,
786 MemoryReadOpLowering,
787 MemoryWriteOpLowering,
788 ModelOpLowering,
789 ReplaceOpWithInputPattern<seq::ToClockOp>,
790 ReplaceOpWithInputPattern<seq::FromClockOp>,
791 SeqConstClockLowering,
792 SimEmitValueOpLowering,
793 StateReadOpLowering,
794 StateWriteOpLowering,
795 StorageGetOpLowering,
796 ZeroCountOpLowering
797 >(converter, &getContext());
798 // clang-format on
799 patterns.add<ExecuteOp>(convert);
800
801 SmallVector<ModelInfo> models;
802 if (failed(collectModels(getOperation(), models))) {
803 signalPassFailure();
804 return;
805 }
806
807 llvm::DenseMap<StringRef, ModelInfoMap> modelMap(models.size());
808 for (ModelInfo &modelInfo : models) {
809 llvm::DenseMap<StringRef, StateInfo> states(modelInfo.states.size());
810 for (StateInfo &stateInfo : modelInfo.states)
811 states.insert({stateInfo.name, stateInfo});
812 modelMap.insert(
813 {modelInfo.name,
814 ModelInfoMap{modelInfo.numStateBytes, std::move(states),
815 modelInfo.initialFnSym, modelInfo.finalFnSym}});
816 }
817
818 patterns.add<SimInstantiateOpLowering, SimSetInputOpLowering,
819 SimGetPortOpLowering, SimStepOpLowering>(
820 converter, &getContext(), modelMap);
821
822 // Apply the conversion.
823 ConversionConfig config;
824 config.allowPatternRollback = false;
825 if (failed(applyFullConversion(getOperation(), target, std::move(patterns),
826 config)))
827 signalPassFailure();
828}
829
830std::unique_ptr<OperationPass<ModuleOp>> circt::createLowerArcToLLVMPass() {
831 return std::make_unique<LowerArcToLLVMPass>();
832}
static llvm::Twine evalSymbolFromModelName(StringRef modelName)
static LogicalResult convert(arc::ExecuteOp op, arc::ExecuteOp::Adaptor adaptor, ConversionPatternRewriter &rewriter, const TypeConverter &converter)
Extension of RewritePatternSet that allows adding matchAndRewrite functions with op adaptors and Conv...
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
void add(mlir::ModuleOp module)
Definition Namespace.h:48
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Definition SymCache.cpp:23
Default symbol cache implementation; stores associations between names (StringAttr's) to mlir::Operat...
Definition SymCache.h:85
create(data_type, value)
Definition hw.py:433
Definition arc.py:1
mlir::LogicalResult collectModels(mlir::ModuleOp module, llvm::SmallVector< ModelInfo > &models)
Collects information about all Arc models in the provided module, and adds it to models.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void populateCombToArithConversionPatterns(TypeConverter &converter, RewritePatternSet &patterns)
void populateCombToLLVMConversionPatterns(mlir::LLVMTypeConverter &converter, RewritePatternSet &patterns)
Get the Comb to LLVM conversion patterns.
void populateHWToLLVMTypeConversions(mlir::LLVMTypeConverter &converter)
Get the HW to LLVM type conversions.
void populateHWToLLVMConversionPatterns(mlir::LLVMTypeConverter &converter, RewritePatternSet &patterns, Namespace &globals, DenseMap< std::pair< Type, ArrayAttr >, mlir::LLVM::GlobalOp > &constAggregateGlobalsMap, std::optional< HWToLLVMArraySpillCache > &spillCacheOpt)
Get the HW to LLVM conversion patterns.
std::unique_ptr< OperationPass< ModuleOp > > createLowerArcToLLVMPass()
Definition hw.py:1
Helper class mapping array values (HW or LLVM Dialect) to pointers to buffers containing the array va...
Definition HWToLLVM.h:47