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