CIRCT 23.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
26#include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h"
27#include "mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h"
28#include "mlir/Conversion/FuncToLLVM/ConvertFuncToLLVM.h"
29#include "mlir/Conversion/IndexToLLVM/IndexToLLVM.h"
30#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
31#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
32#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
33#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
34#include "mlir/Dialect/Func/IR/FuncOps.h"
35#include "mlir/Dialect/Index/IR/IndexOps.h"
36#include "mlir/Dialect/LLVMIR/FunctionCallUtils.h"
37#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
38#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
39#include "mlir/Dialect/SCF/IR/SCF.h"
40#include "mlir/IR/BuiltinDialect.h"
41#include "mlir/Pass/Pass.h"
42#include "mlir/Transforms/DialectConversion.h"
43#include "llvm/Support/Debug.h"
44#include "llvm/Support/FormatVariadic.h"
45
46#include <cstddef>
47
48#define DEBUG_TYPE "lower-arc-to-llvm"
49
50namespace circt {
51#define GEN_PASS_DEF_LOWERARCTOLLVM
52#include "circt/Conversion/Passes.h.inc"
53} // namespace circt
54
55using namespace mlir;
56using namespace circt;
57using namespace arc;
58using namespace hw;
59using namespace runtime;
60
61//===----------------------------------------------------------------------===//
62// Lowering Patterns
63//===----------------------------------------------------------------------===//
64
65static llvm::Twine evalSymbolFromModelName(StringRef modelName) {
66 return modelName + "_eval";
67}
68
69namespace {
70
71struct ModelOpLowering : public OpConversionPattern<arc::ModelOp> {
72 using OpConversionPattern::OpConversionPattern;
73 LogicalResult
74 matchAndRewrite(arc::ModelOp op, OpAdaptor adaptor,
75 ConversionPatternRewriter &rewriter) const final {
76 {
77 IRRewriter::InsertionGuard guard(rewriter);
78 rewriter.setInsertionPointToEnd(&op.getBodyBlock());
79 func::ReturnOp::create(rewriter, op.getLoc());
80 }
81 auto funcName =
82 rewriter.getStringAttr(evalSymbolFromModelName(op.getName()));
83 auto funcType =
84 rewriter.getFunctionType(op.getBody().getArgumentTypes(), {});
85 auto func =
86 mlir::func::FuncOp::create(rewriter, op.getLoc(), funcName, funcType);
87 rewriter.inlineRegionBefore(op.getRegion(), func.getBody(), func.end());
88 rewriter.eraseOp(op);
89 return success();
90 }
91};
92
93struct AllocStorageOpLowering
94 : public OpConversionPattern<arc::AllocStorageOp> {
95 using OpConversionPattern::OpConversionPattern;
96 LogicalResult
97 matchAndRewrite(arc::AllocStorageOp op, OpAdaptor adaptor,
98 ConversionPatternRewriter &rewriter) const final {
99 auto type = typeConverter->convertType(op.getType());
100 if (!op.getOffset().has_value())
101 return failure();
102 rewriter.replaceOpWithNewOp<LLVM::GEPOp>(op, type, rewriter.getI8Type(),
103 adaptor.getInput(),
104 LLVM::GEPArg(*op.getOffset()));
105 return success();
106 }
107};
108
109template <class ConcreteOp>
110struct AllocStateLikeOpLowering : public OpConversionPattern<ConcreteOp> {
112 using OpConversionPattern<ConcreteOp>::typeConverter;
113 using OpAdaptor = typename ConcreteOp::Adaptor;
114
115 LogicalResult
116 matchAndRewrite(ConcreteOp op, OpAdaptor adaptor,
117 ConversionPatternRewriter &rewriter) const final {
118 // Get a pointer to the correct offset in the storage.
119 auto offsetAttr = op->template getAttrOfType<IntegerAttr>("offset");
120 if (!offsetAttr)
121 return failure();
122 Value ptr = LLVM::GEPOp::create(
123 rewriter, op->getLoc(), adaptor.getStorage().getType(),
124 rewriter.getI8Type(), adaptor.getStorage(),
125 LLVM::GEPArg(offsetAttr.getValue().getZExtValue()));
126 rewriter.replaceOp(op, ptr);
127 return success();
128 }
129};
130
131struct StateReadOpLowering : public OpConversionPattern<arc::StateReadOp> {
132 using OpConversionPattern::OpConversionPattern;
133 LogicalResult
134 matchAndRewrite(arc::StateReadOp op, OpAdaptor adaptor,
135 ConversionPatternRewriter &rewriter) const final {
136 auto type = typeConverter->convertType(op.getType());
137 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, type, adaptor.getState());
138 return success();
139 }
140};
141
142struct StateWriteOpLowering : public OpConversionPattern<arc::StateWriteOp> {
143 using OpConversionPattern::OpConversionPattern;
144 LogicalResult
145 matchAndRewrite(arc::StateWriteOp op, OpAdaptor adaptor,
146 ConversionPatternRewriter &rewriter) const final {
147 if (adaptor.getCondition()) {
148 rewriter.replaceOpWithNewOp<scf::IfOp>(
149 op, adaptor.getCondition(), [&](auto &builder, auto loc) {
150 LLVM::StoreOp::create(builder, loc, adaptor.getValue(),
151 adaptor.getState());
152 scf::YieldOp::create(builder, loc);
153 });
154 } else {
155 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(),
156 adaptor.getState());
157 }
158 return success();
159 }
160};
161
162//===----------------------------------------------------------------------===//
163// Time Operations Lowering
164//===----------------------------------------------------------------------===//
165
166struct CurrentTimeOpLowering : public OpConversionPattern<arc::CurrentTimeOp> {
167 using OpConversionPattern::OpConversionPattern;
168 LogicalResult
169 matchAndRewrite(arc::CurrentTimeOp op, OpAdaptor adaptor,
170 ConversionPatternRewriter &rewriter) const final {
171 // Time is stored at offset 0 in storage (no offset needed).
172 Value ptr = adaptor.getStorage();
173 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, rewriter.getI64Type(), ptr);
174 return success();
175 }
176};
177
178// `llhd.int_to_time` is a no-op
179struct IntToTimeOpLowering : public OpConversionPattern<llhd::IntToTimeOp> {
180 using OpConversionPattern::OpConversionPattern;
181 LogicalResult
182 matchAndRewrite(llhd::IntToTimeOp op, OpAdaptor adaptor,
183 ConversionPatternRewriter &rewriter) const final {
184 rewriter.replaceOp(op, adaptor.getInput());
185 return success();
186 }
187};
188
189// `llhd.time_to_int` is a no-op
190struct TimeToIntOpLowering : public OpConversionPattern<llhd::TimeToIntOp> {
191 using OpConversionPattern::OpConversionPattern;
192 LogicalResult
193 matchAndRewrite(llhd::TimeToIntOp op, OpAdaptor adaptor,
194 ConversionPatternRewriter &rewriter) const final {
195 rewriter.replaceOp(op, adaptor.getInput());
196 return success();
197 }
198};
199
200//===----------------------------------------------------------------------===//
201// Memory and Storage Lowering
202//===----------------------------------------------------------------------===//
203
204struct AllocMemoryOpLowering : public OpConversionPattern<arc::AllocMemoryOp> {
205 using OpConversionPattern::OpConversionPattern;
206 LogicalResult
207 matchAndRewrite(arc::AllocMemoryOp op, OpAdaptor adaptor,
208 ConversionPatternRewriter &rewriter) const final {
209 auto offsetAttr = op->getAttrOfType<IntegerAttr>("offset");
210 if (!offsetAttr)
211 return failure();
212 Value ptr = LLVM::GEPOp::create(
213 rewriter, op.getLoc(), adaptor.getStorage().getType(),
214 rewriter.getI8Type(), adaptor.getStorage(),
215 LLVM::GEPArg(offsetAttr.getValue().getZExtValue()));
216
217 rewriter.replaceOp(op, ptr);
218 return success();
219 }
220};
221
222struct StorageGetOpLowering : public OpConversionPattern<arc::StorageGetOp> {
223 using OpConversionPattern::OpConversionPattern;
224 LogicalResult
225 matchAndRewrite(arc::StorageGetOp op, OpAdaptor adaptor,
226 ConversionPatternRewriter &rewriter) const final {
227 Value offset = LLVM::ConstantOp::create(
228 rewriter, op.getLoc(), rewriter.getI32Type(), op.getOffsetAttr());
229 Value ptr = LLVM::GEPOp::create(
230 rewriter, op.getLoc(), adaptor.getStorage().getType(),
231 rewriter.getI8Type(), adaptor.getStorage(), offset);
232 rewriter.replaceOp(op, ptr);
233 return success();
234 }
235};
236
237struct MemoryAccess {
238 Value ptr;
239 Value withinBounds;
240};
241
242static MemoryAccess prepareMemoryAccess(Location loc, Value memory,
243 Value address, MemoryType type,
244 ConversionPatternRewriter &rewriter) {
245 auto zextAddrType = rewriter.getIntegerType(
246 cast<IntegerType>(address.getType()).getWidth() + 1);
247 Value addr = LLVM::ZExtOp::create(rewriter, loc, zextAddrType, address);
248 Value addrLimit =
249 LLVM::ConstantOp::create(rewriter, loc, zextAddrType,
250 rewriter.getI32IntegerAttr(type.getNumWords()));
251 Value withinBounds = LLVM::ICmpOp::create(
252 rewriter, loc, LLVM::ICmpPredicate::ult, addr, addrLimit);
253 Value ptr = LLVM::GEPOp::create(
254 rewriter, loc, LLVM::LLVMPointerType::get(memory.getContext()),
255 rewriter.getIntegerType(type.getStride() * 8), memory, ValueRange{addr});
256 return {ptr, withinBounds};
257}
258
259struct MemoryReadOpLowering : public OpConversionPattern<arc::MemoryReadOp> {
260 using OpConversionPattern::OpConversionPattern;
261 LogicalResult
262 matchAndRewrite(arc::MemoryReadOp op, OpAdaptor adaptor,
263 ConversionPatternRewriter &rewriter) const final {
264 auto type = typeConverter->convertType(op.getType());
265 auto memoryType = cast<MemoryType>(op.getMemory().getType());
266 auto access =
267 prepareMemoryAccess(op.getLoc(), adaptor.getMemory(),
268 adaptor.getAddress(), memoryType, rewriter);
269
270 // Only attempt to read the memory if the address is within bounds,
271 // otherwise produce a zero value.
272 rewriter.replaceOpWithNewOp<scf::IfOp>(
273 op, access.withinBounds,
274 [&](auto &builder, auto loc) {
275 Value loadOp = LLVM::LoadOp::create(
276 builder, loc, memoryType.getWordType(), access.ptr);
277 scf::YieldOp::create(builder, loc, loadOp);
278 },
279 [&](auto &builder, auto loc) {
280 Value zeroValue = LLVM::ConstantOp::create(
281 builder, loc, type, builder.getI64IntegerAttr(0));
282 scf::YieldOp::create(builder, loc, zeroValue);
283 });
284 return success();
285 }
286};
287
288struct MemoryWriteOpLowering : public OpConversionPattern<arc::MemoryWriteOp> {
289 using OpConversionPattern::OpConversionPattern;
290 LogicalResult
291 matchAndRewrite(arc::MemoryWriteOp op, OpAdaptor adaptor,
292 ConversionPatternRewriter &rewriter) const final {
293 auto access = prepareMemoryAccess(
294 op.getLoc(), adaptor.getMemory(), adaptor.getAddress(),
295 cast<MemoryType>(op.getMemory().getType()), rewriter);
296 auto enable = access.withinBounds;
297 if (adaptor.getEnable())
298 enable = LLVM::AndOp::create(rewriter, op.getLoc(), adaptor.getEnable(),
299 enable);
300
301 // Only attempt to write the memory if the address is within bounds.
302 rewriter.replaceOpWithNewOp<scf::IfOp>(
303 op, enable, [&](auto &builder, auto loc) {
304 LLVM::StoreOp::create(builder, loc, adaptor.getData(), access.ptr);
305 scf::YieldOp::create(builder, loc);
306 });
307 return success();
308 }
309};
310
311/// A dummy lowering for clock gates to an AND gate.
312struct ClockGateOpLowering : public OpConversionPattern<seq::ClockGateOp> {
313 using OpConversionPattern::OpConversionPattern;
314 LogicalResult
315 matchAndRewrite(seq::ClockGateOp op, OpAdaptor adaptor,
316 ConversionPatternRewriter &rewriter) const final {
317 rewriter.replaceOpWithNewOp<LLVM::AndOp>(op, adaptor.getInput(),
318 adaptor.getEnable());
319 return success();
320 }
321};
322
323/// Lower 'seq.clock_inv x' to 'llvm.xor x true'
324struct ClockInvOpLowering : public OpConversionPattern<seq::ClockInverterOp> {
325 using OpConversionPattern::OpConversionPattern;
326 LogicalResult
327 matchAndRewrite(seq::ClockInverterOp op, OpAdaptor adaptor,
328 ConversionPatternRewriter &rewriter) const final {
329 auto constTrue = LLVM::ConstantOp::create(rewriter, op->getLoc(),
330 rewriter.getI1Type(), 1);
331 rewriter.replaceOpWithNewOp<LLVM::XOrOp>(op, adaptor.getInput(), constTrue);
332 return success();
333 }
334};
335
336struct ZeroCountOpLowering : public OpConversionPattern<arc::ZeroCountOp> {
337 using OpConversionPattern::OpConversionPattern;
338 LogicalResult
339 matchAndRewrite(arc::ZeroCountOp op, OpAdaptor adaptor,
340 ConversionPatternRewriter &rewriter) const override {
341 // Use poison when input is zero.
342 IntegerAttr isZeroPoison = rewriter.getBoolAttr(true);
343
344 if (op.getPredicate() == arc::ZeroCountPredicate::leading) {
345 rewriter.replaceOpWithNewOp<LLVM::CountLeadingZerosOp>(
346 op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison);
347 return success();
348 }
349
350 rewriter.replaceOpWithNewOp<LLVM::CountTrailingZerosOp>(
351 op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison);
352 return success();
353 }
354};
355
356struct SeqConstClockLowering : public OpConversionPattern<seq::ConstClockOp> {
357 using OpConversionPattern::OpConversionPattern;
358 LogicalResult
359 matchAndRewrite(seq::ConstClockOp op, OpAdaptor adaptor,
360 ConversionPatternRewriter &rewriter) const override {
361 rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(
362 op, rewriter.getI1Type(), static_cast<int64_t>(op.getValue()));
363 return success();
364 }
365};
366
367template <typename OpTy>
368struct ReplaceOpWithInputPattern : public OpConversionPattern<OpTy> {
370 using OpAdaptor = typename OpTy::Adaptor;
371 LogicalResult
372 matchAndRewrite(OpTy op, OpAdaptor adaptor,
373 ConversionPatternRewriter &rewriter) const override {
374 rewriter.replaceOp(op, adaptor.getInput());
375 return success();
376 }
377};
378
379} // namespace
380
381//===----------------------------------------------------------------------===//
382// Simulation Orchestration Lowering Patterns
383//===----------------------------------------------------------------------===//
384
385namespace {
386
387struct ModelInfoMap {
388 size_t numStateBytes;
389 llvm::DenseMap<StringRef, StateInfo> states;
390 mlir::FlatSymbolRefAttr initialFnSymbol;
391 mlir::FlatSymbolRefAttr finalFnSymbol;
392};
393
394template <typename OpTy>
395struct ModelAwarePattern : public OpConversionPattern<OpTy> {
396 ModelAwarePattern(const TypeConverter &typeConverter, MLIRContext *context,
397 llvm::DenseMap<StringRef, ModelInfoMap> &modelInfo)
398 : OpConversionPattern<OpTy>(typeConverter, context),
399 modelInfo(modelInfo) {}
400
401protected:
402 Value createPtrToPortState(ConversionPatternRewriter &rewriter, Location loc,
403 Value state, const StateInfo &port) const {
404 MLIRContext *ctx = rewriter.getContext();
405 return LLVM::GEPOp::create(rewriter, loc, LLVM::LLVMPointerType::get(ctx),
406 IntegerType::get(ctx, 8), state,
407 LLVM::GEPArg(port.offset));
408 }
409
410 llvm::DenseMap<StringRef, ModelInfoMap> &modelInfo;
411};
412
413/// Lowers SimInstantiateOp to a malloc and memset call. This pattern will
414/// mutate the global module.
415struct SimInstantiateOpLowering
416 : public ModelAwarePattern<arc::SimInstantiateOp> {
417 using ModelAwarePattern::ModelAwarePattern;
418
419 LogicalResult
420 matchAndRewrite(arc::SimInstantiateOp op, OpAdaptor adaptor,
421 ConversionPatternRewriter &rewriter) const final {
422 auto modelIt = modelInfo.find(
423 cast<SimModelInstanceType>(op.getBody().getArgument(0).getType())
424 .getModel()
425 .getValue());
426 ModelInfoMap &model = modelIt->second;
427
428 bool useRuntime = op.getRuntimeModel().has_value();
429
430 ModuleOp moduleOp = op->getParentOfType<ModuleOp>();
431 if (!moduleOp)
432 return failure();
433
434 ConversionPatternRewriter::InsertionGuard guard(rewriter);
435
436 // FIXME: like the rest of MLIR, this assumes sizeof(intptr_t) ==
437 // sizeof(size_t) on the target architecture.
438 Type convertedIndex = typeConverter->convertType(rewriter.getIndexType());
439 Location loc = op.getLoc();
440 Value allocated;
441
442 if (useRuntime) {
443 // The instance is using the runtime library
444 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
445
446 Value runtimeArgs;
447 // If present, materialize the runtime argument string on the stack
448 if (op.getRuntimeArgs().has_value()) {
449 SmallVector<int8_t> argStringVec(op.getRuntimeArgsAttr().begin(),
450 op.getRuntimeArgsAttr().end());
451 argStringVec.push_back('\0');
452 auto strAttr = mlir::DenseElementsAttr::get(
453 mlir::RankedTensorType::get({(int64_t)argStringVec.size()},
454 rewriter.getI8Type()),
455 llvm::ArrayRef(argStringVec));
456
457 auto arrayCst = LLVM::ConstantOp::create(
458 rewriter, loc,
459 LLVM::LLVMArrayType::get(rewriter.getI8Type(), argStringVec.size()),
460 strAttr);
461 auto cst1 = LLVM::ConstantOp::create(rewriter, loc,
462 rewriter.getI32IntegerAttr(1));
463 runtimeArgs = LLVM::AllocaOp::create(rewriter, loc, ptrTy,
464 arrayCst.getType(), cst1);
465 LLVM::LifetimeStartOp::create(rewriter, loc, runtimeArgs);
466 LLVM::StoreOp::create(rewriter, loc, arrayCst, runtimeArgs);
467 } else {
468 runtimeArgs = LLVM::ZeroOp::create(rewriter, loc, ptrTy).getResult();
469 }
470 // Call the state allocation function
471 auto rtModelPtr = LLVM::AddressOfOp::create(rewriter, loc, ptrTy,
472 op.getRuntimeModelAttr())
473 .getResult();
474 allocated =
475 LLVM::CallOp::create(rewriter, loc, {ptrTy},
476 runtime::APICallbacks::symNameAllocInstance,
477 {rtModelPtr, runtimeArgs})
478 .getResult();
479
480 if (op.getRuntimeArgs().has_value())
481 LLVM::LifetimeEndOp::create(rewriter, loc, runtimeArgs);
482
483 } else {
484 // The instance is not using the runtime library
485 FailureOr<LLVM::LLVMFuncOp> mallocFunc =
486 LLVM::lookupOrCreateMallocFn(rewriter, moduleOp, convertedIndex);
487 if (failed(mallocFunc))
488 return mallocFunc;
489
490 Value numStateBytes = LLVM::ConstantOp::create(
491 rewriter, loc, convertedIndex, model.numStateBytes);
492 allocated = LLVM::CallOp::create(rewriter, loc, mallocFunc.value(),
493 ValueRange{numStateBytes})
494 .getResult();
495 Value zero =
496 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI8Type(), 0);
497 LLVM::MemsetOp::create(rewriter, loc, allocated, zero, numStateBytes,
498 false);
499 }
500
501 // Call the model's 'initial' function if present.
502 if (model.initialFnSymbol) {
503 auto initialFnType = LLVM::LLVMFunctionType::get(
504 LLVM::LLVMVoidType::get(op.getContext()),
505 {LLVM::LLVMPointerType::get(op.getContext())});
506 LLVM::CallOp::create(rewriter, loc, initialFnType, model.initialFnSymbol,
507 ValueRange{allocated});
508 }
509
510 // Call the runtime's 'onInitialized' function if present.
511 if (useRuntime)
512 LLVM::CallOp::create(rewriter, loc, TypeRange{},
513 runtime::APICallbacks::symNameOnInitialized,
514 {allocated});
515
516 // Execute the body.
517 rewriter.inlineBlockBefore(&adaptor.getBody().getBlocks().front(), op,
518 {allocated});
519
520 // Call the model's 'final' function if present.
521 if (model.finalFnSymbol) {
522 auto finalFnType = LLVM::LLVMFunctionType::get(
523 LLVM::LLVMVoidType::get(op.getContext()),
524 {LLVM::LLVMPointerType::get(op.getContext())});
525 LLVM::CallOp::create(rewriter, loc, finalFnType, model.finalFnSymbol,
526 ValueRange{allocated});
527 }
528
529 if (useRuntime) {
530 LLVM::CallOp::create(rewriter, loc, TypeRange{},
531 runtime::APICallbacks::symNameDeleteInstance,
532 {allocated});
533 } else {
534 FailureOr<LLVM::LLVMFuncOp> freeFunc =
535 LLVM::lookupOrCreateFreeFn(rewriter, moduleOp);
536 if (failed(freeFunc))
537 return freeFunc;
538
539 LLVM::CallOp::create(rewriter, loc, freeFunc.value(),
540 ValueRange{allocated});
541 }
542
543 rewriter.eraseOp(op);
544 return success();
545 }
546};
547
548struct SimSetInputOpLowering : public ModelAwarePattern<arc::SimSetInputOp> {
549 using ModelAwarePattern::ModelAwarePattern;
550
551 LogicalResult
552 matchAndRewrite(arc::SimSetInputOp op, OpAdaptor adaptor,
553 ConversionPatternRewriter &rewriter) const final {
554 auto modelIt =
555 modelInfo.find(cast<SimModelInstanceType>(op.getInstance().getType())
556 .getModel()
557 .getValue());
558 ModelInfoMap &model = modelIt->second;
559
560 auto portIt = model.states.find(op.getInput());
561 if (portIt == model.states.end()) {
562 // If the port is not found in the state, it means the model does not
563 // actually use it. Thus this operation is a no-op.
564 rewriter.eraseOp(op);
565 return success();
566 }
567
568 StateInfo &port = portIt->second;
569 Value statePtr = createPtrToPortState(rewriter, op.getLoc(),
570 adaptor.getInstance(), port);
571 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getValue(),
572 statePtr);
573
574 return success();
575 }
576};
577
578struct SimGetPortOpLowering : public ModelAwarePattern<arc::SimGetPortOp> {
579 using ModelAwarePattern::ModelAwarePattern;
580
581 LogicalResult
582 matchAndRewrite(arc::SimGetPortOp op, OpAdaptor adaptor,
583 ConversionPatternRewriter &rewriter) const final {
584 auto modelIt =
585 modelInfo.find(cast<SimModelInstanceType>(op.getInstance().getType())
586 .getModel()
587 .getValue());
588 ModelInfoMap &model = modelIt->second;
589
590 auto type = typeConverter->convertType(op.getValue().getType());
591 if (!type)
592 return failure();
593 auto portIt = model.states.find(op.getPort());
594 if (portIt == model.states.end()) {
595 // If the port is not found in the state, it means the model does not
596 // actually set it. Thus this operation returns 0.
597 rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(op, type, 0);
598 return success();
599 }
600
601 StateInfo &port = portIt->second;
602 Value statePtr = createPtrToPortState(rewriter, op.getLoc(),
603 adaptor.getInstance(), port);
604 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, type, statePtr);
605
606 return success();
607 }
608};
609
610struct SimStepOpLowering : public ModelAwarePattern<arc::SimStepOp> {
611 using ModelAwarePattern::ModelAwarePattern;
612
613 LogicalResult
614 matchAndRewrite(arc::SimStepOp op, OpAdaptor adaptor,
615 ConversionPatternRewriter &rewriter) const final {
616 StringRef modelName = cast<SimModelInstanceType>(op.getInstance().getType())
617 .getModel()
618 .getValue();
619
620 if (adaptor.getTimePostIncrement()) {
621 // Increment time after step
622 OpBuilder::InsertionGuard g(rewriter);
623 rewriter.setInsertionPointAfter(op);
624 auto oldTime =
625 arc::SimGetTimeOp::create(rewriter, op.getLoc(), op.getInstance());
626 auto newTime = LLVM::AddOp::create(rewriter, op.getLoc(), oldTime,
627 adaptor.getTimePostIncrement());
628 arc::SimSetTimeOp::create(rewriter, op.getLoc(), op.getInstance(),
629 newTime);
630 }
631
632 StringAttr evalFunc =
633 rewriter.getStringAttr(evalSymbolFromModelName(modelName));
634 rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, mlir::TypeRange(), evalFunc,
635 adaptor.getInstance());
636
637 return success();
638 }
639};
640
641// Loads the simulation time (i64 femtoseconds) from byte offset 0 in the
642// model instance's state storage.
643struct SimGetTimeOpLowering : public OpConversionPattern<arc::SimGetTimeOp> {
644 using OpConversionPattern::OpConversionPattern;
645
646 LogicalResult
647 matchAndRewrite(arc::SimGetTimeOp op, OpAdaptor adaptor,
648 ConversionPatternRewriter &rewriter) const final {
649 // Time is stored at offset 0 in the instance storage.
650 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, rewriter.getI64Type(),
651 adaptor.getInstance());
652 return success();
653 }
654};
655
656// Stores the simulation time (i64 femtoseconds) to byte offset 0 in the
657// model instance's state storage.
658struct SimSetTimeOpLowering : public OpConversionPattern<arc::SimSetTimeOp> {
659 using OpConversionPattern::OpConversionPattern;
660
661 LogicalResult
662 matchAndRewrite(arc::SimSetTimeOp op, OpAdaptor adaptor,
663 ConversionPatternRewriter &rewriter) const final {
664 // Time is stored at offset 0 in the instance storage.
665 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(op, adaptor.getTime(),
666 adaptor.getInstance());
667 return success();
668 }
669};
670
671// Global string constants in the module.
672class StringCache {
673public:
674 Value getOrCreate(OpBuilder &b, StringRef formatStr) {
675 auto it = cache.find(formatStr);
676 if (it != cache.end()) {
677 return LLVM::AddressOfOp::create(b, b.getUnknownLoc(), it->second);
678 }
679
680 Location loc = b.getUnknownLoc();
681 LLVM::GlobalOp global;
682 {
683 OpBuilder::InsertionGuard guard(b);
684 ModuleOp m =
685 b.getInsertionBlock()->getParent()->getParentOfType<ModuleOp>();
686 b.setInsertionPointToStart(m.getBody());
687
688 SmallVector<char> strVec(formatStr.begin(), formatStr.end());
689 strVec.push_back(0);
690
691 auto name = llvm::formatv("_arc_str_{0}", cache.size()).str();
692 auto globalType = LLVM::LLVMArrayType::get(b.getI8Type(), strVec.size());
693 global = LLVM::GlobalOp::create(b, loc, globalType, /*isConstant=*/true,
694 LLVM::Linkage::Internal,
695 /*name=*/name, b.getStringAttr(strVec),
696 /*alignment=*/0);
697 }
698
699 cache[formatStr] = global;
700 return LLVM::AddressOfOp::create(b, loc, global);
701 }
702
703private:
704 llvm::StringMap<LLVM::GlobalOp> cache;
705};
706
707FailureOr<LLVM::CallOp> emitPrintfCall(OpBuilder &builder, Location loc,
708 StringCache &cache, StringRef formatStr,
709 ValueRange args) {
710 ModuleOp moduleOp =
711 builder.getInsertionBlock()->getParent()->getParentOfType<ModuleOp>();
712 // Lookup or create printf function symbol.
713 MLIRContext *ctx = builder.getContext();
714 auto printfFunc = LLVM::lookupOrCreateFn(builder, moduleOp, "printf",
715 LLVM::LLVMPointerType::get(ctx),
716 LLVM::LLVMVoidType::get(ctx), true);
717 if (failed(printfFunc))
718 return printfFunc;
719
720 Value formatStrPtr = cache.getOrCreate(builder, formatStr);
721 SmallVector<Value> argsVec(1, formatStrPtr);
722 argsVec.append(args.begin(), args.end());
723 return LLVM::CallOp::create(builder, loc, printfFunc.value(), argsVec);
724}
725
726/// Lowers SimEmitValueOp to a printf call. The integer will be printed in its
727/// entirety if it is of size up to size_t, and explicitly truncated otherwise.
728/// This pattern will mutate the global module.
729struct SimEmitValueOpLowering
730 : public OpConversionPattern<arc::SimEmitValueOp> {
731 SimEmitValueOpLowering(const TypeConverter &typeConverter,
732 MLIRContext *context, StringCache &formatStringCache)
733 : OpConversionPattern(typeConverter, context),
734 formatStringCache(formatStringCache) {}
735
736 LogicalResult
737 matchAndRewrite(arc::SimEmitValueOp op, OpAdaptor adaptor,
738 ConversionPatternRewriter &rewriter) const final {
739 auto valueType = dyn_cast<IntegerType>(adaptor.getValue().getType());
740 if (!valueType)
741 return failure();
742
743 Location loc = op.getLoc();
744
745 ModuleOp moduleOp = op->getParentOfType<ModuleOp>();
746 if (!moduleOp)
747 return failure();
748
749 SmallVector<Value> printfVariadicArgs;
750 SmallString<16> printfFormatStr;
751 int remainingBits = valueType.getWidth();
752 Value value = adaptor.getValue();
753
754 // Assumes the target platform uses 64bit for long long ints (%llx
755 // formatter).
756 constexpr llvm::StringRef intFormatter = "llx";
757 auto intType = IntegerType::get(getContext(), 64);
758 Value shiftValue = LLVM::ConstantOp::create(
759 rewriter, loc, rewriter.getIntegerAttr(valueType, intType.getWidth()));
760
761 if (valueType.getWidth() < intType.getWidth()) {
762 int width = llvm::divideCeil(valueType.getWidth(), 4);
763 printfFormatStr = llvm::formatv("%0{0}{1}", width, intFormatter);
764 printfVariadicArgs.push_back(
765 LLVM::ZExtOp::create(rewriter, loc, intType, value));
766 } else {
767 // Process the value in 64 bit chunks, starting from the least significant
768 // bits. Since we append chunks in low-to-high order, we reverse the
769 // vector to print them in the correct high-to-low order.
770 int otherChunkWidth = intType.getWidth() / 4;
771 int firstChunkWidth =
772 llvm::divideCeil(valueType.getWidth() % intType.getWidth(), 4);
773 if (firstChunkWidth == 0) { // print the full 64-bit hex or a subset.
774 firstChunkWidth = otherChunkWidth;
775 }
776
777 std::string firstChunkFormat =
778 llvm::formatv("%0{0}{1}", firstChunkWidth, intFormatter);
779 std::string otherChunkFormat =
780 llvm::formatv("%0{0}{1}", otherChunkWidth, intFormatter);
781
782 for (int i = 0; remainingBits > 0; ++i) {
783 // Append 64-bit chunks to the printf arguments, in low-to-high
784 // order. The integer is printed in hex format with zero padding.
785 printfVariadicArgs.push_back(
786 LLVM::TruncOp::create(rewriter, loc, intType, value));
787
788 // Zero-padded format specifier for fixed width, e.g. %01llx for 4 bits.
789 printfFormatStr.append(i == 0 ? firstChunkFormat : otherChunkFormat);
790
791 value =
792 LLVM::LShrOp::create(rewriter, loc, value, shiftValue).getResult();
793 remainingBits -= intType.getWidth();
794 }
795 }
796
797 std::reverse(printfVariadicArgs.begin(), printfVariadicArgs.end());
798
799 SmallString<16> formatStr = adaptor.getValueName();
800 formatStr.append(" = ");
801 formatStr.append(printfFormatStr);
802 formatStr.append("\n");
803
804 auto callOp = emitPrintfCall(rewriter, op->getLoc(), formatStringCache,
805 formatStr, printfVariadicArgs);
806 if (failed(callOp))
807 return failure();
808 rewriter.replaceOp(op, *callOp);
809
810 return success();
811 }
812
813 StringCache &formatStringCache;
814};
815
816//===----------------------------------------------------------------------===//
817// `sim` dialect lowerings
818//===----------------------------------------------------------------------===//
819
820// Helper struct to hold the format string and arguments for arcRuntimeFormat.
821struct FormatInfo {
822 SmallVector<FmtDescriptor> descriptors;
823 SmallVector<Value> args;
824};
825
826// Copies the given integer value into an alloca, returning a pointer to it.
827//
828// The alloca is rounded up to a 64-bit boundary and is written as little-endian
829// words of size 64-bits, to be compatible with the constructor of APInt.
830static Value reg2mem(ConversionPatternRewriter &rewriter, Location loc,
831 Value value) {
832 // Round up the type size to a 64-bit boundary.
833 int64_t origBitwidth = cast<IntegerType>(value.getType()).getWidth();
834 int64_t bitwidth = llvm::divideCeil(origBitwidth, 64) * 64;
835 int64_t numWords = bitwidth / 64;
836
837 // Create an alloca for the rounded up type.
838 LLVM::ConstantOp alloca_size =
839 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), numWords);
840 auto ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
841 auto allocaOp = LLVM::AllocaOp::create(rewriter, loc, ptrType,
842 rewriter.getI64Type(), alloca_size);
843 LLVM::LifetimeStartOp::create(rewriter, loc, allocaOp);
844
845 // Copy `value` into the alloca, 64-bits at a time from the least significant
846 // bits first.
847 for (int64_t wordIdx = 0; wordIdx < numWords; ++wordIdx) {
848 Value cst = LLVM::ConstantOp::create(
849 rewriter, loc, rewriter.getIntegerType(origBitwidth), wordIdx * 64);
850 Value v = LLVM::LShrOp::create(rewriter, loc, value, cst);
851 if (origBitwidth > 64) {
852 v = LLVM::TruncOp::create(rewriter, loc, rewriter.getI64Type(), v);
853 } else if (origBitwidth < 64) {
854 v = LLVM::ZExtOp::create(rewriter, loc, rewriter.getI64Type(), v);
855 }
856 Value gep = LLVM::GEPOp::create(rewriter, loc, ptrType,
857 rewriter.getI64Type(), allocaOp, {wordIdx});
858 LLVM::StoreOp::create(rewriter, loc, v, gep);
859 }
860
861 return allocaOp;
862}
863
864// Statically folds a value of type sim::FormatStringType to a FormatInfo.
865static FailureOr<FormatInfo>
866foldFormatString(ConversionPatternRewriter &rewriter, Value fstringValue,
867 StringCache &cache) {
868 Operation *op = fstringValue.getDefiningOp();
869 return llvm::TypeSwitch<Operation *, FailureOr<FormatInfo>>(op)
870 .Case<sim::FormatCharOp>(
871 [&](sim::FormatCharOp op) -> FailureOr<FormatInfo> {
872 FmtDescriptor d = FmtDescriptor::createChar();
873 return FormatInfo{{d}, {op.getValue()}};
874 })
875 .Case<sim::FormatDecOp>([&](sim::FormatDecOp op)
876 -> FailureOr<FormatInfo> {
877 FmtDescriptor d = FmtDescriptor::createInt(
878 op.getValue().getType().getWidth(), 10, op.getIsLeftAligned(),
879 op.getSpecifierWidth().value_or(-1), false, op.getIsSigned());
880 return FormatInfo{{d}, {reg2mem(rewriter, op.getLoc(), op.getValue())}};
881 })
882 .Case<sim::FormatHexOp>([&](sim::FormatHexOp op)
883 -> FailureOr<FormatInfo> {
884 FmtDescriptor d = FmtDescriptor::createInt(
885 op.getValue().getType().getWidth(), 16, op.getIsLeftAligned(),
886 op.getSpecifierWidth().value_or(-1), op.getIsHexUppercase(), false);
887 return FormatInfo{{d}, {reg2mem(rewriter, op.getLoc(), op.getValue())}};
888 })
889 .Case<sim::FormatOctOp>([&](sim::FormatOctOp op)
890 -> FailureOr<FormatInfo> {
891 FmtDescriptor d = FmtDescriptor::createInt(
892 op.getValue().getType().getWidth(), 8, op.getIsLeftAligned(),
893 op.getSpecifierWidth().value_or(-1), false, false);
894 return FormatInfo{{d}, {reg2mem(rewriter, op.getLoc(), op.getValue())}};
895 })
896 .Case<sim::FormatLiteralOp>(
897 [&](sim::FormatLiteralOp op) -> FailureOr<FormatInfo> {
898 if (op.getLiteral().size() < 8 &&
899 op.getLiteral().find('\0') == StringRef::npos) {
900 // We can use the small string optimization.
901 FmtDescriptor d =
902 FmtDescriptor::createSmallLiteral(op.getLiteral());
903 return FormatInfo{{d}, {}};
904 }
905 FmtDescriptor d =
906 FmtDescriptor::createLiteral(op.getLiteral().size());
907 Value value = cache.getOrCreate(rewriter, op.getLiteral());
908 return FormatInfo{{d}, {value}};
909 })
910 .Case<sim::FormatStringConcatOp>(
911 [&](sim::FormatStringConcatOp op) -> FailureOr<FormatInfo> {
912 auto fmt = foldFormatString(rewriter, op.getInputs()[0], cache);
913 if (failed(fmt))
914 return failure();
915 for (auto input : op.getInputs().drop_front()) {
916 auto next = foldFormatString(rewriter, input, cache);
917 if (failed(next))
918 return failure();
919 fmt->descriptors.append(next->descriptors);
920 fmt->args.append(next->args);
921 }
922 return fmt;
923 })
924 .Default(
925 [](Operation *op) -> FailureOr<FormatInfo> { return failure(); });
926}
927
928FailureOr<LLVM::CallOp> emitFmtCall(OpBuilder &builder, Location loc,
929 StringCache &stringCache,
930 ArrayRef<FmtDescriptor> descriptors,
931 ValueRange args) {
932 ModuleOp moduleOp =
933 builder.getInsertionBlock()->getParent()->getParentOfType<ModuleOp>();
934 // Lookup or create the arcRuntimeFormat function symbol.
935 MLIRContext *ctx = builder.getContext();
936 auto func = LLVM::lookupOrCreateFn(
937 builder, moduleOp, runtime::APICallbacks::symNameFormat,
938 LLVM::LLVMPointerType::get(ctx), LLVM::LLVMVoidType::get(ctx), true);
939 if (failed(func))
940 return func;
941
942 StringRef rawDescriptors(reinterpret_cast<const char *>(descriptors.data()),
943 descriptors.size() * sizeof(FmtDescriptor));
944 Value fmtPtr = stringCache.getOrCreate(builder, rawDescriptors);
945
946 SmallVector<Value> argsVec(1, fmtPtr);
947 argsVec.append(args.begin(), args.end());
948 auto result = LLVM::CallOp::create(builder, loc, func.value(), argsVec);
949
950 for (Value arg : args) {
951 Operation *definingOp = arg.getDefiningOp();
952 if (auto alloca = dyn_cast_if_present<LLVM::AllocaOp>(definingOp)) {
953 LLVM::LifetimeEndOp::create(builder, loc, arg);
954 }
955 }
956
957 return result;
958}
959
960struct SimPrintFormattedProcOpLowering
961 : public OpConversionPattern<sim::PrintFormattedProcOp> {
962 SimPrintFormattedProcOpLowering(const TypeConverter &typeConverter,
963 MLIRContext *context,
964 StringCache &stringCache)
965 : OpConversionPattern<sim::PrintFormattedProcOp>(typeConverter, context),
966 stringCache(stringCache) {}
967
968 LogicalResult
969 matchAndRewrite(sim::PrintFormattedProcOp op, OpAdaptor adaptor,
970 ConversionPatternRewriter &rewriter) const override {
971 auto formatInfo = foldFormatString(rewriter, op.getInput(), stringCache);
972 if (failed(formatInfo))
973 return rewriter.notifyMatchFailure(op, "unsupported format string");
974
975 // Add the end descriptor.
976 formatInfo->descriptors.push_back(FmtDescriptor());
977
978 auto result = emitFmtCall(rewriter, op.getLoc(), stringCache,
979 formatInfo->descriptors, formatInfo->args);
980 if (failed(result))
981 return failure();
982 rewriter.replaceOp(op, result.value());
983
984 return success();
985 }
986
987 StringCache &stringCache;
988};
989
990} // namespace
991
992static LogicalResult convert(arc::ExecuteOp op, arc::ExecuteOp::Adaptor adaptor,
993 ConversionPatternRewriter &rewriter,
994 const TypeConverter &converter) {
995 // Convert the argument types in the body blocks.
996 if (failed(rewriter.convertRegionTypes(&op.getBody(), converter)))
997 return failure();
998
999 // Split the block at the current insertion point such that we can branch into
1000 // the `arc.execute` body region, and have `arc.output` branch back to the
1001 // point after the `arc.execute`.
1002 auto *blockBefore = rewriter.getInsertionBlock();
1003 auto *blockAfter =
1004 rewriter.splitBlock(blockBefore, rewriter.getInsertionPoint());
1005
1006 // Branch to the entry block.
1007 rewriter.setInsertionPointToEnd(blockBefore);
1008 mlir::cf::BranchOp::create(rewriter, op.getLoc(), &op.getBody().front(),
1009 adaptor.getInputs());
1010
1011 // Make all `arc.output` terminators branch to the block after the
1012 // `arc.execute` op.
1013 for (auto &block : op.getBody()) {
1014 auto outputOp = dyn_cast<arc::OutputOp>(block.getTerminator());
1015 if (!outputOp)
1016 continue;
1017 rewriter.setInsertionPointToEnd(&block);
1018 rewriter.replaceOpWithNewOp<mlir::cf::BranchOp>(outputOp, blockAfter,
1019 outputOp.getOperands());
1020 }
1021
1022 // Inline the body region between the before and after blocks.
1023 rewriter.inlineRegionBefore(op.getBody(), blockAfter);
1024
1025 // Add arguments to the block after the `arc.execute`, replace the op's
1026 // results with the arguments, then perform block signature conversion.
1027 SmallVector<Value> args;
1028 args.reserve(op.getNumResults());
1029 for (auto result : op.getResults())
1030 args.push_back(blockAfter->addArgument(result.getType(), result.getLoc()));
1031 rewriter.replaceOp(op, args);
1032 auto conversion = converter.convertBlockSignature(blockAfter);
1033 if (!conversion)
1034 return failure();
1035 rewriter.applySignatureConversion(blockAfter, *conversion, &converter);
1036 return success();
1037}
1038
1039//===----------------------------------------------------------------------===//
1040// Runtime Implementation
1041//===----------------------------------------------------------------------===//
1042
1043template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
1044static LLVM::GlobalOp
1045buildGlobalConstantIntArray(OpBuilder &builder, Location loc, Twine symName,
1046 SmallVectorImpl<T> &data,
1047 unsigned alignment = alignof(T)) {
1048 auto intType = builder.getIntegerType(8 * sizeof(T));
1049 Attribute denseAttr = mlir::DenseElementsAttr::get(
1050 mlir::RankedTensorType::get({(int64_t)data.size()}, intType),
1051 llvm::ArrayRef(data));
1052 auto globalOp = LLVM::GlobalOp::create(
1053 builder, loc, LLVM::LLVMArrayType::get(intType, data.size()),
1054 /*isConstant=*/true, LLVM::Linkage::Internal,
1055 builder.getStringAttr(symName), denseAttr);
1056 globalOp.setAlignmentAttr(builder.getI64IntegerAttr(alignment));
1057 return globalOp;
1058}
1059
1060// Construct a raw constant byte array from a vector of struct values
1061template <typename T>
1062static LLVM::GlobalOp
1063buildGlobalConstantRuntimeStructArray(OpBuilder &builder, Location loc,
1064 Twine symName,
1065 SmallVectorImpl<T> &array) {
1066 assert(!array.empty());
1067 static_assert(std::is_standard_layout<T>(),
1068 "Runtime struct must have standard layout");
1069 int64_t numBytes = sizeof(T) * array.size();
1070 Attribute denseAttr = mlir::DenseElementsAttr::get(
1071 mlir::RankedTensorType::get({numBytes}, builder.getI8Type()),
1072 llvm::ArrayRef(reinterpret_cast<uint8_t *>(array.data()), numBytes));
1073 auto globalOp = LLVM::GlobalOp::create(
1074 builder, loc, LLVM::LLVMArrayType::get(builder.getI8Type(), numBytes),
1075 /*isConstant=*/true, LLVM::Linkage::Internal,
1076 builder.getStringAttr(symName), denseAttr, alignof(T));
1077 return globalOp;
1078}
1079
1081 : public OpConversionPattern<arc::RuntimeModelOp> {
1082 using OpConversionPattern::OpConversionPattern;
1083
1084 static constexpr uint64_t runtimeApiVersion = ARC_RUNTIME_API_VERSION;
1085
1086 // Build the constant ArcModelTraceInfo struct and its members
1087 LLVM::GlobalOp
1088 buildTraceInfoStruct(arc::RuntimeModelOp &op,
1089 ConversionPatternRewriter &rewriter) const {
1090 if (!op.getTraceTaps().has_value() || op.getTraceTaps()->empty())
1091 return {};
1092 // Construct the array of tap names/aliases
1093 SmallVector<char> namesArray;
1094 SmallVector<ArcTraceTap> tapArray;
1095 tapArray.reserve(op.getTraceTaps()->size());
1096 for (auto attr : op.getTraceTapsAttr()) {
1097 auto tap = cast<TraceTapAttr>(attr);
1098 assert(!tap.getNames().empty() &&
1099 "Expected trace tap to have at least one name");
1100 for (auto alias : tap.getNames()) {
1101 auto aliasStr = cast<StringAttr>(alias);
1102 namesArray.append(aliasStr.begin(), aliasStr.end());
1103 namesArray.push_back('\0');
1104 }
1105 ArcTraceTap tapStruct;
1106 tapStruct.stateOffset = tap.getStateOffset();
1107 tapStruct.nameOffset = namesArray.size() - 1;
1108 tapStruct.typeBits = tap.getSigType().getValue().getIntOrFloatBitWidth();
1109 tapStruct.reserved = 0;
1110 tapArray.emplace_back(tapStruct);
1111 }
1112 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1113 auto namesGlobal = buildGlobalConstantIntArray(
1114 rewriter, op.getLoc(), "_arc_tap_names_" + op.getName(), namesArray);
1115 auto traceTapsArrayGlobal = buildGlobalConstantRuntimeStructArray(
1116 rewriter, op.getLoc(), "_arc_trace_taps_" + op.getName(), tapArray);
1117
1118 //
1119 // struct ArcModelTraceInfo {
1120 // uint64_t numTraceTaps;
1121 // struct ArcTraceTap *traceTaps;
1122 // const char *traceTapNames;
1123 // uint64_t traceBufferCapacity;
1124 // };
1125 //
1126 auto traceInfoStructType = LLVM::LLVMStructType::getLiteral(
1127 getContext(),
1128 {rewriter.getI64Type(), ptrTy, ptrTy, rewriter.getI64Type()});
1129 static_assert(sizeof(ArcModelTraceInfo) == 32 &&
1130 "Unexpected size of ArcModelTraceInfo struct");
1131
1132 auto globalSymName =
1133 rewriter.getStringAttr("_arc_trace_info_" + op.getName());
1134 auto traceInfoGlobalOp = LLVM::GlobalOp::create(
1135 rewriter, op.getLoc(), traceInfoStructType,
1136 /*isConstant=*/false, LLVM::Linkage::Internal, globalSymName,
1137 Attribute{}, alignof(ArcModelTraceInfo));
1138 OpBuilder::InsertionGuard g(rewriter);
1139
1140 // Struct Initializer
1141 Region &initRegion = traceInfoGlobalOp.getInitializerRegion();
1142 Block *initBlock = rewriter.createBlock(&initRegion);
1143 rewriter.setInsertionPointToStart(initBlock);
1144
1145 auto numTraceTapsCst = LLVM::ConstantOp::create(
1146 rewriter, op.getLoc(), rewriter.getI64IntegerAttr(tapArray.size()));
1147 auto traceTapArrayAddr =
1148 LLVM::AddressOfOp::create(rewriter, op.getLoc(), traceTapsArrayGlobal);
1149 auto tapNameArrayAddr =
1150 LLVM::AddressOfOp::create(rewriter, op.getLoc(), namesGlobal);
1151 auto bufferCapacityCst = LLVM::ConstantOp::create(
1152 rewriter, op.getLoc(),
1153 rewriter.getI64IntegerAttr(runtime::defaultTraceBufferCapacity));
1154
1155 Value initStruct =
1156 LLVM::PoisonOp::create(rewriter, op.getLoc(), traceInfoStructType);
1157
1158 // Field: uint64_t numTraceTaps
1159 initStruct =
1160 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1161 numTraceTapsCst, ArrayRef<int64_t>{0});
1162 static_assert(offsetof(ArcModelTraceInfo, numTraceTaps) == 0,
1163 "Unexpected offset of field numTraceTaps");
1164 // Field: struct ArcTraceTap *traceTaps
1165 initStruct =
1166 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1167 traceTapArrayAddr, ArrayRef<int64_t>{1});
1168 static_assert(offsetof(ArcModelTraceInfo, traceTaps) == 8,
1169 "Unexpected offset of field traceTaps");
1170 // Field: const char *traceTapNames
1171 initStruct =
1172 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1173 tapNameArrayAddr, ArrayRef<int64_t>{2});
1174 static_assert(offsetof(ArcModelTraceInfo, traceTapNames) == 16,
1175 "Unexpected offset of field traceTapNames");
1176 // Field: uint64_t traceBufferCapacity
1177 initStruct =
1178 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1179 bufferCapacityCst, ArrayRef<int64_t>{3});
1180 static_assert(offsetof(ArcModelTraceInfo, traceBufferCapacity) == 24,
1181 "Unexpected offset of field traceBufferCapacity");
1182 LLVM::ReturnOp::create(rewriter, op.getLoc(), initStruct);
1183
1184 return traceInfoGlobalOp;
1185 }
1186
1187 // Create a global LLVM struct containing the RuntimeModel metadata
1188 LogicalResult
1189 matchAndRewrite(arc::RuntimeModelOp op, OpAdaptor adaptor,
1190 ConversionPatternRewriter &rewriter) const final {
1191
1192 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
1193 auto modelInfoStructType = LLVM::LLVMStructType::getLiteral(
1194 getContext(),
1195 {rewriter.getI64Type(), rewriter.getI64Type(), ptrTy, ptrTy});
1196 static_assert(sizeof(ArcRuntimeModelInfo) == 32 &&
1197 "Unexpected size of ArcRuntimeModelInfo struct");
1198
1199 rewriter.setInsertionPoint(op);
1200 auto traceInfoGlobal = buildTraceInfoStruct(op, rewriter);
1201
1202 // Construct the Model Name String GlobalOp
1203 SmallVector<char, 16> modNameArray(op.getName().begin(),
1204 op.getName().end());
1205 modNameArray.push_back('\0');
1206 auto nameGlobalType =
1207 LLVM::LLVMArrayType::get(rewriter.getI8Type(), modNameArray.size());
1208 auto globalSymName =
1209 rewriter.getStringAttr("_arc_mod_name_" + op.getName());
1210 auto nameGlobal = LLVM::GlobalOp::create(
1211 rewriter, op.getLoc(), nameGlobalType, /*isConstant=*/true,
1212 LLVM::Linkage::Internal,
1213 /*name=*/globalSymName, rewriter.getStringAttr(modNameArray),
1214 /*alignment=*/0);
1215
1216 // Construct the Model Info Struct GlobalOp
1217 // Note: The struct is supposed to be constant at runtime, but contains the
1218 // relocatable address of another symbol, so it should not be placed in the
1219 // "rodata" section.
1220 auto modInfoGlobalOp =
1221 LLVM::GlobalOp::create(rewriter, op.getLoc(), modelInfoStructType,
1222 /*isConstant=*/false, LLVM::Linkage::External,
1223 op.getSymName(), Attribute{});
1224
1225 // Struct Initializer
1226 Region &initRegion = modInfoGlobalOp.getInitializerRegion();
1227 Block *initBlock = rewriter.createBlock(&initRegion);
1228 rewriter.setInsertionPointToStart(initBlock);
1229 auto apiVersionCst = LLVM::ConstantOp::create(
1230 rewriter, op.getLoc(), rewriter.getI64IntegerAttr(runtimeApiVersion));
1231 auto numStateBytesCst = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1232 op.getNumStateBytesAttr());
1233 auto nameAddr =
1234 LLVM::AddressOfOp::create(rewriter, op.getLoc(), nameGlobal);
1235 Value traceInfoPtr;
1236 if (traceInfoGlobal)
1237 traceInfoPtr =
1238 LLVM::AddressOfOp::create(rewriter, op.getLoc(), traceInfoGlobal);
1239 else
1240 traceInfoPtr = LLVM::ZeroOp::create(rewriter, op.getLoc(), ptrTy);
1241
1242 Value initStruct =
1243 LLVM::PoisonOp::create(rewriter, op.getLoc(), modelInfoStructType);
1244
1245 // Field: uint64_t apiVersion
1246 initStruct = LLVM::InsertValueOp::create(
1247 rewriter, op.getLoc(), initStruct, apiVersionCst, ArrayRef<int64_t>{0});
1248 static_assert(offsetof(ArcRuntimeModelInfo, apiVersion) == 0,
1249 "Unexpected offset of field apiVersion");
1250 // Field: uint64_t numStateBytes
1251 initStruct =
1252 LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1253 numStateBytesCst, ArrayRef<int64_t>{1});
1254 static_assert(offsetof(ArcRuntimeModelInfo, numStateBytes) == 8,
1255 "Unexpected offset of field numStateBytes");
1256 // Field: const char *modelName
1257 initStruct = LLVM::InsertValueOp::create(rewriter, op.getLoc(), initStruct,
1258 nameAddr, ArrayRef<int64_t>{2});
1259 static_assert(offsetof(ArcRuntimeModelInfo, modelName) == 16,
1260 "Unexpected offset of field modelName");
1261 // Field: struct ArcModelTraceInfo *traceInfo
1262 initStruct = LLVM::InsertValueOp::create(
1263 rewriter, op.getLoc(), initStruct, traceInfoPtr, ArrayRef<int64_t>{3});
1264 static_assert(offsetof(ArcRuntimeModelInfo, traceInfo) == 24,
1265 "Unexpected offset of field traceInfo");
1266
1267 LLVM::ReturnOp::create(rewriter, op.getLoc(), initStruct);
1268
1269 rewriter.replaceOp(op, modInfoGlobalOp);
1270 return success();
1271 }
1272};
1273
1274//===----------------------------------------------------------------------===//
1275// Pass Implementation
1276//===----------------------------------------------------------------------===//
1277
1278namespace {
1279struct LowerArcToLLVMPass
1280 : public circt::impl::LowerArcToLLVMBase<LowerArcToLLVMPass> {
1281 void runOnOperation() override;
1282};
1283} // namespace
1284
1285void LowerArcToLLVMPass::runOnOperation() {
1286 // Replace any `i0` values with an `hw.constant 0 : i0` to avoid later issues
1287 // in LLVM conversion.
1288 {
1289 DenseMap<Region *, hw::ConstantOp> zeros;
1290 getOperation().walk([&](Operation *op) {
1291 if (op->hasTrait<OpTrait::ConstantLike>())
1292 return;
1293 for (auto result : op->getResults()) {
1294 auto type = dyn_cast<IntegerType>(result.getType());
1295 if (!type || type.getWidth() != 0)
1296 continue;
1297 auto *region = op->getParentRegion();
1298 auto &zero = zeros[region];
1299 if (!zero) {
1300 auto builder = OpBuilder::atBlockBegin(&region->front());
1301 zero = hw::ConstantOp::create(builder, result.getLoc(),
1302 APInt::getZero(0));
1303 }
1304 result.replaceAllUsesWith(zero);
1305 }
1306 });
1307 }
1308
1309 // Collect the symbols in the root op such that the HW-to-LLVM lowering can
1310 // create LLVM globals with non-colliding names.
1311 Namespace globals;
1312 SymbolCache cache;
1313 cache.addDefinitions(getOperation());
1314 globals.add(cache);
1315
1316 // Setup the conversion target. Explicitly mark `scf.yield` legal since it
1317 // does not have a conversion itself, which would cause it to fail
1318 // legalization and for the conversion to abort. (It relies on its parent op's
1319 // conversion to remove it.)
1320 LLVMConversionTarget target(getContext());
1321 target.addLegalOp<mlir::ModuleOp>();
1322 target.addLegalOp<scf::YieldOp>(); // quirk of SCF dialect conversion
1323
1324 // Mark sim::Format*Op as legal. These are not converted to LLVM, but the
1325 // lowering of sim::PrintFormattedOp walks them to build up its format string.
1326 // They are all marked Pure so are removed after the conversion.
1327 target.addLegalOp<sim::FormatLiteralOp, sim::FormatDecOp, sim::FormatHexOp,
1328 sim::FormatBinOp, sim::FormatOctOp, sim::FormatCharOp,
1329 sim::FormatStringConcatOp>();
1330
1331 // Setup the arc dialect type conversion.
1332 LLVMTypeConverter converter(&getContext());
1333 converter.addConversion([&](seq::ClockType type) {
1334 return IntegerType::get(type.getContext(), 1);
1335 });
1336 converter.addConversion([&](StorageType type) {
1337 return LLVM::LLVMPointerType::get(type.getContext());
1338 });
1339 converter.addConversion([&](MemoryType type) {
1340 return LLVM::LLVMPointerType::get(type.getContext());
1341 });
1342 converter.addConversion([&](StateType type) {
1343 return LLVM::LLVMPointerType::get(type.getContext());
1344 });
1345 converter.addConversion([&](SimModelInstanceType type) {
1346 return LLVM::LLVMPointerType::get(type.getContext());
1347 });
1348 converter.addConversion([&](sim::FormatStringType type) {
1349 return LLVM::LLVMPointerType::get(type.getContext());
1350 });
1351 converter.addConversion([&](llhd::TimeType type) {
1352 // LLHD time is represented as i64 femtoseconds.
1353 return IntegerType::get(type.getContext(), 64);
1354 });
1355
1356 // Setup the conversion patterns.
1357 ConversionPatternSet patterns(&getContext(), converter);
1358
1359 // MLIR patterns.
1360 populateSCFToControlFlowConversionPatterns(patterns);
1361 populateFuncToLLVMConversionPatterns(converter, patterns);
1362 cf::populateControlFlowToLLVMConversionPatterns(converter, patterns);
1363 arith::populateArithToLLVMConversionPatterns(converter, patterns);
1364 index::populateIndexToLLVMConversionPatterns(converter, patterns);
1365 populateAnyFunctionOpInterfaceTypeConversionPattern(patterns, converter);
1366
1367 // CIRCT patterns.
1368 DenseMap<std::pair<Type, ArrayAttr>, LLVM::GlobalOp> constAggregateGlobalsMap;
1370 std::optional<HWToLLVMArraySpillCache> spillCacheOpt =
1372 {
1373 OpBuilder spillBuilder(getOperation());
1374 spillCacheOpt->spillNonHWOps(spillBuilder, converter, getOperation());
1375 }
1376 populateHWToLLVMConversionPatterns(converter, patterns, globals,
1377 constAggregateGlobalsMap, spillCacheOpt);
1378
1381
1382 // Arc patterns.
1383 // clang-format off
1384 patterns.add<
1385 AllocMemoryOpLowering,
1386 AllocStateLikeOpLowering<arc::AllocStateOp>,
1387 AllocStateLikeOpLowering<arc::RootInputOp>,
1388 AllocStateLikeOpLowering<arc::RootOutputOp>,
1389 AllocStorageOpLowering,
1390 ClockGateOpLowering,
1391 ClockInvOpLowering,
1392 CurrentTimeOpLowering,
1393 IntToTimeOpLowering,
1394 MemoryReadOpLowering,
1395 MemoryWriteOpLowering,
1396 ModelOpLowering,
1397 ReplaceOpWithInputPattern<seq::ToClockOp>,
1398 ReplaceOpWithInputPattern<seq::FromClockOp>,
1400 SeqConstClockLowering,
1401 SimGetTimeOpLowering,
1402 SimSetTimeOpLowering,
1403 StateReadOpLowering,
1404 StateWriteOpLowering,
1405 StorageGetOpLowering,
1406 TimeToIntOpLowering,
1407 ZeroCountOpLowering
1408 >(converter, &getContext());
1409 // clang-format on
1410 patterns.add<ExecuteOp>(convert);
1411
1412 StringCache stringCache;
1413 patterns.add<SimEmitValueOpLowering, SimPrintFormattedProcOpLowering>(
1414 converter, &getContext(), stringCache);
1415
1416 auto &modelInfo = getAnalysis<ModelInfoAnalysis>();
1417 llvm::DenseMap<StringRef, ModelInfoMap> modelMap(modelInfo.infoMap.size());
1418 for (auto &[_, modelInfo] : modelInfo.infoMap) {
1419 llvm::DenseMap<StringRef, StateInfo> states(modelInfo.states.size());
1420 for (StateInfo &stateInfo : modelInfo.states)
1421 states.insert({stateInfo.name, stateInfo});
1422 modelMap.insert(
1423 {modelInfo.name,
1424 ModelInfoMap{modelInfo.numStateBytes, std::move(states),
1425 modelInfo.initialFnSym, modelInfo.finalFnSym}});
1426 }
1427
1428 patterns.add<SimInstantiateOpLowering, SimSetInputOpLowering,
1429 SimGetPortOpLowering, SimStepOpLowering>(
1430 converter, &getContext(), modelMap);
1431
1432 // Apply the conversion.
1433 ConversionConfig config;
1434 config.allowPatternRollback = false;
1435 if (failed(applyFullConversion(getOperation(), target, std::move(patterns),
1436 config)))
1437 signalPassFailure();
1438}
1439
1440std::unique_ptr<OperationPass<ModuleOp>> circt::createLowerArcToLLVMPass() {
1441 return std::make_unique<LowerArcToLLVMPass>();
1442}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static LLVM::GlobalOp buildGlobalConstantIntArray(OpBuilder &builder, Location loc, Twine symName, SmallVectorImpl< T > &data, unsigned alignment=alignof(T))
static LLVM::GlobalOp buildGlobalConstantRuntimeStructArray(OpBuilder &builder, Location loc, Twine symName, SmallVectorImpl< T > &array)
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:27
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
Definition sim.py:1
Static information for a compiled hardware model, generated by the MLIR lowering.
Definition Common.h:70
uint32_t typeBits
Bit width of the traced signal.
Definition TraceTaps.h:28
uint64_t stateOffset
Byte offset of the traced value within the model state.
Definition TraceTaps.h:23
uint64_t nameOffset
Byte offset to the null terminator of this signal's last alias in the names array.
Definition TraceTaps.h:26
uint32_t reserved
Padding and reserved for future use.
Definition TraceTaps.h:30
LLVM::GlobalOp buildTraceInfoStruct(arc::RuntimeModelOp &op, ConversionPatternRewriter &rewriter) const
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